DevOps, Cloud, Backend

Self-Managed ArgoCD Konfiguration (Multicluster / Multistage)

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.

Raspberry Pi Cluster
Raspberry Pi Cluster

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.

Motivation

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:

  1. Einheitliches Infrastruktur-Setup: Alle Cluster benötigen oft dieselben grundlegenden Komponenten, wie Monitoring, Logging, oder standardisierte Namespaces. Diese Konsistenz zu erreichen, ohne jeden Cluster separat zu konfigurieren, ist essenziell.
  2. Zentrale Verwaltung: Die Verwaltung von Clustern und ihrer Infrastruktur sollte möglichst einfach und zentralisiert sein, um den administrativen Aufwand zu minimieren. Niemand will manuell Konfigurationsdateien für jedes Cluster aktualisieren müssen.
  3. Staging-Konzept für Anwendungen: Anwendungen durchlaufen typischerweise mehrere Stages, wie Development, Testing und Production. Dies wird oft über Namespaces (oder physisch getrennte Cluster) realisiert, die flexibel und leicht anpassbar sein sollten.
  4. Erweiterbarkeit und Teamunabhängigkeit: Jedes Team soll autonom arbeiten können. Das bedeutet, dass sie ihre eigenen Repositories verwalten und mit granularen Berechtigungen ausstatten können. Gleichzeitig muss das System so flexibel sein, dass neue Cluster oder Anwendungen nahtlos integriert werden können.

Mit diesem Setup schaffen wir eine Grundlage, die sowohl für kleine Projekte als auch für größere, teamübergreifende Workflows geeignet ist.

Ziel-Architektur (vereinfacht)

Ich habe zunächst mit nur einem Cluster gearbeitet. Die Zielarchitektur meines Setups soll eine klare Struktur und Trennung der Verantwortlichkeiten bieten:

  1. Cluster Stages: Jedes Kubernetes-Cluster wird in vier Stages (Namespaces) unterteilt:
    • dev: Enthält sowohl Entwickler-Anwendungen als auch Infrastruktur-Apps für Tests und Entwicklung.
    • qa: Für Qualitätssicherung, inklusive Entwickler-Anwendungen und Infrastruktur-Apps.
    • prod: Produktionsumgebung, die stabil und sicher laufen muss – ebenfalls mit Entwickler- und Infrastruktur-Apps.
    • mngt: Eine dedizierte Stage ausschließlich für Infrastruktur-Anwendungen, die zentral verwaltet werden.
  2. Deployment von ArgoCD:
    ArgoCD selbst wird nur auf einem der Cluster – im Namespace default – deployt. Von dort aus verwaltet es alle anderen Cluster und deren Stages.
  3. Anwendungstypen und Verantwortlichkeiten:
    Wir unterscheiden zwischen zwei Arten von Anwendungen:
    • Infrastruktur-Apps: Diese werden von den Administratoren gepflegt und sind auf allen Clustern identisch konfiguriert. Dazu gehören beispielsweise Logging, Monitoring oder Security-Tools.
    • Developer-Apps: Diese werden von den jeweiligen Teams oder Nutzern gepflegt und variieren je nach Projekt und Stage.

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.

ArgoCDs Apps-of-Apps Konzept

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:

  • Zentrale Verwaltung: Alle Konfigurationen sind an einem Ort gebündelt.
  • Modularität: Einzelne Anwendungen oder Cluster können unabhängig verwaltet und aktualisiert werden.
  • Skalierbarkeit: Ideal für große Umgebungen mit vielen Teams oder Stages.

Dieses Konzept ist der Schlüssel zu einem sauberen und leicht erweiterbaren Setup.

Beispiel anhand der Zielarchitektur mit Clustern und Staging

Setup Applikationen

Folgende “Applikationen” sollen in unserem Beispiel verwaltet werden:

Infrastruktur-Apps

  • Namespace: default
    • ArgoCD: Zentrales Management-Tool für GitOps.
  • Namespaces: mngt, dev, qa, prod
    • Beinhalten die Namespacedefinitionen selbst

Developer-Apps

  • hello-world
    • dev: Mit Tomcat 9
    • qa: Mit Tomcat 8
    • prod: Mit Tomcat 7
  • nginx
    • dev: Version 1.27
    • qa: Version 1.26

prod: Version 1.25

Manuelle Installation von ArgoCD

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.

  • Herunterladen der Helm Charts
    Lade die neuesten Helm Charts für ArgoCD (Version 7.7.6) herunter und speichere sie unter dem Verzeichnis:

    /infrastructure-apps/argocd

    Die Charts kannst du direkt von Artifact Hub beziehen.

    Vorbereitung der Abhängigkeiten
    Falls in der Chart Redis-HA oder andere Abhängigkeiten verwendet werden, führe vor der Installation folgendes aus:

    helm dependency build .

    Hinweis: Überprüfe die .helmignore-Datei im Chart-Ordner. Falls darin .tgz-Dateien ignoriert werden, entferne diesen Eintrag, da sonst Abhängigkeiten nicht korrekt geladen werden.
  • Installation von ArgoCD
    Navigiere in das Verzeichnis, in dem sich die Helm Charts befinden:

    cd /infrastructure-apps/argocd

    Installiere ArgoCD im Namespace default mit:

    helm install argocd .
  • Validierung der Installation
    Nach der Installation kannst du prüfen, ob alle Pods im Namespace default laufen:

    kubectl get pods

