anycloud node-pool
Node pools are cloud-specific VM pools inside the installation's
cluster: each node pool owns its own compute
credential, region, optional zone, and VM type, and Anycloud provisions its
VMs, joins them to the cluster, and releases them. Deleting a node pool never
deletes the cluster or a sibling node pool. Node pools are created through the
hosted API (anycloud api use https://<cluster-id>.anycloud.sh) after the
compute credential has been added there with anycloud credentials new.
Create a node pool
anycloud node-pool create <name> \
[--cluster <id-or-name>] \
--credentials <name> \
--region <region> \
[--zone <zone>] \
--vm-type <type> \
--min-vms <count> \
--max-vms <count>
Creation is refused with NODE_POOLS_UNAVAILABLE until the cluster is
ready. --cluster is optional and must resolve to the cluster record when
given. The credential, region, zone, and VM type are fixed for the node pool's
whole life. --zone must belong to --region (us-east-1a, us-central1-a)
or be a single digit on Azure; other providers accept no zone. --min-vms and
--max-vms must be equal and at least one. The credential's cloud must support
isolated capacity networking (AWS, Azure, and GCP today). The cluster must run
Kubernetes v1.35 through v1.38, the window the pinned agent supports.
Creation returns after durable intent is stored. Anycloud then provisions each VM with the node pool's own credential in an outbound-only network scope (no public ingress, no peer rules, no SSH key, no cloud credential on the VM), and the VM downloads a checksum-pinned K3s agent and joins the cluster with a short-lived, per-VM bootstrap token pinned to the cluster's certificate authority. The token is removed as soon as the Node object exists, before it is Ready, and a retried attempt replaces its previous token rather than adding one. Only x86_64 VM types are supported: the agent refuses to start on any other architecture. Each VM is one attempt in the same ledger as Job and Service VMs, with the node pool as its only owner, and is billed under the node pool's resource scope. The cluster advertises its public address, so an agent's supervisor tunnel to it works from any cloud, and the cluster reads pool kubelets through that tunnel — no inbound port into a pool VM is ever needed. Agents register their public address as the Node external IP and the cluster's flannel uses external IPs, so Pod-to-Pod traffic between the cluster and a pool in another cloud would need UDP 8472 open between their public addresses; Jobs and node-pool Services do not send Pod traffic across nodes today, so nothing has to be opened for them. An agent that crashed before its Node registered rejoins on restart: its kubelet certificates are reissued from the retained node password on every start.
Node pool Nodes carry the labels anycloud.sh/cluster, anycloud.sh/node-pool,
anycloud.sh/provider, and anycloud.sh/attempt — plus
anycloud.sh/accelerator=nvidia on NVIDIA GPU VM types — and the taint
anycloud.sh/node-pool=<id>:NoSchedule. Work that Anycloud schedules onto a
node pool must select exactly that node pool and tolerate only its taint; a
missing or deleted node pool leaves such work Pending rather than moving it to
a sibling node pool or the management Node. This constrains scheduling only and
does not make node pools independent security or failure domains.
The node pool becomes ready when every VM's Node is Ready and, for GPU VM
types, advertises the catalog's physical GPU count. A VM whose Node never
appears within ten minutes, or stays NotReady for fifteen, is replaced — at
most three times in a row (a replacement only counts as recovered once the
replacement VM itself is Ready) before the node pool is parked failed with a
diagnostic naming the join preconditions to check. Provider failures back off
exponentially (one minute doubling to thirty) and park the node pool after five
consecutive failures. A parked node pool provisions nothing until it is deleted
and recreated. A Node that claims the node pool — by label or by the Node name
Anycloud issued — but does not match a VM the node pool provisioned marks the
node pool failed and is never counted; a Node left behind by a VM the
node pool already released is removed.
Cluster add-ons
Some cluster components are installed once per cluster and shared by every
node pool on it. The first is the NVIDIA stack: the nvidia RuntimeClass and
the NVIDIA device plugin DaemonSet (kube-system/anycloud-nvidia-device-plugin,
image pinned by digest). Anycloud installs them through the registered
connection the first time a GPU node pool is reconciled, before any VM is
bought, and re-checks them on every later GPU node pool pass, after that
node pool's existing VMs have been observed: an object it owns is left alone
while unchanged, rewritten if something else edited it, and recreated if a
field the API server treats as immutable differs. A RuntimeClass that K3s
installed itself with the nvidia handler and no overrides is used as is. Any
other object with the same name that Anycloud does not own marks the GPU
node pool failed with a diagnostic naming the object and the kubectl delete that removes it; Anycloud never adopts or replaces foreign objects,
and an add-on failure never blocks releasing or replacing the node pool's
existing VMs. A write the cluster's RBAC denies fails the node pool with a
diagnostic naming the missing verb and resource. CPU node pools need no add-on
and touch none of these objects, and a cluster with no GPU node pool is not
re-checked.
Add-ons are never uninstalled. Deleting the cluster forgets its connection, so
nothing could remove them, and the objects are inert without GPU Nodes (the
DaemonSet selects none). When the same control plane — the same kube-system
namespace UID — is registered again, Anycloud adopts the add-on objects the
destroyed cluster installed and relabels them for the new record; objects
labelled for any other cluster remain foreign. To uninstall by hand, delete RuntimeClass nvidia and DaemonSet kube-system/anycloud-nvidia-device-plugin after the
last GPU node pool is deleted.
The DaemonSet selects only Nodes labelled anycloud.sh/cluster=<cluster-id> and
anycloud.sh/accelerator=nvidia, tolerates node pool taints, and does not
tolerate the management taint, so it never runs on the management Node or on
CPU node pools. GPU node pool Nodes join with the accelerator label; a GPU Node
that joined before this release without it is labelled by the next
reconciliation pass. The VM image must provide the NVIDIA driver and container
runtime; Anycloud selects a CUDA image for NVIDIA VM types.
A GPU node pool becomes ready only when each Node advertises exactly the
number of nvidia.com/gpu devices the catalog attributes to its VM type as
allocatable. Allocatable is the advertised total, not free devices: Nodes whose
GPUs are all held by running Jobs remain ready capacity. A Ready Node that
advertises zero devices has ten minutes — from its VM's creation, or from the
pass that first saw the devices missing when a plugin restart drops the count
on a Node that was already ready — before the node pool is marked failed
with a diagnostic that names the add-on objects and their state; it counts as
starting meanwhile. A Node that advertises a different non-zero count than
the catalog marks the node pool failed with a device-count mismatch
diagnostic and is never counted; refresh the catalog or choose a VM type whose
physical device count matches. Work scheduled onto a GPU node pool must request
whole nvidia.com/gpu devices — the larger of its biggest init container and
its regular containers' total, no more than one Node advertises — and run
under the nvidia RuntimeClass; work on a CPU node pool must request none.
Ingress entry (proposed behavior)
A node pool that hosts a Service elects one ingress entry: the Ready VM
with the lowest attempt id, labelled anycloud.sh/ingress-entry=<pool-id> on
its Node and shown as Ingress entry: <public IP> in node-pool status. The
entry keeps running ordinary workloads; it is a role, not a dedicated VM. If
its Node stays NotReady for two minutes and another VM's Node is Ready, that
VM takes over and the label moves; with no Ready replacement the entry stays
until its VM is replaced. On the first Service, the pool's network additionally
admits TCP 80 from Cloudflare's published edge ranges only — the same rule
VM-host Services carry. The rule is applied to the pool's network as a
whole, so every VM in the pool accepts that traffic from Cloudflare, while
only the entry Node runs anything on the port; it is never removed. AnyCloud
then installs the pool's ingress release — a pinned Traefik HelmChart
installed through the same cluster add-on mechanism as the NVIDIA stack (its
install Job runs on the management Node like the API release's) — as a
DaemonSet on that pool's entry Node only, on host port 80, tolerating that
pool's taint and never the management Node's, watching only routes labelled
anycloud.sh/ingress-pool=<pool-id>, so a request to one pool's entry is never
served by another pool's Service. Deleting the node pool removes its release.
Publishing a Service's https://<id>.anycloud.sh record at the entry IP is
the next step and is not yet available. Jobs-only node pools elect no entry,
open no port and install nothing.
node-pool status shows the release's progress on two lines — Ingress entry: <IP> — release installing|installed|failed (helm job <phase>[: <exit>])
and Ingress Traefik: <ready>/<desired> ready, entry Node Ready|not Ready — last event: … — read from the cluster at request time. With --json the same view
is ingress: { entryIp, release }, where release is null for a pool that
hosts no Service and otherwise { state, helmJob, daemonSet, lastEvent }:
state is absent (no HelmChart on the cluster), installing, installed or
failed; helmJob is { phase, message } with the install container's
non-zero exit as message, or null before the HelmChart exists;
daemonSet is { desired, ready, entryNodeReady } once Helm created it, else
null; lastEvent is the newest event on the install Job and its Pods (or,
once the DaemonSet exists, in the release namespace) as reason: message,
or a cluster unavailable: … note when the cluster could not be read. ingressEntryIp
remains as a shortcut for ingress.entryIp. The generated Python SDK models
these as nested objects of the node pool summary.
Inspect and delete a node pool
anycloud node-pool list [--cluster <id-or-name>]
anycloud node-pool status <id-or-name>
anycloud node-pool delete <id-or-name>
status and delete accept the node pool ID or its name. Status reports the
lifecycle state, the last reconciliation error, and ready, starting, and
draining VM counts. It does not expose Kubernetes credentials, endpoints,
Pods, or raw Node objects.
Deletion is rejected while Deployments or Workloads target the node pool
(see anycloud deployment create --node-pool),
including a terminated Service whose native objects have not yet been confirmed
removed. That cleanup needs the cluster connection: if the cluster is unreachable or
its identity no longer holds the permissions above, the Service stays
uncleaned, the node pool cannot begin deletion, and the cluster cannot be deleted
either. Restore the connection (re-register the cluster's kubeconfig with the
required permissions) and cleanup resumes on its own; there is no forced
cleanup path in this release. The reference check runs inside the deletion
request itself, so a Deployment or Service admitted at the same moment either
lands before the request and blocks it or finds the node pool already
deleting and is refused. Once nothing references the node pool, deletion
also removes the namespace the node pool owns on the cluster (Deployment
handlers run there; each Service owns its own namespace), re-checking
references immediately before; a namespace of that name Anycloud does not own
is left in place and recorded in the node pool's last error. Otherwise
deletion is durable and asynchronous: the node pool moves to deleting,
Anycloud cordons and removes its Nodes, terminates its VMs, waits for the
provider to confirm each release, removes its isolated network, and then
marks the node pool deleted. An uncertain provider observation keeps the VM
attempt open and the node pool deleting. The cluster and sibling node pools stay
usable throughout. terminate and rm are aliases for delete. Each command
accepts --json.