Envoy是一種高性能C++分佈式代理,專爲單個服務和應用程序設計。做爲Service Mesh中的重要組件,充分理解其配置就顯得尤其重要。本文列出了使用Envoy而不用其餘代理的緣由。並給出了Envoy及其服務的配置,而後對其進行詳細解讀,幫助讀者理解其配置,從而掌握Envoy。node
服務網格是微服務設置中的通訊層,也就是說往返於每一個服務的全部請求都經過網格。服務網格在微服務設置中也成爲基礎架構層,它可以讓服務之間的通訊變得安全可靠。關於Service Mesh的基礎內容,咱們已經在這篇文章中詳細介紹過。git
每個服務都有本身的代理服務(sidecars),而後全部代理服務一塊兒造成服務網格。Sidecars處理服務之間的通訊,也就是說全部的流量都會經過網格而且該透明層能夠控制服務之間如何交互。github
服務網格經過由API控制的組件提供可觀察性、服務發現以及負載均衡等。web
實際上,若是一個服務要調用另外一個服務,它不會直接調用目標服務。而是先將請求路由到本地代理,而後代理再將該請求路由到目標服務。這一過程意味着服務實例不會和其餘服務直接接觸,僅與本地代理進行通訊。算法
根據ThoughtWorks Technology Radar(這是一份半年度的文檔,用於評估現有技術和新生技術的風險和收益)指出,「服務網格提供一致的發現、安全性、跟蹤(tracing)、監控以及故障處理,而無需共享資源(如API網關或ESB)。一個十分典型的用例是輕量的反向代理進程會與每一個服務進程或單獨的容器一塊兒部署。」docker
當談到服務網格時,不可避免談到的是「sidecar」——可用於每一個服務實例的代理。每一個sidecar負責管理一個服務的一個實例。咱們將在本文中進一步詳細討論sidecar。api
當前,愈來愈多的企業和組織開始轉向微服務架構。這樣的企業須要服務網格所提供的上述功能。解耦庫的使用或自定義代碼的方法無疑是贏家。緩存
Envoy不是構建一個服務網格的惟一選擇,市面上還有其餘的代理如Nginx、Traefik等。我之因此選擇Envoy,這個用C++編寫的高性能代理,是由於我更喜歡Envoy的輕量、強大的路由,及其提供的可觀察性和可擴展性。安全
讓咱們首先構建1個包含3個服務的服務網格設置,這是咱們正在嘗試構建的架構:服務器
在咱們的設置中Front Envoy是一個邊緣代理,咱們一般在其中執行TLS終止、身份驗證、生成請求頭等操做。
咱們來看看Front Envoy配置:
--- admin: access_log_path: "/tmp/admin_access.log" address: socket_address: address: "127.0.0.1" port_value: 9901 static_resources: listeners: - name: "http_listener" address: socket_address: address: "0.0.0.0" port_value: 80 filter_chains: filters: - name: "envoy.http_connection_manager" config: stat_prefix: "ingress" route_config: name: "local_route" virtual_hosts: - name: "http-route" domains: - "*" routes: - match: prefix: "/" route: cluster: "service_a" http_filters: - name: "envoy.router" clusters: - name: "service_a" connect_timeout: "0.25s" type: "strict_dns" lb_policy: "ROUND_ROBIN" hosts: - socket_address: address: "service_a_envoy" port_value: 8786
Envoy配置主要由如下部分組成:
一、 監聽器(Listener)
二、 路由
三、 集羣
四、 端點
一個或多個監聽器能夠在單個Envoy實例中運行。在以上9到36行的代碼提到了當前監聽器的地址和端口。每一個監聽器也能夠有一個或多個網絡過濾器。這些過濾器能夠啓用路由、tls終止、流量轉移等活動。除了envoy.http_connection_manager
使用的是內置過濾器以外,Envoy還有其餘幾個過濾器。
22行到34行代碼爲過濾器配置了路由規範,同時它也指定了咱們所接受請求的域以及路由匹配器。路由匹配器能夠根據配置的規則匹配每一個請求,並將請求轉發到適當的集羣。
集羣是Envoy將流量路由到的上游服務規範。41行到48行代碼定義了「Service A」,這是Front Envoy要通訊的惟一上游。「connect_timeout」是在返回503以前創建與上游服務的鏈接的時間限制。
一般狀況下,有多個「Serivce A」實例,而且Envoy支持多種負載均衡算法來路由流量。在本例中,咱們使用了一個簡單的循環算法。
「host」指定咱們要將流量路由到的Service A的實例。在本例中,咱們只有1個實例。
第47行代碼沒有直接與Service A進行通訊,而是與Service A的Envoy代理實例進行通訊,該代理將路由到本地Service A實例。
咱們還能夠利用返回Service A的全部實例的服務名稱(如Kubernetes中的headless服務),來執行客戶端負載均衡。Envoy緩存Service A的全部host,並每5秒刷新一次host列表。
此外,Envoy還支持主動和被動的健康檢查。所以,若是咱們要進行主動健康檢查,咱們須要在集羣配置部分對其進行配置。
第2行到第7行配置了管理服務器,它可以幫助查看配置、更改日誌級別、查看統計信息等。
第8行的「static_resources」能夠手動加載全部配置。咱們將在下文討論如何動態地執行此操做。
雖然這裏描述了許多其餘配置,可是咱們的目標不是全面介紹全部配置,而是以最少的配置開始。
這是Service A的Envoy配置:
admin: access_log_path: "/tmp/admin_access.log" address: socket_address: address: "127.0.0.1" port_value: 9901 static_resources: listeners: - name: "service-a-svc-http-listener" address: socket_address: address: "0.0.0.0" port_value: 8786 filter_chains: - filters: - name: "envoy.http_connection_manager" config: stat_prefix: "ingress" codec_type: "AUTO" route_config: name: "service-a-svc-http-route" virtual_hosts: - name: "service-a-svc-http-route" domains: - "*" routes: - match: prefix: "/" route: cluster: "service_a" http_filters: - name: "envoy.router" - name: "service-b-svc-http-listener" address: socket_address: address: "0.0.0.0" port_value: 8788 filter_chains: - filters: - name: "envoy.http_connection_manager" config: stat_prefix: "egress" codec_type: "AUTO" route_config: name: "service-b-svc-http-route" virtual_hosts: - name: "service-b-svc-http-route" domains: - "*" routes: - match: prefix: "/" route: cluster: "service_b" http_filters: - name: "envoy.router" - name: "service-c-svc-http-listener" address: socket_address: address: "0.0.0.0" port_value: 8791 filter_chains: - filters: - name: "envoy.http_connection_manager" config: stat_prefix: "egress" codec_type: "AUTO" route_config: name: "service-b-svc-http-route" virtual_hosts: - name: "service-b-svc-http-route" domains: - "*" routes: - match: prefix: "/" route: cluster: "service_c" http_filters: - name: "envoy.router" clusters: - name: "service_a" connect_timeout: "0.25s" type: "strict_dns" lb_policy: "ROUND_ROBIN" hosts: - socket_address: address: "service_a" port_value: 8081 - name: "service_b" connect_timeout: "0.25s" type: "strict_dns" lb_policy: "ROUND_ROBIN" hosts: - socket_address: address: "service_b_envoy" port_value: 8789 - name: "service_c" connect_timeout: "0.25s" type: "strict_dns" lb_policy: "ROUND_ROBIN" hosts: - socket_address: address: "service_c_envoy" port_value: 8790
11行到39行定義了一個監聽器來路由流量到實際的Service A實例。在103行到111行中找到service_a
實例的相應集羣定義。
Service A與Service B和Service C進行通訊,它指向了兩個以上的監聽器以及集羣。在本例中,咱們爲每一個上游(localhost、Service B和Service C)分離了監聽器。另外一種方法是使用單個監聽器,並根據URL或請求頭路由到任意上游。
Service B和Service C處於葉級,除了本地主機服務實例外,不與任何其餘上游通訊。所以其配置十分簡單。
而讓事情變得複雜的是上述配置中的單個監聽器和單個集羣。
配置完成後,咱們將此設置部署到Kubernetes或使用docker-compose對其進行測試。你能夠運行docker-compose build
和docker-compose up
並點擊localhost:8080,以查看請求是否成功經過全部服務和Envoy代理。咱們可使用日誌對其進行驗證。
咱們爲每一個sidecar提供了配置,而且根據不一樣的服務,服務之間的配置也有所不一樣。雖然咱們能夠手動製做和管理sidecar配置,但最初至少要提供2或3個服務,而且隨着服務數量的增長,配置會變得十分複雜。此外,每次sidecar配置更改時,你都必須從新啓動Envoy實例,以使更改生效。
正如上文所討論的,咱們能夠經過使用API server來避免手動配置並加載全部組件:集羣(CDS)、端點(EDS)、監聽器(LDS)以及路由(RDS)。因此每一個sidecar將與API server通訊並接收配置。當新配置更新到API server時,它將自動反映在Envoy實例中,從而避免了從新啓動。
你能夠在如下連接中瞭解關於動態配置的信息:
https://www.envoyproxy.io/docs/envoy/latest/configuration/overview/v2_overview#dynamic
這有一個簡單的xDS server:
https://github.com/tak2siva/Envoy-Pilot
本部分將討論若是咱們要在Kubernetes中實現所討論的設置該怎麼辦。如下是架構圖:
所以,將須要更改:
Pod
服務
部署Pod
儘管Pod規範中僅定義了一個容器——按照定義,一個Pod能夠容納一個或多個容器。爲了對每一個服務實例運行sidecar代理,咱們將Envoy容器添加到每一個Pod中。爲了與外界通訊,服務容器將經過localhost與Envoy容器進行對話。
部署文件以下所示:
admin: access_log_path: "/tmp/admin_access.log" address: socket_address: address: "127.0.0.1" port_value: 9901 static_resources: listeners: - name: "service-b-svc-http-listener" address: socket_address: address: "0.0.0.0" port_value: 8789 filter_chains: - filters: - name: "envoy.http_connection_manager" config: stat_prefix: "ingress" codec_type: "AUTO" route_config: name: "service-b-svc-http-route" virtual_hosts: - name: "service-b-svc-http-route" domains: - "*" routes: - match: prefix: "/" route: cluster: "service_b" http_filters: - name: "envoy.router" clusters: - name: "service_b" connect_timeout: "0.25s" type: "strict_dns" lb_policy: "ROUND_ROBIN" hosts: - socket_address: address: "service_b" port_value: 8082
在容器部分添加了Envoy sidecar,而且在33到39行的configmap中咱們掛載了咱們的Envoy配置文件。
Kubernetes服務負責維護Pod端點列表,該列表能夠路由流量。儘管kube-proxy一般處理Pod端點之間的負載均衡,但在本例中,咱們將執行客戶端負載均衡,而且咱們不但願kube-proxy進行負載均衡。此外,咱們想要提取Pod端點列表並對其進行負載均衡。爲此,咱們須要使用「headless服務「來返回端點列表。
以下所示:
apiVersion: apps/v1beta1 kind: Deployment metadata: name: servicea spec: replicas: 2 template: metadata: labels: app: servicea spec: containers: - name: servicea image: dnivra26/servicea:0.6 ports: - containerPort: 8081 name: svc-port protocol: TCP - name: envoy image: envoyproxy/envoy:latest ports: - containerPort: 9901 protocol: TCP name: envoy-admin - containerPort: 8786 protocol: TCP name: envoy-web volumeMounts: - name: envoy-config-volume mountPath: /etc/envoy-config/ command: ["/usr/local/bin/envoy"] args: ["-c", "/etc/envoy-config/config.yaml", "--v2-config-only", "-l", "info","--service-cluster","servicea","--service-node","servicea", "--log-format", "[METADATA][%Y-%m-%d %T.%e][%t][%l][%n] %v"] volumes: - name: envoy-config-volume configMap: name: sidecar-config items: - key: envoy-config path: config.yaml
有兩件事須要注意。一是第6行使服務變成headless,二是咱們不是將Kubernetes服務端口映射到應用程序的服務端口,而是映射到Envoy監聽器端口。這意味着,流量首先通向Envoy。即使如此,Kubernetes也能夠完美運行。
在本文中,咱們看到了如何使用Envoy代理構建服務網格。其中,咱們設置了全部通訊都將經過網格。所以,如今網格不只有大量有關流量的數據,並且還具備控制權。
如下連接中你能夠獲取咱們所討論的配置和代碼:
https://github.com/dnivra26/envoy_servicemesh
原文連接:
https://www.thoughtworks.com/insights/blog/building-service-mesh-envoy-0