ExplanationKubernetes
rke2spray vs. Kubespray
Compare the Ansible automation layer separately from Kubespray's kubeadm execution layer and rke2spray's RKE2 execution layer.
Key takeaways
- The direct Ansible comparison is between the inventory, playbook, role, variable, tag, and validation contracts of Kubespray and
rke2spray - The bootstrap, runtime, and PKI differences between kubeadm and RKE2 belong to the Kubernetes execution-engine layer rather than the Ansible repository layer
- Kubespray is the upstream kubeadm-based implementation, while
rke2sprayis a separate Ansible implementation that translates its public API into an RKE2 lifecycle - Shared
cluster.yml, inventory groups, and role paths preserve names and operational intent without guaranteeing identical task bodies, variable semantics, or resulting state rke2sprayprovides only the reusable core and sample inventory, whilejamie-kr-gitopsowns the realjamie-krinventory as consumer code- Selection must consider both Ansible reuse and whether the team wants to own kubeadm component choice or RKE2 standardization
The comparison separates three layers
| Layer | Kubespray path | rke2spray path | Comparison question |
|---|---|---|---|
| Ansible automation | Original Kubespray playbooks and roles | RKE2 adapters that track the Kubespray API | How are inputs and tasks interpreted |
| Kubernetes bootstrap | kubeadm init/join | RKE2 server and agent join | How is the cluster created |
| Node execution and operation | Composed Kubernetes components | RKE2 service and release unit | What unit is upgraded and recovered |
- The Ansible layer interprets inventory and commands before passing them to the execution-engine layer
- The engine difference causes the implementation differences in Ansible roles, variables, and tags
- The
rke2sprayfindings follow the verifiedmaincode anddocs/kubespray-api-compatibility.yml
jamie-kr-gitops owns the real inventory
rke2sprayandjamie-kr-gitopsare separated as provider and consumerrke2sprayprovides reusable playbooks, roles, plugins, defaults, andinventory/samplejamie-kr-gitopsowns real hosts, group variables, host variables, encrypted Vault values, and execution wrappers
- The dependency remains one-way, with
jamie-kr-gitopsselecting a pinnedrke2sprayrelease- The
rke2spraycore does not referencejamie-krnames, addresses, domains, or topology jamie-kr-gitopsinvokesrke2spraythrough a Collection or pinned checkout
- The
jamie-kr-gitops/
├── inventory/
│ └── jamie-kr/
│ ├── hosts.yml
│ ├── group_vars/
│ └── host_vars/
├── ansible/
│ └── rke2spray.rev
└── apps/
└── ...
│
└── consumes a pinned rke2spray checkout
rke2spray/
├── cluster.yml
├── roles/
├── plugins/
└── inventory/
└── sample/jamie-kr-gitopsmanages both Ansible IaC and Argo CD GitOps declarations for the environmentinventory/andansible/rke2spray.revare reconciliation inputs used before the Kubernetes API existsbootstrap/andapps/are reconciliation inputs used by Argo CD after the Kubernetes API exists
- After the inventory migration, commands run from the
jamie-kr-gitopsrepository
make inventory-vault-check
make inventory-preflight
make cluster-converge
make gitops-bootstrap- GitOps in the repository name describes the management model and does not exclude Ansible IaC ownership
- Inventory is declarative cluster desired state consumed before the Kubernetes API exists
apps/is platform desired state reconciled by Argo CD after the Kubernetes API exists- Both states can be reviewed as one environment release while
rke2sprayand Argo CD remain separate execution engines
C4 · Level 1 · System context
Platform operatorReviews desired state and runs guarded operations
jamie-kr-gitopsOwns environment inventory and Kubernetes desired state
rke2sprayInterprets Kubespray-shaped input as an RKE2 lifecycle
RKE2 platformRuns Kubernetes API, Argo CD, platform and data workloads
External systemsGitHub, DNS, TCP load balancer and managed NFS
The Ansible layer interprets the public API differently
| Ansible area | Kubespray | rke2spray |
|---|---|---|
| Project role | Upstream implementation maintained under Kubernetes SIGs | Adapter between the Kubespray API and RKE2 |
| Collection | kubernetes_sigs.kubespray | jongminchung.rke2spray |
| API baseline | Current repository playbooks, roles, defaults, and tags | Upstream API pinned by KUBESPRAY_BASELINE |
| Inventory groups | Original meaning of kube_control_plane, kube_node, and etcd | Same names reinterpreted as RKE2 server, agent, and datastore topology |
| Root playbooks | Canonical lifecycle implementation | Adapter entrypoints that preserve names and operational intent |
| Role bodies | Execute kubeadm, runtime, CNI, and etcd tasks directly | Classify roles as supported, adapter, conditional, or unsupported |
| Default variables | Consumed directly by the corresponding Kubespray roles | Mapped to RKE2 configuration or rejected at preflight when no equivalent exists |
| Tags | Select upstream tasks | Preserve parity names while selecting RKE2 tasks |
| Extension points | Upstream roles/, contrib/, and documentation | RKE2 extra_playbooks/, native add-ons, and acceptance harness |
| Compatibility failure | Determined by upstream validation and role execution | Forbids silent ignore and reports a preflight error with an alternative |
| Change intake | Consumes Kubespray releases and master directly | Reviews mappings, exceptions, and tests in baseline update pull requests |
- The central Ansible difference is which task graph a shared public name resolves to
- Kubespray's
kubernetes/kubeadmrole executes kubeadm tasks - The
rke2sprayrole at that path connects RKE2 bootstrap, node, and control-plane adapters
- Kubespray's
- A variable present in
rke2spraydoes not automatically mean that the Kubespray feature is supportedcontainer_manager: docker,container_manager: crio, kubeadm patches, and direct external CNI installation are currently rejected- Registry mirror, authentication, and TLS inputs that can be represented losslessly map to RKE2
registries.yaml
- Shared inventory and playbook names can reduce runbook translation cost without creating drop-in compatibility
The kubeadm layer describes Kubespray's execution model
- This layer describes the kubeadm-based cluster model automated by Kubespray, not the Ansible API difference between the two repositories
| Area | Kubespray implementation on kubeadm |
|---|---|
| Bootstrap | Uses kubeadm init, kubeadm join, and phases |
| Control plane | Builds API server, scheduler, and controller configuration through kubeadm configuration and static Pod manifests |
| Kubelet | Kubespray roles manage its binary, configuration, and systemd unit |
| Runtime | Separate roles install and configure containerd, CRI-O, or the Docker path |
| CNI | Ansible controls the ordering between Kubernetes API bootstrap and CNI-specific roles or manifests |
| etcd | Kubespray roles configure colocated or separate topology according to the etcd inventory group |
| PKI | Uses kubeadm and Kubespray certificate commands and paths |
| Versions | Composes Kubernetes, etcd, runtime, and CNI versions by component |
| Upgrade | Cordons and drains before replacing components in a defined order |
- The kubeadm model provides flexibility to select and replace Kubernetes components individually
- Its tradeoff is that the team owns compatibility, upgrade ordering, and failure boundaries across more combinations
The RKE2 layer describes rke2spray's execution model
- This layer describes the RKE2 distribution automated by
rke2spray, not the Ansible interface difference
| Area | rke2spray implementation on RKE2 |
|---|---|
| Bootstrap | Joins servers and agents through a token, bootstrap server, and supervisor :9345 |
| Control plane | The rke2-server service owns the API server, scheduler, and controller lifecycle as a bundle |
| Kubelet | Included in the rke2-server or rke2-agent lifecycle |
| Runtime | Fixes execution to bundled containerd and manages only registry inputs through registries.yaml |
| CNI | Selects packaged Canal, Calico, Cilium, or Flannel through RKE2 cni configuration |
| etcd | Defaults to embedded etcd and can connect to Kubespray-managed external etcd |
| PKI | Uses RKE2 certificate commands, paths, and automatic rotation contracts |
| Versions | Uses a checksum-locked rke2_version that combines a Kubernetes patch and RKE2 revision |
| Upgrade | Converges servers sequentially and agents in bounded batches to an RKE2 release |
- The RKE2 model standardizes the runtime, PKI, and control-plane lifecycle around a release and service unit
- Its tradeoff is that requirements such as alternate runtimes, kubeadm phases, and external or custom CNIs become constraints outside the current standard contract
- A feature technically possible in RKE2 must be distinguished from one supported by the current
rke2sprayadapter
Engine differences translate into Ansible contracts
| Kubespray API | Kubespray task | rke2spray interpretation | Classification |
|---|---|---|---|
kube_version | Selects Kubernetes component versions | Validates a release-locked rke2_version with the same patch | Adapter |
kubernetes/kubeadm | Executes kubeadm bootstrap and join | Translates into RKE2 server and agent bootstrap | Adapter |
container-engine | Installs and tunes a runtime | Manages only registry configuration for bundled containerd | Adapter |
container_manager: docker or crio | Installs the selected runtime | Rejects before lifecycle mutation | Unsupported |
network_plugin | Executes CNI-specific roles and manifests | Translates into packaged CNI selection | Adapter |
| External or custom CNI | Installs external manifests | Rejects because the bootstrap and Ready-order adapter is incomplete | Unsupported |
etcd role | Installs and configures etcd | Runs only when external datastore mode is selected | Conditional |
recover-control-plane.yml | Recovers etcd and control-plane state | Runs RKE2 cluster reset and restore from an embedded-etcd snapshot | Conditional |
- An adapter translates intent without reproducing the original implementation
- Conditional means the public API is valid only when a datastore or topology prerequisite holds
- Unsupported can mean that the current
rke2spraycontract lacks a safe adapter, not that RKE2 can never implement the feature
Shared playbooks preserve intent only
| Operational intent | Shared entrypoint | Kubespray execution | rke2spray execution |
|---|---|---|---|
| Install or reconcile | cluster.yml | Converges kubeadm, runtime, CNI, and etcd tasks | Converges RKE2 servers and agents |
| Add workers | scale.yml | Configures kubelet, runtime, and CNI | Installs the RKE2 agent |
| Upgrade | upgrade-cluster.yml | Replaces components sequentially | Replaces a checksum-locked RKE2 release |
| Remove a node | remove-node.yml | Cleans Kubernetes, etcd, and host state | Cleans the RKE2 service and state |
| Reset | reset.yml | Cleans kubeadm, runtime, and network state | Uses the official RKE2 uninstaller path |
rke2sprayadds health, snapshots, certificate rotation, and add-on plan, converge, and prune operations underextra_playbooks/- Destructive removal, recovery, and ownership-transfer paths add confirmation and preflight gates
Selection evaluates Ansible reuse and the execution model together
| Requirement | Evaluate first | Reason |
|---|---|---|
| Kubespray roles, variables, and tags must retain their original semantics | Kubespray | Its upstream task graph is the public API implementation |
| RKE2 is the standard while Kubespray-shaped runbooks should be retained | rke2spray | It translates the public names into the RKE2 lifecycle |
| Custom CNI, alternate runtime, or kubeadm phase control is mandatory | Kubespray | Those inputs are rejected by the current rke2spray contract |
| Operations should use one release, service, and PKI contract | rke2spray | It treats the RKE2 distribution as the execution unit |
| Broad community, provider, and CNI combinations are the priority | Kubespray | It consumes the Kubernetes SIGs upstream directly |
| A dedicated owner can maintain an adapter across Kubespray and RKE2 | rke2spray | The team accepts baseline, mapping, and acceptance-test maintenance |
- Moving an existing Kubespray cluster to
rke2sprayis a Kubernetes engine migration rather than an Ansible repository swap- Shared inventory groups do not convert current cluster state, PKI, datastore, or CNI state
- Existing variables and custom roles must be audited against the
supported,adapter,conditional, andunsupportedclassifications
Conclusion and next actions
- At the Ansible layer, Kubespray is the original implementation and
rke2sprayis an RKE2 adapter that preserves public names - At the execution layer, kubeadm composes components while RKE2 integrates the distribution, service, and release lifecycle
- At the ownership layer,
jamie-kr-gitopsowns the real inventory whilerke2sprayremains the reusable installation engine - A proof of concept should validate variable mappings, tag reachability, upgrades, node removal, and snapshot recovery in addition to running the shared
cluster.ymlentrypoint
Sources
rke2spray and GitOps Operations Reference
Look up the RKE2 cluster, Ansible lifecycle, GitOps handoff, applications, and verification contracts declared by the current code.
Choose a Cilium Gateway API External Entry Point
Understand the Cilium Gateway API data path and choose between LoadBalancer, NodePort, and hostNetwork exposure.