Van je eerste login tot een eigen cluster met opslag en je eerste workload. Doe stap 1 en 2 vóór het fieldlab, dan kan je team op 23 november meteen bouwen.

Inleiding

Fundament is een cloudplatform voor de overheid, gebouwd uit open source. Het richt fysieke servers automatisch in en maakt er Kubernetes-clusters van, die je zelf aanmaakt en uitbreidt. Extra diensten, zoals opslag en databases, voeg je per cluster toe als plugin. Voor code, container-images en CI/CD stellen we Forgejo ter beschikking.

Een paar begrippen die je op deze pagina tegenkomt:

Organisatie
Je team of overheidsorganisatie. Alles wat je aanmaakt hoort bij een organisatie, en de quota van je organisatie bepalen hoeveel je mag aanmaken.
Cluster en node pool
Een Kubernetes-cluster op eigen servers. Een node pool is een groep servers van één type in dat cluster.
Project en namespace
Een project hoort bij één cluster en regelt wie waar bij mag. Je workloads draaien in de namespaces van een project.
Plugin
Een dienst die je op een cluster installeert, zoals opslag met Ceph of databases met CloudNativePG. Je kunt ook zelf een plugin bouwen.

Meer achtergrond staat in Organisaties en projecten in de Fundament-documentatie.

1. Je laptop klaarmaken

Doe dit vóór het fieldlab. Je werkt drie dagen op je eigen laptop, en veel organisaties beperken wat je daarop mag installeren en welke websites je kunt bereiken. Lukt een van deze stappen niet, regel het dan met je IT-afdeling of neem een andere laptop mee.

Websites bereiken

Open deze websites in je browser:

functl en kubectl installeren

Met kubectl werk je in je cluster. functl is de command-line tool van Fundament: je kubeconfig roept hem bij elk verzoek aan om je aan te melden. Daarom moet functl op je PATH staan, ook als je zelf alleen kubectl gebruikt.

Voer dit uit in Terminal. Heb je een Mac met een Intel-processor, vervang dan arm64 twee keer door amd64.

curl -fsSL https://github.com/fundament-oss/fundament/releases/download/functl-latest/functl_darwin_arm64.tar.gz | tar -xzf - functl curl -fsSLO "https://dl.k8s.io/release/$(curl -fsSL https://dl.k8s.io/release/stable-1.36.txt)/bin/darwin/arm64/kubectl" chmod +x kubectl sudo mkdir -p /usr/local/bin sudo mv functl kubectl /usr/local/bin/

Controleer de installatie:

functl version kubectl version --client

Je cluster draait Kubernetes 1.36, en kubectl mag daar hooguit één versie van afwijken. Had je kubectl al, controleer dan of hij 1.35, 1.36 of 1.37 is; een oudere vervang je met de commando's hierboven.

Installeer op de eerste dag van het fieldlab opnieuw, met dezelfde commando's. Fundament verandert nog snel, en zo werk je met de nieuwste versie van functl.

functl version en kubectl version --client allebei een versie tonen.

2. Inloggen op de console

Ook dit doe je vóór het fieldlab. Na je eerste login bestaat je account, en kan een teamgenoot je uitnodigen in jullie organisatie.

  1. Maak een Pleio-account aan op account.pleio.nl, als je er nog geen hebt.
  2. Ga naar console.fundament-poc.nl en klik op Log in with Pleio.
  3. Log in met je Pleio-account.

Zit je nog niet in een organisatie, dan zie je You are not in an organization yet. Dat klopt: in een organisatie kom je later.

  • Uitgenodigd? Klik op Check again en daarna bij de organisatie op Accept.
  • Zit je in meer organisaties? Wissel rechtsboven, naast je avatar.

In je organisatie kom je op de pagina Clusters. De console is in het Engels; deze pagina noemt de knoppen zoals ze daar heten.

Teamgenoten uitnodigen

Je teamgenoot moet al een keer zijn ingelogd, anders werkt de uitnodiging niet.

  1. Ga naar Members en klik op Invite member….
  2. Vul het e-mailadres in waarmee je teamgenoot inlogt.
  3. Kies een rol: admin mag alles in de organisatie, viewer alleen kijken, tot je hem aan een project toevoegt.
  4. Stuur je teamgenoot het bericht dat de console je geeft. Er gaat geen e-mail vanzelf weg.

je bent ingelogd op de console.

