一个成熟的 Kubernetes 应用,镜像里应该尽量只放“程序本体”,而不是把所有环境配置、账号密码、证书文件都直接烤进镜像。否则,同一份应用在开发、测试、预发、生产之间就很难复用,配置一改就得重新构建镜像,敏感信息也更容易扩散。ConfigMap 和 Secret,正是为了解决这个问题而生。::cite[312]::cite[400]
简单说:
- ConfigMap:放非敏感配置。
- Secret:放敏感信息。::cite[312]::cite[400]
这一章我们会重点讲:为什么配置要与镜像解耦、ConfigMap 和 Secret 的创建与注入方式、配置更新后工作负载会发生什么、以及在生产环境里比较推荐的实践方式。::cite[312]::cite[400]
2.4.1 为什么配置需要与镜像解耦
官方文档明确指出,ConfigMap 的目的之一,就是把环境相关配置与容器镜像分离开,让应用更容易在不同环境中迁移与复用。::cite[312]
如果不解耦,会遇到什么问题?
假设你有一个应用,需要以下配置:
- 数据库地址
- Redis 地址
- 日志级别
- 功能开关
- 第三方 API Token
如果这些内容都直接写死在镜像里,会产生几个明显问题:
- 开发环境和生产环境不能共用一份镜像。
- 配置改动要重新构建、重新推送、重新部署镜像。
- 敏感信息容易跟随镜像分发到不该去的地方。
- 配置与代码耦合,排查问题和回滚都更麻烦。
正确的分工方式
更推荐的做法是:
| 内容类型 | 推荐存放位置 |
|---|---|
| 程序代码、依赖、运行时 | 容器镜像 |
| 非敏感配置 | ConfigMap |
| 密码、Token、证书、密钥 | Secret |
这样一来,镜像可以保持稳定,环境差异由配置对象承载。::cite[312]::cite[400]
2.4.2 ConfigMap:保存非敏感配置
ConfigMap 是一个以键值对形式保存非敏感数据的 API 对象。Pod 可以用几种常见方式消费它:
- 环境变量
- 启动命令参数
- 配置文件卷挂载
- 应用直接调用 Kubernetes API 读取::cite[312]
基础示例:创建一个 ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
APP_NAME: demo-app
LOG_LEVEL: info
application.yaml: |
server:
port: 8080
feature:
enableSignup: true
创建方式 1:通过 YAML
kubectl apply -f app-config.yaml
创建方式 2:通过命令行
kubectl create configmap app-config \
--from-literal=APP_NAME=demo-app \
--from-literal=LOG_LEVEL=info
ConfigMap 的几个关键特性
- 适合存储 非敏感 数据。
- 大小不能超过 1 MiB。
- 同一个命名空间下的 Pod 才能引用它。::cite[312]
2.4.3 ConfigMap 的创建与注入方式
方式 1:按环境变量注入单个键
apiVersion: v1
kind: Pod
metadata:
name: env-single-configmap-demo
spec:
containers:
- name: app
image: busybox:1.36
command:
- /bin/sh
- -c
- env | grep LOG_LEVEL && sleep 3600
env:
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: app-config
key: LOG_LEVEL
方式 2:通过 envFrom 一次性注入全部键
apiVersion: v1
kind: Pod
metadata:
name: envfrom-configmap-demo
spec:
containers:
- name: app
image: busybox:1.36
command:
- /bin/sh
- -c
- env && sleep 3600
envFrom:
- configMapRef:
name: app-config
这种方式简单直接,但要注意:ConfigMap 中的键名必须适合作为环境变量名,否则对应项不会被注入。::cite[312]
方式 3:挂载成配置文件
这是最常见、也最贴近生产环境的方式之一。
apiVersion: v1
kind: Pod
metadata:
name: volume-configmap-demo
spec:
containers:
- name: app
image: busybox:1.36
command:
- /bin/sh
- -c
- cat /etc/config/application.yaml && sleep 3600
volumeMounts:
- name: config-volume
mountPath: /etc/config
readOnly: true
volumes:
- name: config-volume
configMap:
name: app-config
items:
- key: application.yaml
path: application.yaml
如果你省略 items,ConfigMap 中的每个键都会变成一个文件。::cite[312]
2.4.4 Secret:保存敏感信息
Secret 与 ConfigMap 很像,但语义上明确用于保存敏感数据,例如:
- 密码
- Token
- API Key
- TLS 证书
- 私有镜像仓库凭据::cite[400]
官方也特别提醒:Secret 的价值之一,在于你不需要把机密信息写进 Pod 规范或镜像里。::cite[400]
但要注意一个常见误区
Secret 不是“自动安全”。官方明确指出,默认情况下 Secret 存在 API Server 背后的数据存储中时并不是天然强加密的,因此生产环境至少要考虑:
- 开启静态加密(Encryption at Rest)
- 配置最小权限 RBAC
- 限制哪些 Pod 能拿到哪些 Secret
- 必要时接入外部 Secret 管理方案::cite[400]
一个基础 Secret 示例
apiVersion: v1
kind: Secret
metadata:
name: app-secret
type: Opaque
stringData:
DB_USERNAME: demo_user
DB_PASSWORD: super-secret-password
API_TOKEN: token-123456
这里使用 stringData,写起来更直观;Kubernetes 会在内部转换为 data。::cite[400]
命令行创建 Secret
kubectl create secret generic app-secret \
--from-literal=DB_USERNAME=demo_user \
--from-literal=DB_PASSWORD=super-secret-password
2.4.5 Secret 的创建、挂载与环境变量注入
方式 1:按环境变量注入单个 Secret 键
apiVersion: v1
kind: Pod
metadata:
name: secret-env-demo
spec:
containers:
- name: app
image: busybox:1.36
command:
- /bin/sh
- -c
- env | grep DB_ && sleep 3600
env:
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: app-secret
key: DB_USERNAME
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: app-secret
key: DB_PASSWORD
方式 2:挂载为文件
apiVersion: v1
kind: Pod
metadata:
name: secret-volume-demo
spec:
containers:
- name: app
image: busybox:1.36
command:
- /bin/sh
- -c
- ls -la /etc/secret && cat /etc/secret/DB_USERNAME && sleep 3600
volumeMounts:
- name: secret-volume
mountPath: /etc/secret
readOnly: true
volumes:
- name: secret-volume
secret:
secretName: app-secret
方式 3:作为镜像拉取凭据
如果需要从私有仓库拉镜像,还可以把 Secret 用作 imagePullSecrets。这也是 Secret 很常见的使用场景。::cite[400]
2.4.6 综合实战:ConfigMap + Secret 注入到 Deployment
下面给出一个完整示例,模拟一个 Web 应用:
- 非敏感配置放到 ConfigMap
- 敏感信息放到 Secret
- 应用通过环境变量和挂载文件两种方式读取配置
apiVersion: v1
kind: ConfigMap
metadata:
name: web-config
data:
APP_ENV: production
LOG_LEVEL: info
app.properties: |
app.name=demo-web
feature.signup=true
---
apiVersion: v1
kind: Secret
metadata:
name: web-secret
type: Opaque
stringData:
DB_USERNAME: demo_user
DB_PASSWORD: change-me-please
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
labels:
app: web-app
spec:
replicas: 2
selector:
matchLabels:
app: web-app
template:
metadata:
labels:
app: web-app
spec:
containers:
- name: app
image: nginx:1.27
ports:
- containerPort: 80
env:
- name: APP_ENV
valueFrom:
configMapKeyRef:
name: web-config
key: APP_ENV
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: web-config
key: LOG_LEVEL
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: web-secret
key: DB_USERNAME
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: web-secret
key: DB_PASSWORD
volumeMounts:
- name: config-volume
mountPath: /etc/app-config
readOnly: true
- name: secret-volume
mountPath: /etc/app-secret
readOnly: true
volumes:
- name: config-volume
configMap:
name: web-config
items:
- key: app.properties
path: app.properties
- name: secret-volume
secret:
secretName: web-secret
实操步骤
- 创建资源。
kubectl apply -f config-secret-demo.yaml
- 查看对象。
kubectl get configmap
kubectl get secret
kubectl get deploy,pod
- 进入 Pod 检查环境变量。
kubectl exec -it deploy/web-app -- printenv | grep -E 'APP_ENV|LOG_LEVEL|DB_USERNAME'
- 查看挂载文件。
kubectl exec -it deploy/web-app -- sh -c 'ls -l /etc/app-config /etc/app-secret && cat /etc/app-config/app.properties'
2.4.7 配置更新对工作负载的影响
这是线上非常重要的一部分。
ConfigMap 更新后会怎样?
如果 ConfigMap 是以 卷挂载 的方式被使用,更新后内容会以“最终一致”的方式反映到 Pod 中。换句话说,不是瞬间更新,但 kubelet 会在同步周期和缓存传播完成后把新内容投射进去。::cite[312]
但如果 ConfigMap 是以 环境变量 的方式注入,官方明确说明:不会自动更新,通常需要重启 Pod。::cite[312]
Secret 更新后会怎样?
Secret 也类似:
- 卷挂载方式:更新后会最终一致地反映到 Pod 文件中。
- 环境变量方式:不会自动刷新到进程环境变量里,通常需要重建 Pod。::cite[400]
一个特别容易忽略的坑:subPath
无论是 ConfigMap 还是 Secret,如果你通过 subPath 挂载单个文件,后续对象更新都不会自动同步到容器里。::cite[312]::cite[400]
2.4.8 最佳实践
1)ConfigMap 与 Secret 严格分工
- 非敏感配置放 ConfigMap
- 敏感信息放 Secret
不要为了省事把密码放进 ConfigMap。官方也明确强调,ConfigMap 不提供保密能力。::cite[312]
2)尽量优先考虑“挂载文件”而不是“环境变量”
原因很实际:
- 卷挂载更容易承接配置热更新
- 大段配置文件更自然
- 避免环境变量过多导致管理混乱
3)变更后用滚动重启确保生效
如果你的应用通过环境变量读取配置,更新 ConfigMap / Secret 后推荐主动执行:
kubectl rollout restart deployment/web-app
4)对不常改的配置考虑使用 immutable
ConfigMap 和 Secret 都支持 immutable: true。这样可以避免误改,也能减轻集群控制面的 watch 压力。::cite[312]::cite[400]
示例:
apiVersion: v1
kind: ConfigMap
metadata:
name: immutable-app-config
data:
LOG_LEVEL: info
immutable: true
apiVersion: v1
kind: Secret
metadata:
name: immutable-app-secret
type: Opaque
stringData:
TOKEN: abc123
immutable: true
5)Secret 访问要最小权限
Secret 的风险不在“有没有加密成 base64”,而在“谁能读到它”。官方建议至少配合最小权限 RBAC 使用,并限制可以创建 Pod 的主体,因为能创建 Pod 的人往往就有办法把同命名空间里的 Secret 挂出来。::cite[400]
6)避免把大量二进制或超大配置塞进 ConfigMap / Secret
单个 ConfigMap 和 Secret 都有限制,且它们不是大文件分发系统。体积大时,应考虑对象存储、持久卷、外部配置中心或专门的 Secret 管理系统。::cite[312]::cite[400]
2.4.9 一个推荐的实际落地思路
在多数业务中,可以按下面的方式组织:
| 类型 | 建议方式 |
|---|---|
| 应用常规参数 | ConfigMap |
| 敏感账号密码 | Secret |
| 大段应用配置文件 | ConfigMap 卷挂载 |
| 证书 / 私钥 | Secret 卷挂载 |
| 更新后需立即生效 | 结合滚动重启或应用内热加载 |
如果你团队已经有配置中心或外部密钥管理系统,也可以让 Kubernetes 中的工作负载通过 Sidecar、CSI Driver 或 Operator 与这些外部系统集成,但底层原则依旧没变:镜像只放程序,配置与密钥在运行时注入。
2.4.10 本章小结
这一章要真正掌握的核心有六点:
- 配置必须与镜像解耦。
- ConfigMap 用于非敏感配置,Secret 用于敏感信息。
- 两者都可以通过环境变量和卷挂载注入 Pod。
- 卷挂载更新通常能最终一致地反映到容器内,环境变量不会自动刷新。
subPath挂载不会自动跟随更新。- 生产环境下要关注 immutable、RBAC、Secret 最小权限与变更发布流程。
只要把这部分做好,你的应用发布会明显更稳:
- 环境切换更轻松
- 配置变更更可控
- 敏感数据管理更清晰
- 排错时“代码问题”和“配置问题”的边界也更明确
📝 版权声明:本文为原创技术博客,转载请注明出处。
如文章中存在错误或不准确之处,欢迎在评论区指正,感谢您的阅读与支持!