ConfigMap 与 Secret:配置外置与密钥安全
概述#
在 Docker 时代,我们习惯把配置写进镜像(或通过环境变量传入)。到了 Kubernetes,这种做法的局限性就暴露了:
- 配置和镜像耦合:改一个配置就要重新构建镜像、重新部署
- 敏感信息泄露风险:把数据库密码写进 Dockerfile 或环境变量,任何能访问镜像的人都能看到
- 多环境配置管理混乱:dev/staging/prod 的配置不同,但没有统一的管理机制
Kubernetes 提供了两个原语解决这个问题:
- ConfigMap:存非敏感配置(如日志级别、服务端口、配置文件内容)
- Secret:存敏感数据(如数据库密码、API Key、TLS 证书)
核心思想:配置外置(Twelve-Factor App 原则),把"会变化的东西"(配置)和"不变的东西"(镜像)分开管理。
ConfigMap 基础:从创建到使用#
创建方式一:从字面值创建#
# 创建包含单个键值对的 ConfigMap
kubectl create configmap app-config \
--from-literal=LOG_LEVEL=info \
--from-literal=SERVER_PORT=8080 \
--namespace production等效 YAML:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: production
data:
LOG_LEVEL: "info"
SERVER_PORT: "8080"创建方式二:从文件创建#
# 从单个文件创建(键名 = 文件名,值 = 文件内容)
kubectl create configmap app-properties \
--from-file=app.properties \
--namespace production
# 从多个文件创建
kubectl create configmap app-config \
--from-file=config.json \
--from-file=app.properties \
--namespace production
# 指定键名
kubectl create configmap app-config \
--from-file=my-key=app.properties \
--namespace production示例:假设 app.properties 内容是:
database.host=db-svc
database.port=5432
log.level=info创建后,ConfigMap 的内容是:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-properties
namespace: production
data:
app.properties: |
database.host=db-svc
database.port=5432
log.level=info创建方式三:从目录创建#
目录结构:
config-dir/
├── log-level
├── server-port
└── app.yaml每个文件的内容会成为 ConfigMap 的一个键:
kubectl create configmap app-config \
--from-file=./config-dir \
--namespace production使用方式一:作为环境变量注入#
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
template:
spec:
containers:
- name: app
image: myapp:v1.0
env:
# 方式 1:逐个指定
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: app-config
key: LOG_LEVEL
- name: SERVER_PORT
valueFrom:
configMapKeyRef:
name: app-config
key: SERVER_PORT
# 方式 2:批量注入(envFrom 与 env 平级,不是 env 列表里的一项)
envFrom:
- configMapRef:
name: app-config特点:
- ✅ 简单直观,应用代码不需要改(直接读环境变量)
- ❌ Pod 启动后就固定了,ConfigMap 更新后不会自动同步到已运行的 Pod
- ❌ 如果需要更新,必须重启 Pod(如
kubectl rollout restart deployment/my-app)
使用方式二:作为文件挂载#
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
template:
spec:
containers:
- name: app
image: myapp:v1.0
volumeMounts:
- name: config-volume
mountPath: /etc/config
readOnly: true
volumes:
- name: config-volume
configMap:
name: app-config
# 可选:指定默认权限
defaultMode: 0644挂载后的文件路径:
/etc/config/LOG_LEVEL ← 内容是 "info"
/etc/config/SERVER_PORT ← 内容是 "8080"如果 ConfigMap 包含 app.properties 键(文件内容),挂载后是:
/etc/config/app.properties ← 内容是完整的配置文件特点:
- ✅ ConfigMap 更新后,文件会自动刷新(kubelet 定期同步,通常有几秒延迟)
- ✅ 应用可以监听文件变化并热加载配置(如 Nginx 的
nginx -s reload) - ⚠️ 如果使用了
subPath,自动更新会失效(后文详解)
Secret:Base64 不是加密!#
Base64 的本质:编码,不是加密#
新手最容易犯的错误:以为 data 字段里的 base64 字符串是"加密"的。
apiVersion: v1
kind: Secret
metadata:
name: db-secret
type: Opaque
data:
password: bXlwYXNzd29yZDEyMzA= # echo -n 'mypassword1230' | base64真相:bXlwYXNzd29yZDEyMzA= 任何人都能用 echo bXlwYXNzd29yZDEyMzA= | base64 -d 还原成明文。
Base64 只是编码(为了能在 YAML/JSON 里存二进制数据),不是加密!
Secret 的常用类型#
| 类型 | 用途 | data 中的键 |
|---|---|---|
| Opaque(默认) | 用户自定义的任意 Secret | 自定义 |
| kubernetes.io/tls | 存储 TLS 证书和私钥 | tls.crt、tls.key |
| kubernetes.io/basic-auth | 存储 HTTP Basic Auth 的用户名密码 | username、password |
| kubernetes.io/dockerconfigjson | 存储镜像仓库的认证信息 | .dockerconfigjson |
| kubernetes.io/ssh-auth | 存储 SSH 密钥 | ssh-privatekey |
创建 Secret 示例#
Opaque 类型(最常用):
# 从字面值创建
kubectl create secret generic db-secret \
--from-literal=DB_PASSWORD=mypassword1230 \
--namespace production
# 从文件创建
kubectl create secret generic tls-secret \
--from-file=tls.crt=server.crt \
--from-file=tls.key=server.key \
--namespace productionTLS 类型(用于存储证书):
kubectl create secret tls app-tls \
--cert=server.crt \
--key=server.key \
--namespace production等效 YAML:
apiVersion: v1
kind: Secret
metadata:
name: app-tls
type: kubernetes.io/tls
data:
tls.crt: <base64 编码的证书>
tls.key: <base64 编码的私钥>在 Pod 中使用 Secret#
作为文件挂载(推荐):
spec:
containers:
- name: app
image: myapp:v1.0
volumeMounts:
- name: secret-volume
mountPath: /etc/secrets
readOnly: true
volumes:
- name: secret-volume
secret:
secretName: db-secret
defaultMode: 0400 # 只读权限挂载后:
/etc/secrets/DB_PASSWORD ← 内容是 "mypassword1230"作为环境变量注入(不推荐敏感信息):
spec:
containers:
- name: app
image: myapp:v1.0
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: DB_PASSWORD安全提醒:环境变量可能被
printenv、/proc/<pid>/environ泄露,敏感信息优先用文件挂载。
挂载方式对比:文件挂载 vs 环境变量注入#
这是生产环境经常讨论的问题——配置应该怎么传给应用?
对比表#
| 对比项 | 文件挂载(volumeMount) | 环境变量注入(envFrom) |
|---|---|---|
| 更新同步 | ✅ 自动同步(ConfigMap/Secret 更新后,文件内容会刷新) | ❌ 不同步(Pod 启动时固定) |
| 热加载支持 | ✅ 应用可以监听文件变化(inotify) | ❌ 不支持 |
| 敏感信息安全 | ✅ 可以设置 Unix 权限(defaultMode: 0400) | ❌ 可能被 printenv 泄露 |
| 应用改造 | ⚠️ 需要应用读配置文件(而非环境变量) | ✅ 无需改造(直接读环境变量) |
| subPath 影响 | ⚠️ 使用 subPath 会禁用自动更新 | N/A |
| 适用场景 | 配置文件、TLS 证书、需要热加载的配置 | 简单的键值对、应用已支持环境变量配置 |
文件挂载的自动更新机制#
ConfigMap/Secret 以文件挂载时,kubelet 会定期同步(默认每 60 秒检查一次)。
volumes:
- name: config-volume
configMap:
name: app-config
# 可选:设置同步周期(通过 kubelet 的 --sync-frequency 参数)应用如何感知配置变化?
- 应用内监听文件变化(推荐):用
inotify监听配置文件,变化后热加载 - 通过信号触发重载:修改 ConfigMap → 触发 Pod 注解变更 → 用 Sidecar 发送信号给主容器
- 主动重启(简单粗暴):
kubectl rollout restart deployment/my-app
subPath 的坑#
如果你想把 ConfigMap 中的单个键挂载为文件(而不是整个 ConfigMap 挂载为一个目录),会用 subPath:
volumeMounts:
- name: config-volume
mountPath: /etc/app/config.properties
subPath: app.properties # 只挂载这个键问题:使用 subPath 后,ConfigMap 更新不会自动同步到文件!
解决办法:
- 不用
subPath,直接挂载整个 ConfigMap 为一个目录 - 或者用 reloader(https://github.com/stakater/Reloader)监听 ConfigMap 变化并自动重启 Pod
外部 Secret 方案:Vault + External Secrets Operator#
前边讲的 Kubernetes 原生 Secret 有个问题:值以 base64 明文存在 etcd 里(Kubernetes 默认并不对 etcd 做静态加密,必须手动配置 EncryptionConfiguration 才有),而且:
- 访问权限管理复杂(任何有
get secrets权限的人都能读) - 没有审计日志(谁访问了哪个 Secret?)
- 没有自动轮换能力(密码过期了要手动更新)
- 容易泄露进 Git(有人把 Secret 写进 Helm values 然后推到 GitHub)
生产环境推荐方案:用 HashiCorp Vault 存储真正的密钥,然后用 External Secrets Operator(ESO)把 Vault 里的密钥同步成 Kubernetes Secret。
Vault(真正的密钥存储)
↓ ESO 定期同步
Kubernetes Secret(应用还是读这个,无感知)
↓
Pod一句话了解:
- Vault:专业的密钥管理系统,支持密钥轮换、审计日志、动态凭证(每次请求生成临时数据库密码)
- External Secrets Operator:Kubernetes 控制器,把外部密钥系统(Vault、AWS Secrets Manager、GCP Secret Manager)的密钥同步成 K8s Secret
详细配置和部署是进阶内容,新手先掌握 Kubernetes 原生的 ConfigMap/Secret,生产环境再引入 Vault + ESO。
小结#
本章我们学习了 Kubernetes 的配置和密钥管理:
ConfigMap:存非敏感配置,支持从字面值、文件、目录创建。有两种使用方式:
- 文件挂载:ConfigMap 更新后会自动同步到 Pod 内文件(热加载友好)
- 环境变量注入:Pod 启动后固定,需要重启才能更新
Secret:存敏感数据,Base64 是编码不是加密!常用类型有
Opaque(自定义)、kubernetes.io/tls(证书)、kubernetes.io/dockerconfigjson(镜像仓库认证)。挂载方式选择:
- 配置文件、TLS 证书 → 用文件挂载(
volumeMount) - 简单键值对、应用已支持环境变量 → 用环境变量注入(
envFrom) - 敏感信息 → 务必用文件挂载,避免环境变量泄露
- 配置文件、TLS 证书 → 用文件挂载(
外部 Secret 方案:生产环境推荐用 Vault + External Secrets Operator,实现密钥的安全存储、审计、自动轮换。
下一步:我们学会了怎么管理配置和密钥,但还有一个问题——数据持久化。下章我们将深入学习 存储 PV/PVC,搞清楚容器里的数据怎么保存到集群外的存储系统。
实战要点:
- 千万别把 Secret 推进 Git!即使用了 base64,任何人都能解码
- ConfigMap/Secret 有大小限制(受 etcd 对象大小约束,单个约 1MiB,非某版本引入),超大配置文件考虑用外部存储(如 AWS S3、NFS)
- 文件挂载自动更新有延迟(默认 60 秒),对配置实时性要求高的场景要评估
- 使用 subPath 会禁用自动更新!需要热加载的配置避免用 subPath
- 生产环境务必启用 etcd 加密(
EncryptionConfiguration),防止 etcd 备份泄露导致密钥暴露