基于nginx实现上游服务器动态自动上下线无需reload的实现方法


Posted in Servers onMarch 31, 2021

网上关于nginx的介绍有很多,这里讲述的是上游服务(如下图的Java1服务)在没有“网关”的情况下,如何通过nginx做到动态上下线。

基于nginx实现上游服务器动态自动上下线无需reload的实现方法

传统的做法是,手动修改nginx的upstream文件,将Java1的配置注释或者标记为down,然后reload nginx生效。当然可以做成脚本自动化修改,然而对于一个繁忙的nginx来说,贸然reload轻则响应缓慢,重则雪崩丢失流量。

那么怎样做到nginx动态加载upstream配置呢?网上大体有3种方案:

  • 通过Lua脚本结合nginx,也就是Openresty方案;
  • 给nginx的每个server额外添加一个端口,每次通过调用这个端口修改upstream;
  • 给nginx添加数据库,upstream数据放在数据库中,通过修改数据库数据实现修改upstream配置。

对于一个正在运行的生产环境nginx来说,第3个方案无疑是成本最低的。下面让我们具体看一下:

技术方案:nginx1.16+nginx_upstream_check_module+nginx-upsync-module+consul

说明:

  • 这里的consul就是上面所说的数据库,它不只是key/value类型的库,还有一个简洁的web管理页面,可以很方便的管理键值对数据;
  • nginx_upstream_check_module是阿里开源的针对上游服务的健康检测模块;
  • nginx-upsync-module是微博开源的可以与consul/etcd结合的模块。

下面分别通过consul集群部署、nginx改造、创建upstream数据3个方面逐一讨论实施细节。

一、部署consul集群

官网:https://www.consul.io/

假设用下面3台机器组成一个Consul集群:

192.168.21.11
192.168.21.12
192.168.21.13
192.168.21.14 # 这个IP为代理IP,用于代理上面3台机器

1. 准备工作

从官网下载consul压缩包,分别上传到上面3台服务器,这里的consul版本为1.8.4:

unzip consul_1.8.4_linux_amd64.zip
mv consul /usr/local/bin/
[root@nginx-11 tmp]# consul
Usage: consul [--version] [--help] <command> [<args>]

Available commands are:
 acl Interact with Consul's ACLs
 agent Runs a Consul agent
 catalog Interact with the catalog
 ....

3台机器分别创建consul数据、日志、配置文件目录:

mkdir -p /data/consul/{data,log}
mkdir /etc/consul

2.生成consul配置文件

下面以192.168.21.11的配置文件为例:

[root@nginx-11 tmp]# cat /etc/consul/config.json
{
 "datacenter":"dc1",
 "primary_datacenter":"dc1",
 "bootstrap_expect":3,
 "start_join":[
 "192.168.21.11",
 "192.168.21.12",
 "192.168.21.13"
 ],
 "retry_join":[
 "192.168.21.11",
 "192.168.21.12",
 "192.168.21.13"
 ],
 "advertise_addr": "192.168.21.11",
 "bind_addr": "192.168.21.11",
 "client_addr": "0.0.0.0",
 "server":true,
 "connect":{
 "enabled":true
 },
 "node_name":"192.168.21.11",
 "ui": true,
 "data_dir":"/data/consul/data",
 "enable_script_checks":false,
 "enable_local_script_checks":true,
 "log_file":"/data/consul/log/",
 "log_level":"info",
 "log_rotate_bytes":100000000,
 "log_rotate_duration":"24h",
 "encrypt":"a2zC4ItisuFdpl7IqwoYz3GqwA5W1w2CxjNmyVbuhZ4=",
 "acl":{
 "enabled":true,
 "default_policy":"deny",
 "enable_token_persistence":true,
 "enable_key_list_policy":true,
 "tokens":{
 "master":"6c95012f-d086-4ef3-b6b9-35b60f529bd0"
 }
 }
}

说明:

  • 另外2台服务器的配置文件,分别将上面的advertise_addr、bind_addr、node_name对应值修改为对应IP,其他配置不需要改变;
  • 参数 "bootstrap_expect":3 意为希望部署一个3个节点的集群,请根据实际情况配置;
  • encrypt与tokens对应的值,3台机器应保持一致,encrypt值可以通过consul keygen命令生成,token值可以通过uuidgen命令生成,也可以都通过这2个工具生成;
  • 相关参数的理解可以参考:https://juejin.im/post/6844903860717240334

3. 创建consul集群

分别在3台机器上启动consul即可:

consul agent -config-file=/etc/consul/config.json &

通过浏览器访问http://192.168.21.14:8500(或者任意一个IP:Port)即可访问consul后台界面,输入上面master的tokens值可以看到里面具体内容。

