目录

使用 Docker Secrets 管理敏感数据

Docker Secrets 是一种用于在 Docker 容器中安全地存储和管理敏感数据的工具。它提供了一种集中管理和保护敏感数据的机制,例如数据库密码、API 密钥和 SSH 密钥。

关于 Secrets

就 Docker Swarm 服务而言,secrets 是数据包,例如密码、SSH 私钥、SSL 证书或不应通过网络传输或在 Dockerfile 或应用程序源代码中未加密存储的其他数据片断。你可以使用 Docker secrets 来集中管理此类数据,并仅将其安全地传输给需要访问它的那些容器。secrets 在传输过程中和在 Docker Swarm 中处于静止状态时都是加密的。给定 secret 只能由已被明确授予访问权限的那些服务访问,并且仅在那些服务任务正在运行时才能访问。

你可以使用 secrets 来管理容器在运行时所需的任何敏感数据,但不想存储在映像或源代码管理中,例如:

  • 用户名和密码
  • TLS 证书和密钥
  • SSH 密钥
  • 其他重要数据,例如数据库或内部服务器的名称
  • 通用字符串或二进制内容(大小不超过 500 kb)

注意

Docker secrets 仅适用于 Swarm 服务,不适用于独立容器。要使用此功能,请考虑调整你的容器以作为服务运行。有状态容器通常可以在不更改容器代码的情况下以 scale=1 运行。

使用 secrets 的另一个用例是为容器和一组凭据之间提供抽象层。设想一下一个场景,你在应用程序中具有不同的开发、测试和生产环境。这些环境中的每一个都可以具有存储在开发、测试和生产 Swarm 中的凭据,并具有相同的 secret 名称。你的容器仅需知道 secret 的名称即可在所有三个环境中运行。

你还可以使用 secrets 来管理非敏感数据,例如配置文件。但是,Docker 支持使用 configs 来存储非敏感数据。Configs 直接挂载到容器的文件系统中,而无需使用 RAM 磁盘。

Windows 支持

Docker 包括对 Windows 容器的 secrets 支持。如果实现中有差异,那么它们将在下面的示例中被提及。请记住以下显着的差异:

  • Microsoft Windows 没有用于管理 RAM 磁盘的内置驱动程序,因此在运行的 Windows 容器中,secrets 会以纯文本方式持久保存到容器的根磁盘。但是,当容器停止时,secrets 会被明确删除。此外,Windows 不支持使用 docker commit 或类似命令将正在运行的容器持久保存为映像。

  • 在 Windows 上,我们建议在包含 Docker 根目录的主机上的卷上启用 BitLocker,以确保运行的容器的 secrets 在静止状态下已加密。

  • 具有自定义目标的 secrets 文件不会直接绑定挂载到 Windows 容器中,因为 Windows 不支持非目录文件绑定挂载。相反,容器中的 secrets 全部挂载在 C:\ProgramData\Docker\internal\secrets(不应被应用程序依赖的实现细节)中。符号链接用于从那里指向容器中 secrets 的所需目标。默认目标为 C:\ProgramData\Docker\secrets

  • 在创建使用 Windows 容器的服务时,不支持为 secrets 指定 UID、GID 和模式的选项。 secrets 目前只能由具有容器中“系统”访问权限的管理员和用户访问。

Docker 如何管理 secrets

当你将 secrets 添加到 Swarm 时,Docker 会通过相互 TLS 连接将 secrets 发送到 Swarm 管理器。 secrets 存储在 Raft 日志中,该日志已加密。整个 Raft 日志将跨其他管理器复制,从而为 secrets 提供与 Swarm 管理数据的其他部分相同的可用性保证。

当你授予新创建或正在运行的服务访问 secrets 的权限时,已解密的 secrets 将挂载到容器中的内存文件系统中。容器内挂载点的默认位置在 Linux 容器中为 /run/secrets/<secret_name>,在 Windows 容器中为 C:\ProgramData\Docker\secrets。你也可以指定一个自定义位置。

