Nitro Sandboxes on instances with multiple NUMA nodes
Some instance types have more than one non-uniform memory access (NUMA) node. Each node has its own vCPUs and memory, and a vCPU reaches the memory of its own node faster than the memory of another node.
On an instance with more than one node, the parent instance keeps the memory pool, the CPU pool, and the Nitro Sandbox slots separately for each node. Each Nitro Sandbox runs on one node, unless you place it on more than one. On an instance with one node, these differences don't apply.
Contents
Check the number of NUMA nodes
Run the following command on the parent instance.
lscpu | grep NUMA
The following is example output for an m8a.48xlarge instance,
which has two nodes.
NUMA node(s): 2 NUMA node0 CPU(s): 0-95 NUMA node1 CPU(s): 96-191
You can also list the nodes in /sys/devices/system/node.
Check the instance type that you plan to use, because the number of nodes depends
on the instance type and size.
How Nitro Sandboxes use NUMA nodes
-
By default, Firecracker places all vCPUs and all memory of a Nitro Sandbox on node 0.
-
The memory of a Nitro Sandbox comes only from the memory pool on its node. If that pool does not have enough free memory, the launch fails with an out-of-memory error, even when the pool on another node has free memory.
-
The vCPUs and the memory of a Nitro Sandbox are always on the same node. A launch that would place them on different nodes fails.
-
Without CPU overcommit, each vCPU takes a hardware thread of its node. Node 0 has one core fewer in the CPU pool than the other nodes, because the Nitro Enclaves driver keeps the first core of the parent instance for the parent.
-
With CPU overcommit, the vCPUs of a Nitro Sandbox run on the vCPUs that the parent instance has on the same node. A Nitro Sandbox can have at most as many vCPUs on a node as the parent instance has on that node. To give a Nitro Sandbox more vCPUs, place it on more than one node.
-
In confidential mode, the buffers of the virtual devices come from the pool of the lowest-numbered node of the Nitro Sandbox.
-
The guest sees one NUMA node, unless you place the Nitro Sandbox on more than one node.
-
A running Nitro Sandbox stays on its nodes. To move it, stop it and launch it again on another node.
Reserve memory on each node
The nitro_enclaves.mempool kernel parameter takes a comma-separated
list of entries. Each entry reserves memory on one node or on every node. Use one
of the following forms for each entry.
size@node-
Reserves
sizeon nodenode, for example8G@1. percent%-
Reserves
percentpercent of the memory that each node has available at boot, on every node. percent%-size-
Reserves
percentpercent of each node, minussizeon each node. Use this form to keep a fixed amount of memory for the parent instance on every node. percent%+size-
Reserves
percentpercent of each node, plussizeon each node.
To apply a percentage form to one node only, add
@, for example
node50%@1. An entry can subtract or add a size, but not both.
An entry with a size and no node does not control which node gets the memory. On an instance with more than one node, give every entry a node or use a percentage form. On Graviton-based instances, the pool can have at most three entries, and a percentage entry without a node counts once for each node.
The following examples are for an instance with two nodes.
| Parameter | Result |
|---|---|
nitro_enclaves.mempool=8G@0,8G@1 |
8 GiB on each node. |
nitro_enclaves.mempool=16G@0 |
16 GiB on node 0 and no pool on node 1. You can't launch Nitro Sandboxes on node 1. |
nitro_enclaves.mempool=50% |
Half of the available memory of each node. |
nitro_enclaves.mempool=90%-16G |
90 percent of each node, minus 16 GiB on each node. |
nitro_enclaves.mempool=95%-1G@0,4G@1 |
95 percent of node 0 minus 1 GiB, and 4 GiB on node 1. |
Size the pool of each node for the Nitro Sandboxes that you plan to run on that node and their overhead. Also add 128 MiB for each step of 8 slots that the node provides, as described in Check the capacity of each node. If a node can't provide the requested size, the parent instance reserves less on that node, and the reserved size in the kernel log is lower than the requested size.
To add the parameter, follow the steps in Prepare the parent instance. After the reboot, check the pool of each node, in bytes.
sudo dmesg | grep "nitro_enclaves: CMA pool" grep . /sys/class/misc/nitro_enclaves/mem_pool/node*/total
The following is example output for
nitro_enclaves.mempool=64G@0,64G@1.
nitro_enclaves: CMA pool: 131072 MiB reserved in 16 region(s), 131072 MiB requested /sys/class/misc/nitro_enclaves/mem_pool/node0/total:68719476736 /sys/class/misc/nitro_enclaves/mem_pool/node1/total:68719476736
Choose the node of a Nitro Sandbox
To place a Nitro Sandbox on a node other than node 0, add
numa_nodes to the enclave object of the configuration in
Launch a Nitro Sandbox.
The following example places all vCPUs and all memory on node 1.
"machine-config": { "vcpu_count":2, "mem_size_mib":2048}, "enclave": { "cpu_overcommit": true, "numa_nodes": [ { "parent_numa":1, "vcpu_count":2, "mem_size_mib":2048} ] }
To place one Nitro Sandbox on more than one node, add an entry for each node. The following example puts 2 vCPUs and 2 GiB on each of nodes 0 and 1.
"machine-config": { "vcpu_count": 4, "mem_size_mib": 4096 }, "enclave": { "cpu_overcommit": true, "numa_nodes": [ { "parent_numa": 0, "vcpu_count": 2, "mem_size_mib": 2048 }, { "parent_numa": 1, "vcpu_count": 2, "mem_size_mib": 2048 } ] }
The entries must follow these rules:
-
The
vcpu_countandmem_size_mibvalues of all entries add up to the values inmachine-config. -
Each entry names a different node and has at least one vCPU and some memory.
-
The entries appear in ascending order of node number.
-
The memory of every entry except the one with the highest node number is a multiple of 128 MiB.
A Nitro Sandbox with more than one entry sees one NUMA node for each entry. Its guest node 0 is the entry with the lowest node number, and the other guest nodes follow in order. The measured image records that the Nitro Sandbox spans more than one node, so its PCR0 differs from the same Nitro Sandbox on one node, as described in Nitro Sandbox measurements. If you build the image in advance, build it with the same settings that you launch with.
If you also set cpu_ids, choose CPUs on the node that holds the
memory of the Nitro Sandbox. Otherwise, the launch fails.
Check the capacity of each node
The parent instance reports the free capacity of each node in the
/sys/class/misc/nitro_enclaves directory. The
mem_pool/node directories hold
Ntotal, in_use, and
free in bytes. The
cpu_pool/node directories hold the
same files as vCPU counts, and count only Nitro Sandboxes without CPU
overcommit.N
grep . /sys/class/misc/nitro_enclaves/mem_pool/node*/free /sys/class/misc/nitro_enclaves/cpu_pool/node*/free
The parent instance also counts slots per node. Each node can run 4 Nitro Sandboxes, plus 8
for each step of 8 slots whose memory came from that node. When you raise
pcie_slots, the parent instance takes the memory for each step
from the nodes one after another. For example, with pcie_slots set to
20 on an instance with two nodes, each node can run up to 12 Nitro Sandboxes, and
the instance up to 20 in total.
A launch on a node that has no slot left fails,
even when pcie_slots_in_use is lower than
pcie_slots. To run more Nitro Sandboxes on one node, raise
pcie_slots further, or spread the Nitro Sandboxes across
nodes.
Troubleshoot launches on instances with multiple nodes
- The launch fails with an out-of-memory error, and
Firecracker reports
CMA allocation failed -
The pool on the node of the Nitro Sandbox is full or empty. Check
mem_pool/node. Launch the Nitro Sandbox on a node with free memory, or reserve more memory on that node and reboot.N/free - Firecracker reports
Failed to add a vCPU on parent NUMA nodeN -
Without CPU overcommit, node
Nhas no free core left. Checkcpu_pool/node. With CPU overcommit, the Nitro Sandbox asks for more vCPUs on nodeN/freeNthan the parent instance has there. If the error isInvalid argument, the parent instance has no nodeN. - The launch fails while
pcie_slots_in_useis lower thanpcie_slots -
The node has no slot left. Raise
pcie_slots, or launch on another node. - Launches without CPU overcommit fail after you configured Nitro Enclaves
-
A fixed CPU pool for Nitro Enclaves, for example from the Nitro Enclaves allocator service, can hold cores of one node only. Nitro Sandboxes without CPU overcommit take their cores from that pool. Add cores of the node that you launch on to the pool, or use CPU overcommit.
Run workloads on instances with multiple nodes
-
Spread Nitro Sandboxes across nodes so that no single node runs out of pool memory, cores, or slots first. Before each launch, pick a node with enough free capacity.
-
Keep each Nitro Sandbox on one node when it fits. Its vCPUs then reach all of its memory on the same node. Place a Nitro Sandbox on more than one node when it needs more vCPUs or memory than one node has, and run a guest kernel and applications that use the NUMA topology.
-
With CPU overcommit, the Nitro Sandboxes on a node share the vCPUs of that node with the processes of the parent instance on the same node. Account for both when you decide how many Nitro Sandboxes a node runs.