Best practice per i contenitori Kubernetes: controlli dello stato

Best practice per i contenitori Kubernetes: controlli dello stato

TL; DR

  • Per ottenere un’elevata osservabilità di contenitori e microservizi, i log e le metriche primarie non sono sufficienti.
  • Per un ripristino più rapido e una maggiore resilienza, le applicazioni dovrebbero applicare il principio di alta osservabilità (HOP).
  • A livello di applicazione, il NOP richiede: registrazione adeguata, monitoraggio attento, controlli di integrità e tracciamento delle prestazioni/transizioni.
  • Utilizzare gli assegni come elemento del NOR prontezza и Sonda di vivacità Kubernetes.

Che cos'è un modello di controllo dello stato?

Quando si progetta un'applicazione mission-critical e altamente disponibile, è molto importante pensare a un aspetto come la tolleranza agli errori. Un'applicazione è considerata tollerante agli errori se si riprende rapidamente dopo un errore. Una tipica applicazione cloud utilizza un'architettura a microservizi, in cui ciascun componente è inserito in un contenitore separato. E per garantire che l'applicazione su K8 sia altamente disponibile quando si progetta un cluster, è necessario seguire determinati schemi. Tra questi c'è il modello di controllo dello stato. Definisce il modo in cui l'applicazione comunica a k8 che è integra. Non si tratta solo di informazioni sul fatto che il pod sia in esecuzione, ma anche su come riceve e risponde alle richieste. Più Kubernetes conosce lo stato del pod, più decisioni intelligenti prenderà sull'instradamento del traffico e sul bilanciamento del carico. Pertanto, il Principio di Alta Osservabilità consente all’applicazione di rispondere alle richieste in modo tempestivo.

Principio di alta osservabilità (HOP)

Il principio di alta osservabilità è uno di principi per la progettazione di applicazioni containerizzate. In un'architettura di microservizi, ai servizi non interessa il modo in cui viene elaborata la loro richiesta (e giustamente), ma ciò che conta è il modo in cui ricevono le risposte dai servizi riceventi. Ad esempio, per autenticare un utente, un contenitore invia una richiesta HTTP a un altro, aspettandosi una risposta in un determinato formato: tutto qui. PythonJS può anche elaborare la richiesta e Python Flask può rispondere. I contenitori sono come scatole nere con contenuti nascosti tra loro. Tuttavia, il principio NOP richiede che ciascun servizio esponga più endpoint API che ne indichino lo stato di integrità, nonché la sua disponibilità e lo stato di tolleranza agli errori. Kubernetes richiede questi indicatori per riflettere sui passaggi successivi per il routing e il bilanciamento del carico.

Un'applicazione cloud ben progettata registra i suoi eventi principali utilizzando i flussi I/O standard STDERR e STDOUT. Poi c'è un servizio ausiliario, ad esempio filebeat, logstash o fluentd, che fornisce i log a un sistema di monitoraggio centralizzato (ad esempio Prometheus) e un sistema di raccolta dei log (suite software ELK). Il diagramma seguente mostra come funziona un'applicazione cloud secondo il modello di test di integrità e il principio di alta osservabilità.

Best practice per i contenitori Kubernetes: controlli dello stato

Come applicare il modello di controllo dello stato in Kubernetes?

Appena pronto, k8s monitora lo stato dei pod utilizzando uno dei controller (Distribuzioni, ReplicaSets, DemonSet, StatefulSet ecc. ecc.). Avendo scoperto che il pod è caduto per qualche motivo, il controller prova a riavviarlo o a spostarlo su un altro nodo. Tuttavia, un pod potrebbe segnalare di essere attivo e funzionante, ma di per sé non funziona. Facciamo un esempio: la tua applicazione utilizza Apache come server web, hai installato il componente su più pod del cluster. Poiché la libreria è stata configurata in modo errato, tutte le richieste all'applicazione rispondono con il codice 500 (errore interno del server). Quando si controlla la consegna, il controllo dello stato dei pod dà un risultato positivo, ma i clienti la pensano diversamente. Descriveremo questa situazione indesiderabile come segue:

Best practice per i contenitori Kubernetes: controlli dello stato

