Udrulning af applikationer på flere Kubernetes-klynger med Helm

Hvordan Dailymotion bruger Kubernetes: implementering af applikationer

Vi i Dailymotion begyndte at bruge Kubernetes i produktion for 3 år siden. Men at implementere applikationer på flere klynger er en udfordring, så i løbet af de seneste par år har vi arbejdet på at forbedre vores værktøjer og arbejdsprocesser.

Hvordan det hele startede

Her vil vi fortælle, hvordan vi implementerer vores applikationer på flere Kubernetes-klynger rundt omkring i verden.

For at implementere flere Kubernetes-objekter samtidig bruger vi Helm, og alle vores charts opbevares i et enkelt git-repository. For at implementere en fuld applikationsstak fra flere tjenester bruger vi såkaldte overordnede charts. I bund og grund er det et chart, der erklærer afhængigheder og gør det muligt at initialisere API'et og dets tjenester med en enkelt kommando.

Derudover har vi skrevet et lille Python-script oven på Helm for at udføre kontroller, oprette charts, tilføje hemmeligheder og implementere applikationer. Alle disse opgaver udføres på en central CI-platform ved hjælp af et docker-image.

Lad os komme til sagen.

Bemærk. Når du læser dette, er den første release-kandidat for Helm 3 allerede blevet annonceret. Hovedversionen indeholder en række forbedringer, der sigter mod at løse nogle af de problemer, vi har stødt på før.

Udviklingsworkflow for charts

Til applikationer bruger vi branching, og den samme tilgang besluttede vi at anvende på charts.

  • Branch dev brugs til at oprette charts, der skal testes på udviklingsklynger.
  • Når en pull-request sendes til master, bliver de kontrolleret i staging.
  • Endelig opretter vi en pull-request for at overføre ændringerne til branch prod og anvende dem i produktion.

Hvert miljø har sit eget private repository, som opbevarer vores charts, og vi bruger Chartmuseum med meget nyttige API'er. På den måde sikrer vi streng isolation mellem miljøerne og testning af charts under reelle forhold, før vi bruger dem i produktion.

Repository for charts i forskellige miljøer

Det er værd at bemærke, at når udviklere sender en dev-branch, sendes versionen af deres chart automatisk til dev Chartmuseum. På den måde bruger alle udviklere ét dev-repository, og det er vigtigt at angive sin chart-version nøje for at undgå utilsigtet at bruge andres ændringer.

Desuden tjekker vores lille Python-script Kubernetes-objekterne mod Kubernetes OpenAPI-specifikationerne ved hjælp af Kubeval, før de offentliggøres i Chartmuseum.

Generel beskrivelse af workflowet for chart-udvikling

  1. Opsætning af pipeline-opgaver i henhold til specifikationen gazr.io til kvalitetskontrol (lint, enhedstest).
  2. Send docker-billedet med de Python-værktøjer, der implementerer vores applikationer.
  3. Opsætning af miljøet ved navn gren.
  4. Validering af Kubernetes yaml-filer ved hjælp af Kubeval.
  5. Automatisk opgradering af versionen på chartet og dets overordnede charts (charts, der afhænger af det ændrede chart).
  6. Send chartet til Chartmuseum, som svarer til dets miljø

Håndtering af forskelle mellem klynger

Klyngeføderation

Der var en tid, hvor vi brugte Kubernetes-klyngeføderation, hvor vi kunne erklære Kubernetes-objekter fra ét API-endepunkt. Men der opstod problemer. For eksempel kunne nogle Kubernetes-objekter ikke oprettes i føderationens endepunkt, så det var svært at vedligeholde sammenlagte objekter og andre objekter for individuelle klynger.

For at løse problemet begyndte vi at administrere klyngerne uafhængigt, hvilket betydeligt forenklede processen (vi brugte den første version af føderation; i den anden kunne noget have ændret sig).

Geodistribueret platform

Nu er vores platform distribueret på 6 regioner — 3 lokalt og 3 i skyen.


Distribueret udrulning

Globale Helm-værdier

4 globale Helm-værdier gør det muligt at bestemme forskelle mellem klyngerne. Alle vores charts har minimale standardværdier.

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

Globale værdier

Disse værdier hjælper med at definere konteksten for vores applikationer og bruges til forskellige opgaver: overvågning, sporing, logning, eksterne opkald, skalerings osv.

  • "cloud": vi har en hybrid Kubernetes-platform. For eksempel implementeres vores API i GCP-zoner og i vores datacentre.
  • "env": nogle værdier kan ændre sig for ikke-produktionsmiljøer. For eksempel ressource- og autoskaleringens konfiguration.
  • "region": disse oplysninger hjælper med at bestemme klyngens placering og kan bruges til at bestemme de nærmeste endepunkter for eksterne tjenester.
  • "clusterName": hvis og når vi ønsker at bestemme en værdi for en individuel klynge.

Her er et konkret eksempel:

