基于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 负载均衡是什么以及该如何配置
Mar 31 Servers
nginx优化的六点方法
Mar 31 Servers
nginx处理http请求实现过程解析
Mar 31 Servers
Nginx设置日志打印post请求参数的方法
Mar 31 Servers
Nginx配置https原理及实现过程详解
Mar 31 Servers
Nginx域名转发使用场景代码实例
Mar 31 Servers
apache基于端口创建虚拟主机的示例
Apr 24 Servers
Nginx中使用Lua脚本与图片的缩略图处理的实现
Mar 18 Servers
Vertica集成Apache Hudi重磅使用指南
Mar 31 Servers
教你如何用cmd快速登录服务器
Jun 10 Servers
nginx之queue的具体使用
Jun 28 Servers
阿里云服务器(windows)手动部署FTP站点详细教程
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
php dirname(__FILE__) 获取当前文件的绝对路径
2011/06/28 PHP
PHP实现定时执行任务的方法
2014/10/05 PHP
PHP多线程类及用法实例
2014/12/03 PHP
php时间计算相关问题小结
2016/05/09 PHP
可以把编码转换成 gb2312编码lib.UTF8toGB2312.js
2007/08/21 Javascript
javascript基础第一章 JavaScript与用户端
2010/07/22 Javascript
jQuery EasyUI API 中文文档 - Form表单
2011/10/06 Javascript
基于jQuery的简单九宫格实现代码
2012/08/09 Javascript
jQuery下实现等待指定元素加载完毕(可改成纯js版)
2013/07/11 Javascript
jquery 循环显示div的示例代码
2013/10/18 Javascript
String.prototype实现的一些javascript函数介绍
2013/11/22 Javascript
jquery日历控件实现方法分享
2014/03/07 Javascript
基于javascript实现单选及多选的向右和向左移动实例
2015/07/25 Javascript
3种js实现string的substring方法
2015/11/09 Javascript
jquery form表单获取内容以及绑定数据
2016/02/24 Javascript
4个顶级开源JavaScript图表库
2018/09/29 Javascript
浅谈HTTP 缓存的那些事儿
2018/10/17 Javascript
基于JS开发微信网页录音功能的实例代码
2019/04/30 Javascript
vue-cli3+typescript新建一个项目的思路分析
2019/08/06 Javascript
JavaScript实现alert弹框效果
2020/11/19 Javascript
ReactRouter的实现方法
2021/01/25 Javascript
[02:40]2018年度DOTA2最佳新人-完美盛典
2018/12/16 DOTA
python练习程序批量修改文件名
2014/01/16 Python
对python append 与浅拷贝的实例讲解
2018/05/04 Python
python中使用zip函数出现错误的原因
2018/09/28 Python
详解python读取和输出到txt
2019/03/29 Python
python变量的存储原理详解
2019/07/10 Python
python快速安装OpenCV的步骤记录
2021/02/22 Python
薇姿法国官网:Vichy法国
2021/01/28 全球购物
广州一家公司的.NET面试题
2016/06/11 面试题
创立科技Java面试题
2015/11/29 面试题
英文版销售经理个人求职信
2013/11/20 职场文书
代办委托书怎么写
2014/08/01 职场文书
导游词之无锡东林书院
2019/12/11 职场文书
发工资啦!教你用Python实现邮箱自动群发工资条
2021/05/10 Python
粗暴解决CUDA out of memory的问题
2021/05/22 Python