本人是分布式的新手,在實際工作中遇到了需要動態修改nginx的需求,因此寫下實現過程中的想法。Nginx功能強大且靈活,所以這些權當拋磚引玉,希望可以得到大家的討論和指點。(具體代碼在 https://andy-zhangtao.github.io/nginx2svg/ )
如何動態配置Nginx參數
Nginx參數眾多,並且配置是非靈活,因此要達到完美的自動化配置是一件很有挑戰性的事情,這個工具並不能十分完美的自動化調整參數。目前支持自動化修改的參數有:
- server
- upstream
- proxy_pass
- root
下面將介紹Nginx2Svg
是如何實現自動化修改參數的。
預備知識
為了更好的理解Nginx2Svg
,需要一些很簡單的預備知識。 首先需要了解Nginx的配置文件格式,一個典型的Nginx配置文件(假設此處Nginx作為7層反向負載使用)看起來應該是下面的樣子:
# 抄自nginx官網 http://nginx.org/en/docs/example.html
1 user www www;
2
3 worker_processes 2;
4
5 pid /var/run/nginx.pid;
6
7 # [ debug | info | notice | warn | error | crit ]
8
9 error_log /var/log/nginx.error_log info;
10
11 events {
12 worker_connections 2000;
13
14 # use [ kqueue | epoll | /dev/poll | select | poll ];
15 use kqueue;
16 }
17
18 http {
19
20 include conf/mime.types;
21 default_type application/octet-stream;
22
23
24 log_format main '$remote_addr - $remote_user [$time_local] '
25 '"$request" $status $bytes_sent '
26 '"$http_referer" "$http_user_agent" '
27 '"$gzip_ratio"';
28
29 log_format download '$remote_addr - $remote_user [$time_local] '
30 '"$request" $status $bytes_sent '
31 '"$http_referer" "$http_user_agent" '
32 '"$http_range" "$sent_http_content_range"';
33
34 client_header_timeout 3m;
35 client_body_timeout 3m;
36 send_timeout 3m;
37
38 client_header_buffer_size 1k;
39 large_client_header_buffers 4 4k;
40
41 gzip on;
42 gzip_min_length 1100;
43 gzip_buffers 4 8k;
44 gzip_types text/plain;
45
46 output_buffers 1 32k;
47 postpone_output 1460;
48
49 sendfile on;
50 tcp_nopush on;
51 tcp_nodelay on;
52 send_lowat 12000;
53
54 keepalive_timeout 75 20;
55
56 #lingering_time 30;
57 #lingering_timeout 10;
58 #reset_timedout_connection on;
59
60
61 server {
62 listen one.example.com;
63 server_name one.example.com www.one.example.com;
64
65 access_log /var/log/nginx.access_log main;
66
67 location / {
68 proxy_pass http://127.0.0.1/;
69 proxy_redirect off;
70
71 proxy_set_header Host $host;
72 proxy_set_header X-Real-IP $remote_addr;
73 #proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
74
75 client_max_body_size 10m;
76 client_body_buffer_size 128k;
77
78 client_body_temp_path /var/nginx/client_body_temp;
79
80 proxy_connect_timeout 70;
81 proxy_send_timeout 90;
82 proxy_read_timeout 90;
83 proxy_send_lowat 12000;
84
85 proxy_buffer_size 4k;
86 proxy_buffers 4 32k;
87 proxy_busy_buffers_size 64k;
88 proxy_temp_file_write_size 64k;
89
90 proxy_temp_path /var/nginx/proxy_temp;
91
92 charset koi8-r;
93 }
94
95 error_page 404 /404.html;
96
97 location = /404.html {
98 root /spool/www;
99 }
100
101 location /old_stuff/ {
102 rewrite ^/old_stuff/(.*)$ /new_stuff/$1 permanent;
103 }
104
105 location /download/ {
106
107 valid_referers none blocked server_names *.example.com;
108
109 if ($invalid_referer) {
110 #rewrite ^/ http://www.example.com/;
111 return 403;
112 }
113
114 #rewrite_log on;
115
116 # rewrite /download/*/mp3/*.any_ext to /download/*/mp3/*.mp3
117 rewrite ^/(download/.*)/mp3/(.*)\..*$
118 /$1/mp3/$2.mp3 break;
119
120 root /spool/www;
121 #autoindex on;
122 access_log /var/log/nginx-download.access_log download;
123 }
124
125 location ~* \.(jpg|jpeg|gif)$ {
126 root /spool/www;
127 access_log off;
128 expires 30d;
129 }
130 }
131 }
從18行到131行屬於http
配置內容,在這部分參數中,第61行到130行屬於server
配置內容,(一個server對應一個虛擬主機),server
的參數屬於http
參數的子集,當相同參數出現時,server
優先級會高於http
。按照作用域來做類比,http
就是全局變量,server
就是局部變量。
所以18行到60行屬於全局變量,而61行到130則屬於局部變量。 為了簡化后面的操作,我們可以簡化http
和server
之間的包含關系,如下:
1 user nginx;
2 worker_processes 1;
3
4 error_log /var/log/nginx/error.log warn;
5 pid /var/run/nginx.pid;
6
7
8 events {
9 worker_connections 1024;
10 }
11
12
13 http {
15 include /etc/nginx/mime.types;
16 default_type application/octet-stream;
17
18 log_format main '$remote_addr - $remote_user [$time_local] '
19 '"$request" $status $bytes_sent '
20 '"$http_referer" "$http_user_agent" '
21 '"$gzip_ratio"';
22
23 log_format download '$remote_addr - $remote_user [$time_local] '
24 '"$request" $status $bytes_sent '
25 '"$http_referer" "$http_user_agent" '
26 '"$http_range" "$sent_http_content_range"';
27
28 access_log /var/log/nginx/access.log main;
29
30 sendfile on;
31
32 keepalive_timeout 65;
33
34
35 server {
36 listen 80 default_server;
37 server_name _;
38
39 location /status {
40 vhost_traffic_status_display;
41 vhost_traffic_status_display_format html;
42 }
43 }
44
45 include /etc/nginx/conf.d/*.conf;
46 }
通過include
引入其它server配置文件,而上面的內容可以作為nginx.conf
全局默認配置文件,基本就不再修改了。而以后我們所要動態修改的配置文件就是/etc/nginx/conf.d/*.conf
這部分。
配置規則
如果要達到自動化配置的目標,那么就需要設定一些規則。 下面是為了滿足自動化而設置的規則:
- 配置文件規則
- 必須存在server_name。
- 文件名以[server name].conf進行命名。 假設server_name為example.com, 則配置文件名就是example.com.conf。
- 一個文件有並且只有一個server段
- 配置內容規則
- 同一個配置文件中location不重復(正則表達式不在限制范圍內)
解析規則
在滿足上述兩個規則的前提下,我們來看如何實現Nginx參數的自動化配置。首先要明確實現nginx自動化配置的難點在哪里? 基於我的使用經驗來看,難點在於以下三點:
-
nginx配置相當靈活,屬於
非結構化
語義
雖然nginx明確了配置文件的內容和格式,但在配置上可以任意組合(在執行nginx -t或者reload時才會真正驗證)。因此配置文件只規定了最低門檻的結構范式
,而並沒有規定嚴謹的配置格式,造成了只要符合語義都可以驗證成功。這一點在使用者眼里是非常靈活的優點,但從自動化角度來說則是很大的痛點,因為找不到一個統一的解析格式來理解語義。 -
驗證和回滾
nginx是基於文本來進行配置的,每一次修改都是通過IO操作生成文本配置文件而后在加載在每個worker中。 因此當驗證失敗時,如何將新增/刪除的內容恢復到上一個版本中,就變成了一個問題。 -
個性化配置
在真實業務場景中,nginx配置必然無法做到一個配置吃遍天。當某些server需要添加個性化配置參數時,如何平衡個性化配置和自動化配置,也變成了一個需要考慮的問題。
當找到上述三個問題的答案時,大體就可以滿足自動化配置的要求了。
首先來看第一個問題。
如果因為nginx配置靈活而導致正面解析nginx配置文件是一個很困難的事情,那么可以嘗試換個角度來理解這個問題。 如果變化很多而不容易解析,那么就不要讓它變化了
具體怎么理解呢? nginx是通過語義來驗證的,也就是nginx自身其實對結構
不敏感的(可以反向證明,如果nginx是依賴結構來理解配置的,那么它應該會規定嚴謹的配置結構)。所以我們可以事先定義好每個配置文件的配置格式,如下:
1
2
3 upstream 5d148ba37f325500011770af {
4 server xxxxx ;
5 }
6
7
8 server{
9
10 server_name web1.example.com;
11
12
13
14
15 location /server1 {
16 proxy_pass http://5d148ba37f325500011770af;
17 proxy_set_header X-Real-IP $remote_addr;
18 proxy_set_header Host $host;
19 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
20 proxy_next_upstream error timeout http_500 http_502 http_503 http_504 non_idempotent;
21
22
23
24 }
25
26 }
27
每個配置文件都規定好配置結構如下:
upstream
都統一放置在server
之前server_name
放置在location
之前proxy_pass
放置在每個location
首行
當每個配置文件都滿足上述三個條件時,自動化解析程序就可以按照設定好的規則解析並嘗試理解每段語義。
只解析文件還不夠,還需要能動態修改
才可以。 再回到上面的配置內容,里面的變量有三部分,按照從上往下依次是:
- upstream的server IP列表
- server_name中的domain列表
- location列表
動態修改更准確的就是如何動態修改上面三部分值,這三部分的關聯關系如下:
+-------------+
| server_name |
| domain1 |
| domain2 | +-----------------+ +-----------------+
| domain3 |---------------> | location1 |--------------> | upstream1 |
| ....... | +-----------------+ +-----------------+
| domainN |
+-------------+
+-----------------+ +-----------------+
| location2 |--------------> | upstream2 |
+-----------------+ +-----------------+
+-----------------+ +-----------------+
| locationN |--------------> | upstreamN |
+-----------------+ +-----------------+
同一個組的server_name
共享所有的location
數據,而每一個location
則通過proxy_pass
指向特定的upstream
(可以是不同的,也可以是相同的upstream)。
從上圖可以看出server_name
和location
在一個作用域中(在同一個{}
中)而upstream
則游離在外。
三個問題中,server_name可以通過server_name
准確定位,location
也可以准確定位,此時如何從location
通過proxy_pass
定位到upstream
則變成了當前的難點。
在實際使用過程中,我通過添加錨點
來解決這個問題,具體來說就是增加一組upstream
輔助定位數據,例如下圖中的數據:
1
2 ### [5d148ba37f325500011770af]-[/]-[upstream]-[start]
3 upstream 5d148ba37f325500011770af {
4 server xxxxx ;
5 }
6 ### [5d148ba37f325500011770af]-[/]-[upstream]-[end]
7
8 server{
9
10 server_name web1.example.com;
11
12
13
14
15 location /server1 {
16 proxy_pass http://5d148ba37f325500011770af;
17 proxy_set_header X-Real-IP $remote_addr;
18 proxy_set_header Host $host;
19 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
20 proxy_next_upstream error timeout http_500 http_502 http_503 http_504 non_idempotent;
21
22
23
24 }
25
26 }
27
第二行和第六行就是添加的錨點
。 錨點數據需要滿足的條件是:
- 同一個配置文件中不重復
- 有良好的區分度
因此設計了上述的錨點
數據,其格式如下:
### [5d148ba37f325500011770af]-[/]-[upstream]-[start]
----------------------------------------------------
### [24位隨機數]-[/]-[upstream]-[開始/結束標示]
① ② ③ ④
① 三個#開頭
② 滿足錨點,upstream名稱和proxy_pass一致,也就是第二行,第三行和第十六行使用同一個24位隨機數
③ 固定格式,用來保證和其它注釋信息不重復
④ start表示upstream開始, end表示upstream結束。
因此一個完整的自動化配置流程如下:
// 假設配置web1.example.com的/server1 反向配置
if web1.example.com.conf 存在
逐行讀取文件內容
if 找到 server1的location行
解析 proxy_pass,找到 24位隨機數
從頭開始讀取文件內容
if 找到 ### [xxxx]-[/]-[upstream]-[start]
找到錨點,此行往下兩行是ip列表,開始修改
else
沒找到錨點,配置文件出錯,人工介入
else
// 當前沒有此location配置,新建location和upstream
新建location配置
新建相匹配的upstream配置
else
// 當前沒有此域名配置,新建一個
創建 web1.example.com.conf,內容按照既定格式創建
個性化支持
從上面的解析規則來看,如果要支持個性化支持,那么在理解語義時要做到適可而止
,也就是只需要解析到需要的數據就可以了,其它數據原樣復制。例如用戶在location
中添加了個性化參數(需要滿足配置規則第三條
),那么只要解析出proxy_pass
就可以,后續的數據原樣復制不要做變更。