
TL;DR
- Per ottenere un'elevata osservabilità dei contenitori e dei microservizi, dei log e delle metriche primarie non basta.
- Per un recupero più rapido e un aumento della resilienza, le applicazioni devono applicare il Principio di alta osservabilità (HOP, High Observability Principle).
- A livello applicativo, per il HOP sono richiesti: un'adeguata registrazione, un attento monitoraggio, verifiche di disponibilità e tracciamento delle prestazioni/transizioni.
- Utilizzate verifiche come elemento di HOP readinessProbe e livenessProbe Kubernetes.
Cos'è un Modello di verifica di disponibilità?
Quando si progetta un'applicazione critica e ad alta disponibilità, è molto importante considerare un aspetto come la resilienza. Un'applicazione è considerata resiliente se riesce a riprendersi rapidamente da un guasto. Un'applicazione cloud tipica utilizza un'architettura a microservizi, dove ogni componente è contenuto in un contenitore separato. E per garantire che l'applicazione su k8s sia altamente disponibile, durante la progettazione del cluster è necessario seguire determinati modelli. Tra questi c'è il Modello di verifica di disponibilità. Esso definisce come l'applicazione comunica a k8s la propria disponibilità. Non si tratta solo di informazioni su se il pod è attivo, ma anche su come questo gestisce le richieste e risponde. Maggiore è la conoscenza di Kubernetes sulla disponibilità del pod, più intelligenti saranno le decisioni prese sulla gestione del traffico e sul bilanciamento del carico. In questo modo, il Principio di alta osservabilità aiuta l'applicazione a rispondere tempestivamente alle richieste.
Principio di alta osservabilità (HOP)
Il principio di alta osservabilità è uno dei . Nell'architettura a microservizi, ai servizi non importa come viene elaborata la loro richiesta (ed è corretto), ma è fondamentale sapere come ottenere le risposte dai servizi riceventi. Ad esempio, per l'autenticazione di un utente, un contenitore invia una richiesta HTTP a un altro, aspettandosi una risposta in un formato specifico: tutto qui. La richiesta può essere elaborata anche da PythonJS, mentre a rispondere può essere Python Flask. I contenitori sono l'uno per l'altro come delle scatole nere con contenuti nascosti. Tuttavia, il principio di NORN richiede che ogni servizio riveli diversi endpoint API, mostrando quanto sia operativo, nonché lo stato della sua disponibilità e resilienza. Questi indicatori sono quelli richiesti da Kubernetes per pianificare i prossimi passi nella routinizzazione e nel bilanciamento del carico.
Un'applicazione cloud ben progettata registra i propri eventi principali utilizzando i flussi standard di input-output STDERR e STDOUT. Successivamente, un servizio ausiliario, come filebeat, logstash o fluentd, consegna i registri a un sistema di monitoraggio centralizzato (ad esempio Prometheus) e a un sistema di raccolta dei registri (un set di software ELK). Nello schema sottostante è illustrato come un'applicazione cloud operi secondo il Modello di Verifica dell'Operatività e il Principio di Alta Osservabilità.

Come applicare il Modello di Verifica dell'Operatività in Kubernetes?
Di default, k8s monitora lo stato dei pod attraverso uno dei controllori (, , , e altri, ecc.). Se un pod fallisce per qualche motivo, il controllore cercherà di riavviarlo o riprogrammarlo su un altro nodo. Tuttavia, un pod potrebbe segnalare di essere avviato e funzionante, mentre in realtà non sta operando. Facciamo un esempio: la tua applicazione utilizza Apache come server web e hai installato il componente su diversi pod del cluster. Poiché la libreria è stata configurata in modo errato, tutte le richieste all'applicazione restituiscono un codice 500 (errore interno del server). Durante il controllo della fornitura, la verifica dello stato dei pod dà esito positivo, ma i clienti vedono la situazione diversamente. Questa situazione indesiderata può essere descritta nel seguente modo:

