本节主要学习,如何在不终止服务的情况下,升级服务中的应用等。
为了做到零终端,通常,我们应用的部署策略有三种:
三者的一些比较:
微服务部署:蓝绿部署、滚动部署、灰度发布等部署方案对比与总结
蓝绿部署、金丝雀发布(灰度发布)、A/B测试的准确定义
docker swarm默认支持Rolling updates的开箱即用。其他两种方式可通过扩展支持。
1. 滚动部署
关于原理部分可参考上面的链接,下面我们进行实践。
定义一个包含rolling update相关配置的stack,命名为rolling-update.yml:
version: "3.5"
services:
web:
image: nginx:1.13-alpine
ports:
- 8080:80
deploy:
replicas: 10
update_config:
parallelism: 2
delay: 10s
上面的大部分内容之前都学习过,我们这里主要看update_config部分。上面的配置表示,一共会启用10个task,在升级时,每2个task会成为一组进行升级操作,每一组升级成功之后,间隔10s开始下一组的升级。
1、开始部署上面的stack:
用docker stack deploy -c rolling-update.yml web
命令执行部署,部署完成后,可以使用watch docker stack ps web
命令查看,刚开始可能如下图,正在Preparing的一般是镜像还没有拉取下来,要等到超时之后下一次重试(每次重试间隔大约5分钟,应该是可以进行相关设置的。在我没换国内镜像仓库之前失败的次数更多,不过镜像下载一次之后,后续就不用下载了,部署起来速度很快)。
所有的task都必须正常启动了之后,才能开始进行下面的升级工作,如下:
2、升级上面的stack
执行升级之前,建议再开一个窗口,并且将docker-machine环境变量设置为node-1,然后执行watch docker stack ps web
,可以实时查看相关进度。
执行如下命令,升级上面stack中的nginx版本:
docker service update --image nginx:1.14-alpine web_web
可以看到就是每次两个task进行升级操作,其他task继续提供服务。
3、测试完成,删除stack
docker stack rm web
2. 健康检查
有时候,你在查看容器运行的状态时,它可能是显示OK的状态,但容器内的应用是否OK,你这个时候还无法确定。因此,我们通常使用健康检查来确定一个服务是否确实在正常运行。
我们通常有两种方式来定义健康检查。
2.1 Dockerfile定义健康检查
我们可以在制作镜像时,就为镜像内的服务定义健康检查,通常如下:
FROM alpine:3.6
...
HEALTHCHECK --interval=30s \
--timeout=10s
--retries=3
--start-period=60s
CMD curl -f http://localhost:3000/health || exit 1
...
--start-period=60s
:通常服务的启动,需要一定时间,在这段时间内不会执行健康检查。
--interval=30s
:每次检查的时间间隔。
--timeout=10s
:每次检查的超时时间。
--retries=3
:重试多少次后,认为服务不可用。
CMD curl -f http://localhost:3000/health || exit 1
:通过在容器内执行什么命令,来检查服务是否可用。
2.2 stack file文件内定义健康检查
一个示例如下:
version: "3.5"
services:
web:
image: nginx:alpine
healthcheck:
test: ["CMD", "wget", "-qO", "-", "http://localhost"]
interval: 5s
timeout: 2s
retries: 3
start_period: 15s
参数与上面的Dockerfile中的类似,需要注意的是:1、你必须为每个service定义一个健康检查,而不是对整个stack进行。2、如果你的service所引用的镜像内已经包含健康检查,那么这里的定义会覆盖它。
部署上面的示例看看:
docker stack deploy -c stack-health.yml myapp
你会看到与之前有所不同的地方是,在STATUS栏,多了个healthy。
3. 回滚操作
可能有多种原因造成新的版本,确实新版无法正常运行的时候,我们需要预定义好回滚操作。
一个配置示例如下:
version: "3.5"
services:
web:
image: nginx:1.12-alpine
ports:
- 80:80
deploy:
replicas: 10
update_config:
parallelism: 2
delay: 10s
failure_action: rollback
monitor: 10s
healthcheck:
test: ["CMD", "wget", "-qO", "-", "http://localhost"]
interval: 2s
timeout: 2s
retries: 3
start_period: 2s
通过前面的知识,我们知道,一个task要被确认为不健康的状态,需要的时间是:start_period + interval*retries
,因此,这里需要8秒钟。
monitor: 10s
:定义在一个task开始部署后,需要对它持续监控的时间,用来决定是否进行下一组的升级操作。因此,需要大于上面的8秒。
failure_action: rollback
:该参数在默认情况下,如果发现升级失败,那么会立即停止这次升级,并保持这个状态退出升级(例如前面包含10个task,每次升级2个的service,会保持其余8个task还是运行老旧版本的状态,并对外提供服务)。而我们如果配置为rollback
值的话,表示进行回滚操作,那2个失败的task会恢复运行之前的版本,并提供服务。
4. 密码管理
我们经常需要向容器内传递一些敏感信息,例如最常见的密码。我们可以通过-e参数来指定传入的变量以及对应的值,但是,这样我们的密码就以明文形式暴露了。为了解决这个问题,docker swarm 提供了 secret 机制。
secret需要由manager节点创建,并保存。worker需要使用的时候,通过tmpFS
文件系统挂载使用。
4.1 创建密码
从标准输出创建:
echo "sample secret value" | docker secret create sample-secret -
读取文件内容创建:
docker secret create other-secret ~/my-secrets/secret-value.txt
查看当前已有的密码和审查:
[root@node2 swarm]# docker secret ls
ID NAME DRIVER CREATED UPDATED
ams8zjhm15z8qpfv9mgv5wrv9 sample-secret 7 seconds ago 7 seconds ago
[root@node2 swarm]# docker secret inspect sample-secret
[
{
"ID": "ams8zjhm15z8qpfv9mgv5wrv9",
"Version": {
"Index": 893
},
"CreatedAt": "2018-11-05T03:20:51.9911386Z",
"UpdatedAt": "2018-11-05T03:20:51.9911386Z",
"Spec": {
"Name": "sample-secret",
"Labels": {}
}
}
]
4.2 使用密码
[root@node2 swarm]# docker service create --name web \
> --secret sample-secret \
> --publish 8000:8000 \
> fundamentalsofdocker/whoami:latest
jwwd32a5qbld0lviruneezcl5
overall progress: 1 out of 1 tasks
1/1: running [==================================================>]
verify: Service converged
[root@node2 swarm]# docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
jwwd32a5qbld web replicated 1/1 fundamentalsofdocker/whoami:latest *:8000->8000/tcp
[root@node2 swarm]# docker service ps web
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
kd8jknn6llb6 web.1 fundamentalsofdocker/whoami:latest node-3 Running Running 47 seconds ago
[root@node2 swarm]# docker-machine ssh node-3
docker@node-3:~$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
cf2952b075cc fundamentalsofdocker/whoami:latest "/app/http" 2 minutes ago Up 2 minutes 8000/tcp web.1.kd8jknn6llb6vfgne68of5v01
docker@node-3:~$ docker container exec -it cf2952b075cc cat /run/secrets/sample-secret
sample secret value
以上操作为:
1、创建一个使用了sample-secret
密码的service。
2、查看对应的task在哪个节点上,node-3。
3、登录到node-3上,查看对应的容器。
4、在容器内执行cat /run/secrets/sample-secret
,可以得到明文的密码。
以上操作表明了,默认会把密码挂载到容器的/run/secrets/
目录下。
当然,你也可以通过--secret
参数,把密码挂载到容器的指定目录下:
--secret source=sample-secret,target=/run/my-secrets/api-secret-key