Skip to content

System Overview

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
LayerLocationToolPurpose
1. Cluster Deploymentansible/AnsibleNode configuration, k3s installation
2. Infrastructure Configtf/TerraformVault, Keycloak, Grafana setup
3. Application Deploymentargocd/ArgoCDGitOps-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.

RolePurpose
k3s-serverControl plane deployment
k3s-agentWorker node deployment
kube-vipAPI server HA with VIP
ciliumCNI networking via Helm (eBPF + Hubble)
longhorn-disksStorage preparation

Configures infrastructure services via HCP Terraform.

ModulePurpose
cluster-bootstrapInitial secrets and RBAC
vaultSecrets management policies
keycloakIdentity provider configuration
grafanaDashboards and datasources
cloudflareDNS records and tunnels
core-servicesShared service configuration
hcp-terraformHCP Terraform workspace management

GitOps-driven application deployment.

PatternUse Case
Helm chartsComplex applications (Grafana, Vault)
KustomizeEnvironment overlays
Raw manifestsSimple 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
BoardNodesRoleCount
Alpha1-3Control Plane3
Alpha4Worker1
Beta1-4Workers4
ComponentSpecification
ComputeTuring RK1 (RK3588, 8-core ARM)
Memory32GB per node
StorageNVMe SSD per node
Network1GbE per node
OSArmbian 25.08
ComponentHA Strategy
API Serverkube-vip VIP (192.168.20.140)
etcdEmbedded in k3s (3-node quorum)
StorageLonghorn distributed replication
IngressMetalLB 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 TypeAutomation
tf/ changesHCP Terraform apply
argocd/ changesArgoCD sync
ansible/ changesManual 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
CategoryVault PathExample
Cluster servicessecret/fzymgc-house/cluster/*cluster/keycloak
Infrastructuresecret/fzymgc-house/infrastructure/*infrastructure/bmc
Applicationssecret/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.

MethodUse Case
OIDCWeb applications (Grafana, ArgoCD)
Forward AuthServices without native SSO
LDAPLegacy application support
CertificateMachine-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
NetworkCIDRPurpose
Cluster nodes192.168.20.0/24Physical node network
Pod network10.42.0.0/16Kubernetes pods
Service network10.43.0.0/16Kubernetes services
MetalLB pools192.168.20.145-149, 192.168.20.155-159Load balancer IPs
API VIP192.168.20.140kube-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
DomainSolutionPurpose
IdentityKeycloakSSO via OIDC
SecretsVault + ESODynamic secret injection
NetworkCiliumPod network policies (CiliumNetworkPolicy)
IngressTraefikTLS termination, routing
AuditLoki + VMCentralized 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
Factork3sFull k8s
Resource usage~512MB RAM~2GB RAM
InstallationSingle binaryMany components
ARM supportNativeRequires tuning
UpdatesSimpleComplex
Trade-offLess customizableMore flexible

Decision: Resource efficiency on ARM hardware outweighs customization needs.

FactorVaultSealed Secrets
Dynamic secretsYesNo
RotationAutomaticManual re-seal
AuditBuilt-inNone
UIYesNo
ComplexityHigherLower

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.

FactorArgoCDFlux
UIFull dashboardNone (Weave add-on)
Multi-tenancyBuilt-inBasic
App-of-AppsNativeRequires setup
NotificationsExtensiveBasic
Learning curveLowerHigher

Decision: UI and multi-tenancy support improve operations.

TopicLink
Network detailsNetwork Reference
Secrets managementSecrets Reference
Services catalogServices Reference
Technology stackTechnologies Reference