3n.nl / haven-lab / referentie
Dit is de referentie bij het Haven+ thuislab: een complete Haven-omgeving op één gaming-pc. Deel 1 loopt de plaat van onder naar boven door en legt elk vakje uit. Deel 2 is de catalogus: elk component, zijn rol, de open standaard waarop het leunt en waar het in git staat. Deel 3 zet vier architecturen naast elkaar — van deze ene pc tot meerdere infraproviders met failover — en laat zien wat er dan wél en níet verandert. De rode draad: desired state. Alles komt uit git, en dat is precies wat schalen en verhuizen mogelijk maakt zonder herbouw.
de plaat
Vier lagen op elkaar, en rechts de strook "buiten de pc". De architectuurplaat op de hoofdpagina toont hetzelfde systeem schematisch; deze versie noemt de componenten bij naam en versie zoals ze nu draaien.
leeswijzer
De plaat is een stapel van vier lagen, met rechts een strook "buiten de pc". Elke laag levert iets aan de laag erboven en weet niets van de laag eronder: de toepassingen kennen alleen Kubernetes, Kubernetes kent alleen zes machines, en de host weet niet wat er in de VM's draait. Die onwetendheid is het hele punt — het is wat je straks laat verhuizen zonder dat de bovenliggende lagen het merken.
Twee pijlen rechts zijn belangrijker dan alle vakjes samen. Flux pull elke 5 min en autoUpgrade elke 10 min: beide lagen halen hun gewenste toestand zelf uit GitHub. Er is geen beheerder die iets "op de machine zet"; er is een repo, en machines die zich daarnaar vormen. Dat heet desired state, en het is de reden dat dit lab op een leeg cluster in een paar uur opnieuw op te bouwen is — en dat de schaalpaden in deel 3 geen herbouw zijn maar een extra map in git.
Kleuren: grijs is de host, groenblauw het cluster, blauw het platform, paars de toepassingen, oker alles wat van buiten komt.
laag 0
Eén fysieke machine: een gaming-pc (i7-14700KF, 32 GB, binnenkort 64) die van een externe Samsung T7 boot. Het besturingssysteem is NixOS: de hele machine — pakketten, services, firewall, gebruikers — staat als één declaratieve configuratie in git (hosts/havenpc/). Windows op de interne NVMe blijft onaangeraakt; de pc is 's avonds nog gewoon een spelcomputer.
De virtualisatielaag. Incus draait zes KVM-virtuele machines en geeft ze een eigen netwerk (havenbr0, 10.42.0.0/24) met NAT naar buiten.
virtualisation.incus.preseed): profielen, netwerk en opslagpool staan in git.hosts/havenpc/incus.nix; VM's zelf via scripts/haven-vms.shGedeelde opslag voor het cluster. De host exporteert een map; in het cluster wordt die via csi-driver-nfs een StorageClass die ReadWriteMany-volumes kan uitdelen.
fsid=0 hebben (dan is de map de pseudoroot en mount de driver niets). In een echte omgeving vervang je dit door NFS van de provider, Ceph/Longhorn, of objectopslag.hosts/havenpc/services/nfs.nixTijd, firewall en de uitrol-lus van de host.
git pull + nixos-rebuild switch met een read-only deploy key. Een push naar main is de uitrol; een slechte commit rol je terug door te reverten. Dit is de eerste van de twee lussen.De enige toegang. De host adverteert het VM-netwerk in het tailnet, zodat je laptop en telefoon *.haven.3n.nl (→ 10.42.0.200) kunnen bereiken zonder dat er iets op internet open staat.
Praktische randzaken. De NUC in de meterkast kan de pc wakker maken; de interne schijf is nooit aangeraakt, dus de pc blijft ook een gewone pc.
laag 1
Zes VM's draaien Talos Linux 1.13 en vormen samen Kubernetes v1.36. Talos is een besturingssysteem dat alleen Kubernetes kan: geen shell, geen ssh, geen pakketbeheer — alleen een API (talosctl). Alles wat je erop wilt veranderen gaat via een machineconfiguratie die in git staat (haven/talos/, gegenereerd door talhelper). Dat is de reden dat de Haven-check "node hardening" groen is.
De control plane: de hersenen van het cluster. Elk draait de API-server, scheduler, controller-manager en een lid van etcd, de database waarin de hele clusterstaat ligt.
kubectl praat daartegen, niet tegen een specifieke machine.De spieren. Alle platformdiensten en toepassingen draaien hier als pods; de control plane draait zelf niets van de workload.
topology.kubernetes.io/zone. Het cluster ziet drie beschikbaarheidszones en spreidt replica's eroverheen. Op één pc is dat een afspraak; in de schaalpaden wordt het een echte kast, een ander datacenter of een andere provider — zonder dat het cluster anders geconfigureerd hoeft te worden.Wat een cloudprovider normaal "gratis" meelevert en een kaal cluster mist.
kubectl top en autoscaling. Haven-check.infrastructure/havenpc-base/): ook de basislaag is desired state.Talos zet standaard Pod Security Admission op baseline voor alle namespaces. Componenten die privileged containers nodig hebben (Istio's CNI en ztunnel, MetalLB's speaker) starten dan niet. De namespaces istio-system en metallb-system staan daarom expliciet op privileged.
Ook geleerd: Talos-systeemextensions in een Incus-VM zijn riskant. De qemu-guest-agent-extension startte nooit, Talos zag de boot als mislukt en herstartte elke 71 minuten. Het image is nu leeg (geen extensions).
laag 2
Haven zegt hoe het cluster eruit moet zien; Haven+ is de VNG-referentie voor wat er standaard op het cluster hoort: toegang, identiteit, observability, beleid, data en beheer — met OpenID Connect, OpenTelemetry en de Gateway API als gemeenschappelijke taal. Het lab draait een fork van VNG's gitops-flux (rick3n/haven-gitops), met een eigen overlay per cluster (clusters/havenpc/). Flux leest die repo en maakt het cluster er gelijk aan.
De voordeur. Eén gateway op 10.42.0.200 beëindigt TLS en routeert op hostnaam (HTTPRoute) naar de juiste dienst. Ambient-modus betekent: geen sidecar per pod, maar een node-agent (ztunnel) die verkeer tussen pods versleutelt (mTLS) zonder dat toepassingen het merken.
infrastructure/istio*/overlays/havenpc; het LB-adres via metallb.io/loadBalancerIPs.Automatische certificaten. In het lab tekent een eigen CA een wildcard voor *.haven.3n.nl; de root is op de hx90 en de telefoon geïmporteerd. Bij een publiek domein wisselt dezelfde component naar Let's Encrypt of een PKIoverheid-issuer.
*.svc), de buitenkant over https.Eén identiteit voor alles met een browser. Open Zaak, Open Notificaties, Grafana en de werkplek zijn OIDC-clients van realm havenplus; groepslidmaatschap (groups-claim) bepaalt de rol in elke toepassing. De realm zelf — groepen, gebruikers, clients — komt uit git (realm-import), de wachtwoorden en client-secrets uit SOPS.
kubectl) — geheugen.Eén logstroom, één metricsstroom, één tracesstroom. Alloy draait op elke node en verzamelt logs, metrics en OpenTelemetry-traces van alle pods; Loki bewaart logs, Mimir metrics, Tempo traces; Grafana toont ze — met Keycloak-login. Garage is de S3-objectopslag waar Loki en Mimir hun data in zetten.
Beleid als code: admission-regels die bijvoorbeeld privileged containers of ontbrekende resource-limieten tegenhouden (Pod Security Standards "restricted"). In het lab draait alleen de admission-controller, met failurePolicy: Ignore — een onbereikbare webhook mag Flux en MetalLB niet gijzelen. Na de geheugenuitbreiding komen background-, reports- en cleanup-controller terug.
CloudNative-PG is de PostgreSQL-operator: elke toepassing krijgt een eigen Cluster-object (Open Zaak op PostGIS, Keycloak, Grafana), met automatische failover tussen replica's en back-up naar S3 als je dat aanzet. Dit is ook de component die in deel 3 de data over locaties repliceert.
Sealed-secrets zit in de Haven+-referentie; het lab gebruikt SOPS/age (secrets versleuteld in de repo, sleutel alleen op havenpc). Sealed-secrets draait mee maar wordt niet gebruikt.
Flux is de tweede lus: elke vijf minuten haalt het rick3n/haven-gitops op, ontsleutelt secrets met de age-sleutel en past alles toe — inclusief zichzelf. Headlamp is de cluster-UI (met Flux-plugin). De Haven CLI doet de compliance-check: haven check = 15/15.
dependsOn: cluster-resources → kyverno → cert-manager → istio → … → tenants. Zo komt een leeg cluster in de goede volgorde op.clusters/havenpc/infrastructure/kustomization.yaml.laag 3
Toepassingen zijn tenants: een namespace met een Flux-Kustomization, eigen database, eigen secrets, eigen route op de gateway. Ze gebruiken het platform (identiteit, database-operator, S3, gateway) en kennen elkaar alleen via de standaard-API's.
Het zaakregister: de referentie-implementatie van de ZGW-API's (Zaken, Documenten, Catalogi, Besluiten, Autorisaties). Alles wat de demo laat zien — zaaktypen, vertrouwelijkheid, autorisaties per API-client, de 403 — is gedrag van deze API's, niet van een UI.
tenants/openzaak/); het eindigt met een test die faalt als de scheiding niet klopt.De gebeurtenissenbus van Common Ground: toepassingen abonneren zich op kanalen en krijgen een webhook als er een zaak, status of document bijkomt. Open Zaak publiceert erop; later luistert bijvoorbeeld een loket of een archiefcomponent mee.
Een kleine rollen-UI: inloggen via Keycloak, en de groep bepaalt met welke ZGW-client de werkplek praat. Voegt geen autorisatie toe — wat Open Zaak weigert, laat de werkplek als 403 zien. Dit is bewust geen zaakafhandelcomponent; het bestaat om de API-scheiding zichtbaar te maken.
Open Formulieren als echt loket (stap 1 van de demo is nu gescript), Objecten API voor "onderneming" als object, MijnBureau (de BZK-werkplek) na 64 GB. Elk ervan is weer een map onder tenants/.
buiten de pc
Alle gebruikersverkeer komt via Tailscale binnen op de Istio-gateway. DNS voor *.haven.3n.nl wijst naar het LB-adres; de NUC is de DNS van het tailnet.
Twee repo's, twee lussen. nixos_eigen_hardware1 is de host (en de Talos-configuratie); haven-gitops is alles in het cluster. Beide worden gelezen, nooit geschreven, door de machines. Secrets staan erin, versleuteld; de sleutel staat niet in git.
flux install, tailscale up, een DNS-record. Daarna is alles een push.catalogus
Alles op de plaat, met rol, de open standaard of het protocol waarop het leunt (dat is wat het inwisselbaar maakt), waar het in git staat en de status in het lab.
| Component | Laag | Rol | Standaard / protocol | In git | Lab |
|---|---|---|---|---|---|
| NixOS | 0 | Declaratief besturingssysteem van de host | Nix-flake | hosts/havenpc/ | aan |
| Incus | 0 | KVM-virtualisatie, netwerk en opslagpool voor zes VM's | Incus REST-API | hosts/havenpc/incus.nix | aan |
| NFS-server | 0 | RWX-opslag voor het cluster | NFSv4.1 | hosts/havenpc/services/nfs.nix | aan |
| Tailscale | 0 | Enige toegangspad; subnet-router voor 10.42.0.0/24 | WireGuard | hosts/havenpc/ + ACL in Tailscale | aan |
| chrony, nftables, autoUpgrade | 0 | Tijd, firewall, uitrol-lus van de host | NTP; nftables; git | hosts/havenpc/ | aan |
| Talos Linux | 1 | Onveranderlijk node-OS, alleen API | Talos machineconfig | haven/talos/ (talhelper) | 1.13.10 |
| Kubernetes | 1 | Containerorkestratie; 3 cp + 3 workers, 3 zones | CNCF-conformant (Talos v1.36 in het register) | idem | v1.36.2 |
| etcd | 1 | Clusterstaat, quorum over drie control-plane-nodes | Raft | door Talos | aan |
| Flannel | 1 | Pod-netwerk (CNI) | CNI, VXLAN | Talos-standaard | aan |
| MetalLB | 1 | LoadBalancer-adressen (L2) | Kubernetes Service type LoadBalancer | infrastructure/havenpc-base/metallb | aan, frr-k8s uit |
| csi-driver-nfs | 1 | StorageClass "nfs", RWX | CSI | infrastructure/havenpc-base/csi-driver-nfs | aan |
| metrics-server | 1 | Resource-metrics per pod/node | Kubernetes metrics API | infrastructure/havenpc-base/metrics-server | aan |
| Istio (ambient) | 2 | Gateway, routering, mTLS tussen pods | Gateway API, mTLS | infrastructure/istio*/overlays/havenpc | aan |
| cert-manager | 2 | Certificaten, eigen CA haven-ca | ACME / X.509 | infrastructure/cert-manager/config/overlays/havenpc | aan |
| Keycloak (+ operator) | 2 | Identiteit, SSO, groepen → rollen | OpenID Connect, SAML | infrastructure/keycloak/instances/overlays/havenpc | aan |
| Alloy | 2 | Verzamelt logs, metrics, traces op elke node | OpenTelemetry, Prometheus | infrastructure/alloy/overlays/havenpc | aan |
| Loki / Mimir / Tempo | 2 | Opslag van logs / metrics / traces | LogQL, PromQL, OTLP | infrastructure/{loki,mimir,tempo}/… | aan |
| Grafana (+ operator) | 2 | Dashboards, SSO | OIDC | infrastructure/grafana/config/overlays/havenpc | aan |
| Garage | 2 | S3-objectopslag voor Loki/Mimir | S3-API | infrastructure/garage/config/overlays/havenpc | aan |
| Kyverno | 2 | Beleid als code (PSS) | Kubernetes admission | infrastructure/kyverno/*/overlays/havenpc | alleen admission |
| CloudNative-PG | 2 | PostgreSQL-operator, failover, back-up, replicatie | PostgreSQL streaming replication | infrastructure/cloudnative-pg/overlays/havenpc | aan |
| sealed-secrets | 2 | Secrets in git (referentie) | — | infrastructure/sealed-secrets/… | draait, ongebruikt (SOPS) |
| Flux | 2 | GitOps-lus van het cluster, SOPS-decryptie | git, Kustomize, Helm, SOPS/age | clusters/havenpc/ | 2.8.8, zelfbeheerd |
| Headlamp | 2 | Cluster-UI met Flux-plugin | Kubernetes API | infrastructure/headlamp/overlays/havenpc | aan |
| Haven CLI | 2 | Compliance-check (15 checks) | — | ~/bin/haven op havenpc | 15/15 |
| Pinniped, OpenBao, External Secrets, Velero, Falco, Trivy, ECK, OpenCost | 2 | kubectl-OIDC, kluis, secrets-sync, back-up, detectie, scans, Elastic, kosten | OIDC, KMS, S3, … | in de referentie; uitgezet in clusters/havenpc/infrastructure/kustomization.yaml | uit (geheugen) |
| Open Zaak | 3 | Zaakregister: ZGW-API's | ZGW-API's (VNG), OIDC | tenants/openzaak/ | 1.30 |
| Open Notificaties | 3 | Gebeurtenissenbus | Notificaties API (VNG) | tenants/openzaak/ | 1.16 |
| Werkplek | 3 | Rollen-UI op de ZGW-API's | OIDC, ZGW-JWT | tenants/openzaak/werkplek/ | demo |
| Open Formulieren, Objecten API, MijnBureau | 3 | Loket, objecten, werkplek-suite | ZGW, Objecten API | — | na 64 GB |
schaalpaden
De vraag die het lab moet beantwoorden is niet "kan het op één pc", maar "wat verandert er als het niet meer op één pc hoeft". Het antwoord is: minder dan je denkt, en precies de dingen die je wilt kunnen veranderen. Hieronder vier stadia. In elk stadium zijn laag 2 en 3 — het platform en de toepassingen — dezelfde git-mappen. Wat verandert is laag 0 en 1: wie de machines levert, en hoeveel clusters er zijn.
clusters/<naam>/ verschilt: LB-adres, StorageClass, S3-endpoint, DNS-naam. Data loopt als replica naar de tweede locatie. Bij uitval wordt B actief: DNS om, replica promoveren.| 1 · Eén pc | 2 · Eigen cluster | 3 · Gesplitst | 4 · Multi-cloud | |
|---|---|---|---|---|
| Laag 0 (host) | NixOS + Incus op één pc | NixOS/Talos op 3+ machines | + VM's of managed K8s bij een EU-provider | + managed K8s bij een hyperscaler, NL-regio |
| Laag 1 (cluster) | 6 VM's, zones = labels | zones = machines/kasten; Ceph/Longhorn | twee clusters, elk 15/15 | drie of meer clusters, elk 15/15 |
| Laag 2 (Haven+) | dezelfde infrastructure/-mappen; per cluster alleen een overlay met adres, StorageClass, S3-endpoint | |||
| Laag 3 (toepassingen) | dezelfde tenants/-mappen; per cluster alleen wie actief is en waar de database-replica staat | |||
| Opslag | NFS op de host | gerepliceerd over machines | gerepliceerd over locaties (CNPG, S3) | gerepliceerd; back-up bij een derde partij of eigen kluis |
| Toegang | Tailscale | Tailscale of eigen WAN | DNS wijst naar de actieve; VPN tussen locaties | GSLB/anycast of DNS-failover; provider-peering |
| Wat mag uitvallen | niets | één machine | één locatie | één provider |
| Soevereiniteit | volledig | volledig | NL/EU; eigen sleutels | eigen sleutels, identiteit, git en back-up; hyperscaler zonder data in rust |
| Wat erbij komt in git | — | hosts/*, talconfig | clusters/eu/ | clusters/<provider>/ per cluster |
| Wat je ervoor moet oefenen | herbouw op een leeg cluster | één machine uitzetten | DNS omzetten, replica promoveren | een hele provider "verliezen" en terugkomen |
failover
Neem stadium 3 of 4 en laat een locatie wegvallen — stroom, netwerk, een provider die een regio sluit, een juridische reden om ergens weg te moeten. Wat er dan gebeurt, in de volgorde waarin het moet:
Wat dit vraagt is geen bijzondere techniek maar oefening: een failover die je nooit hebt uitgevoerd, bestaat niet. Daarom staat in de tabel per stadium wat je moet oefenen. In het lab is dat nu: herbouw op een leeg cluster — en dat is al gedaan.
soevereiniteit
"Soeverein" is geen locatie maar een lijst van dingen waarvan je zelf bepaalt wie ze heeft. In dit ontwerp:
haven
Wel: dat een Haven-compliant cluster de eigenschappen heeft die de vijftien checks toetsen — drie zones, drie control-plane-nodes, drie workers, hardening, privaat netwerk, actuele Kubernetes, RBAC, RWX-opslag, CNCF-conformance, automatische certificaten, logaggregatie, metrics. Een toepassing die op het ene Haven-cluster draait, draait op het andere.
Niet: dat de clusters elkaar kennen. Replicatie van data, verkeerssturing tussen locaties, identiteitsfederatie en back-up buiten de provider zijn jouw ontwerp bovenop Haven — en precies de dingen die in dit document boven de clusters getekend staan. Haven maakt ze mogelijk door de clusters gelijk te maken; het bouwt ze niet voor je.
Ook niet: kosten of prestaties. Een Haven-cluster bij een hyperscaler is duurder dan dezelfde capaciteit op eigen ijzer, en een Haven-cluster op één gaming-pc is goedkoper dan alles — maar valt om als de stroom uitvalt. De architectuur laat je kiezen; de keuze blijft van jou.