路线图

ConfigMap 与 Secret:配置外置与密钥安全

星辉 2026-07-02 阅读 4 min 733 字 路线图
ConfigMap 与 Secret:配置外置与密钥安全 封面

概述#

在 Docker 时代,我们习惯把配置写进镜像(或通过环境变量传入)。到了 Kubernetes,这种做法的局限性就暴露了:

  • 配置和镜像耦合:改一个配置就要重新构建镜像、重新部署
  • 敏感信息泄露风险:把数据库密码写进 Dockerfile 或环境变量,任何能访问镜像的人都能看到
  • 多环境配置管理混乱:dev/staging/prod 的配置不同,但没有统一的管理机制

Kubernetes 提供了两个原语解决这个问题:

  • ConfigMap:存非敏感配置(如日志级别、服务端口、配置文件内容)
  • Secret:存敏感数据(如数据库密码、API Key、TLS 证书)

核心思想:配置外置(Twelve-Factor App 原则),把"会变化的东西"(配置)和"不变的东西"(镜像)分开管理。


ConfigMap 基础:从创建到使用#

创建方式一:从字面值创建#

bash
# 创建包含单个键值对的 ConfigMap
kubectl create configmap app-config \
  --from-literal=LOG_LEVEL=info \
  --from-literal=SERVER_PORT=8080 \
  --namespace production

等效 YAML:

yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: production
data:
  LOG_LEVEL: "info"
  SERVER_PORT: "8080"

创建方式二:从文件创建#

bash
# 从单个文件创建(键名 = 文件名,值 = 文件内容)
kubectl create configmap app-properties \
  --from-file=app.properties \
  --namespace production

# 从多个文件创建
kubectl create configmap app-config \
  --from-file=config.json \
  --from-file=app.properties \
  --namespace production

# 指定键名
kubectl create configmap app-config \
  --from-file=my-key=app.properties \
  --namespace production

示例:假设 app.properties 内容是:

text
database.host=db-svc
database.port=5432
log.level=info

创建后,ConfigMap 的内容是:

yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-properties
  namespace: production
data:
  app.properties: |
    database.host=db-svc
    database.port=5432
    log.level=info

创建方式三:从目录创建#

目录结构:

text
config-dir/
├── log-level
├── server-port
└── app.yaml

每个文件的内容会成为 ConfigMap 的一个键:

bash
kubectl create configmap app-config \
  --from-file=./config-dir \
  --namespace production

使用方式一:作为环境变量注入#

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  template:
    spec:
      containers:
      - name: app
        image: myapp:v1.0
        env:
        # 方式 1:逐个指定
        - name: LOG_LEVEL
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: LOG_LEVEL
        - name: SERVER_PORT
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: SERVER_PORT
        # 方式 2:批量注入(envFrom 与 env 平级,不是 env 列表里的一项)
        envFrom:
        - configMapRef:
            name: app-config

