從 Nginx 遷移到 Envoy Proxy
本文將會手把手教你如何從 Nginx
遷移到 Envoy Proxy
,你可以將任何以前的經驗和對 Nginx 的理解直接應用於 Envoy Proxy
中。
主要內容:
- 配置 Envoy Proxy 的 server 配置項
- 配置 Envoy Proxy 以將流量代理到外部服務
- 配置訪問日誌和錯誤日誌
學完本教程之後,你將會了解 Envoy Proxy
的核心功能,以及如何將現有的 Nginx 配置檔案遷移到 Envoy Proxy 中。
1. Nginx 與 Envoy Proxy 的核心模組
先來看一個 Nginx 配置檔案的完整示例,該配置檔案取自於
$ 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 的配置通常分為三個關鍵要素:
- 配置 Server 塊、日誌和
gzip
功能,這些配置對全域性生效,可以應用於所有示例。 - 配置 Nginx 以接收
8080
埠上對域名one.example.com
的訪問請求。 - 將 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.com
和 www.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 預設情況下沒有將日誌持久化到磁碟中,而是遵循雲原生方法,其中所有應用程式日誌都輸出到 stdout
和 stderr
。
關於使用者請求資訊的訪問日誌屬於可選項,預設情況下是禁用的。要為 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
複製程式碼