Skip to main content

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. The end-to-end path from anycloud cluster create to a Service running on a node pool is in Clusters.

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>

Pass --wait to wait until the node pool is Ready, with the exit codes of anycloud wait. A pool that reports failed ends the wait with exit 1. With --json, the first line is the node pool, then the wait records.

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 are both required, must be at least one, and must be equal. Node pools are fixed-size; pool scale-out is not supported yet. The credential's cloud must support isolated capacity networking: AWS, Azure, GCP, and Lambda. On Lambda, a node pool VM's own firewall ruleset opens nothing, but TCP 22 and ICMP stay reachable through the account's global firewall rules, which apply to every instance. In us-south-1, which has no firewall rulesets, Lambda rejects the ruleset, so no VM launches and the node pool parks with that error. 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, and a Lambda node pool on an arm64 type (such as gpu_1x_gh200) launches no VM and parks with that error. Each VM is recorded like a Job or Service VM, 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 send no Pod traffic across nodes, and a node pool Service's replica runs on the pool's ingress entry Node so its Traefik reaches it locally, 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.

A node pool uses Workload states: it is provisioning until it becomes running, 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. While it backs off, the node pool keeps its state and reports the failure in its last error, which clears once a new VM launches. Any other unexpected error in a pass, such as a cluster API timeout, also keeps the node pool's state and is reported in its last error until the node pool is next ready. A parked node pool is failed and 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). The hosted API installs them through its own cluster authority 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. They are inert without GPU Nodes (the DaemonSet selects none), and anycloud cluster delete removes the control plane they live on. To uninstall by hand before that, 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 running 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 — a Deployment Pod or a Service replica — 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​

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 <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. A Service on the pool is published at https://<id>.anycloud.sh pointing at the entry IP (see Public URL). Jobs-only node pools elect no entry, open no port and install nothing.

node-pool status shows the release, read from the cluster at request time, on one line: ✓ ingress <IP> once the release is installed and running on a Ready entry VM, ✗ ingress failed: <error>, or ◌ ingress with the first thing it is waiting for: waiting for an entry VM (none elected yet), pending (no ingress release on the cluster yet, or why the cluster could not be read), installing (the release is installing), entry VM not ready (the entry Node is not Ready), or starting (ingress replicas not all ready). With --json the full 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; routes counts the Services the pool's Traefik serves; 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 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 runs through the hosted API's own cluster authority: if the hosted API is down, or its chart-bound ClusterRole no longer holds the permissions above, the Service stays uncleaned, the node pool cannot begin deletion, and the cluster cannot be destroyed either. Restore the hosted API (anycloud api status shows its latest upgrade) or the chart's RBAC 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 terminated and is refused. Once nothing references the node pool, deletion also removes the namespace the node pool owns on the cluster (Deployment Pods 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 terminated · releasing VM (still billing) (releasing: true in --json), Anycloud cordons and removes its Nodes, terminates its VMs, waits for the provider to confirm each release, removes its isolated network, and then drops the releasing label. An uncertain provider observation keeps the VM attempt open and the node pool releasing. The cluster and sibling node pools stay usable throughout. terminate and rm are aliases for delete; list accepts ls and get, and status accepts describe. Each command accepts --json.