特点

  • ✅ 简单直观,应用代码不需要改(直接读环境变量)
  • Pod 启动后就固定了,ConfigMap 更新后不会自动同步到已运行的 Pod
  • ❌ 如果需要更新,必须重启 Pod(如 kubectl rollout restart deployment/my-app

使用方式二:作为文件挂载#

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  template:
    spec:
      containers:
      - name: app
        image: myapp:v1.0
        volumeMounts:
        - name: config-volume
          mountPath: /etc/config
          readOnly: true
      volumes:
      - name: config-volume
        configMap:
          name: app-config
          # 可选:指定默认权限
          defaultMode: 0644

挂载后的文件路径

text
/etc/config/LOG_LEVEL     ← 内容是 "info"
/etc/config/SERVER_PORT  ← 内容是 "8080"

如果 ConfigMap 包含 app.properties 键(文件内容),挂载后是:

text
/etc/config/app.properties  ← 内容是完整的配置文件

特点

  • ConfigMap 更新后,文件会自动刷新(kubelet 定期同步,通常有几秒延迟)
  • ✅ 应用可以监听文件变化并热加载配置(如 Nginx 的 nginx -s reload
  • ⚠️ 如果使用了 subPath自动更新会失效(后文详解)

Secret:Base64 不是加密!#

Base64 的本质:编码,不是加密#

新手最容易犯的错误:以为 data 字段里的 base64 字符串是"加密"的。

yaml
apiVersion: v1
kind: Secret
metadata:
  name: db-secret
type: Opaque
data:
  password: bXlwYXNzd29yZDEyMzA=   # echo -n 'mypassword1230' | base64

真相bXlwYXNzd29yZDEyMzA= 任何人都能用 echo bXlwYXNzd29yZDEyMzA= | base64 -d 还原成明文。

Base64 只是编码(为了能在 YAML/JSON 里存二进制数据),不是加密

Secret 的常用类型#

类型用途data 中的键
Opaque(默认)用户自定义的任意 Secret自定义
kubernetes.io/tls存储 TLS 证书和私钥tls.crttls.key
kubernetes.io/basic-auth存储 HTTP Basic Auth 的用户名密码usernamepassword
kubernetes.io/dockerconfigjson存储镜像仓库的认证信息.dockerconfigjson
kubernetes.io/ssh-auth存储 SSH 密钥ssh-privatekey

创建 Secret 示例#

Opaque 类型(最常用):

bash
# 从字面值创建
kubectl create secret generic db-secret \
  --from-literal=DB_PASSWORD=mypassword1230 \
  --namespace production

# 从文件创建
kubectl create secret generic tls-secret \
  --from-file=tls.crt=server.crt \
  --from-file=tls.key=server.key \
  --namespace production

TLS 类型(用于存储证书):

bash
kubectl create secret tls app-tls \
  --cert=server.crt \
  --key=server.key \
  --namespace production

等效 YAML:

yaml
apiVersion: v1
kind: Secret
metadata:
  name: app-tls
type: kubernetes.io/tls
data:
  tls.crt: <base64 编码的证书>
  tls.key: <base64 编码的私钥>

在 Pod 中使用 Secret#

作为文件挂载(推荐):

yaml
spec:
  containers:
  - name: app
    image: myapp:v1.0
    volumeMounts:
    - name: secret-volume
      mountPath: /etc/secrets
      readOnly: true
  volumes:
  - name: secret-volume
    secret:
      secretName: db-secret
      defaultMode: 0400   # 只读权限

挂载后:

text
/etc/secrets/DB_PASSWORD   ← 内容是 "mypassword1230"

作为环境变量注入(不推荐敏感信息):

yaml
spec:
  containers:
  - name: app
    image: myapp:v1.0
    env:
    - name: DB_PASSWORD
      valueFrom:
        secretKeyRef:
          name: db-secret
          key: DB_PASSWORD

安全提醒:环境变量可能被 printenv/proc/<pid>/environ 泄露,敏感信息优先用文件挂载


挂载方式对比:文件挂载 vs 环境变量注入#

这是生产环境经常讨论的问题——配置应该怎么传给应用?

对比表#

对比项文件挂载(volumeMount)环境变量注入(envFrom)
更新同步✅ 自动同步(ConfigMap/Secret 更新后,文件内容会刷新)❌ 不同步(Pod 启动时固定)
热加载支持✅ 应用可以监听文件变化(inotify❌ 不支持
敏感信息安全✅ 可以设置 Unix 权限(defaultMode: 0400❌ 可能被 printenv 泄露
应用改造⚠️ 需要应用读配置文件(而非环境变量)✅ 无需改造(直接读环境变量)
subPath 影响⚠️ 使用 subPath禁用自动更新N/A
适用场景配置文件、TLS 证书、需要热加载的配置简单的键值对、应用已支持环境变量配置

文件挂载的自动更新机制#

ConfigMap/Secret 以文件挂载时,kubelet 会定期同步(默认每 60 秒检查一次)。

yaml
volumes:
- name: config-volume
  configMap:
    name: app-config
    # 可选:设置同步周期(通过 kubelet 的 --sync-frequency 参数)

应用如何感知配置变化?

  1. 应用内监听文件变化(推荐):用 inotify 监听配置文件,变化后热加载
  2. 通过信号触发重载:修改 ConfigMap → 触发 Pod 注解变更 → 用 Sidecar 发送信号给主容器
  3. 主动重启(简单粗暴):kubectl rollout restart deployment/my-app

subPath 的坑#

如果你想把 ConfigMap 中的单个键挂载为文件(而不是整个 ConfigMap 挂载为一个目录),会用 subPath

yaml
volumeMounts:
- name: config-volume
  mountPath: /etc/app/config.properties
  subPath: app.properties   # 只挂载这个键

问题:使用 subPath 后,ConfigMap 更新不会自动同步到文件

解决办法:

  • 不用 subPath,直接挂载整个 ConfigMap 为一个目录
  • 或者用 reloader(https://github.com/stakater/Reloader)监听 ConfigMap 变化并自动重启 Pod

外部 Secret 方案:Vault + External Secrets Operator#

前边讲的 Kubernetes 原生 Secret 有个问题:值以 base64 明文存在 etcd 里(Kubernetes 默认并不对 etcd 做静态加密,必须手动配置 EncryptionConfiguration 才有),而且:

  • 访问权限管理复杂(任何有 get secrets 权限的人都能读)
  • 没有审计日志(谁访问了哪个 Secret?)
  • 没有自动轮换能力(密码过期了要手动更新)
  • 容易泄露进 Git(有人把 Secret 写进 Helm values 然后推到 GitHub)

生产环境推荐方案:用 HashiCorp Vault 存储真正的密钥,然后用 External Secrets Operator(ESO)把 Vault 里的密钥同步成 Kubernetes Secret。

text
Vault(真正的密钥存储)
    ↓ ESO 定期同步
Kubernetes Secret(应用还是读这个,无感知)
Pod

一句话了解

  • Vault:专业的密钥管理系统,支持密钥轮换、审计日志、动态凭证(每次请求生成临时数据库密码)
  • External Secrets Operator:Kubernetes 控制器,把外部密钥系统(Vault、AWS Secrets Manager、GCP Secret Manager)的密钥同步成 K8s Secret

详细配置和部署是进阶内容,新手先掌握 Kubernetes 原生的 ConfigMap/Secret,生产环境再引入 Vault + ESO。


小结#

本章我们学习了 Kubernetes 的配置和密钥管理:

  1. ConfigMap:存非敏感配置,支持从字面值、文件、目录创建。有两种使用方式:

    • 文件挂载:ConfigMap 更新后会自动同步到 Pod 内文件(热加载友好)
    • 环境变量注入:Pod 启动后固定,需要重启才能更新
  2. Secret:存敏感数据,Base64 是编码不是加密!常用类型有 Opaque(自定义)、kubernetes.io/tls(证书)、kubernetes.io/dockerconfigjson(镜像仓库认证)。

  3. 挂载方式选择

    • 配置文件、TLS 证书 → 用文件挂载volumeMount
    • 简单键值对、应用已支持环境变量 → 用环境变量注入envFrom
    • 敏感信息 → 务必用文件挂载,避免环境变量泄露
  4. 外部 Secret 方案:生产环境推荐用 Vault + External Secrets Operator,实现密钥的安全存储、审计、自动轮换。

下一步:我们学会了怎么管理配置和密钥,但还有一个问题——数据持久化。下章我们将深入学习 存储 PV/PVC,搞清楚容器里的数据怎么保存到集群外的存储系统。


实战要点

  • 千万别把 Secret 推进 Git!即使用了 base64,任何人都能解码
  • ConfigMap/Secret 有大小限制(受 etcd 对象大小约束,单个约 1MiB,非某版本引入),超大配置文件考虑用外部存储(如 AWS S3、NFS)
  • 文件挂载自动更新有延迟(默认 60 秒),对配置实时性要求高的场景要评估
  • 使用 subPath 会禁用自动更新!需要热加载的配置避免用 subPath
  • 生产环境务必启用 etcd 加密EncryptionConfiguration),防止 etcd 备份泄露导致密钥暴露