Nel nostro esempio, k8s lo fa controllo funzionalità. In questo tipo di verifica, il kubelet controlla continuamente lo stato del processo nel contenitore. Una volta capito che il processo si è interrotto, lo riavvierà. Se l'errore può essere risolto semplicemente riavviando l'applicazione e il programma è progettato per spegnersi in caso di errore, è sufficiente un controllo dello stato del processo per seguire il NOP e il modello di test dello stato. L'unico peccato è che non tutti gli errori vengono eliminati riavviando. In questo caso, k8s offre 2 modi più profondi per identificare i problemi con il pod: Sonda di vivacità и prontezza.

Sonda di vitalità

È ora Sonda di vivacità kubelet esegue 3 tipi di controlli: non solo determina se il pod è in esecuzione, ma anche se è pronto a ricevere e rispondere adeguatamente alle richieste:

  • Configura una richiesta HTTP al pod. La risposta deve contenere un codice di risposta HTTP compreso tra 200 e 399. Pertanto, i codici 5xx e 4xx segnalano che il pod ha problemi, anche se il processo è in esecuzione.
  • Per testare i pod con servizi non HTTP (ad esempio, il server di posta Postfix), devi stabilire una connessione TCP.
  • Esegui un comando arbitrario per un pod (internamente). Il controllo viene considerato positivo se il codice di completamento del comando è 0.

Un esempio di come funziona. La successiva definizione del pod contiene un'applicazione NodeJS che genera un errore 500 sulle richieste HTTP. Per garantire che il contenitore venga riavviato quando si riceve un errore di questo tipo, utilizziamo il parametro livenessProbe:

apiVersion: v1
kind: Pod
metadata:
 name: node500
spec:
 containers:
   - image: magalix/node500
     name: node500
     ports:
       - containerPort: 3000
         protocol: TCP
     livenessProbe:
       httpGet:
         path: /
         port: 3000
       initialDelaySeconds: 5

Questa non è diversa da qualsiasi altra definizione di pod, ma stiamo aggiungendo un oggetto .spec.containers.livenessProbe... Parametro httpGet accetta il percorso a cui viene inviata la richiesta HTTP GET (nel nostro esempio questo è /, ma negli scenari di combattimento potrebbe esserci qualcosa di simile /api/v1/status). Un altro livenessProbe accetta un parametro initialDelaySeconds, che indica all'operazione di verifica di attendere un numero specificato di secondi. Il ritardo è necessario perché il contenitore ha bisogno di tempo per avviarsi e, una volta riavviato, non sarà disponibile per un po' di tempo.

Per applicare questa impostazione a un cluster, utilizzare:

kubectl apply -f pod.yaml

Dopo alcuni secondi, puoi controllare il contenuto del pod utilizzando il seguente comando:

kubectl describe pods node500

Alla fine dell'output, trova ecco cosa.

Come puoi vedere, livenessProbe ha avviato una richiesta HTTP GET, il contenitore ha generato un errore 500 (che è ciò per cui era stato programmato) e kubelet lo ha riavviato.

Se ti stai chiedendo come è stata programmata l'applicazione NideJS, ecco app.js e Dockerfile che sono stati utilizzati:

app.js

var http = require('http');

var server = http.createServer(function(req, res) {
    res.writeHead(500, { "Content-type": "text/plain" });
    res.end("We have run into an errorn");
});

server.listen(3000, function() {
    console.log('Server is running at 3000')
})

Dockerfile

FROM node
COPY app.js /
EXPOSE 3000
ENTRYPOINT [ "node","/app.js" ]

È importante notare questo: livenessProbe riavvierà il contenitore solo se fallisce. Se un riavvio non corregge l'errore che impedisce l'esecuzione del contenitore, kubelet non sarà in grado di intraprendere azioni per correggere il problema.

prontezza