你可以随时更新服务以授予其访问更多 secrets 的权限或吊销其访问给定 secrets 的权限。

节点仅在该节点是 Swarm 管理器或正在运行已获得访问 secrets 权限的服务任务时,才有权访问(加密的) secrets 。当容器任务停止运行时,与之共享的解密的 secrets 会从该容器的内存文件系统中卸载,并从节点的内存中刷新。

如果节点在运行具有访问 secrets 权限的任务容器时失去与 Swarm 的连接,那么任务容器仍可以访问其 secrets ,但无法接收更新,直到节点重新连接到 Swarm。

你可以随时添加或检查单个 secrets ,或列出所有 secrets 。你不能删除正在运行的服务所使用的 secrets 。有关在不中断正在运行的服务的情况下删除 secrets 的方法,请参阅 Rotate a secre

为了更轻松地更新或回滚 secrets ,请考虑在 secrets 名称中添加版本号或日期。通过控制容器内 secrets 的挂载点,这更容易实现。

Docker secrets 命令

使用这些链接来阅读有关特定命令的信息,或继续进行 关于使用 secrets 及服务的示例

示例

本节包括三个渐进式示例,说明了如何使用 Docker secrets。这些示例中使用的映像已更新,以便更容易使用 Docker secrets。要了解如何以类似的方式修改你自己的映像,请参阅 在你的映像中构建对 Docker secrets 的支持

注意

这些示例使用单引擎 Swarm 和非缩放服务以简化操作。这些示例使用 Linux 容器,但 Windows 容器也支持 secrets 。请参阅 Windows 支持

在 compose 文件中定义和使用 secrets

docker-composedocker stack 命令都支持在 compose 文件中定义 secrets 。有关详细信息,请参阅 Compose 文件参考

简单示例:开始使用 secrets

这个简单的示例展示了如何在几个命令中使用 secrets 。对于真实示例,请继续转到 中级示例:使用 Nginx 服务的 secrets

  1. 向 Docker 添加一个 secrets 。docker secret create 命令读取标准输入,因为最后一个参数(代表从中读取 secrets 的文件)设置为 -
1
$ printf "This is a secret" | docker secret create my_secret_data -
  1. 创建一个 redis 服务并授予它访问该 secrets 的权限。默认情况下,容器可以从 /run/secrets/<secret_name> 访问该 secrets ,但你可以使用 target 选项自定义容器上的文件名。
1
$ docker service create --name redis --secret my_secret_data redis:alpine
  1. 使用 docker service ps 验证任务是否正在运行,这样你就可以使用 docker container exec 来连接到容器并读取 secrets 数据文件的内​​容,该文件默认可供所有人读取,并与 secrets 的名称同名。第一个命令说明了如何查找容器 ID,第二个和第三个命令使用 shell 自动完成来执行此操作。
1
2
3
4
$ 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  

如果出现错误,任务失败并重复重新启动,你将看到类似以下内容:

1
2
3
4
5
$ 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)"
  1. 使用 docker ps 获取 redis 服务任务容器的 ID,以便你可以使用 docker container exec 来连接到容器并读取 secrets 数据文件的内​​容,该文件默认可供所有人读取,并与 secrets 的名称同名。第一个命令说明了如何查找容器 ID,第二个和第三个命令使用 shell 自动完成来执行此操作。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
$ 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
  1. 验证如果提交容器, secrets 将不可用。
1
2
3
4
5
$ 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
  1. 尝试删除 secrets 。该删除失败,因为 redis 服务正在运行并且有权访问该 secrets 。
1
2
3
4
5
6
7
8
9
$ 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
  1. 通过更新服务从正在运行的 redis 服务中删除对 secrets 的访问。
1
$ docker service update --secret-rm my_secret_data redis
  1. 再次执行步骤 3 和 4,验证服务不再具有访问 secrets 的权限。容器 ID 不同,因为 service update 命令重新部署了该服务。
1
2
3
$ 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
  1. 停止并删除服务,然后从 Docker 中删除 secrets 。
1
2
3
$ docker service rm redis
 
