使用envoy在k8s中作grpc的負載均衡


1. 為我們的應用創建headless服務

在Kubernetes中,有一種稱為headless服務的特定服務,恰好與Envoy的STRICT_DNS服務發現模式一起使用時非常方便。

Headless服務不會為底層Pod提供單個IP和負載平衡,而只是具有DNS配置,該配置為我們提供了一個A記錄,其中包含與標簽選擇器匹配的所有Pod的Pod IP地址。我們希望在實現負載平衡並自己維護與上游Pod的連接的情況下使用此服務類型,這正是我們使用Envoy可以做到的。

我們可以通過將.spec.clusterIP字段設置為“None”來創建headless服務。因此,假設我們的應用程序pod的標簽app的值為myapp,我們可以使用以下yaml創建headless服務。

現在,如果我們在Kubernetes集群中檢查服務的DNS記錄,我們將看到帶有IP地址的單獨的A記錄。如果我們有3個Pod,則會看到與此類似的DNS摘要。

$ nslookup myapp 
Server: 10.40.0.10 
Address: 10.40.0.10#53 

Non-authoritative answer: 
Name: myapp.namespace.svc.cluster.local Address: 10.36.224.5 
Name: myapp.namespace.svc.cluster.local Address: 10.38.187.17 
Name: myapp.namespace.svc.cluster.local Address: 10.38.1.8

Envoy的STRICT_DNS服務發現的工作方式是,它維護DNS返回的所有A記錄的IP地址,並且每隔幾秒鍾刷新一次IP組。

2. 創建Envoy鏡像

Dockerfile

FROM envoyproxy/envoy:latest

COPY envoy.yaml /tmpl/envoy.yaml.tmpl
COPY docker-entrypoint.sh /

RUN chmod 500 /docker-entrypoint.sh

RUN apt-get update && \
    apt-get install gettext -y && \
    rm -rf /var/lib/apt/list/* && \
    rm -rf /var/cache/apk/*

ENTRYPOINT ["/docker-entrypoint.sh"]

docker-entrypoint.sh

#!/bin/sh
set -e

echo "Generating envoy.yaml config file..."
cat /tmpl/envoy.yaml.tmpl | envsubst \$ENVOY_LB_ALG,\$SERVICE_NAME,\$SERVICE_PORT > /etc/envoy.yaml

echo "Starting Envoy..."
/usr/local/bin/envoy -c /etc/envoy.yaml

envoy.yaml

static_resources:
  listeners:
  - address:
      socket_address:
        address: 0.0.0.0
        port_value: ${SERVICE_PORT}
    filter_chains:
    - filters:
      - name: envoy.filters.network.http_connection_manager
        typed_config:
          "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager
          codec_type: auto
          stat_prefix: ingress_http
          access_log:
          - name: envoy.access_loggers.file
            typed_config:
              "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog
              path: "/dev/stdout"
          route_config:
            name: local_route
            virtual_hosts:
            - name: backend
              domains:
              - "*"
              routes:
              - match:
                  prefix: "/" 
                  grpc: {}
                route:
                  cluster: backend_grpc_service
          http_filters:
          - name: envoy.filters.http.router
            typed_config: {}
  clusters:
  - name: backend_grpc_service
    connect_timeout: 0.250s
    type: strict_dns
    lb_policy: round_robin
    http2_protocol_options: {}
    load_assignment:
      cluster_name: backend_grpc_service
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: ${SERVICE_NAME}
                port_value: ${SERVICE_PORT}

3. 創建Envoy deployment

最后,我們必須為Envoy本身創建一個部署。

service.yaml

{
  "kind": "Service",
  "apiVersion": "v1",
  "metadata": {
    "name": "myapp-envoy-service",
    "labels": {
      "app": "myapp-envoy-service"
    }
  },
  "spec": {
    "ports": [
      {
        "name": "grpc",
        "protocol": "TCP",
        "port": 82,
        "targetPort": 82
      }
    ],
    "selector": {
      "app": "myapp-envoy"
    },
    "type": "ClusterIP",
    "sessionAffinity": "None"
  },
  "status": {
    "loadBalancer": {}
  }
}

deployment.yaml

{
  "kind": "Deployment",
  "apiVersion": "extensions/v1beta1",
  "metadata": {
    "name": "myapp-envoy",
    "labels": {
      "app": "myapp-envoy"
    },
    "annotations": {
      "deployment.kubernetes.io/revision": "1"
    }
  },
  "spec": {
    "replicas": 1,
    "selector": {
      "matchLabels": {
        "app": "myapp-envoy"
      }
    },
    "template": {
      "metadata": {
        "creationTimestamp": null,
        "labels": {
          "app": "myapp-envoy"
        }
      },
      "spec": {
        "containers": [
          {
            "name": "myapp-envoy",
            "image": "****/envoy:latest",
            "ports": [
              {
                "name": "http",
                "containerPort": 82,
                "protocol": "TCP"
              },
              {
                "name": "envoy-admin",
                "containerPort": 8881,
                "protocol": "TCP"
              }
            ],
            "env": [
              {
                "name": "ENVOY_LB_ALG",
                "value": "LEAST_REQUEST"
              },
              {
                "name": "SERVICE_NAME",
                "value": "myapp2-service"
              },
              {
                "name": "SERVICE_PORT",
                "value": "82"
              }
            ],
            "resources": {},
            "terminationMessagePath": "/dev/termination-log",
            "terminationMessagePolicy": "File",
            "imagePullPolicy": "IfNotPresent"
          }
        ],
        "restartPolicy": "Always",
        "terminationGracePeriodSeconds": 30,
        "dnsPolicy": "ClusterFirst",
        "securityContext": {}
        ],
        "schedulerName": "default-scheduler"
      }
    },
    "strategy": {
      "type": "RollingUpdate",
      "rollingUpdate": {
        "maxUnavailable": 1,
        "maxSurge": 1
      }
    },
    "revisionHistoryLimit": 2147483647,
    "progressDeadlineSeconds": 2147483647
  }
}

