Ampliare e completare Kubernetes (panoramica e video della presentazione)

Ampliare e completare Kubernetes (panoramica e video della presentazione)

8 aprile alla conferenza Saint HighLoad++ 2019, nel contesto della sezione «DevOps e Operations», è stata presentata la relazione «Espandere e completare Kubernetes», alla quale hanno partecipato tre dipendenti dell'azienda «Flant». In essa parliamo di diversi casi in cui abbiamo voluto ampliare e arricchire le funzionalità di Kubernetes, ma non abbiamo trovato una soluzione pronta e semplice. Le soluzioni necessarie sono emerse sotto forma di progetti Open Source, e il nostro intervento è dedicato anche a questo.

Seguendo la tradizione, siamo lieti di presentare il video della presentazione (50 minuti, molto più informativo di un articolo) e una sintesi testuale. Cominciamo!

Nucleo e aggiunte in K8s

Kubernetes sta cambiando il settore e gli approcci all'amministrazione, che erano consolidati da tempo:

  • Grazie alle sue astrazioni, non operiamo più con concetti come configurazione del file o esecuzione di comandi (Chef, Ansible…), ma utilizziamo raggruppamenti di contenitori, servizi e così via.
  • Possiamo sviluppare applicazioni senza preoccuparci dei dettagli del specifico ambiente, su cui verranno eseguite: bare metal, cloud di uno dei fornitori, ecc.
  • Con K8s, le migliori pratiche per l'organizzazione dell'infrastruttura sono diventate più accessibili: tecniche di scalabilità, auto-ripristino, tolleranza ai guasti, ecc.

Tuttavia, ovviamente, non tutto è così semplice: con Kubernetes sono arrivate anche nuove sfide.

Kubernetes non è un combinato disposto che risolve tutti i problemi degli utenti. Il kernel Kubernetes offre solo un insieme di funzioni minimali, che sono presenti in ogni cluster:

Ampliare e completare Kubernetes (panoramica e video della presentazione)

Nel nucleo di Kubernetes è definito un set base di primitive - per raggruppare contenitori, gestire il traffico e così via. Abbiamo approfondito questo argomento nella relazione di due anni fa..

Ampliare e completare Kubernetes (panoramica e video della presentazione)

D'altra parte, K8s offre fantastiche possibilità di espandere le funzionalità disponibili, aiutando a soddisfare anche altre esigenze - specifiche - degli utenti. Le aggiunte in Kubernetes sono gestite dagli amministratori di cluster, che devono installare e configurare tutto il necessario per far sì che il loro cluster «acquisisca la forma desiderata» [per risolvere i loro compiti specifici]. Quali sono quindi queste aggiunte? Esaminiamo alcuni esempi.

Esempi di aggiunte

Dopo aver installato Kubernetes, potremmo rimanere sorpresi che la rete, fondamentale per l'interazione tra pod all'interno di un nodo e tra nodi, non funziona automaticamente. Il nucleo di Kubernetes non garantisce i collegamenti necessari - invece, definisce una interfaccia (CNI) per le aggiunte di terze parti. Dobbiamo installare una di queste aggiunte, che si occuperà della configurazione della rete.

Ampliare e completare Kubernetes (panoramica e video della presentazione)

Un esempio simile sono le soluzioni per l'archiviazione (disco locale, dispositivo di archiviazione di rete, Ceph…). Inizialmente erano presenti nel nucleo, ma con l'emergere CSI la situazione cambia in modo simile a quanto già descritto: in Kubernetes, l'interfaccia è presente, mentre la sua implementazione si trova in moduli esterni.

Tra gli altri esempi:

  • Ingress-controller (la loro panoramica è disponibile in nel nostro recente articolo).
  • cert-manager:

    Ampliare e completare Kubernetes (panoramica e video della presentazione)

  • Operatori - si tratta di una vera e propria classe di aggiunte (tra cui il già citato cert-manager), che definisce primitiva(e) e controllore(i). La logica del loro funzionamento è limitata solo dalla nostra fantasia e consente di trasformare componenti infrastrutturali già pronti (come database) in primitive, molto più semplici da gestire (rispetto a un insieme di contenitori e delle loro configurazioni). Sono stati scritti un'enormità di operatori - anche se molti di essi non sono ancora pronti per la produzione, è solo una questione di tempo:

    Ampliare e completare Kubernetes (panoramica e video della presentazione)

  • Metriche - ulteriore illustrazione di come in Kubernetes sia stata separata l'interfaccia (Metrics API) dall'implementazione (aggiunte di terze parti, come Prometheus adapter, Datadog cluster agent…).
  • Per monitoraggio e statistiche, dove non sono necessari solo Prometheus e Grafana, ma anche kube-state-metrics, node-exporter e così via.