$ docker secret rm my_secret_data

简单示例:在 Windows 服务中使用 secrets

这是一个非常简单的示例,展示了如何在运行 Windows 容器的 Docker for Windows 上使用 Microsoft IIS 服务及其中的 secrets 。这是一个简单的示例,将网页存储在 secrets 中。

此示例假设你已安装了 PowerShell。

  1. 将以下内容保存到新文件 index.html 中。
1
2
3
<html lang="en">
 <head><title>Hello Docker</title></head> <body>  <p>Hello Docker! You have deployed a HTML page.</p> </body>
</html>
  1. 如果你尚未执行此操作,请初始化或加入 Swarm。
1
docker swarm init
  1. index.html 文件另存为名为 homepage 的 Swarm secrets 。
1
docker secret create homepage index.html
  1. 创建一个 IIS 服务并授予它访问 homepage secrets 的权限。
1
2
docker service create
  --name my-iis  --publish published=8000,target=8000  --secret src=homepage,target="\inetpub\wwwroot\index.html"  microsoft/iis:nanoserver  

注意

从技术上讲,没有理由使用 secrets 来实现此示例;配置文件 更适合。此示例仅用于说明目的。

  1. http://localhost:8000/ 访问 IIS 服务。它应该提供来自第一步的 HTML 内容。

  2. 删除服务和 secrets 。

1
2
3
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 证书和密钥,但此示例使用命令行工具。此步骤有点复杂,但只是一个设置步骤,以便你拥有可以存储为 Docker secrets 的内容。如果你想跳过这些子步骤,你可以使用 Let’s Encrypt 来生成站点密钥和证书,将文件命名为 site.keysite.crt,然后跳到 配置 Nginx 容器

  1. 生成根密钥。
1
$ openssl genrsa -out "root-ca.key" 4096
  1. 使用根密钥生成 CSR。
1
2
$ 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'
  1. 配置根 CA。编辑新文件 root-ca.cnf,并将以下内容粘贴到其中。这将根 CA 限制为签署叶证书,而不是中介 CA。
1
2
3
4
[root_ca]
basicConstraints = critical,CA:TRUE,pathlen:1
keyUsage = critical, nonRepudiation, cRLSign, keyCertSign
subjectKeyIdentifier=hash
  1. 签署证书。
1
2
$ 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
  1. 生成站点密钥。
1
$ openssl genrsa -out "site.key" 4096
  1. 生成站点证书并使用站点密钥对其进行签名。
1
2
$ openssl req -new -key "site.key" -out "site.csr" -sha256 \
     -subj '/C=US/ST=CA/L=San Francisco/O=Docker/CN=localhost'
  1. 配置站点证书。编辑新文件 site.cnf,并将以下内容粘贴到其中。这将站点证书限制为只能用于身份验证服务器,不能用于签署证书。
1
2
3
4
5
6
7
[server]
authorityKeyIdentifier=keyid,issuer
basicConstraints = critical,CA:FALSE
extendedKeyUsage=serverAuth
keyUsage = critical, digitalSignature, keyEncipherment
subjectAltName = DNS:localhost, IP:127.0.0.1
subjectKeyIdentifier=hash
  1. 签署站点证书。
1
2
$ 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
  1. site.csrsite.cnf 文件无需 Nginx 服务,但如果你想生成新站点证书,则需要它们。保护 root-ca.key 文件。

配置 Nginx 容器

  1. 生成一个非常基本的 Nginx 配置,该配置通过 HTTPS 提供静态文件。TLS 证书和密钥作为 Docker secrets 存储,以便可以轻松轮换。

在当前目录中,创建一个名为 site.conf 的新文件,其中包含以下内容:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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;
  }
}
  1. 创建三个 secrets ,分别代表密钥、证书和 site.conf。你可以将任何文件存储为 secrets ,只要它小于 500 KB。这使你能够将密钥、证书和配置与使用它们的 service 分离。在这些命令中的每一个命令中,最后一个参数表示从主机的文件系统中读取 secrets 的文件的路径。在这些示例中, secrets 的名称和文件名相同。
