GCP Overview¶
One project, one region, one cluster, many namespaces. Tomoda's GCP footprint is intentionally small so that every operator can hold the whole topology in their head.
Single-project setup
There is no separate tomoda-dev and tomoda-prod project. Everything runs in development-485000 in asia-east1 and is isolated by Kubernetes namespace. The project name is a historical artifact — treat it as the production project.
Topology at a glance¶
flowchart TB
subgraph GCP["GCP project: development-485000 (asia-east1)"]
direction TB
subgraph TF["Terraform state"]
TFSTATE[(GCS: development-485000-tfstate<br/>prefix: terraform/state<br/>default workspace)]
end
subgraph NET["VPC: gke-tomoda-vpc"]
SUBNET[Subnet 10.0.0.0/24<br/>pods 10.1.0.0/16<br/>services 10.2.0.0/20]
end
subgraph GKE["GKE: gke-tomoda (asia-east1-a)"]
POOL1[spot-nodes pool<br/>e2-medium · count=0]
POOL2[high-mem-spot pool<br/>e2-standard-2 · count=1]
end
subgraph CI["CI / Images"]
CB[Cloud Build<br/>4 triggers]
AR_DEV[(Artifact Registry<br/>tomoda-dev-repo)]
AR_PROD[(Artifact Registry<br/>tomoda-prod-repo)]
end
subgraph SEC["Secrets & OAuth"]
IAP[IAP Brand + OAuth Client]
SM[(Secret Manager<br/>GOOGLE_OAUTH_CLIENT_SECRET)]
end
subgraph STORAGE["GCS Buckets"]
BK_DB[(tomoda-db-backups-…<br/>CNPG WAL + base)]
BK_PI[(…-photon-index<br/>public-read, lifecycle)]
end
GKE --> NET
CB --> AR_DEV
CB --> AR_PROD
IAP --> SM
GKE -. Workload Identity .-> BK_DB
GKE -. Workload Identity .-> BK_PI
GKE -. Workload Identity .-> AR_DEV
GKE -. Workload Identity .-> AR_PROD
end
What lives where¶
| Concern | Resource | Page |
|---|---|---|
| Compute | GKE cluster + 2 node pools | GKE |
| Network | Custom VPC + secondary ranges | VPC |
| CI | Cloud Build triggers + SA | Cloud Build |
| Images | 2 Artifact Registry repos | Artifact Registry |
| GitOps | Argo CD via Helm | Argo CD |
| Auth | IAP brand + OAuth client | OAuth & Dex |
| Geocoder index | …-photon-index GCS bucket |
Photon Indexer |
| DB backups | tomoda-db-backups-… GCS bucket |
Backup |
| Identity | Workload Identity bindings | IAM |
Environment isolation¶
Because there is only one project, "dev" and "prod" are not GCP-level boundaries. They show up as:
- Kubernetes namespaces —
tomoda-dev,tomoda-prod,data(single namespace, holds bothpostgres-devandpostgres-prodclusters). - Artifact Registry repos —
tomoda-dev-repofor main-branch builds,tomoda-prod-repofor semver-tag builds. Cloud Build pushes to whichever repo the trigger configures. - OAuth client IDs — three sets of Google OAuth client IDs (web/iOS/Android) baked into Cloud Build substitutions: one set for dev, one for prod, plus an internal one for middleware tools like Argo CD itself.
If you need stronger isolation later (separate billing, separate IAM blast radius, separate APIs), the migration path is to split the GKE cluster and node pools into a new project; the Terraform code is already parameterised on var.project_id.
Terraform workflow¶
cd infrastructure/gcp
terraform init # connects to gs://development-485000-tfstate
terraform plan -var-file=terraform.tfvars
terraform apply -var-file=terraform.tfvars
The infrastructure/gcp/README.md mentions workspaces (internal / development / production), but the live environment runs the default workspace and the terraform.tfvars checked in points at development-485000. Do not create new workspaces without first migrating state — there is none for them.
Provider quirks¶
- The Google provider sets
user_project_override = trueandbilling_project = var.project_id. This stops API calls from being billed to Google's internal gcloud client project (764086051850) when operators are using user ADC, which would otherwise fail withSERVICE_DISABLED. - The
kubernetesandhelmproviders authenticate against the GKE cluster using the operator's currentgcloudaccess token — there is no kubeconfig file involved.terraform applytherefore requirescontainer.clusters.getCredentialsongke-tomoda.