Veröffentlicht am 2. Dezember 2024
Hinweis Setup Raspberry Pis: In meinem vorherigen Artikel habe ich gezeigt, wie ich mein Kubernetes-Cluster auf einem Raspberry Pi-Setup eingerichtet habe. Falls du diesen Beitrag noch nicht kennst, kannst du ihn hier nachlesen. Darin erkläre ich die grundlegenden Schritte, um ein stabiles und performantes Cluster aufzubauen, das sich perfekt für DevOps-Experimente und kleine Projekte eignet.
In diesem Beitrag geht es einen Schritt weiter: Ich zeige dir, wie ich mit ArgoCD ein Multi-Cluster- und Multi-Stage-Setup aufgebaut habe, um die Anwendungen sauber und effizient zu verwalten. Am Ende kann ArgoCD sich selbst verwalten (Self-Managed ArgoCD) und sogar selbst updaten.
Warum dieses Setup? In der Praxis benötigt man für viele Projekte mehr als nur ein einziges Kubernetes-Cluster. Sei es, um Entwicklungs-, Test- und Produktionsumgebungen sauber zu trennen, oder um mehrere Teams parallel arbeiten zu lassen. Aber genau hier beginnen die Herausforderungen:
Mit diesem Setup schaffen wir eine Grundlage, die sowohl für kleine Projekte als auch für größere, teamübergreifende Workflows geeignet ist.
Ich habe zunächst mit nur einem Cluster gearbeitet. Die Zielarchitektur meines Setups soll eine klare Struktur und Trennung der Verantwortlichkeiten bieten:
Mit dieser Architektur schaffen wir ein konsistentes und skalierbares Setup, das sowohl den Betrieb von Infrastruktur-Anwendungen als auch die Entwicklung und Bereitstellung von Entwickler-Anwendungen unterstützt.
Das App-of-Apps-Konzept in ArgoCD ermöglicht es, komplexe Multi-Cluster- oder Multi-Stage-Setups effizient zu verwalten. Statt jede Anwendung einzeln zu definieren, wird eine „Hauptanwendung“ erstellt, die als Übersicht dient. Diese verweist auf weitere ArgoCD-Anwendungen, die spezifische Ressourcen, Stages oder Cluster steuern.
Vorteile:
Dieses Konzept ist der Schlüssel zu einem sauberen und leicht erweiterbaren Setup.
Folgende “Applikationen” sollen in unserem Beispiel verwaltet werden:
prod: Version 1.25
Bevor ArgoCD sich selbst managen kann, muss ArgoCD zunächst manuell installiert werden.
Um ArgoCD in deinem Kubernetes-Cluster zu installieren, folgen wir einem simplen und nachvollziehbaren Prozess mithilfe von Helm Charts.
/infrastructure-apps/argocd
helm dependency build .
.helmignore
-Datei im Chart-Ordner. Falls darin .tgz
-Dateien ignoriert werden, entferne diesen Eintrag, da sonst Abhängigkeiten nicht korrekt geladen werden.cd /infrastructure-apps/argocd
helm install argocd .
kubectl get pods
Mit diesen Schritten sollte ArgoCD einsatzbereit sein, bereit für die zentrale Verwaltung deiner Anwendungen und Cluster.
Um ArgoCD mit Gitea zu verbinden und Applikationen aus privaten Repositories zu synchronisieren, müssen wir ein paar Schritte durchführen. Das Ziel ist, ArgoCD über SSH mit einem Gitea-Account zu verbinden und Repositories hinzuzufügen. Hier sind die Schritte, die du befolgen musst:
ssh-keygen -t rsa -b 4096 -C "svenguthe@gmail.com"
ssh-keyscan -t rsa 192.168.2.181
192.168.2.181 ssh-rsa AAAAB3NzaC1yc2EAAxxxx………
values.yaml
von ArgoCD unter dem Abschnitt knownHosts
ein. So sieht der Eintrag aus:knownHosts: |
192.168.2.181 ssh-rsa AAAAB3NzaC1yc2EAAxxxx……
apiVersion: v1
kind: Secret
metadata:
name: repo-hello-world-secret
namespace: default
annotations:
managed-by: argocd.argoproj.io
labels:
argocd.argoproj.io/secret-type: repository
stringData:
type: git
url: 'git@192.168.2.181:svenguthe/hello-world.git'
sshPrivateKey: |
-----BEGIN OPENSSH PRIVATE KEY-----
(Dein private Schlüssel hier)
-----END OPENSSH PRIVATE KEY-----
repo-hello-world-secret
und die URL mit den entsprechenden Informationen für dein Repository und den privaten Schlüssel. Wiederhole diesen Schritt für jedes Repository, das du hinzufügen möchtest.Damit ist die Verbindung zwischen ArgoCD und Gitea hergestellt, und ArgoCD kann nun die Konfiguration aus den privaten Repositories abrufen und deployen.
Um ArgoCD vollständig self-managed zu betreiben, richten wir es so ein, dass auch die eigene Konfiguration und zukünftige Updates durch ArgoCD selbst verwaltet werden. Der Schlüssel dazu ist eine „root“-Application, die das gesamte Setup orchestriert.
/cluster-setup
wird eine neue Helm-Applikation erstellt. Diese dient als Basis für die Verwaltung des Self-Managed ArgoCD.Chart.yaml
, um die grundlegenden Informationen und Abhängigkeiten des Charts festzulegen:apiVersion: v2
name: argocd-cluster-setup
description: Self-Managed ArgoCD Setup
version: 1.0.0
root.yaml
root.yaml
-Datei definiert die Haupt-Applikation („root
„), die alle weiteren Anwendungen steuert:apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: root
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: 'git@192.168.2.181:svenguthe/argocd.git'
path: cluster-setup/
targetRevision: HEAD
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
selfHeal: true
argocd.yaml
argocd.yaml
-Datei definiert die ArgoCD-Applikation selbst und sorgt dafür, dass Änderungen an ArgoCD direkt von ArgoCD verwaltet werden:apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: 'git@192.168.2.181:svenguthe/argocd.git'
path: infrastructure-apps/argocd
targetRevision: HEAD
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
selfHeal: true
cd /cluster-setup
helm install root .
kubectl get applications
Als nächstes habe ich die Infrastruktur- und Developerapps installiert. Dafür wird eine Datei /cluster-setup/cluster-setup.yaml
erzeugt. Die Datei cluster-setup.yaml
sorgt dafür, dass alle Cluster im Setup eine gleiche Infrastrukturkonfiguration erhalten und gleichzeitig team-spezifische Projekte und Applikationen installiert werden. Sie nutzt dabei das ArgoCD ApplicationSet, um die Applikationen automatisch auf verschiedenen Clustern zu verteilen.
Aufbau der Datei cluster-setup.yaml
:
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: cluster-setup
spec:
goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators:
- list:
elements:
- cluster: cluster1
url: https://kubernetes.default.svc
- cluster: cluster2
url: cluster2-url
- cluster: cluster3
url: cluster3-url
template:
metadata:
name: '{{`{{.cluster}}`}}-cluster-setup'
spec:
project: default
sources:
- repoURL: 'git@192.168.2.181:svenguthe/argocd.git'
path: clusters/{{`{{.cluster}}`}}
targetRevision: HEAD
directory:
recurse: true
- repoURL: 'git@192.168.2.181:svenguthe/argocd.git'
path: infrastructure-apps/overlays
targetRevision: HEAD
directory:
recurse: true
destination:
server: '{{`{{.url}}`}}'
namespace: default
Hinweis: In infrastructure-apps/overlays
befinden sich noch einmal dev/
, qa/
, prod/
und mngt/
Ordner, welche die Definitionen der Namespaces enhalten. Durch das recures: true
stellen wir sicher, dass alle Unterordner nach Ressourcen durchsucht und erfasst werden.
Im Ordner /clusters
werden für die verschiedenen Cluster die jeweiligen Team-Projekte definiert.
team1.yaml:
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: team1
namespace: default
spec:
sourceRepos:
- git@192.168.2.181:svenguthe/hello-world.git
sourceNamespaces:
- dev
- qa
- prod
destinations:
- namespace: dev
server: https://kubernetes.default.svc
- namespace: qa
server: https://kubernetes.default.svc
- namespace: prod
server: https://kubernetes.default.svc
clusterResourceWhitelist:
- group: '*'
kind: '*'
namespaceResourceWhitelist:
- group: '*'
kind: '*'
---
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: hello-world-application-set
spec:
goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators:
- list:
elements:
- namespace: dev
- namespace: qa
- namespace: prod
template:
metadata:
name: '{{.namespace}}-hello-world'
spec:
project: team1
source:
repoURL: 'git@192.168.2.181:svenguthe/hello-world.git'
path: deployment
targetRevision: '{{.namespace}}'
destination:
server: https://kubernetes.default.svc
namespace: '{{.namespace}}'
syncPolicy:
automated:
prune: true
selfHeal: true
team2.yaml:
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: team2
namespace: default
spec:
sourceRepos:
- git@192.168.2.181:svenguthe/nginx.git
sourceNamespaces:
- dev
- qa
- prod
destinations:
- namespace: dev
server: https://kubernetes.default.svc
- namespace: qa
server: https://kubernetes.default.svc
- namespace: prod
server: https://kubernetes.default.svc
clusterResourceWhitelist:
- group: '*'
kind: '*'
namespaceResourceWhitelist:
- group: '*'
kind: '*'
---
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: nginx-application-set
spec:
goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators:
- list:
elements:
- namespace: dev
- namespace: qa
- namespace: prod
template:
metadata:
name: '{{.namespace}}-nginx'
spec:
project: team2
source:
repoURL: 'git@192.168.2.181:svenguthe/nginx.git'
path: deployment
targetRevision: '{{.namespace}}'
destination:
server: https://kubernetes.default.svc
namespace: '{{.namespace}}'
syncPolicy:
automated:
prune: true
selfHeal: true
cluster-setup.yaml
-Datei wird sichergestellt, dass für jedes Cluster ein einheitliches Setup bereitgestellt wird. Dazu gehört die Konfiguration der Infrastruktur und der spezifischen Team-Projekte und Apps.team1.yaml
und team2.yaml
-Dateien wird definiert, wie die Applikationen für die Teams team1
und team2
bereitgestellt werden, und zwar auf den verschiedenen Umgebungen (dev
, qa
, prod
). Es wird jeweils im Repository und in den jeweiligen Branches nach einem Ordner /deployment
gesucht, in welchem die Files liegen, welche je nach Stage deployt werden sollen.Jeder Branch (z.B. dev
, qa
, prod
) hat seine eigene Konfiguration, die die jeweilige Umgebung abbildet. Diese Konfigurationen können je nach Zielumgebung (z.B. dev
, qa
, prod
) unterschiedliche Versionen verwenden.
Beispiel für Versionen pro Branch:
dev
: Tomcat 9qa
: Tomcat 8prod
: Tomcat 7dev
: Nginx 1.27qa
: Nginx 1.26prod
: Nginx 1.25Beispiel für ein Tomcat Deployment (HelloWorld Applikation)
apiVersion: v1
kind: ConfigMap
metadata:
name: app-bundle
data:
sample.war: |
(binäre WAR-Datei, Base64-kodiert)
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: tomcat
labels:
app: tomcat
spec:
replicas: 3
selector:
matchLabels:
app: tomcat
template:
metadata:
labels:
app: tomcat
spec:
containers:
- name: tomcat
image: tomcat:9 # Kann je nach Branch angepasst werden (z.B. 9, 8, 7)
ports:
- containerPort: 8080
volumeMounts:
- name: app-volume
mountPath: /usr/local/tomcat/webapps/
volumes:
- name: app-volume
configMap:
name: app-bundle
---
apiVersion: v1
kind: Service
metadata:
name: tomcat
labels:
app: tomcat
spec:
ports:
- port: 80
name: http
targetPort: 8080
selector:
app: tomcat
type: LoadBalancer
Beispiel für ein Nginx Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx-container
image: nginx:1.27 # Kann je nach Branch angepasst werden (z.B. 1.27, 1.26, 1.25)
ports:
- containerPort: 80
In ArgoCD können die Teams im Cluster1 die Applikationen auf den Stages dev
, qa
und prod
sehen und überwachen. Jede Umgebung hat ihre eigene, auf den jeweiligen Bedarf abgestimmte Version, wie beispielsweise Tomcat 9
für dev
, Tomcat 8
für qa
und Tomcat 7
für prod
, sowie die entsprechenden Nginx-Versionen, die in den jeweiligen Stages genutzt werden.
Außerdem sehen wir die Infrastruktur “Apps”, welche aktuell nur aus den Namespacedefinitionen “dev
”, “prod
” und “qa
” bestehen.
In dem Bild wurden außerdem weitere Ressourcen manuell ausgeblendet, um das Bild leserlich zu halten:
Check Image-Versionen auf den entsprechenden Namespaces:
$ kubectl get deployment -o wide --all-namespaces | grep nginx
dev nginx-deployment 3/3 3 3 113m nginx-container nginx:1.27 app=nginx
prod nginx-deployment 3/3 3 3 113m nginx-container nginx:1.25 app=nginx
qa nginx-deployment 3/3 3 3 112m nginx-container nginx:1.26 app=nginx
$ kubectl get deployment -o wide --all-namespaces | grep tomcat
dev tomcat 3/3 3 3 73m tomcat tomcat:9 app=tomcat
prod tomcat 3/3 2 3 73m tomcat tomcat:7 app=tomcat
qa tomcat 3/3 3 3 73m tomcat tomcat:8 app=tomcat