1
2
3
4
5
$ docker secret create site.key site.key
 
$ docker secret create site.crt site.crt
 
$ docker secret create site.conf site.conf
1
2
3
4
5
6
$ 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
  1. 创建一个运行 Nginx 并可以访问这三个 secrets 的服务。创建 docker service create 命令的最后一段,从 site.conf secrets 创建一个符号链接到 /etc/nginx.conf.d/ 的位置,Nginx 在该位置查找额外的配置文件。此步骤在 Nginx 实际启动之前进行,因此你不必在更改 Nginx 配置后重新构建映像。

注意

通常,你将创建一个复制 site.conf 的 Dockerfile,构建一个映像,然后使用你自定义的映像运行一个容器。此示例不需要自定义映像。它将 site.conf 放入到位,并在一个步骤中运行容器。

secrets 位于容器中的 /run/secrets/ 目录中,默认情况下,这可能需要容器中的额外步骤才能将 secrets 提供给不同的路径。下面的示例创建了一个符号链接来指向 site.conf 文件的真实位置,以便 Nginx 可以读取它:

1
2
3
4
5
6
7
8
$ 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 "ln -s /run/secrets/site.conf /etc/nginx/conf.d/site.conf && exec nginx -g 'daemon off;'"

不用创建符号链接, secrets 也可以使用 target 选项指定自定义位置。下面的示例演示了如何在不使用符号链接的情况下将 site.conf secrets 提供给 /etc/nginx/conf.d/site.conf

1
2
3
4
5
6
7
8
$ 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.keysite.crt secrets 使用简短语法,没有设置自定义 target 位置。简短语法在 /run/secrets/ 中挂载 secrets ,该 secrets 的名称与 secrets 名称相同。在正在运行的容器中,现在存在以下三个文件:

  • /run/secrets/site.key
  • /run/secrets/site.crt
  • /etc/nginx/conf.d/site.conf
  1. 验证 Nginx 服务是否正在运行。
1
2
3
4
$ docker service ls
 
ID      NAME  MODE    REPLICAS IMAGE
zeskcec62q24 nginx replicated 1/1    nginx:latest
1
2
3
4
$ docker service ps nginx
 
NAME         IMAGE     NODE DESIRED STATE CURRENT STATE     ERROR PORTS
nginx.1.9ls3yo9ugcls nginx:latest moby Running    Running 3 minutes ago
  1. 验证服务可用:您可以访问 Nginx 服务器,并且正在使用正确的 TLS 证书。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ curl --cacert root-ca.crt https://localhost:3000

<!DOCTYPE html>
<html>
<head>
<title>欢迎使用 nginx!</title>
<style>
    body {    width: 35em;    margin: 0 auto;    font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>欢迎使用 nginx!</h1>
<p>如果您看到此页面,则 nginx Web 服务器已成功安装并且
正在运行。需要进行进一步配置。</p>
 
<p>有关在线文档和支持。请参阅
<a href="https://nginx.org">nginx.org</a>.<br/>
商业支持可在以下位置获得
<a href="https://www.nginx.com">nginx.com</a>. 
 
<p><em>感谢您使用 nginx。</em></p>
</body>
</html>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
$ 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)
  1. 在运行此示例后进行清理,请移除 nginx 服务和存储的 secrets。
1
2
3
$ docker service rm nginx
 
$ docker secret rm site.crt site.key site.conf

高级示例:将 secrets 与 WordPress 服务配合使用

