從 Nginx 遷移到 Envoy Proxy

米開朗基楊發表於2018-12-14

原文連結:從 Nginx 遷移到 Envoy Proxy

本文將會手把手教你如何從 Nginx 遷移到 Envoy Proxy,你可以將任何以前的經驗和對 Nginx 的理解直接應用於 Envoy Proxy 中。

主要內容:

  • 配置 Envoy Proxy 的 server 配置項
  • 配置 Envoy Proxy 以將流量代理到外部服務
  • 配置訪問日誌和錯誤日誌

學完本教程之後,你將會了解 Envoy Proxy 的核心功能,以及如何將現有的 Nginx 配置檔案遷移到 Envoy Proxy 中。

1. Nginx 與 Envoy Proxy 的核心模組

先來看一個 Nginx 配置檔案的完整示例,該配置檔案取自於 Nginx wiki,內容如下:

$ cat nginx.conf

user  www www;
pid /var/run/nginx.pid;
worker_processes  2;

events {
  worker_connections   2000;
}

http {
  gzip on;
  gzip_min_length  1100;
  gzip_buffers     4 8k;
  gzip_types       text/plain;

  log_format main      '$remote_addr - $remote_user [$time_local]  '
    '"$request" $status $bytes_sent '
    '"$http_referer" "$http_user_agent" '
    '"$gzip_ratio"';

  log_format download  '$remote_addr - $remote_user [$time_local]  '
    '"$request" $status $bytes_sent '
    '"$http_referer" "$http_user_agent" '
    '"$http_range" "$sent_http_content_range"';


  upstream targetCluster {
    172.18.0.3:80;
    172.18.0.4:80;
  }

  server {
    listen        8080;
    server_name   one.example.com  www.one.example.com;

    access_log   /var/log/nginx.access_log  main;
    error_log  /var/log/nginx.error_log  info;

    location / {
      proxy_pass         http://targetCluster/;
      proxy_redirect     off;

      proxy_set_header   Host             $host;
      proxy_set_header   X-Real-IP        $remote_addr;
    }
  }
}
複製程式碼

Nginx 的配置通常分為三個關鍵要素:

  1. 配置 Server 塊、日誌和 gzip 功能,這些配置對全域性生效,可以應用於所有示例。
  2. 配置 Nginx 以接收 8080 埠上對域名 one.example.com 的訪問請求。
  3. 將 URL 的不同路徑的流量轉發到不同的目標後端。

並不是所有的 Nginx 配置項都適用於 Envoy Proxy,其中有一些配置在 Envoy 中可以忽略。Envoy Proxy 有四個關鍵元件,可以用來匹配 Nginx 的核心配置塊:

  • 監聽器(Listener):監聽器定義了 Envoy 如何處理入站請求,目前 Envoy 僅支援基於 TCP 的監聽器。一旦建立連線之後,就會將該請求傳遞給一組過濾器(filter)進行處理。
  • 過濾器(Filter):過濾器是處理入站和出站流量的鏈式結構的一部分。在過濾器鏈上可以整合很多特定功能的過濾器,例如,通過整合 GZip 過濾器可以在資料傳送到客戶端之前壓縮資料。
  • 路由(Router):路由用來將流量轉發到具體的目標例項,目標例項在 Envoy 中被定義為叢集。
  • 叢集(Cluster):叢集定義了流量的目標端點,同時還包括一些其他可選配置,如負載均衡策略等。

接下來我們將使用這四個關鍵元件建立一個 Envoy Proxy 配置檔案,以匹配前面定義的 Nginx 配置檔案。

2. Nginx 配置遷移

Nginx 配置檔案的第一部分定義了 Nginx 本身執行的工作特性。

Worker 連線數

下面的配置定義了 Nginx 的 worker 程式數和最大連線數,這表明了 Nginx 是如何通過自身的彈效能力來滿足各種需求的。

worker_processes  2;

events {
  worker_connections   2000;
}
複製程式碼

