← Back to list

I Built the Same Kubernetes Platform on AWS, Azure, and GCP. GCP Was the Weird One.

GitHub Actions does not do kubectl apply workloads—Argo CD does. OIDC → Workload Identity Federation replaces long-lived GCP keys.

Birol Tilki · 2026-06-11 12:34 · 0 claps · 6.7 min read
#google-cloud-platform #github-actions #kubernetes #argo-cd #terraform
Open on Medium ↗
Wiki topics: ☁️ · DevOps & Cloud 🔓 · Open Source

I Built the Same Kubernetes Platform on AWS, Azure, and GCP. GCP Was the Weird One.

GitHub Actions does not do kubectl apply workloads—Argo CD does. OIDC → Workload Identity Federation replaces long-lived GCP keys.

If you’ve ever tried to prove that you can run the same production-style platform across multiple clouds, you eventually hit the same wall: the patterns transfer (IaC, CI/CD, GitOps, and observability), but the products do not. Load balancers, identity systems, registries, and ingress controllers all have different names, different defaults, and different sharp edges.

Over the past year, I built three sibling repositories around the same idea — run Online Boutique on Kubernetes with Terraform, GitOps, and digest-based promotion — once on each hyperscaler:

GCP (this repo): micser-apps-GCP-Terraform-Helm-GitOps-monitoring

AWS: micser-apps-AWS-Terraform-Helm-GitOps-monitoring

Azure: micser-apps-Azure-Terraform-Helm-GitOps-monitoring

This article focuses on the GCP implementation and compares it to the AWS and Azure versions in my GitHub repos — the same reference architecture expressed through three different cloud-native implementations.

The patterns transfer. The products do not.

The problem I was solving

Online Boutique is a useful reference application: it includes gRPC backends, a web frontend, Redis, and enough cross-service traffic to expose networking, deployment-order, and service-discovery issues. It is not enough to kubectl apply a YAML bundle and call it a day.

I wanted each repo to demonstrate:

  1. Infrastructure as code with remote state and CI-friendly credentials — no long-lived keys in Git.
  2. Immutable deployments — images pinned by digest, promoted across environments without rebuilding.
  3. GitOps — cluster state driven from Git, with prod gated behind review and manual sync.
  4. Edge HTTPS on a real hostname.
  5. Supply-chain and runtime guardrails appropriate for a portfolio lab while still reflecting senior-level platform engineering practices.

The GCP repo is the most recent iteration. It benefits from lessons learned on AWS and Azure and leans into services that exist (or work best) only on Google Cloud.

On AWS, I pinned upstream demo images at v0.10.4 in a single Helm chart. On GCP, I vendored the full microservices-demo source into apps/ and wired per-service GitHub Actions. The AWS repo deploys a chart; the GCP repo builds, scans, signs, and opens GitOps PRs for the services it owns.

GCP architecture in one picture

The GCP platform is cost-first: one project, one private GKE cluster, three workload namespaces (dev, stage, prod), plus platform namespaces for Argo CD and shared ingress.

GitHub Actions (OIDC → Workload Identity Federation)
        │
        ▼
  Artifact Registry (boutique-dev, boutique-stage, boutique-prod)
        │
        ▼
  Argo CD ApplicationSets → Helm charts → GKE namespaces
        │
        ▼
  Gateway API (global L7) + Certificate Manager → https://*.biroltilki.art
        │
        ▼
  Managed Prometheus + Cloud Logging

Bootstrap Terraform creates the GCS state bucket, the WIF pool, and the service accounts (sa-terraform-ci, sa-build-ci, sa-promote-ci). Foundation Terraform creates the VPC, Cloud NAT, private GKE, three Artifact Registry repos, Cloud DNS, a static gateway IP, KMS encryption for etcd, and a Binary Authorization attestor.

That split mirrors the AWS and Azure repos: bootstrap trust and state first, then the cluster and shared platform.

Auth model: GitHub Actions uses OIDC → Workload Identity Federation. sa-build-ci pushes to boutique-dev only; sa-promote-ci copies digests between registries; sa-terraform-ci owns foundation. GitHub Actions builds and scans images, pushes to Artifact Registry, and opens PRs to update digests. It does not kubectl apply workloads — Argo CD does. No long-lived GCP JSON keys in GitHub.

Bootstrap trust and state first. Then the cluster.

What makes the GCP repo different

1. Real application source, not placeholder images

On AWS, I pinned upstream demo images in a single Helm chart. On Azure, I started from scaffold build contexts and per-service charts.

On GCP, I vendored the actual microservices-demo source into apps/ — Go frontend and catalog, .NET cart, Node currency, Redis , and wired one reusable GitHub Actions workflow per service. A push to apps/frontend/ runs the reusable workflow: build, syft SBOM, Trivy scan, push to boutique-dev, cosign sign, Binary Authorization attestation, then opens a GitOps PR updating gitops/envs/dev/values-frontend.yaml. Argo CD syncs frontend-dev after the merge.

2. GitHub Actions + Workload Identity Federation

Short-lived credentials only. sa-build-ci can push to boutique-dev; sa-promote-ci can copy images between registries; sa-terraform-ci owns foundation. No JSON keys in GitHub secrets.

3. Gateway API instead of NGINX or ALB

