容器配置管理与密钥管理:env/secret/config 全体系

系统梳理容器配置与密钥管理:环境变量的局限与正确用法、Docker Secrets(Swarm)与 configs 文件注入、docker-compose secrets 语法、与 HashiCorp Vault/云厂商 Secret Manager 的集成,以及密钥轮换、审计与生产落地的最佳实践。

前置阅读:建议先阅读 Docker 生产安全指南 与 Docker Swarm 集群编排实战。

关键概念:环境变量(env)适合非敏感配置,但一进 docker inspect 就暴露、且随镜像层固化;Secret 与 Config 是把敏感/非敏感数据作为文件挂载进容器的两类机制——前者加密存储且不落镜像层,后者适合配置文件。生产级密钥管理,还要叠加 Vault 等外部 Secret Manager 的动态密钥与审计。


1. 环境变量:快捷但有边界

1.1 env 的三大局限

局限一:可见性
  docker run -e DB_PASSWORD=xxx → docker inspect 容器就能看到明文
  任何能读 docker 元数据的人 = 拿到全部密钥

局限二:固化到镜像层
  Dockerfile 里 ENV 写死 → 值烙进镜像层
  镜像被推送到仓库 = 密钥永久泄露

局限三:无法审计与轮换
  改密钥要重新起容器,没有"轮换"的机制
# 用 -e 传参虽方便,但不适合生产密钥
docker run -d -e DB_HOST=db.internal -e DB_USER=appuser \
  -e DB_PASSWORD=SuperSecret123 myapp

# 危险示范:把密钥写进 Dockerfile
# ENV DB_PASSWORD=SuperSecret123   # 绝对不要这样

1.2 env 的正确打开方式

env 适合非敏感且需要启动时可变的配置:环境名、日志级别、功能开关、非敏感连接参数。

services:
  api:
    image: myapp:1.2
    environment:
      - NODE_ENV=production
      - LOG_LEVEL=info
      - FEATURE_XX_ENABLED=true
      # 数据库主机名等非敏感值可以进 env
      - DB_HOST=db.internal

一句话:判断标准只有一个——这个值泄露了会不会出事?会,就走 secret;不会,env 就够。


2. Docker Secrets(Swarm 原生)

2.1 Secret 的存储与注入原理

Docker Secret 是 Swarm 模式的内置能力:secret 内容加密存储在 Raft 日志中,仅目标节点解密,且以 tmpfs 内存文件挂载进容器(绝不写进镜像层和容器可写层)。

创建 secret → 加密写入 Swarm 的 Raft 存储
服务创建引用 secret → 管理器把解密后的值下发到目标节点
目标节点 → 挂载为 /run/secrets/<name>(tmpfs,内存中)
容器删除 → secret 随 tmpfs 消失

2.2 Swarm Secrets 实战

# 从文件创建 secret
printf 'SuperSecret123' | docker secret create db_password -
# 或 docker secret create db_password ./db_password.txt

# 从标准输入创建
echo "postgres://app:apppwd@db:5432/appdb" | docker secret create db_url -

# 部署服务时引用 secret
docker service create \
  --secret db_password \
  --secret source=db_url,target=app_database_url \
  --name api \
  myapp:1.2
# 或用 stack 文件声明
version: "3.8"
services:
  api:
    image: myapp:1.2
    secrets:
      - db_password
      - source: db_url
        target: app_database_url
secrets:
  db_password:
    external: true
  db_url:
    external: true

容器内读取:/run/secrets/db_password 文件内容是明文,应用读文件即可:

docker exec -it $(docker ps -q -f name=api) cat /run/secrets/db_password

2.3 Secret 的局限

  • 只能用于 Swarm 服务,单机 docker run 与 Compose 的 secret 是另一套;
  • 无法跨集群管理(没有轮换、版本化);
  • 明文在节点内存与容器文件系统中——需配合文件权限约束。

3. configs:非敏感配置文件的注入

3.1 与 Secret 的分工

维度SecretConfig
适用内容密码、Token、证书私钥nginx.conf、log4j.xml、连接池参数
敏感度敏感非敏感
存储加密(Raft)明文存储
挂载/run/secrets/<name>(tmpfs)/path/指定位置

3.2 configs 使用示例

# 创建 config
docker config create nginx.conf ./nginx.conf
docker config create app.yml ./app.yml