{{
/* Returnerer Horizontal Pod Autoscaler replikaer 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 -}}

Eksempel på en Helm-skabelon

Denne logik er defineret i en hjælpe-skabelon for ikke at overbelaste Kubernetes YAML.

Applikationsdeklaration

Vore implementeringsværktøjer er baseret på flere YAML-filer. Nedenfor er et eksempel på, hvordan vi deklarerer en service og dens skalerings-topologi (antal replikaer) i klyngen.

releases:
  - foo.world

foo.world:                # Release navn
  services:               # Liste over dailymotion's apps/projekter
    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

Servicedeklaration

Dette er et skema over alle de trin, der definerer vores implementeringsworkflow. Det sidste trin implementerer applikationen samtidigt på flere arbejds-klynger.


Implementeringstrin i Jenkins

Hvad med hemmeligheder?

Hvad angår sikkerhed, holder vi styr på alle hemmeligheder fra forskellige steder og opbevarer dem i et unikt lager Vault i Paris.

Vore implementeringsværktøjer henter værdierne af hemmelighederne fra Vault og, når det er tid til implementering, indsætter dem i Helm.

Til dette har vi defineret en mapping mellem hemmelighederne i Vault og de hemmeligheder, som vores applikationer har brug for:

hemmeligheder:                                                                                                                                                                                                        
     - secret_id: "stack1-app1-password"                                                                                                                                                                                  
       kontekster:                                                                                                                                                                                                   
         - navn: "default"                                                                                                                                                                                         
           vaultPath: "\/kv\/dev\/stack1\/app1\/test"                                                                                                                                                               
           vaultKey: "password"                                                                                                                                                                                    
         - navn: "cluster1"                                                                                                                                                                           
           vaultPath: "\/kv\/dev\/stack1\/app1\/test"                                                                                                                                                               
           vaultKey: "password"

  • Vi har defineret generelle regler, der skal følges, når man optager hemmeligheder i Vault.
  • Hvis hemmeligheden hører til en bestemt kontekst eller klynge, skal der tilføjes en specifik post. (Her har konteksten cluster1 sin egen værdi for hemmeligheden stack-app1-password).
  • Ellers bruges værdien som standard.
  • For hvert punkt på denne liste i Kubernetes hemmelighed indsættes et nøgle-værdi-par. Så skabelonen for hemmeligheden i vores charts er meget simpel.

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

Problemer og begrænsninger

Arbejder med flere repositories

I øjeblikket adskiller vi udvikling af charts og applikationer. Det betyder, at udviklerne skal arbejde i to git-repositories: en til applikationen og en anden for at definere dens implementering i Kubernetes. 2 git-repositories betyder 2 arbejdsprocesser, og det er let for nybegyndere at blive forvirrede.

At håndtere generiske charts er besværligt

Som nævnt tidligere er generiske charts meget nyttige til at definere afhængigheder og hurtigt implementere flere applikationer. Men vi bruger --reuse-values, for at undgå at overføre alle værdier hver gang, vi implementerer en applikation, der er en del af dette generiske chart.

I en kontinuerlig leveringsproces har vi kun to værdier, der ændrer sig regelmæssigt: antal replikaer og billedtag (version). Andre, mere stabile værdier, ændres manuelt, og det er ret kompliceret. Desuden kan en fejl i implementeringen af et generelt chart føre til alvorlige nedbrud, som vi har erfaret på egen hånd.

Opdatering af flere konfigurationsfiler

Når en udvikler tilføjer en ny applikation, skal han ændre flere filer: applikationsdeklaration, hemmelighedsliste, tilføjelse af applikationen til afhængigheder, hvis den er en del af det generelle chart.

Jenkins-tilladelser er for brede i Vault

Vi har nu én AppRole, som kan læse alle hemmeligheder fra Vault.

Rollback-processen er ikke automatiseret

For at rulle tilbage skal en kommando udføres på flere klynger, hvilket kan føre til fejl. Vi udfører denne operation manuelt for at sikre, at den rigtige version identificeres.

Vi bevæger os mod GitOps

Vores mål

Vi ønsker at returnere chartet til applikationsrepositoriet, som det implementerer.

Arbejdsprocessen vil være den samme som for udvikling. For eksempel, når en gren sendes til master, vil implementeringen blive startet automatisk. Den væsentligste forskel mellem denne tilgang og den nuværende arbejdsproces vil være, at alt vil blive styret i git (selve applikationen og måden, den implementeres i Kubernetes).

Der er flere fordele:

  • Mere forståeligt for udvikleren. Det er lettere at lære at anvende ændringer i det lokale chart.
  • Definitionen af serviceimplementeringen kan angives lige dér, hvor koden til tjenesten.
  • Håndtering af sletning af generelle charts. Tjenesten vil have sin egen Helm-udgave. Dette vil muliggøre styring af applikationens livscyklus (rollback, opgradering) på et meget fint niveau, så andre tjenester ikke berøres.
  • Fordele ved git til chart-styring: fortryd ændringer, revisionslog osv. Hvis der skal fortrydes en ændring af chartet, kan det gøres med git. Implementeringen startes automatisk.
  • Man kan overveje at forbedre udviklingsarbejdsgangen ved hjælp af værktøjer som Skaffold, hvor udviklere kan teste ændringer i en kontekst, der ligner produktion.

To-trins migration

Vores udviklere har brugt denne arbejdsgang i 2 år, så vi har brug for en så smertefri migration som muligt. Derfor har vi besluttet at tilføje et mellemtrin på vejen mod målet.
Det første trin er enkelt:

  • Vi bevarer en lignende struktur til indstillingerne for udrulning af applikationer, men i ét objekt kaldet 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 per applikation (uden generiske charts).
  • Charts i applikationens Git-repository.

Vi har talt med alle udviklerne, så migrationsprocessen er allerede startet. Det første trin bliver stadig kontrolleret ved hjælp af CI-platformen. Snart skriver jeg et indlæg om det andet trin: hvordan vi gik over til GitOps-arbejdsgangen med Flux. Jeg vil fortælle, hvordan vi satte det hele op, og hvilke udfordringer vi stødte på (flere repositories, hemmeligheder osv.). Følg med for nyheder.

Her har vi forsøgt at beskrive vores fremskridt i arbejdsgangen for udrulning af applikationer over de seneste år, der har ført til tanker om GitOps-tilgangen. Vi har endnu ikke nået målet, og vi vil rapportere om resultaterne, men vi er i øjeblikket overbeviste om, at vi gjorde det rigtige ved at forenkle alt og nærme os udviklernes vaner.

Kilde: habr.com

Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere 🔥 Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere | ProHoster