Multi pool mode

A management plane and N pools, driven by Terragrunt over the same modules. State is in S3 per unit, provisioning is agentless over SSM, and each pool is a folder with a ~25-line terragrunt.hcl — so adding the tenth pool costs the same as adding the second.

Units are applied in dependency order — networkpoolsmgmt — and Terragrunt works that out for itself.

Step 1 — Clone and bootstrap the state bucket

Every unit but this one keeps its state in S3, so the bucket comes first. Set state_bucket and region in bootstrap/terraform.tfvars to match common.hcl, then:

sh
git clone https://github.com/migrx-io/backedblock.git
cd backedblock/terragrunt-scale
(cd bootstrap && terraform init && terraform apply)

bootstrap keeps local state; every other unit uses the bucket it just made.

Step 2 — Size the pools

Volumes first, machines second. Sizing the pool turns the volumes one pool has to serve into the block for Step 4 and the StorageClass parameters for the driver install afterwards.

If the workload needs more nodes than one pool should carry, the calculator says so and gives the split — which is the layout this page is for: several pools of the same shape rather than one oversized one.

Step 3 — Store the secrets in SSM

sh
cp secrets.env.example secrets.env       # then fill in real values
aws ssm put-parameter --type SecureString \
  --name /mgx/main/secrets --value file://secrets.env

Every line is KEY=value and every one has to be filled — the placeholders are not defaults, and a node that reads an unset value will come up unusable.

What each secret is
VariableWhat it is
CASS_USER · CASS_PASSWDThe Cassandra superuser the nodes create and then authenticate with. Internal to the cluster — nothing outside it uses these.
MGX_GW_X_API_KEYThe API key the management API accepts in the X-API-KEY header.
MGX_X_API_KEYThe same idea for a node's own API. It does not have to differ from the gateway key, which is why the example carries one placeholder for both.
MGX_GW_ADMIN_PASSWDPassword for the admin account on the API. This is the one you need again later — it is what the CSI driver authenticates with, and what the CLI logs in with.
GRAFANA_USER · GRAFANA_PASSWDGrafana's admin login, used only when enable_metrics and enable_grafana are on.

Step 4 — Configure

Three files, in order of how often they change: common.hcl for everything shared, pools/_pool.hcl for the pool shape, and one terragrunt.hcl per pool for what makes that pool itself.

common.hcl — the single source of truth
ValueWhat to set
state_bucket · cluster · regionFrom Step 1. cluster names the cluster every node joins.
vpc_idThe existing VPC to deploy into.
azsThe AZs the network builds subnets in — one mgmt and one storage subnet each.
mgmt_subnet_cidrs · storage_subnet_cidrsOne free CIDR per AZ, in the same order as azs.
bastionvpc_subnet is an existing public subnet, which also hosts the NAT gateway; ami an Ubuntu image for your region; whitelist_ips ships as 0.0.0.0/0 — narrow it.
nodes_amiThe published node image for your region — see Node AMIs.
key_name · ssh_public_key_pathThe key pair for SSH access to the nodes. Provisioning itself is agentless over SSM.

azs is a network-level list. Each pool pins itself to one of them with its own az, because EBS cache volumes are AZ-bound — so pools can be spread across zones, but a pool does not straddle them.

pools/_pool.hcl — the shape every pool shares

This is where the output of Step 2 goes, once, for every pool that shares a shape: nodes_count, nodes_instance_type, nvme_node_disks_count, max_volumes_count, the two cache sizes and the ebs_volumes block — see Caching and tiering.

Two settings differ from a standalone pool, and both are right for this layout:

ValueWhy
enable_grafana = falseGrafana runs on the management plane instead, where it can see every pool.
cross_peer_scrape = falseEach node scrapes only itself and mgmt federates them all, so there is no node-selection single point of failure.

A pool that needs a different shape — bigger volumes, more throughput — carries its own overrides in its terragrunt.hcl rather than changing this file.

pools/<pool>/terragrunt.hcl — one per pool
ValueWhat to set
pool_name · descriptionName of the pool and the prefix on its resources.
azThe single AZ this pool sits in, one of the network's azs.
labelsComma-separated key=value pairs. These are what a scheduler config's pool_selector matches, so they are how volumes get steered to one pool rather than another — see Volume scheduling.
s3_bucket_names · s3_backup_bucket_namesGlobally unique bucket names — block data, and snapshots.

Everything else comes from _pool.hcl and common.hcl.

Step 5 — Apply

Terragrunt builds the dependency graph and applies network → pools → mgmt:

sh
terragrunt run --all -- apply
Why the order matters

mgmt has to come after the pools, and has to be re-applied whenever you add or remove one. Each pool's apply publishes its node addresses to SSM (/mgx/<cluster>/pools/<pool>); mgmt's apply reads that path back to build its registry, so the entries must already exist. run --all handles the ordering.

Step 6 — Wait for provisioning

In SSM mode an apply only creates the provisioning resource — setup-node.sh then runs asynchronously on each node, so the command returns before the nodes are ready. Watch the associations:

sh
aws ssm list-associations --region us-east-1 \
  --query "Associations[?starts_with(AssociationName, \`mgx-\`)].[AssociationName,Overview.Status]" \
  --output table

Everything Success means provisioning finished.

Step 7 — Connect to the cluster

mgx-cli is on PATH on every node, and the management nodes see every pool:

sh
(cd network && terragrunt output bastion_public_ip)
(cd mgmt    && terragrunt output node_private_ips)
sh
ssh -J ubuntu@<bastion_public_ip> ubuntu@<mgmt_node_ip>
mgx-cli
text
mgx-core:127.0.0.1:nologin> login admin --cluster main --ns main
Password:
 logged..

The password is the MGX_GW_ADMIN_PASSWD from Step 3. The cluster, with the node currently holding the VIP:

text
mgx-core:127.0.0.1:main:main:admin> cluster list

Every pool, each of which has to read READY:

text
mgx-core:127.0.0.1:main:main:admin> cache pool list

A pool missing from that list is a pool mgmt has not discovered — re-apply mgmt and look again.

Step 8 (Optional) — Grafana

Metrics are on by default here, and Grafana runs on the management plane rather than in a pool, because that is where every pool's metrics are federated:

hcl
enable_metrics = true      # mgmt/terragrunt.hcl
enable_grafana = true

It has no public address, so forward the port over the bastion:

sh
ssh -N -J ubuntu@<bastion_public_ip> -L 3000:localhost:3000 ubuntu@<mgmt_vip_node_ip>

and open http://localhost:3000, with the GRAFANA_USER and GRAFANA_PASSWD from Step 3. Two dashboards matter:

DashboardWhat it is for
CacheThe state of the cache nodes, across every pool — one row per node, and on a healthy fleet every one of them is green.
StorageThe volumes themselves — what is provisioned, and each volume's metrics.

Step 9 — Point the driver at the management plane

The CSI driver points at the mgmt nodes, which present one API for every pool beneath them:

sh
(cd mgmt && terragrunt output node_private_ips)

Each of those on port 8082 is an API endpoint for csiSecret.clusterConfig.nodes — carry them into Install the K8s CSI driver, with admin and MGX_GW_ADMIN_PASSWD as the credentials.

Day-2

TaskAction
Add a poolscripts/new-pool.sh pool3, (cd pools/pool3 && terragrunt apply), then re-apply mgmt. Nothing in mgmt to edit — it discovers pools itself.
Remove a poolterragrunt destroy in its folder, delete the folder, re-apply mgmt.
Scale a poolChange nodes_count, apply that pool.
Maintain a nodeDrain it first — see Pool node management.
Tear down allterragrunt run --all -- destroy.