# 服务引用
docker service create \
  --config source=app.yml,target=/etc/app/config.yml \
  --name api \
  myapp:1.2
version: "3.8"
services:
  api:
    image: myapp:1.2
    configs:
      - source: app.yml
        target: /etc/app/config.yml
        mode: 0440
configs:
  app.yml:
    file: ./app.yml

一句话:规则很简单——会泄露出事的值走 secret,其余文件走 configs;两者都避免了把配置固化进镜像层,改配置只需更新 config/secret 并滚动服务。


4. docker-compose secrets(非 Swarm 也可用)

4.1 compose secrets 的两种来源

单机 Compose(无需 Swarm)支持 secret,可来自文件或环境变量:

services:
  app:
    image: myapp:1.2
    secrets:
      - db_password
      - api_token
    environment:
      # 由 secret 文件映射出环境变量(较常见做法)
      DB_PASSWORD_FILE: /run/secrets/db_password
    env_file:
      - .env           # 非敏感配置走 env_file

secrets:
  db_password:
    file: ./secrets/db_password.txt
  api_token:
    environment: API_TOKEN   # 从宿主环境变量注入

4.2 应用读取 secret 的推荐姿势

# 很多框架支持 *_FILE 形式:先读文件再取值
DB_PASSWORD=$(cat "$DB_PASSWORD_FILE")
// Node 应用
const fs = require('fs');
const dbPassword = fs.readFileSync('/run/secrets/db_password', 'utf8').trim();

4.3 权限控制

secrets:
  db_password:
    file: ./secrets/db_password.txt
  # 挂载进容器的 uid:gid 与权限
services:
  app:
    secrets:
      - source: db_password
        uid: "1000"
        gid: "1000"
        mode: 0400

5. 与 HashiCorp Vault 集成

5.1 为什么外部 Secret Manager

内置 secret 解决"不落镜像层",但轮换、动态密钥、访问审计、多环境一致性需要专职 Secret Manager。Vault 是自建主流方案。

Vault 的差异化能力:
  - 动态密钥:为每个应用实例签发一次性 DB 凭据,用完即失效
  - 密钥版本化与轮换:v1/v2 KV 引擎,支持历史版本
  - 审计日志:谁在什么时间读了什么密钥
  - 加密即服务:应用只拿加密数据,密钥不进业务进程

5.2 Vault + Docker 的集成方式

方式一:Agent 边车模式(把 secret 拉取成文件)

# 容器启动前用 vault-agent 渲染 /tmp/secrets,再启动应用
# vault-agent.hcl
vault {
  address = "https://vault.internal:8200"
  role = "app-api"
  auth {
    type = "kubernetes"   # Swarm 场景可用 approle
    role = "app-api"
    kubernetes_role = "api"
  }
}

template {
  contents = <<EOF
{{ with secret "kv/data/db" }}{{ .Data.data.db_password }}{{ end }}
EOF
  destination = "/tmp/secrets/db_password"
  error_on_missing_key = true
}

方式二:应用 SDK 直连(动态密钥最佳)

// 应用启动时从 Vault 拉一次,进程内持有
client, _ := vault.NewClient(nil)
client.SetTokenFromEnv("VAULT_TOKEN") // 或用 AppRole/K8s Auth
secret, _ := client.Logical().Read("kv/data/db")
dbPassword := secret.Data["data"].(map[string]interface{})["db_password"]

5.3 Docker Secrets 与 Vault 的组合模式

推荐组合:
  引导凭据(如 Vault 的 role/token)→ Docker Secret 注入
  业务凭据(DB/API)            → 应用运行时从 Vault 动态获取
  Vault 自身凭据               → 存 Secret Manager / KMS 加密

好处:业务密钥不落容器、可动态签发、每次轮换无需重启。

一句话:Docker Secrets 管"静态注入",Vault 管"动态 + 审计 + 轮换"——生产环境两者叠加使用,而非二选一。


6. 云厂商 Secret Manager 集成

6.1 三大云厂商方案对比

能力AWS Secrets ManagerGCP Secret ManagerAzure Key Vault
自动轮换支持(Lambda/rotation)支持(定时器)支持(Key Vault 版本化)
权限模型IAM + KMSIAM + CMEKRBAC + Managed HSM
K8s 集成External Secrets / CSISecret Manager CSICSI driver
典型费用按 secret 数量+调用按版本+访问次数按操作

6.2 ECS/EKS 中的 AWS Secrets Manager

# ECS 原生支持引用 secrets(任务定义片段)
# "secrets": [
#   {"name": "DB_PASSWORD", "valueFrom": "arn:aws:secretsmanager:...:db_password"}
# ]

# 本地开发用 AWS CLI 获取
aws secretsmanager get-secret-value \
  --secret-id arn:aws:secretsmanager:ap-southeast-1:123456:secret:db-password \
  --query SecretString --output text

6.3 K8s 上的统一接入:External Secrets Operator

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: db-credentials
  namespace: payments
spec:
  secretStoreRef:
    name: vault-store          # 或 aws-secretsmanager-store
    kind: SecretStore
  target:
    name: db-credentials       # 落到 K8s Secret
  data:
    - secretKey: DB_PASSWORD
      remoteRef:
        key: kv/data/db
        property: db_password

一句话:云厂商 Secret Manager + External Secrets/CSI 的价值在于一个来源管理所有环境的密钥,应用侧无感,轮换与回滚都走云平台。


7. 密钥轮换与审计

7.1 轮换的两种模式

模式做法风险适用
重建式轮换停旧容器→换新密钥→起新容器有停机窗口重启成本低的普通服务
动态轮换密钥有时效(TTL),到期自动换依赖 Secret Manager数据库/短时凭据
# 通过更新 secret 并滚动服务完成轮换(Swarm)
docker secret rm db_password
printf 'NewPassword456' | docker secret create db_password -
docker service update --secret-add db_password --secret-rm db_password api

7.2 审计清单

□ 密钥谁创建/谁更新(Secret Manager 操作日志)
□ 谁读取了哪个 secret(Vault audit / 云审计日志)
□ secret 文件在容器内的权限(0400,非 root 可读)
□ 镜像历史里是否残留历史密钥(docker history 检查)
□ 密钥轮换是否自动化(≥90 天一次为宜)
□ 是否误把 secret 打进了镜像(trivy/SecRET 扫描)
# 检查镜像历史是否泄露密钥
docker history --no-trunc myapp:1.2 | grep -iE "password|secret|token"

# 用 gitleaks 扫描仓库与镜像上下文中的密钥
gitleaks detect --source . --verbose

8. 最佳实践清单

□ 敏感值一律 secret,非敏感值走 env/config
□ 绝不把密钥写进 Dockerfile / .env 提交到 git
□ 应用支持 *_FILE 环境变量读取 secret 文件
□ 单机 Compose 用 compose secrets(file/environment 来源)
□ Swarm 生产用 Swarm Secrets + configs
□ 高安全要求接入 Vault / 云厂商 Secret Manager(动态密钥)
□ 密钥轮换自动化并记录审计
□ 定期用 gitleaks/trivy 扫描镜像与仓库残留密钥

一句话:配置与密钥管理的黄金法则——“分层”:非敏感用 env/configs,静态敏感用 secrets,动态敏感用 Secret Manager,并始终把审计与轮换当作一等公民。


9. 总结

数据推荐机制存储位置泄露风险生产建议
非敏感配置(环境/开关)env / configs明文可查低直接使用
配置文件(nginx/log4j)configs明文存储低用 target 挂载
静态密钥(密码/Token)Docker Secrets加密 + tmpfs中Swarm/compose secrets
动态密钥(DB 凭据)Vault / 云 Secret Manager外部加密存储低自动轮换
引导凭据Secret + KMS加密低与 KMS 绑定

从「-e 一把梭」到「分层治理」,容器密钥管理的成熟度路径是:env → configs/secrets 文件挂载 → Secret Manager 动态密钥 → 全链路审计轮换。每前进一层,都以"密钥不落镜像、不落明文、可轮换、可审计"为验收标准。当密钥泄露不再等于数据库沦陷,你的容器配置体系才算真正生产级。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「docker」更多文章

  1. Docker 容器排障与调试实战:退出码、OOM、exec 与调试工具链
  2. Docker 镜像供应链安全:SBOM、签名、扫描与 SLSA 合规
  3. Docker 容器监控与日志实践:cgroups 资源限制、Prometheus 与日志驱动