TGI model load fails with IAM permission denied during DR failover

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:

  1. Inspect the pod logs for the exact error strings.
  2. 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
  1. 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\""
  }
}
  1. 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:

  1. Restart the deployment:

kubectl rollout restart deployment tgi -n tgi-dr
  1. 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
  1. 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-permissions for 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/logs for the pattern “IAM permission denied” with a threshold of 5 occurrences in 5 minutes.

FAQ

  1. 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 to resource.location == "us-central1". When the request originates from europe-west1, the condition evaluates to false, causing the denial even though the role is present.
  2. 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.
  3. 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 an in list or a regular expression that matches all authorized regions, which only requires updating when adding a completely new region.
  4. What if the DR service account already has the roles but still sees “permission denied”?
    Verify that the bucket’s uniformBucketLevelAccess is enabled. If disabled, object‑level ACLs may override IAM permissions. Ensure the objects themselves grant READER access to the service account or enable uniform bucket-level access.
  5. How can I test the IAM configuration before a real failover?
    Use gcloud iam test-permissions for each required permission and perform a dry‑run download with gsutil cp -n (no‑clobber) to confirm read access without affecting the production bucket.

Related Topic Hub: Model Serving Troubleshooting Hub