什么是 secret
在 Docker Swarm 服务中,secret 是一段数据,例如密码、SSH 私钥、SSL 证书,或其他不应未经加密就在网络上传输、写入 Dockerfile 或应用源代码的数据。Docker secrets 可以集中管理这些数据,只将它们安全地传给确实需要访问的容器。在 Docker Swarm 中,secret 在传输和存储时都会加密。只有明确获准访问的服务能够访问指定 secret,而且只在该服务的任务运行期间可用。
可以用 secret 管理容器在运行时需要、又不希望写入镜像或版本控制系统的敏感数据,例如:
- 用户名与密码。
- TLS 证书与密钥。
- SSH 密钥。
- 数据库名称、内部服务器名称等重要信息。
- 普通字符串或二进制内容,大小最多 500 KB。
注意: Docker secrets 适用于 Swarm 服务,不适用于独立容器。需要使用此功能时,可考虑把容器改为服务运行。有状态容器通常可以用一个副本运行,无须因此修改容器代码。
Secret 还能在容器与凭据之间提供一层抽象。例如,应用有开发、测试和生产环境,各环境使用不同凭据,但在各自 Swarm 中使用同一个 secret 名称。容器只需要知道这个名称,就能在三个环境中工作。
Secret 也可以存放配置文件等非敏感数据。不过 Docker 为非敏感数据提供了 configs。Config 直接挂载到容器文件系统,不使用内存盘。
Windows 支持
Docker 支持在 Windows 容器中使用 secrets。实现上的差异会在下面的示例中说明,主要差异包括:
- Windows 没有管理内存盘的内置驱动,因此运行中的 Windows 容器会把 secret 明文保存在容器根磁盘上;容器停止时会明确删除这些文件。Windows 也不支持通过
docker commit或类似命令,把运行中的容器保存为镜像。 - 建议在宿主机上对 Docker 根目录所在卷启用 BitLocker,使运行中容器的 secret 在磁盘上受到加密保护。
- 自定义目标路径的 secret 文件不会直接绑定挂载到 Windows 容器,因为 Windows 不支持非目录文件的绑定挂载。容器中的所有 secret 会先挂载到
C:\ProgramData\Docker\internal\secrets,再用符号链接指向指定目标。前一个路径属于实现细节,应用不应依赖它;默认目标路径为C:\ProgramData\Docker\secrets。 - Windows 容器服务的 secrets 不支持指定 UID、GID 和 mode。容器中只有管理员以及具有
system访问权限的用户能够访问它们。
Docker 如何管理 secrets
向 Swarm 添加 secret 时,Docker 通过双向 TLS 连接将它发送到 Swarm 管理节点。Secret 保存在加密的 Raft 日志中;完整日志会复制到其他管理节点,使 secrets 与其他 Swarm 管理数据具有相同的高可用保障。
为新建或运行中的服务授予 secret 访问权限后,解密的数据会在 Linux 容器中挂载到内存文件系统。默认路径是 /run/secrets/<secret_name>;Windows 容器的默认路径是 C:\ProgramData\Docker\secrets,其磁盘实现差异见前节。也可以指定自定义位置。
可以随时更新服务,为它增加 secret 访问权限或撤销某个 secret 的访问权限。
只有 Swarm 管理节点,或正在运行获准访问该 secret 的服务任务的节点,才会接收相应的加密 secret。容器任务停止后,其解密 secret 会从容器内存文件系统卸载,并从节点内存清除。
如果正在运行任务的节点与 Swarm 失去连接,该任务容器仍能访问已获得的 secrets;但在重新连接之前,它无法接收更新。
可以随时添加或检查单个 secret,也可以列出全部 secrets。不能删除正在被运行中的服务使用的 secret;轮换 secret一节展示如何在不中断运行中服务的情况下移除旧 secret。
为了便于更新或回退,可以在 secret 名称中加入版本号或日期。由于容器内的挂载点可以单独指定,这样的命名方式较容易实现。
docker secret 命令参考
以下链接介绍具体命令,也可以直接阅读服务使用 secret 的示例。
docker secret createdocker secret inspectdocker secret lsdocker secret rmdocker service create的--secret选项docker service update的--secret-add与--secret-rm选项
在 Compose 文件中定义和使用 secrets
在 Docker Swarm 中配合 Compose 文件使用 secrets,可以先用 docker secret create 定义 secret:
$ printf "my-secret" | docker secret create some_secret -
然后在 compose.yml 顶层的 secrets 中把该 secret 声明为 external:
services:
api:
image: my-web-app
environment:
MY_ENV: /run/secrets/some_secret
secrets:
- some_secret
secrets:
some_secret:
external: true
docker compose 与 docker stack 都支持在 Compose 文件中定义 secrets。语法细节见 Compose 文件参考。它们的分发和存储实现不同;普通 Compose 的文件 secret 按官方说明通过文件绑定挂载交付,不能据此推定它具有 Swarm 的加密 Raft 存储保障。
示例
这一节从简单场景逐步介绍 Docker secrets 的使用方法。示例使用的镜像已增加读取 secret 文件的支持。要给自己的镜像实现类似功能,参阅为镜像添加 Docker secrets 支持。
注意: 为了简化说明,示例使用只有一个 Docker Engine 的 Swarm,服务也不做扩容。大部分示例使用 Linux 容器;Windows 容器同样支持 secrets,差异见 Windows 支持一节。下面的命令及终端输出保留自官方示例,输出用于说明预期形态,不表示本文执行过这些操作。
简单示例:开始使用 secrets
这个例子用少量命令说明 secrets 的基本工作方式。更完整的应用场景见后面的 Nginx 示例。
- 向 Docker 添加 secret。
docker secret create的最后一个参数表示读取 secret 的文件;设为-时,从标准输入读取。
$ printf "This is a secret" | docker secret create my_secret_data -
- 创建
redis服务,并授予它访问该 secret 的权限。默认情况下,容器通过/run/secrets/<secret_name>访问 secret;可以用target指定容器中的文件名。
$ docker service create --name redis --secret my_secret_data redis:alpine
- 用
docker service ps检查任务是否正常。正常运行时,输出形态如下:
$ docker service ps redis
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
bkna6bpn8r1a redis.1 redis:alpine ip-172-31-46-109 Running Running 8 seconds ago
如果任务出错、反复失败和重启,输出可能类似下面这样:
$ docker service ps redis
NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
redis.1.siftice35gla redis:alpine moby Running Running 4 seconds ago
\_ redis.1.whum5b7gu13e redis:alpine moby Shutdown Failed 20 seconds ago "task: non-zero exit (1)"
\_ redis.1.2s6yorvd9zow redis:alpine moby Shutdown Failed 56 seconds ago "task: non-zero exit (1)"
\_ redis.1.ulfzrcyaf6pg redis:alpine moby Shutdown Failed about a minute ago "task: non-zero exit (1)"
\_ redis.1.wrny5v4xyps6 redis:alpine moby Shutdown Failed 2 minutes ago "task: non-zero exit (1)"
- 用
docker ps获取redis服务任务容器的 ID,再用docker container exec连接容器,读取 secret 数据文件。默认文件名与 secret 名称相同,默认权限允许所有用户读取。下面第一个命令说明如何查找容器 ID,后续命令通过 shell 展开自动完成同样的操作。
$ docker ps --filter name=redis -q
5cb1c2348a59
$ docker container exec $(docker ps --filter name=redis -q) ls -l /run/secrets
total 4
-r--r--r-- 1 root root 17 Dec 13 22:48 my_secret_data
$ docker container exec $(docker ps --filter name=redis -q) cat /run/secrets/my_secret_data
This is a secret
- 检查通过提交容器生成的镜像是否包含 secret:
$ docker commit $(docker ps --filter name=redis -q) committed_redis
$ docker run --rm -it committed_redis cat /run/secrets/my_secret_data
cat: can't open '/run/secrets/my_secret_data': No such file or directory
- 尝试删除 secret。由于运行中的
redis服务仍获准访问,删除会失败:
$ docker secret ls
ID NAME CREATED UPDATED
wwwrxza8sxy025bas86593fqs my_secret_data 4 hours ago 4 hours ago
$ docker secret rm my_secret_data
Error response from daemon: rpc error: code = 3 desc = secret
'my_secret_data' is in use by the following service: redis
- 更新运行中的
redis服务,撤销其访问权限:
$ docker service update --secret-rm my_secret_data redis
- 再次执行第 3、4 步,检查服务是否已经无法访问该 secret。容器 ID 会改变,因为
service update会重新部署服务。
$ docker container exec -it $(docker ps --filter name=redis -q) cat /run/secrets/my_secret_data
cat: can't open '/run/secrets/my_secret_data': No such file or directory
- 停止并移除服务,再删除 Docker 中的 secret:
$ docker service rm redis
$ docker secret rm my_secret_data
简单示例:在 Windows 服务中使用 secrets
这个简单例子在 Windows 10 上运行使用 Windows 容器的 Docker for Windows,为 Microsoft IIS 服务提供 secret。它把网页内容存成一个 secret,只用于演示。示例假设已经安装 PowerShell。
- 将以下内容保存为新文件
index.html:
<html lang="en">
<head><title>Hello Docker</title></head>
<body>
<p>Hello Docker! You have deployed a HTML page.</p>
</body>
</html>
- 如果尚未初始化或加入 Swarm,先完成这一操作:
> docker swarm init
- 将
index.html保存为名为homepage的 Swarm secret:
> docker secret create homepage index.html
- 创建 IIS 服务,并授予它访问
homepage的权限:
> docker service create `
--name my-iis `
--publish published=8000,target=8000 `
--secret src=homepage,target="\inetpub\wwwroot\index.html" `
microsoft/iis:nanoserver
注意: 这个例子实际上不需要 secrets,configs 更合适。这里仅用于说明机制。
- 访问
http://localhost:8000/,查看 IIS 是否提供第 1 步的 HTML 内容。 - 移除服务和 secret:
> docker service rm my-iis
> docker secret rm homepage
> docker image remove secret-test
中级示例:在 Nginx 服务中使用 secrets
这个示例分成两部分。第一部分生成站点证书,不直接涉及 Docker secrets;第二部分把证书、密钥和 Nginx 配置保存为 secrets 并使用它们。
生成站点证书
为站点生成根 CA、TLS 证书和密钥。生产站点可以使用 Let’s Encrypt 等服务生成 TLS 证书和密钥;下面使用命令行工具。步骤稍多,是为了准备可存入 secret 的内容。也可以用 Let’s Encrypt 生成站点密钥与证书,将文件分别命名为 site.key 和 site.crt,直接进入“配置 Nginx 容器”一节。
- 生成根密钥:
$ openssl genrsa -out "root-ca.key" 4096
- 使用根密钥生成证书签名请求(CSR):
$ openssl req \
-new -key "root-ca.key" \
-out "root-ca.csr" -sha256 \
-subj '/C=US/ST=CA/L=San Francisco/O=Docker/CN=Swarm Secret Example CA'
- 配置根 CA。新建
root-ca.cnf,写入以下内容。这个配置限制根 CA 只能签署叶子证书,不能签署中间 CA:
[root_ca]
basicConstraints = critical,CA:TRUE,pathlen:1
keyUsage = critical, nonRepudiation, cRLSign, keyCertSign
subjectKeyIdentifier=hash
- 签署证书:
$ openssl x509 -req -days 3650 -in "root-ca.csr" \
-signkey "root-ca.key" -sha256 -out "root-ca.crt" \
-extfile "root-ca.cnf" -extensions \
root_ca
- 生成站点密钥:
$ openssl genrsa -out "site.key" 4096
- 用站点密钥生成站点证书请求:
$ openssl req -new -key "site.key" -out "site.csr" -sha256 \
-subj '/C=US/ST=CA/L=San Francisco/O=Docker/CN=localhost'
- 配置站点证书。新建
site.cnf,写入以下内容。该配置限制证书只能用于验证服务器身份,不能用于签署其他证书:
[server]
authorityKeyIdentifier=keyid,issuer
basicConstraints = critical,CA:FALSE
extendedKeyUsage=serverAuth
keyUsage = critical, digitalSignature, keyEncipherment
subjectAltName = DNS:localhost, IP:127.0.0.1
subjectKeyIdentifier=hash
- 签署站点证书:
$ openssl x509 -req -days 750 -in "site.csr" -sha256 \
-CA "root-ca.crt" -CAkey "root-ca.key" -CAcreateserial \
-out "site.crt" -extfile "site.cnf" -extensions server
- Nginx 服务不需要
site.csr与site.cnf,但以后重新生成站点证书时还会用到。妥善保护root-ca.key。
配置 Nginx 容器
- 准备一个通过 HTTPS 提供静态文件的基础 Nginx 配置。将 TLS 证书和密钥存为 Docker secrets,以便轮换。在当前目录创建
site.conf,内容如下:
server {
listen 443 ssl;
server_name localhost;
ssl_certificate /run/secrets/site.crt;
ssl_certificate_key /run/secrets/site.key;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
}
- 分别为密钥、证书和
site.conf创建三个 secrets。小于 500 KB 的文件都可作为 secret,让密钥、证书及配置与使用它们的服务解耦。下面每个命令的最后一个参数,都是宿主机文件系统中待读取文件的路径;示例中 secret 名称与文件名相同。
$ docker secret create site.key site.key
$ docker secret create site.crt site.crt
$ docker secret create site.conf site.conf
$ docker secret ls
ID NAME CREATED UPDATED
2hvoi9mnnaof7olr3z5g3g7fp site.key 58 seconds ago 58 seconds ago
aya1dh363719pkiuoldpter4b site.crt 24 seconds ago 24 seconds ago
zoa5df26f7vpcoz42qf2csth8 site.conf 11 seconds ago 11 seconds ago
-
创建 Nginx 服务,并授予三个 secrets 的访问权限。第一个
docker service create命令的末尾,将site.conf链接到 Nginx 读取额外配置的/etc/nginx/conf.d/。链接在 Nginx 启动前创建,因此改变配置时不需要重新构建镜像。注意: 常见做法是用 Dockerfile 将
site.conf复制到指定位置,构建自定义镜像,再运行容器。这个例子把放置配置与启动容器合并,不需要自定义镜像。
Secret 默认位于容器的 /run/secrets/,应用要求其他路径时需要额外处理。下面创建指向 secret 实际位置的符号链接,让 Nginx 能读取它:
$ docker service create \
--name nginx \
--secret site.key \
--secret site.crt \
--secret site.conf \
--publish published=3000,target=443 \
nginx:latest \
sh -c "ln -s /run/secrets/site.conf /etc/nginx/conf.d/site.conf && exec nginx -g 'daemon off;'"
也可以通过 target 自定义挂载位置,直接将 site.conf 放在容器内的 /etc/nginx/conf.d/site.conf,不使用符号链接:
$ docker service create \
--name nginx \
--secret site.key \
--secret site.crt \
--secret source=site.conf,target=/etc/nginx/conf.d/site.conf \
--publish published=3000,target=443 \
nginx:latest \
sh -c "exec nginx -g 'daemon off;'"
site.key 与 site.crt 使用简写语法,没有自定义 target,所以会以相同名称挂载在 /run/secrets/。运行中的容器内会出现:
/run/secrets/site.key/run/secrets/site.crt/etc/nginx/conf.d/site.conf
- 检查 Nginx 服务是否运行:
$ docker service ls
ID NAME MODE REPLICAS IMAGE
zeskcec62q24 nginx replicated 1/1 nginx:latest
$ docker service ps nginx
NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
nginx.1.9ls3yo9ugcls nginx:latest moby Running Running 3 minutes ago
- 检查能否访问 Nginx,以及使用的 TLS 证书是否正确。以下为原文的命令与历史示例输出;其中协议、密码套件和会话值不能作为当前生产环境配置建议:
$ curl --cacert root-ca.crt https://localhost:3000
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support. refer to
<a href="https://nginx.org">nginx.org</a>.<br/>
Commercial support is available at
<a href="https://www.nginx.com">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
$ openssl s_client -connect localhost:3000 -CAfile root-ca.crt
CONNECTED(00000003)
depth=1 /C=US/ST=CA/L=San Francisco/O=Docker/CN=Swarm Secret Example CA
verify return:1
depth=0 /C=US/ST=CA/L=San Francisco/O=Docker/CN=localhost
verify return:1
---
Certificate chain
0 s:/C=US/ST=CA/L=San Francisco/O=Docker/CN=localhost
i:/C=US/ST=CA/L=San Francisco/O=Docker/CN=Swarm Secret Example CA
---
Server certificate
-----BEGIN CERTIFICATE-----
…
-----END CERTIFICATE-----
subject=/C=US/ST=CA/L=San Francisco/O=Docker/CN=localhost
issuer=/C=US/ST=CA/L=San Francisco/O=Docker/CN=Swarm Secret Example CA
---
No client certificate CA names sent
---
SSL handshake has read 1663 bytes and written 712 bytes
---
New, TLSv1/SSLv3, Cipher is AES256-SHA
Server public key is 4096 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
SSL-Session:
Protocol : TLSv1
Cipher : AES256-SHA
Session-ID: A1A8BF35549C5715648A12FD7B7E3D861539316B03440187D9DA6C2E48822853
Session-ID-ctx:
Master-Key: F39D1B12274BA16D3A906F390A61438221E381952E9E1E05D3DD784F0135FB81353DA38C6D5C021CB926E844DFC49FC4
Key-Arg : None
Start Time: 1481685096
Timeout : 300 (sec)
Verify return code: 0 (ok)
- 完成演练后,可以移除
nginx服务及保存的 secrets:
$ docker service rm nginx
$ docker secret rm site.crt site.key site.conf
高级示例:在 WordPress 服务中使用 secrets
这个例子创建一个单节点 MySQL 服务,设置自定义 root 密码,把凭据保存为 secrets,再创建一个用这些凭据连接 MySQL 的单节点 WordPress 服务。下一例在此基础上轮换 WordPress 数据库用户的密码并更新服务,使 WordPress 仍能连接 MySQL。
此例演示如何避免把敏感凭据保存在镜像中,或直接写进创建服务的命令参数。
注意: 为了简化说明,例子使用单 Engine Swarm 和单节点 MySQL。不能通过简单增加服务副本来扩容单个 MySQL 实例;配置 MySQL 集群超出此例范围。更改 MySQL root 密码也需要执行查询或
mysqladmin命令,不能只替换磁盘上的文件。
-
生成随机 MySQL 密码,并用
docker secret create保存为mysql_password。调整openssl的最后一个参数可以改变生成长度。下面使用 Base64 表示随机数据,字符不局限于字母和数字;也可以采用其他密码生成方式。注意: Secret 创建后不能原地更新,只能删除再重新创建,而且不能删除正被服务使用的 secret。可以通过
docker service update授予或撤销访问权限。需要轮换时,可以在名称中加入版本,先创建新版本,再让服务使用它,最后删除旧版本。
最后的 - 表示从标准输入读取:
$ openssl rand -base64 20 | docker secret create mysql_password -
l1vinzevzhj4goakjap5ya409
返回值是 secret 的 ID,不是密码。后续例子省略 ID 输出。
再为 MySQL root 用户生成另一个 secret。它只用于初始化 mysql 服务,不会共享给后面创建的 WordPress 服务。
$ openssl rand -base64 20 | docker secret create mysql_root_password -
用 docker secret ls 列出 Docker 管理的 secrets:
$ docker secret ls
ID NAME CREATED UPDATED
l1vinzevzhj4goakjap5ya409 mysql_password 41 seconds ago 41 seconds ago
yvsczlx9votfw3l0nz5rlidig mysql_root_password 12 seconds ago 12 seconds ago
它们保存在 Swarm 的加密 Raft 日志中。
- 创建用户自定义 overlay 网络,供 MySQL 与 WordPress 服务通信。无需把 MySQL 服务暴露给外部宿主机或容器。
$ docker network create -d overlay mysql_private
-
创建 MySQL 服务。它具有以下设置:
- 副本数为
1,只运行一个 MySQL 任务。MySQL 负载均衡留作后续课题,需要的配置不止增加副本。 - 只有
mysql_private网络中的其他容器能访问它。 - 使用
mydata卷保存 MySQL 数据,使服务重启后数据仍保留。 - 两个 secrets 分别挂载在
tmpfs文件系统的/run/secrets/mysql_password与/run/secrets/mysql_root_password,不会变成环境变量,也不会通过docker commit写入镜像。非特权的 WordPress 容器用mysql_password连接数据库。 - 环境变量
MYSQL_PASSWORD_FILE和MYSQL_ROOT_PASSWORD_FILE分别指向对应 secret 文件。MySQL 镜像第一次初始化系统数据库时读取文件中的密码,之后密码保存在 MySQL 系统数据库里。 - 设置
MYSQL_USER与MYSQL_DATABASE。容器启动时创建名为wordpress的数据库;wordpress用户只拥有该数据库的完整权限,不能创建或删除其他数据库,也不能修改 MySQL 配置。
- 副本数为
$ docker service create \
--name mysql \
--replicas 1 \
--network mysql_private \
--mount type=volume,source=mydata,destination=/var/lib/mysql \
--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
- 用
docker service ls检查 MySQL 容器是否运行:
$ docker service ls
ID NAME MODE REPLICAS IMAGE
wvnh0siktqr3 mysql replicated 1/1 mysql:latest
-
创建连接到 MySQL 的 WordPress 服务。它具有以下设置:
- 副本数为
1,只运行一个 WordPress 任务。由于 WordPress 会话数据保存在容器文件系统等限制,负载均衡需要另行设计。 - 将宿主机的 30000 端口对外开放。如果宿主机的 80 端口没有 Web 服务器占用,也可以改为暴露 80 端口。
- 加入
mysql_private网络,与 MySQL 容器通信,并将容器 80 端口发布到全部 Swarm 节点的 30000 端口。 - 访问
mysql_passwordsecret,但把目标文件名设为/run/secrets/wp_db_password。 - 环境变量
WORDPRESS_DB_PASSWORD_FILE指向 secret 文件。WordPress 服务从中读取 MySQL 密码,并写入wp-config.php。 - 使用
wordpress用户和/run/secrets/wp_db_password中的密码连接 MySQL;如果wordpress数据库尚不存在,镜像会尝试创建它。 - 将主题、插件等数据保存在
wpdata卷,使服务重启后这些文件仍在。
- 副本数为
$ docker service create \
--name wordpress \
--replicas 1 \
--network mysql_private \
--publish published=30000,target=80 \
--mount type=volume,source=wpdata,destination=/var/www/html \
--secret source=mysql_password,target=wp_db_password \
-e WORDPRESS_DB_USER="wordpress" \
-e WORDPRESS_DB_PASSWORD_FILE="/run/secrets/wp_db_password" \
-e WORDPRESS_DB_HOST="mysql:3306" \
-e WORDPRESS_DB_NAME="wordpress" \
wordpress:latest
- 用
docker service ls与docker service ps检查服务是否运行:
$ docker service ls
ID NAME MODE REPLICAS IMAGE
wvnh0siktqr3 mysql replicated 1/1 mysql:latest
nzt5xzae4n62 wordpress replicated 1/1 wordpress:latest
$ docker service ps wordpress
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
aukx6hgs9gwc wordpress.1 wordpress:latest moby Running Running 52 seconds ago
此时 WordPress 已把 secret 写入 wp-config.php,理论上可以撤销它对 mysql_password 的访问。先保留访问权限,后面的轮换示例会用到它。
- 从任意 Swarm 节点访问
http://localhost:30000/,通过网页向导完成 WordPress 设置。这些设置保存在 MySQL 的wordpress数据库中。WordPress 会为站点用户生成一个密码,它与 WordPress 访问 MySQL 的密码不同。请安全保存,例如存入密码管理器;轮换数据库密码后,仍需要它登录 WordPress。
可以创建一两篇测试文章,安装插件或主题,检查 WordPress 是否完整可用,以及服务重启后状态是否保留。
- 如果要继续下面的数据库用户密码轮换示例,先保留所有服务和 secrets。
示例:轮换 secret
本例接续前例:创建包含新 MySQL 密码的 secret,让 mysql 和 wordpress 服务使用它,再删除旧 secret。
注意: MySQL 数据库已经存在时,镜像不会重新用环境变量或文件初始化密码。密码保存在数据库中,因此必须另外执行查询或命令来修改。轮换密码或其他 secrets,可能需要 Docker 之外的步骤。
- 创建新密码,保存为
mysql_password_v2:
$ openssl rand -base64 20 | docker secret create mysql_password_v2 -
- 更新 MySQL 服务,让它同时访问旧、新两个 secrets。Secret 不能更新或重命名;可以撤销旧挂载,再以新的目标文件名授予访问权限。
$ docker service update \
--secret-rm mysql_password mysql
$ docker service update \
--secret-add source=mysql_password,target=old_mysql_password \
--secret-add source=mysql_password_v2,target=mysql_password \
mysql
更新会重启服务。重启后,旧 secret 位于 /run/secrets/old_mysql_password,新 secret 位于 /run/secrets/mysql_password。
此时服务能访问两个 secrets,但数据库中 wordpress 用户的实际密码还没有改变。
注意:这个例子不轮换 MySQL
root密码。
- 用
mysqladmin修改wordpress用户的数据库密码。示例命令从/run/secrets文件读取旧、新密码,避免把明文直接写进 shell 历史。但 shell 展开后仍会把密码作为进程参数传给程序,不能将此方法视为没有参数泄露风险;MySQL 的密码安全指南也说明了命令行密码参数的风险。
密码修改后,WordPress 暂时不能连接 MySQL,因此应紧接着更新 WordPress 服务。
先查找 MySQL 任务容器的 ID:
$ docker ps --filter name=mysql -q
c7705cf6176f
将 ID 替换到下面命令中;也可以用后面的 shell 展开版本一步完成。
$ docker container exec <CONTAINER_ID> \
bash -c 'mysqladmin --user=wordpress --password="$(< /run/secrets/old_mysql_password)" password "$(< /run/secrets/mysql_password)"'
或者:
$ docker container exec $(docker ps --filter name=mysql -q) \
bash -c 'mysqladmin --user=wordpress --password="$(< /run/secrets/old_mysql_password)" password "$(< /run/secrets/mysql_password)"'
- 更新
wordpress服务,改用新密码,同时保留目标路径/run/secrets/wp_db_password。这会触发滚动重启,重启后的服务使用新 secret。
$ docker service update \
--secret-rm mysql_password \
--secret-add source=mysql_password_v2,target=wp_db_password \
wordpress
-
再次从任意 Swarm 节点访问
http://localhost:30000/,检查 WordPress 是否可用。使用前面向导生成的站点用户名和密码登录,检查测试文章仍存在,修改过的配置也仍保留。 -
撤销 MySQL 服务对旧 secret 的访问,再从 Docker 删除旧 secret:
$ docker service update \
--secret-rm mysql_password \
mysql
$ docker secret rm mysql_password
- 演练结束后,下面的命令会删除 WordPress 与 MySQL 服务、
mydata与wpdata数据卷,以及 Docker secrets。数据卷删除会移除本例保存的数据,只有确定不再需要这些演练数据时才执行。
$ docker service rm wordpress mysql
$ docker volume rm mydata wpdata
$ docker secret rm mysql_password_v2 mysql_root_password
为镜像添加 Docker secrets 支持
如果开发的容器要以服务运行,而且需要通过环境变量接收凭据等敏感数据,可以考虑让镜像支持 Docker secrets。一种方法是让创建容器时传入的参数,也能从文件中读取。
Docker library 中的许多官方镜像,包括前例使用的 WordPress 镜像,已经支持这种方式。
启动 WordPress 容器时,通常用环境变量传递参数。包含重要信息的变量,例如 WORDPRESS_DB_PASSWORD,还提供从文件读取值的变体 WORDPRESS_DB_PASSWORD_FILE。这样既保持兼容,也允许容器从 Docker 管理的 secret 文件读取数据。
注意: Docker secrets 不会直接设置环境变量。这是有意的设计,因为环境变量可能意外泄露给其他容器,例如使用
--link时。
在 Compose 中使用 secrets
services:
db:
image: mysql:latest
volumes:
- db_data:/var/lib/mysql
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:
- "8000:80"
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wordpress
WORDPRESS_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:
这个 Compose 文件通过两个 secrets 创建一个简单的 WordPress 站点。顶层 secrets 定义 db_password 和 db_root_password,各自的 file 指定其内容来源。db 服务使用两个 secrets,wordpress 只使用其中一个。服务通过环境变量告诉镜像到哪个文件路径读取 secret。
文件在容器内的路径为 /run/secrets/<secret_name>。此处要区分部署方式:普通 Docker Compose 会将宿主机文件绑定挂载到容器;不能保证源文件只保存在内存或从不落盘。 原文这一小节把“只在内存管理”笼统应用到 Compose,本文依据Compose 官方 secrets 说明明确了其文件挂载实现。Swarm 的内存挂载与加密分发规则见前文。
短语法、长语法等更多细节见 Compose Specification。
原样示例的进一步核验: 上面保留的 Compose 代码将 WORDPRESS_DB_HOST 设为 db:3306;应按实际数据库监听端口配置。本稿保留下载当时源文件的全部代码,未执行或保证这些示例与任意 latest 镜像兼容。
来源:Docker 文档团队及贡献者,Manage sensitive data with Docker secrets。Copyright 2016 Docker, Inc.;网站标注 Copyright © 2013–2026 Docker Inc.。正文与代码来源仓库按 Apache License 2.0 使用,许可副本随离线源资料提供。本稿将正文译为中文,保留全部代码和原文样例输出,补充普通 Compose 文件挂载及命令行口令风险说明,并修正了原文关于轮换对象和配置路径的文字不一致。











暂无评论内容