Kubernetes Secret 让数据库密码离开镜像和 Pod 配置

47 阅读6分钟

前言: 昨天把图片预览接口的画质参数交给 ConfigMap 后,我顺手想把数据库密码也塞进去,省得再改镜像。可查了 Kubernetes 官方安全建议才发现,普通参数和凭证不该混在一起;更尴尬的是,把密码写进 Pod 配置一样会留下明文。于是我想弄清楚:

1. 没有 Secret 时,密码通常被放在哪里,会留下什么麻烦?
2. 用了 Secret 后,密码是不是就一定安全了?

这篇只看一个测试用的假密码,弄清 Secret 怎样把凭证交给 Pod,不讨论外部密钥系统。

一. 画质能放进配置对象,密码怎么办

  1. 图片预览接口要连数据库。最省事的写法,是把连接密码放进镜像中的配置文件。开发环境可以跑,等测试和生产的密码不一样时,同一份程序又得分别打包。更糟的是,拿到镜像的人也可能拿到这份密码。

  2. 我第一反应是照搬上一篇:把 DB_PASSWORD 放在 ConfigMap,由容器读取。这样至少不用重新打镜像。但密码仍然是普通配置,能读取该对象的人就能看到它。把值直接填在 Pod 的 env.value 里也不舒服:工作负载清单会跟着带上密码。

图 1:把数据库密码写进镜像或普通配置都会留下可见的值

这张图重点看密码的落点:我们想让程序拿到它,却不想让镜像或工作负载清单带着值到处走。

二. 从“直接写进去”到“只写引用”

  1. 最粗暴的方案是镜像里放密码,打包时就固定了。稍好一点是在 Pod 模板里写环境变量,镜像能够复用,但配置清单依然含有明文。把密码换个位置,不等于解决了传播范围的问题。

  2. 官方把非机密数据交给 ConfigMap,而把密码、令牌这类敏感数据交给 Secret。工作负载配置只写“我要读取哪个 Secret 的哪个键”,值放在另一个对象里。这样至少能把程序交付物、工作负载模板与凭证分开管理。

做法密码放在哪里改密码时要动哪里
写入镜像镜像文件重建并重新发布镜像
写入 Pod 模板工作负载清单修改模板并重建 Pod
使用 Secret独立的 Secret 对象更新凭证,并确认应用读取方式

图 2:镜像内、Pod 模板和 Secret 三种放置方式

这里的变化不是“密码消失了”,而是 Pod 模板只保留引用,凭证的管理入口变得独立。

三. 把 Secret 当成单独交接的钥匙

  1. 如果把镜像想成一台已经装好的咖啡机,上一篇的画质参数是档位卡,数据库密码则更像仓库钥匙。换档位可以公开讨论,钥匙却需要单独保管,只有负责开门的人拿到。对应到这里:镜像是程序,ConfigMap 是普通参数,Secret 是要交给指定容器的凭证。

  2. 注意,这个类比只说明用途和交接范围。它没有承诺钥匙进了 Secret 就会自动加密、自动轮换,或者阻止拿到钥匙的程序把它打印到日志里。官方安全实践明确提醒:Secret 的 data 使用 Base64 编码,编码不是加密;底层 etcd 默认也不会替它加密存储,需要管理员配置静态加密和访问权限。

图 3:普通档位卡与数据库钥匙分别交给不同对象

这张图重点看“谁需要读取”:普通参数和数据库凭证都可以独立于镜像,但凭证还要限制读取范围。

四. 用一个假密码看清交接过程

  1. 下面用 demo-password-only 作为无效的演示值。先在独立命名空间创建 Secret,再创建引用它的 Pod。这里用 --from-literal 让步骤容易看懂;真实密码不宜直接写进终端命令,避免进入命令历史或被旁观者看到。实际环境应使用受控的凭证交付流程。
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
  1. demo.yaml 里最关键的是 secretKeyRef:它写的是对象名和键名,没有直接写密码值。完整清单随文章归档。这里使用环境变量,是为了让读者容易观察交接结果;如果应用支持从文件读取,也可以按官方教程把 Secret 挂成卷。
env:
  - name: DB_PASSWORD
    valueFrom:
      secretKeyRef:
        name: preview-db
        key: password # 注意:这里只写键名,不写密码值
  1. 可以检查环境变量是否存在,同时避免把值直接打印到共享终端或日志:
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。

图 4:Pod 通过名称和键引用 Secret,再把值交给容器

图中 Pod 模板只有“取哪把钥匙”的说明;最终拿到值的是容器内的进程。

五. 用了 Secret,还要防哪几种误会

  1. Secret 不是自动上锁的保险箱。谁能通过 API 读取它、底层是否启用静态加密,都取决于集群权限与配置。应用拿到值以后,也要避免在日志、错误信息或请求里泄漏。多个容器同处一个 Pod 时,也应只把凭证交给确实需要它的容器。

  2. 别被 data 字段里那串看不懂的字符骗了。Base64 很容易还原原文;把含真实密码的清单提交到 GitHub,并不会因为它写在 Secret 里就安全。本文的清单只有引用,示例凭证是无效假值。

  3. 更新凭证以后,同一个容器中的环境变量不会自动变成新值;需要让工作负载创建新 Pod。卷挂载可以最终收到更新,但应用是否重新读取文件仍要另外确认。这一点和上一篇的 ConfigMap 很像。选择交付方式时,要一起考虑密码轮换时应用怎样重新连接。

先看 Secret 是否存在,以及 Pod 引用的名称和键是否正确
再看容器是否拿到值,不把真实密码打印出来
最后确认轮换后应用会不会重新读取或重新创建

图 5:Secret 使用过程中的三个检查边界

这张图重点看最后一层:把凭证交到了容器,不代表它的存储、访问和使用过程都已经安全。

六. 总结:让密码单独交付,也要守住读取边界

  1. 回到图片预览接口,画质参数继续放在 ConfigMap,数据库密码则由 Secret 单独交给需要它的容器。镜像和 Pod 模板不再写密码值,修改凭证也不必重做镜像。

  2. 下次遇到“服务要读密码”这类需求,我会先问三个问题:密码存放在哪里,哪些人和容器能读取,轮换后应用怎样拿到新值。Secret 负责提供专门的交付对象;权限、静态加密和应用侧保护,仍需要我们自己做。

图 6:从存放、交付到轮换的判断路径

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