本文介紹關于Nginx基礎知識點,Nginx Ingress Controller實現原理,以及相關運維能力。
Nginx基礎知識點
在Nginx中,可以采用指令來配置其轉發代理功能邏輯。這些指令通常在/etc/nginx/nginx.conf
配置文件中指定,或者在通過主配置文件include
語句引入的其它配置文件中進行指定。其中Nginx配置文件主要由四部分:Main(全局設置)、Server(主機配置)、Upstream(負載均衡服務器設置)和Location(URL匹配特定位置設置)組成。
Nginx配置文件結構。
Main塊:配置影響Nginx全局的指令。一般有運行Nginx服務器的用戶組、Nginx進程PID存放路徑、日志存放路徑、配置文件引入、允許生成Worker Process數等。
# configuration file /etc/nginx/nginx.conf: # Configuration checksum: 15621323910982901520 # setup custom paths that do not require root access pid /tmp/nginx/nginx.pid; daemon off; worker_processes 31; worker_cpu_affinity auto; worker_rlimit_nofile 1047552; worker_shutdown_timeout 240s ; access_log /var/log/nginx/access.log upstreaminfo if=$loggable; error_log /var/log/nginx/error.log notice;
Events塊:配置影響Nginx服務器或與發起網絡請求的終端用戶的網絡連接,包括每個進程的最大連接數、選取哪種事件驅動模型處理連接請求等。
events { multi_accept on; worker_connections 65536; use epoll; }
HTTP塊:可以嵌套多個Server(每個
server
塊配置了一個虛擬主機或服務器)、代理、緩存、日志定義等絕大多數功能和第三方模塊的配置。例如文件引入、Mime-type定義、日志自定義、是否使用Sendfile傳輸文件、連接超時時間、單連接請求數等。http { lua_package_path "/etc/nginx/lua/?.lua;;"; lua_shared_dict balancer_ewma 10M; lua_shared_dict balancer_ewma_last_touched_at 10M; lua_shared_dict balancer_ewma_locks 1M; lua_shared_dict certificate_data 20M; lua_shared_dict certificate_servers 5M; lua_shared_dict configuration_data 20M; lua_shared_dict global_throttle_cache 10M; lua_shared_dict ocsp_response_cache 5M; init_by_lua_block { collectgarbage("collect") -- init modules local ok, res ok, res = pcall(require, "lua_ingress") if not ok then error("require failed: " .. tostring(res)) else lua_ingress = res lua_ingress.set_config({ use_forwarded_headers = false, use_proxy_protocol = false, is_ssl_passthrough_enabled = false, http_redirect_code = 308, listen_ports = { ssl_proxy = "442", https = "443" }, hsts = true, hsts_max_age = 15724800, hsts_include_subdomains = true, hsts_preload = false, ... } ... }
Server塊:配置虛擬主機的相關參數,一個HTTP中可以有多個Server。
## start server www.exmaple.com server { server_name www.example.com ; listen 80 ; listen [::]:80 ; listen 443 ssl http2 ; listen [::]:443 ssl http2 ; set $proxy_upstream_name "-"; ssl_certificate_by_lua_block { certificate.call() } location / { set $namespace "xapi"; set $ingress_name "xapi-server"; set $service_name "xapi-server-rta"; set $service_port "80"; set $location_path "/"; set $global_rate_limit_exceeding n; rewrite_by_lua_block { lua_ingress.rewrite({ force_ssl_redirect = false, ssl_redirect = false, force_no_ssl_redirect = false, preserve_trailing_slash = false, use_port_in_redirects = false, global_throttle = { namespace = "", limit = 0, window_size = 0, key = { }, ignored_cidrs = { } }, }) balancer.rewrite() plugins.run() } ... } } ## end server www.example.com
Location塊:配置請求的路由,以及各種頁面的處理情況。
location / { proxy_pass http://upstream_balancer; }
下面是一個示例Nginx.conf文件,其中包含一些常見的指令塊:
# the number of worker processes to use
worker_processes 4;
# include additional configuration files
include /etc/nginx/conf.d/*.conf;
http {
# HTTP server settings
server {
# listen on port 80
listen 80;
# server name
server_name www.example.com;
# default location
location / {
# root directory
root /var/www/html;
# index file
index index.html;
}
}
}
在此示例中:
worker_processes
:指令指定Nginx應使用的工作進程數。include
:指令用于包含其他配置文件。server
:塊包含特定服務器的設置。location
:塊指定默認位置的設置,即服務器的根URL。
了解更多的指令詳細信息,請參見Nginx官方文檔。
接下來,我們將概述在Nginx配置文件中最關鍵的幾個部分。
指令塊
HTTP塊
HTTP塊在Nginx配置文件中定義了HTTP服務器的全局配置參數。這些參數包括運行的工作進程的數量、允許的最大連接數、日志記錄的細節等。以下是一個Nginx配置文件中HTTP塊的示例:
http {
worker_processes 4;
worker_rlimit_nofile 8192;
client_max_body_size 128m;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
server {
...
}
}
在該示例中,HTTP塊設定了工作進程的數量、文件描述符的最大數量以及客戶端請求允許的最大正文大小。它還指定了訪問和錯誤日志的位置,并且該HTTP塊內嵌了一個server
塊,用于指定Web服務器的配置。
Server塊
在Nginx配置中,Server塊用于定義特定域名或一組域名的流量請求處理規則。允許您為同一服務器上托管的多個網站或應用程序定義不同的設置和行為。
以下是Nginx配置文件中服務器塊的示例:
server {
listen 80;
server_name www.example.com;
location / {
root /var/www/html/example;
index index.html index.htm;
}
location /app {
proxy_pass http://localhost:3000;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
在此示例中,服務器只處理端口80上www.example.com
域的未加密流量。
listen
指定服務器應偵聽端口80上的傳入連接。server_name
指定此服務器塊應處理的域名。listen
和server_name
一起使用來定義服務器應該偵聽和處理流量的域和端口。
server_name指令
指定虛擬主機域名。
server_name name1 name2 name3
# example:
server_name www.example.com;
域名匹配的四種寫法:
精確匹配:
server_name www.example.com
左側通配:
server_name *.example.com
右側統配:
server_name www.example.*
正則匹配:
server_name ~^www\.example\.*$
匹配優先級:精確匹配 > 左側通配符匹配 > 右側通配符匹配 > 正則表達式匹配。
Location塊
在Nginx中,Location塊是用來定義服務器對于特定URL路徑或URL模式的請求處理規則。允許您根據網站或應用程序的不同部分定義不同的行為,如為特定目錄提供靜態文件,或將請求代理到另一臺服務器。
以下是Nginx配置文件中位置塊的示例:
server {
...
location / {
root /var/www/html/example;
index index.html index.htm;
}
location /app {
proxy_pass http://localhost:3000;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
在此例子中:
/
的位置塊指定應通過提供/var/www/html/example
目錄中的文件來處理對網站根URL的請求。/app
的位置塊指定對/app
URL的請求應代理到端口3000上的localhost
。/50x.html
的位置塊指定服務器應如何通過提供特定文件來處理服務器錯誤。
在Nginx中,當存在多個location
塊時,請求的處理將根據location
的匹配規則和優先級被第一個相匹配的location
塊捕獲并處理。
語法拼接:
location [ = | ~ | ~* | ^~ ] uri { ... }
location @name { ... }
語法規則 | 描述 |
location = /uri | = 開頭表示精確匹配,只有完全匹配上才能生效。 |
location ^~ /uri | ^~ 開頭對URL路徑進行前綴匹配,并且在正則表達式之前,匹配到即停止搜索。 |
location ~ 正則表達式 | ~ 開頭表示區分大小寫的正則匹配。 |
location ~* 正則表達式 | ~* 開頭表示不區分大小寫的正則匹配。 |
location !~ 正則表達式 | !~ 區分大小寫不匹配的正則。 |
location !~*正則表達式 | !~* 不區分大小寫不匹配的正則。 |
location /uri | 不帶任何修飾符,也表示前綴匹配,但在正則匹配之后。 |
location / | 通用匹配,任何未匹配到其他 |
location @名稱 | Nginx內部跳轉。 |
匹配優先級: = > ^~ > ~ > ~* > 不帶任何字符。
參考示例如下:
# 通用的默認匹配,所有未匹配location的都會匹配此種匹配;
location = / {
}
# 前綴匹配
location ^~ /xx/ {
# 匹配任何以 /xx/ 開頭的請求并且停止搜索。
}
# 不帶正則描述的前綴匹配;
location /uri {
}
location / {
# 匹配任何查詢,因為所有請求都已 / 開頭。但是正則表達式規則和長的塊規則將被優先和查詢匹配。
}
# 正則匹配
location ~*.(gif|jpg|jpeg)$ {
# 匹配任何已 gif、jpg 或 jpeg 結尾的請求。
}
Nginx Ingress會將Host下多個Ingress path按長度降序進行排序。了解更多詳情,請參見Ingress路徑匹配和Nginx的服務器和位置塊選擇算法。
指令繼承和覆蓋
在Nginx配置中,指令遵循從外到內的繼承機制,位于內部的子上下文會從其外層的父上下文中繼承配置指令。例如,在http{}
上下文中設置的指令會被所有嵌套于其下的server{}
和location{}
上下文所繼承,同樣,server{}
上下文中的指令會被它所包含的所有location{}
上下文繼承。需要注意的是,當同一個指令在父子上下文中均被定義時,子上下文中的配置將覆蓋父上下文中的相應設置,而不是疊加。
參考示例如下:
http {
add_header X-HTTP-LEVEL-HEADER 1;
add_header X-ANOTHER-HTTP-LEVEL-HEADER 1;
server {
listen 8080;
location / {
return 200 "OK";
}
}
server {
listen 8081;
add_header X-SERVER-LEVEL-HEADER 1;
location / {
return 200 "OK";
}
location /test {
add_header X-LOCATION-LEVEL-HEADER 1;
return 200 "OK";
}
location /correct {
add_header X-HTTP-LEVEL-HEADER 1;
add_header X-ANOTHER-HTTP-LEVEL-HEADER 1;
add_header X-SERVER-LEVEL-HEADER 1;
add_header X-LOCATION-LEVEL-HEADER 1;
return 200 "OK";
}
}
}
Nginx的反向代理配置
proxy_pass指令
Nginx反向代理配置使得Nginx作為代理服務器運行,它監聽客戶端請求并將其轉發至一個或多個后端服務器。這使得Nginx能夠接收請求并將其路由至合適的后端服務進行處理。配置Nginx反向代理,可以在Nginx配置中的位置塊中使用proxy_pass
指令。
以下是配置Nginx反向代理的示例:
server {
listen 80;
server_name www.example.com;
location / {
proxy_pass http://localhost:3000;
}
}
在此示例中,/
的位置塊指定對www.example.com
域的所有請求都應代理到端口3000上的本地主機。這意味著Nginx將接收的請求都將其轉發到在localhost
上運行的服務器3000端口。
proxy_set_header指令
還可以使用proxy_set_header
指令指定要添加到代理請求中的其他標頭。
請參考,示例如下:
server {
listen 80;
server_name www.example.com;
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://localhost:3000;
}
}
在此示例中,proxy_set_header
指令指定應將Host
和X-Real-IP
標頭添加到代理請求中,并將傳入請求的主機名和發出請求的客戶端的IP地址分別設置為對應的值。這對于將傳入請求的信息傳遞到上游服務器非常有用。
Nginx常用指令
常用指令 | 描述 |
| 指定將請求轉發到哪個后端服務器,可以是IP地址加端口號,也可以是域名。 |
| 設置轉發給后端服務器的請求頭信息,如Host、X-Real-IP等。 |
| 設置與后端服務器建立連接的超時時間。 |
| 設置緩存后端服務器響應的緩沖區大小。 |
Nginx常用命令
常用命令 | 描述 |
| 向主進程發送信號,重新加載配置文件,進行熱重啟。 |
| 重啟Nginx。 |
| 快速關閉。 |
| 等待工作進程處理完成后關閉。 |
| 查看當前Nginx最終的配置。 |
| 檢查配置是否有問題。 |
Nginx Ingress Controller
實現原理
Nginx Ingress Controller是一個集控制平面和數據平面于一體的實現方案。每個Pod下有一個Controller進程,同時也包含Nginx相關進程。
Nginx Ingress數據面相關核心模塊
第三方模塊:
ACK Nginx Ingress集成了該模塊,可以實現同ARMS Trace服務的集成。了解更多詳情,請參見實現Nginx Ingress Controller組件的鏈路追蹤。
Nginx Ingress可以通過Configmap配置開啟該模塊。
data: enable-opentelemetry: "true" otlp-collector-host: "otel-coll-collector.otel.svc" ## open telemetry 地址
了解更多模塊詳情,請參見Nginx官方文檔。
配置同步原理
了解配置同步的工作原理之后,我們便能夠掌握如何減少配置Reload的頻率,以及在何種情況下需要執行配置Reload。
Nginx Ingress Controller通過Watch Ingress、Service、Pod、Endpoint相關資源,將配置更新到nginx.conf
或者lua table
下。Lua部分主要包含了Upstream端點、灰度發布和證書部分,對應Nginx本身其他的相關變量參數,若修改了還是會導致nginx.conf
發生變更,觸發Reload。詳細信息,請參見Nginx Ingress社區文檔部分《When a reload is required》。
Nginx Ingress控制面配置
啟動參數說明
了解更多啟動參數詳情,請參見Ingress控制器命令行參數。
查看Nginx Ingress對應的Deployment或者Pod Spec可以看到Nginx-ingress對應的Container的啟動參數,大致如下:
containers:
- args:
- /nginx-ingress-controller
- --election-id=ingress-controller-leader-nginx
- --ingress-class=nginx
- --watch-ingress-without-class
- --controller-class=k8s.io/ingress-nginx
- --configmap=$(POD_NAMESPACE)/nginx-configuration
- --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services
- --udp-services-configmap=$(POD_NAMESPACE)/udp-services
- --annotations-prefix=nginx.ingress.kubernetes.io
- --publish-service=$(POD_NAMESPACE)/nginx-ingress-lb
- --enable-annotation-validation
- --validating-webhook=:8443
- --validating-webhook-certificate=/usr/local/certificates/cert
- --validating-webhook-key=/usr/local/certificates/key
- --v=2
其中-v
指定日志級別:
--v=2
使用Diff顯示有關Nginx中配置更改的詳細信息。--v=3
顯示有關服務、入口規則、端點更改的詳細信息,并以JSON格式轉儲Nginx配置。--v=5
Debug調試模式。
負載均衡算法
Ingress Nginx允許設置全局默認的負載均衡算法,主要提供了Round_robin和Ewma兩種選項,默認使用Round_robin。Round_robin算法循環遍歷后端工作負載,使請求均勻分配,但如果后端性能差異較大,可能會出現負載不均衡。相反,Ewma算法可以將請求發送到加權平均負載最低的工作負載上,加權負載指數會隨著請求的到來而逐漸變化,使得負載均衡更加均衡。
要使用IP或其他變量的一致哈希進行負載平衡,請考慮使用nginx.ingress.kubernetes.io/upstream-hash-by
注釋。要使用會話Cookie進行負載平衡,請考慮使用nginx.ingress.kubernetes.io/affinity
注釋。
相關實現:
相關超時配置
全局超時配置
可通過以下配置項進行Ingress Nginx的全局超時配置:
配置項 | 描述 | 默認值 |
| 設置與代理服務器建立連接的超時時間。 | 默認5s,但通常不能超過75s。 |
| 設置從代理服務器讀取響應的超時。該超時僅在兩個連續的讀取操作之間設置,而不是為整個響應的傳輸設置。 | 60s |
| 設置向代理服務器傳輸請求的超時。該超時只在兩個連續的寫操作之間設置,而不是為整個請求的傳輸設置。 | 60s |
| 限制允許將連接傳遞到下一個服務器的時間。 | 默認為 600s,設置為0值則關閉此限制。 |
| 設置客戶端或代理服務器連接上兩個連續的讀或寫操作之間的超時。如果在這個時間內沒有傳輸數據,連接就會關閉 | 600s |
| 設置一個超時時間,在這個時間內,與上游服務器的空閑連接將保持開放。 |
|
| 設置優雅停機的超時時間。 | 240s |
| 設置接收代理協議頭文件的超時值。默認的5秒可以防止TLS直通處理程序無限期地等待一個中斷的連接。 | 5s |
| 設置 SSL 會話緩存中的會話參數的有效時間。會話過期時間是相對于創建時間而言的。每個會話緩存會占用大約0.25MB的空間。 | 10m |
| 定義讀取客戶端請求正文的超時。 | 60s |
| 定義讀取客戶端請求頭的超時。 | 60s |
特定的資源自定義超時配置
以下是特定的資源自定義超時配置,以及相關的參數配置。
配置項 | 描述 |
| 設置代理連接超時時間。 |
| 設置代理發送超時時間。 |
| 設置代理讀取超時時間。 |
| 配置重試策略或者重試條件,可以使用多個組合用空格分隔,例如設置為
|
| 如果滿足重試條件,則可用的重試次數。 |
| 是否啟用請求緩沖功能。
|
通用全局配置ConfigMap
了解更多詳情信息,請參見Nginx-configuration_configmap。
其他Annotation
了解更多詳情信息,請參見Nginx-configuration_annotations。
自定義配置snippet
nginx.ingress.kubernetes.io/configuration-snippet
:對應配置會生效到Location塊下。nginx.ingress.kubernetes.io/server-snippet
:對應配置會生效到Server塊下。nginx.ingress.kubernetes.io/stream-snippet
:對應配置會生效到Stream塊下。
Nginx Ingress數據面配置
Nginx Ingress的數據平面是通過結合Nginx和ngx_lua
模塊(即OpenResty)來實現的。Nginx采用多模塊化設計,把HTTP請求處理切分成多個階段,允許多個模塊協同工作,其中每個模塊負責處理一個獨立且簡單的功能。使得處理更高效、更可靠,并提高了系統的可擴展性。
OpenResty依托NGINX的處理階段,在Rewrite/Access階段、Content階段和Log階段注入了自定義的Handler。加上系統啟動的初始階段,即Master階段,總計OpenResty提供了11個階段,這些階段賦予了 Lua腳本在HTTP請求處理過程中介入的能力。接下來是OpenResty主要可用階段的圖示描述:
HTTP塊
http {
lua_package_path "/etc/nginx/lua/?.lua;;";
lua_shared_dict balancer_ewma 10M;
lua_shared_dict balancer_ewma_last_touched_at 10M;
lua_shared_dict balancer_ewma_locks 1M;
lua_shared_dict certificate_data 20M;
lua_shared_dict certificate_servers 5M;
lua_shared_dict configuration_data 20M;
lua_shared_dict global_throttle_cache 10M;
lua_shared_dict ocsp_response_cache 5M;
...
}
init_by_lua_block
init_by_lua_block {
collectgarbage("collect")
-- init modules
local ok, res
ok, res = pcall(require, "lua_ingress")
if not ok then
error("require failed: " .. tostring(res))
else
lua_ingress = res
lua_ingress.set_config({
use_forwarded_headers = false,
use_proxy_protocol = false,
is_ssl_passthrough_enabled = false,
http_redirect_code = 308,
listen_ports = { ssl_proxy = "442", https = "443" },
hsts = true,
hsts_max_age = 15724800,
hsts_include_subdomains = true,
hsts_preload = false,
global_throttle = {
memcached = {
host = "", port = 11211, connect_timeout = 50, max_idle_timeout = 10000, pool_size = 50,
},
status_code = 429,
}
})
end
ok, res = pcall(require, "configuration")
if not ok then
error("require failed: " .. tostring(res))
else
configuration = res
configuration.prohibited_localhost_port = '10246'
end
ok, res = pcall(require, "balancer")
if not ok then
error("require failed: " .. tostring(res))
else
balancer = res
end
ok, res = pcall(require, "monitor")
if not ok then
error("require failed: " .. tostring(res))
else
monitor = res
end
ok, res = pcall(require, "certificate")
if not ok then
error("require failed: " .. tostring(res))
else
certificate = res
certificate.is_ocsp_stapling_enabled = false
end
ok, res = pcall(require, "plugins")
if not ok then
error("require failed: " .. tostring(res))
else
plugins = res
end
...
}
初始化加載了Lua相關模塊:
configuration
balancer
monitor
certificate
plugins
init_worker_by_lua_block
init_worker_by_lua_block {
lua_ingress.init_worker()
balancer.init_worker()
monitor.init_worker(10000)
plugins.run()
}
upstream和balancer_by_lua_block
upstream upstream_balancer {
### Attention!!!
#
# We no longer create "upstream" section for every backend.
# Backends are handled dynamically using Lua. If you would like to debug
# and see what backends ingress-nginx has in its memory you can
# install our kubectl plugin https://kubernetes.github.io/ingress-nginx/kubectl-plugin.
# Once you have the plugin you can use "kubectl ingress-nginx backends" command to
# inspect current backends.
#
###
server 0.0.0.1; # placeholder
balancer_by_lua_block {
balancer.balance()
}
keepalive 8000;
keepalive_time 1h;
keepalive_timeout 60s;
keepalive_requests 10000;
}
Stream塊
stream {
lua_package_path "/etc/nginx/lua/?.lua;/etc/nginx/lua/vendor/?.lua;;";
lua_shared_dict tcp_udp_configuration_data 5M;
resolver 192.168.0.10 valid=30s;
init_by_lua_block {
collectgarbage("collect")
-- init modules
local ok, res
ok, res = pcall(require, "configuration")
if not ok then
error("require failed: " .. tostring(res))
else
configuration = res
end
ok, res = pcall(require, "tcp_udp_configuration")
if not ok then
error("require failed: " .. tostring(res))
else
tcp_udp_configuration = res
tcp_udp_configuration.prohibited_localhost_port = '10246'
end
ok, res = pcall(require, "tcp_udp_balancer")
if not ok then
error("require failed: " .. tostring(res))
else
tcp_udp_balancer = res
end
}
init_worker_by_lua_block {
tcp_udp_balancer.init_worker()
}
lua_add_variable $proxy_upstream_name;
log_format log_stream '[$remote_addr] [$time_local] $protocol $status $bytes_sent $bytes_received $session_time';
access_log off;
error_log /var/log/nginx/error.log notice;
upstream upstream_balancer {
server 0.0.0.1:1234; # placeholder
balancer_by_lua_block {
tcp_udp_balancer.balance()
}
}
server {
listen 127.0.0.1:10247;
access_log off;
content_by_lua_block {
tcp_udp_configuration.call()
}
}
}
同HTTP類似,init_by_lua_block
部分加載了TCP相關的Lua模塊并進行了初始化。
日志相關
access_log off;
error_log /var/log/nginx/error.log notice;
關閉了
access_log
。錯誤日志級別設置為了
notice
, 還可以設置為debug
(debug_core
、debug_alloc
、debug_event
、debug_http
、...) 、info
、warn
、error
、crit
、alert
、emerg
等值,級別越高記錄的信息越少。
執行以下命令,可以通過查看Nginx Ingress Pod日志是否有相關報錯信息。
kubectl logs -f <nginx-ingress-pod-name> -n kube-system |grep -E '^[WE]'
相關文檔
關于Nginx Ingress的配置,請參見社區文檔。
關于Nginx Ingress排查方法,請參見常見檢查方法。
關于Nginx Ingress高級用法,請參見Nginx Ingress高級用法。