配置与密钥¶
把配置从镜像里拆出来,是十二要素应用的第一条。但「拆出来」不等于「管好了」:环境变量不会自动更新,Secret 默认只是 base64,挂载卷还有 subPath 的坑。
你将学到¶
- ConfigMap 与 Secret 的创建方式与消费方式
- 环境变量、
envFrom、卷挂载、subPath的行为差异 - 配置热更新的真实边界,以及应用如何感知变更
- Downward API 与 Projected Volume 的组合用法
- Secret 的安全边界:base64 不是加密
ConfigMap 与 Secret 的区别¶
| 维度 | ConfigMap | Secret |
|---|---|---|
| 用途 | 非敏感配置 | 密码、Token、证书、密钥 |
| 存储 | etcd 明文 | etcd 中 base64(可配置静态加密) |
| 大小限制 | 1 MiB | 1 MiB |
| 挂载 | 文件 / 环境变量 | 文件 / 环境变量 |
| RBAC | 可读 | 应严格限制 get/list/watch |
| 版本化 | 手工 | 可用 SealedSecret / External Secrets |
base64 不是加密
data 字段只是 base64 编码,任何有权限读 Secret 的人都能解码。生产环境应开启 etcd 静态加密、限制 RBAC,并考虑 External Secrets Operator / Sealed Secrets。
创建 ConfigMap¶
从字面量¶
kubectl create configmap app-config \
--from-literal=APP_MODE=prod \
--from-literal=LOG_LEVEL=info \
-n demo
从文件¶
kubectl create configmap nginx-conf \
--from-file=myserver.conf=./nginx-conf.d/myserver.conf \
--from-file=myserver-status.cfg=./nginx-conf.d/myserver-status.cfg \
-n demo
从 YAML¶
configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: demo
data:
APP_MODE: "prod"
LOG_LEVEL: "info"
application.yaml: |
server:
port: 8080
logging:
level: info
消费 ConfigMap¶
1. 单个环境变量¶
2. 批量注入¶
envFrom:
- configMapRef:
name: app-config
- secretRef:
name: mysql-secret
- prefix: APP_
configMapRef:
name: app-config
envFrom 会把所有 key 变成环境变量。key 必须是合法的环境变量名;非法 key 会被跳过,并在事件中记录。
3. 挂载为文件¶
volumes:
- name: config
configMap:
name: nginx-conf
items:
- key: myserver.conf
path: conf.d/myserver.conf
containers:
- name: nginx
image: nginx:1.27
volumeMounts:
- name: config
mountPath: /etc/nginx/conf.d
readOnly: true
挂载后目录结构:
4. subPath 的单文件挂载¶
subPath 不会热更新
使用 subPath 挂载的文件,在 ConfigMap 更新后不会同步到容器。这是最常见的热更新坑。需要热更新时,挂载目录而不是单文件,或者接受重启 Pod。
消费 Secret¶
创建¶
kubectl create secret generic mysql-secret \
--from-literal=MYSQL_ROOT_PASSWORD='S3cure!Pass' \
--from-literal=MYSQL_PASSWORD='appPass' \
-n demo
kubectl create secret tls demo-tls \
--cert=./tls/tls.crt \
--key=./tls/tls.key \
-n demo
YAML 形式¶
secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: mysql-secret
namespace: demo
type: Opaque
data:
MYSQL_ROOT_PASSWORD: UzNjdXJlIVBhc3M=
MYSQL_PASSWORD: YXBwUGFzcw==
生成 base64:
解码验证:
常见 Secret 类型¶
| 类型 | 用途 | 必填 key |
|---|---|---|
Opaque |
通用 | 自定义 |
kubernetes.io/tls |
TLS 证书 | tls.crt、tls.key |
kubernetes.io/dockerconfigjson |
镜像仓库凭据 | .dockerconfigjson |
kubernetes.io/basic-auth |
用户名密码 | username、password |
kubernetes.io/ssh-auth |
SSH 私钥 | ssh-privatekey |
镜像拉取凭据¶
kubectl create secret docker-registry regcred \
--docker-server=registry.example.com \
--docker-username=robot \
--docker-password='***' \
--docker-email=dev@example.com \
-n demo
热更新:到底谁会变¶
| 消费方式 | 更新后是否生效 | 说明 |
|---|---|---|
环境变量(env / envFrom) |
否 | 进程启动时读取,必须重启 Pod |
| 卷挂载(目录) | 是(有延迟) | kubelet 周期同步,通常几十秒 |
卷挂载(subPath) |
否 | 单文件绑定,不更新 |
immutable: true |
否 | 禁止修改,只能删了重建 |
即使文件更新了,应用也未必会重新加载。常见做法:
- 应用支持 SIGHUP / 文件监听:最理想;
- 滚动重启:
kubectl rollout restart deploy/app; - Checksum 注解:Helm 中把 ConfigMap 的 hash 写进 Pod template,配置变则 Pod 重建;
- 配置热加载 Sidecar:如 reloader,监听 ConfigMap 变化并触发重启。
checksum-annotation.yaml
spec:
template:
metadata:
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
Downward API¶
Downward API 把 Pod / 容器的元数据注入容器,不需要额外对象。
downward-api.yaml
apiVersion: v1
kind: Pod
metadata:
name: downward-demo
labels:
app: demo
spec:
containers:
- name: app
image: busybox:1.36
command: ["sh", "-c", "env; sleep 3600"]
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: CPU_LIMIT
valueFrom:
resourceFieldRef:
containerName: app
resource: limits.cpu
volumeMounts:
- name: podinfo
mountPath: /etc/podinfo
volumes:
- name: podinfo
downwardAPI:
items:
- path: labels
fieldRef:
fieldPath: metadata.labels
- path: annotations
fieldRef:
fieldPath: metadata.annotations
Projected Volume¶
Projected Volume 把多个来源(Secret、ConfigMap、Downward API、ServiceAccount Token)投影到同一个目录,适合需要把证书、配置、Token 放在一起的场景。
projected.yaml
apiVersion: v1
kind: Pod
metadata:
name: projected-demo
spec:
containers:
- name: app
image: nginx:1.27
volumeMounts:
- name: all-in-one
mountPath: /etc/app
readOnly: true
volumes:
- name: all-in-one
projected:
sources:
- configMap:
name: app-config
- secret:
name: demo-tls
- downwardAPI:
items:
- path: pod-name
fieldRef:
fieldPath: metadata.name
- serviceAccountToken:
path: token
expirationSeconds: 3600
audience: api
实验:配置热更新验证¶
1. 创建 ConfigMap 并挂载¶
kubectl create namespace demo --dry-run=client -o yaml | kubectl apply -f -
kubectl create configmap web-config \
--from-literal=index.html='version 1' \
-n demo
web.yaml
apiVersion: v1
kind: Pod
metadata:
name: web
namespace: demo
spec:
containers:
- name: nginx
image: nginx:1.27
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
volumes:
- name: html
configMap:
name: web-config
2. 更新 ConfigMap¶
kubectl create configmap web-config \
--from-literal=index.html='version 2' \
-n demo --dry-run=client -o yaml | kubectl apply -f -
等待 30–60 秒,再查看:
预期变成 version 2。如果用的是 subPath,这里仍然是 version 1。
3. 环境变量对比¶
给 Pod 加一个 env 引用 web-config,更新 ConfigMap 后检查环境变量,确认不会变化。
4. 不可变 ConfigMap¶
修改会被拒绝:
只能删除重建。
排错表¶
| 现象 | 原因 | 处理 |
|---|---|---|
CreateContainerConfigError |
引用的 ConfigMap/Secret 不存在或 key 缺失 | describe pod 看具体 key |
| 环境变量为空 | key 名不匹配 / envFrom 非法 key |
kubectl get cm -o yaml 对照 |
| 挂载目录为空 | items 路径写错 |
检查 path 与 mountPath |
| 更新不生效 | 用了 subPath / 环境变量 |
改为目录挂载或重启 |
| Secret 解码乱码 | 编码时带了换行 | 用 echo -n 或 printf |
| TLS Secret 不被 Ingress 识别 | 缺少 tls.crt / tls.key |
检查类型与 key |
| 镜像拉取 401 | 没配 imagePullSecrets |
创建 docker-registry Secret |
进阶挑战¶
- 用
immutable: true保护一个生产配置,设计一次安全的配置变更流程。 - 用 Projected Volume 同时挂载 ConfigMap、TLS Secret 和 ServiceAccount Token。
- 写一个脚本,比较 ConfigMap 更新前后 Pod 内文件的内容与 mtime。
- 给 Deployment 加 checksum 注解,验证配置变更会触发滚动更新。
- 用
kubectl auth can-i get secrets检查当前用户是否有权读取 Secret。
小结¶
- ConfigMap 管普通配置,Secret 管敏感数据;Secret 的 base64 不是加密。
- 环境变量不会热更新,目录挂载会,
subPath不会。 - Downward API 注入元数据,Projected Volume 把多个来源合并到一个目录。
- 生产环境要限制 Secret 的 RBAC,并考虑外部密钥管理。