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
- Console
- API
- CLI
- Go
Go to Compute → Instance pools and choose Create instance pool.
After Details comes a Scaling card with Min, Desired and
Max; the rest of the form is the launch template, card for card the
same as creating one instance — Flavor, Image, Boot volume,
Data volumes, Networking, IAM role,
User data.Min is the floor and Max is the ceiling. You can change both
later with Scale.The two tag maps below are named for the difference between them:
Instance tags are stamped on every replica the pool launches, while
Pool tags label the pool itself and reach none of its instances.
template configures the members launched by the pool. Select one or more top-level subnets in the same account, region and VPC. Each member has one interface; new members go into the least-populated selected subnet. The template controls its security groups, address families and optional per-member floating IPs. A template.flavor is required.
IAM role references and responses
Create and replacement requests accepttemplate.iam_role as a UUID, CRN or
exact name string for a role in your account. Attaching the role requires
iam:PassRole and instance trust authorization; see
Roles and instance identity.
Pool responses return an optional role summary inside template, instead of
iam_role_id. This response excerpt shows the attachment identity:
id, crn and optional name, and is visible with pool read access without iam:GetRole. template.iam_role is null when no role is attached. If a referenced role is deleted, its identity remains and its name is omitted. Opening the role’s IAM details still requires iam:GetRole.
Subnet references and responses
Create and update requests use top-levelsubnets string selectors. Responses return subnets, vpc, template.flavor, template.image, template.iam_role and template.security_groups as resource references with id, crn and optional name. Fetch a subnet for its CIDRs and routing details. Convert references to id or crn when sending requests. Response excerpt:
The image is resolved once
template.image 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. A replacement takes over the freed number.
GET /v1/instance-pools/{pool_id}/instances returns full Instance objects in
instances, plus pagination meta, just like the top-level instance list.
Each object includes its state, addresses, flavor, image and role; you do not
need a separate instance request for each member. Read the replica sequence
from metadata["basalt:pool:sequence_num"]. Its value is a string, including
"0" for the first slot.
Filter this list with name, crn, current_state, flavor or image.
It also accepts limit and marker. While meta.has_more is true, request
the next page using meta.marker as marker, keeping the same filters and
limit. Collect instances from every page to list all matching members.
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.volumes creates a disk 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.floating_ip_assignment gives each member its own floating IP on its single interface. Addresses are allocated on scale-out and released on scale-in; each counts against your floating_ips quota. Every selected subnet must support the explicitly requested address families and their internet routes.
That is a different thing from the pool’s shared address —
see below.
Sizing and convergence
All three sizing fields are mutable integers. The resulting values must satisfy0 ≤ min_count ≤ desired_count ≤ max_count ≤ 100.
On create only, omitted bounds default to desired_count.
On PATCH, omitted bounds keep their stored values. If you omit
desired_count, the current target is clamped into the new bounds: raising the
minimum above it raises the target; lowering the maximum below it lowers the
target. A target already inside the bounds stays unchanged.
If you explicitly send desired_count, it must fit the resulting bounds.
Invalid sizing returns 400 without applying any part of the update, including
tags or a template sent with it. Zero is valid, including both bounds at zero.
Sizing does not replace the launch template or request a refresh.
These controls apply to customer-managed pools. Pools owned by a managed
service cannot be resized through the instance-pool API; use that service’s
controls.
Automatic scaling
Anautoscaling policy adjusts desired_count within your configured bounds.
The same policy is available on load balancers.
Creating or updating a policy does not change the launch template.
CPU target tracking requires min_count of at least 1.
- Console
- API
- CLI
- Go
Open the pool and choose Scale. Set Minimum count, Maximum count
and Desired count, then enable Automatic scaling. Choose a
Metric source and set its target; for CPU, use Target CPU (%).
Choose Scale to save. The Scaling tab shows the policy and recent
scaling decisions.
telemetry:ReadMetrics; that permission is
checked again while the policy runs. Only metrics in the pool’s account and
region are eligible.
expected_series must match the
number of selected series; a missing series prevents scale-in. Counter metrics
can use sample_aggregation: "rate" to account for resets before rates are
combined across series.
Publish a fresh zero when the queue is empty. Missing, stale or incomplete data
never means zero and cannot remove capacity. Custom demand metrics can grow a
pool from zero; CPU policies cannot. With several metrics, the largest valid
capacity recommendation wins. Missing data still permits a valid scale-out
recommendation from another metric.
Default warmup is 180 seconds, cooldown is 60 seconds, and scale-down
stabilization is 300 seconds. Each decision adds at most four instances or
removes at most one. These limits and drain_seconds (default 120) are
configurable. Cooldown and stabilization remain in effect across service restarts.
Read autoscaling_status.reason and autoscaling_status.history for the current
wait condition and recent capacity changes.
You can still change sizing manually. Automatic evaluation resumes after the
cooldown. To stop automatic changes, submit the policy with enabled: false;
its metric configuration is retained when you send it back. Policies are replaced
as a whole, so include the metric configuration when disabling one.
Scale-in first marks a member as retiring and withdraws it from load balancer
backend sets and shared floating IPs. Deletion waits for withdrawal acknowledgement
and the drain grace period. This does not invoke an application shutdown hook;
long-running jobs must tolerate instance termination. Long-lived TCP, UDP and
WebSocket sessions can end at the drain deadline.
Changes to shared floating IP forwarding membership can also change connection
placement during withdrawal.
Adjusting the bounds
The following sequence starts with minimum 2, desired 3 and maximum 6. It raises the target to 4, lowers it to 2, then scales to zero. Scaling to zero retires all members; their disks followdelete_on_termination.
- Console
- API
- CLI
- Go
Open the pool and choose Scale to open Scale instance pool.
Set Minimum count to 4, keep Maximum count at 6 and leave
Desired count unchanged. Choose Scale; the target becomes 4.Reopen Scale, set Minimum count to 2 and Maximum count to 2,
and leave Desired count unchanged; the target becomes 2.
To scale to zero, reopen the dialog and set both bounds to 0, again
leaving Desired count unchanged. The console omits an unchanged
desired value so the API can adjust it automatically.
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 means an active error fault — read every entry in faults — and is
still reconciled: the pool keeps being retried. deleting is a teardown in
flight.POOL_LAUNCH_FAILED, POOL_SCALE_OUT_FAILED,
POOL_SCALE_IN_FAILED) clear when the pool reaches its target. A later resize
does not clear them first: 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 whosecurrent_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, min_count, max_count,
autoscaling, 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.
- Console
- API
- CLI
- Go
The pool’s Settings tab edits three parts of it: Tags on the pool,
and Instance tags and Instance metadata on the launch template.
Minimum count, Maximum count and Desired count are under
Scale on the header.Open Edit template to change the launch configuration and selected subnets. Saving Instance tags or Instance metadata preserves the pool’s subnet selection.
template.iam_role to the summary’s
id or crn. Do not send the summary object back. Omitting iam_role from a
replacement clears the attachment for future launches.
The top-level subnets set is independent of the template. Omit it to preserve placement. Sending it replaces the eligible subnets for future launches; existing members move only when you explicitly refresh the pool. Reordering the same subnets does not mark members stale.
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
- Console
- API
- CLI
- Go
- Open Instance pools and select your pool.
- Choose Roll instances beside Scale.
- Confirm replacement of every running member on an older or unknown template. Members on the current template are retained. Without surge headroom, the roll waits; use Scale to raise Maximum count above Desired count (up to 100).
- Watch Roll in progress and Stale instances. A zero stale count does not mean the roll has finished while Roll in progress remains.
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 retirement waits until the pool is above desired 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
With headroom, the pool launches 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 roll progress, and
member_count and live_count for capacity. A zero stale count only means no
members use an old template. After the refresh flag clears, the pool may still
need to remove its surge member and converge to desired. Wait for the flag to
be false, status: "active", and both counts to equal desired before treating
capacity as settled.
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.floating_ip_assignment, which gives each
replica its own.
- Console
- API
- CLI
- Go
The pool’s Floating IPs tab lists its shared addresses and offers
Attach floating IP, which picks from the addresses you hold that are
attached to nothing.One address, every replica explains membership and readiness. Each address has an Interfaces link to its paginated interfaces view, with Health and Reason for each member. An empty pool retains ownership of its shared addresses.
attached_to references the pool even when its interfaces collection is empty. All live replicas can become members, including replicas on the same host. GET /v1/floating-ips/{floating_ip_id}/interfaces returns canonical interface objects plus health and reason. See Reading the bindings.
Membership is maintained for you: a scale-out joins, a scale-in leaves, a
replaced member is swapped. There is no per-replica attach to make, and the
network service’s own attach and detach are refused on a pool’s address —
use these two endpoints.
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 (attached_to: null and an empty interfaces collection) and yours, and every selected pool subnet must
already route 0.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. In the console it is the row action on the
Floating IPs tab, confirmed as Detach floating IP.
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 in floating_ips, plus pagination meta, using the
same response shape as the top-level floating IP list. It accepts name,
crn, limit and marker. Floating IPs have no name, so supplying name
returns an empty list. While meta.has_more is true, pass meta.marker as
marker on the next request, preserving the filters and limit, and collect
floating_ips from every page.
Per-replica addresses are not here — those belong to the
replica and are read from the instance’s
NIC listing.
Deleting a pool
- Console
- API
- CLI
- Go
Delete pool is on the pool’s Settings tab, in the danger zone. The
confirmation restates the cost — terminates every VM in the pool — and
needs the pool name typed back.
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/<name>
— 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, 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
First compare
max_count with desired_count. If there is no surge
headroom, raise maximum above desired (up to 100). The refresh stays
requested and resumes without another refresh call. Bounds changes during
a refresh can put it into this waiting state; normal scaling continues.With headroom, the roll waits for a replacement above desired 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 faults 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. When building a replacement from a pool response, convert the role summary to
one string reference: for example, set
template.iam_role to the summary’s
id or crn. Do not send the summary object back. Omitting iam_role from a
replacement clears the attachment for future launches.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 and build the complete replacement request, converting
the role summary to a string reference as described under
Changing the template.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 or resizing the pool answers 400 on sizing
Creating or resizing the pool answers 400 on sizing
Use whole numbers with
0 ≤ min_count ≤ max_count ≤ 100. An explicit
desired_count must fall within those bounds. On create, omitted bounds
default to desired; on update, they preserve the stored bounds. Omit
desired on update to clamp it automatically. Invalid sizing applies none
of the update. To grow beyond the original maximum, raise max_count
before or together with desired.Next
Instances
Everything a replica is: flavors, images, disks, interfaces and the
lifecycle.
Images
Pinning a build so a pool’s replicas stay identical.

