Verified18 commandsAI-assisted

Compute: GCE & GKE

.md

Verified against Google Cloud SDK 553.0.0, flags verified via `gcloud compute instances create · official docs

What it is and where it fits 🎯#

Compute Engine (GCE) is GCP's IaaS VM primitive — the equivalent of an EC2 or Azure VM — and GKE (Google Kubernetes Engine) is GCP's managed Kubernetes offering, notable for having invented much of what "managed Kubernetes" means (Google originated Kubernetes itself). This page assumes the project/auth basics from page 01. Cloud Run — GCP's fully-managed container platform, sitting one layer above raw compute — is covered at the end, since it's frequently the better default for a stateless HTTP service that doesn't need Kubernetes at all.

Creating a VM instance#

gcloud compute instances create my-instance \
  --zone=us-central1-a --machine-type=e2-medium \
  --image-family=debian-12 --image-project=debian-cloud \
  --network=my-network --subnet=my-subnet --tags=web-server \
  --service-account=my-service@my-project-id.iam.gserviceaccount.com \
  --scopes=cloud-platform

--image-family tracks the latest image in a family automatically (e.g. debian-12 always resolves to the newest Debian 12 build) — pin --image to an exact image name instead when you need a reproducible, non-drifting build for something like a golden-image pipeline. --service-account + --scopes=cloud- platform attaches an identity the instance authenticates as by default, with IAM (not instance scopes) doing the actual access control — the modern pattern; cloud-platform is the broad scope that defers all real restriction to IAM roles on the service account itself.

gcloud compute instances create batch-worker --zone=us-central1-a \
  --machine-type=e2-standard-4 --provisioning-model=SPOT \
  --instance-termination-action=DELETE --max-run-duration=6h

--provisioning-model=SPOT (the current name for what used to be called "preemptible") requests significantly cheaper, interruptible capacity — appropriate for batch/CI workers, not for anything that can't tolerate a sudden termination with roughly 30 seconds' notice.

Listing and inspecting instances#

gcloud compute instances list
gcloud compute instances list --filter="zone:us-central1-a"
gcloud compute instances describe my-instance --zone=us-central1-a
gcloud compute instances describe my-instance --zone=us-central1-a --format="value(status)"

Starting, stopping, and deleting instances#

gcloud compute instances start my-instance --zone=us-central1-a
gcloud compute instances stop my-instance --zone=us-central1-a
gcloud compute instances suspend my-instance --zone=us-central1-a    # preserves memory to disk, faster resume than stop
gcloud compute instances resume my-instance --zone=us-central1-a
gcloud compute instances delete my-instance --zone=us-central1-a
gcloud compute instances delete my-instance --zone=us-central1-a --keep-disks=boot   # delete VM, keep its boot disk

suspend/resume is GCE's closer analogue to a laptop's sleep — it preserves the VM's in-memory state to persistent disk and can resume faster than a cold start, at the cost of continuing to be billed for the reserved disk space while suspended (compute billing stops, similar to Azure's deallocate).

SSHing into an instance#

gcloud compute ssh my-instance --zone=us-central1-a
gcloud compute ssh my-instance --zone=us-central1-a --tunnel-through-iap   # no external IP/firewall rule needed

gcloud compute ssh handles SSH key generation and propagation to the instance metadata automatically — --tunnel-through-iap is the standard pattern for reaching instances with no public IP, tunneling through Identity-Aware Proxy instead of opening an SSH-facing firewall rule.

Managed disks and snapshots#

gcloud compute disks create my-data-disk --zone=us-central1-a --size=128GB --type=pd-ssd
gcloud compute instances attach-disk my-instance --zone=us-central1-a --disk=my-data-disk
gcloud compute disks list --filter="-users:*"                          # unattached (orphaned) disks

gcloud compute disks snapshot my-data-disk --zone=us-central1-a --snapshot-names=my-data-disk-snap
gcloud compute disks create restored-disk --source-snapshot=my-data-disk-snap --zone=us-central1-a

gcloud compute disks list --filter="-users:*" finds disks with no attached instance — the GCP equivalent of Azure's diskState=='Unattached' check, worth running periodically since a detached persistent disk keeps billing until explicitly deleted, the same silent-cost trap covered on the Azure compute page.

Instance templates and Managed Instance Groups (MIGs)#

gcloud compute instance-templates create my-template \
  --machine-type=e2-medium --image-family=debian-12 --image-project=debian-cloud \
  --network=my-network --subnet=my-subnet

gcloud compute instance-groups managed create my-mig \
  --template=my-template --size=3 --zone=us-central1-a

gcloud compute instance-groups managed set-autoscaling my-mig \
  --zone=us-central1-a --min-num-replicas=2 --max-num-replicas=10 --target-cpu-utilization=0.6

gcloud compute instance-groups managed rolling-action start-update my-mig \
  --zone=us-central1-a --version=template=my-new-template   # rolling replace onto a new template

A Managed Instance Group is GCP's autoscaling VM group primitive — the equivalent of an Azure VMSS or an AWS Auto Scaling Group. Instance templates are immutable once created (you can't edit one in place); the rolling update above is how you deploy a change — create a new template, then roll the MIG onto it, rather than mutating instances directly.

Creating a GKE cluster#

gcloud container clusters create my-cluster \
  --zone=us-central1-a --num-nodes=3 --machine-type=e2-medium \
  --release-channel=regular --enable-ip-alias --workload-pool=my-project-id.svc.id.goog
  • --release-channel (rapid, regular, stable) hands Kubernetes version management to Google on a schedule rather than you pinning an exact version — regular is the common production default, balancing currency against stability.
  • --enable-ip-alias creates a VPC-native cluster, giving pods real routable IPs from a secondary subnet range instead of an overlay network — the current recommended default for new clusters, required for several GKE networking features.
  • --workload-pool enables GKE Workload Identity — the same pattern this site's Azure page 02 describes for AKS: a Kubernetes service account federates to a GCP IAM service account, so pods get real GCP credentials with no key file, ever. This has replaced the older node-scoped service-account pattern (every pod on a node sharing the node's broad identity) as the recommended default.

Autopilot vs. Standard mode#

gcloud container clusters create-auto my-autopilot-cluster --region=us-central1
Diagram

Autopilot manages node provisioning, sizing, and most security hardening for you and bills per-pod (CPU/memory/storage requested), rather than per-node; Standard gives full control over node pools, machine types, and node-level customization but requires you to actually manage capacity. You cannot convert an existing cluster between modes — the decision has to be made at creation time, and switching later means standing up a new cluster and migrating workloads. Reach for Standard specifically when you need GPUs beyond what Autopilot supports, DaemonSets, privileged containers, or direct node SSH access; default to Autopilot otherwise, per Google's own current guidance.

Getting kubectl credentials for a cluster#

gcloud container clusters get-credentials my-cluster --zone=us-central1-a

Same role as az aks get-credentials / aws eks update-kubeconfig — writes/merges a kubeconfig entry so kubectl can authenticate to the cluster; it doesn't create or modify anything in the cluster itself.

Listing clusters and node pools#

gcloud container clusters list
gcloud container node-pools list --cluster=my-cluster --zone=us-central1-a
gcloud container clusters describe my-cluster --zone=us-central1-a --format="value(currentMasterVersion)"

Resizing a cluster's node pool#

gcloud container clusters resize my-cluster --zone=us-central1-a --num-nodes=5
gcloud container clusters resize my-cluster --zone=us-central1-a --node-pool=default-pool --num-nodes=5

Manually resizing a node pool is a one-time operation, not a standing policy — if the cluster has a Cluster Autoscaler configured, a manual resize can be immediately reverted by the autoscaler unless you also adjust its min/max node bounds.

Creating and autoscaling node pools#

gcloud container node-pools create high-mem-pool \
  --cluster=my-cluster --zone=us-central1-a \
  --machine-type=e2-highmem-4 --node-locations=us-central1-a,us-central1-b \
  --num-nodes=1 --enable-autoscaling --min-nodes=1 --max-nodes=5

gcloud container node-pools create spot-pool \
  --cluster=my-cluster --zone=us-central1-a \
  --spot --machine-type=e2-standard-4 \
  --node-taints=cloud.google.com/gke-spot=true:NoSchedule \
  --enable-autoscaling --min-nodes=0 --max-nodes=20

gcloud container clusters update my-cluster --zone=us-central1-a \
  --node-pool=default-pool --enable-autoscaling --min-nodes=1 --max-nodes=10

gcloud container clusters update my-cluster --zone=us-central1-a \
  --node-pool=default-pool --no-enable-autoscaling   # turn autoscaling back off

--node-locations on a node pool spreads its nodes across multiple zones within the cluster's region — useful for zonal-failure resilience even on a "zonal" cluster. Autoscaling is a per-node-pool setting, not per-cluster: clusters update needs --node-pool to target the pool you're actually changing bounds on. --spot combined with --node-taints is GKE's version of the Spot-plus-taint pattern this site's Azure page 02 shows for AKS — keep interruptible capacity dedicated to workloads that explicitly tolerate it via a matching toleration, --min-nodes=0 lets the pool cost nothing when idle.

Upgrading clusters and node pools#

gcloud container get-server-config --zone=us-central1-a --format="value(validMasterVersions)"
gcloud container clusters upgrade my-cluster --zone=us-central1-a --master --cluster-version=1.31.1
gcloud container clusters upgrade my-cluster --zone=us-central1-a --node-pool=default-pool

--master upgrades the control plane only; omitting it (with --node-pool specified) upgrades that node pool instead — the two are separate operations by design, the same control-plane-vs-node-pool split covered for AKS on the Azure compute page, letting you validate a new control-plane version before touching worker nodes.

Real-world scenario: rolling out a change to a MIG without downtime#

A fleet of stateless API workers behind a load balancer needs a new instance template rolled out gradually, not all at once:

gcloud compute instance-templates create api-template-v2 --machine-type=e2-standard-2 \
  --image-family=debian-12 --image-project=debian-cloud

gcloud compute instance-groups managed rolling-action start-update my-mig \
  --zone=us-central1-a --version=template=api-template-v2 \
  --max-surge=2 --max-unavailable=0

--max-unavailable=0 combined with --max-surge=2 keeps full serving capacity throughout the rollout — new instances on the new template come up before old ones are torn down, rather than replacing capacity in place, avoiding the brief dip in available backends a naive in-place rolling update would cause.

Real-world scenario: private GKE nodes reachable only through IAP#

A security review flagged that every GKE node had a public IP reachable for SSH debugging — closing that down while keeping operator access:

gcloud container clusters create my-private-cluster \
  --zone=us-central1-a --enable-private-nodes \
  --master-ipv4-cidr=172.16.0.0/28 --enable-ip-alias

gcloud compute ssh <node-name> --zone=us-central1-a --tunnel-through-iap

--enable-private-nodes removes public IPs from worker nodes entirely; IAP tunneling (already the standard SSH pattern from earlier in this page) is what restores operator reachability without reopening a public attack surface — the access path goes through IAM-authorized IAP, not an open firewall rule.

Cloud Run — deploying and managing services#

gcloud run deploy my-service \
  --image=us-docker.pkg.dev/my-project/my-repo/my-image:latest \
  --region=us-central1 --allow-unauthenticated \
  --memory=512Mi --cpu=1 --min-instances=0 --max-instances=10 --concurrency=80

gcloud run deploy my-service --source=. --region=us-central1   # build from local source instead of a pre-built image

gcloud run services list --region=us-central1
gcloud run services describe my-service --region=us-central1
gcloud run services update-traffic my-service --region=us-central1 \
  --to-revisions=my-service-00002-abc=10,my-service-00001-xyz=90   # canary: 10% to the new revision

--source=. hands the build off to Cloud Build automatically (using Buildpacks or a Dockerfile if present) — no separate docker build/docker push step needed. --allow-unauthenticated makes the service publicly reachable; omit it (or pass --no-allow-unauthenticated) to require IAM-authenticated callers, the default and the safer starting point for anything not meant to be public.

Tip

For a stateless HTTP service with no genuine need for Kubernetes-level control (custom scheduling, DaemonSets, a service mesh), default to Cloud Run over standing up a GKE cluster — it scales to zero, requires no node/cluster management at all, and the --source=. build-from-source path is meaningfully less CI/CD plumbing than a container-build-and-kubectl apply pipeline.

CI/CD integration recipe: GitHub Actions deploy to GKE#

# .github/workflows/deploy-gke.yml
name: Deploy to GKE
on:
  push:
    branches: [main]

permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: google-github-actions/auth@v2
        with:
          workload_identity_provider: projects/<project-number>/locations/global/workloadIdentityPools/github-pool/providers/github-provider
          service_account: ci@my-project-id.iam.gserviceaccount.com
      - uses: google-github-actions/get-gke-credentials@v2
        with:
          cluster_name: my-cluster
          location: us-central1-a
      - run: kubectl apply -f k8s/
      - run: kubectl rollout status deployment/my-app --timeout=180s

Common pitfalls#

  • Confusing stop with suspendsuspend preserves memory state and keeps billing reserved disk space; stop doesn't preserve memory but stops compute billing entirely, closer to a true power-off.
  • Leaving orphaned disks after instance deletion — check gcloud compute disks list --filter="-users:*" periodically; a disk isn't deleted automatically unless the instance was created with the disk set to auto-delete (the default for a boot disk, not for most attached data disks).
  • Manually resizing an autoscaled node pool/MIG — the autoscaler can revert it; adjust min/max bounds instead.
  • Expecting gcloud container clusters upgrade with no --master/--node-pool distinction to behave like a single atomic operation — control plane and node pool upgrades are separate calls by design.
  • Standing up GKE Standard for a workload that would run fine on Cloud Run or Autopilot — see the TIP above; Kubernetes-level control is a real cost most stateless HTTP services don't need to pay.

Exit codes#

0 success · non-zero on any API/validation error — a gcloud container clusters create that times out waiting for provisioning still exits non-zero even if the cluster eventually finishes creating in the background; check gcloud container operations list to confirm actual state rather than trusting only the command's own exit code for a long-running operation.

When to reach for something else#

Use kubectl/Helm/GitOps tooling for anything happening inside a cluster once it exists — gcloud container commands provision and manage the cluster and node pool shape, not workloads running on it. For declarative, reviewable provisioning across environments, prefer Terraform's google_container_cluster resource over a growing shell script of gcloud container/gcloud compute calls, consistent with page 01's IaC guidance.