E questo è solo un elenco parziale di aggiunte… Ad esempio, presso l'azienda «Flant», attualmente installiamo 29 aggiunte (tutte insieme creano 249 oggetti Kubernetes). In altre parole, non vediamo la vita di un cluster senza aggiunte.

Automazione

Gli operatori sono stati creati per automatizzare le operazioni di routine con cui ci confrontiamo quotidianamente. Ecco alcuni esempi dalla vita, per i quali la scrittura di un operatore sarebbe una soluzione ideale:

  1. C'è un registry privato (cioè che richiede accesso) con le immagini per l'applicazione. Si presume che a ogni pod venga associato un segreto speciale che consente di autenticarsi nel registry. Il nostro compito è garantire che questo segreto si trovi nel namespace, in modo che i pod possano scaricare le immagini. Possono esserci molte applicazioni (ciascuna delle quali necessita di un segreto), e i segreti stessi è utile aggiornarli regolarmente, quindi l'opzione di distribuire manualmente i segreti non è praticabile. Qui interviene l'operatore: creiamo un controllore che attenderà la creazione del namespace e, a seguito di questo evento, aggiungerà il segreto al namespace.
  2. Per default, l'accesso a Internet dai pod è vietato. Tuttavia, a volte è necessario: è logico che il meccanismo di autorizzazione funzioni in modo semplice, senza richiedere competenze specifiche, ad esempio sulla base della presenza di un certo label nello namespace. Come può aiutarci l'operatore? Si crea un controller che attende l'apparizione di un label nello namespace e aggiunge la policy corrispondente per l'accesso a Internet.
  3. Una situazione simile: supponiamo che sia necessario aggiungere un certo taint, se ha un label analogo (con un certo prefisso). Le azioni con l'operatore sono evidenti...

In ogni cluster, è necessario affrontare compiti di routine, e formulare correttamente per farlo si utilizzano gli operatori.

Riassumendo tutte le storie descritte, arriviamo alla conclusione che per lavorare comodamente in Kubernetes è necessario: a) installare i componenti aggiuntivi, b) sviluppare operatori (per risolvere le attività amministrative quotidiane).

Come scrivere un operatore per Kubernetes?

In generale, lo schema è semplice:

Ampliare e completare Kubernetes (panoramica e video della presentazione)

… ma qui si scopre che:

  • Kubernetes API è una cosa piuttosto non banale, che richiede un certo tempo per essere padroneggiata;
  • programmazione non è per tutti (il linguaggio Go è scelto come preferito, perché esiste un framework specifico – Operator SDK);
  • la situazione è simile anche con il framework stesso.

Risultato: Per scrivere un controller (operatore) è necessario investire risorse significative per studiare le basi. Questo sarebbe giustificato per "grandi" operatori – ad esempio, per i database MySQL. Ma se ricordiamo gli esempi precedentemente descritti (distribuzione di segreti, accesso dei pod a Internet…), che vogliamo fare correttamente, capiremo che gli sforzi spesi supereranno il risultato desiderato in questo momento:

Ampliare e completare Kubernetes (panoramica e video della presentazione)

In sostanza, si presenta un dilemma: investire molte risorse e ottenere lo strumento giusto per scrivere operatori o agire "alla vecchia maniera" (ma velocemente). Per risolverlo – trovare un compromesso tra queste estremità – abbiamo creato il nostro progetto: shell-operator (vedi anche il suo annuncio recente su Habr).

Shell-operator

Come funziona? Nel cluster c'è un pod, in cui si trova un eseguibile Go con shell-operator. Accanto a lui è conservato un set di hook (maggiori dettagli su di essi – vedi sotto).Lo shell-operator si iscrive a determinati eventi eventi nell'API di Kubernetes, al verificarsi dei quali esegue i relativi hook.

Come fa lo shell-operator a capire quali hook invocare per quali eventi? Queste informazioni sono fornite allo shell-operator stesso dagli hook, e lo fanno in modo molto semplice.

Un hook è uno script in Bash o qualsiasi altro file eseguibile, che supporta un unico argomento --config e restituisce JSON in risposta. Quest'ultimo determina quali oggetti lo interessano e a quali eventi (per questi oggetti) dovrebbe reagire:

Ampliare e completare Kubernetes (panoramica e video della presentazione)

Illustrerò l'implementazione sul shell-operator di uno dei nostri esempi – distribuzione di segreti per accedere a un registry privato con le immagini dell'applicazione. Essa consiste in due fasi.

Pratica: 1. Scriviamo un hook

Per prima cosa, nell'hook gestiremo --config, specificando che ci interessano gli namespace, e in particolare – il momento della loro creazione:

[[ $1 == "--config" ]] ; then
  cat << EOF
{
  "onKubernetesEvent": [
    {
      "kind": "namespace",
      "event": ["add"]
    }
  ]
}
EOF
…

