Envoy 快速入門

yandy在掘金發表於2019-03-03

微服務的服務間通訊與服務治理 一文中,我們提到扇貝目前的 Service Mesh 架構是基於 Envoy 來做的。

本文的主角就是這個新秀 Envoy

拋棄了 Nginx, 選擇了 Envoy

其實之前扇貝是大量使用 Nginx 的,無論是對於其部署,配置還是調優都更為有經驗。如果可以,我們還是傾向於選用 Nginx作為 Service Mesh 的核心方案的。

但是碰到幾個繞不開的問題:

  1. Nginx的反向代理不支援 http2/grpc (好像今年3月份剛支援)
  2. 不像 Envoy 幾乎所有的網路配置都可以利用 xDS API 來實現動態變更,Nginx缺乏有效的配置熱變更機制(除非深入開發或者不斷地reload)。
  3. Nginx的很多微服務功能都是要買 Nginx Plus 才有的

Envoy 作為一個主打 Service Mesh 方案的 proxy,其設計處處考慮 Service Mesh,在經過一定地瞭解後,我們果斷入了 Envoy 的坑。

Envoy 是什麼

我們援引一段官網的描述:

Envoy is an L7 proxy and communication bus designed for large modern service oriented architectures. The project was born out of the belief that:
“The network should be transparent to applications. When network and application problems do occur it should be easy to determine the source of the problem.”

Envoy 的核心功能/賣點

  • 非侵入的架構:Envoy 是和應用服務並行執行的,透明地代理應用服務發出/接收的流量。應用服務只需要和 Envoy 通訊,無需知道其他微服務應用在哪裡。
  • 基於 Modern C++11實現,效能優異。
  • L3/L4 過濾器架構:Envoy 的核心是一個 L3/L4 代理,然後通過外掛式的過濾器(network filters)鏈條來執行 TCP/UDP 的相關任務,例如 TCP 轉發,TLS 認證等工作。
  • HTTP L7 過濾器架構:HTTP在現代應用體系裡是地位非常特殊的應用層協議,所以 Envoy 內建了一個非常核心的過濾器: http_connection_managerhttp_connection_manager 本身是如此特殊和複雜,支援豐富的配置,以及本身也是過濾器架構,可以通過一系列 http 過濾器(http filters)來實現 http協議層面的任務,例如:http路由,重定向,CORS支援等等。
  • HTTP/2 作為第一公民:Envoy 支援 HTTP/1.1 和 HTTP/2,推薦使用 HTTP/2。
  • gRPC 支援:因為對 HTTP/2 的良好支援,Envoy 可以方便的支援 gRPC,特別是在負載和代理上。
  • 服務發現: 支援包括 DNS, EDS 在內的多種服務發現方案。
  • 健康檢查:內建健康檢查子系統。
  • 高階的負載均衡方案:除了一般的負載均衡,Envoy 還支援基於 rate limit 服務的多種高階負載均衡方案,包括: automatic retries, circuit breaking, global rate limiting
  • Tracing:方便整合 Open Tracing 系統,追蹤請求
  • 統計與監控:內建 stats 模組,方便整合諸如 prometheus/statsd 等監控方案
  • 動態配置:通過“動態配置API”實現配置的動態調整,而無需重啟 Envoy 服務的。

核心術語解釋

Host

這裡的 Host,可以理解為由 IP, Port 唯一確定的服務例項

Downstream

傳送請求給 Envoy 的 Host 是 Downstream(下游),例如gRPC的 client

Upstream

接收 Enovy 發出的請求的 Host 是Upstream(上游),例如 gRPC的 server

Listener

Envoy 監聽的一個地址,例如 ip:port, unix socket 等等

Cluster

一組功能一致的上游 Host,稱為一個cluster。類似 k8sService, nginxupstream

Http Route Table

HTTP 的路由規則,例如請求的域名,Path符合什麼規則,轉發給哪個 Cluster。

配置(v2)

Envoy 的配置介面以 proto3(Protocol Buffers v3)的格式定義,完整的定義見 data plane API repository

具體的配置檔案編寫支援:yaml, json, pb, pb_text 四種格式。出於可讀性的考慮,我們下面都以 yaml 格式為例。

先看一個簡單的例子:

完全靜態的配置

admin:
  access_log_path: /tmp/admin_access.log
  address:
    socket_address: { address: 127.0.0.1, port_value: 9901 }

static_resources:
  listeners:
  - name: listener_0
    address:
      socket_address: { address: 127.0.0.1, port_value: 10000 }
    filter_chains:
    - filters:
      - name: envoy.http_connection_manager
        config:
          stat_prefix: ingress_http
          route_config:
            name: local_route
            virtual_hosts:
            - name: local_service
              domains: ["example.com"]
              routes:
              - match: { prefix: "/" }
                route: { cluster: some_service }
          http_filters:
          - name: envoy.router
  clusters:
  - name: some_service
    connect_timeout: 0.25s
    type: STATIC
    lb_policy: ROUND_ROBIN
    hosts: [{ socket_address: { address: 127.0.0.2, port_value: 1234 }}]
複製程式碼

在上面的例子中,我們配置了一個 envoy例項,監聽 127.0.0.1:10000,支援 http 協議訪問,http 訪問域名為:example.com。接收到的所有http流量,轉發給 127.0.0.2:1234 的服務。

