ConfigMap 与 Secret

一个常见的问题:应用的配置信息应该放在哪里?

放在代码里?不利于环境差异化管理。 放在环境变量里?配置多的时候难以维护。 放在配置中心?增加了系统复杂度。

Kubernetes 的 ConfigMap 和 Secret 提供了原生的配置管理方案。

ConfigMap 是什么?

ConfigMap 是用于存储非敏感配置的 API 对象。它可以将配置数据与 Pod 分离,使配置可以独立于应用镜像进行管理。

ConfigMap 适用于存储:

  • 环境变量
  • 命令行参数
  • 配置文件
  • 域名列表
  • 其他配置数据

创建 ConfigMap

# 从字面值创建
kubectl create configmap app-config \
  --from-literal=DB_HOST=localhost \
  --from-literal=DB_PORT=5432

# 从文件创建
kubectl create configmap nginx-config \
  --from-file=nginx.conf=./nginx.conf

# 从目录创建
kubectl create configmap app-config \
  --from-file=./config/

# 从 .env 文件创建
kubectl create configmap app-config \
  --from-env-file=./config.env

YAML 方式创建

configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  # 键值对
  DATABASE_HOST: "mysql.default.svc.cluster.local"
  DATABASE_PORT: "3306"
  LOG_LEVEL: "info"

  # 配置文件
  nginx.conf: |
    server {
      listen 80;
      server_name localhost;
      location / {
        root /usr/share/nginx/html;
      }
    }

  redis.conf: |
    maxmemory 256mb
    maxmemory-policy allkeys-lru
# 应用配置
kubectl apply -f configmap.yaml

# 查看 ConfigMap
kubectl get configmap app-config
kubectl describe configmap app-config

Secret 是什么?

Secret 是用于存储敏感数据的 API 对象,如密码、OAuth 令牌、SSH 密钥等。

Tip

虽然叫 Secret,但默认存储在 etcd 中的是 Base64 编码,不是加密的。如果需要真正的加密存储,需要配置 EncryptionConfiguration 或使用 Vault 等外部方案。

Secret 类型

类型用途
Opaque通用 Secret(默认)
kubernetes.io/tlsTLS 证书和私钥
kubernetes.io/dockerconfigjsonDocker 镜像仓库认证
kubernetes.io/basic-authBasic 认证凭据
kubernetes.io/ssh-authSSH 凭据
bootstrap.kubernetes.io/tokenBootstrap Token

创建 Secret

# 从字面值创建
kubectl create secret generic db-credentials \
  --from-literal=username=admin \
  --from-literal=password=secret123

# 从文件创建(TLS 证书)
kubectl create secret tls app-tls \
  --cert=path/to/cert.pem \
  --key=path/to/key.pem

# 从 Docker config 创建
kubectl create secret docker-registry my-registry \
  --docker-server=https://index.docker.io/v1/ \
  --docker-username=myuser \
  --docker-password=mypassword

YAML 方式创建

secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
type: Opaque
data:
  # Base64 编码的值
  username: YWRtaW4=      # echo -n admin | base64
  password: c2VjcmV0MTIz  # echo -n secret123 | base64
stringData:
  # 明文写入,会自动编码
  API_KEY: "sk-xxx-xxx-xxx"
  JWT_SECRET: "my-jwt-secret-key"
Warning

stringData 字段在写入 etcd 时会被转换为 Base64 编码,读取时不会显示明文。如果使用 YAML 创建 Secret,请确保 Base64 编码的值不包含换行符。

使用 ConfigMap 和 Secret

挂载为环境变量

env-configmap-secret.yaml
apiVersion: v1
kind: Pod
metadata:
  name: app
spec:
  containers:
  - name: app
    image: app:1.0
    env:
    # 直接引用 ConfigMap 值
    - name: DATABASE_HOST
      valueFrom:
        configMapKeyRef:
          name: app-config
          key: DATABASE_HOST
    - name: DATABASE_PORT
      valueFrom:
        configMapKeyRef:
          name: app-config
          key: DATABASE_PORT

    # 引用 Secret
    - name: DB_PASSWORD
      valueFrom:
        secretKeyRef:
          name: db-credentials
          key: password

    # 将 ConfigMap 所有键值对导入为环境变量
    envFrom:
    - configMapRef:
        name: app-config
    - secretRef:
        name: db-credentials

挂载为文件

volume-configmap-secret.yaml
apiVersion: v1
kind: Pod
metadata:
  name: app
spec:
  containers:
  - name: app
    image: app:1.0
    volumeMounts:
    # 挂载配置文件
    - name: config
      mountPath: /etc/config
      readOnly: true

    # 挂载 Secret(每个键一个文件)
    - name: credentials
      mountPath: /etc/secrets
      readOnly: true

    # 挂载单个文件
    - name: nginx-conf
      mountPath: /etc/nginx/conf.d
      readOnly: true
      subPath: nginx.conf

  volumes:
  - name: config
    configMap:
      name: app-config
      items:
      - key: nginx.conf
        path: default.conf

  - name: credentials
    secret:
      secretName: db-credentials
      items:
      - key: username
        path: db-user
      - key: password
        path: db-pass
      defaultMode: 0600

  - name: nginx-conf
    configMap:
      name: nginx-config
      items:
      - key: nginx.conf
        path: default.conf
      defaultMode: 0644
