Skip to content

Platform

The core infrastructure components that run the cluster. Everything here is managed by ArgoCD; each component's own page documents its directory layout and configuration.

That is a lot of components for a homelab, and every one of them exists because bare metal does not come with the thing a cloud provider would have handed you. No load balancer, no managed certificates, no block storage API, no identity provider, no backup service. This section is the bill for not having those.

Components

Alphabetical, with the namespace it lands in and the sync wave it lands at. The wave ordering below explains why those numbers are what they are.

Component Namespace Wave What it does
authentik authentik 2 Single sign-on for every platform UI
backup backup 1 Velero, the CSI snapshot controller and an etcd snapshot CronJob — see Backups & Recovery
cert-manager cert-manager -5 Let's Encrypt wildcards over a Route53 DNS-01 challenge
cilium kube-system -1 CNI, kube-proxy replacement, Gateway API, LoadBalancer addresses, WireGuard, Hubble
external-dns external-dns 2 Publishes Route53 records from HTTPRoutes
external-secrets external-secrets -6 Bridges OpenBao to native Kubernetes Secrets
gateway-api gateway-system -4 The two Gateways and the HTTP-to-HTTPS redirect
kube-vip kube-system -1 Holds the control-plane VIP; adopts the static pod Ignition bootstraps — see Control Plane VIP
kubelet-csr-approver kubelet-csr-approver 1 Approves kubelet-serving CSRs against the inventory — see Metrics Server
kubescape kubescape 2 Nightly CIS, NSA and MITRE posture scans, exported to Grafana
kured kured 2 Drains and reboots nodes to apply staged OS, Kubernetes and containerd updates
logging logging 1 Loki and Grafana Alloy, for container, journal and audit logs
metrics-server kube-system 2 The metrics.k8s.io API behind kubectl top and every HPA
monitoring monitoring 1 Prometheus, Grafana, Alertmanager, node-exporter, kube-state-metrics
openbao openbao 0 Cluster-wide secret store
rook-ceph rook-ceph -3 to 0 Replicated block storage and an S3 object store
security policies kube-system 3 Pod Security Admission levels and default-deny ingress policies

Traffic Flow

flowchart LR
    Client([Client]) --> LB[Cilium LoadBalancer]
    LB --> GW[Gateway API]

    subgraph Cluster
        GW -->|HTTPRoute| Svc[Service]
        Svc --> Pod[App Pod]
    end

    style Client fill:#f9f,stroke:#333

Four hops, and Cilium is three of them. When a hostname stops answering, the question is which hop stopped: does the Gateway still hold its LoadBalancer IP, does the HTTPRoute still say Accepted, does the Service still have endpoints. In that order — the answer is usually the first one.

HTTPRoute Locations

HTTPRoutes are co-located with their respective apps:

Service URL HTTPRoute Location
ArgoCD argo.infra.k8s.wlkr.ch payload/argocd/httproute.yaml
Authentik auth.infra.k8s.wlkr.ch payload/platform/authentik/httproute.yaml
Prometheus prometheus.infra.k8s.wlkr.ch payload/platform/authentik/httproute.yaml
Alertmanager alertmanager.infra.k8s.wlkr.ch payload/platform/authentik/httproute.yaml
Grafana monitoring.infra.k8s.wlkr.ch payload/platform/monitoring/httproute.yaml
Hubble hubble.infra.k8s.wlkr.ch payload/platform/cilium/httproute.yaml
OpenBao UI vault.infra.k8s.wlkr.ch payload/platform/openbao/httproute.yaml
Rook Dashboard rook.infra.k8s.wlkr.ch payload/platform/rook-ceph/httproute.yaml
Apps <app>.k8s.wlkr.ch payload/workloads/<app>/httproute.yaml

Usage

Bootstrap (before ArgoCD)

make install-core  # Gateway API + Prometheus operator CRDs, Cilium, cert-manager
make install-argo  # ArgoCD

GitOps (after ArgoCD)

Two parent ArgoCD Applications manage the cluster:

Application Role Path
Platform Parent Core platform components payload/root.yaml (App: platform)
GitOps ArgoCD's own config + HTTPRoute payload/argocd/

A third parent, workloads, is added back alongside the first workload. See Adding a Workload.

Excluded from sync:

  • cilium/values.yaml, values.yaml (Helm values)
  • README.md (documentation)
  • **/*.template (credential templates)

Sync wave ordering. This is the dependency graph made explicit, and it is the reason a fresh bootstrap converges rather than deadlocking on a CRD that does not exist yet:

Wave Applications Other resources in the wave
-10 gateway-api-crds
-6 external-secrets
-5 cert-manager
-4 gateway-api
-3 rook-ceph
-2 rook-ceph-operator
-1 cilium, kube-vip, rook-ceph-cluster
0 openbao Ceph CSI OperatorConfig and Driver
1 kube-prometheus-stack, logging, kubelet-csr-approver, backup the route53-credentials ExternalSecret
2 authentik, external-dns, kubescape, kured, loki, metrics-server, snapshot-controller both Let's Encrypt ClusterIssuers
3 alloy, velero, security the gateway Certificates, the VolumeSnapshotClass

Waves 1 through 3 are where a component and its own children separate: logging is the parent Application at 1, and it brings Loki at 2 and Alloy at 3, in that order because a collector with nowhere to ship is just a collector. The same split puts backup at 1 ahead of the snapshot controller it needs at 2 and the Velero that needs both at 3.

The negative waves are the interesting half: nothing above wave 0 can work until networking, storage and certificates exist, so those get to go first and everything else waits its turn.

External Secrets sits at the very front despite needing OpenBao, which arrives six waves later, because what the wave has to guarantee is the CRD, not a working secret store. An ExternalSecret whose CRD is missing is not applied at all, and ArgoCD keeps the operation open waiting for the rest of the wave — so cert-manager's Route53 credential, which sits beside the Certificate resources that cannot go Ready without it, would wait for a retry that never comes. A store that is not ready yet is a normal, self-correcting state; a resource that was never applied is not.