你是不是也踩过这个坑?我前年有个业务上线,数据库密码配在 ConfigMap 里——对,我知道不应该是这样,但当时项目进度紧,临时紧急上线,想着“先跑起来再说”。结果第二天 Security 团队扫描集群,敏感信息直接暴露,被要求全量整改。

更智障的是另一个场景:我改了 ConfigMap 里的日志级别,等了 20 分钟容器里的配置还是老的,以为没生效,就给了一纸 restart。后来才搞明白——环境变量注入根本不支持热更新,纯属自己给自己加戏

还有一次更离谱的:想挂 Secret 里的单个证书文件到 Pod,用了 subPath,结果改了 Secret 之后容器内文件纹丝不动,排查半天才发现 subPath 挂载不支持热更新。

坑踩多了,就知道这事得彻底捋一遍。今天我把 ConfigMap 和 Secret 从里到外扒干净。


一、是什么干了什么?

一句话定性

  • ConfigMap:存非敏感明文配置。环境变量、日志级别、配置文件,往里塞。记住:敏感信息别往里塞。
  • Secret:存敏感数据。密码、API Key、TLS 证书,用这个。但要清醒——默认情况下 Secret 是 base64 编码,不是加密。血的教训。

速览对比

特性

ConfigMap

Secret

存什么

非敏感配置

敏感数据

存储格式

明文

Base64 编码(默认,不是加密)

大小限制

1MB

1MB

卷挂载默认权限

0644

0400/0644

用途

环境变量、日志级别、app.properties

密码、Token、证书

都支持两种使用方式:环境变量注入、卷挂载。


二、两种使用方式,你选哪一种?

方式一:环境变量注入

ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  LOG_LEVEL: DEBUG
  DB_HOST: postgres-svc
  MAX_CONNECTIONS: "100"
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  template:
    spec:
      containers:
      - name: app
        image: my-app:v1
        env:
        - name: LOG_LEVEL
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: LOG_LEVEL
        - name: DB_HOST
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: DB_HOST
        # 也可以一把梭哈:envFrom
        envFrom:
        - configMapRef:
            name: app-config
Secret(密码)
apiVersion: v1
kind: Secret
metadata:
  name: db-secret
type: Opaque
data:
  DB_PASSWORD: dXNlcjEyM3Bhc3N3b3Jk # base64 编码的密码
---
apiVersion: apps/v1
kind: Deployment
spec:
  template:
    spec:
      containers:
      - name: app
        envFrom:
        - secretRef:
            name: db-secret

提醒:环境变量方式,ConfigMap/Secret 变更后 Pod 需要重启才能生效。线上见过有人改了 ConfigMap 等 20 分钟,最后发现重启 Pod 才生效。


方式二:卷挂载——推荐生产环境使用

基础挂载
apiVersion: v1
kind: Pod
metadata:
  name: configmap-demo
spec:
  containers:
  - name: app
    image: nginx
    volumeMounts:
    - name: config-vol
      mountPath: /etc/config    # 整个目录会被覆盖!
  volumes:
  - name: config-vol
    configMap:
      name: app-config

⚠️ 大坑:整个 mountPath 目录会被 ConfigMap 的内容覆盖。原本目录里的文件全部消失。

subPath:精准挂载单个文件

场景:只想把 nginx.conf 放进去,保留 /etc/nginx/ 下的其他文件。

volumeMounts:
- name: nginx-config-vol
  mountPath: /etc/nginx/nginx.conf   # 直接挂到目标文件路径
  subPath: nginx.conf                # 指定 ConfigMap 里的 key

坑爹的是:subPath 挂载 不支持热更新。改了 ConfigMap/Secret,容器里对应文件不会同步,必须重启 Pod。

我的做法:需要热更新的配置用目录挂载(全量覆盖),不需要热更新或超敏感的单文件用 subPath。两者分开,别混在一起。


三、热更新——别再拍脑袋了!

卷挂载(目录方式)——支持热更新

ConfigMap 改了之后,kubelet 会在 同步周期 + 缓存传播延迟 后将新内容挂载进容器,默认 1-2 分钟。如果等不及:

kubectl rollout restart deployment my-app

环境变量方式——不支持热更新

改了 ConfigMap/Secret,Pod 里的环境变量纹丝不动。必须重建 Pod。

环境变量与卷挂载的行为差异

使用方式

更新是否自动同步

延迟

适用场景

是否需要重启 Pod

环境变量注入

简单配置、启动参数

✅ 必须重启

卷挂载(目录)

~1分钟 + 缓存TTL

配置文件、可重载场景

subPath 挂载

精准替换单个文件

✅ 必须重启

Reloader:配置变更自动重启 Pod

不想手动 rollout?Stakater 的 Reloader 工具可以自动监测 ConfigMap/Secret 变更并触发 Pod 重启:

# 在 Deployment 的 annotations 中加入
annotations:
  secret.reloader.stakater.com/reload: "db-secret"
  configmap.reloader.stakater.com/reload: "app-config"

改了对应的 ConfigMap/Secret,Reloader 自动滚动更新 Pod。


四、敏感数据管理——这几条线必须守住

1. Secret 是 Base64 编码,不是加密

这是个致命问题:默认情况下 Secret 只是 base64 编码,存 etcd 里未经加密。任何人能访问 etcd 或 kube-apiserver,就能直接读取你的明文密码。

2. 启用 etcd 静态加密

# EncryptionConfiguration 示例
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
  - secrets
  providers:
  - aescbc:
      keys:
      - name: key1
        secret: <32字节 base64 密钥>
  - identity: {}

kube-apiserver 启动时加参数 --encryption-provider-config

3. RBAC 最小权限

千万别这么干

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
rules:
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["get", "list", "watch"]   # 权限太宽了!

正确做法:只给必要的 namespaces,只给必要的 verbs,明确到某个 Secret 名称。

4. 外部方案

  • Sealed Secrets:把 Secret 加密后安全提交到 Git
  • External Secrets Operator:从外部 KMS(AWS Secrets Manager、HashiCorp Vault)同步 Secret
  • Secrets Store CSI Driver:直接从外部 KMS 挂载,Secret 不在 etcd 落地

说实话,如果你的团队能接受外部方案,我强烈推荐 BeyondCorp + CSI Driver 的路线,敏感数据根本不落地 K8s,安全水位直接拉满。


五、故障排查两则

报错 1:CreateContainerConfigError

$ kubectl get pod
NAME    READY   STATUS                       RESTARTS   AGE
my-app  0/1     CreateContainerConfigError   0          10s

$ kubectl describe pod my-app
Events:
  Warning  Failed  Error: secret "db-secret" not found

原因:引用的 ConfigMap/Secret 不存在。
解决:检查名字和命名空间:

kubectl get cm -n namespace
kubectl get secret -n namespace

报错 2:Annotation 长度超限

metadata.annotations: Too long: must have at most 262144 bytes

原因kubectl apply 会把整个 spec 塞进 annotation,ConfigMap/Secret 太大塞不下。
解决:用 kubectl create/replace 代替 kubectl apply,或者拆分配置。


六、三个原则 + 一句忠告

原则一:ConfigMap 和 Secret 根本目的是解耦——代码和配置分离,镜像才能通用。
原则二:热更新不是想有就有——环境变量方式不支持,subPath 方式也不支持。
原则三:Secret 不是安全的——Base64 编码不是加密,请开启 etcd 加密或接入 CSI 等外部方案。

你还有什么更好的办法来管理 K8s 的敏感配置?比如 Sealed Secrets 踩过什么坑、CSI 驱动在生产环境的表现如何?评论区聊聊,一起把这套配置管理的坑填平!

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