# 查看挂载的文件
kubectl exec -it app -- ls -la /etc/config/
kubectl exec -it app -- cat /etc/config/DATABASE_HOST

对比:环境变量 vs 文件挂载

方式优点缺点
环境变量简单,不需要修改应用无法热更新
文件挂载可以热更新,结构清晰需要应用读取文件

配置热更新

ConfigMap 热更新

当 ConfigMap 通过 Volume 挂载时,更新 ConfigMap 会自动同步到挂载的文件:

# 更新 ConfigMap
kubectl patch configmap app-config -p '{"data":{"LOG_LEVEL":"debug"}}'

# 查看 Pod 是否感知到更新
kubectl exec -it app -- cat /etc/config/LOG_LEVEL
Info

通过环境变量引用的 ConfigMap 值不会自动更新。如果需要热更新配置,建议使用 Volume 挂载方式,或者重启 Pod。

Secret 热更新

Secret 的热更新行为与 ConfigMap 相同:

# 更新 Secret
kubectl patch secret db-credentials -p '{"data":{"password":"bmV3cGFzc3dvcmQ="}}'

# 查看挂载的文件
kubectl exec -it app -- cat /etc/secrets/db-pass

更新策略

可以配置挂载时不自动更新:

volumes:
- name: config
  configMap:
    name: app-config
    # 禁用自动更新
    defaultMode: 0644
    optional: true  # ConfigMap 不存在时不报错

Secret 加密

启用静态加密

encryption-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
  - secrets
  providers:
  - identity: {}    # 不加密(仅测试用)
  - aescbc:
      keys:
      - name: key1
        secret: <base64 编码的 32 字节密钥>
  - kms:
      name: vault-provider
      endpoint: unix://tmp/vault.sock

启用加密

# 修改 kube-apiserver 配置
kubectl edit configmap kube-apiserver-encryption-config -n kube-system

# 或者使用 kubeadm 配置
apiServer:
  extraArgs:
    encryption-provider-config: /etc/kubernetes/encryption-config.yaml

最佳实践

ConfigMap 最佳实践

  1. 按用途分离:不同用途的配置放在不同的 ConfigMap
  2. 命名规范:使用有意义的名称,如 app-frontend-configapp-backend-config
  3. 版本控制:将 ConfigMap 的 YAML 文件纳入 Git 版本控制
# 推荐的 ConfigMap 组织方式
metadata:
  name: webapp-frontend
  labels:
    app: webapp
    component: frontend
    version: "1.0"

Secret 最佳实践

  1. 不要提交明文 Secret:使用 sealed-secret、vault 等方案
  2. 限制 Secret 访问:使用 RBAC 控制谁可以读取 Secret
  3. 定期轮换:定期更换密钥和密码
# 检查 Secret 访问权限
kubectl auth can-i get secrets --namespace=default

# 限制 ServiceAccount 访问
kubectl create role secret-reader --verb=get,list --resource=secrets
kubectl create rolebinding read-secrets --role=secret-reader --serviceaccount=default:app-sa

镜像仓库凭证

apiVersion: v1
kind: Pod
metadata:
  name: app
spec:
  imagePullSecrets:
  - name: my-registry-secret
  containers:
  - name: app
    image: myregistry.azurecr.io/app:1.0

常见问题

ConfigMap 挂载后文件为空

常见原因:

  1. ConfigMap 的 data 字段为空
  2. items 指定的 key 不存在
  3. subPath 指定的路径不存在
# 检查 ConfigMap 内容
kubectl get configmap app-config -o yaml

Secret 无法挂载

如果 Secret 存在但挂载失败,检查:

  1. Secret 类型是否正确
  2. items 中的 key 是否存在
  3. Pod 是否有权限访问 Secret

环境变量注入时机

环境变量在容器启动时注入,不会动态更新。如果需要更新环境变量,需要重建 Pod:

# 重建 Pod
kubectl rollout restart deployment/app

延伸思考

ConfigMap 和 Secret 是 Kubernetes 配置管理的基石。它们让配置与代码分离,使同一套镜像可以用于不同环境(dev、staging、production)。

但它们也有局限性:

  1. 不支持配置验证:ConfigMap 的内容没有 schema 验证
  2. 不支持热更新(环境变量):只能通过重启或 Volume 挂载解决
  3. Secret 不加密:默认存储是 Base64 编码,不是真正的加密

对于更复杂的配置管理需求,可以考虑:

  • Kustomize:管理多环境的配置差异
  • Helm:管理复杂的应用配置
  • External Secrets Operator:集成 AWS Secrets Manager、Vault 等外部密钥管理

延伸阅读