Architectural overview of the fzymgc-house self-hosted Kubernetes cluster.
graph TB
subgraph Internet["Internet"]
Users["Users"]
CF["Cloudflare"]
end
subgraph Hardware["Hardware Layer"]
TPI_A["TuringPi Alpha"]
TPI_B["TuringPi Beta"]
end
subgraph Kubernetes["Kubernetes Layer (k3s)"]
CP["Control Plane<br/>tpi-alpha-1,2,3"]
Workers["Workers<br/>tpi-alpha-4, tpi-beta-1-4"]
end
subgraph Platform["Platform Services"]
Vault["Vault<br/>Secrets"]
Keycloak["Keycloak<br/>SSO/OIDC"]
Traefik["Traefik<br/>Ingress"]
ArgoCD["ArgoCD<br/>GitOps"]
end
subgraph Apps["Applications"]
Grafana["Grafana"]
VM["VictoriaMetrics"]
Other["Other Apps"]
end
Users --> CF --> Traefik
Traefik --> Keycloak
Traefik --> Apps
TPI_A --> CP
TPI_B --> Workers
CP --> Platform
Workers --> Apps
Vault --> Platform
Vault --> Apps
ArgoCD --> Apps
| Layer | Location | Tool | Purpose |
|---|
| 1. Cluster Deployment | ansible/ | Ansible | Node configuration, k3s installation |
| 2. Infrastructure Config | tf/ | Terraform | Vault, Keycloak, Grafana setup |
| 3. Application Deployment | argocd/ | ArgoCD | GitOps-driven app manifests |
graph LR
subgraph L1["Layer 1: Ansible"]
A1["Node Config"]
A2["k3s Install"]
A3["CNI Setup"]
end
subgraph L2["Layer 2: Terraform"]
T1["Vault Config"]
T2["Keycloak Setup"]
T3["Grafana Config"]
end
subgraph L3["Layer 3: ArgoCD"]
K1["Helm Charts"]
K2["Kustomize"]
K3["Raw Manifests"]
end
A1 --> A2 --> A3 --> T1 --> T2 --> T3 --> K1 & K2 & K3
Handles physical node configuration and k3s installation.
| Role | Purpose |
|---|
k3s-server | Control plane deployment |
k3s-agent | Worker node deployment |
kube-vip | API server HA with VIP |
cilium | CNI networking via Helm (eBPF + Hubble) |
longhorn-disks | Storage preparation |
Configures infrastructure services via HCP Terraform.
| Module | Purpose |
|---|
cluster-bootstrap | Initial secrets and RBAC |
vault | Secrets management policies |
keycloak | Identity provider configuration |
grafana | Dashboards and datasources |
cloudflare | DNS records and tunnels |
core-services | Shared service configuration |
hcp-terraform | HCP Terraform workspace management |
GitOps-driven application deployment.
| Pattern | Use Case |
|---|
| Helm charts | Complex applications (Grafana, Vault) |
| Kustomize | Environment overlays |
| Raw manifests | Simple resources |
Two TuringPi 2 boards with RK1 compute modules:
graph TB
subgraph Alpha["TuringPi Alpha"]
A1["tpi-alpha-1<br/>Control Plane"]
A2["tpi-alpha-2<br/>Control Plane"]
A3["tpi-alpha-3<br/>Control Plane"]
A4["tpi-alpha-4<br/>Worker"]
end
subgraph Beta["TuringPi Beta"]
B1["tpi-beta-1<br/>Worker"]
B2["tpi-beta-2<br/>Worker"]
B3["tpi-beta-3<br/>Worker"]
B4["tpi-beta-4<br/>Worker"]
end
VIP["kube-vip<br/>192.168.20.140"]
A1 & A2 & A3 --> VIP
| Board | Nodes | Role | Count |
|---|
| Alpha | 1-3 | Control Plane | 3 |
| Alpha | 4 | Worker | 1 |
| Beta | 1-4 | Workers | 4 |
| Component | Specification |
|---|
| Compute | Turing RK1 (RK3588, 8-core ARM) |
| Memory | 32GB per node |
| Storage | NVMe SSD per node |
| Network | 1GbE per node |
| OS | Armbian 25.08 |
| Component | HA Strategy |
|---|
| API Server | kube-vip VIP (192.168.20.140) |
| etcd | Embedded in k3s (3-node quorum) |
| Storage | Longhorn distributed replication |
| Ingress | MetalLB load balancing |
sequenceDiagram
participant Dev as Developer
participant GH as GitHub
participant HCP as HCP Terraform
participant Argo as ArgoCD
participant K8s as Kubernetes
Dev->>GH: Push to feature branch
Dev->>GH: Create PR
GH->>HCP: Trigger speculative plan
HCP-->>GH: Post plan results
Dev->>GH: Merge PR
GH->>HCP: Trigger apply (tf/ changes)
HCP->>K8s: Apply Terraform changes
GH->>Argo: Webhook notification
Argo->>GH: Pull latest manifests
Argo->>K8s: Sync applications
K8s-->>Argo: Report sync status
| Change Type | Automation |
|---|
tf/ changes | HCP Terraform apply |
argocd/ changes | ArgoCD sync |
ansible/ changes | Manual playbook run |
sequenceDiagram
participant Vault as HashiCorp Vault
participant ESO as External Secrets Operator
participant K8s as Kubernetes Secret
participant Pod as Application Pod
Vault->>Vault: Store secret at path
ESO->>Vault: Authenticate via K8s auth
Vault-->>ESO: Return secret data
ESO->>K8s: Create/update Secret
K8s-->>Pod: Mount as env/volume
Note over ESO,K8s: Automatic refresh on TTL
| Category | Vault Path | Example |
|---|
| Cluster services | secret/fzymgc-house/cluster/* | cluster/keycloak |
| Infrastructure | secret/fzymgc-house/infrastructure/* | infrastructure/bmc |
| Applications | secret/fzymgc-house/apps/* | apps/grafana |
sequenceDiagram
participant User
participant App as Application
participant Traefik
participant Keycloak
participant Vault
User->>App: Access protected resource
App->>Traefik: Request forwarded
Traefik->>Keycloak: Forward auth check
Keycloak-->>User: Redirect to login
User->>Keycloak: Authenticate
Keycloak->>Keycloak: Validate credentials
Keycloak-->>User: Issue OIDC token
User->>App: Request with token
App->>Traefik: Forward with auth
Traefik->>Keycloak: Validate token
Keycloak-->>Traefik: Token valid
Traefik-->>App: Allow request
App-->>User: Return resource
Note: For the dashboards this cluster actually forward-auths (Temporal, Hubble UI, agentgateway admin UI, Traefik dashboard, Longhorn), forward-auth is mediated by a shared oauth2-proxy component in front of Keycloak, not Keycloak directly — Keycloak is OIDC-only. See ADR hl-1fia for details.
| Method | Use Case |
|---|
| OIDC | Web applications (Grafana, ArgoCD) |
| Forward Auth | Services without native SSO |
| LDAP | Legacy application support |
| Certificate | Machine-to-machine auth |
graph TB
subgraph External["External"]
CF["Cloudflare Tunnel"]
Internet["Internet"]
end
subgraph Cluster["Cluster Network"]
MetalLB["MetalLB<br/>145-149, 155-159"]
Traefik["Traefik Ingress"]
Cilium["Cilium CNI"]
end
subgraph Pods["Pod Network"]
PodCIDR["10.42.0.0/16"]
end
subgraph Services["Service Network"]
SvcCIDR["10.43.0.0/16"]
end
Internet --> CF --> Traefik
MetalLB --> Traefik
Traefik --> Cilium --> Pods
Pods <--> SvcCIDR
| Network | CIDR | Purpose |
|---|
| Cluster nodes | 192.168.20.0/24 | Physical node network |
| Pod network | 10.42.0.0/16 | Kubernetes pods |
| Service network | 10.43.0.0/16 | Kubernetes services |
| MetalLB pools | 192.168.20.145-149, 192.168.20.155-159 | Load balancer IPs |
| API VIP | 192.168.20.140 | kube-vip HA |
See Network Reference for detailed configuration.
graph TB
subgraph Identity["Identity Layer"]
Keycloak["Keycloak SSO"]
OIDC["OIDC Provider"]
end
subgraph Secrets["Secrets Layer"]
Vault["HashiCorp Vault"]
ESO["External Secrets"]
end
subgraph Network["Network Layer"]
Cilium["Cilium Policies"]
Traefik["TLS Termination"]
end
subgraph Audit["Audit Layer"]
Loki["Loki Logs"]
VM["VictoriaMetrics"]
end
Keycloak --> OIDC --> Services
Vault --> ESO --> Services
Cilium --> Services
Traefik --> Services
Services --> Loki & VM
| Domain | Solution | Purpose |
|---|
| Identity | Keycloak | SSO via OIDC |
| Secrets | Vault + ESO | Dynamic secret injection |
| Network | Cilium | Pod network policies (CiliumNetworkPolicy) |
| Ingress | Traefik | TLS termination, routing |
| Audit | Loki + VM | Centralized logging/metrics |
All cluster state is defined in Git. ArgoCD ensures the cluster matches the repository.
- Single source of truth: Git repository
- Declarative: Desired state, not imperative commands
- Automated sync: Changes apply automatically
- Audit trail: Git history tracks all changes
No secrets stored in Git. All sensitive data lives in Vault.
- Dynamic secrets: Generated on-demand with TTL
- Kubernetes auth: Pods authenticate via service accounts
- Automatic rotation: ESO refreshes secrets automatically
- Audit logging: All access logged in Vault
Single sign-on for all services with consistent identity.
- OIDC standard: Industry-standard authentication
- Forward auth: Protect services without native SSO
- Centralized policies: One place for access control
- MFA support: Additional security when needed
Every service is monitored and logged from deployment.
- Metrics/Logs/Traces: OTel collectors push to ClickHouse (
otel_metrics_*, otel_logs, otel_traces)
- Dashboards: HyperDX (ClickStack) provides visibility
- Alerts: HyperDX TILE alerts notify via Pushover
| Factor | k3s | Full k8s |
|---|
| Resource usage | ~512MB RAM | ~2GB RAM |
| Installation | Single binary | Many components |
| ARM support | Native | Requires tuning |
| Updates | Simple | Complex |
| Trade-off | Less customizable | More flexible |
Decision: Resource efficiency on ARM hardware outweighs customization needs.
| Factor | Vault | Sealed Secrets |
|---|
| Dynamic secrets | Yes | No |
| Rotation | Automatic | Manual re-seal |
| Audit | Built-in | None |
| UI | Yes | No |
| Complexity | Higher | Lower |
Decision: Dynamic secrets and audit logging justify additional complexity.
Keycloak is the cluster’s current identity provider, handling OIDC SSO for web applications, Kubernetes API access, and (via a shared oauth2-proxy middleware) forward-auth for internal dashboards. The cluster originally ran Authentik as its IdP and migrated to Keycloak; see ADR hl-12i8 for the migration rationale.
| Factor | ArgoCD | Flux |
|---|
| UI | Full dashboard | None (Weave add-on) |
| Multi-tenancy | Built-in | Basic |
| App-of-Apps | Native | Requires setup |
| Notifications | Extensive | Basic |
| Learning curve | Lower | Higher |
Decision: UI and multi-tenancy support improve operations.