Déployer des applications sur plusieurs clusters Kubernetes avec Helm

Comment Dailymotion utilise Kubernetes : déploiement d'applications

Chez Dailymotion, nous avons commencé à utiliser Kubernetes en production il y a 3 ans. Mais déployer des applications sur plusieurs clusters est amusant, c'est pourquoi nous avons essayé ces dernières années d'améliorer nos outils et nos flux de travail.

Où est-ce que ça a commencé

Nous expliquerons ici comment nous déployons nos applications sur plusieurs clusters Kubernetes à travers le monde.

Pour déployer plusieurs objets Kubernetes à la fois, nous utilisons Casque, et tous nos graphiques sont stockés dans un seul référentiel git. Pour déployer une pile d'applications complète à partir de plusieurs services, nous utilisons ce que l'on appelle le tableau récapitulatif. Il s'agit essentiellement d'un graphique qui déclare les dépendances et vous permet d'initialiser l'API et ses services avec une seule commande.

Nous avons également écrit un petit script Python au-dessus de Helm pour effectuer des vérifications, créer des graphiques, ajouter des secrets et déployer des applications. Toutes ces tâches sont effectuées sur une plateforme CI centrale à l'aide d'une image Docker.

Venons-en au fait.

Note. Au moment où vous lisez ceci, la première release candidate pour Helm 3 a déjà été annoncée. La version principale contient de nombreuses améliorations pour résoudre certains des problèmes que nous avons rencontrés dans le passé.

Flux de travail de développement de graphiques

Nous utilisons le branchement pour les applications et nous avons décidé d'appliquer la même approche aux graphiques.

  • Bifurquer dev utilisé pour créer des graphiques qui seront testés sur des clusters de développement.
  • Lorsqu'une pull request est soumise à maître, ils sont vérifiés en staging.
  • Enfin, nous créons une pull request pour valider les modifications dans la branche poussée et les appliquer en production.

Chaque environnement possède son propre référentiel privé qui stocke nos graphiques, et nous utilisons Musée des cartes avec des API très utiles. De cette façon, nous garantissons une isolation stricte entre les environnements et des tests réels des graphiques avant de les utiliser en production.

Référentiels de graphiques dans différents environnements

Il convient de noter que lorsque les développeurs poussent une branche de développement, une version de leur graphique est automatiquement transférée vers le dev Chartmuseum. Ainsi, tous les développeurs utilisent le même référentiel de développement et vous devez spécifier soigneusement votre version du graphique afin de ne pas utiliser accidentellement les modifications de quelqu'un d'autre.

De plus, notre petit script Python valide les objets Kubernetes par rapport aux spécifications Kubernetes OpenAPI en utilisant Kubeval, avant de les publier sur Chartmusem.

Description générale du workflow de développement de graphiques

  1. Mise en place de tâches de pipeline selon les spécifications gazr.io pour le contrôle qualité (peluches, tests unitaires).
  2. Pousser une image Docker avec les outils Python qui déploient nos applications.
  3. Configuration de l'environnement par nom de branche.
  4. Validation des fichiers yaml Kubernetes à l'aide de Kubeval.
  5. Augmentez automatiquement la version d'un graphique et de ses graphiques parents (graphiques qui dépendent du graphique en cours de modification).
  6. Soumettre une carte à un Chartmuseum qui correspond à son environnement

Gérer les différences entre les clusters

Fédération des Clusters

Il fut un temps où nous utilisions fédération de clusters Kubernetes, où les objets Kubernetes peuvent être déclarés à partir d'un seul point de terminaison d'API. Mais des problèmes sont apparus. Par exemple, certains objets Kubernetes n'ont pas pu être créés dans le point de terminaison de la fédération, ce qui rend difficile la maintenance des objets fédérés et d'autres objets pour des clusters individuels.

Pour résoudre le problème, nous avons commencé à gérer les clusters de manière indépendante, ce qui a grandement simplifié le processus (nous avons utilisé la première version de la fédération ; quelque chose aurait pu changer dans la seconde).

Plateforme géo-distribuée

Notre plateforme est actuellement distribuée dans 6 régions : 3 localement et 3 dans le cloud.


Déploiement distribué

Valeurs globales de Helm

4 valeurs Helm globales permettent d'identifier les différences entre les clusters. Tous nos graphiques ont des valeurs minimales par défaut.

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

Valeurs mondiales

Ces valeurs aident à définir le contexte de nos applications et sont utilisées à diverses fins : surveillance, traçage, journalisation, appels externes, mise à l'échelle, etc.

  • "cloud" : Nous disposons d'une plateforme Kubernetes hybride. Par exemple, notre API est déployée dans les zones GCP et dans nos datacenters.
  • "env" : Certaines valeurs peuvent changer pour les environnements hors production. Par exemple, les définitions de ressources et les configurations de mise à l'échelle automatique.
  • « région » : ces informations permettent de déterminer l'emplacement du cluster et peuvent être utilisées pour déterminer les points de terminaison à proximité pour les services externes.
  • "clusterName" : si et quand nous voulons définir une valeur pour un cluster individuel.

Voici un exemple spécifique :

{{/* 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 -}}

Exemple de modèle de barre

Cette logique est définie dans un modèle d'assistance pour éviter d'encombrer Kubernetes YAML.

Annonce de candidature

Nos outils de déploiement sont basés sur plusieurs fichiers YAML. Vous trouverez ci-dessous un exemple de la façon dont nous déclarons un service et sa topologie de mise à l'échelle (nombre de réplicas) dans un cluster.

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

Définition des services

Ceci est un aperçu de toutes les étapes qui définissent notre flux de travail de déploiement. La dernière étape déploie l'application sur plusieurs clusters de travail simultanément.


Étapes de déploiement de Jenkins

Et les secrets ?

Concernant la sécurité, nous suivons tous les secrets de différents endroits et les stockons dans un coffre-fort unique Voûte à Paris.

Nos outils de déploiement extraient les valeurs secrètes de Vault et, le moment venu, les insèrent dans Helm.

Pour ce faire, nous avons défini un mappage entre les secrets de Vault et les secrets dont nos applications ont besoin :

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"

  • Nous avons défini des règles générales à suivre lors de l'enregistrement de secrets dans Vault.
  • Si le secret s'applique à un contexte ou un cluster spécifique, vous devez ajouter une entrée spécifique. (Ici, le contexte cluster1 a sa propre valeur pour le mot de passe secret stack-app1).
  • Sinon, la valeur est utilisée par défaut.
  • Pour chaque élément de cette liste dans Secret de Kubernetes une paire clé-valeur est insérée. Par conséquent, le modèle secret de nos graphiques est très simple.

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

Défis et limites

Travailler avec plusieurs référentiels

Nous séparons désormais le développement de graphiques et d'applications. Cela signifie que les développeurs doivent travailler dans deux référentiels git : un pour l'application et un pour définir son déploiement sur Kubernetes. 2 référentiels git signifient 2 workflows, et il est facile pour un débutant de se perdre.

La gestion des graphiques généralisés est un véritable casse-tête

Comme nous l'avons déjà dit, les graphiques génériques sont très utiles pour identifier les dépendances et déployer rapidement plusieurs applications. Mais nous utilisons --reuse-valuespour éviter de transmettre toutes les valeurs à chaque fois que nous déployons une application faisant partie de ce tableau généralisé.

Dans un workflow de livraison continue, nous n'avons que deux valeurs qui changent régulièrement : le nombre de répliques et la balise d'image (version). D'autres valeurs plus stables sont modifiées manuellement, ce qui est assez difficile. De plus, une erreur dans le déploiement d’un graphique généralisé peut conduire à de graves échecs, comme nous l’avons vu par notre propre expérience.

Mise à jour de plusieurs fichiers de configuration

Lorsqu'un développeur ajoute une nouvelle application, il doit modifier plusieurs fichiers : la déclaration de l'application, la liste des secrets, l'ajout de l'application en dépendance si elle est incluse dans le tableau généralisé.

Les autorisations Jenkins sont trop étendues dans Vault

Maintenant nous en avons un AppRole, qui lit tous les secrets du coffre-fort.

Le processus de restauration n'est pas automatisé

Pour restaurer, vous devez exécuter la commande sur plusieurs clusters, ce qui est semé d'erreurs. Nous effectuons cette opération manuellement pour garantir que l'ID de version correct est spécifié.

Nous nous dirigeons vers GitOps

Notre but

Nous souhaitons renvoyer le graphique dans le référentiel de l'application qu'il déploie.

Le workflow sera le même que pour le développement. Par exemple, lorsqu’une branche est poussée vers master, le déploiement sera déclenché automatiquement. La principale différence entre cette approche et le flux de travail actuel serait que tout sera géré dans git (l'application elle-même et la manière dont elle est déployée dans Kubernetes).

Il y a plusieurs avantages :

  • Beaucoup plus plus clair pour le développeur. Il est plus facile d'apprendre à appliquer les modifications dans un graphique local.
  • La définition du déploiement de service peut être spécifiée au même endroit que le code service.
  • Gérer la suppression des cartes généralisées. Le service aura sa propre version Helm. Cela vous permettra de gérer le cycle de vie de l'application (rollback, mise à niveau) au plus petit niveau, afin de ne pas affecter les autres services.
  • Avantages de git pour la gestion des graphiques : annulation des modifications, journal d'audit, etc. Si vous devez annuler une modification apportée à un graphique, vous pouvez le faire en utilisant git. Le déploiement démarre automatiquement.
  • Vous pourriez envisager d'améliorer votre flux de travail de développement avec des outils tels que Skaffold, avec lequel les développeurs peuvent tester les changements dans un contexte proche de la production.

Migration en deux étapes

Nos développeurs utilisent ce workflow depuis 2 ans maintenant, nous souhaitons donc que la migration soit aussi simple que possible. Nous avons donc décidé d’ajouter une étape intermédiaire sur le chemin vers l’objectif.
La première étape est simple :

  • Nous conservons une structure similaire pour la mise en place du déploiement d'applications, mais dans un seul objet appelé 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 par application (sans chartes généralisées).
  • Graphiques dans le référentiel git de l'application.

Nous avons parlé à tous les développeurs, le processus de migration a donc déjà commencé. La première étape est toujours contrôlée à l'aide de la plateforme CI. J'écrirai bientôt un autre article sur la phase deux : comment nous sommes passés à un workflow GitOps avec Flux. Je vais vous raconter comment nous avons tout mis en place et quelles difficultés nous avons rencontrées (référentiels multiples, secrets, etc.). Suivez l'actualité.

Nous avons essayé ici de décrire nos progrès dans le workflow de déploiement d'applications au cours des dernières années, ce qui a conduit à une réflexion sur l'approche GitOps. Nous n'avons pas encore atteint l'objectif et rendrons compte des résultats, mais nous sommes désormais convaincus d'avoir fait la bonne chose en décidant de tout simplifier et de le rapprocher des habitudes des développeurs.

Source: habr.com

Ajouter un commentaire