使用 HashiCorp Vault Agent 在容器启动前拉取机密

解读

在国内金融、运营商、政务云等合规场景里,明文写死密码、把密钥打到镜像里都是红线。Docker 容器启动时必须拿到数据库口令、第三方 API Token、证书私钥等敏感信息,而 Vault 作为“统一机密中心”能动态签发、自动轮转、审计留痕。面试官问“容器启动前拉取机密”,实质考察三件事:

  1. 镜像本身零机密,Build 阶段不泄露;
  2. Runtime 阶段最小权限,容器只能拿到自己需要的 Secret;
  3. 整个流程对 Kubernetes 友好,也能在裸 Docker 或 Swarm 落地,并符合国内等保 2.0、关基、密评要求。

知识点

  • Vault Agent 的 auto-authtemplatingrenew 机制
  • Vault Agent Sidecar 与 InitContainer 两种注入模式差异
  • AppRole、JWT、K8s SA、AliCloud RAM 等国内常用认证后端
  • Docker 的 --initENTRYPOINT 信号传递tmpfs 挂载
  • 镜像多阶段构建、distrolesschown 65534 非 root 实践
  • 国内云厂商 KMS 与 Vault 的 transit 统一加解密 对接
  • 审计日志落盘到 LTS(如阿里云 SLS、腾讯 CLS)满足 6 个月可回溯
  • 等保 2.0 对“剩余信息清除”要求:容器停止后 tmpfs 自动销毁

答案

生产级落地分五步,可直接在 Docker 单节点或 Swarm 集群复现,也能平滑迁移到 Kubernetes。

  1. 镜像侧零机密构建
    使用多阶段构建,把 Vault Agent 二进制(官方 1.16.x linux/amd64)COPY 到最终镜像,业务代码与配置文件均不出现密码。

    FROM alpine:3.19 AS vault
    RUN apk add --no-cache wget && \
        wget -O- https://releases.hashicorp.com/vault/1.16.2/vault_1.16.2_linux_amd64.zip | funzip >/vault
    
    FROM gcr.io/distroless/java17-debian12
    COPY --from=vault --chown=65534:65534 /vault /vault/bin/vault
    COPY --chown=65534:65534 app.jar /
    ENTRYPOINT ["/vault/bin/vault", "agent", "-config=/vault/config/agent.hcl"]
    
  2. Vault 侧最小权限策略
    为应用创建独立 namespace 与 AppRole,policy 只读指定路径:

    path "secret/data/myapp/{{identity.entity.metadata.env}}" {
      capabilities = ["read"]
    }
    

    国内合规要求双人复核才能修改 policy,可在 Vault Enterprise 打开 MFA。

  3. 启动侧InitContainer 模式(Docker 单节点可用 docker-compose v3.9 的 init 语法模拟)
    compose 片段:

    services:
      vault-agent:
        image: vault:1.16.2
        volumes:
          - ./agent.hcl:/vault/config/agent.hcl:ro
          - vault-token:/vault/token
          - secrets-store:/secrets
        environment:
          VAULT_ADDR: https://vault.intra:8200
          VAULT_CACERT: /vault/tls/vault-ca.crt
        command: ["vault", "agent", "-config=/vault/config/agent.hcl"]
        networks:
          - internal
      app:
        image: myapp:zero-secret
        depends_on:
          - vault-agent
        volumes:
          - secrets-store:/run/secrets:ro
        entrypoint: ["/bin/sh", "-c", "source /run/secrets/db.env && exec java -jar /app.jar"]
        networks:
          - internal
    volumes:
      vault-token:
      secrets-store:
    

    agent.hcl 关键段落:

    auto_auth {
      method "approle" {
        config = {
          role_id_file_path = "/vault/role-id"
          secret_id_file_path = "/vault/secret-id"
        }
      }
    }
    template {
      source      = "/vault/template/db.env.tpl"
      destination = "/secrets/db.env"
      command     = "pkill -HUP vault-agent"   # 国内部分老内核需要信号触发
    }
    

    模板文件 db.env.tpl:

    export DB_USER="{{ .Data.data.username }}"
    export DB_PASS="{{ .Data.data.password }}"
    
  4. 安全加固

    • /run/secrets 挂载为 tmpfs,容器停止自动清零,满足等保“剩余信息清除”条款。
    • Vault Agent 与业务进程非 root 运行,镜像内提前 chown 65534
    • 开启 TLS 双向认证,Vault 服务端使用国密双证书(RSA+SM2)过密评。
    • 审计日志通过 file_sink 写到宿主机,由 Logtail 实时采集到阿里云 SLS,保存 180 天。
  5. 故障排查 checklist

    • 若 agent 日志出现 permission denied,优先检查 AppRole secret_id_num_uses 是否耗尽;
    • 国内云主机时间不同步会导致 Vault TLS 握手失败,用 Chrony 强制同步;
    • template 输出为空,确认 policy 路径与 secret/data/ 前缀是否匹配,Vault KV2 必须带 /data
    • 容器重启后旧 tmpfs 消失属正常,不要误以为是 Docker 卷泄漏。

拓展思考

  1. 如果未来迁移到 Kubernetes,可把 Vault Agent 注入器换成 Vault Agent InjectorCSI Provider,但策略模板与 AppRole 可以复用,保证混合云平滑过渡
  2. 国内部分银行要求国密算法对机密再做一层信封加密,可在 Vault 的 transit 引擎用 SM4 加密,业务容器拿到的只是密文,启动后再调用 softhsm 解密,实现“双重控制”。
  3. 当 Vault 出现单点故障,Docker Swarm 侧可配置 Consul Template + 本地缓存,在 Vault 不可用时降级读取本地 AES-256 加密文件,但需通过 KMS 托管密钥 解密,保证高可用与合规不冲突