Come Dailymotion utilizza Kubernetes: distribuzione delle applicazioni
Noi di Dailymotion abbiamo iniziato a usare Kubernetes in produzione 3 anni fa. Tuttavia, distribuire applicazioni su più cluster è tutt'altro che semplice, quindi negli ultimi anni abbiamo cercato di migliorare i nostri strumenti e flussi di lavoro.
Da che cosa è iniziato
Qui spieghiamo come distribuiamo le nostre applicazioni su più cluster Kubernetes in tutto il mondo.
Per distribuire più oggetti Kubernetes contemporaneamente, usiamo , e tutti i nostri chart sono archiviati in un unico repository git. Per distribuire l'intero stack dell'applicazione composto da più servizi, utilizziamo quello che chiamiamo un chart aggregato. Fondamentalmente, si tratta di un chart che dichiara le dipendenze e consente di inizializzare l'API e i suoi servizi con un solo comando.
Inoltre, abbiamo scritto un piccolo script Python sopra Helm per fare controlli, creare chart, aggiungere segreti e distribuire applicazioni. Tutte queste operazioni vengono eseguite su una piattaforma CI centrale utilizzando un'immagine docker.
Andiamo al sodo.
Nota. Quando leggi questo, il primo release candidate di Helm 3 è già stato annunciato. La versione principale contiene un'intera serie di miglioramenti volti a risolvere alcuni problemi che abbiamo affrontato in passato.
Flusso di lavoro per lo sviluppo dei chart
Per le applicazioni utilizziamo il branching, e abbiamo deciso di applicare lo stesso approccio ai chart.
- Il ramo dev viene utilizzato per creare chart che saranno testati sui cluster di sviluppo.
- Quando la pull request viene inviata a master, vengono controllate in staging.
- Infine, creiamo una pull request per trasferire le modifiche al ramo prod e applicarle in produzione.
Ogni ambiente ha il suo repository privato che conserva i nostri chart, e utilizziamo con API molto utili. In questo modo garantiamo un'adeguata isolamento tra gli ambienti e la verifica dei chart in condizioni reali prima di utilizzarli in produzione.
Repository di chart nei diversi ambienti
Vale la pena notare che quando gli sviluppatori inviano un ramo dev, la versione del loro chart viene automaticamente inviata a dev Chartmuseum. In questo modo, tutti gli sviluppatori utilizzano un unico repository dev, e è fondamentale specificare accuratamente la propria versione del chart per non utilizzare accidentalmente modifiche di altri.
Inoltre, il nostro piccolo script Python verifica gli oggetti Kubernetes secondo le specifiche di Kubernetes OpenAPI utilizzando , prima di pubblicarli su Chartmusem.
Descrizione generale del flusso di lavoro per lo sviluppo del chart
- Impostazione delle attività della pipeline secondo le specifiche per il controllo qualità (lint, unit-test).
- Invio dell'immagine docker con strumenti Python, che distribuiscono le nostre applicazioni.
- Impostazione dell'ambiente in base al nome del ramo.
- Verifica dei file yaml Kubernetes utilizzando Kubeval.
- Aumento automatico della versione del chart e dei suoi chart parent (chart che dipendono dal chart modificato).
- Invio del chart a Chartmuseum, che corrisponde al suo ambiente
Gestione delle differenze nei cluster
Federazione dei cluster
C'era un tempo in cui usavamo , dove era possibile dichiarare oggetti Kubernetes da un unico endpoint API. Ma sono emersi problemi. Ad esempio, alcuni oggetti Kubernetes non potevano essere creati nell'endpoint di federazione, quindi era difficile gestire oggetti aggregati e altri oggetti per singoli cluster.
Per risolvere il problema, abbiamo iniziato a gestire i cluster in modo indipendente, il che ha semplificato notevolmente il processo (abbiamo utilizzato la prima versione della federazione; nella seconda potrebbe essere cambiato qualcosa).
Piattaforma geograficamente distribuita
Attualmente, la nostra piattaforma è distribuita in 6 regioni — 3 localmente e 3 nel cloud.
Distribuzione distribuita
Valori globali di Helm
4 valori globali di Helm consentono di definire le differenze tra i cluster. Per tutti i nostri chart ci sono valori minimi predefiniti.
global:
cloud: True
env: staging
region: us-central1
clusterName: staging-us-central1Valori globali
Questi valori aiutano a definire il contesto per le nostre applicazioni e vengono utilizzati per diversi compiti: monitoraggio, tracciamento, registrazione, effettuare chiamate esterne, scalabilità, ecc.
- «cloud»: abbiamo una piattaforma Kubernetes ibrida. Ad esempio, il nostro API è distribuito nelle zone GCP e nei nostri datacenter.
- «env»: alcuni valori possono cambiare per gli ambienti non lavorativi. Ad esempio, definizioni delle risorse e configurazioni di autoscalamento.
- «region»: queste informazioni aiutano a definire la posizione del cluster e possono essere utilizzate per determinare i punti finali più vicini per i servizi esterni.
- «clusterName»: se e quando vogliamo definire un valore per un singolo cluster.
Ecco un esempio concreto:
{{
/* Restituisce il numero di repliche dell'Horizontal Pod Autoscaler per 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 -}}Esempio di template Helm
Questa logica è definita in un template ausiliario, per evitare di appesantire il YAML di Kubernetes.
Dichiarazione dell'applicazione
I nostri strumenti di distribuzione si basano su diversi file YAML. Di seguito è riportato un esempio di come dichiariamo il servizio e la sua topologia di scaling (numero di repliche) nel cluster.
releases:
- foo.world
foo.world: # Nome della release
services: # Elenco delle app/progetti di 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: 3Definizione del servizio
Questa è la schematizzazione di tutti i passaggi che definiscono il nostro flusso di lavoro di distribuzione. L'ultimo passaggio distribuisce l'applicazione simultaneamente su più cluster di lavoro.
Passaggi di distribuzione in Jenkins
E i segreti?
Per quanto riguarda la sicurezza, monitoriamo tutti i segreti provenienti da diverse fonti e li conserviamo in uno storage unico a Parigi.
I nostri strumenti di distribuzione estraggono i valori dei segreti da Vault e, quando è il momento della distribuzione, li inseriscono in Helm.
Per questo abbiamo definito una mappatura tra i segreti nel Vault e i segreti necessari alle nostre applicazioni:
segreti:
- secret_id: "stack1-app1-password"
contesti:
- nome: "default"
vaultPath: "\/kv\/dev\/stack1\/app1\/test"
vaultKey: "password"
- nome: "cluster1"
vaultPath: "\/kv\/dev\/stack1\/app1\/test"
vaultKey: "password"- Abbiamo stabilito delle regole generali da seguire quando si registrano segreti in Vault.
- Se il segreto appartiene a un contesto o cluster specifico, è necessario aggiungere una registrazione specifica. (Qui il contesto cluster1 ha un proprio valore per il segreto stack-app1-password).
- Altrimenti viene utilizzato il valore predefinito.
- Per ogni elemento in questo elenco nel segreto Kubernetes viene inserita una coppia chiave-valore. Pertanto, il modello del segreto nei nostri chart è molto semplice.
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: OpaqueProblemi e limitazioni
Lavorare con più repository
Attualmente stiamo separando lo sviluppo di chart e applicazioni. Ciò significa che gli sviluppatori devono lavorare in due repository git: uno per l'applicazione e l'altro per definire il suo deployment in Kubernetes. 2 repository git significano 2 flussi di lavoro e per un principiante è facile confondersi.
Gestire chart generici è problematico
Come abbiamo già detto, i chart generici sono molto utili per definire dipendenze e per il rapido deployment di più applicazioni. Ma usiamo --reuse-values, per evitare di passare ogni volta tutti i valori quando effettuiamo il deployment di un'applicazione inclusa in questo chart generico.
Nel processo di delivery continuo abbiamo solo due valori che cambiano regolarmente: il numero di repliche e il tag dell'immagine (versione). Altri valori, più stabili, vengono modificati manualmente, ed è piuttosto complicato. Inoltre, un errore nel dispiegamento di un chart generico può portare a gravi malfunzionamenti, come abbiamo constatato sulla nostra esperienza.
Aggiornamento di più file di configurazione
Quando uno sviluppatore aggiunge una nuova applicazione, deve modificare diversi file: la dichiarazione dell'applicazione, l'elenco dei segreti, l'aggiunta dell'applicazione alle dipendenze, se è inclusa nel chart generico.
I permessi di Jenkins sono troppo ampi in Vault
Ora abbiamo uno , che legge tutti i segreti da Vault.
Il processo di rollback non è automatizzato
Per il rollback è necessario eseguire il comando su più cluster, e ciò è soggetto a errori. Eseguiamo questa operazione manualmente per garantire di specificare l'identificatore di versione corretto.
Stiamo andando verso GitOps
Il nostro obiettivo
Vogliamo restituire il chart nel repository dell'applicazione che dispiega.
Il flusso di lavoro sarà lo stesso di quello per lo sviluppo. Ad esempio, quando un ramo viene inviato al master, il dispiegamento verrà avviato automaticamente. La principale differenza tra questo approccio e l'attuale flusso di lavoro sarà che tutto sarà gestito in git (l'applicazione stessa e il modo in cui viene dispiegata in Kubernetes).
Ci sono diversi vantaggi:
- Molto più chiaro per lo sviluppatore. È più facile imparare ad applicare modifiche nel chart locale.
- La definizione del dispiegamento del servizio può essere specificata lì dove c'è il codice del servizio.
- Gestione della rimozione dei chart generici. Il servizio avrà il proprio rilascio di Helm. Questo permetterà di gestire il ciclo di vita dell'applicazione (rollback, upgrade) a un livello molto fine, senza influenzare altri servizi.
- Vantaggi di git per la gestione dei chart: annullamento delle modifiche, registro delle modifiche, ecc. Se è necessario annullare una modifica del chart, ciò può essere fatto tramite git. Il dispiegamento viene avviato automaticamente.
- Si potrebbe pensare di migliorare il flusso di lavoro dello sviluppo utilizzando strumenti come Skaffold, con il quale gli sviluppatori possono testare le modifiche in un contesto simile alla produzione.
Migrazione in due fasi
I nostri sviluppatori utilizzano questo workflow da 2 anni, quindi abbiamo bisogno di una migrazione il più indolore possibile. Per questo motivo abbiamo deciso di aggiungere una fase intermedia verso l'obiettivo.
La prima fase è semplice:
- Manteniamo una struttura simile per la configurazione del deployment delle applicazioni, ma in un unico oggetto chiamato 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 rilascio per applicazione (senza chart generali).
- Charts nel repository git dell'applicazione.
Abbiamo parlato con tutti gli sviluppatori, quindi il processo di migrazione è già iniziato. La prima fase è ancora sotto controllo utilizzando la piattaforma CI. Presto scriverò un altro post sulla seconda fase: come siamo passati al workflow GitOps con . Racconterò come abbiamo impostato tutto e quali difficoltà abbiamo affrontato (diversi repository, segreti, ecc.). Rimanete sintonizzati.
Qui abbiamo cercato di descrivere i nostri progressi nel workflow di deployment delle applicazioni negli ultimi anni, che ci hanno portato a riflettere sull'approccio GitOps. Non abbiamo ancora raggiunto l'obiettivo e comunicheremo i risultati, ma siamo già convinti di aver fatto la scelta giusta nel semplificare tutto e avvicinarci alle abitudini degli sviluppatori.
Fonte: habr.com
