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 读取。
这个模式很直接:Google service account JSON 不进 Git,先放到 secret manager,再用只读文件挂进 Pod,最后把 GOOGLE_APPLICATION_CREDENTIALS 指到这个文件。
后端服务需要 Firebase Admin SDK、Google Cloud client library,或者 Java 代码里使用 GoogleCredentials.getApplicationDefault() 时,都可以用这套做法。
下面的示例都做了匿名化处理。实际使用前,把占位符换成自己的环境值。
文中用语说明
| 用语 | 含义 |
|---|---|
GOOGLE_APPLICATION_CREDENTIALS | Google client library 识别的环境变量,用来指定 service account JSON 文件路径。 |
| Secrets Store CSI Driver | Kubernetes CSI driver,可以把 AWS Secrets Manager 这类外部 secret 以文件形式挂进 Pod。 |
SecretProviderClass | Kubernetes 自定义资源,告诉 CSI driver 去哪里取 secret,以及在 Pod 里暴露成什么文件名。 |
| GitOps | Kubernetes 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 侧验证。