| name | gcp-fundamentals |
| description | GCP fundamentals — projects, IAM, service accounts, Workload Identity, gcloud CLI, Application Default Credentials (ADC), and Secret Manager. Use this skill when bootstrapping a GCP project, granting access to a service, or wiring credentials into a containerised app.
|
| category | devops |
| tags | ["gcp","cloud","iam","devops","security","infrastructure"] |
| keywords | ["GCP","Google Cloud","gcloud","IAM","Service Account","Workload Identity","ADC","Application Default Credentials","Secret Manager","Project","Folder","Organization"] |
| related | ["gke-deployment","cloud-build-artifact-registry","gcp-cloud-sql-spring","gcp-pubsub-spring","gcp-observability-spring","gcp-firestore-spring","gcp-vertex-ai-rag"] |
GCP Fundamentals
Get the resource hierarchy, identity, and credentials right; everything else on GCP is just APIs you'll learn as you go.
When to Use This Skill
- Starting a new project on GCP
- Granting an application or developer access to GCP resources
- Wiring credentials into a container running on GKE / Cloud Run / locally
- Auditing IAM bindings before going to production
- Migrating away from long-lived service-account keys
Resource Hierarchy
Organization ← acme.com (one per company)
└── Folder ← e.g. "Production", "Engineering"
└── Project ← billing + IAM + API enablement boundary
└── Resources (GKE clusters, Cloud SQL, GCS buckets, ...)
-
The Project is the unit of isolation. Billing, quota, IAM, and API enablement all attach here. Use separate projects per environment (acme-orders-dev, acme-orders-stg, acme-orders-prod).
-
Folders group projects for shared IAM (Production folder grants all prod projects to the SRE group). Don't over-engineer the tree — two levels (env-folder → project) handles most teams.
-
Project IDs are global and immutable. Pick <company>-<service>-<env> and live with it.
gcloud CLI Setup
brew install --cask google-cloud-sdk
gcloud auth login
gcloud auth application-default login
gcloud config set project acme-orders-dev
gcloud config list
-
Two auth flows, two credential stores.
gcloud auth login — auths the CLI (used by gcloud commands).
gcloud auth application-default login — writes ADC that SDKs / Spring apps pick up.
They are independent. Forgetting the second one is the #1 reason "it works in gcloud but my app says unauthenticated".
-
Configurations for env switching:
gcloud config configurations create dev
gcloud config set project acme-orders-dev
gcloud config configurations activate prod
IAM Model
Three pieces:
Member (who) → Role (what) → Resource (where)
user:alice@acme.com roles/storage.admin on bucket "raw-data"
serviceAccount:foo@… roles/pubsub.publisher on project acme-orders-prod
group:devs@acme.com roles/viewer on folder "Production"
-
Members can be: users, groups, service accounts, or allUsers / allAuthenticatedUsers. Prefer groups for humans — never bind individuals directly.
-
Roles come in three flavours:
- Predefined (
roles/pubsub.publisher) — start here.
- Custom — build only when no predefined role fits. Maintenance cost is real.
- Basic (
roles/owner, roles/editor, roles/viewer) — avoid in production. They span every API and grant far too much.
-
Bind at the lowest level. Resource > project > folder > org. Don't grant roles/storage.admin at the project level if a single bucket needs it.
-
Least privilege is enforced by gcloud quirks too. Use gcloud projects get-iam-policy <project> to audit. See rules/gcp-iam-checklist.md.
Service Accounts
A service account (SA) is a non-human identity owned by a project. Apps use SAs to call GCP APIs.
gcloud iam service-accounts create orders-app \
--display-name "Orders application"
gcloud projects add-iam-policy-binding acme-orders-prod \
--member "serviceAccount:orders-app@acme-orders-prod.iam.gserviceaccount.com" \
--role "roles/cloudsql.client"
-
One SA per workload. orders-app, orders-migrations, orders-pubsub-worker — separate SAs make audits and revocation simple. Don't reuse a single "app SA" everywhere.
-
Never download SA keys (*.json) for use on GCP-hosted workloads. Use Workload Identity (below). Keys are long-lived, easy to leak, and a top finding in security reviews.
-
Local dev uses your user identity via ADC, not an SA key. gcloud auth application-default login is enough for the SDK to authenticate as you.
Workload Identity (the right way to give pods credentials)
For GKE, Workload Identity Federation maps a Kubernetes Service Account (KSA) to a Google Service Account (GSA). Pods running under the KSA get short-lived tokens for the GSA — no JSON keys anywhere.
gcloud container clusters update prod-cluster \
--workload-pool=acme-orders-prod.svc.id.goog
gcloud iam service-accounts create orders-app
kubectl create serviceaccount orders-app -n orders
gcloud iam service-accounts add-iam-policy-binding \
orders-app@acme-orders-prod.iam.gserviceaccount.com \
--member "serviceAccount:acme-orders-prod.svc.id.goog[orders/orders-app]" \
--role "roles/iam.workloadIdentityUser"
kubectl annotate serviceaccount orders-app -n orders \
iam.gke.io/gcp-service-account=orders-app@acme-orders-prod.iam.gserviceaccount.com
Then in your Deployment:
spec:
template:
spec:
serviceAccountName: orders-app
-
No Java code change needed. The Google SDK picks up ADC; on GKE that means the metadata server, which Workload Identity intercepts.
-
For Cloud Run / Cloud Functions / Cloud Build, assign the GSA directly with --service-account=.... Same SDK, same ADC — different injection mechanism.
-
For GitHub Actions, use Workload Identity Federation (google-github-actions/auth@v2 with workload_identity_provider). Stop checking SA keys into GitHub Secrets.
Application Default Credentials (ADC)
The Google SDK looks for credentials in this order:
GOOGLE_APPLICATION_CREDENTIALS env var → path to a key file
~/.config/gcloud/application_default_credentials.json (set by gcloud auth application-default login)
- The metadata server on GCP (Compute Engine, GKE with Workload Identity, Cloud Run, Cloud Build)
Spring code looks like:
@Bean
public Storage storage() {
return StorageOptions.getDefaultInstance().getService();
}
That's it. The SDK figures out where it's running.
-
Set GOOGLE_APPLICATION_CREDENTIALS only as a last resort — usually for local dev with a downloaded key, and only when ADC won't work (e.g. some integration test scenarios).
-
Project ID is also resolved from ADC, but you can override:
spring:
cloud:
gcp:
project-id: acme-orders-prod
Secret Manager
Don't commit secrets. Don't put them in application-prod.yml. Don't bake them into images.
gcloud secrets create db-password --replication-policy=automatic
echo -n "supersecret" | gcloud secrets versions add db-password --data-file=-
gcloud secrets add-iam-policy-binding db-password \
--member "serviceAccount:orders-app@acme-orders-prod.iam.gserviceaccount.com" \
--role "roles/secretmanager.secretAccessor"
Spring integration
With spring-cloud-gcp-starter-secretmanager:
spring:
config:
import: "sm@:"
datasource:
password: ${sm@db-password}
-
Never grant roles/secretmanager.admin to apps. They only need secretAccessor.
-
Rotate via new versions. versions add creates a new version; the app reads latest (or pin a specific version). Old versions get disabled, then destroyed after a retention period.
-
For env vars in K8s, use the Secret Manager CSI driver or pull at startup. Don't copy secrets into Kubernetes Secret objects unnecessarily — that's a separate audit boundary.
Enabling APIs
Every GCP API is off by default in a new project. Common ones for a Java backend:
gcloud services enable \
container.googleapis.com \
artifactregistry.googleapis.com \
cloudbuild.googleapis.com \
sqladmin.googleapis.com \
pubsub.googleapis.com \
secretmanager.googleapis.com \
monitoring.googleapis.com \
logging.googleapis.com \
cloudtrace.googleapis.com
- First call to a new API often fails with
Permission denied even when IAM is correct — the API was just enabled and the propagation takes ~minutes.
Cost Hygiene
-
Set a billing budget alert per project. gcloud billing budgets create or via the console. Cap spend before you discover a forgotten n2-standard-32.
-
Label everything. --labels=env=prod,team=orders,cost-center=eng-platform. Labels become billing-export columns; without them you can't slice spend.
-
Delete unused projects. A project with no resources still has a billing record but costs $0 — but a project with a forgotten Cloud SQL instance costs ~$50/month forever.
Common Pitfalls
| Pitfall | Fix |
|---|
gcloud works, app says "unauthenticated" | Run gcloud auth application-default login (separate from gcloud auth login) |
Granted roles/owner to a service account "to make it work" | Replace with the specific predefined role; revoke owner |
| SA key checked into git | Rotate immediately, switch to Workload Identity, run secret scanning on history |
API call fails with SERVICE_DISABLED | Enable the API in the project (gcloud services enable ...) |
| Cross-project access fails silently | Bind the calling SA in both projects, or grant on the resource directly |
| Cloud Run app gets the wrong project ID | Set the GOOGLE_CLOUD_PROJECT env var or spring.cloud.gcp.project-id |
Pre-Production Checklist
Related Skills