Azure uses NGINX Ingress, cert-manager, and external-dns on Azure DNS — a familiar pattern, but you operate the ingress controller yourself.

AWS uses ALB + ACM via the AWS Load Balancer Controller — solid, but AWS-specific manifest glue.

On GCP, I chose GKE Gateway API (gke-l7-global-external-managed) with a Certificate Manager cert map on the Gateway. HTTPRoutes attach per environment (dev.biroltilki.art, stage.biroltilki.art, apex biroltilki.art). One global IP, TLS managed in GCP-native services. Rejected: NGINX Ingress (the Azure pattern) — not the GCP-native default. The trade-off is that you learn Gateway API resources and Certificate Manager mappings instead of the more familiar Ingress annotation model.

Promote and merge backing services before the frontend, or the storefront returns HTTP 500/503 when gRPC upstreams are missing. I hit this in the stage environment: frontend pods were running, but the lookup currencyservice timed out because currency and catalog had not been promoted yet. Deploy order: redis → catalog → currency → cart → frontend.

4. Managed observability vs self-hosted Prometheus

Both AWS and Azure deploy kube-prometheus-stack (Prometheus, Grafana, Alertmanager) through Argo CD or Helm bootstrap.

On GCP, I enabled GKE Managed Prometheus and configured Cloud Logging for log search. This removes a heavy in-cluster monitoring stack and aligns with the cost-first design, but it also means giving up the out-of-the-box Grafana experience provided by kube-prometheus-stack. Rejected: self-hosted kube-prometheus-stack on the cluster (the AWS/Azure pattern).

5. GitOps shape: ApplicationSet matrix

The ApplicationSet generates frontend-dev, cartservice-stage, and so on from an env × service matrix. Prod apps intentionally do not auto-sync; you merge a promotion PR, then sync in the Argo CD UI.

6. Promotion without rebuilding

All three repos share the same promotion model:

Build once in dev → copy image by digest → update GitOps values → Argo syncs

On GCP, gcloud artifacts docker images copy is not available in recent gcloud SDK releases; gcrane is the registry-to-registry path that works. The workflow opens a PR; stage auto-syncs; prod waits for manual Argo sync and optional GitHub Environment approval.

  • No rebuild on promote — same digest, different Artifact Registry repo.
  • **stage**: auto-sync after PR merge.
  • **prod**: manual Argo Sync + GitHub prod environment approval on the promote workflow.

A promotion copies frontend at digest sha256:abc123… from boutique-dev to boutique-stage. The Git diff is a one-line digest of changes in YAML.

7. Supply chain on GCP

The reusable CI workflow runs syft (SBOM), Trivy (fail on HIGH/CRITICAL), cosign sign, and creates Binary Authorization attestations. Kyverno enforces digest-only images and registry allowlists at admission time. Of the three siblings, this repo has the deepest supply-chain stack — aligned with GCP’s Binary Authorization model.

No rebuild on promote — same digest, different Artifact Registry repo.

Side-by-side comparison

I also maintain lighter multi-env IaC repos (aws-multi-env-iac, azure-multi-env-iac) for Terraform-only foundations. The three micser-apps-* repos are the full application + GitOps + observability stacks.

The ingress/TLS row alone shows the point: ALB + ACM on AWS, NGINX + cert-manager on Azure, Gateway API + Certificate Manager on GCP — same user-facing requirement, three operational models.

Lessons Learned While Building on GCP

These are documented in the phased implementation guide under docs/implementation/ — seven phases from Terraform through teardown. Run Phase 7 when the lab is finished; see docs/cost/teardown.md for cost notes (approximately $110–235 per month for a single private GKE cluster, depending on node sizing and usage patterns).

Wildcard *.biroltilki.art does not match the apex.

Who is this for?

  • Platform engineers comparing cloud-native ingress and identity models.
  • DevOps practitioners building a portfolio repo with real CI/CD and GitOps, not a tutorial that stops at docker run.
  • Hiring managers and technical reviewers who want to see the same problem solved three ways with explicit trade-offs.

Try it yourself

  1. Clone micser-apps-GCP-Terraform-Helm-GitOps-monitoring.
  2. Start at docs/implementation/README.md — follow the phases in order, from GCP project creation through prod promotion.
  3. Compare with the AWS and Azure READMEs and architecture docs.

The goal was never to build three identical repositories. The goal was to express the same reference architecture — build once, promote by digest, and deploy through GitOps — in the native idioms of each cloud provider. GCP is the most Google-native of the three; AWS and Azure remain the counterpoints that make the design choices visible.

License: Apache 2.0. Application code under apps/ retains Google LLC copyright from the upstream microservices-demo project.

Author: Birol Tilki


메타데이터
post_id
fe26d6f0a96c
slug
i-built-the-same-kubernetes-platform-on-aws-azure-and-gcp-gcp-was-the-weird-one-fe26d6f0a96c
url
https://medium.com/@btilki/i-built-the-same-kubernetes-platform-on-aws-azure-and-gcp-gcp-was-the-weird-one-fe26d6f0a96c
canonical_url
https://medium.com/@btilki/i-built-the-same-kubernetes-platform-on-aws-azure-and-gcp-gcp-was-the-weird-one-fe26d6f0a96c
author_url
https://medium.com/@btilki
status
ok
fetched_at
2026-06-12 10:20:10