Nel nostro esempio, k8s esegue la verifica dell'operatività. In questo tipo di controllo, kubelet verifica costantemente lo stato del processo nel contenitore. Appena capisce che il processo si è bloccato, lo riavvia. Se l'errore si risolve semplicemente riavviando l'applicazione, e il programma è progettato per fermarsi in caso di errore, allora per seguire le NORME e il Modello di controllo dell'operatività è sufficiente controllare l'operatività del processo. Peccato però che non tutti gli errori si risolvano con un riavvio. In questo caso, k8s offre 2 metodi più approfonditi per diagnosticare i problemi nel funzionamento del pod: e .
LivenessProbe
Durante livenessProbe kubelet esegue 3 tipi di verifiche: non solo determina se il pod è attivo, ma anche se è pronto a ricevere e rispondere adeguatamente alle richieste:
- Impostare una richiesta HTTP al pod. La risposta deve contenere un codice di stato HTTP compreso tra 200 e 399. Pertanto, i codici 5xx e 4xx segnalano che ci sono problemi con il pod, anche se il processo è attivo.
- Per controllare i pod con servizi non HTTP (ad esempio, il server di posta Postfix), è necessario stabilire una connessione TCP.
- Esecuzione di un comando arbitrario per il pod (internamente). Il controllo è considerato riuscito se il codice di uscita del comando è 0.
Ecco un esempio di come funziona. La definizione del seguente pod contiene un'applicazione NodeJS che restituisce un errore 500 per le richieste HTTP. Per assicurarci che il contenitore venga riavviato in caso di un errore del genere, 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: 5Non si discosta da qualsiasi altra definizione di pod, ma aggiungiamo l'oggetto .spec.containers.livenessProbe. Il parametro httpGet che accetta il percorso al quale invia la richiesta HTTP GET (nel nostro esempio è /, ma in scenari di produzione potrebbe anche essere qualcosa come /api/v1/status). Inoltre, l'livenessProbe accetta il parametro initialDelaySeconds, che prescrive all'operazione di controllo di attendere un determinato numero di secondi. È necessaria una pausa poiché il contenitore ha bisogno di tempo per avviarsi e, durante il riavvio, non sarà disponibile per un certo periodo.
Per applicare questa configurazione al cluster, utilizza:
kubectl apply -f pod.yamlDopo alcuni secondi, puoi controllare il contenuto del pod usando il seguente comando:
kubectl describe pods node500Alla fine dell'output, cerca .
Come potete vedere, l'livenessProbe ha inviato una richiesta HTTP GET, il contenitore ha restituito un errore 500 (come programmato), kubelet lo ha riavviato.
Se siete interessati a come è stato programmato l'applicativo NodeJS, ecco il file app.js e il 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 che l'livenessProbe riavvierà il contenitore solo in caso di malfunzionamento. Se il riavvio non risolve l'errore che impedisce il funzionamento del contenitore, kubelet non potrà prendere provvedimenti per risolvere il guasto.
readinessProbe
La readinessProbe funziona in modo simile alle livenessProbe (richieste GET, TCP di connessione ed esecuzione di comandi), tranne per le azioni di risoluzione dei problemi. Un contenitore che ha riscontrato un errore non viene riavviato, ma isolato dal traffico in entrata. Immaginate che uno dei contenitori stia eseguendo molti calcoli o sia sotto un carico pesante, il che aumenta il tempo di risposta alle richieste. Nel caso della livenessProbe viene attivato un controllo di disponibilità della risposta (attraverso il parametro di controllo timeoutSeconds), dopodiché kubelet riavvia il contenitore. Durante l'avvio, il contenitore inizia a eseguire operazioni intensive in termini di risorse, e viene riavviato nuovamente. Questo può essere critico per le applicazioni in cui la velocità di risposta è importante. Ad esempio, un veicolo in movimento attende una risposta dal server, se la risposta è in ritardo, il veicolo va in crash.
Scriviamo una definizione per la readinessProbe, che stabilisca un tempo di risposta per la richiesta GET non superiore a due secondi, mentre l'applicazione risponde alla richiesta GET dopo 5 secondi. Il file pod.yaml dovrebbe apparire come segue:
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: 2Distribuiamo il pod con kubectl:
kubectl apply -f pod.yamlAspettiamo un paio di secondi e poi vediamo come ha funzionato la readinessProbe:
kubectl describe pods nodedelayedAlla fine dell'output è possibile vedere che parte degli eventi è simile a .
Come potete vedere, kubectl non ha riavviato il pod quando il tempo di controllo ha superato i 2 secondi. Invece ha annullato la richiesta. Le connessioni in entrata vengono reindirizzate ad altri pod funzionanti.
Nota: ora che il carico superfluo è stato rimosso dal pod, kubectl reindirizza nuovamente le richieste a esso: le risposte alle richieste GET non sono più ritardate.
A titolo di confronto: ecco 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 in esecuzione sulla porta 3000')
})TL;DR
Prima dell'emergere delle applicazioni cloud, i log erano il principale strumento di monitoraggio e verifica dello stato delle applicazioni. Tuttavia, non c'erano strumenti per prendere provvedimenti per risolvere i problemi. I log sono ancora utili oggi, devono essere raccolti e inviati a un sistema di raccolta log per analizzare situazioni di emergenza e prendere decisioni. [tutto ciò era possibile anche senza le applicazioni cloud, ad esempio con monit, ma con k8s è diventato molto più semplice 🙂 – nota dell'editore. ]
Oggi, le correzioni devono essere apportate quasi in tempo reale, quindi le applicazioni non devono più essere scatole nere. No, devono mostrare i punti finali, consentendo ai sistemi di monitoraggio di richiedere e raccogliere dati preziosi sullo stato dei processi, in modo da poter reagire istantaneamente se necessario. Questo è chiamato modello di progettazione per il controllo della salute, che segue il principio di alta osservabilità (HOP).
Kubernetes offre di default 2 tipi di controlli di salute: readinessProbe e livenessProbe. Entrambi utilizzano gli stessi tipi di controlli (richieste HTTP GET, connessioni TCP ed esecuzione di comandi). Si differenziano per le decisioni che prendono in risposta ai problemi nei pod. La livenessProbe riavvia il container sperando che l'errore non si ripresenti, mentre la readinessProbe isola il pod dal traffico in entrata fino a quando non viene risolta la causa del problema.
Un corretto design dell'applicazione dovrebbe includere entrambi i tipi di controllo e raccogliere dati sufficienti, specialmente quando si verifica una situazione eccezionale. Dovrebbe anche mostrare i punti finali API necessari, che forniscano al sistema di monitoraggio (allo stesso Prometheus) metriche importanti sullo stato di salute dell'applicazione.
Fonte: habr.com