3. Een cluster aanmaken

Hier zie je de kern van Fundament. Als je een cluster aanmaakt, zoekt Fundament vrije servers, installeert ze en bouwt er een Kubernetes-cluster van. Je kiest welk type server je wilt en tussen hoeveel servers het cluster mag schalen; de autoscaler voegt servers toe als je workloads meer ruimte nodig hebben.

  1. Ga naar Clusters en klik op New cluster….
  2. Name your cluster and pick a region: kies een naam. Regio en Kubernetes-versie laat je staan.
  3. Configure your cluster node pools: kies het kleinste machinetype en zet Autoscale minimum en Autoscale maximum op 1. Voor het fieldlab is één server genoeg.
  4. Controleer de samenvatting en klik op Create cluster.

Je cluster staat eerst op Provisioning. Na ongeveer tien minuten is het Running. Tussendoor kan de status een paar minuten Unhealthy zijn terwijl de laatste onderdelen opstarten.

Welke machinetypen je kunt kiezen en hoeveel servers je mag gebruiken, hangt af van de quota van je organisatie (Quotas in het menu) en van wat er vrij is. Een node pool telt mee met zijn maximum, ook als dat nooit bereikt wordt. Of er genoeg vrije servers zijn, zie je pas na Create cluster, aan de status van je node pool.

Beheer je je omgeving liever als code? Clusters, projecten en namespaces kun je ook met OpenTofu of Terraform aanmaken, met de Fundament Terraform-provider. Zet in het provider-blok naast endpoint ook authn_endpoint = "https://authn.fundament-poc.nl", anders kan de provider niet inloggen.

je cluster in de console op Running staat.

4. Toegang tot je cluster

Je meldt functl aan met een API-key. Daarna haal je de kubeconfig op waarmee kubectl je cluster bereikt.

Inloggen met een API-key

  1. Klik in de console rechtsboven op je avatar en dan op API keys.
  2. Klik op New API key… en vul een Name in.
  3. Kies bij Expires after hoe lang de key geldig blijft, bijvoorbeeld 30 dagen, en klik op Create API key.
  4. Kopieer de key meteen: je ziet hem maar één keer.
  5. Log in met de key en zoek het ID van je organisatie op: functl auth login functl org list
  6. Kies je organisatie. Vervang <org-id> door het ID van je organisatie: functl org set <org-id>

Kubeconfig ophalen

  1. Zoek het ID van je cluster op en maak een map voor de kubeconfig: functl cluster list mkdir -p ~/.kube
  2. Sla de kubeconfig op. Vervang <cluster-id> door het ID van je cluster: functl cluster kubeconfig <cluster-id> > ~/.kube/fundament.yaml
  3. Gebruik de kubeconfig en probeer hem uit: export KUBECONFIG=~/.kube/fundament.yaml kubectl get ns

In de console kan het ook: Download kubeconfig op de pagina van je cluster. KUBECONFIG geldt alleen voor dit terminalvenster. Heb je nog geen ~/.kube/config, dan kun je de kubeconfig ook onder die naam opslaan.

Werk je liever met een tool als k9s? Die gebruikt dezelfde kubeconfig, zolang functl op je PATH staat.

kubectl get ns de namespaces van je cluster toont.

5. Plugins: opslag, internet en databases

Een nieuw cluster kan nog weinig: geen opslag die blijft bestaan, geen adres op internet, geen databases. Die voeg je toe met plugins. Elke plugin installeer je op dezelfde manier:

  1. Klik in het menu links op Plugins en dan op Browse plugins. De marketplace opent.
  2. Kies een plugin en klik op Install plugin.
  3. Klik bij je cluster op Install en kies de bovenste versie.
  4. Vraagt de plugin om instellingen, vul ze in en klik op Install.

Na een paar minuten draait de plugin en verschijnen zijn schermen in de console onder je project. Installeer voor het voorbeeld in stap 6 deze drie: Ceph, Envoy Gateway en CloudNativePG.

Ceph Storage (Rook): opslag

Ceph maakt van de lege schijven in je servers opslag voor je workloads. Op een cluster met één server vul je bij het installeren in:

  • Monitor count: 1
  • Manager count: 1
  • Allow multiple daemons per node: aangevinkt

Met drie of meer servers laat je de standaardwaarden staan. Na het installeren staat Ceph de eerste minuten op Degraded; dat hoort zo.

