使用 Docker secrets 管理敏感数据

关于 secrets

就 Docker Swarm service 而言,secret 是一块数据,例如密码、SSH 私钥、SSL 证书,或者其他不应通过网络传输或以未加密形式存储在 Dockerfile 或应用程序源代码中的数据。您可以使用 Docker secrets 集中管理这些数据,并仅将其安全地传输给需要访问它们的容器。Secrets 在 Docker swarm 中传输和存储时会加密。给定 secret 仅对那些已被明确授予访问权限的 services 可访问,并且仅在这些 service 任务运行时才可访问。

您可以使用 secrets 管理容器在运行时需要访问的任何敏感数据,但您不想将其存储在镜像或源代码控制中,例如:

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

注意

Docker secrets 仅适用于 swarm services,不适用于独立容器。要使用此功能,请考虑将您的容器调整为作为 service 运行。有状态容器通常可以在不更改容器代码的情况下以规模 1 运行。

使用 secrets 的另一个用例是在容器和一组凭据之间提供一层抽象。考虑一个场景,您的应用程序有独立的开发、测试和生产环境。每个环境都可以有不同的凭据,以相同的 secret 名称存储在开发、测试和生产 swarm 中。您的容器只需知道 secret 的名称即可在所有三个环境中运行。

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

Windows 支持

Docker 支持在 Windows 容器上使用 secrets。实现中的差异会在以下示例中指出。请记住以下值得注意的差异:

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

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

  • 带有自定义目标的 Secret 文件不会直接 bind-mount 到 Windows 容器中,因为 Windows 不支持非目录文件 bind-mount。相反,容器的 secrets 都挂载在容器内的 C:\ProgramData\Docker\internal\secrets 中(这是一个不应被应用程序依赖的实现细节)。符号链接用于从该位置指向容器内 secret 的目标位置。默认目标是 C:\ProgramData\Docker\secrets

  • 创建使用 Windows 容器的 service 时,secrets 不支持指定 UID、GID 和模式的选项。Secrets 目前只能由容器内的管理员和具有 system 访问权限的用户访问。

Docker 如何管理 secrets

当您向 swarm 添加 secret 时,Docker 通过相互 TLS 连接将 secret 发送给 swarm manager。Secret 存储在加密的 Raft 日志中。整个 Raft 日志在其他 manager 之间复制,确保 secrets 与 swarm 管理数据的其余部分具有相同的高可用性保证。

当您授予新创建或运行中的 service 访问 secret 的权限时,解密的 secret 会被挂载到容器内的内存文件系统中。挂载点在 Linux 容器中的默认位置是 /run/secrets/<secret_name>,在 Windows 容器中是 C:\ProgramData\Docker\secrets。您也可以指定自定义位置。

您可以随时更新 service,以授予其访问其他 secrets 的权限或撤销其对给定 secret 的访问权限。

节点只有在是 swarm manager 或正在运行已被授予访问 secret 权限的 service task 时,才能访问(加密的)secrets。当容器 task 停止运行时,共享给它的解密 secrets 会从该容器的内存文件系统中卸载,并从节点内存中清除。

如果节点在运行具有 secret 访问权限的 task 容器时与 swarm 断开连接,该 task 容器仍然可以访问其 secrets,但在重新连接到 swarm 之前无法接收更新。

您可以随时添加或检查单个 secret,或列出所有 secrets。您无法移除正在运行的 service 正在使用的 secret。请参阅 轮换 secret 以了解如何在不中断运行中 services 的情况下移除 secret。

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

阅读更多关于 docker secret 命令的信息

使用这些链接阅读特定命令,或继续阅读 使用 secrets 和 service 的示例

示例

本节包含三个循序渐进的示例,说明如何使用 Docker secrets。这些示例中使用的镜像已更新,以便更轻松地使用 Docker secrets。要了解如何以类似方式修改您自己的镜像,请参阅 在您的镜像中构建对 Docker Secrets 的支持

注意

这些示例使用单个 Engine swarm 和未扩展的 services 以简化说明。示例使用 Linux 容器,但 Windows 容器也支持 secrets。请参阅 Windows 支持

在 compose 文件中定义和使用 secrets

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

简单示例:secrets 入门

这个简单示例通过几个命令展示了 secrets 如何工作。有关实际示例,请继续阅读 中级示例:在 Nginx service 中使用 secrets

  1. 将 secret 添加到 Docker。docker secret create 命令读取标准输入,因为最后一个参数(表示要从中读取 secret 的文件)设置为 -

    $ printf "This is a secret" | docker secret create my_secret_data -
    
  2. 创建一个 redis service 并授予其访问 secret 的权限。默认情况下,容器可以在 /run/secrets/<secret_name> 访问 secret,但您可以使用 target 选项自定义容器上的文件名。

    $ docker service  create --name redis --secret my_secret_data redis:alpine
    
  3. 使用 docker service ps 验证 task 是否运行正常。如果一切正常,输出应类似于:

    $ 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  
    

    如果发生错误,并且 task 失败并反复重启,您会看到类似以下内容:

    $ 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)"
    
  4. 使用 docker ps 获取 redis service task 容器的 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
    
  5. 验证如果您提交容器,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
    
  6. 尝试移除 secret。移除失败,因为 redis service 正在运行并访问着该 secret。

    $ 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
    
  7. 通过更新 service 从正在运行的 redis service 移除对 secret 的访问权限。

    $ docker service update --secret-rm my_secret_data redis
    
  8. 再次重复步骤 3 和 4,验证 service 是否不再访问 secret。容器 ID 会不同,因为 service update 命令会重新部署 service。

    $ 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
    
  9. 停止并移除 service,并从 Docker 中移除 secret。

    $ docker service rm redis
    
    $ docker secret rm my_secret_data
    

简单示例:在 Windows service 中使用 secrets

这是一个非常简单的示例,展示了如何在运行 Windows 容器的 Docker for Windows(运行在 Microsoft Windows 10 上)中使用 secrets 和 Microsoft IIS service。这是一个将网页存储在 secret 中的简单示例。

本示例假定您已安装 PowerShell。

  1. 将以下内容保存到新文件 index.html 中。

    <html lang="en">
      <head><title>Hello Docker</title></head>
      <body>
        <p>Hello Docker! You have deployed a HTML page.</p>
      </body>
    </html>
  2. 如果您尚未初始化或加入 swarm,请先执行此操作。

    > docker swarm init
    
  3. index.html 文件保存为名为 homepage 的 swarm secret。

    > docker secret create homepage index.html
    
  4. 创建一个 IIS service 并授予其访问 homepage secret 的权限。

    > docker service create `
        --name my-iis `
        --publish published=8000,target=8000 `
        --secret src=homepage,target="\inetpub\wwwroot\index.html" `
        microsoft/iis:nanoserver
    

    注意

    技术上来说,这个示例没有理由使用 secrets;configs 更适合。本示例仅用于说明。

  5. 访问 http://localhost:8000/ 上的 IIS service。它应该提供第一步中的 HTML 内容。

  6. 移除 service 和 secret。

    > docker service rm my-iis
    > docker secret rm homepage
    > docker image remove secret-test
    

中级示例:在 Nginx service 中使用 secrets

本示例分为两部分。第一部分完全是关于生成站点证书,并不直接涉及 Docker secrets,但它为 第二部分做准备,在第二部分中,您将站点证书和 Nginx 配置作为 secrets 存储和使用。

生成站点证书

为您的站点生成根 CA 和 TLS 证书及密钥。对于生产站点,您可能希望使用 Let’s Encrypt 等服务生成 TLS 证书和密钥,但本示例使用命令行工具。此步骤有些复杂,但只是设置步骤,以便您拥有可以作为 Docker secret 存储的内容。如果您想跳过这些子步骤,可以使用 Let's Encrypt 生成站点密钥和证书,将文件命名为 site.keysite.crt,然后跳到 配置 Nginx 容器

  1. 生成根密钥。

    $ openssl genrsa -out "root-ca.key" 4096
    
  2. 使用根密钥生成 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'
    
  3. 配置根 CA。编辑一个名为 root-ca.cnf 的新文件并将以下内容粘贴到其中。这会将根 CA 限制为仅签名叶证书,而非中间 CA。

    [root_ca]
    basicConstraints = critical,CA:TRUE,pathlen:1
    keyUsage = critical, nonRepudiation, cRLSign, keyCertSign
    subjectKeyIdentifier=hash
  4. 签名证书。

    $ 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
    
  5. 生成站点密钥。

    $ openssl genrsa -out "site.key" 4096
    
  6. 生成站点证书并使用站点密钥对其签名。

    $ openssl req -new -key "site.key" -out "site.csr" -sha256 \
              -subj '/C=US/ST=CA/L=San Francisco/O=Docker/CN=localhost'
    
  7. 配置站点证书。编辑一个名为 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
  8. 签名站点证书。

    $ 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
    
  9. Nginx service 不需要 site.csrsite.cnf 文件,但如果您想生成新的站点证书,则需要它们。保护 root-ca.key 文件。

