Appearance
配置与密钥
Kubernetes 里普通配置放 ConfigMap,敏感数据放 Secret。它们通过环境变量、文件挂载或镜像拉取凭据的方式进入容器。配置变更后应用是否真的加载了新值,不只看 kubectl apply 有没有成功——环境变量只在启动时注入一次,文件挂载和 subPath 的热更新行为也不一样。
ConfigMap
ConfigMap 保存明文配置,可以是键值对,也可以是完整的配置文件:
yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: demo
data:
LOG_LEVEL: info
APP_MODE: prod
app.yaml: | # 多行配置可以直接放在 ConfigMap 里
server:
port: 8080把整段 ConfigMap 注入环境变量:
yaml
envFrom:
- configMapRef:
name: app-config # ConfigMap 里每个 key 变成一个环境变量,key 名即变量名envFrom 注入的环境变量只在容器进程启动时读取一次。后续 kubectl edit cm 修改 ConfigMap,不会改变已经在运行的容器的环境变量。要让新配置生效,需要重建 Pod(滚动重启 Deployment、StatefulSet 等控制器触发新建)。
只取单个 key:
yaml
env:
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: app-config
key: LOG_LEVEL # 只取 ConfigMap 里这个 key 的值文件挂载
ConfigMap 作为文件挂载到容器目录,kubelet 会周期性(默认约 1 分钟)把 ConfigMap 的变更同步到挂载目录的文件里:
yaml
volumes:
- name: config
configMap:
name: app-config
containers:
- name: app
image: harbor.example.com/demo/app:v1.0.0
volumeMounts:
- name: config
mountPath: /etc/app # ConfigMap 里每个 key 成为 /etc/app/ 下的一个文件ConfigMap 更新后,kubelet 会修改挂载目录里的文件,但应用进程什么时候读到新文件是另一回事。Nginx 需要 nginx -s reload,很多 Java 应用需要重启,Go 应用如果做文件 watch 可以热加载。ConfigMap 文件变化和进程加载之间没有自动关联。
subPath 不会热更新
用 subPath 只挂载 ConfigMap 里的某一个文件:
yaml
volumeMounts:
- name: config
mountPath: /etc/app/app.yaml
subPath: app.yaml # 只挂这一个文件,不挂整个目录subPath 挂载的文件是挂载时的一个快照,ConfigMap 更新后这个文件不会跟着变。这是 Linux bind mount 的固有行为,不是 K8s 的 bug。要用 subPath 的场景里,配置变更后通常靠滚动重启让新 Pod 拿到新值:
bash
kubectl -n demo rollout restart deploy/appSecret
Secret 和 ConfigMap 结构类似,但设计上用于存储敏感数据。它默认以 base64 编码存储(不是加密),能读到 Secret 对象的账号或能访问 etcd 的人都能拿到里面的明文内容。
用命令行创建:
bash
kubectl -n demo create secret generic db-auth \
--from-literal=username=app \
--from-literal=password='change-me'环境变量引用:
yaml
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-auth
key: password文件挂载并限制权限:
yaml
volumes:
- name: db-auth
secret:
secretName: db-auth
defaultMode: 0400 # 只有文件 owner 可读,其他用户不可读写Secret 真正的风险不只在"有没有加密"。即使 etcd 加密了、RBAC 收紧了,应用日志里把 Secret 打印出来、CI 输出里带了密码、YAML 被原样提交到 Git 仓库——这些才是更容易泄露的路径。生产环境更完整的方案通常结合 External Secrets Operator(从外部 Secret Manager 同步)、Sealed Secrets(加密后可安全存入 Git)或云厂商的 Secret Manager。
镜像拉取 Secret
私有镜像仓库需要配置拉取凭据:
bash
kubectl -n demo create secret docker-registry harbor-auth \
--docker-server=harbor.example.com \
--docker-username=robot-demo \
--docker-password='<password>' \
--docker-email=ops@example.comPod 引用:
yaml
spec:
imagePullSecrets:
- name: harbor-auth
containers:
- name: app
image: harbor.example.com/demo/app:v1.0.0imagePullSecrets 必须和 Pod 在同一个 namespace。两种常见的 ImagePullBackOff:一种是 Secret 建错了 namespace,Pod 找不到;另一种是 --docker-server 写成了带路径的完整 URL(正确的写法是只写 registry 地址 harbor.example.com,不包含路径)。
给 ServiceAccount 打上 imagePullSecrets 可以让同一 namespace 下用这个 SA 的 Pod 自动带上,不用每个 Pod 单独写。
配置变更排查
bash
kubectl -n demo get cm app-config -o yaml
kubectl -n demo describe pod <pod-name>
# 进入容器确认实际挂载的文件内容和权限
kubectl -n demo exec <pod-name> -- ls -l /etc/app
kubectl -n demo exec <pod-name> -- cat /etc/app/app.yaml| 现象 | 原因 | 处理方向 |
|---|---|---|
| ConfigMap 已改,环境变量没变 | 环境变量只在容器启动时注入 | 重启 Pod |
| 文件已更新,应用没生效 | 应用没有 re-read 配置文件的机制 | reload 或重启 |
| subPath 文件不变 | subPath 不支持热更新 | 滚动重启 |
| Secret 报找不到 | namespace 不匹配、Secret 名写错、key 名写错 | 核对三个名称 |
| 私有镜像拉取失败 | imagePullSecrets 跨 namespace、docker-server 值写错、仓库证书 | 看 Pod Events、节点上 crictl pull |
| 启动后读到的配置是旧的 | 容器内缓存了文件内容、用了环境变量启动参数 | 确认应用读取配置的方式 |
配置发布后从三个角度确认:K8s 对象是否更新、容器内文件是否变化、应用进程是否加载了新配置。