Maak daarna onder je project, bij Ceph Storage (Rook), de opslag aan. Deze schermen worden nog vernieuwd, dus de knoppen kunnen anders heten.

  1. Disk Pools: maak één disk pool met alle schijven die je ziet. Daaruit putten alle soorten opslag.
  2. Block Storages: maak block storage aan en vink Default StorageClass aan. Elk volume zonder storageClassName komt hierop terecht.

Is het formulier leeg, zonder velden of knop? Dat is een bekende fout. Maak de opslag dan met kubectl:

  1. Zoek de naam van je schijf op: kubectl get disks.ceph.fundament.io
  2. Sla dit op als ceph.yaml. Vervang <disk-name> door de naam uit de eerste kolom van een schijf waarbij AVAILABLE op true staat. Heb je er meer, zet ze dan allemaal onder disks: apiVersion: ceph.fundament.io/v1alpha1 kind: DiskPool metadata: name: pool spec: disks: - name: <disk-name> --- apiVersion: ceph.fundament.io/v1alpha1 kind: BlockStorage metadata: name: block spec: default: true
  3. Maak de opslag aan en wacht tot beide op Ready staan: kubectl apply -f ceph.yaml kubectl get diskpools.ceph.fundament.io,blockstorages.ceph.fundament.io

Ceph kan nog twee soorten opslag maken, als je die nodig hebt:

  • File Storages: een map die veel pods tegelijk gebruiken. Je krijgt de StorageClass cephfs-<naam>; vraag een volume aan met accessModes: [ReadWriteMany].
  • Object Storages: S3-buckets. Op de pagina van je object storage maak je met Create bucket een bucket in je namespace. Adres en inloggegevens staan dan in een ConfigMap en een Secret met de naam van de bucket; geef ze aan je container mee met envFrom. Het adres werkt alleen binnen je cluster.

Gebruik de StorageClass csi-lvm niet: daarmee blijven volumes hangen op Pending.

Gateway API (Envoy Gateway): bereikbaar vanaf internet

Met deze plugin krijgt je cluster een Gateway: één publiek adres waarachter je al je webapplicaties zet. Hij vraagt geen instellingen. Hoe je hem gebruikt, staat in het voorbeeld van stap 6.

CloudNativePG: databases

CloudNativePG maakt PostgreSQL-databases voor je, met één exemplaar en zonder back-ups. Een database hoort bij een namespace; maak die eerst, zoals in stap 6 bij punt 1 en 2.

  1. Ga in je project naar Databases.
  2. Klik op New database….
  3. Kies een naam en de namespace van je app, en klik op Create database.
  4. Wacht ongeveer een minuut. De pagina van je database toont hoe je verbinding maakt.

Is het formulier leeg, zonder velden of knop? Maak de database dan zo:

  1. Sla dit op als db.yaml: apiVersion: postgresql.cnpg.io/v1 kind: Cluster metadata: name: db spec: instances: 1 storage: size: 1Gi
  2. Maak de database aan en wacht tot hij Cluster in healthy state is. Vervang <namespace> door de namespace van je app, bijvoorbeeld tnt-demo--app: kubectl apply -n <namespace> -f db.yaml kubectl get cluster.postgresql.cnpg.io -n <namespace>

Het wachtwoord en een complete verbindings-URL (uri) staan in de Secret <naam>-app.

Meer plugins

  • cert-manager maakt en vernieuwt certificaten voor HTTPS. Hoe je met Let's Encrypt een certificaat voor je Gateway haalt, staat in stap 6.
  • Haven+ zet het Haven+-platform op je cluster en brengt zijn eigen certificaten, databases, gateway en monitoring mee. Installeer ernaast alleen Ceph, niet de andere plugins hierboven. Je hebt er een eigen domein voor nodig. Voor vragen is op het fieldlab een eigen team aanwezig.

Ceph, Envoy Gateway en CloudNativePG onder Plugins staan, en je block storage op Ready staat.

6. Je eerste app

