4.Haproxy使用socat工具管理和静态调度算法
socat工具
用来发送消息给socket文件
这里help看到是仅仅haproxy.sock支持的指令,不是所有socket文件支持这种方式。
通过haproxy的socket查看和配置
[root@realserver2 conf.d]# echo help | socat stdio /var/lib/haproxy/haproxy.sock
The following commands are valid at this level:
abort ssl ca-file <cafile> : abort a transaction for a CA file
abort ssl cert <certfile> : abort a transaction for a certificate file
abort ssl crl-file <crlfile> : abort a transaction for a CRL file
。。。 。。。
set severity-output [none|number|string]: set presence of severity level in feedback information
。。。
show backend : list backends in the current running config
通过这个比如可以查看连接数
1、wget --limit-rate 限速查看长连接
2、ab 并发来制造连接数
查看后端服务器的状态
查看调度权重
注意:3(initial 3) 就是现在是3,最初也是3的权重。说明可能会变化的。比如通过socket修改掉。
echo "get weight web_servers/web1" |socat stdio /var/lib/haproxy/haproxy.sock
echo "set weight web_servers/web1 10" |socat stdio /var/lib/haproxy/haproxy.sock
此时就通过sock修改了调度的比例
测试如下,确实是10比1的调度👇
这个haproxy就是可以通过sock去修改,而不用修改配置文件再reload,
而实际操作:reload不生效的时候也不报错,接着restart就报错;
如果配置文件修改ok,直接reload就很nice
下线上线:weight 0 就是停机维护了
这种方式的切换都不会出现503报错
1、如果是后端服务节点挂了一个,此时健康检查一般设置也就是1秒 2次 就判定为故障也差不多了
2、而用socat 传cli给sock的方式,业务切换丝滑流畅
不过也对,故障检测本来就是有一个判断过程,而socat属于强制下线切换自然无缝。
然后将所有后端节点都设置为0的权重,此时就会调度到backup节点了
这是主配置文件
这是子配置文件
这是services文件,要利用起来子配置文件,就需要再service里修改i的
但是haproxy -c -f 这个cli只能检查主配置文件的语法了就
多线程haproxy下socat发送的情况
haproxy本来是多进程的不像现在是多线程的,所以以前存在这么个情况:
不同的链接请求也是落在不同的进程上的。所以socat发送信号实现需求的时候不能直接👇这么发送了就。
需要修改配置文件,将sock和进程绑定,此后socat发给哪个socket就是哪个进程了。
而现在haproxy优化为多线程了,一个socket就是对应一个进程,然后多个线程都是在这个进程下的,所以你往一个socket发送就能发送到全部线程了。其实本质还是一个进程下的所有线程共享内存空间的。
静态算法和动态算法
cli: balance
https://docs.haproxy.org/3.0/configuration.html#4.1
静态算法:不关心后端服务器负载状态,什么连接数,资源是否富裕,不管统统,就定死了怎么调度;
动态算法:会根据后端服务器的负载情况,动态跳转调度机制。
haproxy里的静态算法机制,不支持 上面提到的echo "get weight web_servers/web1 0" socat stdio /var/lib/haproxy/haproxy.sock这种用法的。 也不是一点都不支持,可以改成0,就是仅支持上线下线。
看来默认就是动态算法,支持set 其他数值。
cli的补充:
小实验:上下线后端服务器的脚本:
两台后端服务器改为docker
132本身也是haproxy,简单模拟用户curl下
简单的web升级脚本👇
#!/bin/bash
WEB_SERVERS="
192.168.126.133
192.168.126.134
"
BACKEND=web_servers
APP=nginx
IMAGE=$APP:latest
DELAY=15
for i in $WEB_SERVERS;do
echo "set server $BACKEND/$i state maint" |socat stdio /var/lib/haproxy/haproxy.sock
ssh $i docker rm -f $APP
ssh $i "echo Docker $i WEBSITE $i v1.0 > /data/www/index.html"
ssh $i docker run -d -p 80:80 -v /data/www:/usr/share/nginx/html --name $APP $IMAGE
sleep $DELAY
echo "set server $BACKEND/$i state ready" |socat stdio /var/lib/haproxy/haproxy.sock
done
还得做一下ssh的key登入
ssh-keygen
ssh-copy-id 192.168.126.133
ssh-copy-id 192.168.126.134
验证key登入
再修改以下配置文件
将名称改为ip,这样上面的切换脚本就可以用了
关于平滑升级
nginx本身的平滑升级
关键字:-USE2,-WINCH
其实官方早就针对Nginx平滑升级做足了功夫,基本原理就是,启动新的Nginx(master+worker)进程,之后给旧的master进程发送-USER2指令,这样就能同时让新版和旧版本进程同时接收处理请求。之后我们再发送-WINCH给旧进程,让它停止工作服务(关闭所有旧worker进程,但是旧的master进程没关,防止后面你遇到问题回滚). 如果确认新Nginx没问题,那么再手动Kill旧的master进程即可完成平滑升级.
https://mojun.me/2022/09/20/nginx-reload-in-prod/
https://bbs.huaweicloud.com/forum/thread-0245111487078556005-1-1.html
开始测试实验
1、模拟用户长连接
2、查看旧版本1.24的进程信息
主进程ID是1682,子进程ID是1683和1684,两个子进程也是从配置文件里里worker_processess auto;来的,因为2核CPU,所以auto成了2个worker进程。
查看旧版本的编译参数,当然我这里是yum的,不过也有编译的残留,有点尴尬
可能是用的yum,编译的没用
这就能说明是用的yum的了吧,rpm查到这个bin文件是来自包的。
这段无所谓,是我自己的环境不干净导致的,不过查确认就这么查
yum的nginx的编译参数就多了
3、二进制文件备份
mv nginx nginx.old
4、准备好新版本
编译参数就用常规的就好,如果是老的nginx是编译安装就直接nginx -V那里复制过来就行了,yum的没必要,就是用常规的参数就行。
cd /tmp/
curl -LO https://nginx.org/download/nginx-1.26.1.tar.gz
tar xvf nginx-1.26.1.tar.gz -C /usr/local/share/
cd /usr/local/share/
ll
cd nginx-1.26.1/
# 这里的prefix可能要修改,第一次我没改也成了奇怪。。 重启服务的时候 nginx二进制里只会认/apps/nginx这个根目录,会找不到很多log和cfg文件的
./configure --prefix=/apps/nginx \
--user=nginx \
--group=nginx \
--with-http_ssl_module \
--with-http_v2_module \
--with-http_realip_module \
--with-http_stub_status_module \
--with-http_gzip_static_module \
--with-pcre \
--with-stream \
--with-stream_ssl_module \
--with-stream_realip_module
make # make install没必要,只需要一个二进制文件
# 只需要一个obj/nginx 二进制文件其实
echo $?
# 以上初步安装好了新版1.26的nginx
5、拷贝新的二进制nginx,覆盖之前旧版本的nginx
之前已经备份了第三步,所以直接用新的nginx二进制放到老的nginx路径下,这里用软连接
上图备份了老版本的nginx二进制文件,然后从仅仅make出来的objs/下复制过去nginx新版本的二进制文件。
此时环境变量已经ok了
已经是新的nginx了
6、发送关键信号-USR2
kill -USR2 1682 # 既可以升级,也可以降级,还可以同级别新建主进程
# 这条cli敲下去,会负载均衡到两个版本的nginx的,只不过编译的二进制nginx的prefix就是设置的老版本的nginx的主目录,所以这里负载均衡的效果就看不清了,因为页面都是一样的,这也是为什么不用make install的原因,只是用了二进制,如果测试就要看负载均衡的到两个版本的效果,可以make的prefix到别的路径,然后在make install一下看看,明儿弄吧。 好像不是,配置文件路径 不会变。。。
发送kill -USR2 xxx(旧版nginx主进程)进行替换,此时会多出一个nginx主进程和2个子进程
而且从图上看,新的nginx高版本的主进程貌似挂载老版本呢的主进程下的。
此时 client 用户的下载长连接没有断
下载进度还在
测试都是访问的老的nginx的页面,肯定的,启动的配置文件路径都是老的,内容也一样。
7、发送-winch信号,停止旧版本接收新的请求
确认升级成功,-WINCH 信号 停止旧版master接收新的请求(此时旧版本nginx master进程没死,只是停止接收新的请求)
8、回退
9、quit
- 主进程支持的信号
TERM,INT | 立刻退出 |
---|---|
QUIT | 等待工作进程结束在退出 |
KILL | 强子终止进程 |
HUP | 重新加载配置文件,使用新的的配置启动工作进程,并逐步关闭旧进程 |
USR1 | 重新打开日志文件 |
USR2 | 启动新的主进程,实现热升级 |
WINCH | 逐步关闭工作进程 |
- 工作进程支持的信号:
TERM,INT | 立刻退出 |
---|---|
QUIT | 等待请求处理结束后再退出 |
USR1 | 重新打开日志文件 |
下面是高版本做了makeinstall的实验
这里有个反代的缓存开了,所以进程利厄多了两个cache。
然后准好备好make install的nginx 1.26.0版本
然后用户做一个长连接也就是限速下载,再做一个循环curl
注意编译的新版本的nginx的prefix是 --prefix=/apps/nginx ,所以一定和现在的老版本的nginx的工作路径是不一样的,
所以配置文件首当其冲的不同,于是首页也不同了,借此可以观察kill -USR2后的新老两个版本nginx同时工作的效果--负载分担
然后:
备份老版本的nginx二进制
复制新版本的nginx二进制进去
在kill -usr2前先看下进程
发现 cache loader process没了,不管
敲入kill -USR2后发现新版本的主进程挂载了老版本的主进程下👇但是不知道为什么配置文件的路径没有变为/apps/nginx,
此时用户那边看看
老样子???是啥,因为版本升上去了,而且配置文件的路径还是老的,自然不会出现期待的负载分担了。
再降级降回去
再升回去
此时 原来的长连接的端口都没变过,就是没断过了
加上此时长连接的用户完成了数据交互,老的进程就会自动退出了
这么看难道编译的nginx二进制里是不带那个什么prefix=/apps/nginx的?还是被systemctl给接管了
对的,就是这个意思。去掉-c /etc/nginx/nginx.conf,用老版本nginx二进制还能systemctl restart nginx起得来,新版本就起不来了。
新版本的nginx启动后,默认就是走的/apps/nginx的
重新编译为老版本的prefix
还是有一些问题
不过有进一步进程,说明就是编译的时候没有指对配置文件路径👆仔细看/etc/nginx/conf/nginx.conf是不存在,
虽然--prefix一样,但是yum的nginx的cfg是在/etc/nginx/nginx.conf比你编译的一层级的。
再来,不烦了,直接复制
报错了精简下,把后面的--with-cc-op全部删掉试试
所以总结下
1、nginx的平滑热升级注意事项
①只需要二进制nginx文件,
②编译的时候要用原来nginx -V看到的参数,可适当删减,比如yum的nginx -V那个你编译可能会卡很久,上图删掉最后一大段单引号的选项就行了。测试OK;
②如果原来也是编译安装的,那么正好,就用原来的-V的编译参数全盘复制过来。
②如果编译的时候参数不同,也会被systemd的service文件接管,-c就会改了你的配置文件,所以也不会遇到问题
③如果两个新老版本都是cli启动的,那么问题就来了,-c 不指定,就是默认编译的时候的配置文件了,此时就会导致问题。
④你肯定希望版本升级,业务文件配置文件数据文件不变,所以就是要用老的版本呢的编译选项
⑤但是如果你就是要两个版本呢不同的工作路径,做出来复制分担的效果,下午继续
试下用cli启动nginx,两版本不同页面的热升级负载效果
1、先启动nginx,这是新版本了
目录在/apps/nginx下,页面就是
2、覆盖掉nginx二进制,用老版本
此时长连接挂起来,while curl继续
又出现了kill 不出来效果了
好像必须是systemctl启动的方式才会接收kill -USR2信号
那就这样,用修改后的service文件来启动吧
这样就是nginx二进制里当初编译的prefix是啥就用啥了,也能接收USR2信息了
然后关闭所有nginx进程,再用systemctl start nginx启动,
此时是nginx 1.24版本
下面准备好1.26版的二进制,
此时终于看到了 新 老 两个 nginx的负载分担效果
整体看来还是新的nginx进程多点
长连接也不会断
然后发送winch信号
不工作的worker子进程都直接关闭,还在长连接的就shutting down
此时ss -nt 观察 长连接还是不变的
quit敲一下
关闭client端的长连接,就当下完了吧
结果nginx 所有进程没了。。。哈哈哈,惨败~~每次都能做出不一样的bug哦~
再来
发送USR2后,两个版本同时工作,负载不均衡分担
同时长连接不断端口孩子啊
发送WINCH给主进程,此时长连接依然不变,但是请求调度不在发给被切换的版本了,这里是降级操作了。从1.26切到1.24的。
关闭client的下载,观察nginx进程,发现存在两个主进程,
关闭被切换的主进程,希望这一次不会全部关掉
失败。。。。
看看哪里出问题了。
用人家本来的也就是yum的service文件没问题
研究下
1、上面为啥那么-QUIT全部kill掉了
2、如何curl的时候看到当前版本呢?就是测试不用不同工作目录,而是用同一个service里面指定的配置文件去看nginx的版本
其实我还可以这样
就是中间插入一个手动修改service文件的过程
估计就是PIDFile的问题
注释掉PIDFile哪一行 再试试
果然👆
但是一旦没有这个PIDFile,就没法实现平滑切换,就会报错,就是QUIT发过去,全部就kill了。
解决1、上面为啥那么-QUIT全部kill掉了这个问题
就是PIDFile两个新老nginx版本要一直,才好USR2吧,我来试试
希望这是最后一次测试,不过最开始的已经可以实现需求,只不过涉及yum安装cli安装,kill -USR2没效果,两个nginx的根目录以及配置文件和页面不共用看到负载分担,等等这些问题都要解决才折腾了这么久,各位看官 你觉的我多此一举,NONONO,各位看官~ 你细听分说
暗杠:
这江山风雨 岁月山河
刀光剑影 美了多少世间传说
且看他口若悬河 衣上有风尘
却原来是一位江湖说书人
那天山女子 独守枯城
也只是为了曾经的那一个人
好了,继续。。。
找到了👆
修改service文件
进入源码路径,make clean,
这样还是新建根目录,但是pid文件和老版本共用👆
make一下,不要make install
这样两个版本的nginx二进制就准备ok了,
长链接挂起来
curl 循环起来
所以这次实验就是关键PIDFile
切换nginx二进制文件
发送USR2
此时长连接不会断,循环curl可见负载分担
发送WINCH
是循环curl就已经全部负载到新的nginx版本上了
最后一步 发送QUIT
失败。。。
我要离开地球。。。。
改成这样试试
奇怪没有去我以为的地方生成PID文件啊,而是配置文件抢先了。。。
好继续,老子又有信心了
注销点/apps/nginx/conf/nginx.conf里的pid那行
好了这下 新老nginx的pid文件一致了
确认下service文件配置,还是改成这样试试👇
好了,继续测试
长连接和curl
二进制文件的处理
USR2发送
长连接不断👆,负载分担观察👇
发送WINCH
curl的不在负载到被切换之前的版本,以及长连接不断
发送QUIT
终于搞定了~地球我还是回来吧~~~
再切回1.26.0去,同理了就~~~,
以上就是切回1.26的擦欧总
总之:
1、生产中,就不要这么麻烦,直接
编译的时候 复制老版本nginx -V的所有参数,可能要略作删减,就是报错了就删掉没必要的一些,特别是老版本是yum的参数会过多
然后处理二进制文件
然后usr2
然后winch
最后quit
就搞定了
由于新版本编译也是老的编译参数,所以负载分担看不到效果,其实只是我不会看,而已,实际就是usr2发过去,两个版本同时工作,
winch发过去,老版本nginx 不处理新的请求,
quit发过去,处理完老版本的请求连接后,就退出了,就将新版本的master进程独立出来了。
2、我做成两个版本独立配置就是看看而已的,
本来两个版本独立是 最后一步quit发过去,就所有nginx进程都退掉了,
所以才需要修改service文件,而修改的关键就是去掉-c /etc/nginx/nginx.conf配置文件,同时将新版本的pidfile 和 老版本的pidfile 保持一致,才保证最后quit信号不会杀掉所有的nginx进程的。
3、研究下如何实现正儿八经生产中,也就是不修改service直接第1点中 如何观察新老版本的负载效果
开始操作
1、当前service,当前nginx版本如下
2、测试手法
长连接挂起来
3、开始降级,当然是热降级
处理二进制
其实不是上图说的"不看看到1.24.0出来",可以看到,观察久一点,还是可以看见的
他是一阵子全部转给1.26,一阵子全部转给1.24,观察久点就看到了,试过两遍了,确实要等很久才会看到负载分担地效果,这个比不同地页面来的慢很多。
长连接不会断
回退操作
一样啊,回退就当作降级操作,一样的,不过好像有HUP的操作,不管了。
长连接挂着,从升级到回退都没断
明天研究下 升级 回退 长连接始终不断地操作,上面只是研究了 升级 降级 长连接不断地操作。
HUP好像只能回滚 新开地子master进程。明天再弄
回滚好像又可以了,
分析是QUIT以后就无法使用HUP回滚了
有个长连接挂着,所以有个shutting down👆
一旦quit了确实就不能hup回滚
就只能等用户长连接自己结束了,再去做平滑降级了。
因为quit了就不会再HUP打开子woker进程了,没法回退了,只能做回降,不过回降,也不会导致用户长连接断啊,明天继续
结论:所以热升级最后一个QUIT是不要敲地,可以保持时刻HUP回滚。
如果敲了HUP,是不是可以再在子master下挂master?试试,不行,不能多层挂下去,
就是一个maser挂再挂一个master,所以quit不能乱敲,要保证随时可以hup回去。
工作案例
开发内网copilot放行和自动关闭git push的措施