Kubernetes zuhause - Anwendung deployen mit Flux CI/CD und Helm
Im vorherigen Beitrag habe ich Flux CI/CD in Kubernetes installiert und einen Namespace als erste Resource angelegt. Dass das Konzept funktioniert wäre damit belegt, besonders nützlich ist das aber noch nicht. In diesem Beitrag will ich eine erste Anwendung mit Hilfe von Helm und Flux in meinem Git-Repository konfigurieren und ausrollen.
Andere Beiträge
Dieser Beitrag ist Teil einer Reihe von Blogposts. Ich betreibe zuhause einen Kubernetes-Cluster, in dem ich ein paar Anwendungen wie Nextcloud, Jellyfin oder Vaultwarden betreibe.
- Kubernetes zuhause mit Rancher Desktop und K3s - Einführung und Installation
- Praktische Tools - Die Arbeit mit Kubernetes etwas erleichtern
- GitOps mit Flux CI/CD - Kubernetes-Konfiguration im Git pflegen
- CI/CD-Deployment von Apps - CI/CD-Deployment von Apps
- Anwendung deployen mit Flux CI/CD und Helm
- Webanwendung nach außen verfügbar machen mit Ingress
Theorie im Hintergrund: Wieso Helm verwenden?
Ressourcen in Kubernetes werden zumeist mit einer Vielzahl von Ressourcen konfiguriert und ausgerollt: Ein Deployment regelt, welche und wie viele Container laufen. ConfigMaps halten Konfiguration für die Anwendung vor. Ein Service macht die Sammlung der Container im Cluster erreichbar. Ein Ingress macht die Anwendung nach außen hin verfügbar, unter einer URL. Diese Resourcen laufen natürlich im Cluster, sind eigentlich aber eher Voraussetzungen für die Anwendung und nicht Teil der Infrastruktur. Läuft die Anwendung nicht, braucht man auch genau diese Ressourcen nicht. Es macht also Sinn, diese Ressourcen zusammen mit der Anwendung zu paketieren und zu versionieren. Noch mehr Sinn macht es, als Herausgeberin einer solchen Anwendung direkt die Kubernetes-Konfiguration mitzuliefern. Ein paar Dinge kann die Betreiberin eines Kubernetes-Clusters dann noch an die eigenen Gegebenheiten anpassen (Versionsnummern, FQDNs für URLs, Datenbankserver, etc) und ansonsten die vorgegebenen Rezepturen verwenden.
Der gebräuchlichste Ansatz dafür sind Helm Charts. Für viele Anwendungen, die sich in Clustern betreiben lassen, gibt es bereits diese fertigen Rezepte - Charts - zum Deployment. Über Values lassen sich noch eigene Parameter übergeben. Charts sind in Repositories organisiert. Zuallererst ist helm
allerdings eine Kommandozeilen-Anwendung und das Installieren von Charts, die aus Repositories kommen, ist einfach ein Aufruf von helm install
. Das ist nicht besonders deklarativ oder zuverlässig reproduzierbar.
Theorie im Hintergrund: Wie Helm deklarativ verwenden?
Um die Installation von Helm Charts auch im Git-Repository ordentlich konfigurieren zu können, liefert Flux CI/CD deswegen einen Operator mit. Ein Operator ist eine Anwendung, die im Cluster läuft und Cutom Resource Definitions umsetzt. In diesem Fall werden die neuen Ressourcen-Typen HelmRepository und HelmRelease eingeführt, die sich über Kubernetes-Manifeste definieren lassen. So kann in einem HelmRelease die Verwendung eines Charts wieder im Git-Repository deklarativ konfiguriert werden. Findet der Helm Operator dann eine Resource des Typs HelmRelease, startet er die Installation des Charts gemäß der damit verbundenen Konfiguration.
Installation von podinfo
mit Helm
Nun der praktische Teil. Zum Testen der Installation mit Helm wird podinfo deployed. Das ist eine kleine Anwendung, gut geeignet für Hello World ohne große Abhängigkeiten.
Namespace als Ziel für das Deployment der Anwendung
Den Anfang macht der podinfo
-Namespace, in den die Anwendung installiert werden soll. Um diesen automatisch anzulegen, genügt eine Datei bootstrap/namespace/namespace-podinfo.yaml
:
---
apiVersion: v1
kind: Namespace
metadata:
name: podinfo
Nachdem diese Datei im globalen bootstrap/
-Verzeichnis liegt, dessen Inhalt automatisch in den Cluster gespielt wird, muss ansonsten nichts mehr zur Einrichtung eines Namespace vorgenommen werden.
HelmRepository als Quelle für Helm Charts
Die Installation der Anwendung erfolgt durch ein HelmRelease, dazu gleich mehr. Das Release bezieht sich auf ein Chart, das aus einem Repository kommt. In einem Repository können mehrere Charts vorhanden sein und ein und das selbe Chart kann mehrfach installiert werden, auch für verschiedene Anwendungen. Es macht daher Sinn, das HelmRepository global zu pflegen und nicht an die Anwendung zu koppeln. Deswegen gibt es im bootstrap/
-Verzeichnis dafür einen eigenen Ordner und es bietet sich an, die HelmRepository-Resource nicht in den eben erstellten Namespace der Anwendung zu installieren.
Der Inhalt einer bootstrap/helmrepository/helmrepository-podinfo.yaml
könnte also so aussehen:
---
apiVersion: source.toolkit.fluxcd.io/v1beta1
kind: HelmRepository
metadata:
name: podinfo
namespace: default
spec:
interval: 15m
url: https://stefanprodan.github.io/podinfo
Das Feld apiVersion
hilft Kubernetes herauszufinden, wie diese Resource umgesetzt werden kann. In diesem Fall ist das keine native Resource von Kubernetes sondern kam erst mit Flux CI/CD dazu. Der name
und der namespace
beziehen sich hier natürlich auf das HelmRepository und nicht auf die Anwendung, die daraus später deployed wird! Die meisten HelmRepositories sind über eine URL erreichbar. Der Cluster wird alle 15 Minuten nachsehen, ob es im Repository Neuerungen gibt. Automatisch installiert werden diese allerdings nicht; das wäre eine Einstellung des HelmRelease.
HelmRelease zur Installation der Anwendung
Nun wird die Anwendung selbst konfiguriert. Dazu bekommt sie ein Verzeichnis, in das die notwendigen Manifeste alle zusammengesammelt werden. Damit das Verzeichnis von Flux CI/CD gefunden wird, bekommt die Anwendung im Anschluss noch ein Manifest für seine Kustomization.
Ich lege also das Verzeichnis apps/podinfo
an und erstelle dort eine helmrelease-podinfo.yaml
:
---
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: podinfo
namespace: podinfo
spec:
chart:
spec:
chart: podinfo
version: 6.x
sourceRef:
kind: HelmRepository
name: podinfo
namespace: default
interval: 15m
timeout: 5m
releaseName: podinfo
values:
replicaCount: 1
Die Konfigurationsmöglichkeiten eines HelmRelease sind vielfältig. Zu bedenken ist, dass das HelmRelease diesmal in den podinfo
-Namespace kommt und dass in der sourceRef
als Quelle für das Release das HelmRepository von vorhin referenziert wird, was seinerseits aber im default
-Namespace lebt.
Unter values
können die Parameter für das Deployment des Helm Charts mitgegeben werden. Hier habe ich beispielsweise die Anzahl der zu startenden Container auf 1
gesetzt. Welche Parameter unterstützt werden, kommt auf das Helm Chart an und wird am besten jeweils der Dokumentation des Charts entnommen.
In diesem Fall bin ich nun mit einem einfachen HelmRelease
als Resource ausgekommen. Hätte ich darüber hinaus noch z.B. eine ConfigMap
gebraucht, hätte ich die einfach in das selbe Verzeichnis legen können und Flux CI/CD hätte sie auch von dort aus in den Cluster installiert.
Kustomization
Damit Flux CI/CD das HelmRelease findet, muss es noch in einer Kustomization referenziert werden. Dazu lege ich eine Datei im automatisch geladenen Bootstrapping-Ordner an - bootstrap/kustomization/kustomization-podinfo.yaml
:
---
apiVersion: kustomize.toolkit.fluxcd.io/v1beta1
kind: Kustomization
metadata:
name: podinfo
namespace: default
spec:
interval: 15m
path: "apps/podinfo"
validation: server
prune: true
sourceRef:
kind: GitRepository
name: flux-system
namespace: flux-system
Hier wird Flux CI-CD mitgeteilt, dass es eine Kustomization gibt, die podinfo
heißt und deren Inhalt im GitRepository namens flux-system
liegt. Dabei handelt es sich um die Kubernetes-Ressource, die sich auf das Git-Repository bezieht, mit welcher der Cluster konfiguriert wird. Das selbe Repository also, in dem die Kustomization auch liegt. Alle 15 Minuten wird dort im Pfad apps/podinfo/
nachgesehen und dessen Inhalt auf dem Cluster versucht anzuwenden (dry-run). Damit wird ermittelt, ob sich entweder im Cluster etwas getan hat, was so nicht im Git-Repository steht oder ob sich im Git-Repository etwas geändert hat, was im Cluster noch umzusetzen ist (drift detection). Ressourcen aus Manifesten, die im Git-Repository nicht mehr stehen, werden dabei auch aus dem Cluster entfernt (prune).
Wie wird das jetzt umgesetzt?
Beim periodischen Überprüfen dieser Kustomization wird Flux CI/CD also auf das HelmRelease stoßen und dieses im Cluster als Ressource anlegen. Der Vorgang, den Unterschied zwischen Git-Repository und Wirklichkeit im Cluster auszugleichen, heißt hier Reconciliation. Der Helm Controller stößt auf das HelmRelease und beginnt seinerseits mit einer Reconciliation, lädt das Helm Chart aus dem HelmRepository herunter und installiert es gemäß der Konfiguration im HelmRelease-Manifest.
Den Vorgang des Deployments kann man mit dem Kommandozeilentool von Flux beobachten. Für einen Überblick über alle von Flux verwalteten Ressourcen:
$ watch -n3 flux get all -A
NAMESPACE NAME REVISION SUSPENDED READY MESSAGE
flux-system gitrepository/flux-system main/3782386 False True stored artifact for revision 'main/37823864bb3f1da405e4fe73bf304290c6e9cec4'
NAMESPACE NAME REVISION SUSPENDED READY MESSAGE
default helmrepository/podinfo 70c481e96b98984040c7150f644b77cc27baeebf8bbc7916ab40d5852297c8d3 False True stored artifact for revision '70c481e96b98984040c7150f644b77cc27baeebf8bbc7916ab40d5852297c8d3'
NAMESPACE NAME REVISION SUSPENDED READY MESSAGE
default helmchart/podinfo-podinfo 6.3.0 False True pulled 'podinfo' chart with version '6.3.0'
NAMESPACE NAME REVISION SUSPENDED READY MESSAGE
podinfo helmrelease/podinfo 6.3.0 False True Release reconciliation succeeded
NAMESPACE NAME REVISION SUSPENDED READY MESSAGE
flux-system kustomization/flux-system main/3782386 False True Applied revision: main/3782386
default kustomization/podinfo main/3782386 False True Applied revision: main/3782386
Dabei ist zu beachten, dass hier nur erkannte Ressourcen beobachtet werden können! Hat man etwa keine Kustomization angelegt, die auf das HelmRelease verweist, weiß Flux CI/CD nichts davon und kann es hier auch nicht anzeigen.
Um zu sehen, was passiert, gibt es Logs von Flux CI/CD:
$ flux logs
info GitRepository/flux-system.flux-system - stored artifact for commit 'add podinfo helmrelease'
info GitRepository/flux-system.flux-system - garbage collected 1 artifacts
info GitRepository/flux-system.flux-system - no changes since last reconcilation: observed revision 'main/ca481dccca4b910e7375f7f3668c786deb541e2e'
info GitRepository/flux-system.flux-system - stored artifact for commit 'add podinfo kustomization'
info GitRepository/flux-system.flux-system - garbage collected 1 artifacts
Außerdem kann auch im Cluster beobachtet werden, was passiert:
$ kubectl get events -A
flux-system 12m Normal Progressing kustomization/flux-system Kustomization/default/podinfo created
flux-system 10m Normal NewArtifact gitrepository/flux-system stored artifact for commit 'add podinfo kustomization'
flux-system 10m Normal Progressing kustomization/flux-system Kustomization/default/podinfo configured
flux-system 10m Normal ReconciliationSucceeded kustomization/flux-system Reconciliation finished in 419.014875ms, next run in 10m0s
default 10m Normal Progressing kustomization/podinfo HelmRelease/podinfo/podinfo created
default 10m Normal ReconciliationSucceeded kustomization/podinfo Reconciliation finished in 48.9025ms, next run in 15m0s
podinfo 10m Normal info helmrelease/podinfo HelmChart 'default/podinfo-podinfo' is not ready
default 10m Normal ChartPullSucceeded helmchart/podinfo-podinfo pulled 'podinfo' chart with version '6.3.0'
podinfo 10m Normal info helmrelease/podinfo Helm install has started
podinfo 10m Normal ScalingReplicaSet deployment/podinfo Scaled up replica set podinfo-5b58b74576 to 1
podinfo 10m Normal SuccessfulCreate replicaset/podinfo-5b58b74576 Created pod: podinfo-5b58b74576-gd7px
podinfo 10m Normal info helmrelease/podinfo Helm install succeeded
podinfo 10m Normal Scheduled pod/podinfo-5b58b74576-gd7px Successfully assigned podinfo/podinfo-5b58b74576-gd7px to lima-rancher-desktop
podinfo 10m Normal Pulled pod/podinfo-5b58b74576-gd7px Container image "ghcr.io/stefanprodan/podinfo:6.3.0" already present on machine
podinfo 10m Normal Created pod/podinfo-5b58b74576-gd7px Created container podinfo
podinfo 10m Normal Started pod/podinfo-5b58b74576-gd7px Started container podinfo
flux-system 9m56s Normal GarbageCollectionSucceeded gitrepository/flux-system garbage collected 1 artifacts
flux-system 8m Normal ReconciliationSucceeded kustomization/flux-system Reconciliation finished in 489.568ms, next run in 10m0s
default 3m9s Normal ArtifactUpToDate helmrepository/podinfo artifact up-to-date with remote revision: '70c481e96b98984040c7150f644b77cc27baeebf8bbc7916ab40d5852297c8d3'
flux-system 44s Normal GitOperationSucceeded gitrepository/flux-system no changes since last reconcilation: observed revision 'main/37823864bb3f1da405e4fe73bf304290c6e9cec4'
Ausprobieren
Ist Flux CI/CD fertig mit Reconciling, sollte es ein paar Ressourcen im Cluster-Namespace podinfo
geben:
$ kubectl get all -n podinfo
NAME READY STATUS RESTARTS AGE
pod/podinfo-5b58b74576-gd7px 1/1 Running 0 1m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/podinfo ClusterIP 10.43.244.205 <none> 9898/TCP,9999/TCP 1m
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/podinfo 1/1 1 1 1m
NAME DESIRED CURRENT READY AGE
replicaset.apps/podinfo-5b58b74576 1 1 1 1m
Sind diese Ressourcen schlussendlich alle im Ready
-Zustand, regelt ein Deployment mit Hilfe eines ReplicaSet, dass ein Pod läuft. Der Service sammelt alle Pods ein - in diesem Fall nur den einen. Der Service hat TCP-Ports offen und leitet Anfragen an diese Ports an die Pods weiter. Nun ist aber der Service nur mit einer ClusterIP erreichbar - nicht von außerhalb des Clusters. Zum einfachen Testen lässt sich der Port aber über kubectl
auch lokal auf der eigenen Workstation erreichbar machen:
$ kubectl port-forward -n podinfo service/podinfo 9898:9898
Forwarding from 127.0.0.1:9898 -> 9898
Forwarding from [::1]:9898 -> 9898
Damit wird ein Port-Forwarding auf http://localhost:9898 eingerichtet, bei dem der Service
mit Port 9898
ran geht. Ruft man diese Adresse im Browser auf, wird man von der Podinfo-Anwendung begrüßt.