在此示例中,您创建一个自定义 root 密码的单节点 MySQL 服务,将凭据作为 secrets 添加,并创建一个使用这些凭据连接到 MySQL 的单节点 WordPress 服务。[下一示例](#example-rotate-a-secret) 基于此示例,并向您展示如何轮转 MySQL 密码并更新服务,以便 WordPress 服务仍可连接到 MySQL。

此示例演示了一些使用 Docker secrets 的技术,以避免将敏感凭据保存在您的镜像中或直接在命令行中传递它们。

注意

此示例出于简单起见使用了单引擎 swarm,并使用了单节点 MySQL 服务,因为单个 MySQL 服务实例不能仅仅通过使用复制的服务进行扩展,并且设置 MySQL 集群不在此示例的讨论范围内。

此外,更改 MySQL root 密码并不像更改磁盘上的文件那么简单。您必须使用查询或 mysqladmin 命令来更改 MySQL 中的密码。

  1. 使用 docker secret create 命令将随机字母数字密码用于 MySQL 并将其作为 Docker secret 存储,其名称为 mysql_password。若要使密码变短或变长,请调整 openssl 命令的最后一个参数。这只是创建相对随机密码的一种方法。如果您愿意,可以使用其他命令来生成密码。

注意

创建 secret 后,您将无法更新它。您只能将其移除并重新创建,并且无法移除服务正在使用的 secret。但是,您可以使用 docker service update 授予或撤销正在运行的服务对 secrets 的访问权限。如果您需要能够更新 secret,请考虑在 secret 名称中添加一个版本组件,以便您可以稍后添加新版本,然后更新该服务以使用它,然后删除旧版本。

最后一个参数设置为 -,表示从标准输入读取输入。

1
2
3
$ openssl rand -base64 20 | docker secret create mysql_password -
 
l1vinzevzhj4goakjap5ya409

返回的值不是密码,而是 secret 的 ID。在本教程的其余部分,ID 输出将被省略。

为 MySQL root 用户生成第二个 secret。此 secret 未与稍后创建的 WordPress 服务共享。它仅用于引导 mysql 服务。

1
$ openssl rand -base64 20 | docker secret create mysql_root_password -

使用 docker secret ls 列出由 Docker 管理的 secrets:

1
2
3
4
5
$ 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

secrets 存储在 swarm 的加密 Raft 日志中。

  1. 创建一个用户定义的 overlay 网络,用于在 MySQL 和 WordPress 服务之间进行通信。没有必要将 MySQL 服务公开给任何外部主机或容器。
1
$ docker network create -d overlay mysql_private
  1. 创建 MySQL 服务。MySQL 服务具有以下特点:
  • 因为 scale 设置为 1,所以只运行一个 MySQL 任务。负载均衡 MySQL 留给读者作为练习,并且涉及的不仅仅是扩展服务。

  • 只能由 mysql_private 网络上的其他容器访问。

  • 使用卷 mydata 存储 MySQL 数据,以便它在重新启动 mysql 服务期间持续存在。

  • 每个 secrets 都安装在一个 tmpfs 文件系统中,分别位于 /run/secrets/mysql_password/run/secrets/mysql_root_password。它们绝不会公开为环境变量,也无法在运行 docker commit 命令时提交给镜像。WordPress 容器使用 mysql_password secret 连接到 MySQL。

  • 将环境变量 MYSQL_PASSWORD_FILEMYSQL_ROOT_PASSWORD_FILE 设置为指向文件 /run/secrets/mysql_password/run/secrets/mysql_root_passwordmysql 镜像在第一次初始化系统数据库时从这些文件读取密码字符串。之后,密码存储在 MySQL 系统数据库本身中。

  • 设置环境变量 MYSQL_USERMYSQL_DATABASE。容器启动时会创建一个名为 wordpress 的新数据库,并且 wordpress 用户仅对此数据库具有完全权限。此用户无法创建或删除数据库或更改 MySQL 配置。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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
  1. 使用 docker service ls 命令验证 mysql 容器是否正在运行。
1
2
3
4
$ docker service ls
 
ID      NAME  MODE    REPLICAS IMAGE
wvnh0siktqr3 mysql replicated 1/1    mysql:latest

此时,您实际上可以撤销 mysql 服务对 mysql_passwordmysql_root_password secrets 的访问权限,因为密码已保存在 MySQL 系统数据库中。现在不要这样做,因为我们稍后使用它们来促进轮转 MySQL 密码。

  1. 现在 MySQL 已设置好,创建一个连接到 MySQL 服务的 WordPress 服务。WordPress 服务具有以下特点:
  • 因为 scale 设置为 1,所以只运行一个 WordPress 任务。由于 WordPress 会话数据存储在容器文件系统上的限制,因此将 WordPress 负载均衡留给读者作为练习。

  • 将 WordPress 暴露在主机机器的端口 30000 上,以便您可以从外部主机访问它。如果您没有 Web 服务器运行在主机机器的端口 80 上,您可以公开端口 80。

  • 连接到 mysql_private 网络,以便它可以与 mysql 容器通信,并且还将端口 80 发布到所有 swarm 节点的端口 30000。

  • 可以访问 mysql_password secret,但指定了容器内的不同目标文件名。WordPress 容器使用挂载点 /run/secrets/wp_db_password。还通过将模式设置为 0400 来指定该 secret 不是组或全局可读的。

  • 将环境变量 WORDPRESS_DB_PASSWORD_FILE 设置为 secret 挂载路径。WordPress 服务从该文件读取 MySQL 密码字符串,并将其添加到 wp-config.php 配置文件中。

  • 使用用户名 wordpress/run/secrets/wp_db_password 中的密码连接到 MySQL 容器,如果 wordpress 数据库尚不存在,则创建该数据库。

  • 将其数据(例如主题和插件)存储在名为 wpdata 的卷中,以便这些文件在重启服务时持续存在。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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,mode=0400 \
    -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
  1. 使用 docker service lsdocker service ps 命令验证服务是否正在运行。
1
2
3
4
5
$ docker service ls
 
ID      NAME    MODE    REPLICAS IMAGE
wvnh0siktqr3 mysql   replicated 1/1    mysql:latest
nzt5xzae4n62 wordpress replicated 1/1    wordpress:latest
1
2
3
4
$ 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 服务对 mysql_password secret 的访问权限,因为 WordPress 已将其 secret 复制到其配置文件 wp-config.php 中。现在不要这样做,因为我们稍后使用它来促进轮转 MySQL 密码。

  1. 从任何 swarm 节点访问 http://localhost:30000/ 并使用基于 Web 的向导设置 WordPress。所有这些设置都存储在 MySQL wordpress 数据库中。WordPress 会自动为您创建 WordPress 用户的密码,该密码与 WordPress 用于访问 MySQL 的密码完全不同。请安全存储此密码,例如在密码管理器中。在 轮转 secret 后登录 WordPress 时需要此密码。

继续编写一两篇博客文章并安装一个 WordPress 插件或主题,以验证 WordPress 是否完全可用,并且其状态在服务重启期间保存下来。

  1. 如果你打算继续进入下一个示例,展示如何轮转 MySQL root 密码,请不要清理任何服务或 secrets。

示例:轮转一个 secret

此示例基于上一个示例。在此场景中,您创建了一个带有新 MySQL 密码的新 secret,更新 mysqlwordpress 服务以使用它,然后删除旧的 secret。

注意

更改 MySQL 数据库上的密码涉及运行额外的查询或命令,而不是仅仅更改单个环境变量或文件,因为镜像只在数据库尚不存在时设置 MySQL 密码,并且 MySQL 默认情况下将密码存储在 MySQL 数据库中。轮转密码或其他 secrets 可能涉及 Docker 之外的其他步骤。

  1. 创建新密码并将其存储为名为 mysql_password_v2 的 secret。
1
$ openssl rand -base64 20 | docker secret create mysql_password_v2 -
  1. 更新 MySQL 服务以授予其对旧 secret 和新 secret 的访问权限。请记住,您无法更新或重命名 secret,但您可以使用新的目标文件名撤销 secret 并授予其访问权限。
1
2
3
4
5
6
7
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

更新服务会导致它重新启动,当 MySQL 服务第二次重启时,它可以访问 /run/secrets/old_mysql_password 下的旧 secret 和 /run/secrets/mysql_password 下的新 secret。

即使 MySQL 服务现在可以同时访问旧 secret 和新 secret,但 WordPress 用户的 MySQL 密码尚未更改。

注意

此示例不轮换 MySQL root 密码。

  1. 现在,使用 mysqladmin CLI 更改 wordpress 用户的 MySQL 密码。此命令从 /run/secrets 中的文件读取旧密码和新密码,但不在命令行中公开它们或将它们保存在 shell 历史记录中。

快速执行此操作并继续执行下一步,因为 WordPress 失去了连接 MySQL 的能力。

首先,找到 mysql 容器任务的 ID。

1
2
3
$ docker ps --filter name=mysql -q
 
c7705cf6176f

将 ID 替换在下面的命令中,或者使用第二个变体,它使用 shell 展开来一步完成所有操作。

1
2
docker container exec <CONTAINER_ID> \
bash -c 'mysqladmin --user=wordpress --password="$(< /run/secrets/old_mysql_password)" password "$(< /run/secrets/mysql_password)"'

或者:

1
2
$ 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)"'
  1. 更新 wordpress 服务以使用新密码,将目标路径保持在 /run/secrets/wp_db_password,并将文件权限保持在 0400。这会触发 WordPress 服务的滚动重启,并使用新的 secret。
