基于Nginx dyups模块的动态上下线
基于以上这些情况, 我计划实现一个工具,这个工具首先解决站点上下线和动态扩容问题,也就是说在不需要重启nginx的情况下,并且在保证请求不丢失的情况下来更新站点。 同时带有部分服务治理功能。

服务上线
在一个新服务上线的时候,一般会提前申请几台机器, 运维会在nginx上新增server,并新增server对应的upstream ,正常情况下upstream应该配置是后端服务器的IP,但是这里不配置(如果允许,甚至这一步都可以省略)。
服务部署好并启动,在启动的时候,向注册中心注册自身的服务信息,包括IP和端口。
注册中心收到请求后,会对服务进行健康检测,确保提供的服务没有问题,则将服务状态标示为预上线状态。
在后台管理中心,就可以将预上线的服务设置为上线,服务管理中心会调用nginx的上线接口,将服务IP新增或者更新到upstream中,服务就可以提供访问。
服务更新
假如我们现在有一个服务需要更新,则执行以下步骤:
在后台管理中心,将一个服务设为下线,此时服务中心会调用nginx的下线接口,将指定服务器的IP设置为下线。
在等待1分钟后,确保没有新连接连过来,则可以开始更新服务站点。
更新完毕后,再手动设为上线,此时服务中心会调用nginx的上线接口,将指定服务器的IP设置为上线。当然对于成熟的服务,这些都可以自动化,有些公司会有一些自动化发布工具, 与自动化发布工具集成,可以一键下线,更新并上线。
服务运行期间
在服务运行过程中,会有一个健康检测的服务对所有提供服务的站点进行健康检测,一旦检测到有问题,就执行下线逻辑。 直到问题被解决,最后执行上线流程。
动态加减机器
在服务运行过程中,可能因为某些原因,服务请求飙高(前提是这些请求都是合法的),超过了当前集群的承载能力,当系统检测到这些情况后,可以动态扩充机器,比如现在流行的docker,在启动容器的时候,同时启动应用,应用在启动的时候,将自身信息注册给注册中心,注册中心再将这些信息同步到nginx,应用就可以提供访问,整体上就可以实现弹性计算。
为什么不实现服务动态发现?
这里可以看到图中已经有一个服务注册中心。 既然有了服务注册中心了, 那可以让业务站点连接服务注册中心来获取真实的服务IP,然后绕过nginx来连接服务,这里之所以没有这样做,是因为:
实现服务动态发现,这个需要和RPC框架配合,而且需要做服务的软负载,失败重连,限流等,整个项目设计就上升了一个复杂度, 考虑到有些项目还未使用RPC,并且不想对原有的项目有过多的侵入, 所以这里不做实现。 但是并不意味没有这些功能,服务的负载, 失败重连, 限流,其实这些功能在nginx中同样也有,可以直接使用,所以没有必要重新再开发。
实现服务动态发现,获取到真实的服务IP,然后直连,这些一般是在流量特别大,nginx上出现短板的时候使用,但实际情况,一般很少会耗尽nginx的性能,即使有,也可以通过ngxin水平扩展来实现,所以这里依然使用nginx作为负载均衡。
这里讲一下这个项目的关键点:
服务的注册和健康检测这个没有技术难点,这里不做解释。
关于操作nginx上下线,这里的确是一个难点,因为nginx本身并没有提供这些上下线API,需要openresty并配合一些第三方扩展来实现。 这里主要用到了两个扩展模块:ngx_http_dyups_module lua-upstream-nginx-module
ngx_http_dyups_module(https://github.com/yzprofile/ngx_http_dyups_module)提供了粗粒度的upstream管理方法,可以对整个upstream进行新增,删除。
lua-upstream-nginx-module(https://github.com/openresty/lua-upstream-nginx-module) ,则提供了细粒度的管理方式,可以对某一个服务IP进行管理,其中提供的set_peer_down方法,可以对upstream中的某个ip进行上下线。
也可以使用ngx_dynamic_upstream(https://github.com/cubicdaiya/ngx_dynamic_upstream)
这些插件有一个共同点,那就是在不需要重启nginx的基础上, 动态修改nginx的配置。
方案一:模块ngx_dynamic_upstream,api+python/shell方式(直接操作upstream里的server)
因为api接口操作不是太直观,可以在django上以web方式增删查改upstream
Directivesdynamic_upstream
Syntax | dynamic_upstream |
---|---|
Default | - |
Context | location |
Now ngx_dynamic_upstream
supports dynamic upstream under only http
context.
upstream backends {
server 127.0.0.1:6001;
server 127.0.0.1:6002;
server 127.0.0.1:6003;
zone zone_for_backends 1m;
}
server {
listen 6000;
location /dynamic {
allow 127.0.0.1;
deny all;
dynamic_upstream;
}
location / {
proxy_pass http://backends;
}
}
HTTP APIs
You can operate upstreams dynamically with HTTP APIs.
list
$ curl "http://127.0.0.1:6000/dynamic?upstream=zone_for_backends"
server 127.0.0.1:6001;
server 127.0.0.1:6002;
server 127.0.0.1:6003;
$
verbose
$ curl "http://127.0.0.1:6000/dynamic?upstream=zone_for_backends&verbose="
server 127.0.0.1:6001 weight=1 max_fails=1 fail_timeout=10;
server 127.0.0.1:6002 weight=1 max_fails=1 fail_timeout=10;
server 127.0.0.1:6003 weight=1 max_fails=1 fail_timeout=10;
$
update_parameters
$ curl "http://127.0.0.1:6000/dynamic?upstream=zone_for_backends&server=127.0.0.1:6003&weight=10&max_fails=5&fail_timeout=5"
server 127.0.0.1:6001 weight=1 max_fails=1 fail_timeout=10;
server 127.0.0.1:6002 weight=1 max_fails=1 fail_timeout=10;
server 127.0.0.1:6003 weight=10 max_fails=5 fail_timeout=5;
$
The supported parameters are blow.
weight
max_fails
fail_timeout
down
$ curl "http://127.0.0.1:6000/dynamic?upstream=zone_for_backends&server=127.0.0.1:6003&down="
server 127.0.0.1:6001 weight=1 max_fails=1 fail_timeout=10;
server 127.0.0.1:6002 weight=1 max_fails=1 fail_timeout=10;
server 127.0.0.1:6003 weight=1 max_fails=1 fail_timeout=10 down;
$
up
$ curl "http://127.0.0.1:6000/dynamic?upstream=zone_for_backends&server=127.0.0.1:6003&up="
server 127.0.0.1:6001 weight=1 max_fails=1 fail_timeout=10;
server 127.0.0.1:6002 weight=1 max_fails=1 fail_timeout=10;
server 127.0.0.1:6003 weight=1 max_fails=1 fail_timeout=10;
$
add
$ curl "http://127.0.0.1:6000/dynamic?upstream=zone_for_backends&add=&server=127.0.0.1:6004"
server 127.0.0.1:6001;
server 127.0.0.1:6002;
server 127.0.0.1:6003;
server 127.0.0.1:6004;
$
remove
$ curl "http://127.0.0.1:6000/dynamic?upstream=zone_for_backends&remove=&server=127.0.0.1:6003"
server 127.0.0.1:6001;
server 127.0.0.1:6002;
server 127.0.0.1:6004;
$
API调用脚本:
# cat nginx-upstream.sh
#------------------------------------------------------
#!/bin/bash
nginx_list="
172.16.1.1
172.16.1.2
172.16.1.3
"
cd /data/script/nginx
upstream="$1"
if [ -n "$upstream" ]; then
upstream=${upstream//./_}
for ip in ${nginx_list}; do
if [ -n "$2" -a -n "$ip" ]; then
action=${@: -1}
if [ "$action" != "up" -a "$action" != "down" ]; then
echo "action error!"
else
echo "### 【NGINX】 $ip"
server=${@: 2:$#-2}
for s in $server; do
curl -sq "http://${ip}/dynamic?upstream=${upstream}&server=${s}&${action}=" | egrep "server.+${s}.+;$" >/dev/null || echo "Action: $action Upstream: $upstream Server: $s Failed"
done
curl -sq "http://${ip}/dynamic?upstream=${upstream}&verbose="
echo
fi
else
echo "### 【NGINX】 $ip"
curl -sq "http://${ip}/dynamic?upstream=${upstream}&verbose="
echo
fi
done
else
echo "$0 <domain> <server1> <server2> <server3> <up/down>"
fi
通过脚本调用接口:
# 禁用
nginx-upstream test.qsh.com 172.16.10.100:6001 down
# 启用
nginx-upstream test.qsh.com 172.16.10.100:6001 up
方案二:lua(lua_code_cache)热装载+upstream ,直接操作upstream
简说热装载(热加载) :lua代码产生变更,利用lua_code_cache on ,实现自动加载,不用reload nginx。
亮点: 通过lua处理,不用重启nginx,利用api进行增删查改
不足:操作还是基于命令方式,后期还得加上web操作
GET
/detail get all upstreams and their servers
/list get the list of upstreams
/upstream/name find the upstream by it's name
POST
/upstream/name update one upstream
body commands;
body server ip:port;
DELETE
/upstream/name delete one upstream
Call the interface, when you get the return code is HTTP_INTERNAL_SERVER_ERROR 500, you need to reload nginx to make the Nginx work at a good state.
If you got HTTP_CONFLICT 409, you need resend the same commands again latter.
The /list and /detail interface will return HTTP_NO_CONTENT 204 when there is no upstream.
Other code means you should modify your commands and call the interface again.
ATTENEION: You also need a third-party to generate the new config and dump it to Nginx'conf directory.
Sample
» curl -H "host: dyhost" 127.0.0.1:8080
<html>
<head><title>502 Bad Gateway</title></head>
<body bgcolor="white">
<center><h1>502 Bad Gateway</h1></center>
<hr><center>nginx/1.3.13</center>
</body>
</html>
» curl -d "server 127.0.0.1:8089;server 127.0.0.1:8088;" 127.0.0.1:8081/upstream/dyhost
success
» curl -H "host: dyhost" 127.0.0.1:8080
8089
» curl -H "host: dyhost" 127.0.0.1:8080
8088
» curl 127.0.0.1:8081/detail
host1
server 127.0.0.1:8088
host2
server 127.0.0.1:8089
dyhost
server 127.0.0.1:8089
server 127.0.0.1:8088
» curl -i -X DELETE 127.0.0.1:8081/upstream/dyhost
success
» curl 127.0.0.1:8081/detail
host1
server 127.0.0.1:8088
host2
server 127.0.0.1:8089
API
extern ngx_flag_t ngx_http_dyups_api_enable;
ngx_int_t ngx_dyups_update_upstream(ngx_str_t *name, ngx_buf_t *buf,
ngx_str_t *rv);
ngx_int_t ngx_dyups_delete_upstream(ngx_str_t *name, ngx_str_t *rv);
extern ngx_dyups_add_upstream_filter_pt ngx_dyups_add_upstream_top_filter;
extern ngx_dyups_del_upstream_filter_pt ngx_dyups_del_upstream_top_filter;
NOTICE: you should add the directive dyups_interface into your config file to active this feature
content_by_lua '
local dyups = require "ngx.dyups"
local status, rv = dyups.update("test", [[server 127.0.0.1:8088;]]);
ngx.print(status, rv)
if status ~= ngx.HTTP_OK then
ngx.print(status, rv)
return
end
ngx.print("update success")
status, rv = dyups.delete("test")
if status ~= ngx.HTTP_OK then
ngx.print(status, rv)
return
end
ngx.print("delete success")
';
方案三:模块fly操作upstream;web方式增删查改upsterem里的server

# 无drop重启nginx
kill -HUP `cat /data/nginx/logs/nginx.pid`
#!/bin/sh
BASE_DIR='/usr/local/'
${BASE_DIR}nginx/sbin/nginx -t -c ${BASE_DIR}nginx/conf/nginx.conf >& ${BASE_DIR}nginx/logs/nginx.start
info=`cat ${BASE_DIR}nginx/logs/nginx.start`
if [ `echo $info | grep -c "syntax is ok" ` -eq 1 ]; then
if [ `ps aux|grep "nginx"|grep -c "master"` == 1 ]; then
kill -HUP `cat ${BASE_DIR}nginx/logs/nginx.pid`
echo "ok"
else
killall -9 nginx
sleep 1
${BASE_DIR}nginx/sbin/nginx
fi
else
echo "######## error: ########"
cat ${BASE_DIR}nginx/logs/nginx.start
fi