僅當我們使Envoy Docker鏡像可參數化時,才需要env變量。

Apply此Yaml后,Envoy代理應該可以運行,並且您可以通過將請求發送到Envoy服務的主端口來訪問基礎服務。

故障排除和監視

在Envoy配置文件中,您可以看到admin:部分,用於配置Envoy的管理端點。可用於檢查有關代理的各種診斷信息。

如果您沒有發布admin端口的服務,默認情況下為9901,您仍然可以通過端口轉發到帶有kubectl的容器來訪問它。假設其中一個Envoy容器稱為myapp-envoy-656c8d5fff-mwff8,那么您可以使用命令kubectl port-forward myapp-envoy-656c8d5fff-mwff8 9901開始端口轉發。然后您可以訪問http://localhost:9901上的頁面。

一些有用的端點:

  • /config_dump:打印代理的完整配置,這對於驗證正確的配置是否最終在Pod上很有用。
  • /clusters:顯示Envoy發現的所有上游節點,以及為每個上游節點處理的請求數。例如,這對於檢查負載平衡算法是否正常工作很有用。

進行監視的一種方法是使用Prometheus從代理pods獲取統計信息。 Envoy對此提供了內置支持,Prometheus統計信息在管理端口上的/ stats/prometheus路由上發布。

您可以從該存儲庫下載可視化這些指標的Grafana儀表板,這將為您提供以下圖表。

b-08.png

關於負載均衡算法

負載平衡算法會對集群的整體性能產生重大影響。對於需要均勻分配負載的服務(例如,當服務占用大量CPU並很容易超載時),使用最少請求算法可能是有益的。另一方面,最少請求的問題在於,如果某個節點由於某種原因開始發生故障,並且故障響應時間很快,那么負載均衡器會將不成比例的大部分請求發送給故障節點,循環負載均衡算法不會有問題。

我使用dummy API進行了一些基准測試,並比較了輪詢和最少請求LB算法。事實證明,最少的請求可以帶來整體性能的顯着提高。

我使用不斷增加的輸入流量對API進行了約40分鍾的基准測試。在整個基准測試中,我收集了以下指標:

  • 服務器執行的請求數("requests in flight")
  • 每台服務器平均正在執行的請求數
  • 請求速率(每5分鍾增加一次)
  • 錯誤率(通常沒有,但是當事情開始放慢時,這開始顯示出超時)
  • 服務器上記錄的響應時間百分位數(0.50、0.90和0.99)

ROUND_ROBIN的統計數據看起來像這樣:
b-09.png

這些是LEAST_REQUEST的結果:
b-10.png

您可以從結果中看到LEAST_REQUEST可以導致流量在節點之間的分配更加順暢,從而在高負載下降低了平均響應時間。

確切的改進取決於實際的API,因此,我絕對建議您也使用自己的服務進行基准測試,以便做出決定。

總結

我希望此介紹對在Kubernetes中使用Envoy有所幫助。順便說一下,這不是在Kubernetes上實現最少請求負載平衡的唯一方法。可以執行相同操作的各種ingress控制器(其中一個是在Envoy之上構建的Ambassador)。

 

參考:https://segmentfault.com/a/1190000021800561


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM