使用Docker secrets管理敏感数据

关于密钥

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

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

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

注意

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

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

你也可以使用secrets来管理非敏感数据,例如配置文件。然而,Docker支持使用 configs 来存储非敏感数据。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 如何管理密钥

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

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

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

一个节点只有在它是群集管理器或者运行已被授予访问秘密的服务任务时,才能访问(加密的)秘密。当容器任务停止运行时,共享给它的解密秘密将从该容器的内存文件系统中卸载,并从节点的内存中清除。

如果一个节点在运行一个具有访问秘密权限的任务容器时失去了与集群的连接,该任务容器仍然可以访问其秘密,但在节点重新连接到集群之前无法接收更新。

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

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

了解更多关于docker secret命令的信息

使用这些链接阅读特定命令,或继续查看 关于在服务中使用机密的示例

示例

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

注意

这些示例使用单引擎群集和未扩展的服务以简化操作。示例使用Linux容器,但Windows容器也支持密钥。请参阅 Windows支持

在compose文件中定义和使用secrets

无论是 docker-compose 还是 docker stack 命令,都支持在 compose 文件中定义 secrets。详情请参阅 Compose 文件参考

简单示例:开始使用secrets

这个简单的例子展示了如何在几个命令中使用secrets。要查看一个实际应用的例子,请继续阅读 中级示例:在Nginx服务中使用secrets

  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服务中使用密钥

这是一个非常简单的示例,展示了如何在运行于Microsoft Windows 10上的Docker for Windows上运行的Microsoft IIS服务中使用secrets。这是一个天真的示例,将网页存储在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秘密。

    > 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
    

    注意

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

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

  6. 移除服务和密钥。

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

中级示例:在Nginx服务中使用secrets

这个例子分为两部分。 第一部分完全是关于生成站点证书的,并不直接涉及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;'"
    

    与创建符号链接不同,secrets 允许你使用 target 选项指定一个自定义位置。下面的示例展示了如何在不使用符号链接的情况下,将 site.conf secret 在容器内的 /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.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)
    
  6. 在运行此示例后清理,请删除nginx服务和存储的密钥。

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

高级示例:在WordPress服务中使用secrets

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

这个例子展示了一些使用Docker secrets的技术,以避免在镜像中保存敏感凭证或直接在命令行上传递它们。

注意

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

此外,更改MySQL根密码并不像更改磁盘上的文件那样简单。您必须使用查询或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 管理的机密,使用 docker secret ls

    $ 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 服务具有以下特点:

    • Because the scale is set to 1, only a single WordPress task runs. Load-balancing WordPress is left as an exercise to the reader, because of limitations with storing WordPress session data on the container filesystem.
    • Exposes WordPress on port 30000 of the host machine, so that you can access it from external hosts. You can expose port 80 instead if you do not have a web server running on port 80 of the host machine.
    • Connects to the mysql_private network so it can communicate with the mysql container, and also publishes port 80 to port 30000 on all swarm nodes.
    • Has access to the mysql_password secret, but specifies a different target file name within the container. The WordPress container uses the mount point /run/secrets/wp_db_password.
    • Sets the environment variable WORDPRESS_DB_PASSWORD_FILE to the file path where the secret is mounted. The WordPress service reads the MySQL password string from that file and add it to the wp-config.php configuration file.
    • Connects to the MySQL container using the username wordpress and the password in /run/secrets/wp_db_password and creates the wordpress database if it does not yet exist.
    • Stores its data, such as themes and plugins, in a volume called wpdata so these files persist when the service restarts.
    $ 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/并使用基于网络的向导设置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. 通过再次在任何群集节点上浏览到 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 库中的Docker官方镜像,例如上述示例中使用的wordpress镜像,已经以这种方式更新。

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

注意

Docker 密钥不会直接设置环境变量。这是一个有意识的决定,因为环境变量可能会在容器之间无意中泄露(例如,如果您使用 --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/下挂载一个文件。这些文件永远不会持久化到磁盘上,而是在内存中管理。

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

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