← Back to posts

GCP ACE: Managed Instance Groups and Health Checks

A Managed Instance Group (MIG) is how Compute Engine runs a fleet of identical VMs as a single resource: autoscaling, autohealing, rolling updates and multi-zone distribution all come from the group, not from any individual VM. MIGs are also the standard backend for a load balancer, but that part of the picture isn’t covered here — this post is about the group itself, its templates, and the health checks that keep it honest.

Instance templates

Every MIG is built from an instance template: a reusable definition of machine type, boot disk, network interfaces, metadata, labels, service account and scopes, GPUs, and Shielded VM settings. Templates are immutable — you can’t edit one after creation. To change anything, you create a new template version and point the MIG at it; the old template stays around until nothing references it.

gcloud compute instance-templates create web-template-v2 \
  --machine-type=e2-medium \
  --image-family=debian-12 \
  --image-project=debian-cloud \
  --network=default \
  --tags=allow-health-check \
  --metadata=startup-script='#! /bin/bash
apt-get update && apt-get install -y nginx'

Templates are global by default, usable from any region. A regional instance template exists too, and you need it when the template references something region-scoped — a reserved internal IP, a regional persistent disk, or a specific subnet that only exists in one region.

Creating the group

MIGs come in two shapes:

gcloud compute instance-groups managed create web-mig \
  --template=web-template-v2 \
  --size=3 \
  --region=europe-southwest1

Size limits that show up on the exam: a zonal MIG supports up to 1,000 instances; a regional MIG supports up to 2,000 instances. Both are soft limits — Cloud Support can raise them on request, but don’t expect the exam to test the exception, just the default ceiling.

Target distribution shape (regional MIGs)

A regional MIG’s --target-distribution-shape decides how it spreads instances across zones:

ShapeBehavior
EVEN (default)Keeps instance counts within 1 of each other across zones. Required for sole-tenant VM groups.
BALANCEDPrioritizes getting capacity where it’s available, while still spreading across zones to limit the blast radius of a zonal failure.
ANYPicks zones purely to maximize use of available resources/reservations — no HA guarantee. Good for batch workloads.
ANY_SINGLE_ZONEPuts the whole group in one zone, chosen for resource availability. For batch jobs with heavy inter-instance communication.

BALANCED and ANY_SINGLE_ZONE require disabling proactive instance redistribution.

Autoscaling

The autoscaler adds or removes instances between --min-num-replicas and --max-num-replicas based on one or more signals: CPU utilization, HTTP load balancing serving capacity, a Cloud Monitoring metric, or a schedule.

gcloud compute instance-groups managed set-autoscaling web-mig \
  --region=europe-southwest1 \
  --min-num-replicas=2 \
  --max-num-replicas=10 \
  --target-cpu-utilization=0.6 \
  --cool-down-period=90

--cool-down-period is the time the autoscaler gives a new instance to initialize before counting its metrics — default 60 seconds. Set it longer if your app has a slow startup (JVM warm-up, cache priming, etc.), or the autoscaler will judge a still-booting instance as underutilized and scale down prematurely.

--mode controls what the autoscaler is allowed to do:

ModeBehavior
ON (default)Scales out and in according to the policy.
ONLY_SCALE_OUTAdds instances when load rises but never shrinks the group. Useful to protect capacity, for example during a rollout or while investigating VMs.
OFFTemporarily disables autoscaling. The configuration is kept, so you can turn it back on later.

In every mode the autoscaler keeps calculating a recommended group size, so you can see what it would do even when it’s off. Separately, --scale-in-control caps how fast the group shrinks, which helps avoid flapping.

Autohealing

Autohealing is what makes a MIG self-repairing at the application level, as opposed to only the infrastructure level. Compute Engine already restarts a VM that stops running; autohealing goes further and replaces a VM whose app has stopped responding correctly, even though the VM itself looks fine.

gcloud compute instance-groups managed update web-mig \
  --region=europe-southwest1 \
  --health-check=web-autohealing-check \
  --initial-delay=300

--initial-delay is the grace period (in seconds) the MIG waits after an instance is created before it starts counting health check failures against it — this stops a VM from being killed mid-boot. There’s no fixed exam-tested default; the practical pattern is to set it a bit longer than your app’s real startup time (300s is the commonly used example in Google’s own docs).

If a VM fails the attached health check for the configured unhealthy threshold, the MIG deletes it and creates a replacement from the current instance template — same as a normal scale-up, not a reboot.