readinessProbe funziona in modo simile a livenessProbes (richieste GET, comunicazioni TCP ed esecuzione di comandi), ad eccezione delle azioni di risoluzione dei problemi. Il contenitore in cui viene rilevato l'errore non viene riavviato, ma viene isolato dal traffico in entrata. Immagina che uno dei contenitori stia eseguendo molti calcoli o sia sottoposto a un carico pesante, causando un aumento dei tempi di risposta. Nel caso di livenessProbe viene attivato il controllo della disponibilità della risposta (tramite il parametro di controllo timeoutSeconds), dopodiché il kubelet riavvia il contenitore. Una volta avviato, il contenitore inizia a eseguire attività ad uso intensivo di risorse e viene riavviato nuovamente. Questo può essere fondamentale per le applicazioni che necessitano di velocità di risposta. Ad esempio, un'auto mentre è in viaggio attende una risposta dal server, la risposta viene ritardata e l'auto subisce un incidente.

Scriviamo una definizione redinessProbe che imposterà il tempo di risposta della richiesta GET su un massimo di due secondi e l'applicazione risponderà alla richiesta GET dopo 5 secondi. Il file pod.yaml dovrebbe assomigliare a questo:

apiVersion: v1
kind: Pod
metadata:
 name: nodedelayed
spec:
 containers:
   - image: afakharany/node_delayed
     name: nodedelayed
     ports:
       - containerPort: 3000
         protocol: TCP
     readinessProbe:
       httpGet:
         path: /
         port: 3000
       timeoutSeconds: 2

Distribuiamo un pod con kubectl:

kubectl apply -f pod.yaml

Aspettiamo un paio di secondi e poi vediamo come ha funzionato readinessProbe:

kubectl describe pods nodedelayed

Alla fine dell'output puoi vedere che alcuni eventi sono simili questo.

Come puoi vedere, kubectl non ha riavviato il pod quando il tempo di controllo ha superato i 2 secondi. Invece ha annullato la richiesta. Le comunicazioni in entrata vengono reindirizzate ad altri pod funzionanti.

Tieni presente che ora che il pod è stato scaricato, kubectl instrada nuovamente le richieste ad esso: le risposte alle richieste GET non vengono più ritardate.

Per confronto, di seguito è riportato il file app.js modificato:

var http = require('http');

var server = http.createServer(function(req, res) {
   const sleep = (milliseconds) => {
       return new Promise(resolve => setTimeout(resolve, milliseconds))
   }
   sleep(5000).then(() => {
       res.writeHead(200, { "Content-type": "text/plain" });
       res.end("Hellon");
   })
});

server.listen(3000, function() {
   console.log('Server is running at 3000')
})

TL; DR
Prima dell'avvento delle applicazioni cloud, i log erano il mezzo principale per monitorare e verificare lo stato delle applicazioni. Tuttavia, non vi era alcuna possibilità di intraprendere alcuna azione correttiva. I log sono utili ancora oggi; devono essere raccolti e inviati a un sistema di raccolta log per analizzare le situazioni di emergenza e prendere decisioni. [Tutto questo potrebbe essere fatto senza applicazioni cloud utilizzando monit, ad esempio, ma con k8s è diventato molto più semplice :) – ndr. ]

Oggi le correzioni devono essere apportate quasi in tempo reale, quindi le applicazioni non devono più essere scatole nere. No, dovrebbero mostrare endpoint che consentano ai sistemi di monitoraggio di interrogare e raccogliere dati preziosi sullo stato dei processi in modo che possano rispondere immediatamente se necessario. Questo è chiamato Performance Test Design Pattern, che segue il principio dell'alta osservabilità (HOP).

Kubernetes offre 2 tipi di controlli di integrità per impostazione predefinita: readinessProbe e livenessProbe. Entrambi utilizzano gli stessi tipi di controlli (richieste HTTP GET, comunicazioni TCP ed esecuzione di comandi). Differiscono nelle decisioni che prendono in risposta ai problemi nei pod. livenessProbe riavvia il contenitore nella speranza che l'errore non si ripeta e readinessProbe isola il pod dal traffico in entrata finché la causa del problema non viene risolta.

Una corretta progettazione dell'applicazione dovrebbe includere entrambi i tipi di controllo e garantire che raccolgano dati sufficienti, soprattutto quando viene generata un'eccezione. Dovrebbe anche mostrare gli endpoint API necessari che forniscono al sistema di monitoraggio (Prometheus) importanti parametri sanitari.

Fonte: habr.com

Aggiungi un commento