Bereitstellen von Anwendungen in mehreren Kubernetes-Clustern mit Helm

Wie Dailymotion Kubernetes nutzt: Anwendungsbereitstellung

Wir bei Dailymotion haben vor drei Jahren damit begonnen, Kubernetes in der Produktion einzusetzen. Aber die Bereitstellung von Anwendungen über mehrere Cluster hinweg macht Spaß. Deshalb haben wir in den letzten Jahren versucht, unsere Tools und Arbeitsabläufe zu verbessern.

Wo hat es angefangen?

Hier erfahren Sie, wie wir unsere Anwendungen in mehreren Kubernetes-Clustern auf der ganzen Welt bereitstellen.

Um mehrere Kubernetes-Objekte gleichzeitig bereitzustellen, verwenden wir Helm, und alle unsere Diagramme werden in einem Git-Repository gespeichert. Um einen vollständigen Anwendungsstapel aus mehreren Diensten bereitzustellen, verwenden wir das sogenannte Zusammenfassungsdiagramm. Im Wesentlichen handelt es sich dabei um ein Diagramm, das Abhängigkeiten deklariert und es Ihnen ermöglicht, die API und ihre Dienste mit einem Befehl zu initialisieren.

Wir haben außerdem ein kleines Python-Skript auf Helm geschrieben, um Prüfungen durchzuführen, Diagramme zu erstellen, Geheimnisse hinzuzufügen und Anwendungen bereitzustellen. Alle diese Aufgaben werden auf einer zentralen CI-Plattform mithilfe eines Docker-Images ausgeführt.

Kommen wir zum Punkt.

Notiz. Während Sie dies lesen, wurde bereits der erste Release Candidate für Helm 3 angekündigt. Die Hauptversion enthält eine ganze Reihe von Verbesserungen, um einige der Probleme zu beheben, auf die wir in der Vergangenheit gestoßen sind.

Workflow für die Diagrammentwicklung

Wir verwenden Verzweigungen für Anwendungen und haben uns entschieden, den gleichen Ansatz auf Diagramme anzuwenden.

  • Zweig dev Wird zum Erstellen von Diagrammen verwendet, die auf Entwicklungsclustern getestet werden.
  • Wenn eine Pull-Anfrage gesendet wird Master, werden sie im Staging geprüft.
  • Abschließend erstellen wir eine Pull-Anfrage, um die Änderungen an den Zweig zu übertragen Stoß und diese in der Produktion anzuwenden.

Jede Umgebung verfügt über ein eigenes privates Repository, in dem unsere Diagramme gespeichert sind und die wir verwenden Kartenmuseum mit sehr nützlichen APIs. Auf diese Weise stellen wir eine strikte Isolierung zwischen den Umgebungen sicher und testen die Diagramme in der Praxis, bevor wir sie in der Produktion verwenden.

Diagramm-Repositorys in verschiedenen Umgebungen

Es ist erwähnenswert, dass, wenn Entwickler einen Entwicklungszweig übertragen, automatisch eine Version ihres Diagramms an das Entwicklungs-Chartmuseum übertragen wird. Daher verwenden alle Entwickler dasselbe Entwicklungs-Repository, und Sie müssen Ihre Version des Diagramms sorgfältig angeben, um nicht versehentlich die Änderungen anderer zu verwenden.

Darüber hinaus validiert unser kleines Python-Skript Kubernetes-Objekte anhand der Kubernetes OpenAPI-Spezifikationen Kubeval, bevor sie auf Chartmusem veröffentlicht wurden.

Allgemeine Beschreibung des Diagrammentwicklungs-Workflows

  1. Einrichten von Pipeline-Aufgaben gemäß Spezifikation gazr.io zur Qualitätskontrolle (Lint, Unit-Test).
  2. Pushen eines Docker-Images mit Python-Tools, die unsere Anwendungen bereitstellen.
  3. Einrichten der Umgebung nach Filialnamen.
  4. Validierung von Kubernetes-YAML-Dateien mit Kubeval.
  5. Erhöhen Sie automatisch die Version eines Diagramms und seiner übergeordneten Diagramme (Diagramme, die vom geänderten Diagramm abhängen).
  6. Einreichen einer Karte an ein Kartenmuseum, die zu seiner Umgebung passt