注意:

  • 上面配置文件中的acl配置,“enable_key_list_policy”配置一定要加上,且值要配成“true”,否则匿名用户可能访问不到consul配置内容。

4. 为非管理员创建consul访问权限

1)创建访问策略

通过浏览器访问consul,点击ACL -> Access Controls -> Policies -> 右上角Create创建一个只读“upstreams”kv策略,名称为:readonlykv,Rules内容为:

key_prefix "upstreams/" {
 policy = "list"
}

创建一个可以写“upstreams”kv策略,名称为:writekv,Rules内容为:

key_prefix "upstreams/" {
 policy = "write"
}

创建好的2条策略截图如下:

基于nginx实现上游服务器动态自动上下线无需reload的实现方法

2)创建访问token

在匿名用户token中加入允许访问只读“upstreams”kv策略,用于允许nginx模块匿名读取consul配置:
点击00000002,在Policies中选择readonlykv即可。

创建可以写“upstreams”kv的token,用于脚本带此token修改consul配置:
通过浏览器访问consul,点击ACL -> Access Controls -> Tokens -> 右上角Create,在Policies中选择writekv。
修改/创建好的2条token截图如下:

基于nginx实现上游服务器动态自动上下线无需reload的实现方法

到此Consul集群部署完成。

二、nginx改造

1. 升级nginx

下载nginx相关模块:

nginx-upsync-module:https://github.com/weibocom/nginx-upsync-module

nginx_upstream_check_module:https://github.com/xiaokai-wang/nginx_upstream_check_module

注意:

  • 下载nginx_upstream_check_module模块时请一定到xiaokai-wang的GitHub上下载,千万不要到阿里的官方GitHub上下载,否则版本不兼容编译不过去;
  • 在对Nginx升级前请先做好数据备份。

1)对nginx_upstream_check_module打patch

cd nginx-1.16.0
patch -p1 < /usr/local/src/nginx-1.16/nginx_upstream_check_module-master/check_1.12.1+.patch

说明:我把下载的2个nginx模块源码包放在了/usr/local/src/nginx-1.16/路径下。

2)编译nginx

./configure --prefix=/usr/local/nginx --add-module=/usr/local/src/nginx-1.16/nginx_upstream_check_module-master --add-module=/usr/local/src/nginx-1.16/nginx-upsync-module-master ...

说明:

我把nginx安装在/usr/local/下面;

命令后面的省略号是你要安装的模块,请根据实际情况添加,通过nginx -V可以看到当前安装了哪些模块,然后加上去即可。

3)安装nginx

make
# 如果是平滑升级,该步不要执行 
make install

4)升级nginx

#再次备份nginx二进制文件
mv /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx16.old
#用新nginx二进制文件替换老的
cp objs/nginx /usr/local/nginx/sbin/
#查看已安装的nginx模块
/usr/local/nginx/sbin/nginx -V

提醒:经过测试发现nginx1.6通过reload或者发送kill -USR2命令,老的nginx进程并不会退出,需要重启nginx才可以生效,不知道是不是Bug。

/usr/local/nginx/sbin/nginx -s stop
#如果老的nginx进程仍未推出,使用kill -9强制杀掉ps -ef |grep nginx
#开启nginx
/usr/local/nginx/sbin/nginx 
# 说明:发送kill -USR2命令
kill -USR2 `cat /usr/local/nginx/logs/nginx.pid`

到此,nginx升级完成。

2. 配置nginx

1)首先配置nginx展示页面,用于快速了解nginx运行状态

