原文連接:從 Nginx 遷移到 Envoy Proxynginx
本文將會手把手教你如何從 Nginx
遷移到 Envoy Proxy
,你能夠將任何之前的經驗和對 Nginx 的理解直接應用於 Envoy Proxy
中。docker
主要內容:json
學完本教程以後,你將會了解 Envoy Proxy
的核心功能,以及如何將現有的 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 的配置一般分爲三個關鍵要素:安全
gzip
功能,這些配置對全局生效,能夠應用於全部示例。8080
端口上對域名 one.example.com
的訪問請求。並非全部的 Nginx 配置項都適用於 Envoy Proxy,其中有一些配置在 Envoy 中能夠忽略。Envoy Proxy 有四個關鍵組件,能夠用來匹配 Nginx 的核心配置塊:bash
GZip
過濾器能夠在數據發送到客戶端以前壓縮數據。接下來咱們將使用這四個關鍵組件建立一個 Envoy Proxy 配置文件,以匹配前面定義的 Nginx 配置文件。微信
Nginx 配置文件的第一部分定義了 Nginx 自己運行的工做特性。網絡
下面的配置定義了 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 博客上找到更多信息。
Nginx 的下一個配置塊是 HTTP 塊,包括資源的媒體類型(mime type)、默認超時和 gzip 壓縮配置。這些功能在 Envoy Proxy 中都是經過過濾器來實現的,下文將會詳細討論。
在 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 的運行參數。
下面的配置項將建立一個新的監聽器並將其綁定到 8080
端口。
static_resources:
listeners:
- name: listener_0
address:
socket_address: { address: 0.0.0.0, port_value: 8080 }
複製代碼
這裏不須要定義 server_name
,域名將會交給過濾器來處理。
當請求進入 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 中是由過濾器管理的。
對於靜態配置文件而言,過濾器定義瞭如何處理傳入請求。這裏咱們將會建立一個與上一節 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 官方文檔。
在 Nginx 中,upstream
配置項定義了用來接收流量的目標服務集羣。下面的 upstream 配置項分配了兩個後端實例:
upstream targetCluster {
172.18.0.3:80;
172.18.0.4:80;
}
複製代碼
這部分配置在 Envoy 中是由集羣(Cluster)管理的。
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 官方文檔。
最後一部分須要遷移的配置是應用日誌。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 }
複製代碼
如今已經將 Nginx 的全部配置轉化爲 Envoy Proxy 的配置,接下來就是啓動 Envoy 實例並進行測試。
在 Nginx 配置文件的頂部有一行配置 user www www;
,表示以低權限用戶身份運行 Nginx 以提升安全性。而 Envoy 則採用雲原生的方法來管理進程全部者,當咱們經過容器來啓動 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
複製代碼
如今你應該會看到請求已被成功響應,而且能夠從日誌中看到哪一個容器響應了該請求。
若是請求成功,你會在請求的響應頭文件中看到一些附加的字段,這些字段包含了上游主機處理請求所花費的時間(以毫秒爲單位)。若是客戶端想要肯定由於網絡延遲致使的請求處理延時,這些字段將會頗有幫助。
x-envoy-upstream-service-time: 0
server: envoy
複製代碼