Mit diesen Schritten sollte ArgoCD einsatzbereit sein, bereit für die zentrale Verwaltung deiner Anwendungen und Cluster.

Setup Gitea

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:

  • Vorbereitung: SSH-Key-Paar erzeugen
    Zunächst habe ich ein SSH-Schlüsselpaar erstellt, das für die Verbindung von ArgoCD zu Gitea verwendet wird:

    ssh-keygen -t rsa -b 4096 -C "svenguthe@gmail.com"

    Dies erzeugt ein privates und ein öffentliches Schlüsselpaar. Der private Schlüssel wird für die Verbindung von ArgoCD zu Gitea benötigt, während der öffentliche Schlüssel zu Gitea hinzugefügt wird.
  • Public Key zu Gitea hinzufügen
    Der erzeugte öffentliche Schlüssel muss nun zu dem Gitea-Account hinzugefügt werden.
  • Repositories in Gitea hinzufügen
    Nun müssen die Repositories, die von ArgoCD verwaltet werden sollen, erstellt oder bereits vorhanden sein. In diesem Beispiel verwenden wir die Repositories argocd, hello-world und nginx, die du zu Gitea hinzufügen solltest.
  • Verbindung ArgoCD -> Gitea Repositories herstellen
    Jetzt stellen wir die Verbindung zwischen ArgoCD und den Gitea-Repositories her. Dazu muss die Liste der „Known Hosts“ erweitert und sichergestellt werden, dass ArgoCD die Verbindung zu dem Gitea-Server herstellen kann.

    Bekannte Hosts hinzufügen:
    Folgenden Befehl habe ich ausgeführt, um den öffentlichen SSH-Schlüssel von Gitea zu scannen und die Host-Informationen zu erhalten:

    ssh-keyscan -t rsa 192.168.2.181

    Der Output sollte ungefähr so aussehen:

    192.168.2.181 ssh-rsa AAAAB3NzaC1yc2EAAxxxx………

    Füge diesen Schlüssel in die values.yaml von ArgoCD unter dem Abschnitt knownHosts ein. So sieht der Eintrag aus:

    knownHosts: |
    192.168.2.181 ssh-rsa AAAAB3NzaC1yc2EAAxxxx……
  • SSH Private Key in ArgoCD einfügen
    Nun muss man ArgoCD den privaten SSH-Schlüssel zur Verfügung stellen, um eine Verbindung zu den Gitea-Repositories herzustellen. Dafür können wir folgende Secret-Ressource nutzen:
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-----
  • Ersetze 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.

    Anschließend sehen wir die Repositories in ArgoCD:

Damit ist die Verbindung zwischen ArgoCD und Gitea hergestellt, und ArgoCD kann nun die Konfiguration aus den privaten Repositories abrufen und deployen.

Self-Managed Argo CD

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.

  • Helm Chart für das Self-Managed Setup erstellen
    Im Verzeichnis /cluster-setup wird eine neue Helm-Applikation erstellt. Diese dient als Basis für die Verwaltung des Self-Managed ArgoCD.
  • Chart.yaml
    Definiere das 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
  • Template-Dateien erstellen
    root.yaml
    Die 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
  • Template-Dateien erstellen
    argocd.yaml
    Die 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
  • Installation des Root-Charts
    Um das Self-Managed Setup zu aktivieren, installiere die Helm-Applikation:

    cd /cluster-setup
    helm install root .
  •  Funktion prüfen
    Nach der Installation sollte ArgoCD in der Lage sein, seine eigene Konfiguration zu synchronisieren und Änderungen selbstständig zu übernehmen. Überprüfe, ob die Anwendungen korrekt erstellt wurden:

    kubectl get applications

    Mit diesem Self-Managed Setup wird dein ArgoCD-Cluster nicht nur zum zentralen Verwaltungswerkzeug, sondern auch zu einer eigenständig wartbaren Infrastruktur-Komponente.

Infrastruktur App und Developer App

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

Wie funktioniert das Ganze?

  1. Cluster-Setup: Durch die 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.
  2. Team-Projekte: In den jeweiligen 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:

  • hello-world (Tomcat):
    • dev: Tomcat 9
    • qa: Tomcat 8
    • prod: Tomcat 7
  • Nginx:
    • dev: Nginx 1.27
    • qa: Nginx 1.26
    • prod: Nginx 1.25

Beispiel 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

Fazit

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

0 Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert