前言: 昨天把图片预览接口的画质参数交给 ConfigMap 后,我顺手想把数据库密码也塞进去,省得再改镜像。可查了 Kubernetes 官方安全建议才发现,普通参数和凭证不该混在一起;更尴尬的是,把密码写进 Pod 配置一样会留下明文。于是我想弄清楚:
text
1. 没有 Secret 时,密码通常被放在哪里,会留下什么麻烦?
2. 用了 Secret 后,密码是不是就一定安全了?
这篇只看一个测试用的假密码,弄清 Secret 怎样把凭证交给 Pod,不讨论外部密钥系统。
一. 画质能放进配置对象,密码怎么办
-
图片预览接口要连数据库。最省事的写法,是把连接密码放进镜像中的配置文件。开发环境可以跑,等测试和生产的密码不一样时,同一份程序又得分别打包。更糟的是,拿到镜像的人也可能拿到这份密码。
-
我第一反应是照搬上一篇:把
DB_PASSWORD放在 ConfigMap ,由容器读取。这样至少不用重新打镜像。但密码仍然是普通配置,能读取该对象的人就能看到它。把值直接填在 Pod 的env.value里也不舒服:工作负载清单会跟着带上密码。

这张图重点看密码的落点:我们想让程序拿到它,却不想让镜像或工作负载清单带着值到处走。
二. 从"直接写进去"到"只写引用"
-
最粗暴的方案是镜像里放密码,打包时就固定了。稍好一点是在 Pod 模板里写环境变量,镜像能够复用,但配置清单依然含有明文。把密码换个位置,不等于解决了传播范围的问题。
-
官方把非机密数据交给 ConfigMap ,而把密码、令牌这类敏感数据交给 Secret 。工作负载配置只写"我要读取哪个 Secret 的哪个键",值放在另一个对象里。这样至少能把程序交付物、工作负载模板与凭证分开管理。
| 做法 | 密码放在哪里 | 改密码时要动哪里 |
|---|---|---|
| 写入镜像 | 镜像文件 | 重建并重新发布镜像 |
| 写入 Pod 模板 | 工作负载清单 | 修改模板并重建 Pod |
| 使用 Secret | 独立的 Secret 对象 | 更新凭证,并确认应用读取方式 |

这里的变化不是"密码消失了",而是 Pod 模板只保留引用,凭证的管理入口变得独立。
三. 把 Secret 当成单独交接的钥匙
-
如果把镜像想成一台已经装好的咖啡机,上一篇的画质参数是档位卡,数据库密码则更像仓库钥匙。换档位可以公开讨论,钥匙却需要单独保管,只有负责开门的人拿到。对应到这里:镜像是程序,ConfigMap 是普通参数,Secret 是要交给指定容器的凭证。
-
注意,这个类比只说明用途和交接范围 。它没有承诺钥匙进了 Secret 就会自动加密、自动轮换,或者阻止拿到钥匙的程序把它打印到日志里。官方安全实践明确提醒:Secret 的
data使用 Base64 编码,编码不是加密;底层 etcd 默认也不会替它加密存储,需要管理员配置静态加密和访问权限。

这张图重点看"谁需要读取":普通参数和数据库凭证都可以独立于镜像,但凭证还要限制读取范围。
四. 用一个假密码看清交接过程
- 下面用
demo-password-only作为无效的演示值 。先在独立命名空间创建 Secret ,再创建引用它的 Pod 。这里用--from-literal让步骤容易看懂;真实密码不宜直接写进终端命令,避免进入命令历史或被旁观者看到。实际环境应使用受控的凭证交付流程。
bash
kubectl create namespace blog-secret-lab
kubectl -n blog-secret-lab create secret generic preview-db \
--from-literal=password='demo-password-only'
kubectl -n blog-secret-lab apply -f demo.yaml
kubectl -n blog-secret-lab wait --for=condition=Ready pod/preview-secret-demo --timeout=120s
demo.yaml里最关键的是secretKeyRef:它写的是对象名和键名,没有直接写密码值 。完整清单随文章归档。这里使用环境变量,是为了让读者容易观察交接结果;如果应用支持从文件读取,也可以按官方教程把 Secret 挂成卷。
yaml
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: preview-db
key: password # 注意:这里只写键名,不写密码值
- 可以检查环境变量是否存在,同时避免把值直接打印到共享终端或日志:
bash
kubectl -n blog-secret-lab exec preview-secret-demo -- \
sh -c 'test -n "$DB_PASSWORD" && echo "password received"'
kubectl -n blog-secret-lab describe secret preview-db
第二条命令显示键和字节数,不会显示明文值。需要排查时也别顺手把完整对象清单贴进工单。实验结束执行 kubectl delete namespace blog-secret-lab。

图中 Pod 模板只有"取哪把钥匙"的说明;最终拿到值的是容器内的进程。
五. 用了 Secret,还要防哪几种误会
-
Secret 不是自动上锁的保险箱。谁能通过 API 读取它、底层是否启用静态加密,都取决于集群权限与配置。应用拿到值以后,也要避免在日志、错误信息或请求里泄漏。多个容器同处一个 Pod 时,也应只把凭证交给确实需要它的容器。
-
别被
data字段里那串看不懂的字符骗了。Base64 很容易还原原文;把含真实密码的清单提交到 GitHub ,并不会因为它写在 Secret 里就安全。本文的清单只有引用,示例凭证是无效假值。 -
更新凭证以后,同一个容器中的环境变量不会自动变成新值;需要让工作负载创建新 Pod 。卷挂载可以最终收到更新,但应用是否重新读取文件仍要另外确认。这一点和上一篇的 ConfigMap 很像。选择交付方式时,要一起考虑密码轮换时应用怎样重新连接。
text
先看 Secret 是否存在,以及 Pod 引用的名称和键是否正确
再看容器是否拿到值,不把真实密码打印出来
最后确认轮换后应用会不会重新读取或重新创建

这张图重点看最后一层:把凭证交到了容器,不代表它的存储、访问和使用过程都已经安全。
六. 总结:让密码单独交付,也要守住读取边界
-
回到图片预览接口,画质参数继续放在 ConfigMap ,数据库密码则由 Secret 单独交给需要它的容器。镜像和 Pod 模板不再写密码值,修改凭证也不必重做镜像。
-
下次遇到"服务要读密码"这类需求,我会先问三个问题:密码存放在哪里,哪些人和容器能读取,轮换后应用怎样拿到新值。Secret 负责提供专门的交付对象;权限、静态加密和应用侧保护,仍需要我们自己做。

这张图把判断顺序压成一条线:先把密码从镜像和普通配置里拿出来,再逐层确认谁能拿到它、何时换成新值。