Create a pool
The launch template, the sizing bounds, and how replicas get named.
Sizing and healing
What the pool converges, what it replaces, and the counter to alert on.
Rolling a template change
Why editing the template changes nothing yet, and what a refresh does.
A shared public address
Anycast across the replicas — and the ways it is not a load balancer.
Creating a pool
template is the launch config, in the same shape a
standalone instance create takes — same field
names, same types, same meanings. There is no separate launch-template resource
to create, version or share; the template belongs to the pool.
A flavor and a primary subnet are required: template.flavor_id and
template.networks[0].subnet_id. Index 0 is the primary NIC; the rest are
extras.
An older, flat shape —
flavor_id, subnet_id, extra_nics and friends at
the top level — still works, and the response renders both, so nothing
written against either has to move. They are alternatives, not layers:
sending template together with any flat launch field is a 400 rather than
a precedence rule you would have to know to predict what your replicas boot
as.Read template in responses. The flat fields are a projection of the same
stored config and cannot disagree with it.The image is resolved once
template.image_id takes the same three forms instance create does — an id,
name:version, or a bare name. Unlike instance create, the reference is
resolved once, when the pool is created (or when the template is replaced), and
the resulting image id is what every replica boots — including replacements
spawned months later.
That is deliberate. A tag re-resolved per replica would let a healed member
boot a newer build than its siblings, and a pool whose members are quietly not
identical is the premise of the primitive breaking silently. To move a pool to
a new build, change the template and refresh.
What the replicas are called
Each replica gets a sequence number, stable for as long as it holds the slot, and is named<pool-name>-<sequence_num> — web-asg-0, web-asg-1, and
so on. GET /v1/instance-pools/{pool_id}/instances lists the live bindings with
their sequence numbers; a replacement takes over the freed number.
Instance names are unique per account, so a pool called
web cannot coexist
with an instance you already named web-0. Pool names themselves are 1–127
characters of letters, digits, dot, dash and underscore, unique per account.Per-replica disks and addresses
template.data_volumes creates a blank volume with each replica and reclaims
it with that replica. delete_on_termination defaults to true; set it to
false and a replica scaled in, replaced, or torn down with the pool
releases its volume back to available instead of destroying it.
template.assign_public_ip gives each replica its own floating IP on its
primary NIC, allocated as the pool scales out and released as it scales in. A
NIC in template.networks[] carries its own flag, so a secondary interface can
be the public one. Each address counts against your floating_ips quota.
That is a different thing from the pool’s shared address —
see below.
Sizing and convergence
0–100
The target. This is the only sizing field you can change later.
fixed at create
Bounds on
desired_count. Both default to desired_count when omitted, and
neither is patchable — to change them, create a new pool.201 immediately. The instances are spawned by a
background reconciler, which is also what converges the pool toward
desired_count whenever you change it, so watch the pool rather than expecting
members in the create response.
Four counters tell you where a pool stands, and confusing two of them is the
usual source of a false alarm:
status: "active" means member_count == desired_count — the pool holds the
members it was asked for. It is not a claim that all of them are up. A
pool can be active with live_count below desired_count when members have
stopped. Read live_count for liveness.scaling means it does not hold its target and is converging: after a
create, after a desired_count change, and for the length of a refresh.
error carries error_message and is still reconciled — the pool keeps
being retried. deleting is a teardown in flight.error_message is cleared when the pool reaches its target, and is left in
place through a later resize: a pool that failed to spawn and is being scaled
again has not yet proved the failure is behind it.
What gets replaced, and what does not
Each pass, the pool replaces any member whosevm_state is error or
deleted, and any binding whose instance has been deleted out from under it.
The replacement takes the freed sequence number and launches from the pool’s
current template.
Scaling in removes the highest sequence numbers first, so a scale from 5 to
3 retires -4 and -3. Each retirement runs the full instance delete, so its
quota, its volumes and its addresses are handled exactly as for a standalone
instance.
Replicas are spread across hosts — best effort. Each new replica avoids the
hosts its siblings already occupy, but when the fleet has no room the spread is
dropped rather than the launch failing, so replicas can end up sharing a host.
Changing the template
PATCH /v1/instance-pools/{pool_id} changes desired_count, the pool’s tags,
the template, or any combination. Every field is optional; sending none of
them is a 400 rather than a silent no-op.
A template change decides what the pool launches next. The instances
already running keep what they booted with, because a live VM cannot change
flavor, tier, subnet or its tags in place.
So between the edit and a roll, the pool legitimately holds members from two
different templates. stale_instance_count is how many are on the older one,
and a non-zero value is the signal that a template change has not been rolled
out yet.
This is the same split between “edit the template” and “replace the
instances” that a
PATCH silently replacing every running member would erase
— a destructive operation wearing the shape of an edit.Rolling the pool
202 with the pool as it stands, and replaces every member not
launched from the current template — including any that predate template
tracking. Asynchronous, and deliberately so: each replacement is a VM boot, and
a request that waited would time out long before a pool of any size finished.
1
One member per pass
The reconciler retires one stale member at a time, oldest slot first, so
the roll walks the pool in a predictable order.
2
And only once the pool is whole
The next replacement waits until the pool is back at size with every member
running. A template that does not boot therefore stalls the roll with the
pool intact, instead of walking it down one instance at a time.
3
Capacity does not dip
For the length of the roll the pool runs one instance over its target, so a
replacement is already serving before anything is retired.
desired_count
is not touched — the surge is derived, not written into what you asked for.refresh_in_progress and stale_instance_count for progress. The pool
reads scaling for the duration and flips back to active only when nothing
is stale — calling it settled mid-roll would tell you a refresh had finished
while most of it had not started.
Asking again while a roll is running is accepted and does not restart it.
Two sets of tags
A pool carries two tag maps and they answer different questions.tags
Labels the pool resource. Read by IAM conditions as
basalt:ResourceTag/<key> and used for cost attribution. Takes effect
immediately, touches no instance, and replaces the whole set — an empty
object clears them, an omitted field leaves them alone.template.tags
Stamped on every replica the pool launches. Part of the launch config,
so changing it affects future launches only and needs a refresh to reach
what is already running.
template.tags alone is the easiest way to end up with a pool whose
members carry two different tag sets — which matters if an IAM policy or a cost
report keys on them. stale_instance_count is how many are still on the old
set.
One address for the whole pool
POST /v1/instance-pools/{pool_id}/floating-ips binds a floating IP you
already allocated to the pool. One public IP, answered by every replica — an
anycast address — as opposed to template.assign_public_ip, which gives each
replica its own.
The operation names the pool because the pool is what makes the address
work. Each member’s translation rule is matched per hypervisor, so two
members on one hypervisor both match there: one answers and the other
receives nothing, with no signal anywhere that half the capacity is dark.A pool spreads its replicas across hypervisors, and the address’s members are
derived from the live ones — one per hypervisor. A replica sharing a
hypervisor with an existing member is left out rather than added and
silently starved, so the address can have fewer members than the pool has
replicas.
members on the floating IP says which.It is not a load balancer
With more than one member, the region’s edge picks one member per connection, by hashing the flow’s addresses and ports, and every packet of that connection goes to the same one. That spreads connections across independent instances and survives the loss of a host.Requirements and removal
The floating IP must be unattached and yours, and the pool’s subnet must already route0.0.0.0/0 to an internet gateway. Attaching is idempotent:
re-attaching the same address to the same pool returns it unchanged. A 409
means the address is already attached to something, or already belongs to
another pool.
DELETE /v1/instance-pools/{pool_id}/floating-ips/{floating_ip_id} stops
routing the address to the pool.
The address is not released. You allocated it, it stays yours and
unattached, to reuse or to release with
DELETE /v1/floating-ips/{floating_ip_id}. Detaching one the pool does not
hold answers 204.GET /v1/instance-pools/{pool_id}/floating-ips lists the shared addresses with
their current members. Per-replica addresses are not here — those belong to the
replica and are read from the instance’s
NIC listing.
Deleting a pool
DELETE /v1/instance-pools/{pool_id} tears down every instance the pool owns
and drops the pool. It is idempotent, and answers 204.
Deleting the pool is the only way to remove its members: deleting a replica
directly just frees its sequence number, and the pool spawns a replacement for
it on the next pass.
Permissions
Every pool operation authorizes againstcrn:compute:<region>:<account>:instance-pool/<id>
— that is the value an IAM policy statement must name to scope a permission to
one pool.
A refresh authorizes as
compute:UpdateInstancePool, the same action as
a PATCH, not as an action of its own. Granting someone the ability to edit
a pool therefore also grants them the ability to roll it, which replaces
every running member.compute:CreateInstance permission — and its iam:PassRole on
template.iam_role_id, if the template carries one — is what every later heal
and scale-out runs under. See policies and
roles.
Troubleshooting
The pool says active but capacity is down
The pool says active but capacity is down
active means member_count == desired_count, not that the members are
up. Read live_count. A gap between them is members that exist and are not
running — stopped, still booting, or wedged — and the pool does not replace
a stopped member.A refresh is not progressing
A refresh is not progressing
The roll waits for the pool to be back at size with every member
running before retiring the next one. If the new template does not boot,
the roll stalls there by design rather than emptying the pool — check the
newest replica’s
fault and its
console output.stale_instance_count stops falling as soon as that happens, and
refresh_in_progress stays true.I edited the template and nothing changed
I edited the template and nothing changed
Expected. A template change decides what the pool launches next; the
instances already running keep what they booted with.
stale_instance_count
counts them, and POST /v1/instance-pools/{pool_id}/refresh rolls them.The template lost a NIC or a data volume
The template lost a NIC or a data volume
template on a PATCH replaces the stored config wholesale — anything
omitted is cleared. Read the pool back, edit the whole template object
you got, and send all of it.Attaching a floating IP to the pool answers 400
Attaching a floating IP to the pool answers 400
Three causes, and the message says which: the id is missing or malformed;
the pool’s subnet has no default route to an internet gateway; or the
region does not have shared pool addresses switched on. A
409 is
different — that is an address already attached to something else, or
already held by another pool.A replica I deleted came back
A replica I deleted came back
The pool converges toward
desired_count, so deleting a member is read as
drift and refilled at the freed sequence number. Lower desired_count, or
delete the pool.Creating the pool answers 400 on min or max
Creating the pool answers 400 on min or max
desired_count must sit within min_count and max_count, and none may
exceed the platform cap of 100. Both bounds default to desired_count when
omitted and are fixed for the pool’s life — a pool created without
max_count cannot later grow past the size it was created at.Next
Instances
Everything a replica is: flavors, images, disks, interfaces and the
lifecycle.
Images
Pinning a build so a pool’s replicas stay identical.