1
2
3
4
docker service update \
    --secret-rm mysql_password \
    --secret-add source=mysql_password_v2,target=wp_db_password,mode=0400 \
    wordpress   
  1. 再次浏览任何 swarm 节点上的 http://localhost:30000/ 来验证 WordPress 是否工作。使用您在完成上一个任务中的 WordPress 向导时设置的 WordPress 用户名和密码。

验证您撰写的博客文章仍然存在,如果您更改了任何配置值,请验证它们是否仍然更改。

  1. 从 MySQL 服务撤销对旧 secret 的访问权限,并从 Docker 中删除旧 secret。
1
2
3
4
5
$ docker service update \
     --secret-rm mysql_password \
     mysql
 
$ docker secret rm mysql_password
  1. 运行以下命令以删除 WordPress 服务、MySQL 容器、mydatawpdata 卷以及 Docker secrets:
1
2
3
4
5
$ docker service rm wordpress mysql
 
$ docker volume rm mydata wpdata
 
$ docker secret rm mysql_password_v2 mysql_root_password

在您的镜像中构建对 Docker Secret 的支持

如果您开发了一个可以部署为服务并且需要敏感数据(例如凭据)作为环境变量的容器,请考虑调整您的镜像以利用 Docker secrets。一种方法是确保您可以将创建容器时传递给镜像的每个参数也都从文件中读取。

