本人是分佈式的新手,在實際工做中遇到了須要動態修改nginx的需求,所以寫下實現過程當中的想法。Nginx功能強大且靈活,因此這些權當拋磚引玉,但願能夠獲得你們的討論和指點。(具體代碼在 https://andy-zhangtao.github.io/nginx2svg/ )html
Nginx參數衆多,而且配置是非靈活,所以要達到完美的自動化配置是一件頗有挑戰性的事情,這個工具並不能十分完美的自動化調整參數。目前支持自動化修改的參數有:node
下面將介紹Nginx2Svg
是如何實現自動化修改參數的。nginx
爲了更好的理解Nginx2Svg
,須要一些很簡單的預備知識。 首先須要瞭解Nginx的配置文件格式,一個典型的Nginx配置文件(假設此處Nginx做爲7層反向負載使用)看起來應該是下面的樣子:git
# 抄自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
就是局部變量。github
因此18行到60行屬於全局變量,而61行到130則屬於局部變量。 爲了簡化後面的操做,咱們能夠簡化http
和server
之間的包含關係,以下:web
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
這部分。正則表達式
若是要達到自動化配置的目標,那麼就須要設定一些規則。 下面是爲了知足自動化而設置的規則:shell
在知足上述兩個規則的前提下,咱們來看如何實現Nginx參數的自動化配置。首先要明確實現nginx自動化配置的難點在哪裏? 基於個人使用經驗來看,難點在於如下三點:app
nginx配置至關靈活,屬於非結構化
語義
雖然nginx明確了配置文件的內容和格式,但在配置上能夠任意組合(在執行nginx -t或者reload時纔會真正驗證)。所以配置文件只規定了最低門檻的結構範式
,而並無規定嚴謹的配置格式,形成了只要符合語義均可以驗證成功。這一點在使用者眼裏是很是靈活的優勢,但從自動化角度來講則是很大的痛點,由於找不到一個統一的解析格式來理解語義。dom
驗證和回滾
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
首行當每一個配置文件都知足上述三個條件時,自動化解析程序就能夠按照設定好的規則解析並嘗試理解每段語義。
只解析文件還不夠,還須要能動態修改
才能夠。 再回到上面的配置內容,裏面的變量有三部分,按照從上往下依次是:
動態修改更準確的就是如何動態修改上面三部分值,這三部分的關聯關係以下:
+-------------+ | 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
就能夠,後續的數據原樣複製不要作變動。