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 , 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 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 , før de offentliggøres i Chartmuseum.
Generel beskrivelse af workflowet for chart-udvikling
- Opsætning af pipeline-opgaver i henhold til specifikationen til kvalitetskontrol (lint, enhedstest).
- Send docker-billedet med de Python-værktøjer, der implementerer vores applikationer.
- Opsætning af miljøet ved navn gren.
- Validering af Kubernetes yaml-filer ved hjælp af Kubeval.
- Automatisk opgradering af versionen på chartet og dets overordnede charts (charts, der afhænger af det ændrede chart).
- Send chartet til Chartmuseum, som svarer til dets miljø
Håndtering af forskelle mellem klynger
Klyngeføderation
Der var en tid, hvor vi brugte , 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-central1Globale 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: 3Servicedeklaration
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 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: OpaqueProblemer 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 , 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 . 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
