第 8 章 配置管理:ConfigMap 与 Secret¶
配套实验手册:《Kubernetes 实验手册》(manual/ 目录)实验 06「ConfigMap 和 Secret」(5 个 Lab + Secret 类型补充 + Downward API 补充)。本章承接第 4 章"容器怎么配置"(env/command 硬编码),讲配置怎么外部化——把配置从镜像/yaml 里"抽"出来,交给 ConfigMap 与 Secret 统一管理。
学习目标¶
学完本章,你应该能够:
- 解释"配置外部化"的必要性(镜像不可变/多环境/敏感信息三痛点)
- 解释 ConfigMap 的本质与两种消费方式(卷挂载 vs 环境变量)的机制差异
- 解释"卷挂载支持热更新、env 注入需要重启"的底层原因
- 解释 Secret 与 ConfigMap 的关系,以及"base64 是编码不是加密"的深刻含义
- 说出 Secret 的四种类型及各自用途(Opaque/tls/dockerconfigjson/service-account-token)
- 区分 ConfigMap/Secret(外部配置)与 Downward API(自身元数据)
- 说出 Secret 的安全边界(RBAC/etcd 加密/最小权限)
- 设计一个应用的完整配置方案(哪些进 ConfigMap、哪些进 Secret、哪些进 Downward API)
8.1 为什么需要"配置外部化"¶
8.1.1 配置写死在镜像/代码里的三个痛点¶
第 4 章学了 env 和 command——但如果把配置直接写进镜像或 Deployment yaml,会遇到:
- 镜像不可变原则被破坏:镜像(第 1 章分层)一旦构建就不该改——改了配置就得重新构建镜像、重新发布
- 多环境无法复用:dev/test/prod 的数据库地址、日志级别不同——写死一份镜像只能服务一个环境
- 敏感信息暴露:密码写进 yaml → 进 Git → 泄密;写进镜像 → 所有拉取镜像的人都能看到
8.1.2 配置外部化的原则¶
十二要素(12-Factor)的核心:"配置"与"代码"分离——同一份镜像,通过注入不同的配置运行在不同环境。
Kubernetes 的答案是两个专用对象:
- ConfigMap:非敏感配置(连接串、开关、日志级别)
- Secret:敏感配置(密码、Token、证书)
两者机制几乎一样,唯一的本质区别是数据的敏感程度。
8.2 ConfigMap:非敏感配置¶
8.2.1 本质与创建¶
ConfigMap 就是一个"键值对仓库"(data 区),键可以是短字符串,也可以是整个配置文件的内容:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
LOG_LEVEL: info # 键值对(短配置)
APP_PORT: "8080"
app.conf: | # 键 = 文件名,值 = 文件内容(长配置)
server.port=8080
server.timeout=30
创建方式:
kubectl create configmap xxx --from-literal=KEY=VAL(字面量)kubectl create configmap xxx --from-file=app.conf(文件)kubectl create configmap xxx --from-file=conf.d/(目录,每个文件一个键)- 或声明式 yaml(生产推荐)
8.2.2 消费方式一:卷挂载(键变文件)¶
把 ConfigMap 挂成卷,每个键变成目录里的一个文件:
spec:
containers:
- name: app
volumeMounts:
- name: config
mountPath: /etc/app # ConfigMap 挂到这里
volumes:
- name: config
configMap:
name: app-config
适合:应用读配置文件的场景(配置文件是文件,不是环境变量)。
8.2.3 消费方式二:环境变量(键变变量)¶
把指定的键注入为环境变量:
spec:
containers:
- name: app
env:
- name: LOG_LEVEL # 环境变量名
valueFrom:
configMapKeyRef:
name: app-config
key: LOG_LEVEL # 从 ConfigMap 取这个键
适合:应用读环境变量的场景(12-Factor 风格)。
8.2.4 两种方式对比¶
| 维度 | 卷挂载 | env 注入 |
|---|---|---|
| 形态 | 键 → 文件 | 键 → 环境变量 |
| 应用读取 | 读文件(配置类应用) | 读环境变量 |
| 热更新 | 改 ConfigMap 后文件自动更新(无需重启) | 改后需重启 Pod 才生效 |
| 场景 | 配置文件、整个 conf 目录 | 少量开关、连接参数 |
flowchart TD
Q{"配置形态\n与应用读取方式?"}
Q -- "整个配置文件\n应用读文件" --> V["卷挂载\n(键变文件,支持热更新)"]
Q -- "少量参数\n应用读环境变量" --> E["env 注入\n(一次性,改需重启)"]
style V fill:#E8F8E8,stroke:#5BA85B
style E fill:#E8F4FD,stroke:#4A90D9
读图要点:判断依据是"形态 + 读取方式"——配置是文件 → 卷挂载;配置是少量键值且应用读 env → env 注入;热更新需求直接排除 env。
8.2.5 热更新的底层原理(重要)¶
- 卷挂载为什么能热更新:kubelet 定期同步 ConfigMap 到本地缓存目录,挂载是"软链/绑定挂载"——ConfigMap 变了,文件内容跟着变,应用读到新值(应用自己要不要"重新读文件"取决于应用实现)
- env 为什么不能:环境变量是进程启动时注入的——进程已经跑起来了,改 env 不会进到正在运行的进程里,只能重启 Pod(新 Pod 用新值)
决策逻辑:需要频繁改配置、应用读文件 → 卷挂载(热更新);少量一次性参数、应用读 env → env 注入。
8.2.6 subPath 挂载陷阱(经典坑,必读)¶
subPath 让你只挂 ConfigMap 里的单个文件(而不是整个目录):
volumeMounts:
- name: config
mountPath: /etc/nginx/nginx.conf # 只挂这个文件
subPath: nginx.conf # 而不是整个 config 目录
为什么是坑:subPath 挂载是直接复制文件(不做软链接)——ConfigMap 更新后,subPath 挂载的文件不会跟着变(彻底丧失热更新)!要更新只能重启 Pod。
决策逻辑:
subPath适合"只挂单个文件 + 配置基本不变"的场景;需要热更新的配置文件不要用 subPath(挂整个目录或目录里放单文件)。这是 K8s 社区最经典的配置坑之一。
8.2.7 immutable 与热更新工具(生产性能)¶
- immutable(不可变):
immutable: true的 ConfigMap/Secret 禁止修改(只能删除重建)——好处:kubelet 不再轮询检查变化,大规模集群控制面压力大幅下降;适合"基本不变"的配置(如公共证书、系统级配置) - Reloader 工具(第三方):自动监听 ConfigMap/Secret 变化并滚动重启关联 Deployment——解决"env/subPath 不能热更新"的自动化方案(
reloader.stakater.com/auto: "true"注解标记)
生产组合:配置文件卷挂载(热更新)+ 少量参数 immutable(省轮询)+ 需要重启生效的场景用 Reloader 自动滚动。
8.3 Secret:敏感配置¶
8.3.1 与 ConfigMap 的关系¶
Secret 的结构与 ConfigMap 几乎一样(data 区键值对),差别在:
- 值必须 base64 编码(ConfigMap 明文)
describe/describe默认不显示内容- 有类型字段(§8.3.3)
- 访问控制更严格(RBAC 可单独授权,§8.3.5)
apiVersion: v1
kind: Secret
metadata:
name: mysql-pass
type: Opaque
data:
password: d29yZHByZXNzMTIz # "wordpress123" 的 base64
消费方式与 ConfigMap 相同(卷挂载 secret 卷 / env 的 secret)——挂载后自动还原明文(文件里是原始值,不是 base64)。
8.3.2 重要认知:base64 是编码,不是加密¶
- base64 只是"字节 → 可打印字符"的编码(第 2 章 kubeconfig 里的证书数据也是 base64)——任何人都能解码
- 证明:
kubectl get secret mysql-pass -o yaml拿到密文 →kubectl get secret mysql-pass -o yaml秒还原(实验 06 Lab 4 亲手验证) - Secret 的真正安全依赖:
- RBAC:谁能读 Secret(第 11 章授权机制)
- etcd 静态加密:落盘加密(实验 09 Lab 9 实操,防备份泄露)
- 最小权限:不用的 Secret 不创建、不授权
一句话:把 base64 当加密是新手最常见的误解——它只是"传输/存储格式",安全靠权限和加密存储。
8.3.3 Secret 的类型¶
| 类型 | 用途 | 键要求 |
|---|---|---|
Opaque(默认) |
通用敏感值(密码/Token/API Key) | 任意键 |
kubernetes.io/tls |
TLS 证书(Ingress 的 HTTPS) | 固定 kubernetes.io/tls + kubernetes.io/tls |
kubernetes.io/dockerconfigjson |
私有镜像仓库凭据 | 固定 kubernetes.io/dockerconfigjson |
kubernetes.io/service-account-token |
SA 令牌(系统使用) | 自动管理 |
两个"非通用"消费特例(不通过 env/卷,而是被系统直接引用):
- tls 类型 → Ingress 的
spec.tls.secretName(实验 07 Ingress TLS) - dockerconfigjson 类型 → Pod 的
imagePullSecrets(拉私有镜像时用它认证)
kubectl create secret tls my-tls --cert=cert.crt --key=key.key
kubectl create secret docker-registry regcred --docker-server=... --docker-username=... --docker-password=...
8.3.4 消费方式¶
与 ConfigMap 完全相同(卷挂载 / env 注入)+ 上面两个系统级特例。区别只在数据的敏感性——设计上 Secret 更"金贵":只给需要的 Pod 挂,别一把梭。
8.3.5 Secret 的安全边界¶
- RBAC:Secret 的读权限要单独收紧(
get secret就是拿到了全部值——第 11 章) - etcd 静态加密:默认 etcd 里 Secret 明文存储——配 EncryptionConfiguration 落盘加密(实验 09 Lab 9)
- 最小权限:一个 Secret 只给需要的命名空间/应用;定期轮换
- 外部密钥管理(进阶):生产可接 External Secrets(Vault/AWS Secrets Manager),集群里不落明文——知道存在即可
8.4 Downward API:注入"自己是谁"¶
第 4 章 §4.5.4 讲过 Downward API(实验 06 补充实操)。放在本章对比是为了建立完整图景——三种"注入"的界限:
| 注入来源 | 对象 | 典型内容 | 使用 |
|---|---|---|---|
| ConfigMap | 外部配置 | 数据库地址、开关、配置文件 | 应用"要什么" |
| Secret | 外部敏感配置 | 密码、Token、证书 | 应用"凭什么" |
| Downward API | Pod 自身元数据 | Pod 名、命名空间、节点名、labels | 应用"我是谁" |
┌─────────────────────────────────────────┐
│ 应用容器(env / 卷 两种注入通道) │
│ ├─ ConfigMap → 外部配置(第 8 章) │
│ ├─ Secret → 敏感配置(第 8 章) │
│ └─ Downward → 自身元数据(第 4 章) │
└─────────────────────────────────────────┘
判断标准:数据是"环境给的"(CM/Secret)还是"我自己身上的"(Downward)?
8.5 配置管理最佳实践(生产)¶
- 配置全进对象,yaml 零硬编码:Deployment yaml 里不该出现环境相关值(地址/密码/开关)
- 按敏感性分流:非敏感 → ConfigMap;敏感 → Secret(别图省事全放 CM)
- 文件名即配置:配置文件用卷挂载(支持热更新);少量参数用 env
- Secret 最小权限:RBAC 收紧 + etcd 加密 + 定期轮换
- 多环境复用:同一镜像 + 不同命名空间的 CM/Secret = 一套镜像跑 dev/prod
- 修改流程:改 CM(卷方式)→ 应用自动感知(热更新);改 env → 滚动重启 Pod
8.6 实验演练指引¶
本章机制对应实验 06「ConfigMap 和 Secret」(5 Lab + 2 补充):
- Lab 1 文件型 ConfigMap:
--from-file创建、卷挂载进 mysql(配置外部化实例) - Lab 2 键值对 ConfigMap:
--from-literal创建、键变文件 - Lab 3 env 映射 ConfigMap:
configMapKeyRef注入环境变量 - Lab 4 Secret 保存敏感信息:base64 编码、
secretKeyRef注入 mysql 密码——亲手验证"base64 秒还原" - Lab 5 文件型 Secret:整个配置文件封装进 Secret、挂载自动还原明文
- 补充:Secret 类型:tls/dockerconfigjson(Ingress/私有仓库)
- 补充:Downward API:fieldRef env 注入 + downwardAPI 卷(labels/annotations 文件)
教学建议:Lab 1-3 对比记忆"卷 vs env"两种消费;Lab 4 重点体验"编码≠加密";补充小节对应 §8.3.3 与 §8.4。
本章小结¶
- 为什么外部化:镜像不可变、多环境复用、敏感信息不落地——同一镜像跑所有环境
- ConfigMap:非敏感键值对;卷挂载(键变文件、热更新)vs env 注入(一次性、需重启)——读文件用卷、读 env 用 env
- Secret:结构与 CM 同构 + base64;base64 ≠ 加密(安全靠 RBAC + etcd 加密 + 最小权限);四种类型(Opaque/tls/dockerconfigjson/SA-token),后两种是系统级消费特例
- Downward API:注入"自己是谁"(与外部配置界限分明)
- 生产实践:配置全进对象、按敏感性分流、Secret 最小权限、多环境复用
衔接:第 9 章讲网络(Service/Ingress)——tls 类型的 Secret 就是 Ingress HTTPS 的原料;第 11 章 RBAC 会给 Secret 的访问控制提供机制。
思考题¶
- 为什么"改 env 注入的配置"要重启 Pod,而"改卷挂载的配置"不用?(提示:进程启动时注入 vs 文件系统挂载)
kubectl get secret xxx -o yaml里能看到密码吗?怎么防?(提示:编码 vs 加密)- 私有镜像仓库的凭据用什么 Secret 类型?Pod 怎么用它?
- 数据库密码、日志级别、Pod 所在节点名,分别应该用 CM/Secret/Downward 哪个?
- 为什么 Secret 的"值"要 base64 编码?ConfigMap 为什么不用?
CKA 考点标注(对应域 1/2): - 必考操作:
kubectl create configmap/secret(--from-literal/--from-file)、kubectl create configmap/secret(base64 解码) - 必考配置:configMapKeyRef/secretKeyRef(env)、configMap/secret 卷(文件)、imagePullSecrets、kubectl create secret tls/docker-registry- 必考认知:卷挂载热更新 vs env 需重启、base64 是编码不是加密 - 排障关联(域 5):secret "xxx" not found(引用名错/命名空间错)、env 没生效(改了没重启)