TGI model load fails with IAM permission denied during DR failover
Problem
During a disaster‑recovery (DR) failover to the secondary region, the Text Generation Inference (TGI) service repeatedly aborts model loading. The pod logs contain IAM permission denied errors when accessing the model artifacts stored in Cloud Storage and when reading the configuration stored in Secret Manager.
2026-10-25T14:32:07.123Z ERROR model_loader.go:112] failed to download model.tar.gz from gs://tgi-models-prod/model_v3.tar.gz: storage: bucket doesn't exist or AccessDenied: IAM permission denied
2026-10-25T14:32:07.130Z ERROR config_loader.go:78] cannot access secret tgi-config-prod: permission denied (IAM)
The failure prevents the TGI endpoint from becoming ready, breaking downstream inference requests.
Root Cause
The DR environment replicates the primary deployment but uses a distinct service account (tgi-dr-sa@project-id.iam.gserviceaccount.com) that lacks the required roles/storage.objectViewer on the model bucket and roles/secretmanager.secretAccessor on the configuration secret. The primary service account (tgi-prod-sa@project-id.iam.gserviceaccount.com) has these roles, but the DR service account was created from a minimal template and never granted the same permissions.
Additionally, the bucket policy includes a condition that restricts access to the primary region (resource.location == "us-central1"). During failover to europe-west1 the condition evaluates to false, causing the access check to fail even if the role were present.
Debug
Follow these steps to confirm the IAM mismatch and policy condition:
- Inspect the pod logs for the exact error strings.
- Identify the service account used by the TGI pod:
kubectl get pod tgi-0 -n tgi-dr -o jsonpath='{.spec.serviceAccountName}'
# Output: tgi-dr-sa
- Validate the bucket IAM bindings:
gsutil iam get gs://tgi-models-prod
{
"bindings": [
{
"role": "roles/storage.objectViewer",
"members": [
"serviceAccount:tgi-prod-sa@project-id.iam.gserviceaccount.com"
]
}
],
"condition": {
"title": "PrimaryRegionOnly",
"expression": "resource.location == \"us-central1\""
}
}
- Test permissions for the DR service account:
gcloud iam test-permissions tgi-dr-sa@project-id.iam.gserviceaccount.com \
--project=project-id \
--permissions=storage.objects.get,secretmanager.secrets.access
{
"permissions": []
}
The empty list confirms that the DR service account lacks both permissions.
Solution
Grant the missing roles to the DR service account and adjust the bucket condition to allow access from the DR region.
Before (IAM policy snippet)
bindings:
- role: roles/storage.objectViewer
members:
- serviceAccount:tgi-prod-sa@project-id.iam.gserviceaccount.com
condition:
title: PrimaryRegionOnly
expression: resource.location == "us-central1"
After (IAM policy update)
# Add DR service account to the bucket IAM
gsutil iam ch \
serviceAccount:tgi-dr-sa@project-id.iam.gserviceaccount.com:objectViewer \
gs://tgi-models-prod
# Update the bucket condition to include the DR region
gsutil iam set - <
For Secret Manager, grant the accessor role:
gcloud secrets add-iam-policy-binding tgi-config-prod \
--member=serviceAccount:tgi-dr-sa@project-id.iam.gserviceaccount.com \
--role=roles/secretmanager.secretAccessor \
--project=project-id
Redeploy the TGI pods so they pick up the updated service account permissions.
Verify
After applying the changes, confirm that the model loads without IAM errors:
- Restart the deployment:
kubectl rollout restart deployment tgi -n tgi-dr
- Watch the pod logs for successful download messages:
kubectl logs -f -l app=tgi -n tgi-dr
2026-10-25T14:45:12.001Z INFO model_loader.go:115] downloaded model.tar.gz (2.3 GB) from gs://tgi-models-prod/model_v3.tar.gz
2026-10-25T14:45:13.210Z INFO config_loader.go:45] loaded secret tgi-config-prod
2026-10-25T14:45:14.000Z INFO server.go:78] TGI service ready on :8080
- Run a health‑check request:
curl -s http://tgi-dr-service.tgi-dr.svc.cluster.local:8080/healthz | jq .
{
"status": "ok",
"model": "model_v3",
"region": "europe-west1"
}
If the health endpoint returns "status":"ok", the DR failover is functional.
Prevent
- Policy as code: Store bucket IAM bindings and Secret Manager policies in Terraform or Config Connector. Include the allowed regions list as a variable so DR regions are automatically added during environment provisioning.
- Automated permission validation: Add a CI step that runs
gcloud iam test-permissionsfor the DR service account against all required resources. Fail the pipeline if any permission is missing. - Region‑agnostic bucket conditions: Prefer using
resource.location in ["us-central1","europe-west1"]instead of equality checks, reducing the chance of forgetting a DR region. - Service account inheritance: Create a single “tgi‑svc‑sa” with the superset of permissions and reference it from both primary and DR deployments, reducing drift.
- Monitoring alerts: Set up a Cloud Monitoring alert on
container/logsfor the pattern “IAM permission denied” with a threshold of 5 occurrences in 5 minutes.
FAQ
- Why does the primary region work but the DR region fails with the same service account?
The bucket IAM policy includes a conditional expression that restricts access toresource.location == "us-central1". When the request originates fromeurope-west1, the condition evaluates to false, causing the denial even though the role is present. - Can I avoid modifying bucket conditions by using a signed URL?
Yes, generating a signed URL for the model artifact bypasses IAM checks, but it adds operational complexity and expires. Granting the correct IAM role is the preferred, auditable solution. - Do I need to update the IAM policy for every new DR region?
If you use a static equality condition, yes. Instead, define the condition using aninlist or a regular expression that matches all authorized regions, which only requires updating when adding a completely new region. - What if the DR service account already has the roles but still sees “permission denied”?
Verify that the bucket’suniformBucketLevelAccessis enabled. If disabled, object‑level ACLs may override IAM permissions. Ensure the objects themselves grantREADERaccess to the service account or enable uniform bucket-level access. - How can I test the IAM configuration before a real failover?
Usegcloud iam test-permissionsfor each required permission and perform a dry‑run download withgsutil cp -n(no‑clobber) to confirm read access without affecting the production bucket.
Related Topic Hub: Model Serving Troubleshooting Hub