Implementarea aplicațiilor pe mai multe clustere Kubernetes cu Helm

Cum folosește Dailymotion Kubernetes: implementarea aplicațiilor

Noi, la Dailymotion, am început să folosim Kubernetes în producție acum 3 ani. Însă implementarea aplicațiilor pe mai multe clustere nu este tocmai plăcută, așa că în ultimii câțiva ani am încercat să ne îmbunătățim instrumentele și fluxurile de lucru.

Cum a început totul

Aici vom explica cum implementăm aplicațiile noastre pe mai multe clustere Kubernetes din întreaga lume.

Pentru a implementa mai multe obiecte Kubernetes deodată, folosim Helm, iar toate diagramele noastre sunt stocate într-un singur depozit git. Pentru a implementa un întreg stivă de aplicație din mai multe servicii, utilizăm așa-numita diagramă de agregare. Practic, aceasta este o diagramă care declară dependențele și permite inițializarea API-ului și a serviciilor sale dintr-o singură comandă.

De asemenea, am scris un mic script Python deasupra Helm pentru a efectua verificări, a crea diagrame, a adăuga secrete și a implementa aplicațiile. Toate aceste sarcini sunt realizate pe o platformă CI centralizată folosind o imagine docker.

Să trecem la subiect.

Notă. Când citiți asta, primul release candidate Helm 3 a fost deja anunțat. Versiunea principală conține un set întreg de îmbunătățiri menite să rezolve unele probleme cu care ne-am confruntat în trecut.

Fluxul de lucru pentru dezvoltarea diagramelor

Pentru aplicații, folosim ramificarea, iar aceeași abordare am decis să o aplicăm și diagramelor.

  • Ramura dev este utilizată pentru a crea diagrame care vor fi testate pe clusterele de dezvoltare.
  • Când un pull request este trimis în master, acestea sunt verificate în staging.
  • În cele din urmă, creăm un pull request pentru a trimite modificările în ramura prod și a le aplica în producție.

Fiecare mediu are un depozit privat care stochează diagrama noastră, și utilizăm Chartmuseum cu API-uri foarte utile. Astfel, garantăm o izolare strictă între medii și verificarea diagramelor în condiții reale înainte de a le utiliza în producție.

Depozitele diagramelor în diferite medii

Trebuie menționat că atunci când dezvoltatorii trimit o ramură dev, versiunea diagramei lor este trimisă automat în dev Chartmuseum. Astfel, toți dezvoltatorii folosesc un singur depozit dev și trebuie să își specifice cu atenție versiunea diagramei pentru a nu folosi accidental modificările altora.

În plus, scriptul nostru Python mic verifică obiectele Kubernetes conform specificațiilor OpenAPI Kubernetes cu ajutorul Kubeval, înainte de a le publica în Chartmuseum.

Descriere generală a fluxului de lucru pentru dezvoltarea chartului

  1. Configurarea sarcinilor pipeline-ului conform specificației gazr.io pentru controlul calității (lint, unit-test).
  2. Trimiterea imaginilor Docker cu instrumentele Python care desfășoară aplicațiile noastre.
  3. Configurarea mediului în funcție de numele ramurii.
  4. Verificarea fișierelor YAML Kubernetes cu ajutorul Kubeval.
  5. Creșterea automată a versiunii chartului și a charturilor parent (charturi care depind de chartul modificat).
  6. Trimiterea chartului în Chartmuseum, care corespunde mediului său

Gestionarea diferențelor între clustere

Federația clusterelor

A fost un timp când foloseam federarea clusterelor Kubernetes, unde puteam declara obiecte Kubernetes dintr-un singur punct final API. Dar au apărut probleme. De exemplu, unele obiecte Kubernetes nu puteau fi create la punctul final al federației, astfel că era dificil să întreținem obiectele consolidate și alte obiecte pentru clustere individuale.

Pentru a rezolva problema, am început să gestionăm clusterele independent, ceea ce a simplificat semnificativ procesul (folosind prima versiune a federației; în a doua ceva ar fi putut să se schimbe).

Platformă geo-distribuită

Acum platforma noastră este distribuită în 6 regiuni — 3 local și 3 în cloud.


Desfășurare distribuită

Valori globale Helm

4 valori globale Helm permit definirea diferențelor între clustere. Pentru toate charturile noastre există valori minime implicite.

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

Valori globale

Aceste valori ajută la definirea contextului pentru aplicațiile noastre și sunt utilizate pentru diferite sarcini: monitorizare, urmărire, registrare, apeluri externe, scalare etc.

  • "cloud": avem o platformă Kubernetes hibridă. De exemplu, API-ul nostru este desfășurat în zonele GCP și în centrele noastre de date.
  • "env": unele valori pot varia pentru medii non-production. De exemplu, definițiile resurselor și configurația de auto-scaling.
  • "region": aceste informații ajută la definirea locației clusterei și pot fi folosite pentru a determina cele mai apropiate puncte finale pentru servicii externe.
  • "clusterName": dacă și când dorim să definim o valoare pentru un cluster individual.

Iată un exemplu concret:

