4.Haproxy使用socat工具管理和静态调度算法

socat工具

用来发送消息给socket文件

image-20240812165740216

image-20240812165932352

这里help看到是仅仅haproxy.sock支持的指令,不是所有socket文件支持这种方式。

image-20240812170601374

image-20240812170758118

通过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

image-20240812173242635

通过这个比如可以查看连接数

1、wget --limit-rate 限速查看长连接

image-20240812175629310

2、ab 并发来制造连接数

image-20240812175728541

查看后端服务器的状态

image-20240812175930865

查看调度权重

image-20240812180611956

注意: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

image-20240812181039155

此时就通过sock修改了调度的比例

测试如下,确实是10比1的调度👇

image-20240813093845349

这个haproxy就是可以通过sock去修改,而不用修改配置文件再reload,

而实际操作:reload不生效的时候也不报错,接着restart就报错;

如果配置文件修改ok,直接reload就很nice

下线上线:weight 0 就是停机维护了

这种方式的切换都不会出现503报错

1、如果是后端服务节点挂了一个,此时健康检查一般设置也就是1秒 2次 就判定为故障也差不多了

image-20240813105828142

image-20240813105842399

2、而用socat 传cli给sock的方式,业务切换丝滑流畅

不过也对,故障检测本来就是有一个判断过程,而socat属于强制下线切换自然无缝。

image-20240813103052172

image-20240813103142040

然后将所有后端节点都设置为0的权重,此时就会调度到backup节点了

image-20240813103853756

image-20240813103935843

这是主配置文件

image-20240813104012467

这是子配置文件

image-20240813104045628

这是services文件,要利用起来子配置文件,就需要再service里修改i的

image-20240813104124438

但是haproxy -c -f 这个cli只能检查主配置文件的语法了就

image-20240813104210183

多线程haproxy下socat发送的情况

​ haproxy本来是多进程的不像现在是多线程的,所以以前存在这么个情况:

​ 不同的链接请求也是落在不同的进程上的。所以socat发送信号实现需求的时候不能直接👇这么发送了就。

image-20240822151312971

​ 需要修改配置文件,将sock和进程绑定,此后socat发给哪个socket就是哪个进程了。

image-20240822151458422

​ 而现在haproxy优化为多线程了,一个socket就是对应一个进程,然后多个线程都是在这个进程下的,所以你往一个socket发送就能发送到全部线程了。其实本质还是一个进程下的所有线程共享内存空间的。

静态算法和动态算法

cli: balance

https://docs.haproxy.org/3.0/configuration.html#4.1

image-20240826170154614

静态算法:不关心后端服务器负载状态,什么连接数,资源是否富裕,不管统统,就定死了怎么调度;

动态算法:会根据后端服务器的负载情况,动态跳转调度机制。

haproxy里的静态算法机制,不支持 上面提到的echo "get weight web_servers/web1 0" socat stdio /var/lib/haproxy/haproxy.sock这种用法的。 也不是一点都不支持,可以改成0,就是仅支持上线下线。

image-20240826165811617

image-20240826165915406

看来默认就是动态算法,支持set 其他数值。

cli的补充:

image-20240822154101375

image-20240822154358516

小实验:上下线后端服务器的脚本

两台后端服务器改为docker

image-20240822161236527

image-20240822161247345

132本身也是haproxy,简单模拟用户curl下

image-20240822161710449

简单的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

image-20240822162115928

image-20240822162137293

image-20240822162152405

验证key登入

image-20240822162243420

再修改以下配置文件

image-20240822173313383

将名称改为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、模拟用户长连接

image-20240813141422435

image-20240813151451546

2、查看旧版本1.24的进程信息

image-20240813142551828

主进程ID是1682,子进程ID是1683和1684,两个子进程也是从配置文件里里worker_processess auto;来的,因为2核CPU,所以auto成了2个worker进程。

查看旧版本的编译参数,当然我这里是yum的,不过也有编译的残留,有点尴尬

可能是用的yum,编译的没用

image-20240813144040827

这就能说明是用的yum的了吧,rpm查到这个bin文件是来自包的。

这段无所谓,是我自己的环境不干净导致的,不过查确认就这么查

yum的nginx的编译参数就多了

image-20240813144232406

3、二进制文件备份

mv nginx nginx.old

image-20240813150715184

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路径下,这里用软连接

image-20240813182318231

上图备份了老版本的nginx二进制文件,然后从仅仅make出来的objs/下复制过去nginx新版本的二进制文件。