而 Envoy Proxy 則以不同的方式來管理 Worker 程式和連線。預設情況下,Envoy 為系統中的每個硬體執行緒生成一個工作執行緒。(可以通過 --concurrency 選項控制)。每個 Worker 執行緒是一個“非阻塞”事件迴圈,負責監聽每個偵聽器,接受新連線,為每個連線例項化過濾器棧,以及處理所有連線生命週期內 IO 事件。所有進一步的處理都在 Worker 執行緒內完成,其中包括轉發。

Envoy 中的所有連線池都和 Worker 執行緒繫結。 儘管 HTTP/2 連線池一次只與每個上游主機建立一個連線,但如果有四個 Worker,則每個上游主機在穩定狀態下將有四個 HTTP/2 連線。Envoy 以這種方式工作的原因是將所有連線都在單個 Worker 執行緒中處理,這樣幾乎所有程式碼都可以在無鎖的情況下編寫,就像它是單執行緒一樣。擁有太多的 Worker 將浪費記憶體,建立更多空閒連線,並導致連線池命中率降低。

你可以在 Envoy Proxy 部落格上找到更多資訊。

HTTP 配置

Nginx 的下一個配置塊是 HTTP 塊,包括資源的媒體型別(mime type)、預設超時和 gzip 壓縮配置。這些功能在 Envoy Proxy 中都是通過過濾器來實現的,下文將會詳細討論。

3. Server 配置遷移