Verwalten von Unterschieden zwischen Clustern

Föderation der Cluster

Es gab eine Zeit, in der wir es benutzten Föderation von Kubernetes-Clustern, wobei Kubernetes-Objekte von einem einzelnen API-Endpunkt aus deklariert werden könnten. Doch es traten Probleme auf. Beispielsweise konnten einige Kubernetes-Objekte nicht im Verbundendpunkt erstellt werden, was die Verwaltung von Verbundobjekten und anderen Objekten für einzelne Cluster erschwert.

Um das Problem zu lösen, begannen wir, die Cluster unabhängig zu verwalten, was den Prozess erheblich vereinfachte (wir verwendeten die erste Version der Föderation; in der zweiten hätte sich möglicherweise etwas geändert).

Geoverteilte Plattform

Unsere Plattform ist derzeit auf 6 Regionen verteilt – 3 lokal und 3 in der Cloud.


Verteilte Bereitstellung

Globale Helm-Werte

Mit 4 globalen Helm-Werten können Sie Unterschiede zwischen Clustern identifizieren. Alle unsere Diagramme haben standardmäßige Mindestwerte.

global:
  cloud: True
  env: staging
  region: us-central1
  clusterName: staging-us-central1

Globale Werte

Diese Werte helfen bei der Definition des Kontexts für unsere Anwendungen und werden für verschiedene Zwecke verwendet: Überwachung, Nachverfolgung, Protokollierung, Tätigung externer Aufrufe, Skalierung usw.

  • „cloud“: Wir haben eine hybride Kubernetes-Plattform. Unsere API wird beispielsweise in GCP-Zonen und in unseren Rechenzentren bereitgestellt.
  • „env“: Einige Werte können sich für Nicht-Produktionsumgebungen ändern. Zum Beispiel Ressourcendefinitionen und Autoscaling-Konfigurationen.
  • „Region“: Diese Informationen helfen bei der Bestimmung des Standorts des Clusters und können zur Bestimmung nahegelegener Endpunkte für externe Dienste verwendet werden.
  • „clusterName“: ob und wann wir einen Wert für einen einzelnen Cluster definieren möchten.

Hier ist ein konkretes Beispiel:

{{/* Returns Horizontal Pod Autoscaler replicas for GraphQL*/}}
{{- define "graphql.hpaReplicas" -}}
{{- if eq .Values.global.env "prod" }}
{{- if eq .Values.global.region "europe-west1" }}
minReplicas: 40
{{- else }}
minReplicas: 150
{{- end }}
maxReplicas: 1400
{{- else }}
minReplicas: 4
maxReplicas: 20
{{- end }}
{{- end -}}

Beispiel für eine Helmvorlage

Diese Logik ist in einer Hilfsvorlage definiert, um Kubernetes YAML nicht zu überladen.

Bewerbungsankündigung

Unsere Bereitstellungstools basieren auf mehreren YAML-Dateien. Nachfolgend finden Sie ein Beispiel dafür, wie wir einen Dienst und seine Skalierungstopologie (Anzahl der Replikate) in einem Cluster deklarieren.

releases:
  - foo.world

foo.world:                # Release name
  services:               # List of dailymotion's apps/projects
    foobar:
      chart_name: foo-foobar
      repo: [email protected]:dailymotion/foobar
      contexts:
        prod-europe-west1:
          deployments:
            - name: foo-bar-baz
              replicas: 18
            - name: another-deployment
              replicas: 3

Dienstdefinition

Dies ist eine Übersicht aller Schritte, die unseren Bereitstellungsworkflow definieren. Im letzten Schritt wird die Anwendung gleichzeitig auf mehreren Worker-Clustern bereitgestellt.


Jenkins-Bereitstellungsschritte

Was ist mit Geheimnissen?

Aus Sicherheitsgründen verfolgen wir alle Geheimnisse von verschiedenen Orten und bewahren sie in einem einzigartigen Tresor auf Gewölbe in Paris.

