前置回顾
前面五篇解决了"应用怎么部署,怎么访问"的问题.但还有一个基本问题没有涉及: 应用怎么拿到自己的配置?
数据库地址,API Key,功能开关,日志级别...这些不应该硬编码在镜像里(否则改个配置就要重新构建镜像).
本篇讲解 K8s 原生的配置管理方案:ConfigMap(非敏感)和 Secret(敏感),以及它们与实践中更常用的 Nacos,Apollo,Rainbow等远程配置平台的异同.
配置外置的动机
|
这正是十二要素应用(12-Factor App) 的第三条原则:"在环境中存储配置".K8s 的 ConfigMap 和 Secret 就是这个原则的落地实现.
ConfigMap - 非敏感配置
ConfigMap 存储非敏感的键值对或文件内容.它本质上就是一个 map[string]string,存在 etcd 中.
创建方式
|
|
使用方式
ConfigMap 有两种注入 Pod 的方式:环境变量和文件挂载.
|
|
subPath 的陷阱:不支持热更新
使用 subPath 挂载单个文件时,ConfigMap 更新后容器内的文件不会自动更新(因为 subPath 用的是绑定挂载,不走 kubelet 的 symlink 机制).
如果需要热更新,应该挂载整个目录而不是单个文件.
环境变量 vs 文件挂载:怎么选
| 维度 | 环境变量 | 文件挂载 |
|---|---|---|
| 适合 | 简单键值对(DB_HOST,LOG_LEVEL) | 结构化配置文件(nginx.conf,application.yaml) |
| 热更新 | 不支持(Pod 启动时注入,之后不变) | 支持(kubelet 定期同步,延迟约 30-60s) |
| 可见性 | env 命令可查看 |
文件系统中可读 |
| 大小 | 适合短值 | 支持整个配置文件(ConfigMap 上限 1MB) |
Secret - 敏感配置
Secret 的 API 和 ConfigMap 几乎一模一样,核心区别是语义上的:Secret 用于存储密码,Token,证书等敏感数据.
创建方式
|
|
Base64 不是加密
Secret 的 data 字段用 Base64 编码,但这不是加密: 任何人拿到 Secret 的 YAML 都能直接 echo YWRtaW4= | base64 -d 解出明文.
Base64 只是为了安全地传输二进制数据(如证书),不提供保密性.
Secret 的真正安全性依赖 RBAC 权限控制 + etcd 加密(后文详述).
使用方式
和 ConfigMap 完全一致--环境变量或文件挂载:
|
Secret 类型
| 类型 | 用途 | data 中的键 |
|---|---|---|
Opaque |
通用(默认) | 任意 |
kubernetes.io/tls |
TLS 证书 | tls.crt + tls.key |
kubernetes.io/dockerconfigjson |
镜像拉取凭证 | .dockerconfigjson |
kubernetes.io/basic-auth |
HTTP Basic Auth | username + password |
kubernetes.io/ssh-auth |
SSH 私钥 | ssh-privatekey |
热更新机制与限制
ConfigMap / Secret 更新后,Pod 内看到的值什么时候变?
|
文件更新了 ≠ 应用感知到了
kubelet 更新挂载文件后,应用进程不会收到任何信号.应用需要自己实现 watch 机制(如 fsnotify)或定期重读配置文件.
很多框架(如 Spring Cloud,Viper)内置了文件热加载能力,但默认可能未开启.如果应用不支持动态重载,最简单的方式还是 kubectl rollout restart deployment/xxx 触发 Pod 重建.
不可变 ConfigMap / Secret
对于确定不需要修改的配置(如版本信息,固定参数),可以标记为不可变:
|
好处:kubelet 不再定期同步该 ConfigMap 的变化 → 减少 API Server 压力(大规模集群有意义).
etcd 静态加密
默认情况下,Secret 在 etcd 中是明文存储的(只做了 Base64 编码).任何能直接读 etcd 的人都能拿到所有密钥.
|
|
托管集群通常已开启
如果你使用云厂商的托管 K8s(GKE,EKS,TKE),etcd 加密通常已经默认开启,密钥由云平台的 KMS 管理, 你不需要手动配置.自建集群(kubeadm)则需要自己搞定.
Secret 安全最佳实践
| 实践 | 做法 |
|---|---|
| RBAC 最小权限 | 只允许需要的 ServiceAccount 读取特定 Secret;禁止 list/watch 全部 Secret |
| 不提交到 Git | Secret YAML 不纳入版本库;使用 SealedSecrets 或 External Secrets Operator 加密后才提交 |
| 开启 etcd 加密 | 自建集群必须配置 Encryption at Rest |
| 限制 Secret 挂载 | Pod 的 ServiceAccount 只能访问明确绑定的 Secret;开启 BoundServiceAccountTokenVolume |
| 定期轮换 | 数据库密码,API Key 定期更新;配合 External Secrets Operator 自动同步 |
| 审计日志 | 开启 K8s Audit Log,追踪谁读取了哪个 Secret |
外部密钥管理:Vault 集成
K8s 原生 Secret 的根本问题:密钥存在 etcd 里,而 etcd 由 K8s 集群管理.集群被攻破,密钥全部泄露.
外部密钥管理的思路是:密钥不存在 K8s 内部,运行时从外部系统动态获取.
HashiCorp Vault 模式
|
两种常见的集成方式:
| 方式 | 原理 | 适用 |
|---|---|---|
| Vault Agent Sidecar | 每个 Pod 注入一个 Vault Agent 容器,启动时向 Vault 认证并获取密钥写入共享卷 | 需要动态轮换,细粒度控制 |
| External Secrets Operator | 一个集群级 Controller,从 Vault / AWS Secrets Manager 等外部源同步到 K8s Secret 对象 | 想保持 K8s 原生用法(Pod 仍然读 Secret),但密钥源头在外部 |
|
与远程配置平台的对比
企业实际生产中,配置管理通常不止 K8s ConfigMap 一种方案.Nacos,Apollo,七彩石(Rainbow)等远程配置中心也是常见选择.它们和 K8s ConfigMap/Secret 解决的问题有重叠但不完全相同.
能力对比
| 维度 | K8s ConfigMap/Secret | 远程配置平台(Nacos / Apollo / 七彩石) |
|---|---|---|
| 定位 | 容器编排层面的配置注入 | 应用层面的配置管理中心 |
| 热更新 | Volume 挂载延迟 30s~2min;应用需自行 watch 文件变化 | 实时推送(长轮询/WebSocket),SDK 内置监听回调,秒级生效 |
| 版本管理 | 无内置版本(需要 Git + GitOps 实现) | 内置版本历史,对比,回滚 |
| 灰度发布 | 不支持(ConfigMap 更新立即影响所有引用的 Pod) | 支持按 IP / 集群 / 环境 / 百分比灰度推送 |
| 权限控制 | K8s RBAC(namespace 级别) | 独立的权限体系(项目/环境/审批流) |
| 可视化 | kubectl 或 Dashboard | 完整 Web UI(编辑,搜索,批量操作) |
| 多环境 | 每个环境一个 namespace + 独立 ConfigMap | 内置环境隔离(dev / staging / prod) |
| 监听通知 | 无原生推送机制 | SDK 提供 listener/watcher 回调 |
| 依赖 | 仅依赖 K8s 集群本身 | 需要额外部署和维护配置中心集群 |
| 适用范围 | 容器化应用 | 任何应用(容器/VM/物理机),不依赖 K8s |
什么时候用哪个
|
各平台特色
| 平台 | 特色 | 典型用法 |
|---|---|---|
| Nacos | 配置管理 + 服务发现一体化;长轮询实时推送;多数据中心 | Spring Cloud 生态;Java 微服务 |
| Apollo | 强权限管理 + 审批流;namespace 隔离清晰;变更灰度发布 | 大型团队多环境管理;配置变更需审批 |
| Rainbow | 深度集成腾讯内部基础设施;支持配置模板 + 分组灰度推送 | 腾讯内部服务;结合 trpc 框架使用 |
| etcd / Consul | 轻量级 KV 存储 + watch 机制;无 Web UI(需自建) | 基础设施级配置;服务发现 |
ConfigMap 和配置平台不是二选一
生产环境几乎都是两者并用:
- ConfigMap 负责"让应用能启动起来"(连接地址,证书,环境标识),配置平台负责"让应用在运行时能灵活调整行为".
- 前者是基础设施层面的配置注入,后者是应用层面的动态配置管理.
生产环境常见模式
GitOps 管理 ConfigMap
|
利用 annotation 触发滚动更新
ConfigMap 变了但 Pod 不重启(因为 Deployment 的 template 没变).一个常用技巧:
|
如果不用 Helm,也可以手动:
|
小结
快速回顾
- 配置外置原则:配置与镜像分离,同一镜像 + 不同配置 = 不同环境
- ConfigMap:非敏感键值对/文件,注入方式为环境变量或 Volume 挂载
- Secret:敏感数据(密码,证书),API 和 ConfigMap 相同;Base64 不是加密
- 热更新:环境变量和 subPath 挂载不会热更新;Volume 挂载(无 subPath)延迟 30s~2min 自动更新,但应用需自行感知文件变化
- 安全:RBAC 最小权限 + etcd 加密 + 不提交 Secret 到 Git + 定期轮换
- 外部密钥管理:Vault / External Secrets Operator 实现密钥不存在 K8s 内部
- 与配置平台的关系:ConfigMap 负责基础设施配置(启动时确定),Nacos/Apollo/七彩石负责业务配置(运行时动态调整),两者互补不互斥
- 生产模式:GitOps 管理 ConfigMap 变更 + annotation hash 触发滚动更新
动手练习
- 创建一个 ConfigMap(含 LOG_LEVEL=debug 和一个 nginx.conf 文件),分别用 envFrom 和 volumeMount 注入到 Pod 中.exec 进 Pod 验证环境变量和文件内容.
- 修改 ConfigMap 的值(如 LOG_LEVEL=info),观察:使用 envFrom 的 Pod 是否变化?使用 Volume 挂载的 Pod 文件是否更新?(等 1-2 分钟后检查)
- 创建一个 Secret(用 stringData 写明文),然后
kubectl get secret xxx -o yaml查看存储格式.尝试用base64 -d解码验证. - 尝试使用 subPath 挂载 ConfigMap 中的单个文件,修改 ConfigMap 后验证文件是否更新(预期不会更新).
- 将 Deployment 的 annotation 加上 ConfigMap 的 hash,修改 ConfigMap 并重新 apply Deployment,观察是否触发滚动更新.
- (进阶)安装 External Secrets Operator,配置一个 SecretStore 指向本地 Vault(dev 模式),创建 ExternalSecret 并验证 K8s Secret 是否自动生成.