在 HTTP 配置塊中,Nginx 配置指定了監聽 8080 埠並接收對域名 one.example.comwww.one.example.com 的訪問請求。

 server {
    listen        80;
    server_name   one.example.com  www.one.example.com;
複製程式碼

這部分配置在 Envoy 中是由 Listener 管理的。

Envoy 監聽器

讓 Envoy 能正常工作最重要的一步是定義監聽器。首先需要建立一個配置檔案用來描述 Envoy 的執行引數。

下面的配置項將建立一個新的監聽器並將其繫結到 8080 埠。

static_resources:
  listeners:
  - name: listener_0
    address:
      socket_address: { address: 0.0.0.0, port_value: 8080 }
複製程式碼

這裡不需要定義 server_name,域名將會交給過濾器來處理。

4. Location 配置遷移

當請求進入 Nginx 時,Location 塊定義瞭如何處理流量的後設資料,以及如何轉發處理後的流量。在下面的配置項中,進入站點的所有流量都被代理到名為 targetCluster 的上游叢集。上游叢集定了用來接收流量的後端例項,下一節再詳細討論。

location / {
    proxy_pass         http://targetCluster/;
    proxy_redirect     off;

    proxy_set_header   Host             $host;
    proxy_set_header   X-Real-IP        $remote_addr;
}
複製程式碼

這部分配置在 Envoy 中是由過濾器管理的。

Envoy 過濾器

對於靜態配置檔案而言,過濾器定義瞭如何處理傳入請求。這裡我們將會建立一個與上一節 Nginx 配置中的 server_names 相匹配的過濾器,當收到與過濾器中定義的域名和路由相匹配的入站請求時,就會將該請求的流量轉發到指定的叢集。這裡的叢集相當於 Nginx 中的 upstream 配置。

filter_chains:
- filters:
  - name: envoy.http_connection_manager
    config:
      codec_type: auto
      stat_prefix: ingress_http
      route_config:
        name: local_route
        virtual_hosts:
        - name: backend
          domains:
            - "one.example.com"
            - "www.one.example.com"
          routes:
          - match:
              prefix: "/"
            route:
              cluster: targetCluster
      http_filters:
      - name: envoy.router
複製程式碼

envoy.http_connection_manager 是 Envoy 中的內建 HTTP 過濾器。除了該過濾器,Envoy 中還內建了一些其他過濾器,包括 Redis、Mongo、TCP 等,完整的過濾器列表請參考 Envoy 官方文件

5. Proxy 與 upstream 配置遷移

在 Nginx 中,upstream 配置項定義了用來接收流量的目標服務叢集。下面的 upstream 配置項分配了兩個後端例項:

upstream targetCluster {
  172.18.0.3:80;
  172.18.0.4:80;
}
複製程式碼

這部分配置在 Envoy 中是由叢集(Cluster)管理的。

Envoy 叢集

upstream 配置項在 Envoy 中被定義為 Cluster。Cluster 中的 hosts 列表用來處理被過濾器轉發的流量,其中 hosts 的訪問策略(例如超時)也在 Cluster 中進行配置,這有利於更精細化地控制超時和負載均衡。

clusters:
- name: targetCluster
  connect_timeout: 0.25s
  type: STRICT_DNS
  dns_lookup_family: V4_ONLY
  lb_policy: ROUND_ROBIN
  hosts: [
    { socket_address: { address: 172.18.0.3, port_value: 80 }},
    { socket_address: { address: 172.18.0.4, port_value: 80 }}
  ]
複製程式碼

當使用 STRICT_DNS 型別的服務發現時,Envoy 將持續並非同步地解析指定的 DNS 目標。DNS 結果中每個返回的 IP 地址將被視為上游叢集中的顯式主機。這意味著如果查詢返回三個 IP 地址,Envoy 將假定該叢集有三臺主機,並且所有三臺主機應該負載均衡。如果有主機從 DNS 返回結果中刪除,則 Envoy 會認為它不再存在,並且會將它從所有的當前連線池中排除。更多詳細內容請參考 Envoy 官方文件

6. 日誌配置遷移

最後一部分需要遷移的配置是應用日誌。Envoy Proxy 預設情況下沒有將日誌持久化到磁碟中,而是遵循雲原生方法,其中所有應用程式日誌都輸出到 stdoutstderr

關於使用者請求資訊的訪問日誌屬於可選項,預設情況下是禁用的。要為 HTTP 請求啟用訪問日誌,請在 envoy.http_connection_manager 過濾器中新增 access_log 配置項,日誌路徑可以是塊裝置(如 stdout),也可以是磁碟上的檔案,具體取決於你的需求。

下面的配置項將所有的訪問日誌傳遞給 stdout:

access_log:
- name: envoy.file_access_log
  config:
    path: "/dev/stdout"
複製程式碼

將該配置項複製到 envoy.http_connection_manager 過濾器的配置中,完整的過濾器配置如下:

- name: envoy.http_connection_manager
  config:
    codec_type: auto
    stat_prefix: ingress_http
    access_log:
    - name: envoy.file_access_log
        config:
        path: "/dev/stdout"
    route_config:
複製程式碼

Envoy 預設情況下使用格式化字串來輸出 HTTP 請求的詳細日誌:

[%START_TIME%] "%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%"
%RESPONSE_CODE% %RESPONSE_FLAGS% %BYTES_RECEIVED% %BYTES_SENT% %DURATION%
%RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% "%REQ(X-FORWARDED-FOR)%" "%REQ(USER-AGENT)%"
"%REQ(X-REQUEST-ID)%" "%REQ(:AUTHORITY)%" "%UPSTREAM_HOST%"\n
複製程式碼

本示例中的日誌輸出如下所示:

[2018-11-23T04:51:00.281Z] "GET / HTTP/1.1" 200 - 0 58 4 1 "-" "curl/7.47.0" "f21ebd42-6770-4aa5-88d4-e56118165a7d" "one.example.com" "172.18.0.4:80"
複製程式碼

可以通過設定格式化欄位來自定義日誌輸出內容,例如:

access_log:
- name: envoy.file_access_log
  config:
    path: "/dev/stdout"
    format: "[%START_TIME%] "%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%" %RESPONSE_CODE% %RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% "%REQ(X-REQUEST-ID)%" "%REQ(:AUTHORITY)%" "%UPSTREAM_HOST%"\n"
複製程式碼

你也可以通過設定 json_format 欄位來輸出 JSON 格式的日誌,例如:

access_log:
- name: envoy.file_access_log
  config:
    path: "/dev/stdout"
    json_format: {"protocol": "%PROTOCOL%", "duration": "%DURATION%", "request_method": "%REQ(:METHOD)%"}
複製程式碼

關於 Envoy 日誌配置的更多詳細配置請參考 www.envoyproxy.io/docs/envoy/…

在生產環境中使用 Envoy Proxy 時,日誌不是獲取可觀察性的唯一方法,Envoy 中還內建了更高階的功能,如分散式追蹤和監控指標。你可以在分散式追蹤文件中找到更多詳細內容。

完整的 Envoy 配置檔案如下所示:

static_resources:
  listeners:
  - name: listener_0
    address:
      socket_address: { address: 0.0.0.0, port_value: 8080 }
    filter_chains:
    - filters:
      - name: envoy.http_connection_manager
        config:
          codec_type: auto
          stat_prefix: ingress_http
          route_config:
            name: local_route
            virtual_hosts:
            - name: backend
              domains:
                - "one.example.com"
                - "www.one.example.com"
              routes:
              - match:
                  prefix: "/"
                route:
                  cluster: targetCluster
          http_filters:
          - name: envoy.router
  clusters:
  - name: targetCluster
    connect_timeout: 0.25s
    type: STRICT_DNS
    dns_lookup_family: V4_ONLY
    lb_policy: ROUND_ROBIN
    hosts: [
      { socket_address: { address: 172.18.0.3, port_value: 80 }},
      { socket_address: { address: 172.18.0.4, port_value: 80 }}
    ]

admin:
  access_log_path: /tmp/admin_access.log
  address:
    socket_address: { address: 0.0.0.0, port_value: 9090 }
複製程式碼

7. 啟動 Envoy Proxy

現在已經將 Nginx 的所有配置轉化為 Envoy Proxy 的配置,接下來就是啟動 Envoy 例項並進行測試。

以普通使用者身份執行

在 Nginx 配置檔案的頂部有一行配置 user www www;,表示以低許可權使用者身份執行 Nginx 以提高安全性。而 Envoy 則採用雲原生的方法來管理程式所有者,當我們通過容器來啟動 Envoy Proxy 時,可以通過命令列引數來指定一個低許可權使用者。

啟動 Envoy Proxy

下面的命令將通過容器啟動 Envoy Proxy,該命令將 Envoy 容器暴露在 80 埠上以監聽入站請求,但容器內的 Envoy Proxy 監聽在 8080 埠上。通過 --user 引數以允許程式以低許可權使用者身份執行。

$ docker run --name proxy1 -p 80:8080 --user 1000:1000 -v /root/envoy.yaml:/etc/envoy/envoy.yaml envoyproxy/envoy
複製程式碼

測試

啟動代理之後,現在就可以進行訪問測試了。下面的 curl 命令使用 Envoy 配置檔案中定義的 請求標頭檔案中的 Host 欄位發出請求:

$ curl -H "Host: one.example.com" localhost -i
複製程式碼

如果不出意外,該請求將會返回 503 錯誤,因為上游叢集還沒有執行,處於不可用狀態,Envoy Proxy 找不到可用的目標後端來處理該請求。下面就來啟動相應的 HTTP 服務:

$ docker run -d katacoda/docker-http-server
$ docker run -d katacoda/docker-http-server
複製程式碼

啟動這些服務之後,Envoy 就可以成功將流量代理到目標後端:

$ curl -H "Host: one.example.com" localhost -i
複製程式碼

現在你應該會看到請求已被成功響應,並且可以從日誌中看到哪個容器響應了該請求。

附加的 HTTP 響應標頭檔案

如果請求成功,你會在請求的響應標頭檔案中看到一些附加的欄位,這些欄位包含了上游主機處理請求所花費的時間(以毫秒為單位)。如果客戶端想要確定因為網路延遲導致的請求處理延時,這些欄位將會很有幫助。

x-envoy-upstream-service-time: 0
server: envoy
複製程式碼

從 Nginx 遷移到 Envoy Proxy

更多精彩內容請關注微信公眾號

相關文章