很快大家發現一個問題,就是 some_service 這個cluster中 hosts 是固定的(127.0.0.2:1234),但是很有可能對應的 host是會變的。例如:增加了新的 host(水平擴充套件), k8s環境中,發了新程式碼(pod變了),等等。這時候,總不至於我們需要不停的該 cluster 中的 hosts吧?放心,如果是這樣,我們就不會用 Envoy 了。

通過EDS動態配置cluster hosts

接下來我們會碰到第一個動態配置的例子,就是動態配置 cluster 的 hosts。

我們先假設有這麼一個服務A,地址是:127.0.0.3:5678,會返回 proto3 編碼的下屬響應

version_info: "0"
resources:
- "@type": type.googleapis.com/envoy.api.v2.ClusterLoadAssignment
  cluster_name: some_service
  endpoints:
  - lb_endpoints:
    - endpoint:
        address:
          socket_address:
            address: 127.0.0.2
            port_value: 1234
複製程式碼

那麼我們把 envoy 的配置調整為:

admin:
  access_log_path: /tmp/admin_access.log
  address:
    socket_address: { address: 127.0.0.1, port_value: 9901 }

static_resources:
  listeners:
  - name: listener_0
    address:
      socket_address: { address: 127.0.0.1, port_value: 10000 }
    filter_chains:
    - filters:
      - name: envoy.http_connection_manager
        config:
          stat_prefix: ingress_http
          route_config:
            name: local_route
            virtual_hosts:
            - name: local_service
              domains: ["example.com"]
              routes:
              - match: { prefix: "/" }
                route: { cluster: some_service }
          http_filters:
          - name: envoy.router
  clusters:
  - name: some_service
    connect_timeout: 0.25s
    lb_policy: ROUND_ROBIN
    type: EDS
    eds_cluster_config:
      eds_config:
        api_config_source:
          api_type: GRPC
          cluster_names: [xds_cluster]
  - name: xds_cluster
    connect_timeout: 0.25s
    type: STATIC
    lb_policy: ROUND_ROBIN
    http2_protocol_options: {}
    hosts: [{ socket_address: { address: 127.0.0.3, port_value: 5678 }}]
複製程式碼

就可以實現 some_service 這個 cluster 的 hosts 的動態配置了。新的配置中,some_service 這個 cluster 的 hosts 是 EDS(Endpoint Discovery Service) 的返回值決定的,就是說 EDS 會返回 some_service 這個 cluster 的 hosts 的列表。新配置中,EDS 服務的地址定義在 xds_cluster 這個 cluster中,地址正是我們開頭假設的服務A的地址。

其實 Envoy 的 listener, cluster, http route 等都是可以動態配置的,方法和 EDS一樣,通過 LDS, CDS, RDS 來實現配置,而它們統稱為 xDS。一個完整的動態配置的例子如下:

基於xDS的動態配置

admin:
  access_log_path: /tmp/admin_access.log
  address:
    socket_address: { address: 127.0.0.1, port_value: 9901 }

dynamic_resources:
  lds_config:
    api_config_source:
      api_type: GRPC
      cluster_names: [xds_cluster]
  cds_config:
    api_config_source:
      api_type: GRPC
      cluster_names: [xds_cluster]

static_resources:
  clusters:
  - name: xds_cluster
    connect_timeout: 0.25s
    type: STATIC
    lb_policy: ROUND_ROBIN
    http2_protocol_options: {}
    hosts: [{ socket_address: { address: 127.0.0.3, port_value: 5678 }}]
複製程式碼

這裡我們假設 127.0.0.3:5678 提供完整的 xDS

LDS:

version_info: "0"
resources:
- "@type": type.googleapis.com/envoy.api.v2.Listener
  name: listener_0
  address:
    socket_address:
      address: 127.0.0.1
      port_value: 10000
  filter_chains:
  - filters:
    - name: envoy.http_connection_manager
      config:
        stat_prefix: ingress_http
        codec_type: AUTO
        rds:
          route_config_name: local_route
          config_source:
            api_config_source:
              api_type: GRPC
              cluster_names: [xds_cluster]
        http_filters:
        - name: envoy.router
複製程式碼

RDS:

version_info: "0"
resources:
- "@type": type.googleapis.com/envoy.api.v2.RouteConfiguration
  name: local_route
  virtual_hosts:
  - name: local_service
    domains: ["*"]
    routes:
    - match: { prefix: "/" }
      route: { cluster: some_service }
複製程式碼

CDS:

version_info: "0"
resources:
- "@type": type.googleapis.com/envoy.api.v2.Cluster
  name: some_service
  connect_timeout: 0.25s
  lb_policy: ROUND_ROBIN
  type: EDS
  eds_cluster_config:
    eds_config:
      api_config_source:
        api_type: GRPC
        cluster_names: [xds_cluster]
複製程式碼

EDS:

version_info: "0"
resources:
- "@type": type.googleapis.com/envoy.api.v2.ClusterLoadAssignment
  cluster_name: some_service
  endpoints:
  - lb_endpoints:
    - endpoint:
        address:
          socket_address:
            address: 127.0.0.2
            port_value: 1234
複製程式碼

結束語

作為一篇 Envoy 的快速入門文,我們大概地瞭解了 Envoy的核心功能,術語,以及配置。關於更加深入的定製配置,可以進一步翻閱 Envoy 的官方文件。

在扇貝,Envoy 作為 Service Mesh 的核心元件,承載了微服務間的所有直接呼叫流量。

我們的不同的微服務對應一個個 Cluster,通過 authority 來設定 Route Table。

利用 Envoy 的 stats 資料做服務呼叫的效能監控,Envoy 的 access log 做流量日誌收集,rate limit 做服務保護。