目录

k8s 配置与密钥管理

导读

将配置和凭据从镜像中剥离,是云原生部署的基础能力。Kubernetes 提供 ConfigMap 和 Secret 两类对象,分别承载普通配置与敏感数据,通过环境变量或 Volume 两条途径注入容器。掌握它们的注入差异、热更新边界以及与镜像解耦的实践模式,才能写出可跨环境复用的部署清单。

核心概念

ConfigMap 用于存储非敏感的配置数据,以键值对形式存在,支持从字面量、文件或目录创建。最常见的创建方式有三种:用 kubectl create configmap --from-literal 直接传键值对、用 --from-file 把整个文件内容作为值、以及用声明式 YAML 定义 data 字段。ConfigMap 本身不做类型校验,存储的是字符串,容器消费时由应用自行解析。

Secret 与 ConfigMap 结构相似,但面向敏感数据,其 data 字段的值经过 Base64 编码存储。Kubernetes 内置多种 Secret 类型:Opaque 用于通用键值对,kubernetes.io/dockerconfigjson 存放私有镜像仓库认证,kubernetes.io/tls 存放证书与私钥。Base64 只是编码格式而非加密,任何能读取该 Secret 的用户都能解码出原文,因此 Secret 的安全性依赖 RBAC 权限控制与 etcd 静态加密。

两类对象进入容器有两条路径,env 注入与 volume 挂载各有适用场景。前者适合启动参数,后者适合配置文件。理解二者的生效机制差异,是正确使用配置对象的前提。

flowchart LR CM[ConfigMap] --> PATH{注入方式} SEC[Secret] --> PATH PATH -->|env 注入| ENV[容器环境变量] PATH -->|volume 挂载| VOL[容器文件系统] ENV --> R1[Pod 启动时固化<br/>变更需重启 Pod] VOL --> R2[kubelet 周期同步<br/>支持热更新]

图 1 展示了 ConfigMap 和 Secret 殊途同归的两条注入路径。env 注入在容器启动时把键值写进环境变量,一旦 Pod 运行便固定,ConfigMap 变更不会反映到已注入的环境变量,需要重启 Pod。volume 挂载则把对象内容以文件形式投射到容器文件系统,kubelet 会周期性同步,从而支持热更新。选择哪条路径,直接决定配置变更的生效方式。

图解原理

配置热更新的能力来源于 volume 投射机制。当 ConfigMap 以 Volume 挂载时,kubelet 在节点上将其内容写为临时文件,容器通过 projected volume 读取。kubelet 默认每分钟同步一次,ConfigMap 的改动会在下一次同步后反映到容器内的文件。应用若想实时感知,需要自行监听文件变更或定期重读,Kubernetes 本身不主动通知进程。

SubPath 挂载是热更新的一个重要限制。当 volumeMount 指定了 subPath,该文件以独立卷形式挂载,kubelet 不再同步其内容。这意味着通过 SubPath 引用的 ConfigMap 文件,在源对象更新后不会热更新,必须重启 Pod。需要热更新的场景应避免 SubPath,改为挂载整个目录。

将配置从镜像中抽离,是配置管理的核心实践。同一镜像配合不同 ConfigMap 即可适配多环境部署。镜像只承载应用代码,环境差异全部交给配置对象。

graph TB IMG[应用镜像 myapp:v1.2.0] --> DEP[Deployment 模板] DEP --> DEV[Dev 环境] DEP --> STG[Staging 环境] DEP --> PRD[Prod 环境] DEV -.读取.-> CM1[ConfigMap: dev-config] STG -.读取.-> CM2[ConfigMap: staging-config] PRD -.读取.-> CM3[ConfigMap: prod-config] DEV -.读取.-> SEC1[Secret: dev-credentials] PRD -.读取.-> SEC2[Secret: prod-credentials]

图 2 描绘了配置与镜像解耦的部署模式。同一份镜像推送到所有环境,每个环境通过各自的 ConfigMap 注入数据库地址、日志级别等参数,通过各自的 Secret 注入凭据。这种模式让镜像只需构建一次,环境间的差异收敛为配置对象的差异,大幅降低多环境维护成本。

动手实操

创建 ConfigMap 最直接的方式是用命令行传入字面量。

kubectl create configmap app-config \
  --from-literal=LOG_LEVEL=debug \
  --from-literal=MAX_CONNECTIONS=100

声明式 YAML 更适合纳入版本管理,data 字段存放明文字符串,binaryData 存放 Base64 编码内容。

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: default
data:
  LOG_LEVEL: "info"
  MAX_CONNECTIONS: "100"
  app.properties: |
    server.port=8080
    cache.ttl=300

把 ConfigMap 注入为环境变量,用 envFrom 批量导入,或用 env.valueFrom 逐个引用。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: demo
  template:
    metadata:
      labels:
        app: demo
    spec:
      containers:
      - name: app
        image: busybox:1.36
        command: ["sh", "-c", "env | grep LOG_LEVEL && sleep 3600"]
        envFrom:
        - configMapRef:
            name: app-config

把 ConfigMap 挂载为 Volume,每个键变成一个文件,文件名即键名,内容即键值。

apiVersion: v1
kind: Pod
metadata:
  name: demo-volume
spec:
  containers:
  - name: app
    image: busybox:1.36
    command: ["sh", "-c", "cat /etc/config/app.properties && sleep 3600"]
    volumeMounts:
    - name: config-volume
      mountPath: /etc/config
  volumes:
  - name: config-volume
    configMap:
      name: app-config

创建 Opaque 类型的 Secret,命令行会自动对值做 Base64 编码。

kubectl create secret generic db-credentials \
  --from-literal=username=admin \
  --from-literal=password=S3cr3t

声明式 YAML 需要手动编码,用 echo -n 避免尾随换行符混入编码结果。

echo -n "admin" | base64
echo -n "S3cr3t" | base64
apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
type: Opaque
data:
  username: YWRtaW4=
  password: UzNjcjN0

拉取私有镜像仓库的镜像,需要创建 dockerconfigjson 类型的 Secret。

kubectl create secret docker-registry reg-cred \
  --docker-server=registry.example.com \
  --docker-username=admin \
  --docker-password=S3cr3t \
  --docker-email=admin@example.com

在 ServiceAccount 或 Pod 的 imagePullSecrets 字段中引用该 Secret,即可完成拉取认证。TLS Secret 用于 Ingress 等场景,用证书和私钥文件直接创建。

kubectl create secret tls tls-cred \
  --cert=server.crt \
  --key=server.key

验证热更新行为,先挂载 Volume 形式的 ConfigMap,再修改其内容并观察容器内文件。

kubectl edit configmap app-config
# 修改 LOG_LEVEL 的值后保存退出
kubectl exec demo-volume -- cat /etc/config/LOG_LEVEL
# 等待约一分钟,再次执行上述命令观察文件内容变化

常见问题与避坑

SubPath 挂载不享受热更新。当 volumeMount 指定了 subPath,该文件以独立卷形式挂载,kubelet 不再同步其内容。依赖配置热更新的场景应挂载整个目录而非单文件。

Secret 的 Base64 不是加密。Base64 可被任意解码,Secret 在 etcd 中默认以 Base64 明文存储。生产环境应开启 etcd 静态加密并配合 RBAC 限制 Secret 的读取权限。

env 注入的配置不会热更新。环境变量在容器启动时固化,修改 ConfigMap 后已运行的 Pod 不会感知。需要动态生效的配置应改用 Volume 挂载,或在配置变更后触发滚动更新。

单个 ConfigMap 的 data 总量上限为 1 MiB,超过会被 API Server 拒绝。大配置文件应考虑挂载独立卷或引入外部配置中心,而非全部塞进 ConfigMap。

小结与进阶

ConfigMap 和 Secret 是配置与镜像解耦的基石,选择 env 还是 Volume 注入决定了配置能否热更新。下一篇将进入存储体系,讲解 PersistentVolume 与 PersistentVolumeClaim 如何为有状态应用提供持久化能力。