Inventory generation is only useful if the output matches the repo’s actual inventory contract.
A script can successfully read kubectl get nodes and still generate inventory that the playbooks cannot use. The important details are usually group names, expected child groups, node roles, load balancer entries, and local conventions around readiness metadata.
Problem
A per-cluster RKE2 inventory workflow needed output like:
site_a_ops_rke2
rke2_servers
rke2_agents
api_lb
But the generator output did not match the checked-in inventory shape. Common breaks included:
- kube context names with dashes where Ansible group vars expected underscores.
- missing
rke2_serversandrke2_agentsgroups. - missing API load balancer inventory entries.
- missing or inconsistent
rke2_node_readyvalues. - requiring explicit kubeconfig and environment flags even when the output filename already identified the target.
Inputs
Use Kubernetes as the live source for nodes:
kubectl --context site-a-ops-rke2 get nodes -o wide
Extract the facts the inventory actually needs:
node name
internal IP
Kubernetes roles
Ready or NotReady condition
cluster/context name
environment inferred from output path or context
Output Contract
For a per-cluster inventory, generate stable groups first:
all:
children:
site_a_ops_rke2:
children:
rke2_servers:
rke2_agents:
api_lb:
Then classify nodes by role:
control-plane / master / etcd -> rke2_servers
worker -> rke2_agents
API load balancers -> api_lb
Include readiness as metadata, not as a reason to silently omit the node:
site-a-worker-2:
ansible_host: 192.0.2.20
rke2_node_ready: false
That lets downstream tasks decide whether to skip, remediate, or alert.
Filename-Based Inference
If the caller writes to a per-cluster inventory path, infer what can be safely inferred:
./ansible/generate-inventory.sh \
~/.kube/config \
--output ansible/inventory/site-a-ops-rke2.yaml
The script can derive:
context: site-a-ops-rke2
group name: site_a_ops_rke2
environment: ops
Do not require redundant flags unless ambiguity remains.
Test With A Fake kubectl
Inventory generators should have tests because small formatting changes break downstream playbooks.
A simple fixture pattern:
TMPDIR=$(mktemp -d)
mkdir -p "$TMPDIR/bin"
cat > "$TMPDIR/bin/kubectl" <<'EOF'
#!/usr/bin/env bash
case "$*" in
*"config get-contexts"*)
printf 'site-a-ops-rke2\n'
;;
*"get nodes"*)
cat ./testdata/nodes-site-a-ops.json
;;
*)
exit 1
;;
esac
EOF
chmod +x "$TMPDIR/bin/kubectl"
PATH="$TMPDIR/bin:$PATH" ./ansible/generate-inventory.sh --output /tmp/site-a-ops-rke2.yaml
Then assert the output contains required groups and fields:
grep -q 'site_a_ops_rke2:' /tmp/site-a-ops-rke2.yaml
grep -q 'rke2_servers:' /tmp/site-a-ops-rke2.yaml
grep -q 'rke2_agents:' /tmp/site-a-ops-rke2.yaml
grep -q 'api_lb:' /tmp/site-a-ops-rke2.yaml
grep -q 'rke2_node_ready:' /tmp/site-a-ops-rke2.yaml
Validation
After generation, validate Ansible can parse the file:
ansible-inventory -i ansible/inventory/site-a-ops-rke2.yaml --graph
Then verify host counts against Kubernetes:
kubectl --context site-a-ops-rke2 get nodes --no-headers | wc -l
ansible site_a_ops_rke2 -i ansible/inventory/site-a-ops-rke2.yaml --list-hosts
The counts do not need to match if inventory includes API load balancers, but the difference should be explainable.
Operating Rule
Do not generate inventory as a loose YAML export.
Generate the exact shape expected by playbooks and group_vars, test that shape with a fake kubectl, and keep NotReady nodes visible as inventory facts instead of hiding them.