cat nginx.conf
 server {
 listen 80;
 server_name localhost;

 # 在server 80中展示upstream,相当于全局配置,其他配置文件不需要配置          # 浏览器访问http://nginx-ip:80/upstream_show能查看到nginx upstream的具体配置信息
 location = /upstream_show {
  upstream_show;
 }

 # 在server 80中展示check详情,相当于全局配置,其他配置文件不需要配置          # 浏览器访问http://nginx-ip:80/status能查看到上游服务的健康状态,报红即为有问题,白色即为正常
 location /status {
  check_status;
 }

 # 在server 80中展示nginx自带的状态,相当于全局配置,其他配置文件不需要配置          # nginx原生自带功能
 location /NginxStatus {
  stub_status on;
  access_log off;
  allow 192.168.0.0/16;
  deny all;
 }
 }
     # 引入具体server配置,每个server需要配置nginx-upsync-module模块的配置
 include /usr/local/nginx/conf/vhosts/*.conf;

2)server配置

http方式检测

upstream rs1 {
 server 127.0.0.1:11111;
 upsync 192.168.21.14:8500/v1/kv/upstreams/rs1/ upsync_timeout=6m upsync_interval=500ms upsync_type=consul strong_dependency=off;
 upsync_dump_path /usr/local/nginx/conf/servers/servers_rs1.conf;

 check interval=1000 rise=2 fall=2 timeout=3000 type=http default_down=false;
 check_http_send "HEAD /health.htm HTTP/1.0\r\n\r\n";
 check_http_expect_alive http_2xx http_3xx;
}

server {
 listen 80;
...

tcp方式检测(tcp为默认检测方式)

upstream rs2 {
 server 127.0.0.1:11111;
 upsync 192.168.21.14:8500/v1/kv/upstreams/rs2/ upsync_timeout=6m upsync_interval=500ms upsync_type=consul strong_dependency=off;
 upsync_dump_path /usr/local/nginx/conf/servers/servers_rs2.conf;

 check interval=1000 rise=2 fall=2 timeout=3000 type=tcp default_down=false;
}

server {
 listen 80;
...

说明:

  • 推荐使用http方式检测,http比tcp方式更准确,该检测方式为nginx_upstream_check_module提供,功能强大,参数简单解释:每隔1秒进行1次健康检查,每次超时时间为3秒,连续2次健康检查成功则认为这个上游服务健康,将会被上线或一直保持在线;连续2次健康检查失败则认为这个上游服务不健康,将会被剔除下线。“/health.htm”是上游服务的健康检查接口,通过它判断服务是否健康。具体参数解释可参考:http://tengine.taobao.org/document_cn/http_upstream_check_cn.html
  • 参数简单解释:nginx-upsync-module模块会每隔0.5秒向consul数据库检查一次配置,每次超时时间为6分钟。具体参数解释可参考:https://github.com/weibocom/nginx-upsync-module
  • nginx会在/usr/local/nginx/conf目录下面创建servers子目录,该子目录下会自动创建相关server配置文件。

到此,nginx配置修改完成。

三、创建upstream数据(consul键值对)

可以通过web页面或者脚本创建upstream数据,方法如下:

1. web页面操作

如果需要创建目录,在要创建的字段后面加上"/"即可,如:upstreams/ 。

"Key/Value"中必须先创建"upstreams"目录(后面有字母s),然后再创建对应的server名称,截图如下:

基于nginx实现上游服务器动态自动上下线无需reload的实现方法

2. 命令行操作

使用命令行时不需要先创建"upstreams/"目录,命令会自动创建目录以及server数据。

下面以上游服务Java1(IP为192.168.20.100,端口号为8080,upstream分组名称为rs1)为例:

添加记录

curl -X PUT http://192.168.21.14:8500/v1/kv/upstreams/rs1/192.168.20.100:8080?token=$token

上述命令执行后,会形成一条nginx的upstream默认配置信息,即:

server 192.168.20.100:8080 weight=1 max_fails=2 fail_timeout=10s;

可以通过下面命令自定义权重等值:

curl -X PUT -d "{\"weight\":100, \"max_fails\":2, \"fail_timeout\":10}" http://192.168.21.14:8500/v1/kv/upstreams/rs1/192.168.20.100:8080?token=$token
# 或者 
curl -X PUT -d '{"weight":100, "max_fails":2, "fail_timeout":10}' http://192.168.21.14:8500/v1/kv/upstreams/rs1/192.168.20.100:8080?token=$token

删除记录

curl -X DELETE http://192.168.21.14:8500/v1/kv/upstreams/rs1/192.168.20.100:8080?token=$token

更新权重

curl -X PUT -d "{\"weight\":100, \"max_fails\":2, \"fail_timeout\":10}" http://192.168.21.14:8500/v1/kv/upstreams/rs1/192.168.20.100:8080?token=$token
# 或者 
curl -X PUT -d '{"weight":100, "max_fails":2, "fail_timeout":10}' http://192.168.21.14:8500/v1/kv/upstreams/rs1/192.168.20.100:8080?token=$token

下线服务

curl -X PUT -d "{\"weight\":2, \"max_fails\":2, \"fail_timeout\":10, \"down\":1}" http://192.168.21.14:8500/v1/kv/upstreams/rs1/192.168.20.100:8080?token=$token
# 或者
curl -X PUT -d '{"weight":2, "max_fails":2, "fail_timeout":10, "down":1}' http://192.168.21.14:8500/v1/kv/upstreams/rs1/192.168.20.100:8080?token=$token

查看upstream rs1下面有哪些上游服务器

curl http://192.168.21.14:8500/v1/kv/upstreams/rs1?recurse

推荐使用命令行操作,建议将命令行组装成脚本实现DevOps

四、一点感悟

在改造该动态发现方案期间,遇到了很多问题,最棘手的一个问题是测试环境种nginx一直报错,upstream数据始终无法完整下载,经过各种排查还是没有发现问题,中间我怀疑过是consul的问题,换成了etcd还是同样的报错,最后通过抓包跟踪,发现是Linux内核参数配置不当,导致队列溢出tcp三次握手失败,影响nginx与consul通信。

很多方案理论上是没有问题的,甚至说有人已经成功运用了,但是实际上亲自实施的话还是会遇到各种各样的问题,有些甚至是致命的,这时候就需要耐心的解决。希望大家在看到这篇文章的时候也去动手试试,如果遇到了问题请静下心来耐心排查。

还有一个是,很多人说运维是不产生价值的,我认为这么说是不对的,运维需要体现的价值有很多,SRE就是其中的一种。

到此这篇关于基于nginx实现上游服务器动态自动上下线无需reload的文章就介绍到这了!


Tags in this post...

Servers 相关文章推荐
Nginx 根据URL带的参数转发的实现
Apr 01 Servers
Windows下用Nginx配置https服务器及反向代理的问题
Sep 25 Servers
使用Nginx搭载rtmp直播服务器的方法
Oct 16 Servers
Nginx中使用Lua脚本与图片的缩略图处理的实现
Mar 18 Servers
CentOS安装Nginx并部署vue
Apr 12 Servers
Linux下使用C语言代码搭建一个简单的HTTP服务器
Apr 13 Servers
Windows Server 2019 域控制器安装图文教程
Apr 28 Servers
nginx静态资源的服务器配置方法
Jul 07 Servers
Apache Kafka 分区重分配的实现原理解析
Jul 15 Servers
WIN10使用IIS部署ftp服务器详细教程
Aug 05 Servers
Win10系统搭建ftp文件服务器详细教程
Aug 05 Servers
ubuntu如何搭建vsftpd服务器
Dec 24 Servers
为什么 Nginx 比 Apache 更牛逼
Mar 31 #Servers
Nginx的rewrite模块详解
Mar 31 #Servers
nginx常用命令放入shell脚本详解
Mar 31 #Servers
详解如何修改nginx的默认端口
nginx前后端同域名配置的方法实现
Mar 31 #Servers
Nginx同一个域名配置多个项目的实现方法
Mar 31 #Servers
Apache压力测试工具的安装使用
You might like
ThinkPHP中公共函数路径和配置项路径的映射分析
2014/11/22 PHP
PHP CodeIgniter框架的工作原理研究
2015/03/30 PHP
thinkphp利用模型通用数据编辑添加和删除的实例代码
2016/11/20 PHP
如何通过View::first使用Laravel Blade的动态模板详解
2017/09/21 PHP
JS 强制设为首页的代码
2009/01/31 Javascript
EasyUi tabs的高度与宽度根据IE窗口的变化自适应代码
2010/10/26 Javascript
jquery实现滑动图片自己测试的例子
2013/11/05 Javascript
javascript Event对象详解及使用示例
2013/11/22 Javascript
什么是cookie?js手动创建和存储cookie
2014/05/27 Javascript
JavaScript实现16进制颜色值转RGB的方法
2015/02/09 Javascript
jQuery的Cookie封装,与PHP交互的简单实现
2016/10/05 Javascript
教你用十行node.js代码读取docx的文本
2017/03/08 Javascript
jQuery实现轮播图效果demo
2020/01/11 jQuery
基于javascript实现日历功能原理及代码实例
2020/05/07 Javascript
[08:44]和酒神一起战斗 DOTA2教你做大人
2014/03/27 DOTA
[13:25]VP vs VICI (BO3)
2018/06/07 DOTA
[00:14]护身甲盾
2019/03/06 DOTA
Python抓取京东图书评论数据
2014/08/31 Python
Python对列表中的各项进行关联详解
2017/08/15 Python
在python下读取并展示raw格式的图片实例
2019/01/24 Python
python中使用while循环的实例
2019/08/05 Python
python中用logging实现日志滚动和过期日志删除功能
2019/08/20 Python
pymysql模块的使用(增删改查)详解
2019/09/09 Python
Python 如何测试文件是否存在
2020/07/31 Python
详解Pycharm安装及Django安装配置指南
2020/09/15 Python
Numpy数组的广播机制的实现
2020/11/03 Python
Python类class参数self原理解析
2020/11/19 Python
关于前端上传文件全面基础扫盲贴(入门)
2019/08/01 HTML / CSS
HTML5新增form控件和表单属性实例代码详解
2019/05/15 HTML / CSS
英国皇家造币厂:The Royal Mint
2018/10/05 全球购物
将"引用"作为函数参数有哪些特点
2013/04/05 面试题
物业管理毕业生个人的求职信
2013/11/30 职场文书
关于空气污染危害的感想
2015/08/11 职场文书
2015年教师党员个人总结
2015/11/24 职场文书
Redis集群新增、删除节点以及动态增加内存的方法
2021/09/04 Redis
MySQL数据库之内置函数和自定义函数 function
2022/06/16 MySQL