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儀表板,這將為您提供以下圖表。
關於負載均衡算法
負載平衡算法會對集群的整體性能產生重大影響。對於需要均勻分配負載的服務(例如,當服務占用大量CPU並很容易超載時),使用最少請求算法可能是有益的。另一方面,最少請求的問題在於,如果某個節點由於某種原因開始發生故障,並且故障響應時間很快,那么負載均衡器會將不成比例的大部分請求發送給故障節點,循環負載均衡算法不會有問題。
我使用dummy API進行了一些基准測試,並比較了輪詢和最少請求LB算法。事實證明,最少的請求可以帶來整體性能的顯着提高。
我使用不斷增加的輸入流量對API進行了約40分鍾的基准測試。在整個基准測試中,我收集了以下指標:
- 服務器執行的請求數("requests in flight")
- 每台服務器平均正在執行的請求數
- 請求速率(每5分鍾增加一次)
- 錯誤率(通常沒有,但是當事情開始放慢時,這開始顯示出超時)
- 服務器上記錄的響應時間百分位數(0.50、0.90和0.99)
ROUND_ROBIN的統計數據看起來像這樣:
這些是LEAST_REQUEST的結果:
您可以從結果中看到LEAST_REQUEST可以導致流量在節點之間的分配更加順暢,從而在高負載下降低了平均響應時間。
確切的改進取決於實際的API,因此,我絕對建議您也使用自己的服務進行基准測試,以便做出決定。
總結
我希望此介紹對在Kubernetes中使用Envoy有所幫助。順便說一下,這不是在Kubernetes上實現最少請求負載平衡的唯一方法。可以執行相同操作的各種ingress控制器(其中一個是在Envoy之上構建的Ambassador)。