Come apparirà la logica? Anche piuttosto semplice:

…
else
  createdNamespace=$(jq -r '.[0].resourceName' $BINDING_CONTEXT_PATH)
  kubectl create -n ${createdNamespace} -f - << EOF
Kind: Secret
...
EOF
fi

Primo passo: scopriamo quale namespace è stato creato, e secondo – creiamo tramite kubectl un segreto per questo spazio dei nomi.

Pratica: 2. Creiamo un'immagine

Rimane da comunicare l'hook creato allo shell-operator – come fare? Lo shell-operator viene fornito come immagine Docker, quindi il nostro compito è aggiungere l'hook in una directory speciale all'interno di quest'immagine:

FROM flant/shell-operator:v1.0.0-beta.1
ADD my-handler.sh /hooks

Rimane da costruirlo e pusharlo:

$ docker build -t registry.example.com/my-operator:v1 .
$ docker push registry.example.com/my-operator:v1

Ultimo tocco – deployare l'immagine nel cluster. Per fare ciò scriveremo Deployment:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: my-operator
spec:
  template:
    spec:
      containers:
      - name: my-operator
        image: registry.example.com/my-operator:v1 # 1
      serviceAccountName: my-operator              # 2

In esso bisogna prestare attenzione a due aspetti:

  1. l'indicazione dell'immagine appena creata;
  2. questo è un componente di sistema, che ha bisogno (almeno) di diritti per iscriversi agli eventi in Kubernetes e per distribuire segreti sugli namespace, quindi creiamo per l'hook un ServiceAccount (e un insieme di regole).

Il risultato è che abbiamo risolto il nostro problema nel modo nativo

per Kubernetes, creando un operatore per la distribuzione di segreti.

Altre funzionalità dello shell-operator Per limitare gli oggetti del tipo selezionato con cui lavorerà l'hook,è possibile filtrare, selezionando in base a determinati label (o tramite matchExpressions):

"onKubernetesEvent": [
  {
    "selector": {
      "matchLabels": {
        "foo": "bar",
       },
       "matchExpressions": [
         {
           "key": "allow",
           "operation": "In",
           "values": ["wan", "warehouse"],
         },
       ],
     }
     …
  }
]

Previste meccanismo di deduplicazione, che — tramite un filtro jq — consente di trasformare grandi JSON di oggetti in piccoli, mantenendo solo i parametri di cui vogliamo monitorare le modifiche.

Quando viene chiamato l'hook, il shell-operator gli passa dati sull'oggetto, che possono essere utilizzati per qualsiasi necessità.

Gli eventi che attivano gli hook non sono limitati agli eventi di Kubernetes: il shell-operator supporta anche l'attivazione di hook basati sul tempo (simile a crontab in un planner tradizionale), oltre a un evento speciale onStartup. Tutti questi eventi possono essere combinati e assegnati allo stesso hook.

E altre due caratteristiche del shell-operator:

  1. Funziona in modo asincrono. Dalla ricezione di un evento di Kubernetes (ad esempio, la creazione di un oggetto), potrebbero essere accaduti altri eventi nel cluster (ad esempio, la cancellazione dello stesso oggetto), e ciò deve essere considerato negli hook. Se un hook termina con errore, di default verrà riattivato fino a un completamento riuscito (questo comportamento può essere modificato).
  2. Esporta metriche per Prometheus, che consentono di capire se il shell-operator sta funzionando, di conoscere il numero di errori per ogni hook e la dimensione attuale della coda.

In sintesi, questa parte della relazione:

Ampliare e completare Kubernetes (panoramica e video della presentazione)

Installazione di addon

Per un funzionamento ottimale con Kubernetes è stata menzionata anche la necessità di installare addon. Ne parlerò prendendo come esempio il percorso della nostra azienda su come lo facciamo attualmente.

Abbiamo iniziato a lavorare con Kubernetes con alcuni cluster, l'unico addon in essi era Ingress. In ogni cluster doveva essere installato in modo diverso, e abbiamo creato diverse configurazioni YAML per diversi ambienti: bare metal, AWS…

I cluster aumentavano — aumentava anche il numero delle configurazioni. Inoltre, miglioravamo queste configurazioni, diventando piuttosto eterogenee:

Ampliare e completare Kubernetes (panoramica e video della presentazione)

Per mettere tutto in ordine, siamo partiti da uno script (install-ingress.sh), che accettava come argomento il tipo di cluster in cui dovevamo deployare, generava la configurazione YAML necessaria e la invocava in Kubernetes.

