GCP Secret Manager:版本管理、IAM 细粒度控制与 Cloud Run 集成
说起 GCP 的 Secret Manager,我最先想到的是它的“版本”设计。 我之前在 AWS 和 Azure 上也折腾过密钥管理,但 GCP 的版本模型有点不一样——它把每个密钥的每次变更都当作一个独立版本,而且这些版本可以有自己的“状态”。 说白了,你改一次密码,不是覆盖旧值,而是生成一个新版本,旧版本还在那里,只是状态变成“已禁用”或者“已销毁”。 这种设计在回滚的时候特别有用,我就在项目里吃过亏:有一次更新了数据库连接串,结果新串配错了,幸好旧版本还在,一秒切回去,线上没受影响。
版本管理:从创建到销毁
每个 Secret 可以包含多个版本。每个版本有三个状态:enabled、disabled、destroyed。 版本一旦被 destroyed,就永久删除了,无法恢复。我建议在生产环境里,不要轻易 destroy,先用 disabled 挂起来,观察一阵再说。
| 状态 | 含义 | 能否被访问 | 能否恢复 |
|---|---|---|---|
| enabled | 正常可用 | 是 | — |
| disabled | 手动禁用 | 否 | 可以再次启用 |
| destroyed | 永久删除 | 否 | 不能恢复 |
用 gcloud 创建和切换版本很简单:
# 创建一个新版本(值通过 --data-file 传入)
gcloud secrets versions add my-secret --data-file=/path/to/new_value.txt
# 查看所有版本
gcloud secrets versions list my-secret
# 禁用某个版本
gcloud secrets versions disable 2 --secret=my-secret
# 启用版本
gcloud secrets versions enable 2 --secret=my-secret
我个人习惯在 CI/CD 里对每个部署都生成一个新版本,并在部署成功后才禁用旧版本。这样一旦发布出问题,可以马上回滚到上一个 enabled 版本。
IAM 细粒度控制:不只是“能看不能看”
GCP 的 IAM 可以精确到单个 Secret 的单个版本。你是不是觉得“给某个服务账号只读权限”就够细了? 嗯,GCP 还能做到:只允许读取 最新版本,但不能访问历史版本;或者只允许管理版本,但不能查看值。
主要用到这几个角色:
| 角色 | 权限范围 | 适用场景 |
|---|---|---|
| roles/secretmanager.secretAccessor | 读取 Secret 值(所有版本) | 给 Cloud Run 服务账号用 |
| roles/secretmanager.secretVersionManager | 管理版本(添加/禁用/销毁) | 自动化流水线 |
| roles/secretmanager.secretVersionAdder | 只能添加新版本,不能读 | 外部系统写入密钥 |
| roles/secretmanager.secretVersionDestroyer | 只能销毁版本 | 谨慎使用,清理任务 |
我曾经犯过一个错:给一个 CI 服务账号直接绑了 roles/editor,结果它不仅能读 secret,还能删。 后来我改成只绑 secretVersionManager + 对特定 Secret 的 secretAccessor,才安心。 你用 IAM Conditions 甚至可以限制“只有在某个 VPC 网络内才能读取”,这个玩起来挺有意思。
与 Cloud Run 集成:零信任的密钥注入
Cloud Run 是 GCP 的 Serverless 容器平台。在我刚接触的时候,我还以为要自己写 SDK 去拉 Secret,后来发现完全不用。 Cloud Run 原生支持两种方式加载 Secret Manager 里的值:
- 环境变量注入:直接把 secret 值当作环境变量放进容器。
- Volume 挂载:把 secret 以文件形式挂载到 /secrets/ 下,每次容器冷启动时加载最新版本。
我推荐用 Volume 挂载方式。为什么?因为环境变量在容器启动后是静态的,而 Volume 挂载的文件可以随 secret 版本更新而自动更新(Cloud Run 会在新实例创建时重新挂载最新版本)。 不过要注意:正在运行的旧实例不会自动刷新文件内容,需要触发新的 revision 才会拉新版本。
配置一个 Cloud Run 服务,让它能读取 Secret Manager:
# service.yaml
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: my-app
spec:
template:
metadata:
annotations:
autoscaling.knative.dev/maxScale: "10"
spec:
containers:
- image: gcr.io/my-project/my-app:latest
volumeMounts:
- name: secrets
mountPath: /secrets
volumes:
- name: secrets
secret:
secretName: projects/123456789/secrets/db-password/versions/latest
同时需要给 Cloud Run 的服务账号授予 roles/secretmanager.secretAccessor 角色。
我之前忘了这一步,结果容器一直在报“permission denied”,折腾了半天才发现是 IAM 没绑对。
versions/latest,再配合 CI 里自动禁用旧版本,才算解决。
核心思路:版本 + 权限 + 接入
把上面三个点串起来,是这样的逻辑:
- 版本管理保证你有历史可追溯、可回滚。
- IAM 细粒度保证最小权限,谁该读什么版本的密钥,清清楚楚。
- Cloud Run 集成让应用无感知地获取最新密钥,不需要改代码。
说到这,你可能理解了为什么我说 GCP Secret Manager 在 Serverless 场景下特别顺手。 下面这张图能帮你快速把握这三个模块的关系:
说实话,当你把这三个能力组合起来,GCP Secret Manager 在云原生领域算是数一数二的。 我最近一个项目甚至把 TLS 证书、、数据库密码全部放进去,Cloud Run 那边一行代码没改,全靠 Volume 挂载动态拉取最新值。 嗯,这就是我为什么喜欢它在 Serverless 场景下的表现。