1、前言
前一篇文章介绍了nginx通过nginx_upstream_check_module模块实现后端健康检查,基于这篇文章介绍一下服务优雅上下线的实现方案。
对于微服务来说,服务的优雅上下线是必要的。对于上线来说,如果服务没有启动成功,就不应该对外暴露。对于下线来说,如果服务已经停止了,就应该保证已下线,避免上游流量进入不健康的机器。
在项目升级的时候,绝大部分采用滚动升级的方式,需要停止一个旧的服务,然后启动一个新的服务,在这个过程中往往会出现服务的中断,那如何最大限度的做到发布的优雅,尽可能让升级的这个过程不影响到线上正在运行的业务,这时就需要实现服务的优雅上下线。
服务的优雅上下线就是保证服务的稳定可用,避免流量中断,导致业务不可用。优雅上线其实就是等服务启动完全就绪后,对外提供服务,也叫无损发布,延迟暴露,服务预热。优雅下线其实就是在服务收到停机指令后,要先到注册中心注销,拒绝新的请求后,将旧的业务处理完成。
2、nginx作为服务的流量入口
对于传统架构来说,后端服务常用nginx来做服务的入口或系统之间的交互。
3、应用服务使用注册中心
同一个项目系统之间内部调用,使用注册中心的方式。如:zk、consul、eureka、nacos等。
4、服务优雅上线-运维
应用服务上线流程介绍:
① 执行启动脚本如:【sh server.sh start】,启动服务以java为例:【java -jar app.jar】
② 服务启动需要时间,先等待n秒,然后调用上线接口【http://127.0.0.1:port/ops/v1/on】,接口用来检测服务是否启动成功,若满足上线要求:如果涉及注册中心,则将服务注册到注册中心。
③ 更新接口【http://127.0.0.1:port/ops/v1/check】状态码,若第②部启动成功则更新成【200】,否则保持默认【599】。
④ 若【check】接口状态码非【200】,等待10秒,从新执行【on】接口,最多循环5次。
⑤ 若【check】接口状态码【200】,nginx根据【nginx_upstream_check_module】检查机制自动将服务上线,转发流量到此节点。
5、服务优雅下线-运维
应用服务下线流程介绍:
① 执行停止脚本【sh server.sh stop】,执行下线接口【http://127.0.0.1:port/ops/v1/down】,若服务涉及注册中心,则【down】接口自动调用相关接口将服务从注册中心下线。
② 下线接口执行成功后,同时更新【check】接口状态码为【404】,否则保持不变【200】
③ 当【check】接口状态码是非【200】时,nginx根据【nginx_upstream_check_module】检查机制自动从upstream中摘除,不再分发流量到此节点。
④ 当【check】接口状态码还是【200】时,从新执行第①步进行下线,最多循环10次。
⑤ 等待n秒(根据实际情况定),若有注册中心,虽然从注册中心摘除了节点,但是注册中心刷新列表需要时间,naocs:30s,以及让服务在这n秒内将之前进来的请求执行完毕(若执行不完,分析原因优化接口)。然后开始执行kill命令,先kill pid结束服务进程,n次kill不了服务,再kill -9 强制终止。
6、k8s 上下线思路
基于上面的介绍,在k8s当中可以结合钩子函数和探针资源对象实现pod的上下线。
钩子函数:postStart 、 preStop
k8s在主容器的启动之后和停止之前提供了两个钩子函数,钩子函数能够感知自身生命周期中的事件,并在相应的时刻运行用户指定的指令。
探针
- livenessProbe:存活性探针
- readinessProbe:就绪性探针
- startupProbe:启动探针
7、示例演示demo
1、下面是两个docker服务,端口:9091,9092
# docker ps -a | grep health
15cadf1009a6 registry.cn-beijing.aliyuncs.com/wl_666/app:health-check-v2 "/bin/bash start.sh" 2 weeks ago Up 4 seconds 0.0.0.0:9092->3000/tcp, :::9092->3000/tcp tender_meninsky
2c691a9985bf registry.cn-beijing.aliyuncs.com/wl_666/app:health-check-v1 "/bin/bash start.sh" 2 weeks ago Up 4 seconds 0.0.0.0:9091->3000/tcp, :::9091->3000/tcp funny_diffie
2、服务接口信息如下:
# curl -s http://192.168.100.210:9091 | jq
{
"/info": "服务运行信息",
"/ops/v1/check": "健康检查接口状态",
"/ops/v1/down": "服务下线",
"/ops/v1/on": "服务上线",
"describe": "请调用一下接口"
}
3、nginx配置如下:
# cat nginx_upstream_check.conf
upstream cluster{
server 192.168.100.210:9091;
server 192.168.100.210:9092;
check interval=3000 rise=2 fall=2 timeout=3000 type=http;
check_http_send "GET /ops/v1/check HTTP/1.0\r\n\r\n ";
check_http_expect_alive http_2xx http_3xx;
}
server {
listen 8888;
server_name localhost;
#charset koi8-r;
access_log logs/nginx_upstream_check.log main;
location / {
root html;
index index.html;
}
location ^~ /nginxServer/ {
proxy_pass http://cluster/;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass_request_body on;
proxy_set_header Cookie $http_cookie;
real_ip_header X-Real-IP;
}
location /nginx_status {
check_status;
access_log off;
}
}
8、上线演示
1、服务刚启动还未调用上线接口时,nginx状态如下,通过nginx调用后端接口502。
# curl http://192.168.100.210:8888/nginxServer/info
<html>
<head><title>502 Bad Gateway</title></head>
<body>
<center><h1>502 Bad Gateway</h1></center>
<hr><center>nginx</center>
</body>
</html>
# curl -I http://192.168.100.210:8888/nginxServer/info
HTTP/1.1 502 Bad Gateway
Server:
Date: Wed, 07 Aug 2024 13:00:09 GMT
Content-Type: text/html
Content-Length: 150
Connection: keep-alive
2、将第一个服务上线后:
查看上线前的chek接口状态码
# curl -s http://192.168.100.210:9091/ops/v1/check | jq
{
"url": "/ops/v1/check",
"url_code": "599",
"time": "2024-08-07 21:03:13"
}
调用上线接口
[root@node-210 ~]# curl -s http://192.168.100.210:9091/ops/v1/on | jq
{
"url": "/ops/v1/on",
"url_code": "200",
"time": "2024-08-07 21:03:18"
}
调用上线后的check接口状态码
[root@node-210 ~]# curl -s http://192.168.100.210:9091/ops/v1/check | jq
{
"url": "/ops/v1/check",
"url_code": "200",
"time": "2024-08-07 21:03:21"
}
通过nginx调用后端接口
# curl -s http://192.168.100.210:8888/nginxServer/info | jq
{
"time": "2024-08-07 21:05:27",
"hostname": "2c691a9985bf",
"ipaddress": "172.17.0.3",
"version": "v1"
}
3、第二个服务同样方式上线
# curl -s http://192.168.100.210:9092/ops/v1/on | jq
{
"url": "/ops/v1/on",
"url_code": "200",
"time": "2024-08-07 21:06:42"
}
[root@node-210 ~]# curl -s http://192.168.100.210:9092/ops/v1/check | jq
{
"url": "/ops/v1/check",
"url_code": "200",
"time": "2024-08-07 21:06:52"
}
通过nginx调用后端接口:都会调用到了,注意返回version字段
[root@node-210 ~]# curl -s http://192.168.100.210:8888/nginxServer/info | jq
{
"time": "2024-08-07 21:07:48",
"hostname": "2c691a9985bf",
"ipaddress": "172.17.0.3",
"version": "v1"
}
[root@node-210 ~]# curl -s http://192.168.100.210:8888/nginxServer/info | jq
{
"time": "2024-08-07 21:07:50",
"hostname": "15cadf1009a6",
"ipaddress": "172.17.0.2",
"version": "v2"
}
9、下线演示
1、下线一个服务
下线前check接口状态码:200
[root@node-210 ~]# curl -s http://192.168.100.210:9092/ops/v1/check | jq
{
"url": "/ops/v1/check",
"url_code": "200",
"time": "2024-08-07 21:11:00"
}
调用下线接口
[root@node-210 ~]# curl -s http://192.168.100.210:9092/ops/v1/down | jq
{
"url": "/ops/v1/down",
"url_code": "404",
"time": "2024-08-07 21:11:08"
}
下线后check接口状态码:404
[root@node-210 ~]# curl -s http://192.168.100.210:9092/ops/v1/check | jq
{
"url": "/ops/v1/check",
"url_code": "404",
"time": "2024-08-07 21:11:12"
}
nginx已经从upstream中将此节点摘除了
文章多多少少会有不足的地方,请多多见谅!ღ ღ ღ ღ ღ