cloud-sre

把 GOOGLE_APPLICATION_CREDENTIALS 挂进 Kubernetes Pod 的通用步骤

一套可复用的 EKS/Kubernetes 做法:把 Google service account JSON 放进 AWS Secrets Manager,通过 Secrets Store CSI Driver 挂载到 Pod,再让 Java 通过 GOOGLE_APPLICATION_CREDENTIALS 读取。

Jun 26, 2026
KubernetesEKSAWS Secrets ManagerSecrets Store CSIFirebaseGitOps

这个模式很直接:Google service account JSON 不进 Git,先放到 secret manager,再用只读文件挂进 Pod,最后把 GOOGLE_APPLICATION_CREDENTIALS 指到这个文件。

后端服务需要 Firebase Admin SDK、Google Cloud client library,或者 Java 代码里使用 GoogleCredentials.getApplicationDefault() 时,都可以用这套做法。

下面的示例都做了匿名化处理。实际使用前,把占位符换成自己的环境值。

文中用语说明

用语含义
GOOGLE_APPLICATION_CREDENTIALSGoogle client library 识别的环境变量,用来指定 service account JSON 文件路径。
Secrets Store CSI DriverKubernetes CSI driver,可以把 AWS Secrets Manager 这类外部 secret 以文件形式挂进 Pod。
SecretProviderClassKubernetes 自定义资源,告诉 CSI driver 去哪里取 secret,以及在 Pod 里暴露成什么文件名。
GitOpsKubernetes manifest 先提交到 Git,再由 ArgoCD 这类控制器同步到集群。

目标形态

容器运行时应该有这个环境变量:

GOOGLE_APPLICATION_CREDENTIALS=/mnt/secrets-store/google-application-credentials.json

Pod 里应该能看到这个文件:

/mnt/secrets-store/google-application-credentials.json

service account JSON 本身只存在外部 secret store,不进入 GitOps 仓库。

1. 把 JSON 放进 AWS Secrets Manager

按环境和用途创建独立 secret。名字要能看出归属和用途。

aws secretsmanager create-secret \
  --region <region> \
  --profile <profile> \
  --name <env>/shared/google_application_credentials_<app> \
  --description "Google service account credentials for <app>" \
  --secret-string file://./google-service-account.json

不要把 JSON 粘到 Kubernetes manifest 里,也不要把 JSON 文件提交到仓库。

如果 secret 已经存在,用下面的命令更新版本:

aws secretsmanager put-secret-value \
  --region <region> \
  --profile <profile> \
  --secret-id <env>/shared/google_application_credentials_<app> \
  --secret-string file://./google-service-account.json

2. 给 Workload Role 读 Secret 的权限

Pod 使用的 IAM role,或者 CSI driver 使用的 IAM role,需要能读取这个 secret。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "secretsmanager:GetSecretValue",
        "secretsmanager:DescribeSecret",
        "secretsmanager:ListSecretVersionIds"
      ],
      "Resource": "arn:aws:secretsmanager:<region>:<account-id>:secret:<env>/shared/google_application_credentials_<app>*"
    }
  ]
}

权限范围尽量收窄。如果同一个 role 本来就负责少量 Google credential secret,也可以用类似 <env>/shared/google_application_credentials* 的小范围通配。

3. 新增 SecretProviderClass

在 workload 所在 namespace 里创建 SecretProviderClass

apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
  name: <app>-google-application-credentials
  namespace: <namespace>
spec:
  provider: aws
  parameters:
    objects: |
      - objectName: "<env>/shared/google_application_credentials_<app>"
        objectType: "secretsmanager"
        objectAlias: "google-application-credentials.json"

objectName 是 AWS Secrets Manager 里的 secret 名字。objectAlias 是 Pod 里看到的文件名。

4. 在 Deployment 里挂载

Deployment 里加三块:环境变量、只读 volumeMount、CSI volume。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: <app>
  namespace: <namespace>
spec:
  template:
    spec:
      serviceAccountName: <workload-service-account>
      containers:
        - name: <app>
          image: <image>
          env:
            - name: GOOGLE_APPLICATION_CREDENTIALS
              value: /mnt/secrets-store/google-application-credentials.json
          volumeMounts:
            - name: secrets-store
              mountPath: /mnt/secrets-store
              readOnly: true
      volumes:
        - name: secrets-store
          csi:
            driver: secrets-store.csi.k8s.io
            readOnly: true
            volumeAttributes:
              secretProviderClass: <app>-google-application-credentials

如果这个 workload 已经有 config volume 或 application-secret volume,Google JSON 仍然建议单独放在 secrets-store volume。它是凭证文件,不是应用 YAML 配置覆盖。

5. 加到 Kustomize

如果 GitOps 目录用 Kustomize 管理:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: <namespace>

resources:
  - secret-provider-class.yaml
  - deployment.yaml
  - external-secret.yaml
  - pdb.yaml

提交的只有 manifest。JSON key 仍然留在 AWS Secrets Manager。

6. Java 代码怎么用

Firebase Admin SDK 或 Google Cloud library 里,优先用 Application Default Credentials:

FirebaseOptions options = FirebaseOptions.builder()
    .setCredentials(GoogleCredentials.getApplicationDefault())
    .build();

FirebaseApp app = FirebaseApp.initializeApp(options);

只要 GOOGLE_APPLICATION_CREDENTIALS 已经设置,Java 进程会自动读取挂载的 JSON 文件。

如果某些环境不启用 Firebase,可以用配置开关包住这段 bean:

@Configuration
@ConditionalOnProperty(name = "firebase.enabled", havingValue = "true")
public class FirebaseConfig {
    // Firebase beans
}

环境配置里再打开:

firebase:
  enabled: true

7. 先看 GitOps 渲染内容

推送前先渲染:

kubectl kustomize <path-to-kustomize-dir>

确认输出里有:

GOOGLE_APPLICATION_CREDENTIALS
secretProviderClass: <app>-google-application-credentials

之后再推送 GitOps 变更,让 ArgoCD 或其他同步控制器接管。

8. 在集群里验证

同步后先看 rollout:

kubectl -n <namespace> rollout status deployment/<app> --timeout=120s

确认 SecretProviderClass

kubectl -n <namespace> get secretproviderclass <app>-google-application-credentials \
  -o jsonpath='{.spec.parameters.objects}{"\n"}'

确认环境变量:

kubectl -n <namespace> get deployment <app> \
  -o jsonpath='{range .spec.template.spec.containers[0].env[*]}{.name}{"="}{.value}{"\n"}{end}'

最后只确认文件存在,不输出 secret 内容:

kubectl -n <namespace> exec deployment/<app> -- sh -c \
  'test -s "$GOOGLE_APPLICATION_CREDENTIALS" && echo credential_file_present'

这个命令只检查文件存在且非空,不会打印 JSON。

常见问题

现象检查点
Pod 挂载 volume 失败检查 Secrets Store CSI Driver 和 AWS provider 是否已安装。
挂载时报 AccessDenied检查 IAM role 是否能读取准确的 Secrets Manager ARN。
文件存在,但 Java 仍然失败检查 GOOGLE_APPLICATION_CREDENTIALS 是否指向了实际挂载出来的文件名。
Firebase bean 没有创建检查 firebase.enabled=true 或对应的应用开关。
更新 secret 后应用没拿到新值除非集群明确启用并验证了 secret rotation,否则重启或重新部署 workload。

这套做法的边界很清楚:基础设施负责提供文件路径,应用代码使用 Google 默认凭证链。这样 secret 不进代码库,运行时行为也能直接从 Kubernetes 侧验证。