Unsere Bereitstellungstools extrahieren geheime Werte aus Vault und fügen sie zum Zeitpunkt der Bereitstellung in Helm ein.

Dazu haben wir eine Zuordnung zwischen den Geheimnissen in Vault und den Geheimnissen definiert, die unsere Anwendungen benötigen:

secrets:                                                                                                                                                                                                        
     - secret_id: "stack1-app1-password"                                                                                                                                                                                  
       contexts:                                                                                                                                                                                                   
         - name: "default"                                                                                                                                                                                         
           vaultPath: "/kv/dev/stack1/app1/test"                                                                                                                                                               
           vaultKey: "password"                                                                                                                                                                                    
         - name: "cluster1"                                                                                                                                                                           
           vaultPath: "/kv/dev/stack1/app1/test"                                                                                                                                                               
           vaultKey: "password"

  • Wir haben allgemeine Regeln definiert, die beim Aufzeichnen von Geheimnissen in Vault befolgt werden müssen.
  • Wenn das Geheimnis gilt auf einen bestimmten Kontext oder Cluster beziehen, müssen Sie einen bestimmten Eintrag hinzufügen. (Hier hat der Kontext Cluster1 einen eigenen Wert für das geheime Stack-App1-Passwort).
  • Ansonsten wird der Wert verwendet Default.
  • Für jedes Element in dieser Liste in Kubernetes-Geheimnis Es wird ein Schlüssel-Wert-Paar eingefügt. Daher ist die geheime Vorlage in unseren Diagrammen sehr einfach.

apiVersion: v1
data:
{{- range $key,$value := .Values.secrets }}
  {{ $key }}: {{ $value | b64enc | quote }}
{{ end }}
kind: Secret
metadata:
  name: "{{ .Chart.Name }}"
  labels:
    chartVersion: "{{ .Chart.Version }}"
    tillerVersion: "{{ .Capabilities.TillerVersion.SemVer }}"
type: Opaque

Herausforderungen und Grenzen

Arbeiten mit mehreren Repositorys

Jetzt trennen wir die Entwicklung von Diagrammen und Anwendungen. Das bedeutet, dass Entwickler in zwei Git-Repositorys arbeiten müssen: eines für die Anwendung und eines für die Definition ihrer Bereitstellung in Kubernetes. 2 Git-Repositories bedeuten 2 Arbeitsabläufe und es ist für einen Neuling leicht, verwirrt zu werden.

Die Verwaltung verallgemeinerter Diagramme ist mühsam

Wie bereits erwähnt, sind generische Diagramme sehr nützlich, um Abhängigkeiten zu identifizieren und schnell mehrere Anwendungen bereitzustellen. Aber wir nutzen --reuse-valuesum zu vermeiden, dass jedes Mal, wenn wir eine Anwendung bereitstellen, die Teil dieses allgemeinen Diagramms ist, alle Werte übergeben werden.

In einem Continuous-Delivery-Workflow haben wir nur zwei Werte, die sich regelmäßig ändern: die Anzahl der Replikate und das Image-Tag (Version). Andere, stabilere Werte werden manuell geändert, was recht schwierig ist. Darüber hinaus kann ein Fehler bei der Bereitstellung eines generalisierten Diagramms zu schwerwiegenden Fehlern führen, wie wir aus eigener Erfahrung gesehen haben.

Mehrere Konfigurationsdateien aktualisieren

Wenn ein Entwickler eine neue Anwendung hinzufügt, muss er mehrere Dateien ändern: die Anwendungsdeklaration, die Liste der Geheimnisse und das Hinzufügen der Anwendung als Abhängigkeit, wenn sie im generalisierten Diagramm enthalten ist.

Jenkins-Berechtigungen sind in Vault zu weitreichend

Jetzt haben wir eins AppRole, das alle Geheimnisse aus dem Tresor liest.

Der Rollback-Prozess ist nicht automatisiert

Für ein Rollback müssen Sie den Befehl auf mehreren Clustern ausführen, was mit Fehlern behaftet ist. Wir führen diesen Vorgang manuell durch, um sicherzustellen, dass die richtige Versions-ID angegeben wird.

Wir bewegen uns in Richtung GitOps

Unser Ziel