此时环境变量已经ok了

image-20240813150844058

已经是新的nginx了

6、发送关键信号-USR2

kill -USR2 1682  # 既可以升级,也可以降级,还可以同级别新建主进程

# 这条cli敲下去,会负载均衡到两个版本的nginx的,只不过编译的二进制nginx的prefix就是设置的老版本的nginx的主目录,所以这里负载均衡的效果就看不清了,因为页面都是一样的,这也是为什么不用make install的原因,只是用了二进制,如果测试就要看负载均衡的到两个版本的效果,可以make的prefix到别的路径,然后在make install一下看看,明儿弄吧。  好像不是,配置文件路径 不会变。。。

发送kill -USR2 xxx(旧版nginx主进程)进行替换,此时会多出一个nginx主进程和2个子进程

image-20240813151228055

而且从图上看,新的nginx高版本的主进程貌似挂载老版本呢的主进程下的。

此时 client 用户的下载长连接没有断

image-20240813151510397

下载进度还在

image-20240813151524800

测试都是访问的老的nginx的页面,肯定的,启动的配置文件路径都是老的,内容也一样。

image-20240813153135276

7、发送-winch信号,停止旧版本接收新的请求

确认升级成功,-WINCH 信号 停止旧版master接收新的请求(此时旧版本nginx master进程没死,只是停止接收新的请求)

image-20240813153432028

8、回退

9、quit

  • 主进程支持的信号
TERM,INT 立刻退出
QUIT 等待工作进程结束在退出
KILL 强子终止进程
HUP 重新加载配置文件,使用新的的配置启动工作进程,并逐步关闭旧进程
USR1 重新打开日志文件
USR2 启动新的主进程,实现热升级
WINCH 逐步关闭工作进程
  • 工作进程支持的信号:
TERM,INT 立刻退出
QUIT 等待请求处理结束后再退出
USR1 重新打开日志文件

下面是高版本做了makeinstall的实验

image-20240814103727049

这里有个反代的缓存开了,所以进程利厄多了两个cache。

然后准好备好make install的nginx 1.26.0版本

image-20240814103834320

然后用户做一个长连接也就是限速下载,再做一个循环curl

image-20240814105845123

image-20240814104210183

注意编译的新版本的nginx的prefix是 --prefix=/apps/nginx ,所以一定和现在的老版本的nginx的工作路径是不一样的,

image-20240814104321291

所以配置文件首当其冲的不同,于是首页也不同了,借此可以观察kill -USR2后的新老两个版本nginx同时工作的效果--负载分担

然后:

备份老版本的nginx二进制

复制新版本的nginx二进制进去

image-20240814105826967

在kill -usr2前先看下进程

image-20240814105914044

image-20240814105935146

发现 cache loader process没了,不管

敲入kill -USR2后发现新版本的主进程挂载了老版本的主进程下👇但是不知道为什么配置文件的路径没有变为/apps/nginx,

image-20240814110405976

此时用户那边看看

image-20240814110512597

image-20240814110523094

老样子???是啥,因为版本升上去了,而且配置文件的路径还是老的,自然不会出现期待的负载分担了。

再降级降回去

image-20240814110619760

image-20240814110700674

再升回去

image-20240814111120048

image-20240814111153195

image-20240814111202095

此时 原来的长连接的端口都没变过,就是没断过了

加上此时长连接的用户完成了数据交互,老的进程就会自动退出了

image-20240814111314421

image-20240814111356677

image-20240814112022144

这么看难道编译的nginx二进制里是不带那个什么prefix=/apps/nginx的?还是被systemctl给接管了

image-20240814112116570

对的,就是这个意思。去掉-c /etc/nginx/nginx.conf,用老版本nginx二进制还能systemctl restart nginx起得来,新版本就起不来了。

image-20240814113209519

image-20240814113222892

新版本的nginx启动后,默认就是走的/apps/nginx的

image-20240814113348503

image-20240814113407265

重新编译为老版本的prefix

image-20240814113616622

image-20240814113946380

还是有一些问题

image-20240814114220514

不过有进一步进程,说明就是编译的时候没有指对配置文件路径👆仔细看/etc/nginx/conf/nginx.conf是不存在,

虽然--prefix一样,但是yum的nginx的cfg是在/etc/nginx/nginx.conf比你编译的一层级的。

image-20240814114152576

再来,不烦了,直接复制

image-20240814115212825

报错了精简下,把后面的--with-cc-op全部删掉试试

image-20240814115553281

所以总结下

1、nginx的平滑热升级注意事项

①只需要二进制nginx文件,

②编译的时候要用原来nginx -V看到的参数,可适当删减,比如yum的nginx -V那个你编译可能会卡很久,上图删掉最后一大段单引号的选项就行了。测试OK;

②如果原来也是编译安装的,那么正好,就用原来的-V的编译参数全盘复制过来。

②如果编译的时候参数不同,也会被systemd的service文件接管,-c就会改了你的配置文件,所以也不会遇到问题

③如果两个新老版本都是cli启动的,那么问题就来了,-c 不指定,就是默认编译的时候的配置文件了,此时就会导致问题。

④你肯定希望版本升级,业务文件配置文件数据文件不变,所以就是要用老的版本呢的编译选项

⑤但是如果你就是要两个版本呢不同的工作路径,做出来复制分担的效果,下午继续

试下用cli启动nginx,两版本不同页面的热升级负载效果

1、先启动nginx,这是新版本了

image-20240814133955488

目录在/apps/nginx下,页面就是

image-20240814134033346

2、覆盖掉nginx二进制,用老版本

image-20240814134248941

此时长连接挂起来,while curl继续

image-20240814134558377

image-20240814134615882

image-20240814135156337

又出现了kill 不出来效果了

好像必须是systemctl启动的方式才会接收kill -USR2信号

那就这样,用修改后的service文件来启动吧

image-20240814141340017

这样就是nginx二进制里当初编译的prefix是啥就用啥了,也能接收USR2信息了

然后关闭所有nginx进程,再用systemctl start nginx启动,

image-20240814143000890

此时是nginx 1.24版本

下面准备好1.26版的二进制,

image-20240814143052297

image-20240814143211009

image-20240814143411862

此时终于看到了 新 老 两个 nginx的负载分担效果

image-20240814143648184

整体看来还是新的nginx进程多点

image-20240814143732908

长连接也不会断

image-20240814143745050

然后发送winch信号

image-20240814143824542

不工作的worker子进程都直接关闭,还在长连接的就shutting down

此时ss -nt 观察 长连接还是不变的

image-20240814143908666

quit敲一下

image-20240814143949204

关闭client端的长连接,就当下完了吧

image-20240814144021333

image-20240814144011297

结果nginx 所有进程没了。。。哈哈哈,惨败~~每次都能做出不一样的bug哦~

image-20240814144119648

再来

image-20240814144426565

发送USR2后,两个版本同时工作,负载不均衡分担

同时长连接不断端口孩子啊

image-20240814144614296

image-20240814144537486

发送WINCH给主进程,此时长连接依然不变,但是请求调度不在发给被切换的版本了,这里是降级操作了。从1.26切到1.24的。

image-20240814144804029

image-20240814144846551

关闭client的下载,观察nginx进程,发现存在两个主进程,

image-20240814144954988

关闭被切换的主进程,希望这一次不会全部关掉

image-20240814145043338

失败。。。。

看看哪里出问题了。

用人家本来的也就是yum的service文件没问题

image-20240814145648731

研究下

1、上面为啥那么-QUIT全部kill掉了

2、如何curl的时候看到当前版本呢?就是测试不用不同工作目录,而是用同一个service里面指定的配置文件去看nginx的版本

其实我还可以这样

就是中间插入一个手动修改service文件的过程

image-20240814150612488

估计就是PIDFile的问题

image-20240814150627496

注释掉PIDFile哪一行 再试试

image-20240814150709628

果然👆

但是一旦没有这个PIDFile,就没法实现平滑切换,就会报错,就是QUIT发过去,全部就kill了。

解决1、上面为啥那么-QUIT全部kill掉了这个问题

就是PIDFile两个新老nginx版本要一直,才好USR2吧,我来试试

希望这是最后一次测试,不过最开始的已经可以实现需求,只不过涉及yum安装cli安装,kill -USR2没效果,两个nginx的根目录以及配置文件和页面不共用看到负载分担,等等这些问题都要解决才折腾了这么久,各位看官 你觉的我多此一举,NONONO,各位看官~ 你细听分说

暗杠:

这江山风雨 岁月山河

刀光剑影 美了多少世间传说

且看他口若悬河 衣上有风尘

却原来是一位江湖说书人

那天山女子 独守枯城

也只是为了曾经的那一个人

好了,继续。。。

image-20240814163333384

找到了👆

修改service文件

image-20240814163455646

image-20240814164144821

进入源码路径,make clean,

image-20240814163444096

image-20240814163548372

这样还是新建根目录,但是pid文件和老版本共用👆

make一下,不要make install

image-20240814163652839

image-20240814164103142

这样两个版本的nginx二进制就准备ok了,

image-20240814164237722

长链接挂起来

image-20240814164257348

image-20240814164327356

curl 循环起来

image-20240814164312143

所以这次实验就是关键PIDFile

image-20240814164457168

切换nginx二进制文件

image-20240814164601607

发送USR2

image-20240814164646813

此时长连接不会断,循环curl可见负载分担

image-20240814164722745

image-20240814164740859

image-20240814164748782

发送WINCH

image-20240814165024161

是循环curl就已经全部负载到新的nginx版本上了

image-20240814165057717

最后一步 发送QUIT

image-20240814165204774

失败。。。

我要离开地球。。。。

改成这样试试

image-20240814165406330

image-20240814170618614

奇怪没有去我以为的地方生成PID文件啊,而是配置文件抢先了。。。

好继续,老子又有信心了

注销点/apps/nginx/conf/nginx.conf里的pid那行

image-20240814170733644

image-20240814170846336

好了这下 新老nginx的pid文件一致了

确认下service文件配置,还是改成这样试试👇

image-20240814170950428

image-20240814171048514

好了,继续测试

长连接和curl

image-20240814171123183

image-20240814171133942

image-20240814171154188

二进制文件的处理

image-20240814171243161

USR2发送

image-20240814171308684

长连接不断👆,负载分担观察👇

image-20240814171323418

发送WINCH

image-20240814171419914

curl的不在负载到被切换之前的版本,以及长连接不断

image-20240814171447494

发送QUIT

image-20240814171702320

image-20240814171724230

image-20240814171731684

终于搞定了~地球我还是回来吧~~~

再切回1.26.0去,同理了就~~~,

image-20240814180145671

image-20240814180156168

image-20240814180303093

image-20240814180311363

image-20240814180317484

image-20240814180325426

image-20240814180336832

以上就是切回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版本如下

image-20240814181202841

2、测试手法

image-20240814181336681

image-20240814181413320

长连接挂起来

image-20240814181540089

image-20240814181556987

3、开始降级,当然是热降级

处理二进制

image-20240814181645437

image-20240814182109718

其实不是上图说的"不看看到1.24.0出来",可以看到,观察久一点,还是可以看见的

image-20240814183310579

他是一阵子全部转给1.26,一阵子全部转给1.24,观察久点就看到了,试过两遍了,确实要等很久才会看到负载分担地效果,这个比不同地页面来的慢很多。

image-20240814182136391

image-20240814182201055

长连接不会断

image-20240814182308925

回退操作

一样啊,回退就当作降级操作,一样的,不过好像有HUP的操作,不管了。

image-20240814183752552

长连接挂着,从升级到回退都没断

image-20240814183828817

明天研究下 升级 回退 长连接始终不断地操作,上面只是研究了 升级 降级 长连接不断地操作。

image-20240814184603342

HUP好像只能回滚 新开地子master进程。明天再弄

回滚好像又可以了,

image-20240814185457185

分析是QUIT以后就无法使用HUP回滚了

image-20240814185627767

有个长连接挂着,所以有个shutting down👆

一旦quit了确实就不能hup回滚

image-20240814190026131

就只能等用户长连接自己结束了,再去做平滑降级了。

因为quit了就不会再HUP打开子woker进程了,没法回退了,只能做回降,不过回降,也不会导致用户长连接断啊,明天继续

结论:所以热升级最后一个QUIT是不要敲地,可以保持时刻HUP回滚。

如果敲了HUP,是不是可以再在子master下挂master?试试,不行,不能多层挂下去,

就是一个maser挂再挂一个master,所以quit不能乱敲,要保证随时可以hup回去。

工作案例

https://docs.github.com/en/copilot/managing-copilot/managing-github-copilot-in-your-organization/configuring-your-proxy-server-or-firewall-for-copilot

开发内网copilot放行和自动关闭git push的措施

nginx后端节点提供服务的平滑升级

haporxy后端节点的平滑升级

Copyright 🌹 © oneyearice@126.com 2022 all right reserved,powered by Gitbook文档更新时间: 2024-08-30 18:03:35

results matching ""

    No results matching ""