在當今分佈式的世界中,單體架構愈來愈多地被多個,更小,相互鏈接的服務(不論是好是壞)所取代,代理和負載平衡技術彷佛正在復興。除了老玩家之外,近年來還涌現出幾種新的代理技術,它們以各類技術實現,並以不一樣的功能進行普及,例如易於集成到某些雲提供商(「雲原生」),高性能和低內存佔用,或動態配置。git
能夠說,兩種最流行的「經典」代理技術是NGINX(C)和HAProxy(C),而其中的一些新成員是Zuul(Java),Linkerd(Rust),Traefik(Go),Caddy(Go)和Envoy(C++)。github
全部這些技術具備不一樣的功能集,而且針對某些特定場景或託管環境(例如,Linkerd通過微調,可在Kubernetes中使用)。
在本文中,我將不作這些比較,而只是關注一個特定的場景:如何將Envoy用做Kubernetes中運行的服務的負載平衡器。算法
Envoy是最初在Lyft實施的「高性能C++分佈式代理」,但此後獲得了普遍的採用。它性能高,資源佔用少,支持由「控制平面」 API管理的動態配置,並提供了一些高級功能,例如各類負載平衡算法,限流,熔斷和影子鏡像。docker
因爲多種緣由,我選擇Envoy做爲負載平衡器代理:shell
在開始使用Envoy以前,我是經過類型爲LoadBalancer的服務對象訪問Kubernetes中的服務的,這是從Kubernetes中從外部訪問服務的一種很是典型的方法。負載均衡器服務的確切工做方式取決於託管環境。我使用的是Google Kubernetes引擎,其中每一個負載平衡器服務都映射到TCP級別的Google Cloud負載平衡器,該負載平衡器僅支持循環負載平衡算法。服務器
就我而言,這是一個問題,由於個人服務具備如下特徵:架構
因爲上述特性,輪循負載均衡算法不太適合,由於常常(偶然)多個請求最終在同一節點上結束,這使得平均響應時間比羣集的平均響應時間差得多。因此須要分配更均勻的負載。app
在本文的其他部分中,我將描述將Envoy部署爲在Kubernetes中運行的服務以前用做負載平衡器的必要步驟。負載均衡
在Kubernetes中,有一種稱爲headless服務的特定服務,剛好與Envoy的STRICT_DNS服務發現模式一塊兒使用時很是方便。less
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組。
在不以動態API形式提供控制平面的狀況下使用Envoy的最簡單方法是將硬編碼配置添加到靜態yaml文件中。
如下是一個基本配置,該配置將負載均衡到域名myapp給定的IP地址。
注意如下幾個部分:
type: STRICT_DNS
:在這裏,咱們指定服務發現類型。將其設置爲STRICT_DNS很重要,由於它能夠與咱們設置的無頭服務一塊兒使用。lb_policy:LEAST_REQUEST
:咱們能夠從各類負載平衡算法中進行選擇,可能ROUND_ROBIN和LEAST_REQUEST是最多見的。(請記住,LEAST_REQUEST不會檢查全部上游節點,而只會從2個隨機選擇的選項中進行選擇。)hosts: [{ socket_address: { address: myapp, port_value: 80 }}]
:這是咱們在其中用address字段指定Envoy必須從中獲取要發送到A記錄的域名的部分。您能夠在文檔中找到有關各類配置參數的更多信息。
如今,咱們必須將如下Dockerfile放在envoy.yaml配置文件同一目錄層級。
FROM envoyproxy/envoy:latest COPY envoy.yaml /etc/envoy.yaml CMD /usr/local/bin/envoy -c /etc/envoy.yaml
最後一步是構建鏡像,並將其推送到某個地方(例如Dockerhub或雲提供商的容器註冊表),以便可以從Kubernetes使用它。
假設我想將此推送到個人我的Docker Hub賬戶,可使用如下命令來完成。
$ docker build -t markvincze/myapp-envoy:1 . $ docker push markvincze/myapp-envoy:1
若是咱們但願可以使用環境變量自定義Envoy配置的某些部分而無需重建Docker鏡像,則能夠在yaml配置中進行一些env var替換。假設咱們但願可以自定義要代理的headless服務的名稱以及負載均衡器算法,而後咱們必須按如下方式修改yaml配置。
而後實施一個小shell腳本(docker-entrypoint.sh),在其中執行環境變量替換。
#!/bin/sh set -e echo "Generating envoy.yaml config file..." cat /tmpl/envoy.yaml.tmpl | envsubst \$ENVOY_LB_ALG,\$SERVICE_NAME > /etc/envoy.yaml echo "Starting Envoy..." /usr/local/bin/envoy -c /etc/envoy.yaml
並更改咱們的Dockerfile以運行此腳本,而不是直接啓動Envoy。
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 ENTRYPOINT ["/docker-entrypoint.sh"]
請記住,若是使用這種方法,則必須在Kubernetes部署中指定這些環境變量,不然它們將爲空。
最後,咱們必須爲Envoy自己建立一個部署。
僅當咱們使Envoy Docker鏡像可參數化時,才須要env變量。
Apply此Yaml後,Envoy代理應該能夠運行,而且您能夠經過將請求發送到Envoy服務的主端口來訪問基礎服務。
在此示例中,我僅添加了類型爲ClusterIP的服務,可是若是要從羣集外部訪問代理,還可使用LoadBalancer服務或Ingress對象。
下圖說明了整個設置的體系結構。
該圖僅顯示一個Envoy Pod,可是若是須要,您能夠將其擴展以具備更多實例。固然,您能夠根據須要使用Horizontal Pod Autoscaler自動建立更多副本。 (全部實例將是自治的且彼此獨立。)
實際上,與基礎服務相比,代理所需的實例可能要少得多。在當前使用Envoy的生產應用程序中,咱們在 〜400個上游Pod上提供了〜1000個請求/秒,可是咱們只有3個Envoy實例在運行,CPU負載約爲10%。
在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分鐘的基準測試。在整個基準測試中,我收集了如下指標:
ROUND_ROBIN的統計數據看起來像這樣:
這些是LEAST_REQUEST的結果:
您能夠從結果中看到LEAST_REQUEST能夠致使流量在節點之間的分配更加順暢,從而在高負載降低低了平均響應時間。
確切的改進取決於實際的API,所以,我絕對建議您也使用本身的服務進行基準測試,以便作出決定。
我但願此介紹對在Kubernetes中使用Envoy有所幫助。順便說一下,這不是在Kubernetes上實現最少請求負載平衡的惟一方法。能夠執行相同操做的各類ingress控制器(其中一個是在Envoy之上構建的Ambassador)。