Sizing and capacity planning
Size your cluster from one number: how many spaces you want running at the same time. This page turns that number into nodes and disk, with the arithmetic shown, so you can plan a pilot or a company-wide rollout with confidence. The exact machine-type variables for each cloud are on its install page. How the pieces fit together is on Architecture.
Two pools, two jobs
Every Marshal cluster splits its compute into two node pools:
| Control plane | Spaces | |
|---|---|---|
| Runs | API, gateway, web app, marketplace and Redis | One pod per running space |
| Load | Steady, always on | Grows and shrinks with the number of running spaces |
| Installer default | 8 vCPU, 32 GB (m5.2xlarge on EKS, e2-standard-8 on GKE) | 8 vCPU, 32 GB (m5.2xlarge on EKS with a 200 GB disk, e2-standard-8 on GKE with a 300 GB disk) |
| Disk | Small: no per-space storage | Larger: holds each space’s working files |
The installer keeps the pools separate by default: the control-plane pool is tainted, and the chart
schedules the platform onto it. A burst of spaces then never takes CPU from the services
everything else depends on. The switch is taint_control_plane_pool, on by default, and each
cloud’s install page covers it.
Control plane
The chart runs at least 2 replicas each of the API, gateway, web app and marketplace: 8 pods before the first person signs in. Their requests add up as follows:
| Service | CPU request | Memory request | Replicas |
|---|---|---|---|
| API | 1 | 1 GiB | 2 |
| Gateway | 500m | 512 MiB | 2 |
| Web app | 200m | 256 MiB | 2 |
| Marketplace | 500m | 512 MiB | 2 |
| Total | 4.4 vCPU | 4.5 GiB |
(1 + 0.5 + 0.2 + 0.5) × 2 = 4.4 vCPU. (1024 + 512 + 256 + 512) MiB × 2 = 4608 MiB = 4.5 GiB.
A 4 vCPU machine has less than 4 vCPU allocatable (about 3.9 on an e2-standard-4), so this
baseline does not fit, and pods stay Pending with Insufficient cpu. Start the control plane
on the 8 vCPU class. Each service scales out under load (the API and gateway up to 10 replicas,
the web app and marketplace up to 6), so leave room above the baseline.
Spaces
Each space asks the scheduler for the requests of its compute class. The class is set per manifest, stack or org, and Medium is the default. See Compute sizes for every class and how a space gets its class. Admins can move every class to persistent disk, or to one storage class, in a single action: see Set storage on many sizes at once.
| Class | CPU request | Memory request | Disk reserved |
|---|---|---|---|
Extra Small (xs) | 100m | 256 MiB | 2 GiB |
| Small | 250m | 512 MiB | 5 GiB |
| Medium (default) | 500m | 1 GiB | 10 GiB |
| Large | 1 | 2 GiB | 20 GiB |
Extra Large (xlarge) | 2 | 4 GiB | 40 GiB |
2x Large (2xlarge) | 4 | 8 GiB | 80 GiB |
4x Large (4xlarge) | 8 | 16 GiB | 160 GiB |
Spaces per node
On the default spaces node (8 vCPU with about 7.9 allocatable, 32 GB memory with about 29 GiB allocatable, and about 178 GB of allocatable disk on EKS or 146 GiB on GKE), the number of spaces that fit is the smallest of three divisions. Disk is shown for EKS first, then GKE, because the clouds leave different shares of the boot disk for pods (about 89% on EKS, about 49% on GKE):
| Class | By CPU (7.9 ÷ request) | By memory (29 GiB ÷ request) | By disk, EKS · GKE | Spaces per node, EKS · GKE |
|---|---|---|---|---|
| Extra Small | 79 | 116 | 89 · 73 | 79 · 73 |
| Small | 31 | 58 | 35 · 29 | 31 · 29 |
| Medium | 15 | 29 | 17 · 14 | 15 · 14 |
| Large | 7 | 14 | 8 · 7 | 7 · 7 |
| Extra Large | 3 | 7 | 4 · 3 | 3 · 3 |
| 2x Large | 1 | 3 | 2 · 1 | 1 · 1 |
| 4x Large | 0 | 1 | 1 · 0 | Needs a larger node |
These are upper bounds. The operating system, the image cache and system pods also use part of each node. On this node shape CPU and disk run out close together, so neither strands the other. Disk is the first limit for spaces on Persistent disk storage: each one reserves 16 GiB of node disk, which fits about 11 spaces on an EKS node and 9 on a GKE node, against 15 by CPU. The installer’s disk defaults come from this: the built-in classes reserve 20 GiB of disk per vCPU, far more than a node’s boot disk offers per vCPU, so size the disk before the machine.
To run 4x Large spaces, add a pool with a larger machine type. On GKE, add an entry to
spaces_pools. On EKS, add an entry to spaces_node_groups. See
Change node size without stopping spaces.
On GKE, image streaming makes the first spaces on a freshly added node start faster. See Faster space start on new nodes.
Nodes for your peak
Nodes needed = running spaces ÷ spaces per node, rounded up. The counts below use the lower of the EKS and GKE figures, so they hold on both. Disk is nodes × the boot disk (200 GB on EKS, 300 GB on GKE).
| Running spaces | Small (29 per node) | Medium (14 per node) | Large (7 per node) |
|---|---|---|---|
| 10 | 1 node | 1 node | 2 nodes |
| 25 | 1 node | 2 nodes | 4 nodes |
| 50 | 2 nodes | 4 nodes | 8 nodes |
| 100 | 4 nodes | 8 nodes | 15 nodes |
Worked example: 50 running Medium spaces. 50 ÷ 14 = 3.6, so 4 spaces nodes: 800 GB of disk on EKS (4 × 200 GB) or 1.2 TB on GKE (4 × 300 GB), plus one 8 vCPU control-plane node. Real packing is looser than the division, because spaces start and stop all day. Add about 30% for a steady load and 45% for the peak: here, 5 to 6 nodes.
Both clouds add and remove spaces nodes for you. On GKE the spaces pool autoscales up to 5
nodes per zone by default (max_node_count). On EKS the installer runs the Kubernetes Cluster
Autoscaler (enable_cluster_autoscaler, on by default) between node_min_size and
node_max_size. Raise the maximum to your peak from the table above, and make sure your vCPU
quota covers it. A node that holds a running space is never removed. If you run Karpenter or
your own autoscaler on EKS, set enable_cluster_autoscaler = false.
A space only uses node resources while it runs. A stopped space frees its place on the node. A
class with the Persistent disk storage mode puts the home folder on
its own disk, and the space asks the node for only 16 GiB of local disk, for /tmp, container
layers and logs. An admin tunes that with Network Disk Space Node Reservation (GiB) in
Settings > Configuration, Spaces group, from 4 to 200. The limit is twice the value.
Lower it to fit more spaces on a node, raise it if spaces are evicted for using too much node
disk. Spaces launched after the change get the new value.
Cloud quotas
Your cloud tracks CPU quota and disk quota separately, and either one can stop the cluster from
scaling. Both look the same from the cluster: new nodes do not appear and pods stay Pending.
- CPU quota (for example GCP
CPUS_ALL_REGIONS, or the AWS vCPU quota for your instance family) caps the vCPUs of all node pools together. The defaults need 72 vCPU on AWS and 48 vCPU on GCP at create. - Disk quota (for example GCP’s regional
SSD_TOTAL_GB) caps the total disk of all nodes, separately from CPU. The spaces pool’s disk size × node count counts against it, and so does the control plane’s. The GKE defaults need about 1050 GB of regionalSSD_TOTAL_GB.
Before you raise a pool’s maximum node count, check both quotas at the new maximum. A CPU limit needs more CPU quota or a smaller machine. A disk limit needs more disk quota, a smaller disk or fewer nodes.
Putting it together
- Start the control plane at 8 vCPU. The chart’s own baseline sets the floor, whatever your traffic on day one.
- Pick the class most of your spaces use, and read spaces per node from the table.
- Divide your peak running spaces by it to get the spaces pool’s maximum node count.
- Check CPU and disk quota at that maximum, for both pools.
For the exact machine-type variables and disk settings on your cloud, continue to Installing Marshal.
Change node size without stopping spaces
Changing the machine type or boot disk of a running pool replaces it, and a replacement stops every space on it. Move over instead, with a second pool beside the first:
-
Check quota for the old and new pools together. Both run during the move.
-
Add the new pool. On GKE, add an entry to
spaces_pools. On EKS, add an entry tospaces_node_groups. Leave the existing pool untouched, and confirmterraform planshows only additions. -
Cordon the old nodes. Running spaces keep running, and new spaces land on the new pool:
# GKE kubectl cordon -l cloud.google.com/gke-nodepool=<old-pool-name> # EKS kubectl cordon -l eks.amazonaws.com/nodegroup=<cluster_name>-spacesBefore the control-plane pool was tainted, a space could also start on a control-plane node. List where every space runs with
kubectl get pods -n marshal-spaces -o wide, and include any space on a control-plane node in the next step too. -
Ask your team to stop and start their spaces over the next few hours. A space that starts again comes back on the new pool with its files.
-
Remove the old pool when it is empty. Check that no space pods remain on the old nodes (
kubectl get pods -n marshal-spaces -o wide). On GKE, delete the old key fromspaces_pools. On EKS, setcreate_default_spaces_node_group = false, or delete the old entry fromspaces_node_groups. The plan should show only the old pool going away.
To roll back before step 5, run kubectl uncordon on the old nodes.