Chapter 06

配置管理:ConfigMap 与 Secret

将配置与代码分离,掌握 ConfigMap 的多种挂载方式、Secret 类型与安全存储,以及外部 Secret 管理方案。

为什么要将配置与代码分离

"同一个镜像,跑在 dev/staging/prod 不同环境"是容器化的核心优势。但不同环境的数据库地址、日志级别、第三方 API Key 各不相同。这些配置不能硬编码在镜像里——否则就丧失了可移植性。

K8s 通过两种资源分离配置:

ConfigMap

存储非敏感配置,如应用参数、配置文件、日志级别。数据以明文存储在 etcd 中。

Secret

存储敏感信息,如密码、Token、TLS 证书。数据以 Base64 编码存储(注意:不是加密!)。

ConfigMap

创建 ConfigMap

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: default
data:                              # 键值对数据
  LOG_LEVEL: info                  # 简单键值
  APP_PORT: "8080"
  DATABASE_HOST: mysql-service
  nginx.conf: |                    # 多行文件内容(| 保留换行)
    server {
        listen 80;
        server_name example.com;
        location / {
            proxy_pass http://backend;
        }
    }
  app.properties: |
    spring.datasource.url=jdbc:mysql://mysql-service:3306/mydb
    spring.datasource.max-pool-size=10
# 从文件创建 ConfigMap
kubectl create configmap nginx-conf --from-file=nginx.conf

# 从目录创建(目录下所有文件)
kubectl create configmap app-config --from-file=config/

# 从字面值创建
kubectl create configmap env-config \
  --from-literal=LOG_LEVEL=info \
  --from-literal=APP_PORT=8080

挂载方式一:作为环境变量

spec:
  containers:
  - name: app
    image: my-app:v1
    env:                            # 注入单个键
    - name: LOG_LEVEL
      valueFrom:
        configMapKeyRef:
          name: app-config        # ConfigMap 名称
          key: LOG_LEVEL          # ConfigMap 中的键
    envFrom:                       # 注入全部键(作为环境变量)
    - configMapRef:
        name: app-config

挂载方式二:作为 Volume 文件

spec:
  containers:
  - name: nginx
    image: nginx:1.25
    volumeMounts:
    - name: config-vol
      mountPath: /etc/nginx/conf.d  # ConfigMap 所有 key 成为该目录下的文件
    - name: config-vol
      mountPath: /etc/nginx/nginx.conf
      subPath: nginx.conf           # subPath:只挂载指定键为单个文件
  volumes:
  - name: config-vol
    configMap:
      name: app-config
      defaultMode: 0644             # 文件权限(八进制)
      items:                        # 只挂载指定 key
      - key: nginx.conf
        path: default.conf          # 重命名文件
配置热更新

以 Volume 方式挂载的 ConfigMap,K8s 会在 ConfigMap 更新后(约 1 分钟内)自动更新挂载的文件,无需重启 Pod。但环境变量方式注入的配置不会热更新,需要重启 Pod 才生效。应用还需要自行监听文件变更并重新加载配置(如 nginx 的 -s reload)。

Secret

Secret 类型

Opaque
默认类型,存储任意 base64 编码的键值对。常用于存储数据库密码、API Token 等。
kubernetes.io/tls
存储 TLS 证书和私钥。必须包含 tls.crt(证书)和 tls.key(私钥)字段。Ingress 的 TLS 配置使用此类型。
kubernetes.io/dockerconfigjson
存储 Docker 镜像仓库认证信息(imagePullSecret)。用于拉取私有镜像仓库的镜像。
kubernetes.io/service-account-token
ServiceAccount Token,由 K8s 自动创建,注入到 Pod 中用于访问 API Server。

创建 Opaque Secret

apiVersion: v1
kind: Secret
metadata:
  name: db-secret
type: Opaque
data:                              # base64 编码的值
  username: YWRtaW4=              # echo -n 'admin' | base64
  password: cGFzc3dvcmQxMjM=     # echo -n 'password123' | base64
# 也可以用 stringData(明文,K8s 自动编码,不会写回 YAML)
stringData:
  dsn: "mysql://admin:password123@mysql:3306/mydb"
# 命令行创建(推荐,避免 base64 手动编码出错)
kubectl create secret generic db-secret \
  --from-literal=username=admin \
  --from-literal=password=password123

# 创建 TLS Secret
kubectl create secret tls api-tls \
  --cert=tls.crt \
  --key=tls.key

# 创建 Docker 镜像拉取 Secret
kubectl create secret docker-registry registry-secret \
  --docker-server=registry.example.com \
  --docker-username=user \
  --docker-password=passwd

在 Pod 中使用 Secret

spec:
  imagePullSecrets:               # 拉取私有镜像
  - name: registry-secret
  containers:
  - name: app
    image: my-app:v1
    env:
    - name: DB_USERNAME
      valueFrom:
        secretKeyRef:
          name: db-secret
          key: username
    - name: DB_PASSWORD
      valueFrom:
        secretKeyRef:
          name: db-secret
          key: password
    volumeMounts:
    - name: tls-certs
      mountPath: /etc/ssl/certs
      readOnly: true
  volumes:
  - name: tls-certs
    secret:
      secretName: api-tls
      defaultMode: 0400            # 私钥文件权限要严格
Secret 不等于加密!

K8s Secret 的 data 字段只是 Base64 编码,不是加密!任何能读取 Secret 的人都可以解码。必须做到:
1. 开启 etcd 静态加密(EncryptionConfiguration
2. 严格控制 RBAC,最小化 Secret 读取权限
3. 生产环境推荐使用 Vault 或 External Secrets Operator 管理敏感配置
4. 永远不要把 Secret YAML 提交到 Git 仓库!

外部 Secret 管理

External Secrets Operator

External Secrets Operator(ESO)将外部 Secret 管理系统(Vault、AWS Secrets Manager、GCP Secret Manager 等)的 Secret 同步为 K8s Secret。

# ExternalSecret:定义从哪个外部系统同步什么数据
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: db-external-secret
spec:
  refreshInterval: 1h             # 每小时同步一次
  secretStoreRef:                 # 引用 SecretStore(外部系统连接配置)
    name: vault-backend
    kind: SecretStore
  target:
    name: db-secret               # 生成的 K8s Secret 名称
  data:
  - secretKey: password          # K8s Secret 中的键名
    remoteRef:
      key: production/db          # Vault 中的路径
      property: password         # Vault 中的字段
Sealed Secrets

如果你无法使用 Vault 等外部系统,可以考虑 Sealed Secrets(Bitnami)。它允许将 Secret 加密后安全地提交到 Git(只有集群内的控制器能解密),实现 GitOps 安全管理 Secret。