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 版本。

一条经验:版本不是无限的。一个 Secret 最多只能有 10,000 个版本。如果你的 CI 每天跑几十次,很快会打满。我建议定期清理旧版本,或者只保留最近 N 个 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 网络内才能读取”,这个玩起来挺有意思。

小技巧:如果你只想让某个服务读取最新版本,可以用 IAM Conditions 检查 resource.name 是否以 /versions/latest 结尾。这样就算有人拿到了凭证,也读不到历史版本。

与 Cloud Run 集成:零信任的密钥注入

Cloud Run 是 GCP 的 Serverless 容器平台。在我刚接触的时候,我还以为要自己写 SDK 去拉 Secret,后来发现完全不用。 Cloud Run 原生支持两种方式加载 Secret Manager 里的值:

  1. 环境变量注入:直接把 secret 值当作环境变量放进容器。
  2. 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 没绑对。

避坑指南:我曾经把 secret 的版本号写死了(比如 versions/3),结果更新秘钥后忘了改配置,导致线上用的还是旧密码,数据库连不上。后来我统一改成 versions/latest,再配合 CI 里自动禁用旧版本,才算解决。

核心思路:版本 + 权限 + 接入

把上面三个点串起来,是这样的逻辑:

  • 版本管理保证你有历史可追溯、可回滚。
  • IAM 细粒度保证最小权限,谁该读什么版本的密钥,清清楚楚。
  • Cloud Run 集成让应用无感知地获取最新密钥,不需要改代码。

说到这,你可能理解了为什么我说 GCP Secret Manager 在 Serverless 场景下特别顺手。 下面这张图能帮你快速把握这三个模块的关系:

GCP Secret Manager 核心逻辑图 版本管理 ● 创建 / 启用 / 禁用 ● 最多 10,000 版本 ● 支持 latest 别名 IAM 细粒度控制 ● secretAccessor ● secretVersionManager ● 支持 Conditions Cloud Run 集成 ● 环境变量注入 ● Volume 挂载 ● 自动绑定权限 版本管理提供回滚能力,IAM 确保权限最小化,Cloud Run 无侵入接入 安全、可控、自动化的密钥管理

说实话,当你把这三个能力组合起来,GCP Secret Manager 在云原生领域算是数一数二的。 我最近一个项目甚至把 TLS 证书、、数据库密码全部放进去,Cloud Run 那边一行代码没改,全靠 Volume 挂载动态拉取最新值。 嗯,这就是我为什么喜欢它在 Serverless 场景下的表现。