從 Nginx 遷移到 Envoy Proxy

原文連接:從 Nginx 遷移到 Envoy Proxynginx

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

主要內容:json

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

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

1. Nginx 與 Envoy Proxy 的核心模塊

先來看一個 Nginx 配置文件的完整示例,該配置文件取自於 Nginx wiki,內容以下:api

$ 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 的核心配置塊:bash

  • 監聽器(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
複製代碼

更多精彩內容請關注微信公衆號
相關文章
相關標籤/搜索