Docker Swarm 实现 Golang 业务滚动更新

自动化构建会让我们的业务发版非常快,如何对用户无感知的更新是一个问题,在网上有大量的文章介绍,但他们中间是会有一个服务完全中断的过程来完成替换,比如等待容器关闭后再启动新的容器,或者像我在 LNMP 部署中使用的方法,拉取后直接暴力重启全部服务。在未使用 Docker Swarm 之前,我另外一个思考是 docker-compose 部署两个一个的服务容器,监听不同的端口,然后通过 nginx 来进行流量分发,但 nginx 开源版没办法主动进行心跳检测。

随着对 Docker Swarm 深入了解,它用来做更新真的非常方便,内置了滚动的支持,不需要我们预先准备好两个容器(这里不是指 replicas),支持新的容器启动后再销毁旧的容器,支持同一时间更新容器的数量、更新之间的间隔时间。

Golang 方面的代码,主要是监听信号量,然后不再对新请求进行处理,同时等待旧的请求处理完毕后退出,这块网上文章非常多,提供下我们测试环境的: https://github.com/hongfs/env/blob/main/golang-signal/main.go

虚构一个业务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
version: "3.9"
services:
web:
image: ghcr.io/hongfs/env:golang-signal
ports:
- "80:80"
deploy:
replicas: 4 # 启动 4 个容器
update_config:
parallelism: 1 # 同一时间只更新一个容器
delay: 10s # 更新之间的间隔时间
failure_action: rollback # 更新失败后回滚
monitor: 10s # 更新后的健康检查时间
order: start-first # 新容器启动后再销毁旧容器

进行部署

1
$ docker stack deploy --compose-file docker-compose.yaml hongfs

查看现在的容器

1
2
3
4
5
6
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
828f9911e9ce ghcr.io/hongfs/env:golang-signal "./main" 55 minutes ago Up 55 minutes 80/tcp hongfs_web.3.7wqdim5z7hsksdi1kuv5febgi
cc2268a9c95a ghcr.io/hongfs/env:golang-signal "./main" 56 minutes ago Up 56 minutes 80/tcp hongfs_web.4.eu98e6roc600k7ubmcvdxkb1d
0f71f604059f ghcr.io/hongfs/env:golang-signal "./main" 56 minutes ago Up 56 minutes 80/tcp hongfs_web.1.rfmkxg8zsg7109r8louipqyud
394ae065d7e5 ghcr.io/hongfs/env:golang-signal "./main" 57 minutes ago Up 57 minutes 80/tcp hongfs_web.2.z6k6aacmkm6gk6jnqd9beok8f

进行更新,切记一定要带上镜像,提前 docker pull 来更新镜像是不行的。

1
2
3
4
5
6
7
$ docker service update --image ghcr.io/hongfs/env:golang-signal --force hongfs_web
hongfs_web
overall progress: 1 out of 4 tasks
1/4: running [==================================================>]
2/4:
3/4:
4/4:

更新完成后重新看容器列表

1
2
3
4
5
6
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
028f049ee6bf ghcr.io/hongfs/env:golang-signal "./main" 19 minutes ago Up 19 minutes 80/tcp hongfs_web.1.tu75a0m946v4cxosqoagrhkqk
fb211f11d1d9 ghcr.io/hongfs/env:golang-signal "./main" 20 minutes ago Up 20 minutes 80/tcp hongfs_web.4.ls3jogwefrcc55u0rfs7v6pji
2097bdfdaacc ghcr.io/hongfs/env:golang-signal "./main" 20 minutes ago Up 20 minutes 80/tcp hongfs_web.2.uhro1mmi5z8fkofw22xnpqzn1
5bbd87f9c48b ghcr.io/hongfs/env:golang-signal "./main" 21 minutes ago Up 21 minutes 80/tcp hongfs_web.3.f3yb4wttoh9gd2fz02odwyhjo

更新开始之前就启动了 WRK 进行压测,更新结束后终止了压测,可以看见是没有请求有异常的。

1
2
3
4
5
6
7
8
9
10
$ wrk -c10 -t10 -d10m http://127.0.0.1
Running 10m test @ http://127.0.0.1
10 threads and 10 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 400.67us 0.86ms 45.92ms 94.82%
Req/Sec 4.20k 1.06k 10.50k 74.86%
25057428 requests in 10.00m, 3.27GB read
Socket errors: connect 0, read 15, write 0, timeout 0
Requests/sec: 41755.76
Transfer/sec: 5.57MB

到这里,我们就可以放心的进行更新了,不用担心更新过程中有请求失败,也不用担心更新过程中有请求超时,这些通过信号量和 Docker Swarm 处理好了,不过在长任务场景下,就是一个容器里面有请求耗时非常长,那会影响容器的释放和进入下一个更新阶段,这个可能就不适合了,或者要独立出来部署。

最后,你应该把更新代码加入到自动化部署流程中来。

往上