Kubernetes zuhause - GitOps mit Flux CI/CD
In diesem Beitrag setze ich GitOps mit Flux CI/CD in meinem lokalen Kubernetes-Cluster auf. Mein Ziel ist es, die im Cluster betriebenen Ressourcen mit deklarativ mit Hilfe von Konfigurationsdateien in einem Git-Repository zu pflegen. Der Cluster soll auf dem Stand gehalten werden, der im Repository mit Konfiguration dokumentiert ist. Dem gegenüber stünde, Änderungen imperativ einfach mit kubectl
oder helm
auf der Kommandozeile in den Cluster zu bringen.
Flux wird als Anwendung in den Cluster deployed. Von dort aus wird es regelmäßig den Inhalt des Git-Repositories prüfen und Änderungen an der Konfiguration umsetzen. Kubernetes-Ressourcen können dort beispielsweise YAML-Manifeste oder Helm-Charts sein.
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
Voraussetzungen
Der Kubernetes-Cluster muss bereits laufen und lokal müssen die Admin-Berechtigungen vorhanden sein. Die Konfiguration wird zu GitHub hochgeladen. Alternativ kann man dafür auch GitLab nehmen (hosted oder selfhosted) oder sonst irgendein Git-Repository. Ich verwende einfach GitHub.
Flux installieren
Zuerst wird ein GitHub Personal Access Token gebraucht - von der Classic-Sorte. Als Scope sind notwendig: repo (komplett, also der erste Haken) und admin:public_key (auch komplett).
Auf der lokalen Maschine wird das Kommandozeilentool für Flux gebraucht. Das kann auf vielfältige Weise installiert werden, beispielsweise mit Homebrew:
$ brew install fluxcd/tap/flux
Als nächstes ein paar grundlegende Checks machen:
$ flux check --pre
► checking prerequisites
✔ Kubernetes 1.25.4+k3s1 >=1.20.6-0
✔ prerequisites checks passed
Ist alles in Ordnung, kann mit dem Bootstrapping begonnen werden. Ist das GITHUB_REPO
noch nicht vorhanden, wird es automatisch angelegt.
$ export GITHUB_USER=der_github_username
$ export GITHUB_TOKEN=das_github_personal_access_token_von_vorhin
$ export GITHUB_REPO=k8s
$ flux bootstrap github \
--owner=$GITHUB_USER \
--repository=$GITHUB_REPO \
--branch=main \
--path=./bootstrap \
--personal
Dann passieren ein paar Dinge:
- Das Repository wird angelegt, falls es noch nicht existiert.
- Im Repository wird
bootstrap/flux-system
angelegt. Dort liegen die Bootstrapping-Resourcen. - Dem Repository bei GitHub wird ein öffentlicher SSH-Schlüssel als Deploy-Key hinzugefügt, mit dem sich Flux aus dem Cluster bei GitHub anmeldet
- Im Cluster werden die notwendigen Ressourcen angelegt, darunter:
- Controller für Helm, Kustomize.
CustomResourceDefinition
s um Git Repositories, Helm Repositories und Charts sowie Kustomizations abzubilden.- Ein
Secret
mit dem privaten SSH-Schlüssel, mit dem sich Flux aus dem Cluster bei GitHub anmeldet. - Ein
GitRepository
, das den Abruf des Repositories regelt und eine anonymeKustomization
, diebootstrap/flux-system
in diesem Repository einbindet. Dort findet sich die endgültigeKustomization
, die wiederum rekursiv den ganzenbootstrap/
-Ordner einbindet.
Als nächster Schritt sollte dann auch das Repository lokal ausgecheckt werden:
$ git clone [email protected]:$GITHUB_USER/$GITHUB_REPO
$ tree $GITHUB_REPO
k8s
└── bootstrap
└── flux-system
├── gotk-components.yaml
├── gotk-sync.yaml
└── kustomization.yaml
2 directories, 3 files
Grundlegende Struktur hinzufügen
Im Cluster können nun Anwendungen und weitere Ressourcen über eine Kustomization
, ein GitRepository
oder ein HelmChart
installiert werden. Bevor es damit los geht, hilft eine Verzeichnisstruktur als Schema. Ein Beispiel könnte so aussehen:
$ tree $GITHUB_REPO
k8s
├── apps
│ └── exampleapp
│ └── helmchart-exampleapp.yaml
└── bootstrap
├── flux-system
│ ├── gotk-components.yaml
│ ├── gotk-sync.yaml
│ └── kustomization.yaml
├── helmrepository
│ └── helmrepository-example.yaml
├── kustomization
│ └── kustomization-example.yaml
└── namespace
├── namespace-exampe.yaml
└── namespace-infra.yaml
Zur Begründung:
apps/
beinhaltet Unterordner mit der Konfiguration, die einzelne Anwendungen in den Cluster installiert. Hier im Beispiel alsHelmChart
. Die einzelnen Dateien in diesen Ordnern werden nur angewandt, wenn sie von einerKustomization
aus/bootstrap
referenziert werden.bootstrap/
wird von Flux bereits rekursiv angewandt. Dort können einfach Kubernetes-Manifeste als YAML-Dateien hinterlegt werden und Flux wird sie anlegen. Einzelne Apps konfiguriere ich dennoch nicht einfach dort sondern nehme den Umweg über eineKustomization
, die sich auf einen Ordner in/apps/
bezieht. So lässt sich eine App leicht aktivieren und deaktivieren.helmrepository/
beinhaltet mehrfach nutzbare Helm Repositories für einzelne Helm Charts.kustomization/
beinhaltet pro Anwendung eineKustomization
als Verweis auf einen Ordner in/apps/
.namespace/
legt unabhängig von den AnwendungenNamespace
s an.
Kustomizations sind recht mächtige Konfigurationsmöglichkeiten für Kubernetes. In ihrer einfachsten Form referenzieren sie einfach andere Dateien, in denen Kubernetes-Manifeste abgelegt sind. Darüber hinaus lassen sich beispielsweise auch mehrstufige Deployments (dev, staging, prod) deklarativ abbilden. Das wäre analog zur Verwendung von Templating, was sich deklarativ allerdings schwieriger abbilden lässt. Für’s erste bleibe ich bei der einfachsten Form und setze sie als Verweise auf weitere Manifeste ein. Löscht man eine Kustomization
aus /bootstrap/kustomization
, wird die davon referenzierte Anwendung in /apps
ebenfalls aus dem Cluster geworfen.
Umsetzung: Einen Namespace anlegen
Viel Theorie, nun etwas Praxis. Zum Test der Funktion wird ein Namespace infra
angelegt.
$ cd $GITHUB_REPO
$ mkdir -p bootstrap/namespace
$ kubectl create --dry-run=client namespace infra --output yaml | tee -a bootstrap/namespace/namespace-infra.yaml
apiVersion: v1
kind: Namespace
metadata:
creationTimestamp: null
name: infra
spec: {}
status: {}
$ git add bootstrap/namespace/namespace-infra.yaml
$ git commit -m "create infra namespace"
[main 313373] create infra namespace
1 file changed, 6 insertions(+)
create mode 100644 bootstrap/namespace/namespace-infra.yaml
$ git push
Enumerating objects: 7, done.
# […]
$ flux get kustomizations --watch
NAME REVISION SUSPENDED READY MESSAGE
flux-system main/313373 False True Applied revision: main/313373
Mit dem letzten Befehl wird die Anwendung der Kustomization
s überwacht. Bisher gibt es nur flux-system
, die unter anderem den /bootstrap/namespace
-Ordner überwacht. Sobald unter READY
der Erfolg mit True
angegeben wird, gibt es den Namespace:
$ kubectl get ns
NAME STATUS AGE
default Active 65m
kube-system Active 65m
kube-public Active 65m
kube-node-lease Active 65m
flux-system Active 58m
infra Active 2m
Mit diesem einfachen Beispiel sind die Grundlagen demonstriert. Die komplizierteren und nützlicheren Anwendungen folgen in den nächsten Artikeln.