本人是分布式的新手, 在实际工作中遇到了需要动态修改 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
- user www www;
- worker_processes 2;
- pid /var/run/nginx.pid;
- # [ debug | info | notice | warn | error | crit ]
- error_log /var/log/nginx.error_log info;
- events {
- worker_connections 2000;
- # use [ kqueue | epoll | /dev/poll | select | poll ];
- use kqueue;
- }
- http {
- include conf/mime.types;
- default_type application/octet-stream;
- 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"';
- client_header_timeout 3m;
- client_body_timeout 3m;
- send_timeout 3m;
- client_header_buffer_size 1k;
- large_client_header_buffers 4 4k;
- gzip on;
- gzip_min_length 1100;
- gzip_buffers 4 8k;
- gzip_types text/plain;
- output_buffers 1 32k;
- postpone_output 1460;
- sendfile on;
- tcp_nopush on;
- tcp_nodelay on;
- send_lowat 12000;
- keepalive_timeout 75 20;
- #lingering_time 30;
- #lingering_timeout 10;
- #reset_timedout_connection on;
- server {
- listen one.example.com;
- server_name one.example.com www.one.example.com;
- access_log /var/log/nginx.access_log main;
- location / {
- proxy_pass http://127.0.0.1/;
- proxy_redirect off;
- proxy_set_header Host $host;
- proxy_set_header X-Real-IP $remote_addr;
- #proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- client_max_body_size 10m;
- client_body_buffer_size 128k;
- client_body_temp_path /var/nginx/client_body_temp;
- proxy_connect_timeout 70;
- proxy_send_timeout 90;
- proxy_read_timeout 90;
- proxy_send_lowat 12000;
- proxy_buffer_size 4k;
- proxy_buffers 4 32k;
- proxy_busy_buffers_size 64k;
- proxy_temp_file_write_size 64k;
- proxy_temp_path /var/nginx/proxy_temp;
- charset koi8-r;
- }
- error_page 404 /404.HTML;
- location = /404.HTML {
- root /spool/www;
- }
- location /old_stuff/ {
- rewrite ^/old_stuff/(.*)$ /new_stuff/$1 permanent;
- }
- location /download/ {
- valid_referers none blocked server_names *.example.com;
- if ($invalid_referer) {
- #rewrite ^/ http://www.example.com/;
- return 403;
- }
- #rewrite_log on;
- # rewrite /download/*/mp3/*.any_ext to /download/*/mp3/*.mp3
- rewrite ^/(download/.*)/mp3/(.*)\..*$
- /$1/mp3/$2.mp3 break;
- root /spool/www;
- #autoindex on;
- access_log /var/log/nginx-download.access_log download;
- }
- location ~* \.(jpg|jpeg|gif)$ {
- root /spool/www;
- access_log off;
- expires 30d;
- }
- }
- }
从 18 行到 131 行属于 http 配置内容, 在这部分参数中, 第 61 行到 130 行属于 server 配置内容,(一个 server 对应一个虚拟主机),server 的参数属于 http 参数的子集, 当相同参数出现时, server 优先级会高于 http. 按照作用域来做类比, http 就是全局变量, server 就是局部变量.
所以 18 行到 60 行属于全局变量, 而 61 行到 130 则属于局部变量. 为了简化后面的操作, 我们可以简化 http 和 server 之间的包含关系, 如下:
- user nginx;
- worker_processes 1;
- error_log /var/log/nginx/error.log warn;
- pid /var/run/nginx.pid;
- events {
- worker_connections 1024;
- }
- http {
- include /etc/nginx/mime.types;
- default_type application/octet-stream;
- 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"';
- access_log /var/log/nginx/access.log main;
- sendfile on;
- keepalive_timeout 65;
- server {
- listen 80 default_server;
- server_name _;
- location /status {
- vhost_traffic_status_display;
- vhost_traffic_status_display_format HTML;
- }
- }
- include /etc/nginx/conf.d/*.conf;
- }
通过 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 是依赖结构来理解配置的, 那么它应该会规定严谨的配置结构). 所以我们可以事先定义好每个配置文件的配置格式, 如下:
- upstream 5d148ba37f325500011770af {
- server xxxxx ;
- }
- server{
- server_name web1.example.com;
- location /server1 {
- proxy_pass http://5d148ba37f325500011770af;
- proxy_set_header X-Real-IP $remote_addr;
- proxy_set_header Host $host;
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- proxy_next_upstream error timeout http_500 http_502 http_503 http_504 non_idempotent;
- }
- }
每个配置文件都规定好配置结构如下:
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 辅助定位数据, 例如下图中的数据:
- ### [5d148ba37f325500011770af]-[/]-[upstream]-[start]
- upstream 5d148ba37f325500011770af {
- server xxxxx ;
- }
- ### [5d148ba37f325500011770af]-[/]-[upstream]-[end]
- server{
- server_name web1.example.com;
- location /server1 {
- proxy_pass http://5d148ba37f325500011770af;
- proxy_set_header X-Real-IP $remote_addr;
- proxy_set_header Host $host;
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- proxy_next_upstream error timeout http_500 http_502 http_503 http_504 non_idempotent;
- }
- }
第二行和第六行就是添加的锚点. 锚点数据需要满足的条件是:
同一个配置文件中不重复
有良好的区分度
因此设计了上述的锚点数据, 其格式如下:
- ### [5d148ba37f325500011770af]-[/]-[upstream]-[start]
- ----------------------------------------------------
- ### [24 位随机数]-[/]-[upstream]-[开始 / 结束标示]
- 1 2 3 4
1 三个 #开头
2 满足锚点, upstream 名称和 proxy_pass 一致, 也就是第二行, 第三行和第十六行使用同一个 24 位随机数
3 固定格式, 用来保证和其它注释信息不重复
4 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 就可以, 后续的数据原样复制不要做变更.
来源: https://www.cnblogs.com/vikings-blog/p/11675270.html