Health checks

This is the part that trips people up on the exam: the same health check resource can be used two different ways, and the two uses answer different questions.

You can point both uses at the same health check object, or use two separate ones with different thresholds — e.g., a lenient LB health check that pulls an instance out quickly, and a stricter autohealing check that only replaces it after a longer, more certain failure pattern, so you don’t churn VMs over a brief blip.

Creating a health check

gcloud compute health-checks create http web-health-check \
  --port=80 \
  --request-path=/healthz \
  --check-interval=10s \
  --timeout=5s \
  --healthy-threshold=2 \
  --unhealthy-threshold=3

Defaults if you don’t override them: --check-interval=5s, --timeout=5s, --healthy-threshold=2, --unhealthy-threshold=2. healthy-threshold is the number of consecutive successes needed to mark an unhealthy instance healthy again; unhealthy-threshold is consecutive failures before it’s marked unhealthy.

Supported protocols: HTTP, HTTPS, HTTP/2, TCP, SSL (TLS), and gRPC. Pick the protocol that matches what the backend actually serves — an HTTPS backend behind an HTTP health check will often fail in ways that are confusing to debug.

Legacy vs modern health checks

Older http-health-checks / https-health-checks resources (no health-checks in the command) still exist and are what some regional external passthrough Network Load Balancers require, since they only support legacy checks via target pools. Everywhere else — HTTP(S), TCP, SSL, gRPC, all the current load balancer types, and autohealing — use the modern, unified health-checks resource. Default to the modern one unless something specifically forces you into target pools.

The firewall rule nobody remembers

Health check probes come from Google’s own IP ranges, not from the backend’s network, so they need an explicit ingress firewall rule or they’ll be silently dropped — which looks exactly like “my app is down” in the backend service’s health state.

gcloud compute firewall-rules create fw-allow-health-check \
  --network=default \
  --direction=INGRESS \
  --action=ALLOW \
  --rules=tcp:80 \
  --source-ranges=130.211.0.0/22,35.191.0.0/16 \
  --target-tags=allow-health-check

130.211.0.0/22 and 35.191.0.0/16 are the standard ranges for proxy-based and most modern health checks. This is one of the highest-yield things to just memorize for the exam — “instances are healthy per the app but the LB/MIG shows them unhealthy” is almost always this firewall rule missing.

Updating a MIG

Changing the instance template doesn’t touch running instances by itself — you have to trigger a rollout.

gcloud compute instance-groups managed rolling-action start-update web-mig \
  --region=europe-southwest1 \
  --version=template=web-template-v2 \
  --max-surge=2 \
  --max-unavailable=0

You can also run a canary update: roll the new template out to a fixed number or percentage of instances first with --canary-version, watch it, then promote.

Instance redistribution (regional MIGs)

A regional MIG can proactively move instances between zones to keep the target distribution shape balanced — e.g., after you add a new zone, or after a zone outage ends and the group rebalances back. --instance-redistribution-type=PROACTIVE (default) does this automatically; NONE leaves existing instances where they are and only affects placement of new ones.

Stateful MIGs

By default a MIG is stateless: any instance can be deleted and replaced, and nothing instance-specific survives. A stateful MIG preserves per-instance state across recreation — attached non-boot persistent disks, instance name, and/or specific metadata — for workloads like a cluster of brokers that each need their own data and identity. You configure this per-instance or at the MIG level with --stateful-disk. It’s less common on the exam than the stateless path, but you should recognize “preserve instance names/disks across restarts” as the cue for it.

Standalone instance groups vs MIGs

An unmanaged / standalone instance group is just a flat list of existing VMs grouped together — useful mainly for load-balancing a set of heterogeneous legacy VMs that weren’t built from a common template. It gets none of the MIG machinery: no autoscaling, no autohealing, no rolling updates, no instance templates. If a question mentions any of those features, the answer is a MIG; if it explicitly says the VMs are different configurations that already exist and just need to sit behind a load balancer, that’s the standalone case.

Named ports

A MIG exposes named ports — a label-to-port mapping (e.g., http:80) that a backend service references instead of a raw port number. This is what lets a load balancer’s backend service know which port on each instance to actually send traffic to, independent of the instance’s own configuration.

gcloud compute instance-groups managed set-named-ports web-mig \
  --region=europe-southwest1 \
  --named-ports=http:80,https:443

Exam Takeaways

Sources

↗ LinkedIn