Wenn man mit Kubernetes arbeitet, wird man in den seltensten Fällen alles auf einem einzigen Cluster erledigen. Unabhängig davon, ob man ein lokales Minikube mit Docker verwendet oder auf verschiedene Cluster zugreift, ist der Aufbau und die Instandhaltung der Cluster von großer Bedeutung. Dies führt zu erheblichem Entwicklungsaufwand, zumal je nach Hyperscaler und deren APIs unterschiedliches Wissen erforderlich ist. Neben der Gewährleistung funktionaler Anforderungen müssen auch Qualitätsmerkmale wie Sicherheit oder Compliance berücksichtigt werden. Auch die Frage nach der idealen Größe und dem grundsätzlichen Nutzen des Clusters ist von Bedeutung. Ein zu großer Cluster oder ein Cluster, der nur für Testzwecke eingerichtet wurde, kann unnötige Kosten verursachen. Gerade in Cloud-Umgebungen werden provisionierte Ressourcen und ihre Laufzeit in Rechnung gestellt.
Auch der Betrieb, besser bekannt als Day-2-Operations, bringt weitere Schwierigkeiten mit sich. Je mehr Cluster in einer Umgebung vorhanden sind, desto umfangreicher und komplexer wird das Lifecycle-Management. Es müssen regelmäßig kritische Sicherheitspatches sowie Minor- und Major-Updates von Kubernetes und seinen Abhängigkeiten installiert werden, um Sicherheitslücken und Angriffe zu vermeiden. Besonders in großen Unternehmen mit verschiedenen Abteilungen und Bereichen findet man immer wieder Silos, die nicht auf dem aktuellen Stand sind.
GitOps für Multi-Cluster
Von der deklarativen Konfiguration bis zur Automatisierung bietet GitOps eine effiziente und skalierbare Lösung zur Cluster-Verwaltung im Unternehmen. Wie im letzten Kapitel 9 bereits erklärt wurde, ist es nicht möglich, alle Operationen mit GitOps zu automatisieren. Das Ziel sollte daher sein, konsistent die Aktionen zu automatisieren, die mit GitOps möglich sind. So bleibt genug Kapazität für manuelle Tätigkeiten.
In diesem Kapitel möchten wir erläutern, wie wir mit GitOps diese Herausforderungen lösen können. Wir werden hierfür exemplarisch Cluster API für das Cluster-Management einsetzen.
Es ist wichtig zu prüfen, ob ein Cluster benötigt wird. Mittels Namespaces kann eine logische Trennung von Clustern erreicht werden, was im Allgemeinen zu einer höheren Pod-Dichte als bei physisch isolierten Clustern führt. Dadurch bleibt weniger ungenutzte Compute-Kapazität im Cluster übrig. Durch die flexible Architektur von Kubernetes lässt sich die Anzahl der Worker-Nodes gemäß den Anforderungen hoch- oder herunterskalieren. Dieser Ansatz ist leicht zu verwalten und die Kosten bleiben überschaubar.
Jedoch sind Namespaces zunächst nur Namensräume, damit Ressourcen mit gleichem Namen mehrfach im Cluster deployt werden können. Der Zugriff auf die Namespaces, genauer gesagt auf die jeweilige API, kann mit RBAC verwaltet werden. Jedoch ist als Admin zu beachten, dass das Netzwerk zwischen den Namespaces im Standardfall nicht segmentiert ist. Ein Pod hat einfachen Zugriff auf einen Pod aus einem anderen Namespace. Die Netzwerkkommunikation ist davon abhängig, welche CNI-Implementierung (Container Network Interface) eingesetzt wird und wie diese konfiguriert ist. Im Falle von Calico kann über sogenannte Policies das Netzwerk, genauer gesagt die jeweiligen Namespaces, auf unterschiedlichen OSI-Schichten abgesichert werden1.
Eine vollständige Isolierung ist jedoch in der Regel nicht erreichbar. Außerdem besteht eine Gefahr, wenn Hacker Zugriff auf hochprivilegierte Konten erlangen. Daher sind nahezu alle Namespaces anfällig für Angriffe. Eine gängige Methode der Cluster-Isolierung ist die physische Trennung von Kubernetes-Clustern. In diesem Isolationsmodell werden Teams oder Workloads eigene Cluster zugewiesen. Jedoch erhöht diese simple Variante den Verwaltungs- und Finanzaufwand enorm, weil physisch isolierte Cluster in der Regel über eine geringe Pod-Dichte verfügen. Jedes Team oder jeder Workload hat einen eigenen Cluster, wodurch dem Cluster oft zu viele Computerressourcen zugewiesen werden. Dieser Nachteil kann jedoch durch verschiedene Mechanismen, wie eine automatisierte Zuweisung von Ressourcen mittels Autoscaling, gemildert werden.
Das Projekt Cluster API (CAPI) ist ein Unterprojekt von Kubernetes, das sich mit der deklarativen Verwaltung von Kubernetes-Clustern befasst. Mithilfe von APIs und Werkzeugen können Cluster bereitgestellt, aktualisiert und verwaltet werden. Das Ziel der Initiatoren der Kubernetes Special Interest Group (SIG) Cluster Lifecycle ist es, die Verwaltung und den Betrieb von Kubernetes so einfach wie möglich zu gestalten2.
Die Rolle von kubeadm
Für den Aufbau eines Clusters gibt es zahlreiche Distributionen für unterschiedliche Zwecke. In allen Lösungen ist kubeadm die zentrale Komponente für den Aufbau standardisierter Kubernetes-Cluster3. Es ermöglicht den Aufbau von Kubernetes-Clustern, die den Kubernetes Conformance Test4 bestehen können und reduziert somit die Komplexität. Seit seinen Anfängen ist kubeadm zum Standard-Bootstrapping-Tool für mehrere andere Anwendungen geworden, darunter Kubespray5, minikube6 und KinD7. Jedoch beschränkt sich kubeadm lediglich auf das Einrichten des Clusters. Genauso wichtig wie die Einrichtung von Clustern ist jedoch das Lifecycle Management. Hier setzt die Cluster API an.
Das Projekt beinhaltet das Einrichten von Clustern sowie die Konfiguration ihrer Infrastruktur. Hierzu gehören VPCs, Netzwerke und VMs. Eine API steht zur Verfügung, die eine einheitliche und generische Installation von Clustern über alle Anbieter hinweg ermöglicht. Die dazugehörigen Provider vereinfachen und standardisieren die Installation auf verschiedenen Cloud-Plattformen wie AWS, Azure, Google Cloud und auch On-Premise-Lösungen8. Dies hält die Lernkurve flach und erleichtert den Einstieg. Wenn jedoch die Notwendigkeit besteht, eine eigene Lösung zu entwickeln, kann das Framework durch selbst erstellte Provider erweitert werden.
Wie in Abb. 10–1 gezeigt wird, ist die Control Plane auf dem Management-Cluster die zentrale Komponente der Cluster API.
Abb. 10–1
Über die Cluster API können Benutzer mithilfe vorher festgelegter Spezifikationen Workload-Cluster auf verschiedene Plattformen bringen, ohne dabei plattformspezifisches Wissen zu benötigen.
Für dieses Vorhaben ist ein Kubernetes-Cluster unerlässlich. Der zentrale Management-Cluster überwacht alle Komponenten und Zustände der Workload-Cluster. Zur Konfiguration können sowohl eine imperative als auch eine deklarative Methode verwendet werden. Für die deklarative Konfiguration muss das Projekt Cluster API Operator9 genutzt werden. Zusätzlich sind in diesem Cluster alle benötigten Infrastruktur-Provider installiert, um von dort die Workload-Cluster zu provisionieren. Die Cluster API erweitert die Kubernetes-API um eigene CRDs, damit Cluster deklarativ spezifiziert werden können. So können wir domänenspezifische Ressourcen erstellen – und der Cluster API Controller gleicht stetig den aktuellen mit dem gewünschten Zustand ab. Auch gängige Konfigurationswerkzeuge wie Kustomize oder Helm lassen sich problemlos verwenden.
Die hohe Flexibilität von Kubernetes ermöglicht es uns, Cluster spezifisch an unsere Anforderungen anzupassen. Dabei können wir zwischen verschiedenen Optionen wie Bare Metal, virtuellen Maschinen oder Managed Services wählen und die Größe sowie Sicherheitsanforderungen individuell festlegen. Um den Anforderungen gerecht zu werden, nutzen wir die Architektur von Cluster API, die eine hohe Flexibilität ermöglicht. Hierbei werden neben der Cluster API selbst auch Provider eingesetzt, die wiederum in Bootstrap- und Infrastruktur-Provider unterteilt sind.
Abb. 10–2
Cluster API CRDs
Die Darstellung 10–2 zeigt das Zusammenspiel der Ressourcen auf, wenn wir Cluster API-Ressourcen (1) definieren. Dabei gibt es nicht nur generische Cluster API-eigene Ressourcen wie Cluster oder Machine, sondern auch providerspezifische Ressourcen (2). Die Bootstrap-Config (3) erzeugt und verwaltet die notwendigen Daten zur Registrierung einer Maschine als Knoten in einem Kubernetes Cluster.
Entwickelnde haben den Vorteil, dass sie die unterliegenden, providerspezifischen Ressourcen individuell austauschen und anpassen können. Wenn ich mich entscheide, meinen Workload von einem On-Premise-System auf einen Hyperscaler zu migrieren, reicht es aus, die Implementierung entsprechend anzupassen. Auch bereits existierende Cluster, die auf anderem Wege bereitgestellt wurden, können mithilfe von Cluster API verwaltet und migriert werden10. Cluster API unterstützt somit eine Vielzahl an Providern. Neben Hyperscalern können auch andere Provider genutzt werden11.
Wir gehen davon aus, dass ein einfacher EKS-Cluster mithilfe der Cluster API eingerichtet werden soll. In diesem Fall werden die in Abb. 10–3 aufgeführten Ressourcen benötigt.
Abb. 10–3
EKS-Cluster mit Cluster API
Wie bereits zu Beginn dieses Kapitels erwähnt, ist es mit Cluster API einfach möglich, standardisierte Kubernetes-Cluster zu provisionieren und zu verwalten. Allerdings erfordert die Control Plane von Cluster API einen Management-Cluster, der für unseren Zweck folgende Kriterien erfüllen muss:
Wir müssen einen zuverlässigen und ausfallsicheren Cluster in unserer Infrastruktur aufbauen. Zusätzlich müssen wir sicherstellen, dass unser Management-Cluster wartbar ist und Updates problemlos eingespielt werden können. Wir nutzen hierfür ebenfalls Cluster API. Um Kubernetes-Cluster zu provisionieren, benötigen wir jedoch selbst einen Kubernetes-Cluster, der wiederum einen weiteren Kubernetes-Cluster benötigt. Um dieses »Henne-Ei«-Problem zu lösen, verwenden wir einen temporären Bootstrap-Cluster. Der Prozess ist dann wie folgt:
Unser Management-Cluster kann sowohl imperativ als auch deklarativ konfiguriert werden. Dabei werden wir unseren Bootstrap-Cluster imperativ bauen, wohingegen wir für den Management-Cluster den Cluster API Operator einsetzen. Wir setzen hier exemplarisch EKS ein.
Zunächst stellen wir sicher, dass wir mit KinD einen lokalen Kubernetes-Cluster bauen können und clusterctl12 installiert haben. Um neue Cluster bei AWS bereitstellen zu können, müssen selbstverständlich die entsprechenden Anforderungen des jeweiligen Providers erfüllt werden. Für die Einrichtung von AWS als Provider müssen zunächst die Rollen und Rechte mit Identity and Access Management (IAM) angelegt werden. Zur Vereinfachung stellt AWS das Tool clusterawsadm zur Verfügung13, das die benötigten Ressourcen über AWS Cloud-Formation auf AWS bereitstellt. Hierfür ist wichtig ein Access- oder Session-Token als Umgebungsvariable zu definieren. Mithilfe des folgenden Befehls werden dann alle erforderlichen Ressourcen erstellt:
Listing 10–1
Anlegen der IAM-Rollen und Policies für den Management-Cluster mit AWS-Provider
1
export AWS_REGION=[Region]
2
export AWS_ACCESS_KEY_ID=[Key-Id]
3
export AWS_SECRET_ACCESS_KEY=[Secret]
4
clusterawsadm bootstrap iam create-cloudformation-stack
5
export AWS_B64ENCODED_CREDENTIALS=
6
$(clusterawsadm bootstrap credentials encode-as-profile)
Listing 10–2
Ausgabe nach Anlage aller Ressourcen
1
Attempting to create AWS CloudFormation stack
2
cluster-api-provider-aws-sigs-k8s-io
3
4
Following resources are in the stack:
5
6
Resource |Type |Status
7
AWS::IAM::InstanceProfile
8
|control-plane.cluster-api-provider-aws
9
.sigs.k8s.io |CREATE_COMPLETE
10
AWS::IAM::InstanceProfile |controllers.cluster-api-provider-aws
11
.sigs.k8s.io |CREATE_COMPLETE
12
...
13
14
|CREATE_COMPLETE
Nun können wir mit folgendem Befehl unseren Management-Cluster aufbauen:
Listing 10–3
Installation des Management-Clusters
1
export EXP_MACHINE_POOL=true
2
clusterctl init --infrastructure aws
Mit dieser zusätzlichen Umgebungsvariable aktivieren wir in der Cluster API die experimentelle Nutzung der sogenannten MachinePools. Mit dieser Ressource lassen sich Worker-Nodes logisch gruppieren, um unter anderem zentral die minimale und maximale Anzahl der Worker-Nodes zu verwalten.
Der Unterschied zwischen einem MachinePool und einem Machine-Deployment ist, dass ein MachinePool das Lifecycle Management der Maschinen der darunterliegenden Infrastruktur delegiert. Im Falle von AWS wird eine Autoscaling Group14 erstellt. So können beim Hoch- und Herunterskalieren des Clusters alle Vorteile wie Autoscaling der jeweiligen Cloud-Provider genutzt werden. Im Falle von EKS wird eine Managed Node Group15 angelegt, die den Lebenszyklus der Knoten verwaltet, was technisch gesehen EC2-Instanzen sind.
Listing 10–4
Ausgabe nach Anlage aller Ressourcen
1
Fetching providers
2
Installing cert-manager Version="v1.13.0"
3
Waiting for cert-manager to be available...
4
Installing Provider="cluster-api" Version="v1.5.2"
5
TargetNamespace="capi-system"
6
Installing Provider="bootstrap-kubeadm" Version="v1.5.2"
7
TargetNamespace="capi-kubeadm-bootstrap-system"
8
Installing Provider="control-plane-kubeadm" Version="v1.5.2"
9
TargetNamespace="capi-kubeadm-control-plane-system"
10
Installing Provider="infrastructure-aws" Version="v2.2.4"
11
TargetNamespace="capa-system"
12
13
Your management cluster has been initialized successfully!
Im Zuge des init-Prozesses werden Cluster API und kubeadm für die Provider Bootstrap und Control Plane installiert. Zudem wird AWS als Infrastructure-Provider eingerichtet, um unseren ersten funktionsfähigen Cluster deployen zu können.
Wie in Listing 10–4 ersichtlich ist, ermöglicht uns der Befehl clusterctl generate cluster die Generierung der benötigten Ressourcen als YAML-Dateien. Um unsere Workload-Cluster auf AWS bereitzustellen, müssen wir neben der Definition der benötigten AWS-Maschinentypen und des AWS EC2 Key-Pairs auch die Region definieren21, in der die Cluster erstellt werden sollen22,23. Diese Informationen müssen als Umgebungsvariablen zur Generierung von EKS-spezifischen Ressourcen definiert werden, wobei der sogenannte flavor als Kommando-Parameter angegeben werden muss. Zusätzlich können weitere Parameter wie die Kubernetes-Version und die Anzahl der Worker-Nodes definiert werden. Das Kommando ändert sich wie folgt:
Listing 10–5
Erstellung des Workload-Clusters
1
export AWS_REGION=us-east-1
2
export AWS_NODE_MACHINE_TYPE=t3.large
3
export AWS_SSH_KEY_NAME=capi-eks
4
clusterctl generate cluster gitops-eks-dev-cluster \
5
--flavor eks-managedmachinepool \
6
--kubernetes-version v1.25.3 \
7
--worker-machine-count=3 > capi-eks.yaml
Bei Ausführung werden nun die folgenden Ressourcen erzeugt:
Listing 10–6
Generierte Ressourcen für den Workload-Cluster
1
apiVersion: cluster.x-k8s.io/v1beta1
2
kind: Cluster
3
metadata:
4
name: gitops-eks-dev-cluster
5
spec: {}
6
---
7
apiVersion: infrastructure.cluster.x-k8s.io/v1beta2
8
kind: AWSManagedCluster
9
metadata:
10
name: gitops-eks-dev-cluster
11
spec: {}
12
---
13
apiVersion: controlplane.cluster.x-k8s.io/v1beta2
14
kind: AWSManagedControlPlane
15
metadata:
16
name: gitops-eks-dev-cluster-control-plane
17
spec: {}
18
---
19
apiVersion: cluster.x-k8s.io/v1beta1
20
kind: MachinePool
21
metadata:
22
name: gitops-eks-dev-cluster-pool-0
23
spec: {}
24
---
25
apiVersion: infrastructure.cluster.x-k8s.io/v1beta2
26
kind: AWSManagedMachinePool
27
metadata:
28
name: gitops-eks-dev-cluster-pool-0
29
spec: {}
Wir haben nun eine Cluster-Ressource mit den dazugehörigen EKS-relevanten Ressourcen AWSManagedControlPlane und einem AWS-ManagedCluster. Weiterhin haben wir ein MachinePool, das ein AWSManagedMachinePool beinhaltet.
Da wir den Workload nach der Migration in Argo CD als Application verwalten wollen, erzeugen wir gleich eine kustomization.yaml und spezifizieren das generierte Manifest hier als Ressource hinzu:
Listing 10–7
Kustomize für den Workload-Cluster
1
apiVersion: kustomize.config.k8s.io/v1beta1
2
kind: Kustomization
3
resources:
4
- capi-eks.yaml
Die Ressourcen können durch kubectl apply -k. erzeugt werden. Gleichzeitig versionieren wir dieses Manifest in Git.
Listing 10–8
Apply der Cluster API-Ressourcen
1
kubectl -f capi-eks-quickstart.yaml apply
2
cluster.cluster.x-k8s.io/gitops-eks-dev-cluster created
3
awsmanagedcluster.infrastructure.cluster.x-k8s.io
4
/gitops-eks-dev-cluster created
5
awsmanagedcontrolplane.controlplane.cluster.x-k8s.io
6
/gitops-eks-dev-cluster-control-plane created
7
machinepool.cluster.x-k8s.io/gitops-eks-dev-cluster-pool-0
8
created
9
awsmanagedmachinepool.infrastructure.cluster.x-k8s.io
10
/gitops-eks-dev-cluster-pool-0 created
Um den Status des Deployments zu betrachten, kann folgender clusterctl-Befehl genutzt werden:
Listing 10–9
Den Status der Provisionierung mit clusterctl abrufen
1
clusterctl describe cluster gitops-eks-dev-cluster
2
NAME READY SEVERITY REASON SINCE MESSAGE
3
Cluster/gitops-eks-dev-cluster False Warning
4
NatGatewaysReconciliationFailed 4m55s 3 of 10 completed
5
|-- ClusterInfrastructure -
6
AWSManagedCluster/gitops-eks-dev-cluster
7
|-- ControlPlane - AWSManagedControlPlane/
8
gitops-eks-dev-cluster-control-plane False
9
Warning NatGatewaysReconciliationFailed
10
4m55s 3 of 10 completed
11
|-- Workers
12
|--MachinePool/gitops-eks-dev-cluster-pool-0
13
False Info WaitingForInfrastructure 6m17s
14
|-- MachineInfrastructure - AWSManagedMachinePool/
15
gitops-eks-dev-cluster-pool-0
Im Erfolgsfall werden folgende Informationen angezeigt:
Listing 10–10
Erfolgreiche Provisionierung des Workload-Clusters
1
clusterctl describe cluster gitops-eks-dev-cluster
2
Cluster/gitops-eks-dev-cluster True 30m
3
|-- ClusterInfrastructure - AWSManagedCluster
4
/gitops-eks-dev-cluster
5
|-- ControlPlane - AWSManagedControlPlane
6
/gitops-eks-dev-cluster-control-plane True 30m
7
|-- Workers
8
|-- MachinePool/gitops-eks-dev-cluster-pool-0 True 30m
Der Zugang zum EKS-Cluster wird durch das Erstellen eines neuen Kontexts in der kubeconfig ermöglicht. Hierfür muss der folgende Befehl ausgeführt werden:
Listing 10–11
Neuen Kontext setzen
1
aws eks update-kubeconfig \
2
--name gitops-eks-dev-cluster-control-plane \
3
--region us-east-1
4
Added new context arn:aws:eks:us-east-1:[ID]:
5
cluster/gitops-eks-dev-cluster-control-plane to
6
/Users/bariscubukcuoglu/.kube/config
So können wir mit kubectl auf den Workload-Cluster zugreifen:
Listing 10–12
Zugriff auf den Workload-Cluster
1
kubectl get nodes
2
NAME STATUS ROLES AGE VERSION
3
ip-10-0-168-232.ec2.internal Ready <none> 2d22h
4
v1.25.12-eks-8ccc7ba
Nun haben wir unseren Workload-Cluster erzeugt. Im nächsten Schritt bauen wir diesen Cluster in einen Management-Cluster um. Dabei wollen wir exakt den gleichen Aufbau wie in unserer lokalen Umgebung.
Um zu verstehen, warum der Cluster API Operator ein ideales Werkzeug für unsere Zwecke ist, müssen wir zuerst verstehen, wie clusterctl arbeitet.
Der Befehl clusterctl init installiert die Provider basierend auf einer standardmäßigen Konfiguration. Jeder Provider hat natürlich seine eigenen Ressourcen-Manifeste mit Platzhaltern für die entsprechenden Parameter. Diese Manifeste werden auf den jeweiligen Projektseiten veröffentlicht. Die CLI verwaltet diese in einem eigenen lokalen Repository.
Listing 10–13
Hinterlegte Repositories in der CLI
1
> clusterctl config repositories
2
NAME TYPE URL FILE
3
cluster-api CoreProvider
4
https://github.com/kubernetes-sigs/cluster-api/releases/latest/
5
core-components.yaml
6
kubeadm BootstrapProvider
7
https://github.com/kubernetes-sigs/cluster-api/releases/latest/
8
bootstrap-components.yaml
9
aws InfrastructureProvider
10
https://github.com/kubernetes-sigs/cluster-api-provider-aws/
11
releases/latest/
12
infrastructure-components.yaml
13
...
Wenn ein neuer Provider installiert wird, geschieht Folgendes:
Wir können diese Manifeste mit dem Befehl clusterctl generate provider --infrastructure aws unter Berücksichtigung unserer Parameter exportieren und versionieren. Eine nachträgliche Anpassung unserer Parameter gestaltet sich jedoch als schwierig, da sie bereits eng mit den Manifesten verknüpft sind. Darüber hinaus sind die Manifeste auf eine bestimmte Version beschränkt, was ein Upgrade erschwert.
Die Absicht hinter dem Cluster API Operator ist es, den Lebenszyklus der Cluster API Provider deklarativ zu verwalten. Zu diesem Zweck installiert der Cluster API Operator einen Operator und führt ständig eine Reconciliation der Provider-Ressourcen durch. Die Provider-Ressourcen sind eigene CRDs und werden in CoreProvider, BootstrapProvider, ControlPlaneProvider und InfrastructureProvider unterteilt. Diese Ressourcen sind Wrapper für die eigentlichen Provider-Manifeste. Der Vorteil besteht darin, dass Modifikationen deklarativ vorgenommen werden können.
Für die Installation des Cluster API Operators ist der Cert-Manager erforderlich, während die Cluster API Provider den Cluster API Operator benötigen. Wir legen zunächst die Argo CD Application-Ressourcen an, führen sie jedoch nicht aus. Für den Cert-Manager definieren wir die folgende Ressource:
Listing 10–14
Anlegen der Cert-Manager-Ressourcen
1
apiVersion: argoproj.io/v1alpha1
2
kind: Application
3
metadata:
4
name: cert-manager
5
namespace: argocd
6
finalizers:
7
- resources-finalizer.argocd.argoproj.io
8
spec:
9
project: default
10
source:
11
repoURL: 'https://charts.jetstack.io'
12
targetRevision: v1.13.0
13
helm:
14
parameters:
15
- name: installCRDs
16
value: 'true'
17
chart: cert-manager
18
destination:
19
server: 'https://kubernetes.default.svc'
20
namespace: cert-manager
21
syncPolicy:
22
syncOptions:
23
- CreateNamespace=true
24
- ApplyOutOfSyncOnly=true
Danach erstellen wir die Ressource für den Cluster API Operator:
Listing 10–15
Cluster API Operator wird angelegt
1
apiVersion: argoproj.io/v1alpha1
2
kind: Application
3
metadata:
4
name: cluster-api-operator
5
namespace: argocd
6
finalizers:
7
- resources-finalizer.argocd.argoproj.io
8
project: default
9
source:
10
repoURL: 'https://kubernetes-sigs.github.io/
11
cluster-api-operator'
12
targetRevision: 0.6.0
13
chart: cluster-api-operator
destination:
15
server: 'https://kubernetes.default.svc'
16
namespace: capi-operator-system
17
syncPolicy:
18
syncOptions:
19
- CreateNamespace=true
20
ignoreDifferences:
21
- group: apiextensions.k8s.io
22
kind: CustomResourceDefinition
23
jsonPointers:
24
- /spec/conversion/webhook/clientConfig/caBundle
Da das Zertifikat zur Laufzeit mithilfe des Cert-Managers generiert wird, entfernen wir das caBundle explizit aus der Reconciliation.
Nun müssen wir unsere Provider-Ressource anlegen. Um exakt unseren Management-Cluster nachzubilden, benötigen wir 4 Provider-Ressourcen und müssen eine kustomization.yaml erstellen, damit die Provider als Einheit deployt werden können. Exemplarisch legen wir folgenden Infrastructure-Provider für AWS.
Listing 10–16
Anlegen der Cluster API Provider
1
apiVersion: operator.cluster.x-k8s.io/v1alpha2
2
kind: InfrastructureProvider
3
metadata:
4
name: aws
5
namespace: capa-system
6
spec:
7
version: v2.2.4
8
configSecret:
9
name: aws-variables
10
namespace: capa-system
11
manager:
12
featureGates:
13
EKS: true
14
EKSEnableIAM: true
15
EKSAllowAddRoles: true
16
EKSFargate: false
17
MachinePool: true
Über den Namen und die Version legen wir fest, welchen Provider wir benötigten. Zudem definieren wir Feature-Gates, damit benötigte Features aktiviert werden. Unsere kustomization.yaml ist wie folgt:
Listing 10–17
Wir fassen alle Ressourcen zu einer Einheit zusammen
1
apiVersion: kustomize.config.k8s.io/v1beta1
2
kind: Kustomization
3
resources:
4
- cluster-api-control-plane-provider.yaml
5
- cluster-api-bootstrap-provider.yaml
6
- cluster-api-core-provider.yaml
7
- cluster-api-infra-provider.yaml
Nun können wir diese Ressourcen wie folgt in Argo CD anlegen:
Listing 10–18
Wir legen eine Application-Ressource für die Provider an
1
apiVersion: argoproj.io/v1alpha1
2
kind: Application
3
metadata:
4
name: cluster-api-providers
5
namespace: argocd
6
spec:
7
destination:
8
server: 'https://kubernetes.default.svc'
9
source:
10
path: management/operator/provider
11
repoURL: 'git@gitlab.com:gitops-book/
12
multi-cluster-config.git'
13
targetRevision: HEAD
14
sources: []
15
project: default
16
syncPolicy:
17
syncOptions:
18
- CreateNamespace=true
19
- ApplyOutOfSyncOnly=true
Wir pushen alle definierten Ressourcen auf Git. Nun wenden wir das bekannte App Of Apps-Pattern an. Dazu fassen wir die definierten Ressourcen in einer kustomization.yaml-Ressource zusammen.
Listing 10–19
Wir fassen nun alle Application Ressources zu einer Einheit zusammen
1
apiVersion: kustomize.config.k8s.io/v1beta1
2
kind: Kustomization
3
resources:
4
- cert-manager.yaml
5
- cluster-api-operator.yaml
6
- cluster-api-providers.yaml
Für Argo CD legen wir eine neue Application-Ressource an:
Listing 10–20
Definieren einer Application-Ressource für die App Of Apps-Konfiguration
1
apiVersion: argoproj.io/v1alpha1
2
kind: Application
3
metadata:
4
name: cluster-api-config
5
namespace: argocd
6
project: default
7
source:
8
repoURL: 'git@gitlab.com:gitops-book/multi-cluster-config.git'
9
path: management/operator
10
targetRevision: HEAD
11
destination:
12
server: 'https://kubernetes.default.svc'
13
syncPolicy:
14
syncOptions:
15
- CreateNamespace=true
16
- ApplyOutOfSyncOnly=true
Jedoch haben wir bisher unsere Abhängigkeit nicht gelöst. Hierzu setzen wir in Argo CD das Feature Sync Phases and Waves24 ein. Damit können wir eine aufsteigende Abfolge von Wellen definieren, die sicherstellt, dass Ressourcen »Healthy« sind, bevor die nachfolgenden Ressourcen synchronisiert werden. Allerdings beschränkt sich dieses Feature auf Kubernetes-Ressourcen. Mit einem »Hack« funktioniert diese Funktion auch für Application-Ressourcen25. Wir verändern hierzu eine ConfigMap-Ressource argocd-cm wie folgt:
Listing 10–21
Anpassung der ConfigMap, damit Applications ebenfalls Sync-Waves verwenden können
1
apiVersion: v1
2
kind: ConfigMap
3
metadata:
4
name: argocd-cm
5
namespace: argocd
6
labels:
7
app.kubernetes.io/name: argocd-cm
8
app.kubernetes.io/part-of: argocd
data:
10
resource.customizations: |
11
argoproj.io/Application:
12
health.lua: |
13
hs = {}
14
hs.status = "Progressing"
15
hs.message = ""
16
if obj.status ~= nil then
17
if obj.status.health ~= nil then
18
hs.status = obj.status.health.status
19
if obj.status.health.message ~= nil then
20
hs.message = obj.status.health.message
21
end
22
end
23
end
24
return hs
Dadurch wird sichergestellt, dass der Application-Controller den Zustand der Application-Ressource korrekt meldet. Nun definieren wir zu jeder Application die entsprechenden Wellen wie folgt:
Listing 10–22
Definition einer Sync-Wave
1
annotations:
2
argocd.argoproj.io/sync-wave: "1"
Für den Cert-Manager definieren wir »0«, für den Cluster API Operator eine »1«, für die Cluster API Provider eine »2«.
Wenn wir Application-Ressource cluster-api-config deployen und synchronisieren, werden alle Ressourcen nacheinander bereitgestellt. In Abb. 10–4 sehen wir den Cert-Manager, in Abb. 10–5 den Operator und schließlich auch den Provider in Abb. 10–6.
Nun können wir damit beginnen, unseren Workload vom Bootstrap-Cluster auf unseren Management-Cluster zu übertragen. Dazu verwenden wir das folgende Kommando:
Listing 10–23
Migration der Workloads auf einen Management-Cluster
1
> clusterctl move --to-kubeconfig=eks-kubeconfig.yaml
2
Discovering Cluster~API objects
3
Moving Cluster~API objects Clusters=1
4
Moving Cluster~API objects ClusterClasses=0
5
Creating objects in the target cluster
6
Deleting objects from the source cluster
Abb. 10–4
Installation des Cert-Managers
Abb. 10–5
Installation des Cluster API-Operators
Abb. 10–6
Installation des Cluster API-Providers
Wir überprüfen, ob sich tatsächlich keine Cluster mehr auf unserem Bootstrap-Cluster befinden:
Listing 10–24
Prüfen, ob auf dem Bootstrap-Cluster noch Cluster-Ressourcen vorhanden sind
1
> kubectl get cluster
2
No resources found in default namespace.
Nun ist es möglich, für den aktuellen Management-Cluster eine Argo CD Application-Ressource zu erstellen. Dafür referenzieren wir auf das zuvor definierte kustomization.yaml.
Listing 10–25
Application-Ressource für Management-Cluster
1
apiVersion: argoproj.io/v1alpha1
2
kind: Application
3
metadata:
4
name: mgmt-cluster
5
namespace: argocd
6
project: default
7
source:
8
repoURL: 'git@gitlab.com:gitops-book/multi-cluster-config.git'
9
path: management/cluster
10
targetRevision: HEAD
11
destination:
12
server: 'https://kubernetes.default.svc'
Es ist zu erkennen, dass die Ressourcen nun durch Argo CD synchronisiert werden, wie in Abb. 10–7 dargestellt.
Abb. 10–7
Management-Cluster wird durch Argo CD synchronisiert
Für unseren Boostrap-Cluster gibt es keine Verwendung mehr. Wir können den Cluster nun abbauen.
In einer heterogenen Unternehmenslandschaft können Cluster unterschiedlicher Art, Größe und Zweck existieren. Ein Entwicklungsteam benötigt typischerweise drei Cluster: Entwicklung, Staging und Produktion. So findet die Entwicklung hauptsächlich im Development-Cluster statt, während Staging zum Testen und für den Preview-Zugang für Stakeholder genutzt wird und Production als Produktionsumgebung dient.
Im vorliegenden Fall liegt exakt der beschriebene Aufbau mit drei Clustern vor.
Abb. 10–8
Ordnerstruktur der Cluster
Im base-Ordner sind alle Grundeinstellungen der Cluster definiert. Der Ordner environments beinhaltet EKS-spezifischen Ausprägungen. Im Falle von EKS wird die Amazon VPC CNI als Add-on installiert26.
Listing 10–26
Manifest für die AWSManagedControlPlane
1
apiVersion: controlplane.cluster.x-k8s.io/v1beta2
2
kind: AWSManagedControlPlane
3
metadata:
4
name: capi-eks-control-plane
5
namespace: default
6
spec:
7
region: us-east-1
8
sshKeyName: capi-eks
endpointAccess:
10
public: false
11
private: true
12
addons:
13
- name: vpc-cni
14
version: v1.13.4-eksbuild.1
15
conflictResolution: overwrite
Schließlich enthält der Ordner overlays die Definition der Umgebungen und die entsprechenden Anpassungen. In dev ist festgelegt, dass Spot-VMs verwendet werden sollen.
Listing 10–27
Manifest für den AWSManagedMachinePool
1
apiVersion: infrastructure.cluster.x-k8s.io/v1beta2
2
kind: AWSManagedMachinePool
3
metadata:
4
name: capi-eks-pool
5
namespace: default
6
spec:
7
capacityType: spot
Diese Struktur sowie weitere Environments wie Docker und EC2 sind in den Beispielen zu finden27.
Die Frage ist, wie die Prinzipien von GitOps angewendet werden können, um Änderungen im Cluster vorzunehmen. Vorab muss Argo CD autorisiert werden, Cluster API-Ressourcen zu verwalten. Hierzu muss ein ClusterRoleBinding angelegt werden, um dem Service-Account von Argo CD die erforderlichen Berechtigungen zuzuordnen. Der Einfachheit halber fügen wir die Rolle cluster-admin dem Service-Account argocd-application-controller hinzu.
Listing 10–28
Zuweisung der Rolle zum Argo CD ServiceAccount
1
apiVersion: rbac.authorization.k8s.io/v1
2
kind: ClusterRoleBinding
3
metadata:
4
name: cluster-admin-argocd-contoller
5
subjects:
6
- kind: ServiceAccount
7
name: argocd-application-controller
8
namespace: default
9
roleRef:
10
apiGroup: rbac.authorization.k8s.io
11
kind: ClusterRole
12
name: cluster-admin
Sowohl der Development-Cluster als auch der Staging-Cluster müssen als Application eingerichtet werden. Hierzu muss zuerst das Repository in Argo CD hinterlegt werden:
Listing 10–29
Anlage des Git-Repositorys als Argo CD Repo
1
argocd repo add \
2
git@gitlab.com:gitops-book/multi-cluster-config.git \
3
--[further Parameter]
Dann muss die jeweilige Umgebung als Application definiert werden:
Listing 10–30
Anlegen der Development-Umgebung als Application-Ressource in Argo CD
1
argocd app create acme-cluster-dev \
2
--project default \
3
--path overlays/dev \
4
--repo git@gitlab.com:gitops-book/multi-cluster-config.git \
5
--sync-policy auto \
6
--dest-namespace default \
7
--dest-server https://kubernetes.default.svc
Listing 10–31
Anlegen der Staging-Umgebung als Application-Ressource in Argo CD
1
argocd app create acme-cluster-staging \
2
--project default \
3
--path overlays/staging \
4
--repo git@gitlab.com:gitops-book/multi-cluster-config.git \
5
--sync-policy auto \
6
--dest-namespace default \
7
--dest-server https://kubernetes.default.svc
Im optimalen Fall sollte der Zustand in Argo CD als Healthy und Synced angezeigt werden, wie in Abb. 10–9 gezeigt.
Wir beabsichtigen, die Kubernetes-Cluster auf Version v1.27.3, sowohl in der Umgebung Entwicklung als auch in der Umgebung Staging, zu aktualisieren. Es ist wichtig, die Control Plane-Version und die Worker-Nodes entsprechend anzupassen. Zusätzlich zur Aktualisierung der Kubernetes-Version muss auch die korrekte CNI-Version für EKS installiert werden. Hierfür muss die Datei /base/control-plane-kubeadm.yaml im Verzeichnis base wie folgt angepasst werden:
Listing 10–32
Upgrade der Control Plane der Workload-Cluster
1
apiVersion: controlplane.cluster.x-k8s.io/v1beta1
2
kind: KubeadmControlPlane
3
metadata:
4
name: capi-control-plane
5
namespace: default
6
spec:
7
version: v1.27.3
Abb. 10–9
Sowohl Dev als auch Staging sind Healthy und Synced.
Für die Worker-Nodes muss eine Änderung in der MachinePool-Ressource in der Datei environments/aws-eks/resources/machine-pool. yamlvorgenommen werden.
Listing 10–33
Upgrade der Worker-Nodes der Workload-Cluster
1
apiVersion: cluster.x-k8s.io/v1beta1
2
kind: MachinePool
3
metadata:
4
name: capi-eks-pool
5
namespace: default
6
spec:
7
clusterName: capi-cluster
8
replicas: 1
9
template:
10
spec:
11
** further configs
12
version: v1.27.3
Für das CNI müssen wir die Ressource environments/aws-eks/patches/aws-managed-control-plane.yaml wie folgt anpassen:
Listing 10–34
Upgrade der EKS CNI
1
apiVersion: controlplane.cluster.x-k8s.io/v1beta2
2
kind: AWSManagedControlPlane
3
metadata:
4
name: capi-eks-control-plane
5
namespace: default
6
spec:
7
** further configs
8
addons:
- name: vpc-cni
10
version: v1.13.4-eksbuild.1
11
conflictResolution: overwrite
Die Änderungen müssen lediglich committet werden, damit die Reconciliation durch Argo CD durchgeführt werden kann. Die Einstellung auto-sync bewirkt den Apply der Änderungen auf dem Management-Cluster und startet somit das Upgrade beider Cluster, wie in Abb. 10–10 und Abb. 10–11 dargestellt.
Abb. 10–10
Apply auf Dev
Abb. 10–11
Upgrade des Dev-Clusters auf AWS
Nach dem Upgrade sollte die EKS-Version bei beiden Clustern jeweils in der Zielversion vorhanden sein, wie in Abb. 10–12 und Abb. 10–13 dargestellt.
Abb. 10–12
Der Stand nach dem Upgrade der Cluster
Abb. 10–13
Der Stand der Node Group nach dem Upgrade der Cluster
Zudem muss die AWS Node Group ebenfalls aktualisiert werden und in der gleichen Version vorliegen. Zur Überprüfung kann nochmals das clusterctl-Kommando abgesetzt werden.
Listing 10–35
Kontrolle nach Upgrade mit clusterctl
1
clusterctl describe cluster dev-capi-cluster
2
NAME READY SEVERITY REASON SINCE MESSAGE
3
Cluster/dev-capi-cluster True 34m
4
|-- ClusterInfrastructure - AWSManagedCluster
5
/dev-capi-cluster
6
|-- ControlPlane - AWSManagedControlPlane
7
/dev-capi-eks-control-plane True 34m
8
|-- Workers
9
|-- MachinePool/dev-capi-eks-pool True 26m
GitOps mit der Cluster API funktioniert einwandfrei. In diesem Beispiel konnten wir zeigen, dass die Verwaltung einer komplexen Cluster-Umgebung sicher und mit geringem Aufwand umsetzbar ist.