Dit voorbeeld brengt alles samen: een webserver met een eigen volume, bereikbaar vanaf internet. Bij elke start schrijft hij de tijd in zijn pagina. Omdat het volume blijft bestaan, groeit die lijst.

  1. Klik links onder Projects op New project…, kies een naam en je cluster, en klik op Create project.
  2. Ga in je project naar Namespaces, klik op New namespace…, kies een naam en klik op Create namespace.
  3. Sla dit op als hello.yaml: apiVersion: v1 kind: PersistentVolumeClaim metadata: name: hello-data spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi --- apiVersion: apps/v1 kind: Deployment metadata: name: hello spec: replicas: 1 strategy: type: Recreate selector: matchLabels: app: hello template: metadata: labels: app: hello spec: initContainers: - name: init image: busybox:1.38 command: ["sh", "-c", "date >> /data/index.html"] volumeMounts: - name: data mountPath: /data containers: - name: web image: nginxinc/nginx-unprivileged:1.30-alpine ports: - containerPort: 8080 volumeMounts: - name: data mountPath: /usr/share/nginx/html volumes: - name: data persistentVolumeClaim: claimName: hello-data --- apiVersion: v1 kind: Service metadata: name: hello spec: selector: app: hello ports: - port: 80 targetPort: 8080 --- apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: web spec: gatewayClassName: eg listeners: - name: http protocol: HTTP port: 80 --- apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: hello spec: parentRefs: - name: web rules: - backendRefs: - name: hello port: 80
  4. Stel je namespace in als standaard, zodat je hem maar één keer invult. Vervang <namespace> door de volledige naam van je namespace, bijvoorbeeld tnt-demo--app voor namespace app in project demo: kubectl config set-context --current --namespace=<namespace>
  5. Zet de app neer en vraag het publieke adres op: kubectl apply -f hello.yaml kubectl rollout status deploy/hello kubectl get gateway web
  6. Open http://<ADDRESS> in je browser.
  7. Herstart de app en herlaad de pagina; er staat een tijd bij: kubectl rollout restart deploy/hello

Maak namespaces in de console, niet met kubectl create namespace. Fundament neemt een namespace die het niet zelf heeft gemaakt nooit over: hij verschijnt niet in de console en je projectleden krijgen er geen rechten op. Heb je er al een, maak dan in de console een nieuwe en zet je app daar opnieuw neer.

Wil je een eigen domeinnaam, zet dan het adres van je Gateway als A-record in je DNS. Het adres blijft van jou zolang de Gateway bestaat. Verwijder je de Gateway of je cluster, dan krijg je misschien een ander adres en pas je je DNS aan. Meer hierover staat in Service IP addresses in de Fundament-documentatie.

Wat elk onderdeel doet:

  • PersistentVolumeClaim: een volume op je block storage. strategy: Recreate stopt de oude pod voordat de nieuwe start, want een volume hangt aan één pod tegelijk.
  • Deployment en Service: de webserver zelf.
  • Gateway: krijgt een publiek adres. Eén per cluster is genoeg.
  • HTTPRoute: stuurt verkeer van de Gateway naar je app. Voor een tweede app maak je een tweede route naar dezelfde Gateway.

HTTPS met Let's Encrypt

Heb je cert-manager geïnstalleerd (stap 5) en wijst een domeinnaam naar het adres van je Gateway, dan haalt cert-manager zelf een certificaat op. Sla dit op als tls.yaml:

apiVersion: cert-manager.io/v1 kind: Issuer metadata: name: letsencrypt spec: acme: server: https://acme-v02.api.letsencrypt.org/directory privateKeySecretRef: name: letsencrypt-account solvers: - http01: gatewayHTTPRoute: parentRefs: - name: web --- apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: web annotations: cert-manager.io/issuer: letsencrypt spec: gatewayClassName: eg listeners: - name: http protocol: HTTP port: 80 - name: https protocol: HTTPS port: 443 hostname: <hostname> tls: certificateRefs: - name: web-tls

Vervang <hostname> door je domeinnaam. Om het zonder eigen domein te proberen, kun je <adres-met-streepjes>.sslip.io gebruiken, bijvoorbeeld 194-135-48-131.sslip.io; Let's Encrypt geeft daarvoor maar een beperkt aantal certificaten per week uit, voor alle gebruikers samen. Zet het daarna neer en wacht tot het certificaat verschijnt en READY op True staat; stop dan met Ctrl+C:

kubectl apply -f tls.yaml kubectl get certificate -w

Je app is dan bereikbaar op https://<hostname>. De Gateway in tls.yaml vervangt die uit hello.yaml; hij houdt hetzelfde adres. Zet je hello.yaml daarna opnieuw neer, dan verdwijnt HTTPS weer; haal de Gateway dan eerst uit hello.yaml.

