基于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+Tomcat实现负载均衡、动静分离的原理解析
Mar 31 Servers
win10安装配置nginx的过程
Mar 31 Servers
nginx配置文件使用环境变量的操作方法
Jun 02 Servers
详解nginx进程锁的实现
Jun 14 Servers
Linux中Nginx的防盗链和优化的实现代码
Jun 20 Servers
详解nginx安装过程并代理下载服务器文件
Feb 12 Servers
CentOS下安装Jenkins的完整步骤
Apr 07 Servers
阿里云k8s服务升级时502错误 springboot项目应用
Apr 09 Servers
关于windows server 2012 DC 环境 重启后蓝屏代码:0xc00002e2的问题
May 25 Servers
vscode远程免密登入Linux服务器的配置方法
Jun 28 Servers
使用 DataAnt 监控 Apache APISIX的原理解析
Jul 07 Servers
Docker容器harbor私有仓库部署和管理
Aug 05 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
网页游戏开发入门教程三(简单程序应用)
2009/11/02 PHP
php fputcsv命令 写csv文件遇到的小问题(多维数组连接符)
2011/05/24 PHP
php cli配置文件问题分析
2015/10/15 PHP
javascript String 的扩展方法集合
2008/06/01 Javascript
利用javascript移动div层-javascript 拖动层
2009/03/22 Javascript
JS trim去空格的最佳实践
2011/10/30 Javascript
javascript与cookie 的问题详解
2013/11/11 Javascript
JavaScript生成随机数的4种自定义函数分享
2015/02/28 Javascript
JavaScript中常见获取元素的方法汇总
2015/03/04 Javascript
Javascript常用小技巧汇总
2015/06/24 Javascript
JS封装cookie操作函数实例(设置、读取、删除)
2015/11/17 Javascript
javascript数组遍历的方法实例分析
2016/09/13 Javascript
前端分页功能的实现以及原理(jQuery)
2017/01/22 Javascript
Vue 2中ref属性的使用方法及注意事项
2017/06/12 Javascript
详解Vue学习笔记进阶篇之列表过渡及其他
2017/07/17 Javascript
基于LayUI分页和LayUI laypage分页的使用示例
2017/08/02 Javascript
微信小程序页面跳转功能之从列表的item项跳转到下一个页面的方法
2017/11/27 Javascript
使用nvm和nrm优化node.js工作流的方法
2019/01/17 Javascript
nodejs检测因特网是否断开的解决方案
2019/04/17 NodeJs
详解小程序横屏方案对比
2020/06/28 Javascript
ES2020让代码更优美的运算符 (?.) (??)
2021/01/04 Javascript
Python爬虫之pandas基本安装与使用方法示例
2018/08/08 Python
python中的for循环
2018/09/28 Python
Python 自动登录淘宝并保存登录信息的方法
2019/09/04 Python
SpringBoot实现登录注册常见问题解决方案
2020/03/04 Python
Python rabbitMQ如何实现生产消费者模式
2020/08/24 Python
AmazeUI 模态窗口的实现代码
2020/08/18 HTML / CSS
墨尔本复古时尚品牌:Dangerfield
2018/12/12 全球购物
小学信息技术教学反思
2014/02/10 职场文书
教师远程培训感言
2014/03/06 职场文书
文明城市创建标语
2014/06/16 职场文书
初中英语教师个人工作总结2015
2015/07/21 职场文书
事业单位工作人员岗前培训心得体会
2016/01/08 职场文书
用Python实现Newton插值法
2021/04/17 Python
如何理解python接口自动化之logging日志模块
2021/06/15 Python
Win11怎么添加用户?Win11添加用户账户的方法
2022/07/15 数码科技