使用 Docker 机密管理敏感数据

关于机密

在 Docker Swarm 服务中,机密(secret)是一块数据,例如密码、SSH 私钥、SSL 证书或其他不应通过网络传输或未加密存储在 Dockerfile 或应用程序源代码中的数据。您可以使用 Docker 机密 来集中管理这些数据,并将其安全地传输到仅需要访问它的容器。机密在传输过程中和在 Docker swarm 中静止时都是加密的。给定的机密仅对那些已明确授予访问权限的服务可用,且仅在这些服务任务运行时可用。

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

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

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

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

您也可以使用机密来管理非敏感数据,例如配置文件。但是,Docker 支持使用 配置(configs) 来存储非敏感数据。配置会直接挂载到容器的文件系统中,而不使用 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-compose` 和 `docker 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 服务中使用机密

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

此示例假定您已安装 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://: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.key` 和 `site.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.csr` 和 `site.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.key 和 site.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://: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 机密来避免在镜像中保存敏感凭证或在命令行中直接传递它们的技术。

注意

为简单起见,本例使用单引擎 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. 创建一个用户自定义的覆盖网络,用于 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_FILE` 和 `MYSQL_ROOT_PASSWORD_FILE` 设置为指向文件 `/run/secrets/mysql_password` 和 `/run/secrets/mysql_root_password`。`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
      
  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 发布到所有 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 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 服务对 `mysql_password` 机密的访问权限,因为 WordPress 已经将机密复制到了其配置文件 `wp-config.php` 中。现在不要这样做,因为我们稍后会用它来方便地轮换 MySQL 密码。

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

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

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

示例:轮换机密

这个例子建立在前一个例子的基础上。在这个场景中,您将创建一个带有新 MySQL 密码的新机密,更新 `mysql` 和 `wordpress` 服务以使用它,然后移除旧的机密。

注意

更改 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 节点上浏览 https://:30000/ 来验证 WordPress 是否正常工作。使用您在先前任务中运行 WordPress 向导时获得的 WordPress 用户名和密码。

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

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

    $ docker service update \
         --secret-rm mysql_password \
         mysql
    
    $ docker secret rm mysql_password
    
  7. 运行以下命令以移除 WordPress 服务、MySQL 容器、`mydata` 和 `wpdata` 卷以及 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_password` 和 `db_root_password`。

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

db 服务使用这两个机密,而 wordpress 使用其中一个。

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

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

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

© . This site is unofficial and not affiliated with Kubernetes or Docker Inc.