很多 Docker 官方镜像在 Docker 库中,例如上述示例中使用的 wordpress 镜像,都已以这种方式更新。

当您启动一个 WordPress 容器时,通过设置它们作为环境变量向它提供它需要的参数。WordPress 镜像已更新,以便包含重要 WordPress 数据的环境变量(例如 WORDPRESS_DB_PASSWORD)也有可从文件读取它们值的变体(WORDPRESS_DB_PASSWORD_FILE)。这种策略可确保向后兼容性,同时允许您的容器从 Docker 管理的 secret 读取信息,而不是直接传递。

注意

Docker secrets 不直接设置环境变量。这是一个深思熟虑的决定,因为环境变量可能会在容器之间意外泄露(例如,如果您使用 --link)。

在 Compose 中使用 Secrets

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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 文件在简单的 WordPress 站点中创建两个 secrets。

顶级元素 secrets 定义了两个 secrets db_passworddb_root_password

在部署时,Docker 会创建这两个 secrets,并将 Compose 文件中指定的内容填充到其中。

db 服务使用这两个 secrets,wordpress 使用一个。

在部署时,Docker 在服务中安装一个文件,位于 /run/secrets/<secret_name> 之下。这些文件绝不会持久化到磁盘中,而是保存在内存中。

每个服务都使用环境变量指定该服务应该在哪里查找 secret 数据。

您可以在 Compose 规范 中找到有关 secrets 短语法和长语法的更多信息。

参考资料