使用 Docker secrets 管理敏感数据

关于密钥

就 Docker Swarm 服务而言,密钥是一块数据,例如密码、SSH 私钥、SSL 证书或其他不应通过网络传输或以未加密方式存储在 Dockerfile 或应用程序源代码中的数据。您可以使用 Docker 密钥来集中管理此数据并安全地将其仅传输到需要访问它的容器。密钥在传输过程中和在 Docker swarm 中处于静止状态时都会被加密。给定的密钥仅对已获得对其显式访问权限的服务以及这些服务任务正在运行时才可访问。

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

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

注意

Docker 密钥仅适用于 swarm 服务,不适用于独立容器。要使用此功能,请考虑将您的容器调整为作为服务运行。有状态容器通常可以按比例缩放 1,而无需更改容器代码。

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

您还可以使用密钥管理非敏感数据,例如配置文件。但是,Docker 支持使用配置 来存储非敏感数据。配置直接安装到容器的文件系统中,无需使用 RAM 磁盘。

Windows 支持

Docker 包含对 Windows 容器中密钥的支持。如果实现存在差异,则会在下面的示例中指出。请记住以下显着差异:

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

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

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

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

Docker 如何管理密钥

将密钥添加到 Swarm 时,Docker 通过相互 TLS 连接将密钥发送到 Swarm 管理器。密钥存储在 Raft 日志中,该日志已加密。整个 Raft 日志会在其他管理器之间复制,确保密钥与 Swarm 管理数据的其余部分具有相同的可用性保证。

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

您可以随时更新服务以授予其对其他密钥的访问权限,或撤销其对给定密钥的访问权限。

只有在节点是 Swarm 管理器或正在运行已授予其访问密钥的服务任务时,节点才能访问(已加密的)密钥。当容器任务停止运行时,与其共享的已解密密钥将从该容器的内存文件系统卸载,并从节点的内存中清除。

如果节点在运行具有密钥访问权限的任务容器时与 Swarm 断开连接,则任务容器仍然可以访问其密钥,但在节点重新连接到 Swarm 之前无法接收更新。

您可以随时添加或检查单个密钥,或列出所有密钥。您无法删除正在运行的服务正在使用的密钥。请参阅旋转密钥,了解如何在不中断运行服务的情况下删除密钥的方法。

为了更轻松地更新或回滚密钥,请考虑在密钥名称中添加版本号或日期。通过能够控制给定容器内密钥的挂载点,可以更轻松地做到这一点。

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

使用以下链接阅读有关特定命令的信息,或继续访问关于将密钥与服务一起使用的示例

示例

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

注意

这些示例出于简便起见使用单引擎 Swarm 和未缩放的服务。这些示例使用 Linux 容器,但 Windows 容器也支持密钥。请参阅Windows 支持

在 compose 文件中定义和使用密钥

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

简单示例:开始使用密钥

此简单示例展示了密钥如何在几个命令中工作。有关实际示例,请继续访问中间示例:将密钥与 Nginx 服务一起使用

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

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

    $ docker service  create --name redis --secret my_secret_data redis:alpine
    
  3. 使用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)"
    
  4. 使用docker ps获取redis服务任务容器的 ID,以便您可以使用docker container exec连接到容器并读取密钥数据文件的内容,该文件默认为所有用户可读,并且与密钥的名称相同。下面的第一个命令说明了如何查找容器 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. 验证提交容器后密钥是否不可用。

    $ 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. 尝试删除密钥。删除失败,因为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
    
  7. 通过更新服务来删除运行的redis服务对密钥的访问权限。

    $ docker service update --secret-rm my_secret_data redis
    
  8. 再次重复步骤 3 和 4,验证服务不再具有对密钥的访问权限。容器 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
    
  9. 停止并删除服务,然后从 Docker 中删除密钥。

    $ docker service rm redis
    
    $ docker secret rm my_secret_data
    

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

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

此示例假设您已安装 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 密钥。

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

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

    注意

    从技术上讲,此示例没有理由使用密钥;配置更适合。此示例仅供说明。

  5. https://127.0.0.1:8000/访问 IIS 服务。它应该提供第一步中的 HTML 内容。

  6. 删除服务和密钥。

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

中等示例:将密钥与 Nginx 服务一起使用

此示例分为两部分。第一部分完全关于生成站点证书,根本不直接涉及 Docker 密钥,但它设置了第二部分,您可以在其中将站点证书和 Nginx 配置存储和用作密钥。

生成站点证书

为您的站点生成根 CA 和 TLS 证书和密钥。对于生产站点,您可能希望使用诸如Let’s Encrypt之类的服务来生成 TLS 证书和密钥,但此示例使用命令行工具。此步骤有点复杂,但这只是一个设置步骤,以便您有一些内容可以存储为 Docker 密钥。如果您想跳过这些子步骤,您可以使用 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 服务不需要site.csrsite.cnf文件,但如果您想生成新的站点证书,则需要它们。保护root-ca.key文件。

配置 Nginx 容器

  1. 生成一个非常基本的 Nginx 配置,用于通过 HTTPS 提供静态文件。TLS 证书和密钥存储为 Docker 密钥,以便可以轻松旋转它们。

    在当前目录中,创建一个名为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密钥的位置创建一个到/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://127.0.0.1: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 服务一起使用

在此示例中,您将创建一个具有自定义root密码的单节点MySQL服务,将凭据添加为密钥,并创建一个使用这些凭据连接到MySQL的单节点WordPress服务。下一个示例在此示例的基础上构建,并向您展示如何旋转MySQL密码并更新服务,以便WordPress服务仍然可以连接到MySQL。

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

注意

为简便起见,此示例使用单个引擎集群,并使用单节点MySQL服务,因为单个MySQL服务器实例不能仅通过使用复制服务来扩展,并且设置MySQL集群超出了本示例的范围。

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

  1. 使用docker secret create命令生成一个随机的字母数字MySQL密码并将其存储为名为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
    

    密钥存储在集群的加密Raft日志中。

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

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

    • 由于规模设置为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服务具有以下特性:

    • 由于规模设置为1,因此只运行一个WordPress任务。WordPress的负载均衡留给读者作为练习,因为在容器文件系统上存储WordPress会话数据存在限制。
    • 在主机机的30000端口公开WordPress,以便您可以从外部主机访问它。如果您没有在主机机的80端口上运行Web服务器,则可以使用80端口代替。
    • 连接到mysql_private网络,以便它可以与mysql容器通信,并将80端口发布到所有集群节点上的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. 从任何集群节点访问https://127.0.0.1:30000/,并使用基于Web的向导设置WordPress。所有这些设置都存储在MySQL wordpress数据库中。WordPress会自动为您生成的WordPress用户生成一个密码,这与WordPress用于访问MySQL的密码完全不同。安全地存储此密码,例如在密码管理器中。在旋转密钥后,您需要它来登录WordPress。

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

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

示例:轮换密钥

此示例基于上一个示例构建。在这种情况下,您将创建一个具有新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. 通过再次浏览到任何集群节点上的https://127.0.0.1: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 密钥的支持构建到您的镜像中

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

Docker库中许多Docker官方镜像,例如wordpress中使用的上例镜像,已经过这种方式更新。

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

注意

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

在 Compose 中使用密钥


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/ 下挂载一个文件。这些文件永远不会持久保存到磁盘上,而是在内存中管理。

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

有关密钥的简短和长语法,请参阅Compose 规范