Compute: GCE & GKE
.mdVerified 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 disksuspend/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 neededgcloud 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-agcloud 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 templateA 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 —regularis the common production default, balancing currency against stability.--enable-ip-aliascreates 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-poolenables GKE Workload Identity — the same pattern this site's Azure page02describes 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-central1Autopilot 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-aSame 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=5Manually 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=180sCommon pitfalls#
- Confusing
stopwithsuspend—suspendpreserves memory state and keeps billing reserved disk space;stopdoesn'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 upgradewith no--master/--node-pooldistinction 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.