配置 Nginx 容器

  1. 生成一个非常基本的 Nginx 配置,用于通过 HTTPS 提供静态文件。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;
        }
    }
  2. 创建三个秘密,分别代表密钥、证书和 site.conf 文件。只要文件小于 500 KB,您可以将其存储为秘密。这允许您将密钥、证书和配置与使用它们的服务解耦。在这些命令中,最后一个参数表示从主机文件系统读取秘密的文件路径。在这些示例中,秘密名称与文件名相同。

    $ 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
    
  3. 创建一个运行 Nginx 并能访问这三个秘密的服务。docker service create 命令的最后一部分从 site.conf 秘密的位置创建一个符号链接(symlink)到 /etc/nginx.conf.d/,Nginx 会在该目录查找额外的配置文件。这一步在 Nginx 实际启动之前发生,因此如果您更改 Nginx 配置,无需重建镜像。

    注意

    通常,您会创建一个 Dockerfile 来将 site.conf 复制到指定位置,构建镜像,然后使用您的自定义镜像运行容器。本示例不需要自定义镜像。它一步就位地将 site.conf 文件放置好并运行容器。

    秘密默认位于容器内的 /run/secrets/ 目录中,这可能需要在容器内执行额外的步骤才能将秘密提供给不同的路径。下面的示例创建了一个符号链接,指向 site.conf 文件的实际位置,以便 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.keysite.crt 秘密使用了简写语法,没有设置自定义的 target 位置。简写语法将秘密挂载到 `/run/secrets/` 目录下,名称与秘密名称相同。在运行的容器内部,现在存在以下三个文件

    • /run/secrets/site.key
    • /run/secrets/site.crt
    • /etc/nginx/conf.d/site.conf
  4. 验证 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
    
  5. 验证服务是否正常运行:您可以访问 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.ac.cn">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)
    
  6. 运行完此示例后进行清理,删除 nginx 服务和存储的秘密。

    $ docker service rm nginx
    
    $ docker secret rm site.crt site.key site.conf
    

高级示例:在 WordPress service 中使用 secrets

在本示例中,您将创建一个具有自定义 root 密码的单节点 MySQL 服务,将凭据添加为秘密,并创建一个使用这些凭据连接到 MySQL 的单节点 WordPress 服务。下一个示例基于此示例,向您展示如何轮换(rotate)MySQL 密码并更新服务,以便 WordPress 服务仍然可以连接到 MySQL。

本示例演示了使用 Docker secrets 避免将敏感凭据保存在镜像中或直接在命令行上传递的一些技巧。

注意

本示例为简化起见使用了单 Engine swarm,并使用单节点 MySQL 服务,因为单个 MySQL 服务器实例无法通过简单的复制服务进行扩展,并且设置 MySQL 集群超出了本示例的范围。

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

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

    注意

    创建秘密后,您无法更新它。您只能删除并重新创建它,并且您不能删除正在使用的秘密。但是,您可以使用 docker service update 命令授予或撤销正在运行的服务对秘密的访问权限。如果您需要更新秘密的能力,请考虑在秘密名称中添加版本组件,以便以后可以添加新版本,更新服务以使用新版本,然后删除旧版本。

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

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

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

    为 MySQL root 用户生成第二个秘密。此秘密不会与之后创建的 WordPress 服务共享。它仅用于引导 mysql 服务。

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

    使用 docker secret ls 命令列出 Docker 管理的秘密

    $ 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 日志中。

  2. 创建一个用户定义的 overlay 网络,用于 MySQL 和 WordPress 服务之间的通信。无需将 MySQL 服务暴露给任何外部主机或容器。

    $ docker network create -d overlay mysql_private
    
  3. 创建 MySQL 服务。MySQL 服务具有以下特点:

    • 由于 scale 设置为 1,因此只运行一个 MySQL 任务。MySQL 的负载均衡留给读者自行练习,并且不仅仅是扩展服务那么简单。

    • 仅可通过 mysql_private 网络上的其他容器访问。

    • 使用卷 mydata 存储 MySQL 数据,以便在 mysql 服务重启后数据仍能持久存在。

    • 这些秘密分别挂载到 tmpfs 文件系统中,路径为 /run/secrets/mysql_password/run/secrets/mysql_root_password。它们绝不会作为环境变量暴露,如果在运行 docker commit 命令时,它们也无法被提交到镜像中。mysql_password 秘密是由非特权的 WordPress 容器用于连接到 MySQL 的秘密。

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

    • 设置环境变量 MYSQL_USERMYSQL_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
      
  4. 使用 docker service ls 命令验证 mysql 容器正在运行。

    $ docker service ls
    
    ID            NAME   MODE        REPLICAS  IMAGE
    wvnh0siktqr3  mysql  replicated  1/1       mysql:latest
    
  5. 现在 MySQL 已设置完成,创建一个连接到 MySQL 服务的 WordPress 服务。WordPress 服务具有以下特点:

    • 由于 scale 设置为 1,因此只运行一个 WordPress 任务。WordPress 的负载均衡留给读者自行练习,这是因为将 WordPress 会话数据存储在容器文件系统上存在限制。
    • 将 WordPress 暴露在主机机器的端口 30000 上,以便您可以从外部主机访问它。如果主机机器的端口 80 上没有运行 Web 服务器,您可以暴露端口 80 来代替。
    • 连接到 mysql_private 网络,以便它可以与 mysql 容器通信,同时还将端口 80 发布到所有 swarm 节点的端口 30000 上。
    • 拥有访问 mysql_password 秘密的权限,但在容器内指定了一个不同的目标文件名。WordPress 容器使用挂载点 /run/secrets/wp_db_password
    • 设置环境变量 WORDPRESS_DB_PASSWORD_FILE 为秘密挂载的文件路径。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
    
  6. 使用 docker service lsdocker 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 服务对 mysql_password 秘密的访问权限,因为 WordPress 已将该秘密复制到其配置文件 wp-config.php 中。现在不要这样做,因为我们稍后会使用它来方便轮换 MySQL 密码。

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

    继续撰写一两篇博客文章并安装一个 WordPress 插件或主题,以验证 WordPress 是否完全正常运行,并且其状态在服务重启后得以保存。

  8. 如果您打算继续下一个示例(演示如何轮换 MySQL root 密码),请不要清理任何服务或秘密。

示例:轮换 secret

本示例在前一个示例的基础上构建。在此场景中,您将创建一个包含新 MySQL 密码的新秘密,更新 mysqlwordpress 服务以使用它,然后删除旧秘密。

注意

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

  1. 创建新密码并将其存储为名为 mysql_password_v2 的秘密。

    $ openssl rand -base64 20 | docker secret create mysql_password_v2 -
    
  2. 更新 MySQL 服务,使其能够访问旧秘密和新秘密。请记住,您无法更新或重命名秘密,但您可以使用新的目标文件名撤销旧秘密并授予新秘密的访问权限。

    $ 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 下的旧秘密和 /run/secrets/mysql_password 下的新秘密。

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

    注意

    本示例不轮换 MySQL root 密码。

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

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

    首先,找到 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)"'
    
  4. 更新 wordpress 服务以使用新密码,保持目标路径为 /run/secrets/wp_db_password。这将触发 WordPress 服务的滚动重启,并使用新秘密。

    $ docker service update \
         --secret-rm mysql_password \
         --secret-add source=mysql_password_v2,target=wp_db_password \
         wordpress    
    
  5. 通过再次访问任何 swarm 节点上的 http://localhost:30000/ 来验证 WordPress 是否正常工作。使用您在之前任务中运行 WordPress 向导时设置的 WordPress 用户名和密码。

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

  6. 撤销 MySQL 服务对旧秘密的访问权限,并从 Docker 中删除旧秘密。

    $ docker service update \
         --secret-rm mysql_password \
         mysql
    
    $ docker secret rm mysql_password
    
  7. 运行以下命令删除 WordPress 服务、MySQL 容器、mydatawpdata 卷以及 Docker 秘密

    $ 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 中的许多 Docker 官方镜像,例如上述示例中使用的 wordpress 镜像,已按此方式更新。

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

注意

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 文件中的两个秘密创建一个简单的 WordPress 网站。

顶层元素 secrets 定义了两个秘密 db_passworddb_root_password

部署时,Docker 会创建这两个秘密,并用 Compose 文件中指定的文件内容填充它们。

db 服务使用了这两个秘密,而 wordpress 服务使用了一个秘密。

部署时,Docker 会在服务的 /run/secrets/<secret_name> 路径下挂载一个文件。这些文件永远不会持久化到磁盘上,而是在内存中管理。

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

有关秘密的简写和长写语法的更多信息可以在Compose Specification 中找到。

页面选项