Nach zwei Jahren Entwicklung erfolgte die Veröffentlichung von Debian.

Wie Dailymotion Kubernetes nutzt: Anwendungsbereitstellung

Wir bei Dailymotion haben vor 3 Jahren begonnen, Kubernetes in der Produktion zu nutzen. Aber die Bereitstellung von Anwendungen über mehrere Cluster ist mühsam, weshalb wir in den letzten Jahren versucht haben, unsere Werkzeuge und Arbeitsabläufe zu verbessern.

Wie alles begann

Hier erzählen wir, wie wir unsere Anwendungen über mehrere Kubernetes-Clustern weltweit bereitstellen.

Um mehrere Kubernetes-Objekte gleichzeitig bereitzustellen, nutzen wir Helm, und alle unsere Charts werden in einem Git-Repository gespeichert. Um einen vollständigen Anwendungsstack aus mehreren Diensten bereitzustellen, verwenden wir das sogenannte Aggregationschart. Im Grunde genommen ist es ein Chart, das Abhängigkeiten erklärt und es ermöglicht, die API und deren Dienste mit einem Befehl zu initialisieren.

Außerdem haben wir ein kleines Python-Skript über Helm geschrieben, um Überprüfungen durchzuführen, Charts zu erstellen, Geheimnisse hinzuzufügen und Anwendungen bereitzustellen. All diese Aufgaben werden auf einer zentralen CI-Plattform mit einem Docker-Image durchgeführt.

Kommen wir zu den Details.

Hinweis: Wenn Sie dies lesen, wurde der erste Release-Kandidat von Helm 3 bereits angekündigt. Die Hauptversion enthält eine Reihe von Verbesserungen, die darauf abzielen, einige Probleme zu lösen, mit denen wir in der Vergangenheit konfrontiert waren.

Entwicklungsworkflow für Charts

Für Anwendungen nutzen wir das Branching und haben beschlossen, diesen Ansatz auch für Charts zu verwenden.

  • Branch dev wird verwendet, um Charts zu erstellen, die auf Entwicklungsclustern getestet werden.
  • Wenn ein Pull-Request an mastergesendet wird, werden diese in der Staging-Umgebung überprüft.
  • Schließlich erstellen wir einen Pull-Request, um die Änderungen in den Branch prod zu überführen und diese in der Produktion anzuwenden.

Jede Umgebung hat ihr eigenes privates Repository, das unsere Charts speichert, und wir verwenden Chartmuseum mit sehr nützlichen APIs. Dadurch gewährleisten wir strikte Isolation zwischen den Umgebungen und testen die Charts in realen Bedingungen, bevor wir sie in der Produktion verwenden.

Charts-Repositories in verschiedenen Umgebungen

Es ist erwähnenswert, dass, wenn Entwickler einen dev-Branch einpflegen, die Version ihres Charts automatisch an das dev Chartmuseum gesendet wird. So nutzen alle Entwickler ein gemeinsames dev-Repository, und es ist wichtig, die eigene Chart-Version genau anzugeben, um versehentlich die Änderungen anderer zu vermeiden.

Darüber hinaus überprüft unser kleines Python-Skript Kubernetes-Objekte anhand der Spezifikationen der Kubernetes OpenAPI mit Hilfe von Kubeval, bevor sie in Chartmuseum veröffentlicht werden.

Allgemeine Beschreibung des Chart-Entwicklungsprozesses

  1. Konfiguration der Pipeline-Jobs gemäß der Spezifikation gazr.io zur Qualitätskontrolle (Linting, Unit-Tests).
  2. Bereitstellung eines Docker-Images mit Python-Tools, die unsere Anwendungen bereitstellen.
  3. Einrichtung der Umgebung nach dem Branch-Namen.
  4. Überprüfung von Kubernetes-YAML-Dateien mit Kubeval.
  5. Automatisches Hochsetzen der Version des Charts und seiner übergeordneten Charts (Charts, die von dem veränderbaren Chart abhängen).
  6. Hochladen des Charts in Chartmuseum, das mit seiner Umgebung übereinstimmt.

Verwaltung der Unterschiede in den Clustern.

Federation von Clustern

Es gab eine Zeit, in der wir die Kubernetes-Cluster-Federationnutzten, bei der man Kubernetes-Objekte von einem API-Endpunkt aus deklarieren konnte. Aber es traten Probleme auf. Zum Beispiel konnten einige Kubernetes-Objekte nicht am Federation-Endpunkt erstellt werden, was die Verwaltung kombinierter Objekte und anderer Objekte für einzelne Cluster erschwerte.

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 Federation; in der zweiten könnte sich etwas geändert haben).

Geodistribuierte Plattform

Aktuell ist unsere Plattform über 6 Regionen verteilt – 3 lokal und 3 in der Cloud.


Verteilte Bereitstellung

Globale Helm-Werte

4 globale Helm-Werte ermöglichen es, Unterschiede zwischen Clustern zu definieren. Für all unsere Charts gibt es minimale Standardwerte.

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

Globale Werte

Diese Werte helfen dabei, den Kontext für unsere Anwendungen zu definieren und werden für verschiedene Aufgaben verwendet: Monitoring, Tracing, Logging, externe Aufrufe, Skalierung usw.

  • "cloud": wir haben eine hybride Kubernetes-Plattform. Zum Beispiel wird unsere API in GCP-Zonen und in unseren Rechenzentren bereitgestellt.
  • "env": einige Werte können sich für nicht-productive Umgebungen ändern. Zum Beispiel Ressourcendefinitionen und Autoskalierungskonfigurationen.
  • "region": diese Information hilft dabei, den Standort des Clusters zu definieren und kann verwendet werden, um die nächstgelegenen Endpunkte für externe Dienste zu bestimmen.
  • "clusterName": wenn und wann wir einen Wert für einen bestimmten Cluster definieren möchten.

Hier ist ein konkretes Beispiel:

{{
/* Gibt die Horizontal Pod Autoscaler-Replikate für GraphQL zurück */}}
{{- 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 eines Helm-Templates

Diese Logik ist in einer Hilfs-Template definiert, um das Kubernetes YAML nicht zu überladen.

Anwendungsdeklaration

Unsere Deployment-Tools basieren auf mehreren YAML-Dateien. Hier ist ein Beispiel, wie wir einen Dienst und seine Skalierungstopologie (Anzahl der Replikate) im Cluster deklarieren.

releases:
  - foo.world

foo.world:                # Release-Name
  services:               # Liste der Apps/Projekte von dailymotion
    foobar:
      chart_name: foo-foobar
      repo: git@github.com:dailymotion/foobar
      contexts:
        prod-europe-west1:
          deployments:
            - name: foo-bar-baz
              replicas: 18
            - name: another-deployment
              replicas: 3

Dienstdefinition

Das ist ein Schema aller Schritte, die unseren Deployment-Prozess definieren. Der letzte Schritt deployt die Anwendung gleichzeitig auf mehreren Arbeits-Clustern.


Deployment-Schritte in Jenkins

Und die Geheimnisse?

Was die Sicherheit angeht, überwachen wir alle Geheimnisse aus verschiedenen Quellen und speichern sie in einem einzigartigen Vault. Vault in Paris.

Unsere Deployment-Tools extrahieren die Werte der Geheimnisse aus dem Vault und fügen sie bei der Bereitstellung in Helm ein.

Dafür haben wir eine Zuordnung zwischen den Geheimnissen im Vault und den Geheimnissen, die unsere Anwendungen benötigen, definiert:

geheimnisse:                                                                                                                                                                                                        
     - geheimnis_id: "stack1-app1-password"                                                                                                                                                                                  
       kontexte:                                                                                                                                                                                                   
         - name: "standard"                                                                                                                                                                                         
           vaultPath: "/kv/dev/stack1/app1/test"                                                                                                                                                               
           vaultKey: "password"                                                                                                                                                                                    
         - name: "cluster1"                                                                                                                                                                           
           vaultPath: "/kv/dev/stack1/app1/test"                                                                                                                                                               
           vaultKey: "password"

  • Wir haben allgemeine Richtlinien festgelegt, die beim Speichern von Geheimnissen im Vault beachtet werden müssen.
  • Wenn das Geheimnis sich auf einen bestimmten Kontext oder Cluster bezieht, muss ein spezifischer Eintrag hinzugefügt werden. (Hier hat der Kontext cluster1 einen eigenen Wert für das Geheimnis stack-app1-password).
  • Andernfalls wird der Wert verwendet standardmäßig.
  • Für jeden Punkt in dieser Liste wird im Kubernetes-Geheimnis ein Schlüssel-Wert-Paar eingefügt. Daher ist die Vorlage des Geheimnisses in unseren Charts 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

Probleme und Einschränkungen

Arbeiten mit mehreren Repositories

Momentan trennen wir die Entwicklung von Charts und Anwendungen. Das bedeutet, dass Entwickler in zwei Git-Repositories arbeiten müssen: eines für die Anwendung und das andere für die Definition ihrer Bereitstellung in Kubernetes. 2 Git-Repositories bedeuten 2 Arbeitsabläufe, und es ist für Neulinge leicht, sich zu verwirren.

Es ist mühsam, mit generischen Charts zu arbeiten

Wie bereits erwähnt, sind generische Charts sehr praktisch, um Abhängigkeiten zu definieren und mehrere Anwendungen schnell bereitzustellen. Aber wir verwenden --reuse-values, um zu vermeiden, dass wir jedes Mal alle Werte übermitteln, wenn wir eine Anwendung aus diesem generischen Chart bereitstellen.

Im Arbeitsprozess der kontinuierlichen Lieferung haben wir nur zwei Werte, die sich regelmäßig ändern: die Anzahl der Repliken und das Tag des Images (Version). Andere, stabilere Werte werden manuell geändert, was ziemlich kompliziert ist. Darüber hinaus kann ein Fehler bei der Bereitstellung eines generischen Charts zu schwerwiegenden Ausfällen führen, wie wir aus eigener Erfahrung festgestellt haben.

Aktualisierung mehrerer Konfigurationsdateien

Wenn ein Entwickler eine neue Anwendung hinzufügt, muss er mehrere Dateien ändern: die Anwendungsdeklaration, die Liste der Geheimnisse, die Hinzufügung der Anwendung zu den Abhängigkeiten, falls sie im generischen Chart enthalten ist.

Jenkins-Berechtigungen sind zu weit gefasst im Vault

Jetzt haben wir einen AppRole, der alle Geheimnisse aus dem Vault liest.

Der Rollback-Prozess ist nicht automatisiert

Für den Rollback muss auf mehreren Clustern ein Befehl ausgeführt werden, was fehleranfällig 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 Chart in das Repository der Anwendung zurückbringen, die es bereitstellt.

Der Workflow wird derselbe sein wie bei der Entwicklung. Beispielsweise wird das Deployment automatisch gestartet, wenn ein Branch in den Master gesendet wird. Der Hauptunterschied zwischen diesem Ansatz und dem aktuellen Workflow wird sein, dass alles in git verwaltet wird (die Anwendung selbst und die Art und Weise ihrer Bereitstellung in Kubernetes).

Es gibt mehrere Vorteile:

  • Es ist viel verständlicher für den Entwickler. Es ist einfacher, Änderungen im lokalen Chart anzuwenden.
  • Die Bereitstellung der Dienstleistung kann dort definiert werden, wo auch der Code der Dienstleistung steht.
  • Management der Löschung generischer Charts. Der Dienst hat seine eigene Helm-Version. Dies wird es ermöglichen, den Lebenszyklus der Anwendung (Rollback, Upgrade) auf der kleinsten Ebene zu verwalten, ohne andere Dienste zu beeinträchtigen.
  • Vorteile von git für die Verwaltung von Charts: Änderungsrückgängig machen, Audit-Protokoll usw. Wenn eine Änderung des Charts rückgängig gemacht werden muss, kann dies mit git erfolgen. Das Deployment wird automatisch gestartet.
  • Man könnte überlegen, den Entwicklungsworkflow mit Tools wie Skaffold, mit dem Entwickler Änderungen in einem der Produktionsumgebung ähnlichen Kontext testen können.

Zwei-Stufen-Migration

Unsere Entwickler verwenden diesen Arbeitsablauf seit 2 Jahren, daher benötigen wir eine möglichst reibungslose Migration. Deshalb haben wir entschieden, einen ZwischenSchritt auf dem Weg zum Ziel hinzuzufügen.
Der erste Schritt ist einfach:

  • Wir behalten eine ähnliche Struktur für die Konfiguration der Anwendungsbereitstellung bei, aber in einem Objekt mit dem Namen 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 allgemeine Charts).
  • Charts im Git-Repository der Anwendung.

Wir haben mit allen Entwicklern gesprochen, daher hat der Migrationsprozess bereits begonnen. Der erste Schritt wird weiterhin mit der CI-Plattform überwacht. Bald werde ich einen weiteren Beitrag über den zweiten Schritt schreiben: wie wir auf den GitOps-Arbeitsablauf umgestellt haben mit Flux. Ich werde erzählen, wie wir alles eingerichtet haben und mit welchen Schwierigkeiten wir konfrontiert waren (einige Repositories, Geheimnisse usw.). Bleiben Sie dran.

Hier haben wir versucht, unseren Fortschritt im Anwendungsbereitstellungsprozess der letzten Jahre zu beschreiben, der zu Überlegungen zum GitOps-Ansatz führte. Wir haben unser Ziel noch nicht erreicht und werden über die Ergebnisse berichten, sind jedoch davon überzeugt, dass wir es richtig gemacht haben, als wir entschieden haben, alles zu vereinfachen und es den Gewohnheiten der Entwickler näherzubringen.

Quelle: habr.com

60GB SSD 8Gb DDR4