http://<ADDRESS> na een herstart twee tijden toont.

7. Een eigen plugin

Je kunt Fundament uitbreiden met een eigen plugin. Hiervoor heb je naast functl ook git, mise en Docker nodig. mise installeert de Go-versie en de andere tools die je plugin vastlegt. De commando's hieronder werken in elke terminal, ook in PowerShell.

Maken

functl plugin create my-plugin cd my-plugin mise trust mise install

functl plugin create vraagt om een paar keuzes, zoals het sjabloon en of je plugin een eigen scherm in de console krijgt. De uitleg per keuze staat in Een plugin schrijven.

Bouw en test je plugin:

mise exec -- go mod tidy mise exec -- just build mise exec -- just test

Bouwen

De servers van je cluster zijn x86-machines, dus bouw je image voor linux/amd64. De Dockerfile compileert daarvoor, ook op een Mac met Apple Silicon:

docker build --platform linux/amd64 -t my-plugin:dev .

Pushen, publiceren en installeren

docker image inspect my-plugin:dev als architectuur amd64 toont.

Forgejo: code, images en CI/CD

Voor het fieldlab stellen we Forgejo ter beschikking: Git-repositories voor je code, een registry voor je container-images en runners voor CI/CD. Je organisatie wordt daar ingericht, zodat jullie bij alle drie kunnen.

Opnieuw beginnen

Loopt er iets vast en wil je met een schoon cluster verder, verwijder dan alles in deze volgorde. Een cluster met projecten erop kun je niet verwijderen.

  1. Namespaces: open in je project een namespace en klik op Delete namespace. Alles wat erin draait verdwijnt mee.
  2. Project: klik in je project onder General op Delete project en typ ter bevestiging <organisatie>/<cluster>/<project>.
  3. Cluster: klik op de pagina van je cluster op Delete cluster en typ <organisatie>/<cluster>.

Het cluster staat dan een paar minuten op Deleting. Tot het helemaal weg is, telt het nog mee in je quota. Wacht daarop voordat je een nieuw cluster aanmaakt.

Problemen oplossen

Een foutmelding kan meerdere oorzaken hebben. Hieronder de meldingen die je het meest zult zien, met wat je als eerste kunt controleren.

kubectl vindt functl niet

Unable to connect to the server: getting credentials: exec: executable functl not found

Controleer met functl version of functl op je PATH staat, en open een nieuw terminalvenster na het installeren. Grafische tools zoals Freelens of Headlamp krijgen niet altijd het PATH van je terminal; start ze dan vanuit de terminal.

Je API-key is verlopen

Error: failed to exchange API key for token: ... unauthenticated: token expired Unable to connect to the server: getting credentials: exec: executable functl failed with exit code 1

Maak een nieuwe API-key (stap 4) en log opnieuw in met functl auth login.

kubectl krijgt geen verbinding met je cluster

Error from server (ServiceUnavailable): the server is currently unable to handle the request

Controleer in de console of je cluster op Running staat. Zolang het wordt aangemaakt, is dit de verwachte melding.

Je cluster staat op Unhealthy

Vlak na het aanmaken kan dat een paar minuten zo zijn. Blijft het zo, kijk dan op de pagina van je cluster bij de status en de gebeurtenissen wat er niet goed gaat.

Je namespace wordt niet gevonden

Error from server (NotFound): namespaces "app" not found

Controleer of je de volledige naam gebruikt: tnt-<project>--<namespace>. Met de korte naam geeft kubectl get ook No resources found, terwijl je workload wel draait.

Een volume blijft op Pending

Bekijk met kubectl -n <namespace> describe pvc <naam> wat Kubernetes erover meldt. Veelvoorkomende oorzaken: er is geen standaard StorageClass (stap 5), de claim gebruikt csi-lvm, of je Ceph-opslag staat nog niet op Ready.

Hulp tijdens het fieldlab

Vragen over Fundament stel je in het Mattermost-kanaal ~users-poc.

Verder lezen

De Fundament-documentatie gaat dieper in op elke stap. Meer bronnen staan op Resources.

Werk je liever met OpenTofu of Terraform? De Fundament Terraform-provider en zijn documentatie staan op GitHub.

23, 24 en 25 november 2026 in The Social Hub, Den Haag. Drie dagen samen bouwen aan een soevereine basis voor de digitale overheid. Vragen over het fieldlab, over deelname of over de techniek? We helpen je graag verder.