Wir möchten das Diagramm an das Repository der von ihm bereitgestellten Anwendung zurückgeben.

Der Arbeitsablauf ist derselbe wie für die Entwicklung. Wenn beispielsweise ein Branch an den Master gepusht wird, wird die Bereitstellung automatisch ausgelöst. Der Hauptunterschied zwischen diesem Ansatz und dem aktuellen Workflow wäre dieser Alles wird in Git verwaltet (die Anwendung selbst und die Art und Weise, wie sie in Kubernetes bereitgestellt wird).

Es gibt mehrere Vorteile:

  • Viel klarer für den Entwickler. Es ist einfacher zu lernen, wie Sie Änderungen in einem lokalen Diagramm anwenden.
  • Die Servicebereitstellungsdefinition kann angegeben werden Gleiche Stelle wie der Code Service.
  • Verwaltung der Entfernung verallgemeinerter Diagramme. Der Dienst wird über eine eigene Helm-Version verfügen. Dadurch können Sie den Anwendungslebenszyklus (Rollback, Upgrade) auf der kleinsten Ebene verwalten, sodass andere Dienste nicht beeinträchtigt werden.
  • Vorteile von Git für die Diagrammverwaltung: Änderungen rückgängig machen, Protokoll prüfen usw. Wenn Sie eine Änderung an einem Diagramm rückgängig machen müssen, können Sie dies mit Git tun. Die Bereitstellung startet automatisch.
  • Sie könnten erwägen, Ihren Entwicklungsworkflow mit Tools wie zu verbessern Gerüst, mit dem Entwickler Änderungen produktionsnah testen können.

Zweistufige Migration

Unsere Entwickler verwenden diesen Workflow nun schon seit 2 Jahren, daher möchten wir, dass die Migration so reibungslos wie möglich verläuft. Deshalb haben wir uns entschieden, auf dem Weg zum Ziel einen Zwischenschritt einzubauen.
Der erste Schritt ist einfach:

  • Wir behalten eine ähnliche Struktur zum Einrichten der Anwendungsbereitstellung bei, jedoch in einem einzigen Objekt namens DailymotionRelease.

apiVersion: "v1"
kind: "DailymotionRelease"
metadata:
  name: "app1.ns1"
  environment: "dev"
  branch: "mybranch"
spec:
  slack_channel: "#admin"
  chart_name: "app1"
  scaling:
    - context: "dev-us-central1-0"
      replicas:
        - name: "hermes"
          count: 2
    - context: "dev-europe-west1-0"
      replicas:
        - name: "app1-deploy"
          count: 2
  secrets:
    - secret_id: "app1"
      contexts:
        - name: "default"
          vaultPath: "/kv/dev/ns1/app1/test"
          vaultKey: "password"
        - name: "dev-europe-west1-0"
          vaultPath: "/kv/dev/ns1/app1/test"
          vaultKey: "password"

  • 1 Release pro Anwendung (ohne verallgemeinerte Diagramme).
  • Diagramme im Git-Repository der Anwendung.

Wir haben mit allen Entwicklern gesprochen, sodass der Migrationsprozess bereits begonnen hat. Die Steuerung der ersten Stufe erfolgt weiterhin über die CI-Plattform. Ich werde bald einen weiteren Beitrag über Phase zwei schreiben: wie wir zu einem GitOps-Workflow mit übergegangen sind Fluss. Ich erzähle Ihnen, wie wir alles eingerichtet haben und auf welche Schwierigkeiten wir gestoßen sind (mehrere Repositories, Geheimnisse usw.). Verfolgen Sie die Nachrichten.

Hier haben wir versucht, unsere Fortschritte im Anwendungsbereitstellungs-Workflow der letzten Jahre zu beschreiben, die zu Überlegungen zum GitOps-Ansatz geführt haben. Wir haben das Ziel noch nicht erreicht und werden über die Ergebnisse berichten, aber jetzt sind wir überzeugt, dass wir das Richtige getan haben, als wir beschlossen haben, alles zu vereinfachen und näher an die Gewohnheiten der Entwickler heranzuführen.

Source: habr.com

Kommentar hinzufügen