{{
/* Returnează replicile Horizontal Pod Autoscaler pentru 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 -}}

Exemplu de șablon Helm

Această logică este definită într-un șablon auxiliar, pentru a nu aglomera YAML-ul Kubernetes.

Declararea aplicației

Instrumentele noastre de desfășurare se bazează pe mai multe fișiere YAML. Iată un exemplu despre cum declarăm serviciul și topologia sa de scalare (numărul de replici) în cluster.

releases:
  - foo.world

foo.world:                # Numele release-ului
  services:               # Lista aplicațiilor/proiectelor 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

Definirea serviciului

Aceasta este schema tuturor pașilor care definesc fluxul nostru de desfășurare. Ultimul pas desfășoară aplicația simultan pe mai multe clustere de lucru.


Pașii desfășurării în Jenkins

Dar secretele?

În ceea ce privește securitatea, urmăream toate secretele din diferite locuri și le stocăm într-un depozit unic Vault în Paris.

Instrumentele noastre de desfășurare extrag valorile secrets din Vault și, când vine timpul desfășurării, le introduc în Helm.

Pentru aceasta, am definit o asociere între secretele din Vault și secretele necesare aplicațiilor noastre:

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"

  • Am stabilit reguli generale care trebuie respectate la înregistrarea secretelor în Vault.
  • Dacă secretul se referă la un context sau cluster specific, trebuie adăugată o înregistrare specifică. (Aici, contextul cluster1 are o valoare proprie pentru secretul stack-app1-password).
  • În caz contrar, se folosește valoarea implicit.
  • Pentru fiecare element din această listă în secretul Kubernetes se introduce o pereche cheie-valoare. Prin urmare, șablonul secretului în graficele noastre este foarte simplu.

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 și limitări

Lucrul cu mai multe repository-uri

În prezent, separăm dezvoltarea graficelor și aplicațiilor. Asta înseamnă că dezvoltatorii trebuie să lucreze în două repository-uri git: unul pentru aplicație și al doilea pentru definirea desfășurării sale în Kubernetes. 2 repository-uri git înseamnă 2 fluxuri de lucru, iar unui începător îi este ușor să se confunde.

Gestionarea graficelor generice este o provocare

Așa cum am menționat anterior, graficele generice sunt foarte utile pentru definirea dependențelor și desfășurarea rapidă a mai multor aplicații. Însă folosim --reuse-values, pentru a evita transmiterea tuturor valorilor de fiecare dată când desfășurăm o aplicație care face parte din acest grafic generic.

În procesul de livrare continuă avem doar două valori care se schimbă în mod regulat: numărul de replici și eticheta imaginii (versiunea). Alte valori, mai stabile, se modifică manual, ceea ce este destul de complicat. Mai mult, o eroare în desfășurarea unui chart generic poate conduce la defecțiuni grave, așa cum am constatat din propria experiență.

Actualizarea mai multor fișiere de configurare

Când un dezvoltator adaugă o nouă aplicație, trebuie să modifice mai multe fișiere: declarația aplicației, lista secretelor, adăugarea aplicației în dependențe, dacă face parte din chartul generic.

Permisiunile Jenkins sunt prea extinse în Vault

Acum avem unul AppRole, care citește toate secretele din Vault.

Procesul de rollback nu este automatizat

Pentru rollback, trebuie să executăm comanda pe mai multe clustere, ceea ce este predispus la erori. Facem această operațiune manual pentru a ne asigura că indicăm corect identificatorul versiunii.

Ne îndreptăm spre GitOps

Scopul nostru

Vrem să aducem chartul în repository-ul aplicației pe care o desfășoară.

Fluxul de lucru va fi același ca pentru dezvoltare. De exemplu, când o ramură este trimisă în master, desfășurarea va fi inițiată automat. Principala diferență între această abordare și fluxul de lucru actual va fi că totul va fi gestionat în git (aplicația în sine și modul ei de desfășurare în Kubernetes).

Există mai multe avantaje:

  • Este mult mai clar pentru dezvoltator. Este mai simplu să înveți cum să aplici modificări în chartul local.
  • Definiția desfășurării serviciului poate fi specificată acolo unde se află codul serviciului.
  • Gestionarea eliminării charturilor generice. Serviciul va avea propria sa eliberare Helm. Aceasta va permite gestionarea ciclului de viață al aplicației (rollback, upgrade) la cel mai mic nivel, fără a afecta alte servicii.
  • Avantajele git pentru gestionarea charturilor: anularea modificărilor, jurnalul de audit etc. Dacă este nevoie să anulezi o modificare a chartului, aceasta poate fi realizată prin git. Desfășurarea este inițiată automat.
  • Se poate gândi la îmbunătățirea fluxului de lucru de dezvoltare cu ajutorul unor instrumente precum Skaffold, cu care dezvoltatorii pot testa modificările în context apropiat de producție.

Migrarea în două etape

De doi ani, dezvoltatorii noștri folosește acest flux de lucru, așa că avem nevoie de o migrare cât mai ușoară. Prin urmare, am decis să adăugăm o etapă intermediară pe drumul către obiectiv.
Prima etapă este simplă:

  • Menținem o structură similară pentru configurarea desfășurării aplicațiilor, dar într-un singur obiect numit 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 pe aplicație (fără grafice generalizate).
  • Grafice în depozitul git al aplicației.

Am discutat cu toți dezvoltatorii, așa că procesul de migrare a început deja. Prima etapă este în continuare controlată utilizând platforma CI. În curând voi scrie un alt articol despre a doua etapă: cum am trecut la fluxul de lucru GitOps cu Flux. Voi explica cum am configurat totul și cu ce dificultăți ne-am confruntat (câteva depozite, secrete etc.). Rămâneți la curent.

Aici am încercat să descriem progresul nostru în fluxul de desfășurare a aplicațiilor din ultimii ani, care ne-a dus la gânduri despre abordarea GitOps. Încă nu am atins obiectivul și vă vom informa despre rezultate, dar acum suntem convinși că am făcut bine când am decis să simplificăm totul și să ne apropiem de obiceiurile dezvoltatorilor.

Sursa: habr.com

Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS 🔥 Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS | ProHoster