本人是分布式的新手,在实际工做中遇到了须要动态修改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
就能够,后续的数据原样复制不要作变动。