rke2spray and GitOps Operations Reference
Look up the RKE2 cluster, Ansible lifecycle, GitOps handoff, applications, and verification contracts declared by the current code.
Key facts
rke2sprayowns hosts, RKE2, cluster-foundation add-ons, and the Argo CD bootstrap- The GitOps repository owns platform, data, observability, and storage resources after the root Application
- The single-cluster RKE2, CNI, topology, and network values are declared under
inventory/ - GitOps verifies 15 Argo CD
Applicationresources including the rootbootstrap, and301Giof application PVC requests - Telemetry follows two persistent queues before ClickHouse, then a ClickHouse-owned
ZSTD(3)and SeaweedFS S3 lifecycle - The NFS endpoint, external L4 load balancer, and absence of external backups define the current availability and recovery boundaries
arc42 coverage map
1. Goals and verification baseline
- This page was verified on
2026-08-30against the currentmaincode in both repositoriesjongminchung/rke2sprayis the cluster source- The target repository's
bootstrap/,apps/, andtests/directories define the GitOps contract - When prose differs from executable code, inventory, Playbooks, manifests, and verification scripts are authoritative
| Information | Authoritative code |
|---|---|
| Cluster versions and topology | jamie-kr-gitops/inventory/ |
| Ansible public API | rke2spray/cluster.yml, root Playbooks, and Makefile |
| Kubespray compatibility | rke2spray/docs/kubespray-api-compatibility.yml |
| GitOps handoff gates | rke2spray/extra_playbooks/gitops_bootstrap.yml |
| Argo CD applications | bootstrap/templates/applications.yaml |
| Workload declarations | apps/ |
| Deployment completion | tests/integration/verify.sh |
2. Architecture constraints
- The Kubernetes distribution is fixed at
RKE2 v1.36.3+rke2r1, withCilium 1.20.1as its CNIjamie-kr-gitops/ansible/rke2spray.revselects the verifiedrke2spraycommit
- ClickHouse Server and Keeper use the supported
26.3.25LTS line- Merge concurrency and memory reservations are bounded for the three-replica observability workload
- Control-plane API and Gateway traffic depend on a stable external TCP load balancer outside both repositories
- A server must leave LB backend pools before node removal begins
- Persistent workloads currently share one managed NFS endpoint
durable-rwoand replica spreading do not isolate an NFS service failure or data loss
- Plaintext secrets are not stored in Git
- Host bootstrap values use Ansible Vault, while Kubernetes application values use the SOPS and age boundary
3. System context and repository ownership
C4 · Level 1 · System context
| Resource or state | Owner | Owning code |
|---|---|---|
| Host preparation and system packages | rke2spray | roles/bootstrap-os, roles/system_packages |
| RKE2 servers, agents, and embedded etcd | rke2spray | roles/kubernetes, roles/etcd |
| Cilium and the Gateway API foundation | rke2spray | Inventory and roles/network_plugin |
| cert-manager, trust-manager, and external-dns | rke2spray | roles/kubernetes-apps/rke2_native |
| NFS CSI and StorageClasses | rke2spray | roles/kubernetes-apps/rke2_native |
| Argo CD installation and root Application | rke2spray | roles/kubernetes-apps/argocd, extra_playbooks/gitops_bootstrap.yml |
| Namespaces, NetworkPolicies, and platform foundation | GitOps repository | apps/platform |
| PostgreSQL and poolers | GitOps repository | apps/data |
| ClickStack and OpenTelemetry | GitOps repository | apps/observability |
| SeaweedFS and its S3 route | GitOps repository | apps/storage |
| Rancher and HyperDX public routes | GitOps repository | apps/platform-routes |
- Concurrent ownership of the same Kubernetes resource is not supported
- An RKE2 manifest owner maps to
/var/lib/rancher/rke2/server/manifests/<owner> native-addons-handoffverifies owner, basename, and checksum during an ownership transfer- An
ownership-onlyledger entry transfers ownership without deleting the Kubernetes resource - A
pruneledger entry is removable only with the exact confirmation value
- An RKE2 manifest owner maps to
4. The solution strategy switches reconciliation engines at API readiness
rke2sprayreconciles hosts and RKE2 desired state before the Kubernetes API exists- It validates Kubespray-shaped inventory and translates it into the RKE2 server and agent lifecycle
- The bootstrap gate turns API creation and platform readiness into executable conditions
- It checks nodes, Cilium, CoreDNS, Gateway API, cert-manager, and NFS CSI readiness
- Argo CD continuously reconciles application desired state after API creation
- The root Application creates role-scoped child Applications and sync waves
- Both engines consume one environment release without concurrently owning the same resource
- This boundary is the condition for safely combining Ansible IaC and GitOps in one repository
C4 · Level 2 · Containers and ownership
5. rke2spray building blocks
Topology
| Item | Declaration variable or group |
|---|---|
| Inventory profile | jamie-kr-gitops/inventory/ |
| RKE2 topology | rke2_topology_mode |
| Control plane | kube_control_plane inventory group |
| Workers | kube_node inventory group |
| Datastore | Embedded etcd or rke2_external_datastore |
| Bootstrap host | rke2_bootstrap_server |
| Failure-domain label | rke2_failure_domain_mode |
| Registration endpoint | rke2_registration_address |
Versions and networking
| Item | Declaration variable |
|---|---|
| Kubernetes | kube_version |
| RKE2 | rke2_version and checksum release lock |
| CNI | kube_network_plugin, cilium_version |
| RKE2 Cilium chart | rke2_cilium_chart_url |
| kube-proxy | kube_proxy_remove |
| Cilium kube-proxy replacement | cilium_kube_proxy_replacement |
| Gateway API | gateway_api_enabled, gateway_api_channel |
| Gateway exposure | cilium_gateway_exposure_mode |
| Gateway nodes | cilium_gateway_host_network_nodes |
| Pod CIDR | kube_pods_subnet |
| Service CIDR | kube_service_addresses |
| cert-manager | cert_manager_enabled, cert_manager_version |
| RKE2 ingress controller | rke2_ingress_controller |
Storage and snapshots
| Item | Declaration variable |
|---|---|
| Storage profile | rke2_storage_profile |
| NFS endpoint | rke2_nfs_server, rke2_nfs_share |
| NFS version | rke2_nfs_version |
| RWO contract | rke2_storage_contract.durable_rwo |
| RWX contract | rke2_storage_contract.shared_rwx |
| local-path provisioner | local_path_provisioner_enabled |
| etcd snapshot schedule | rke2_etcd_snapshot_schedule_cron |
| etcd snapshot retention | rke2_etcd_snapshot_retention |
| etcd snapshot compression | rke2_etcd_snapshot_compress |
durable-rwoandshared-rwxexpress workload intent and currently share one NFS failure domainRetainandonDelete: retainrestrict PV deletion and do not provide NFS data backups
RKE2 native add-on profile
| Source | Function |
|---|---|
external-dns.yaml | Manage the allowed public DNS records |
nfs-csi.yaml | Manage NFS CSI controllers and node plugins |
nfs-csi-pdb.yaml | Manage NFS CSI controller availability policy |
reloader.yaml | Roll out workloads after Secret or ConfigMap changes |
trust-manager.yaml | Manage the cluster trust bundle |
6. rke2spray operations runtime
- These targets are defined by the repository-root
Makefile
| Target | Main inputs | Behavior |
|---|---|---|
make check | Development tools | Run lint, unit, contract, parity, and syntax checks |
make inventory-graph | INVENTORY, VAULT_PASSWORD_FILE | Print the resolved inventory graph |
make inventory-vault-check | INVENTORY, VAULT_PASSWORD_FILE | Check Vault inputs and file mode without SSH |
make inventory-preflight | Inventory and environment | Check live topology, ports, and storage prerequisites |
make cluster-converge | Inventory and Vault | Run cluster.yml |
make cluster-health | Inventory and Vault | Inspect API, Node, etcd, and service health |
make cluster-scale NODE=<node> | Worker name | Reconcile one selected worker with scale.yml |
make cluster-add-server NODE=<node> | Server name | Reconcile one server with cluster.yml --limit |
make cluster-upgrade | Updated version lock | Cordon, drain, change release, and verify readiness |
make cluster-snapshot SNAPSHOT=<name> | Snapshot name | Create a manual embedded-etcd snapshot |
make cluster-recover SERVER=<server> SNAPSHOT=<path> | Server and absolute path | Restore the control plane after checksum validation |
make cluster-remove-node NODE=<node> | Node and LB-removal confirmation | Remove LB, etcd, Kubernetes Node, and RKE2 state |
make cluster-rotate-certificates | Inventory and Vault | Rotate RKE2 certificates |
make gateway-access | Inventory and Vault | Inspect and verify the Gateway access contract |
make gitops-bootstrap | GitOps inputs and Vault | Apply Argo CD and the root Application after readiness gates |
make gitops-integration | GITOPS_INTEGRATION_SCRIPT | Run a GitOps integration script on the bootstrap server |
-
Cluster access does not copy a long-lived kubeconfig into the environment repository
kubeconfig_localhost: falseandkubectl_localhost: falsekeep Ansible-controller artifacts disabled- Read-only checks should run the bundled
kubectlremotely throughscripts/run_with_ssh_jump.py - Local integration tests use
operations/with-cluster-kubeconfig.sh, which retrieves/etc/rancher/rke2/rke2.yamlintoXDG_RUNTIME_DIR, opens a loopback SSH tunnel, verifies/readyz, and removes the credential on exit operations/cluster-kubeconfig.mdis the detailed issuance, validation, cleanup, and incident-response runbook
-
cluster-reset,cluster-recover,cluster-remove-node, add-on pruning, and failure probes can destroy or interrupt state- Each target requires a bounded target, confirmation value, or opt-in environment variable
- Server removal requires prior removal from external LB backends on ports
80,443,6443, and9345 - Off-host snapshot transfer and retention are outside the
rke2spraycontract
6. GitOps handoff runtime
Argo CD declaration
| Item | Declared value or variable |
|---|---|
| Argo CD | 3.5.1 |
| Argo CD Helm chart | 10.4.0 |
| helm-secrets | 4.7.7 |
| SOPS | 3.13.3 |
| Repository | argocd_gitops_repo_url |
| Revision | argocd_gitops_revision |
| Destination | argocd_gitops_destination_server |
| Initial-install deferral | argocd_defer_install_until_platform_ready |
| Root apply during normal cluster run | argocd_apply_root_application |
Bootstrap gates
-
extra_playbooks/gitops_bootstrap.ymlrequires all of the following conditions- RKE2 API
/readyzreturnsok - Every RKE2 Node reports
Ready - Cilium and CoreDNS rollouts are complete
- Gateway API CRDs report
Establishedand the CiliumGatewayClassreportsAccepted - cert-manager and NFS CSI rollouts are complete
- NFS migration-gate status, database status, StorageClass UID, and cluster UID match the live cluster
- RKE2 API
-
Secrets move from Ansible Vault into Argo CD Secrets
- Read-only SSH private key for the Git repository
- GitHub known-hosts value
- SOPS age private key
- Neither repository stores plaintext private keys
Animated runtime view · two reconciliation engines
- ReviewInventory and manifests
- ConvergeAnsible + RKE2
- Ready gateAPI, nodes and add-ons
- ReconcileArgo CD sync waves
- VerifyHealth and disruption contracts
7. Deployment view: GitOps applications
Application inventory
| Application | Wave | Source | Prune |
|---|---|---|---|
bootstrap | root | bootstrap/ chart | true |
platform | -40 | apps/platform | true |
cloudnative-pg | -30 | chart 0.29.0 | true |
clickstack-operators | -30 | chart 1.1.0 | true |
clickstack | -20 | chart 3.1.1 and SOPS values | false |
postgresql | -10 | apps/data/postgresql | false |
rancher | -10 | chart 2.14.3 | true |
seaweedfs-database | -9 | apps/storage/seaweedfs-database | false |
seaweedfs-foundation | -8 | apps/storage/seaweedfs-foundation | true |
seaweedfs | -7 | chart 4.42.0 | false |
seaweedfs-route | -6 | apps/storage/seaweedfs-route | true |
otel-agent | 0 | OTel chart 0.170.0 | true |
otel-cluster | 0 | OTel chart 0.170.0 | true |
otel-gateway | 0 | OTel chart 0.170.0 | false |
platform-routes | 1 | apps/platform-routes | true |
- Applications with pruning disabled do not use a finalizer
- Applications with pruning enabled use
resources-finalizer.argocd.argoproj.io - Every Application uses automated sync and
selfHeal: true
Workload topology
| Workload | Current topology | Persistent allocation |
|---|---|---|
| PostgreSQL | 3 instances | 10Gi per instance |
| PgBouncer RW and RO | 2 instances each | None |
| ClickHouse | 1 shard × 3 replicas | 60Gi per replica |
| Keeper | 3 replicas | 2Gi per replica |
| MongoDB | 3 replicas | 5Gi data and 1Gi log |
| HyperDX | 2 replicas | None |
| ClickStack ingestion | 2 StatefulSet replicas | 1Gi queue per replica |
| OTel Agent | DaemonSet on workers | None |
| OTel Cluster Collector | 1 replica | None |
| OTel Gateway | 2 replicas | 1Gi queue per replica |
| SeaweedFS Master | 3 replicas | 1Gi per replica |
| SeaweedFS Volume | 3 replicas | 20Gi per replica |
| SeaweedFS Filer | 2 replicas | CNPG metadata |
| SeaweedFS S3 | 2 replicas | None |
| ClickHouse S3 gateway | 3 replicas | None |
- Application PVC requests total
301Giand the deployment ceiling is400Gi - PostgreSQL, ClickHouse, Keeper, MongoDB, and SeaweedFS Master and Volume replicas spread across three distinct workers
- SeaweedFS uses placement
001, retaining two volume copies on different racks for newly written S3 chunks
7. Deployment view: services and public routes
ClusterIP services
| Purpose | Endpoint |
|---|---|
| OTLP gRPC | otel-gateway.observability.svc.cluster.local:4317 |
| OTLP HTTP | otel-gateway.observability.svc.cluster.local:4318 |
| ClickStack ingest | clickstack-ingest.observability.svc.cluster.local:4317 |
| ClickHouse cold S3 | clickhouse-s3-gateway.observability.svc.cluster.local:8333 |
| PostgreSQL RW | postgresql-pooler-rw.postgresql.svc.cluster.local:5432 |
| PostgreSQL RO | postgresql-pooler-ro.postgresql.svc.cluster.local:5432 |
- Data services in
observability,postgresql, andseaweedfsmay not useNodePortorLoadBalancer - The ClickHouse cold S3 endpoint is private and distinct from the public SeaweedFS S3 API
Gateway API routes
| Hostname | Backend | Listener |
|---|---|---|
<rancher-hostname> | cattle-system/rancher:80 | apps-https |
<observability-hostname> | observability/clickstack-app:3000 | apps-https |
<s3-hostname> | seaweedfs/seaweedfs-s3:8333 | s3-root-https |
<s3-wildcard-hostname> | seaweedfs/seaweedfs-s3:8333 | s3-https |
8. Cross-cutting observability and storage concepts
Authenticated durable ingestion
Observability runtime · authenticated durable path
- SourcesApplication OTLP, node logs and cluster metrics
- OTel Gateway ×2Batch, retry and 1Gi PVC queue per replica
- ClickStack ingest ×2Bearer auth and 1Gi PVC exporter queue
- ClickHouse ×3ReplicatedMergeTree on hot durable-rwo volumes
- SeaweedFS S3Private gateway ×3 and 001 two-copy cold volumes
- Every telemetry signal enters through the two-replica OTel Gateway
- Agent, Cluster Collector, and application OTLP traffic share the same batch, infinite retry, and
1GiPVC queue boundary - The Gateway authenticates to ClickStack ingestion with the protected HyperDX API credential
- Agent, Cluster Collector, and application OTLP traffic share the same batch, infinite retry, and
- ClickStack ingestion is a two-replica StatefulSet with a second persistent queue boundary
- Both ClickHouse exporters use
file_storage, queue size8192, and infinite retry - A ClickHouse outage after Gateway delivery therefore remains recoverable from the ingestion PVC queue
- Both ClickHouse exporters use
- Resource identity preserves application semantics
- Collectors add
k8s.cluster.name=jamie-krand their platform role - They do not overwrite an SDK-provided
service.namespace
- Collectors add
- NetworkPolicies limit OTLP and S3 ingress
- Only the OTel Gateway and HyperDX can reach ClickStack ingestion ports
- Only ClickHouse can reach the private S3 data port, while the Cluster Collector can reach its metrics port
Compression and cold-storage lifecycle
ClickHouse lifecycle · compression and cold tier
- Day 0Ingest replicated telemetry into the hot volume
- Hot windowQuery recent parts from local ClickHouse PVCs
- Day 2Recompress eligible parts with ZSTD level 3
- Day 3Move parts to the cold_s3 volume
- Day 14Delete table parts and owned S3 objects
- All nine ClickStack telemetry tables use the
hot_coldClickHouse storage policy- New replicated parts start on the
defaulthot volume backed bydurable-rwo - Eligible parts are recompressed with
CODEC(ZSTD(3))after two days - Parts move to the
cold_s3volume after three days - ClickHouse deletes table parts and their owned S3 objects after 14 days
- New replicated parts start on the
- ClickHouse is the single lifecycle owner
- SeaweedFS does not apply an independent bucket TTL that could remove objects before ClickHouse metadata changes
- The PostSync retention Job creates the private bucket idempotently before applying table settings and TTL expressions
- Cold storage reduces hot PVC pressure but is not a backup
- ClickHouse replicas use separate S3 prefixes and still depend on the same SeaweedFS and NFS failure domain
- Deletion, corruption, or loss of the shared storage layer is not recoverable from Git
9. Architecture decisions for telemetry retention
| Decision | Reason | Consequence |
|---|---|---|
| Two persistent queue boundaries | Preserve accepted telemetry across Gateway, ingestion, or ClickHouse restarts | Adds two Gateway and two ingestion PVCs |
| Dedicated private S3 gateway | Isolate ClickHouse credentials and traffic from the public S3 route | Adds three stateless gateway replicas and a PDB |
| ClickHouse-owned TTL | Keep table metadata and object deletion in one state machine | SeaweedFS bucket lifecycle must remain disabled for this bucket |
| Recompress before cold movement | Lower long-term object size without applying maximum compression to hot ingestion | Background merges consume CPU after two days |
| Fourteen-day deletion | Bound telemetry storage growth | Older telemetry requires a separate backup design if retention requirements change |
10. Quality requirements and verification contracts
rke2spray static verification
| Command | Coverage |
|---|---|
make check | YAML and Ansible lint, unit, role interface, release lock, lifecycle, topology, and syntax |
make parity | Public API parity with the pinned Kubespray baseline |
make inventory-vault-check | .vault_pass mode and inventory Vault values |
GitOps static verification
| Command | Coverage |
|---|---|
tests/static/validate.sh | Policy, YAML and shell lint, gitleaks, and SOPS encryption |
tests/static/render.sh | Bootstrap chart, upstream charts, and Kustomize rendering |
kubeconform | Kubernetes and extracted CRD schemas |
Live-cluster verification
-
tests/integration/verify.shdefines the following completion state- All 15 Argo CD Applications report
SyncedandHealthy - Stateful and stateless Application prune and finalizer policies match
- Three-replica workloads are
Readyon three distinct workers - Services excluded from public exposure remain
ClusterIP - No Pod is Pending and every Node root filesystem remains below
60%usage durable-rwousesnfs.csi.k8s.io,Immediate,Retain, andonDelete=retain- All target PVCs use
durable-rwoand total301Gi - ClickStack ingestion has two ready replicas, while the private S3 gateway has three ready replicas on distinct workers
- SeaweedFS master and filer commands retain the
001two-copy replication placement - Collector self-metrics include the
otelcol_*family in ClickHouse - All nine telemetry tables use
hot_cold,ZSTD(3), three-day cold movement, and fourteen-day deletion - The
cold_s3disk is visible insystem.disks - Gateway
HTTPRouteresources reportAccepted=TrueandResolvedRefs=True
- All 15 Argo CD Applications report
-
Verification that writes or deletes state requires an explicit opt-in variable
- The NFS performance gate requires
RUN_NFS_PERFORMANCE_GATE=true - The database gate requires
RUN_NFS_DATABASE_GATE=true - Tri-signal telemetry generation requires
RUN_TELEMETRY_E2E=true - Workload disruption checks require
RUN_DISRUPTIVE_TESTS=true
- The NFS performance gate requires
-
The tri-signal E2E gate proves the complete write path
- A digest-pinned
telemetrygenPod emits logs, metrics, and traces with a uniqueservice.name - Queries require all three signal families and
k8s.cluster.name=jamie-krto appear in ClickHouse - Generator Pods are removed after the test, while their small telemetry payload follows the normal retention lifecycle
- A digest-pinned
11. Risks and recovery boundaries
| Boundary | Current contract |
|---|---|
| Single-node failure | Control-plane and major stateful-workload replicas provide tolerance |
| External LB failure | Infrastructure failure domain outside both repositories |
| NFS endpoint failure | Can affect every NFS-backed stateful workload |
| SeaweedFS S3 cold tier | Capacity tier inside the same platform failure boundary, not backup |
| PVC deletion | Retain and stateful prune: false restrict automatic deletion |
| Complete NFS loss | Not recoverable from repository assets alone |
| PostgreSQL backup and PITR | Out of scope |
| Existing PVC and legacy workloads | Automatic migration is out of scope |
| Off-host etcd snapshot retention | Outside the rke2spray contract |
| Argo CD SSO and operator RBAC hardening | Out of scope |
| Temporary admin kubeconfig | Cluster-admin credential limited to private runtime and SSH tunnel |
12. Core glossary
| Term | Meaning in this document |
|---|---|
| Desired state | A reviewed Git declaration that a reconciliation engine makes real |
| Inventory | Host, topology, and environment input used before the Kubernetes API exists |
| Bootstrap gate | Checks API, Node, CNI, and storage readiness before the Argo CD handoff |
| Native AddOn | Cluster foundation owned by rke2spray through an RKE2 manifest or HelmChart |
| Root Application | Entry point that expands jamie-kr-gitops/bootstrap into child Applications |
| Reconciliation owner | The single actor that converges a resource from desired to actual state |
Documentation update contract
- Update this page's
updatedAtandverifiedAtwhen any of the following code changes- Version, topology, network, or storage inputs in
inventory/ - Public operator targets or required variables in
Makefile - Readiness gates in
gitops_bootstrap.yml - Applications, waves, chart versions, or prune policies in
bootstrap/templates/applications.yaml - Replicas, PVCs, Services, or public routes under
apps/ - Completion assertions in
tests/integration/verify.sh - Kubeconfig issuance or cleanup behavior in
operations/cluster-kubeconfig.mdandoperations/with-cluster-kubeconfig.sh
- Version, topology, network, or storage inputs in
OpenTelemetry, ClickStack, and SeaweedFS cold-tier operations reference
Reference the external OTLP boundary, node-agent loop prevention, gateway durability queue, ClickHouse cold S3 retention, and SeaweedFS S3 audit path.
rke2spray vs. Kubespray
Compare the Ansible automation layer separately from Kubespray's kubeadm execution layer and rke2spray's RKE2 execution layer.