replicated mode vs global mode
Swarm 可以在 service 创建或运行过程中灵活地通过 --replicas
调整容器副本的数量,内部调度器则会根据当前集群的资源使用状况在不同 node 上启停容器,这就是 service 默认的 replicated mode
。在此模式下,node 上运行的副本数有多有少,一般情况下,资源更丰富的 node 运行的副本数更多,反之亦然。
除了 replicated mode
,service 还提供了一个 global mode
,其作用是强制在每个 node 上都运行一个且最多一个副本。
此模式特别适合需要运行 daemon 的集群环境。
比如要收集所有容器的日志,就可以 global mode 创建 service,在所有 node 上都运行 gliderlabs/logspout 容器,即使之后有新的 node 加入,swarm 也会自动在新 node 上启动一个 gliderlabs/logspout 副本。
docker service create \
--mode global \
--name logspout \
--mount type=bind,source=/var/run/docker.sock,destination=/var/run/docker.sock \
gliderlabs/logspout
用 Label 控制 Service 的位置
无论采用 global mode 还是 replicated mode,副本运行在哪些节点都是由 Swarm 决定的,作为用户我们可以使用label精细控制 service 的运行位置。
- 1.为每个 node 定义 label。
-
2.设置 service 运行在指定 label 的 node 上。
- 使用ubuntu1作为测试环境
docker node update --label-add env=test ubuntu1
docker node inspect ubuntu1 --pretty
- 使用ubuntu3作为生产环境
docker node update --label-add env=prod ubuntu3
- 部署到测试环境
docker service create \
--constraint node.labels.env==test \
--replicas 3 \
--name my_web \
--publish 8080:80 \
httpd
- 更新 service,将其迁移到生产环境:
docker service update --constraint-rm node.labels.env==test my_web
docker service update --constraint-add node.labels.env==prod my_web
label 还可以跟 global 模式配合起来使用,比如只收集生产环境中容器的日志。
docker service create \
--mode global \
--constraint node.labels.env==prod \
--name logspout \
--mount type=bind,source=/var/run/docker.sock,destination=/var/run/docker.sock \
gliderlabs/logspout
只有 ubuntu3
节点上才会运行 logspout。
Health Check
Docker 支持的 Health Check
可以是任何一个单独的命令,Docker 会在容器中执行该命令,如果返回 0
,容器被认为是 healthy
,如果返回 1
,则为 unhealthy
。
- Http请求检查
对于提供 HTTP 服务接口的应用,常用的 Health Check 是通过 curl 检查 HTTP 状态码,比如:
curl --fail http://localhost:8080/ || exit 1
如果 curl 命令检测到任何一个错误的 HTTP 状态码,则返回 1,Health Check 失败。
- health场景
docker service create --name my_db \
--health-cmd "curl --fail http://localhost:8091/pools || exit 1" \
couchbase
--health-cmd
Health Check 的命令,还有几个相关的参数:
--timeout
: 命令超时的时间,默认 30s。
--interval
: 命令执行的间隔时间,默认 30s。
--retries
: 命令失败重试的次数,默认为 3,如果 3 次都失败了则会将容器标记为 unhealthy。swarm 会销毁并重建 unhealthy 的副本。
- unhealthy场景
docker service create --name my_db2 \
--health-cmd "curl --fail http://localhost:8091/non-exist || exit 1" \
couchbase
Docker 默认只能通过容器进程的返回码判断容器的状态,Health Check 则能够从业务角度判断应用是否发生故障,是否需要重启。
Secret(密码、私钥)
docker swarm 提供了 secret 机制,允许将敏感信息加密后保存到 secret 中,用户可以指定哪些容器可以使用此 secret。
启动MySQL容器
- 1.创建密码文件
echo "my-secret-pw" | docker secret create my_secret_data -
- 2.启动MySQL service,并制定使用secret
docker service create \
--name mysql \
--secret source=my_secret_data,target=mysql_root_password \
-e MYSQL_ROOT_PASSWORD_FILE="/run/secrets/mysql_root_password" \
mysql:latest
① source 指定容器使用 secret 后,secret 会被解密并存放到容器的文件系统中,默认位置为 /run/secrets/<secret_name>
。--secret source=my_secret_data,target=mysql_root_password
的作用就是指定使用 secret my_secret_data,然后把器解密后的内容保存到容器 /run/secrets/mysql_root_password
文件中,文件名称 mysql_root_password
由 target
指定。
②环境变量 MYSQL_ROOT_PASSWORD_FILE
指定从 /run/secrets/mysql_root_password
中读取并设置 MySQL 的管理员密码。
优势
创建 secret 和使用 secret 是分开完成的,将密码和容器解耦合。secret 可以由专人(比如管理员)创建,而运行容器的用户只需使用 secret 而不需要知道 secret 的内容。也就是说,两个步骤可以由不同的人在不同的时间完成。
疑问
secret 是以文件的形式 mount 到容器中,容器怎么知道去哪里读取 secret 呢?
答:这需要 image 的支持。如果 image 希望它部署出来的容器能够从 secret 中读取数据,那么此 image 就应该提供一种方式,让用户能够指定 secret 的位置。最常用的方法就是通过环境变量,Docker 的很多官方 image 都是采用这种方式。
比如 MySQL 镜像同时提供了 MYSQL_ROOT_PASSWORD
和 MYSQL_ROOT_PASSWORD_FILE
两个环境变量。用户可以用 MYSQL_ROOT_PASSWORD
显示地设置管理员密码,也可以通过 MYSQL_ROOT_PASSWORD_FILE
指定 secret 路径。
Secret 的使用场景
我们可以用 secret 管理任何敏感数据。这些敏感数据是容器在运行时需要的,同时我们不又想将这些数据保存到镜像中。
secret 可用于管理:
- 1.用户名和密码。
- 2.TLS 证书。
- 3.SSH 秘钥。
- 3.其他小于 500 KB 的数据。
secret 只能在 swarm service 中使用。普通容器想使用 secret,可以将其包装成副本数为 1 的 service。
Secret 的安全性
当在 swarm 中创建 secret 时,Docker 通过 TLS 连接将加密后的 secret 发送给所以的 manager 节点。
secret 创建后,即使是 swarm manager 也无法查看 secret 的明文数据,只能通过 docker secret inspect
查看 secret 的一般信息
部署WordPress
我们会部署一个 WordPress 应用,WordPress 是流行的开源博客系统。
我们将创建一个 MySQL service,将密码保存到 secret 中。我们还会创建一个 WordPress service,它将使用 secret 连接 MySQL。这个例子将展示如何用 secret 避免在 image 中存放敏感信息,或者在命令行中直接传递敏感数据。
- 1.创建secret
openssl rand -base64 20 | docker secret create mysql_root_password -
一般情况下,应用不会直接用 root 密码访问 MySQL。我们会创建一个单独的用户 workpress,密码存放到 secret mysql_password中。
openssl rand -base64 20 > password.txt
docker secret create mysql_password password.txt
- 2.创建自定义的 overlay 网络
MySQL 通过 overlay 网络 mysql_private 与 WordPress 通信,不需要将 MySQL service 暴露给外部网络和其他容器。
docker network create -d overlay mysql_private
- 3.创建 MySQL service
docker service create \
--name mysql \
--network mysql_private \
--secret source=mysql_root_password,target=mysql_root_password \
--secret source=mysql_password,target=mysql_password \
-e MYSQL_ROOT_PASSWORD_FILE="/run/secrets/mysql_root_password" \
-e MYSQL_PASSWORD_FILE="/run/secrets/mysql_password" \
-e MYSQL_USER="wordpress" \
-e MYSQL_DATABASE="wordpress" \
mysql:latest
MYSQL_DATABASE
: 指明创建数据库 wordpress。
MYSQL_USER
和 MYSQL_PASSWORD_FILE
指明创建数据库用户 workpress,密码从 secret mysql_password 中读取。
- 4.创建 WordPress service
docker service create \
--name wordpress \
--network mysql_private \
--publish 30000:80 \
--secret source=mysql_password,target=wp_db_password \
-e WORDPRESS_DB_HOST="mysql:3306" \
-e WORDPRESS_DB_NAME="wordpress" \
-e WORDPRESS_DB_USER="wordpress" \
-e WORDPRESS_DB_PASSWORD_FILE="/run/secrets/wp_db_password" \
wordpress:latest
WORDPRESS_DB_HOST
: 指明 MySQL service 地址 mysql:3306,这里用到了 DNS。
WORDPRESS_DB_NAME
: 指明 WordPress 的数据库为 wordpress,与前面 MYSQL_DATABASE 一致。
WORDPRESS_DB_USER
: 指明连接 WordPress 数据库的用户为 wordpress,与前面 MYSQL_USER 一致。
WORDPRESS_DB_PASSWORD_FILE
: 指明数据库的用户 wordpress 的密码,从 secret mysql_password 中获取。
- 5.验证 WordPress
浏览器访问 http://${swarm_master_ip}:30000/
总结
- 1.首先创建 secret。
- 2.然后创建 MySQL service,这是 WordPress 依赖的服务。
- 3.最后创建 WordPress service。
也就是说,这个应用包含了两个 service:MySQL 和 WordPress,它们之间有明确的依赖关系,必须先启动 MySQL。
为了保证这个依赖关系,我们控制了 docker secret 和 docker service 命令的执行顺序,只不过这个过程是手工完成的。
stack(功能与docker-compose基本相同,但有所区别))
- 1.准备secret
openssl rand -base64 20 > db_password.txt
openssl rand -base64 20 > db_root_password.txt
- 2.编写文件
vi wordpress.yml
version: '3.1'
services:
db:
image: mysql:latest
command:
- --default_authentication_plugin=mysql_native_password
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
volumes:
- db_data:/var/lib/mysql
restart: always
environment:
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_root_password
- db_password
wordpress:
depends_on:
- db
image: wordpress:latest
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wordpress
WOREPRESS_DB_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
secrets:
db_password:
file: db_password.txt
db_root_password:
file: db_root_password.txt
volumes:
db_data: {}
YAML 是一种阅读性很强的文本格式,上面这个 stack 中定义了三种资源:service、secret 和 volume。
① services 定义了两个 service:db 和 wordpress。
② secrets 定义了两个 secret:db_password 和 db_root_password,在 service db 和 wordpress 的定义中引用了这两个 secret。
③ volumes 定义了一个 volume:db_data,service db 使用了此 volume。
④ wordpress 通过 depends_on 指定自己依赖 db 这个 service。Docker 会保证当 db 正常运行后再启动 wordpress。
可以在 YAML 中定义的元素远远不止这里看到的这几个,完整列表和使用方法可参考官方文档
- 3.部署应用
docker stack deploy -c wordpress.yml wpstack
- 4.查看部署结果
ubuntu2@root ~ docker stack ls
NAME SERVICES ORCHESTRATOR
wpstack 2 Swarm
ubuntu2@root ~ docker stack services wpstack
ID NAME MODE REPLICAS IMAGE PORTS
5gzychnlkzct wpstack_db replicated 1/1 mysql:latest
iac2l00okp89 wpstack_wordpress replicated 1/1 wordpress:latest *:8080->80/tcp
ubuntu2@root ~ docker stack ps wpstack
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
l9vpx2ueqto5 wpstack_db.1 mysql:latest ubuntu1 Running Running about a minute ago
coqcmhe2i50h wpstack_wordpress.1 wordpress:latest ubuntu2 Running Running 2 minutes ago
ubuntu2@root ~ docker secret ls
ID NAME DRIVER CREATED UPDATED
2o5v0mg35ew69tbwun3xj17i9 wpstack_db_password 2 minutes ago 2 minutes ago
euqfdtey6n2kr32m694xpn9lj wpstack_db_root_password 2 minutes ago 2 minutes ago
ubuntu2@root ~ docker stack rm wpstack
Removing service wpstack_db
Removing service wpstack_wordpress
Removing secret wpstack_db_password
Removing secret wpstack_db_root_password
Removing network wpstack_default