Python Ops and GCP MIGs — the instance that is not a name
Yesterday's instance name is gone. The group is still the target.
<!-- hal:authoritative:yaml -->
Yesterday's instance name is gone. The group is still the target.
§I — Frame
Monday's lesson named a hop that dropped a wait. CloudFormation held a pre-signed URL. A Lambda that raised without a PUT left the stack in CREATE_IN_PROGRESS. Coin of that day: the signal that must still fire. The cloud seat was AWS because the Cert seat was DOP.
Today the Cert seat is GCP PCA, the second visit. 08-08 opened the hierarchy. Today the Compute surface is a managed instance group. The hop is the other way around. You held a name last night: web-instance-abcd. Autohealing replaced the VM. The name is gone, or worse, a new VM is wearing a name that looks like the old one. A tool that SSHes to last night's string is talking to a ghost, or to a stranger.
The Bootcamp Ch.2 lab mints that world in order (Chapter-2/README.md). An instance template is the type. A MIG named web-mig is created from web-template with --base-instance-name web-instance and --size 2. A health check named basic-check is attached for autohealing with a 300-second initial delay. Autoscaling sits on CPU. An HTTP load balancer is then stacked: global address, backend service, URL map, target HTTP proxy, forwarding rule. The members are not the architecture. The group is.
Name the duty. Coin it: the instance that is not a name.
The Python ops tool talks to the Compute Engine API (google-cloud-compute, or the discovery client if that is what the fleet already imports). It lists managed instances on the group. It does not get a VM by last night's last path segment and call the job done. Health is a group signal: instance_status and current_action on each member, plus the health check the MIG was given, plus the health check the backend service was given. Those last two are different configs even when a lab reuses one resource.
08-08 already taught a retry budget and a circuit breaker against Google APIs. This lesson does not reopen that client. It assumes the call can be made. The question is which resource you name in the request.
§II — Foundations: four facts about the group
Fact one. The template is the type. The member is disposable.
Ch.2 Step 3 creates web-template: machine type, image family, metadata startup script, network tag http-server. Step 4 creates web-mig from that template. The MIG's job, in the lab's own words, is to manage the VMs, keep them available, and enable autohealing and autoscaling.
A list of VM names is a census of tonight. It is not the type. Recreate, rolling replace, and autoheal all mint new names under --base-instance-name. The self-link of a member looks stable until it is not:
https://www.googleapis.com/compute/v1/projects/P/zones/us-central1-a/instances/web-instance-abcd
The last segment is a label the group assigned. It is not a primary key you get to keep.
The adjacent PCA note on VMs is the same idea at a different verb: restoring a VM from a snapshot gives you a new VM with a different IP by default (GCP-PCA-Notes, VMs). The bytes came back. The address did not. A name-shaped cache loses both stories the same way.
**Fact two. list_managed_instances is the census. instances.get is a name.**
The zonal client is compute_v1.InstanceGroupManagersClient. The method is list_managed_instances. The request names project, zone, and instance_group_manager. It does not name a VM. The regional cousin is RegionInstanceGroupManagersClient and names a region instead of a zone. Ch.2's lab is zonal (--zone us-central1-a). A regional MIG is a different resource. Calling the zonal method with a regional name is a 404 that looks like "the group vanished."
Each item carries at least instance (the self-link), instance_status (the VM lifecycle: RUNNING, STAGING, STOPPING, and the rest of the Compute set), and current_action (NONE, CREATING, DELETING, RECREATING, REFRESHING, RESTARTING, VERIFYING, ABANDONING). NONE means the group is not doing work to that member right now. It does not mean the load balancer is sending it traffic.
instances.get(project, zone, instance) is legal. It is a name lookup. Use it after the group census, when you already have a self-link from this list, and only for a field the list did not give you. Do not start there.
Fact three. Health is a group signal, and it is two signals.
Ch.2 Step 5 creates basic-check on port 80. Step 6 attaches it to the MIG for autohealing (--health-check basic-check --initial-delay 300). Step 10 creates the backend service with --health-checks=basic-check and adds web-mig as the backend. The lab reuses one health-check resource for both jobs.
The PCA notes refuse that collapse when they speak in exam voice (GCP-PCA-Notes, Load Balancing / a few more details): when load balancing with MIG autohealing, two separate health checks are configured; autohealing is set on the MIG, the load balancer check is set on the load balancer; the autoheal check is about app responsiveness, not merely "the instance is running." A lab that reuses basic-check is a shortcut. An operator tool that prints one "healthy" boolean has already lost a clock.
Autohealing uses the MIG attachment. If that check fails past the initial delay, the group recreates the member. The new member has a new self-link. Yesterday's name is gone. The load balancer uses the backend-service attachment. If that check fails, the member is pulled from rotation and the group may still believe the VM is fine. Autoscaling is a third signal: Ch.2 Step 7 sets --target-cpu-utilization 0.6 with a 90-second cool-down. CPU is not HTTP. Three witnesses. One group.
Fact four. Target the group. Labels find; tags gate.
The request's identity is (project, zone|region, instance_group_manager). That is the handle you put in config, in the pager, and in the cache key. Member names are output.
The adjacent PCA note splits labels from tags (GCP-PCA-Notes, Billing and Resource Management). Labels are key:value pairs for search and cost. Tags are a network primitive; firewall rules match them. Ch.2's template sets --tags http-server, and Step 8's firewall allows tcp:80 to that tag. A label env:prod will not open port 80. A tool that filters "the prod instances" by tag is reading the wrong map.
Shielded VM is recorded as best practice in the same VMs note. It is a template flag, not a reason to pin a member name. Put it on web-template. Let the group mint the members.
§III — Worked example: list the group, refuse the leftover name
The fleet already knows web-mig in us-central1-a. Last night's runbook still has web-instance-abcd in a column titled "primary." Tonight the job is: show who is in the group, who is being recreated, and whether any leftover name is still a member.
from dataclasses import dataclass
from google.cloud import compute_v1
@dataclass(frozen=True)
class MemberView:
self_link: str
last_segment: str
instance_status: str
current_action: str
def last_segment(self_link: str) -> str:
return self_link.rstrip("/").rsplit("/", 1)[-1]
def list_zonal_members(project: str, zone: str, group: str) -> tuple[MemberView, ...]:
client = compute_v1.InstanceGroupManagersClient()
request = compute_v1.ListManagedInstancesInstanceGroupManagersRequest(
project=project,
zone=zone,
instance_group_manager=group,
)
views = []
for item in client.list_managed_instances(request=request):
link = item.instance
views.append(
MemberView(
self_link=link,
last_segment=last_segment(link),
instance_status=item.instance_status,
current_action=item.current_action,
)
)
return tuple(views)
def leftover_name(members: tuple[MemberView, ...], remembered: str) -> bool:
return all(m.last_segment != remembered for m in members)
The client is constructed once. The request names the group. Each MemberView keeps the self-link as the thing you would pass to a later instances.get, and the last segment as a display field. leftover_name is the pager line: if last night's string is in no row, the name is not a member. Autoheal already spent it.
A discovery-client fleet writes the same census as service.instanceGroupManagers().listManagedInstances(...). The resource is the group either way. Do not wrap instances().get() in a loop over a remembered name list and call it a MIG tool.
Print current_action next to instance_status. A member can be RUNNING and RECREATING at the edge of a replace. A member can be STAGING and CREATING after a scale-out. The group is the story; the pair of fields is the sentence.
If the on-call paste is a name and not a group, the tool's first job is to refuse the paste and ask for web-mig. Looking the name up with instances.get and then searching every MIG in the zone for that self-link is a recovery path, not the happy path. The happy path already knew the group.
Regional groups swap the client and the location field. Keep that on a different function. A boolean regional= that still calls InstanceGroupManagersClient is how you page a healthy regional MIG as missing.
§IV — Failure mode: the name that came back
The common bug is a dict keyed by last segment.
cache: dict[str, dict] = {}
def remember(members: tuple[MemberView, ...]) -> None:
for m in members:
cache[m.last_segment] = {
"status": m.instance_status,
"action": m.current_action,
"link": m.self_link,
}
def still_healthy(name: str) -> bool:
row = cache.get(name)
return bool(row) and row["status"] == "RUNNING" and row["action"] == "NONE"
At 01:00 web-instance-abcd is RUNNING / NONE. At 01:10 autoheal recreates it. At 01:12 the list no longer contains that segment. still_healthy("web-instance-abcd") is false because the key is gone, which happens to be the correct answer for a minute. At 01:20 the group mints a new member. Base names collide. web-instance-abcd is back, RUNNING / NONE, new self-link, new disks, new SSH host key, new process table. still_healthy returns true. The on-call thinks the old workload survived. The new VM is an empty Apache from Ch.2's startup script.
That is the instance that is not a name. Equality of the last segment is not identity of the member. The Dev lesson will say that in language. Here the cache is the bug.
A second failure is treating instances.get success as membership. The VM can exist after you abandon it, or during a delete that has not finished, or in a different group you are not paging. Membership is the list on the MIG you named.
A third failure is one health boolean. The MIG autoheal check is green, so the tool prints healthy, while the backend-service check has taken the member out of the URL map's serving set. Or the reverse: the load balancer still has a draining connection (PCA notes, connection draining) while autoheal has already scheduled a recreate. Print both attachments. Do not average them.
A fourth failure is yesterday's zone. web-mig in us-central1-a is not web-mig in us-central1-b. A zonal get against the wrong zone is a missing group. A regional MIG in us-central1 is not in any of those zone calls.
The tell is an SSH timeout to a name the dashboard still shows as a VM somewhere, or a 200 from a new instance that does not have last night's session. List the group. Diff self-links, not last segments.
§V — Pairing
Today's Dev lesson is the language form of the same coin. is is identity. == is equality. Default __eq__ is identity. A dataclass that compares by name will treat the recycled VM as the old object. __hash__ must follow __eq__. A WeakValueDictionary keyed by generation is optional furniture. The phrase is the same: the instance that is not a name.
Today's Cert lesson is PCA on the Ch.2 chain. Instance template, MIG, two health-check attachments, named ports, regional versus zonal, backend service, URL map, target proxy, forwarding rule. The exam will offer you a VM name and a group. The group is the answer. 08-08 stays closed: organization, folders, Shared VPC, org policy.
08-17 stays a prior-arc wait: the URL still had to be written. 08-08's retry budget still belongs on the client that calls list_managed_instances, not as today's topic. 08-14's child process is not this census.
§VI — Drills
web-instance-abcd. Autoheal recreated the member. list_managed_instances on web-mig no longer returns that last segment. The operator runs instances.get on the old name and gets a 404. What should the tool have queried, and what two fields on each item tell you the group is still working?project, zone, instance_group_manager). instance_status and current_action on each managed instance. The 404 is a name miss, not a dead MIG.basic-check to the MIG and to the backend service. An operator tool prints one "healthy" flag from the MIG attachment. The load balancer has taken the member out of rotation. Which Bootcamp note did the tool collapse, and what should it print instead?web-instance-abcd is RUNNING / NONE. The self-link in the cache is yesterday's URL. Today's list has the same last segment and a different self-link. Is the cached row the same instance? What is the cache key that would have refused the collision?Related
- Prior arc: the signal that must still fire (2026-08-17)
- Domain hub: Cross-References/domains/01-Earth-DevOps
- Grounding: Bootcamp Ch.2 MIG + HTTP LB lab · GCP PCA Notes (VMs; two health checks)
🫡 ⚖️ 📜 Leo.Syri — Praetor Consulate, Imperium Luminaura Filed 2026-08-20 · Fajr · sprint track Python day 29 · tenth Python visit · trio #95