In breve, il nostro percorso successivo e le riflessioni ad esso collegate erano le seguenti:

  • per lavorare con le configurazioni YAML è necessario un templater (inizialmente era un semplice sed);
  • con l'aumento del numero dei cluster è emersa la necessità di aggiornamenti automatici (la prima soluzione — abbiamo messo lo script in Git, lo aggiorniamo con cron e lo eseguiamo);
  • uno script simile si è reso necessario per Prometheus (install-prometheus.sh), ma è notevole in quanto richiede molte più informazioni e anche la loro archiviazione (ideale sarebbe centralizzata e nel cluster), e alcune informazioni (le password) potrebbero essere generate automaticamente:

    Ampliare e completare Kubernetes (panoramica e video della presentazione)

  • il rischio di installare qualcosa di errato su un numero crescente di cluster aumentava costantemente, quindi abbiamo capito che agli installer (vale a dire, ai due script: per Ingress e Prometheus) serviva un sistema di staging (diverse branch in Git, diversi cron per il loro aggiornamento nei corrispondenti: cluster stabili o di test);
  • con kubectl apply diventava difficile lavorare, poiché non era dichiarativo e sapeva solo creare oggetti, ma non prendere decisioni sul loro stato/eliminarli;
  • mancavano alcune funzionalità che non avevamo realizzato in quel momento:
    • un controllo completo sui risultati degli aggiornamenti dei cluster,
    • una determinazione automatica di alcuni parametri (input per gli script di installazione) sulla base dei dati che potevano essere ottenuti dal cluster (discovery),
    • il suo sviluppo logico in forma di continuous discovery.

Tutta questa esperienza accumulata l'abbiamo realizzata all'interno di un altro progetto — addon-operator.

Addon-operator

Alla base c'è il già menzionato shell-operator. L'intero sistema appare come segue:

Agli hook del shell-operator vengono aggiunti:

  • un repository di values,
  • Chart Helm,
  • un componente che controlla il repository di values e — in caso di modifiche — chiede a Helm di ri-implementare il chart.

Ampliare e completare Kubernetes (panoramica e video della presentazione)

In questo modo, possiamo reagire a un evento in Kubernetes, attivare un hook e, da questo hook — apportare modifiche al repository, dopo di che il chart sarà ri-implementato. Nello schema risultante evidenziamo un insieme di hook e il chart in un unico componente, che chiamiamo modulo:

Ampliare e completare Kubernetes (panoramica e video della presentazione)

Possono esserci più moduli e a questi aggiungiamo hook globali, un repository globale di values e un componente che controlla questo repository globale.

Ora, quando in Kubernetes accade qualcosa, possiamo rispondere tramite un hook globale e modificare qualcosa nel repository globale. Questa modifica sarà notata e attiverà la ri-implementazione di tutti i moduli nel cluster:

Ampliare e completare Kubernetes (panoramica e video della presentazione)

Questo schema soddisfa tutti i requisiti per l'installazione di addon esposti in precedenza:

  • La templating e la dichiaratività sono gestite da Helm.
  • La questione dell'autoaggiornamento è stata risolta tramite un hook globale, che programma l'accesso al registro e, se rileva un nuovo modello di sistema, lo ricompila (cioè, «si rigenera da solo»).
  • La memorizzazione delle impostazioni nel cluster è implementata tramite ConfigMap, in cui sono registrati i dati primari per gli archivi (alla partenza vengono caricati negli archivi).
  • I problemi di generazione delle password, discovery e continuous discovery sono stati risolti attraverso degli hook.
  • Il staging è stato ottenuto grazie ai tag supportati nativamente da Docker.
  • Il controllo dei risultati avviene tramite metriche, che ci permettono di comprendere lo stato.

Tutto questo sistema è realizzato in un unico binario su Go, che ha preso il nome di addon-operator. Grazie a ciò, lo schema appare più semplice:

Ampliare e completare Kubernetes (panoramica e video della presentazione)

Il componente principale in questo schema è un insieme di moduli (evidenziati in grigio in basso). Ora possiamo scrivere un modulo per l'add-on desiderato con pochi sforzi e avere la certezza che sarà installato in ogni cluster, verrà aggiornato e reagirà agli eventi necessari nel cluster.

Flant utilizza addon-operator in oltre 70 cluster Kubernetes. Stato attuale — versione alfa. Stiamo preparando la documentazione per rilasciare la beta, mentre nel repository sono disponibili esempi, da cui è possibile creare il proprio addon.

Dove trovare i moduli stessi per l'addon-operator? La pubblicazione della propria libreria è il nostro prossimo passo, abbiamo in programma di farlo in estate.

Video e slide

Video della presentazione (~50 minuti):

Riproduci video

Presentazione della relazione:

P.S.

Altre presentazioni nel nostro blog:

Potresti anche essere interessato alle seguenti pubblicazioni:

Fonte: habr.com

Acquista un hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista un hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster