Nota del traduttore.: gli autori di questo articolo sono ingegneri di una piccola azienda ceca chiamata pipetail. Sono riusciti a raccogliere un notevole elenco di [problemi che sono a volte banali, ma ancora] così attuali, legati all'uso di cluster Kubernetes.

Negli anni in cui abbiamo utilizzato Kubernetes, abbiamo lavorato con un gran numero di cluster (sia gestiti che non gestiti, su GCP, AWS e Azure). Col tempo, abbiamo iniziato a notare che alcuni errori si ripetono costantemente. Tuttavia, non c'è nulla di cui vergognarsi: noi stessi abbiamo commesso la maggior parte di essi!
In questo articolo sono raccolti gli errori più comuni, insieme a suggerimenti su come correggerli.
1. Risorse: richieste e limiti
Questo punto merita sicuramente la massima attenzione e il primo posto in lista.
La richiesta CPU di solito è spesso non definita o ha un valore molto basso (per ospitare il maggior numero possibile di pod su ogni nodo). Di conseguenza, i nodi risultano sovraccarichi. Durante un carico elevato, la potenza di calcolo del nodo è completamente utilizzata e il carico di lavoro specifico ottiene solo ciò che ha "richiesto" tramite il throttling della CPU. Questo porta a ritardi aumentati nell'applicazione, timeout e altre conseguenze spiacevoli. (Ulteriori dettagli su questo possono essere trovati in un altro nostro recente articolo: "» — nota del traduttore.)
Miglior sforzo (altamente non consigliato):
resources: {}Una richiesta CPU estremamente bassa (altamente non consigliato):
resources:
Requests:
cpu: "1m"D'altra parte, avere un limite sulla CPU può portare a salti di ciclo ingiustificati da parte dei pod, anche se la CPU del nodo non è completamente caricata. Anche in questo caso, ciò può portare a ritardi aumentati. Le discussioni attorno al parametro CPU CFS quota nel kernel Linux e al throttling della CPU in base ai limiti impostati, così come alla disattivazione della quota CFS... Purtroppo, i limiti sulla CPU possono causare più problemi di quanti ne risolvano. Maggiori dettagli sono disponibili nel link qui sotto.
L'overcommitting della memoria può portare a problemi più gravi. Raggiungere il limite della CPU provoca salti di ciclo, mentre raggiungere il limite di memoria porta all'"uccisione" del pod. Hai mai notato la memoria può portare a problemi più ampi. Raggiungere il limite della CPU comporta la perdita di cicli, mentre raggiungere il limite della memoria porta alla "terminazione" del pod. Avete mai osservato OOMkill? Да, речь идет именно о нем.
Vuoi minimizzare la probabilità di questo evento? Non allocare quantità eccessive di memoria e utilizzare Guaranteed QoS (Quality of Service), impostando la richiesta di memoria uguale al limite (come nell'esempio qui sotto). Maggiori dettagli su questo possono essere trovati nella (ingegnere principale di Zalando).
Variabile (maggiore probabilità di essere OOMkilled):
resources:
requests:
memory: "128Mi"
cpu: "500m"
limits:
memory: "256Mi"
cpu: 2Garantito:
resources:
requests:
memory: "128Mi"
cpu: 2
limits:
memory: "128Mi"
cpu: 2Cosa può aiutare potenzialmente nella configurazione delle risorse?
Con metrics-server permette di visualizzare il consumo attuale delle risorse CPU e l'uso della memoria da parte dei pod (e dei contenitori al loro interno). Probabilmente lo stai già usando. Esegui semplicemente i seguenti comandi:
kubectl top pods
kubectl top pods --containers
kubectl top nodesTuttavia, mostrano solo l'uso attuale. Può fornire un'idea approssimativa dell'ordine di grandezza, ma alla fine sarà necessaria una cronologia delle metriche nel tempo (per rispondere a domande come: "Qual era il carico massimo sulla CPU?", "Qual era il carico ieri mattina?" — e così via). Per questo, puoi utilizzare Prometheus, DataDog e altri strumenti. Essi semplicemente ottengono le metriche da metrics-server e le memorizzano, consentendo all'utente di richiederle e generare i grafici corrispondenti.
permette automatizzare questo processo. Tiene traccia della cronologia dell'uso della CPU e della memoria e configura nuove richieste e limiti basandosi su queste informazioni.
Un uso efficace delle risorse di calcolo è un compito complesso. È come giocare a tetris in continuazione. Se stai pagando troppo per la capacità di calcolo con un basso consumo medio (diciamo, ~10 %), ti consigliamo di considerare prodotti basati su AWS Fargate o Virtual Kubelet. Sono costruiti su un modello di fatturazione serverless/pay-per-usage, il che in tali condizioni può risultare più economico.
2. Liveness e readiness probes
Di default, i controlli di stato liveness e readiness in Kubernetes non sono abilitati. E a volte si dimentica di attivarli...
Ma come si può altrimenti avviare il riavvio di un servizio in caso di errore irreparabile? E come fa il bilanciatore di carico a sapere che un certo pod è pronto per ricevere traffico? O che è in grado di gestire più traffico?
Spesso questi controlli vengono confusi tra loro:
- Liveness — il controllo di "vitalità", che riavvia il pod in caso di esito negativo;
- Readiness — il controllo di disponibilità, che scollega il pod dal servizio Kubernetes in caso di esito negativo (questo può essere verificato con
kubectl get endpoints) e il traffico non viene instradato finché il controllo successivo non è completato con successo.
Entrambi questi controlli VENGONO EFFETTUATI PER L'INTERO CICLO DI VITA DEL POD. Questo è molto importante.
È un malinteso comune che le probe di readiness vengano eseguite solo all'inizio, in modo che il bilanciatore di carico possa sapere che il pod è pronto (Pronto) e può iniziare a gestire il traffico. Tuttavia, questa è solo una delle loro applicazioni.
Un'altra è identificare se il traffico sul pod è eccessivo e lo sovraccarica (o se il pod sta eseguendo calcoli intensivi). In questo caso, il controllo di readiness aiuta a ridurre il carico sul pod e a "raffreddarlo". Il completamento con successo della verifica di readiness in futuro consente di aumentare nuovamente il carico sul pod. In tal caso (in caso di fallimento della probe di readiness) il fallimento della verifica di liveness sarebbe estremamente controproducente. Perché riavviare un pod che è sano e sta lavorando duramente?
Pertanto, in alcuni casi, un completo assenza di controlli è meglio che attivarli con parametri configurati in modo errato. Come detto sopra, se il controllo di liveness copia il controllo di readiness, allora sei in grossi guai. Un'opzione può essere configurare , e Entrambi i tipi di controlli non devono fallire in caso di guasti alle dipendenze comuni, altrimenti questo porterà a un fallimento a cascata di tutti i pod. In altre parole,
non danneggiatevi da soli .
3. LoadBalancer per ogni servizio HTTP
È probabile che nel vostro cluster ci siano servizi HTTP che desiderate esporre al mondo esterno.
Se aprite il servizio come type: LoadBalancer, il suo controller (a seconda del fornitore di servizi) fornirà e configurerà un LoadBalancer esterno (non necessariamente operante su L7, probabilmente su L4), il che può influenzare i costi (indirizzo statico IPv4 esterno, potenza di calcolo, fatturazione per secondo) a causa della necessità di creare un gran numero di risorse simili.
In questo caso, ha molto più senso utilizzare un unico bilanciatore di carico esterno, aprendo i servizi come type: NodePort. O, meglio ancora, distribuire qualcosa come nginx-ingress-controller (o traefik), che fungerà da unico NodePort endpoint collegato al bilanciatore di carico esterno, e smisterà il traffico all'interno del cluster utilizzando ingress-risorse Kubernetes.
Altri servizi (micro) interni che interagiscono tra loro possono "comunicare" utilizzando servizi di tipo ClusterIP e il meccanismo di scoperta dei servizi tramite DNS. Ricordate solo di non utilizzare i loro DNS/IP pubblici, poiché ciò potrebbe influenzare la latenza e comportare un aumento dei costi dei servizi cloud.
4. Autoscaling del cluster senza considerare le sue peculiarità
Quando aggiungete o rimuovete nodi dal cluster, non dovreste fare affidamento su alcune metriche di base come l'utilizzo della CPU su questi nodi. La pianificazione di un pod deve tener conto di molti limitazioni, come l'affinità dei pod/nodi, i taints e i tolerations, le richieste di risorse, QoS, ecc. L'uso di un autoscaler esterno, che non considera queste sfumature, può portare a problemi.
Immaginate che un certo pod debba essere pianificato, ma tutte le risorse CPU disponibili siano richieste/occupate e il pod si blocchi in uno stato Pending. L'autoscaler esterno vede il carico medio corrente della CPU (e non quello richiesto) e non avvia l'espansione (scale-out) — non aggiunge un altro nodo. Di conseguenza, questo pod non verrà pianificato.
Tuttavia, la riduzione della scala (scale-in) — la rimozione di un nodo dal cluster — è sempre più difficile da realizzare. Immaginate di avere un pod stateful (con uno storage persistente connesso). I volumi persistenti di solito appartengono a una determinata zona di disponibilità e non vengono replicati nella regione. Pertanto, se l'autoscaler esterno rimuove il nodo con questo pod, il pianificatore non potrà pianificare questo pod su un altro nodo, poiché ciò può avvenire solo nella zona di disponibilità in cui si trova lo storage persistente. Il pod rimarrà bloccato in uno stato Pending.
Nella comunità Kubernetes, c'è una grande popolarità di . Funziona nel cluster, supporta API dai principali fornitori di servizi cloud, considera tutte le limitazioni e sa scalare nei casi sopra menzionati. È anche in grado di eseguire il scale-in mantenendo tutte le limitazioni impostate, risparmiando così denaro (che altrimenti sarebbe stato speso per risorse non utilizzate).
5. Ignorare le funzionalità IAM/RBAC
Fate attenzione a utilizzare utenti IAM con segreti permanenti per macchine e applicazioni.Organizzate l'accesso temporaneo utilizzando ruoli e account di servizio (service accounts).
Ci troviamo spesso di fronte al fatto che le chiavi di accesso (e i segreti) vengono 'hardcodate' nella configurazione dell'applicazione, così come alla negligenza nella rotazione dei segreti nonostante l'accesso a Cloud IAM. Utilizzate i ruoli IAM e gli account di servizio invece degli utenti, dove appropriato.

Dimenticate kube2iam e passate direttamente ai ruoli IAM per gli account di servizio (come descritto nella Štěpán Vraný):
apiVersion: v1
kind: ServiceAccount
metadata:
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/my-app-role
name: my-serviceaccount
namespace: defaultUn'annotazione. Non è poi così complicato, vero?
Inoltre, non concedete privilegi di admin e cluster-admin, agli account di servizio e ai profili delle istanze se non ne hanno bisogno. Questo è un po' più complicato da attuare, specialmente in RBAC K8s, ma ne vale sicuramente la fatica.
6. Non fare affidamento sull'anti-affinità automatica per i pod
Immaginate di avere tre repliche di un certo deployment su un nodo. Il nodo va in crash, e con esso tutte le repliche. Una situazione poco piacevole, giusto? Ma perché tutte le repliche erano sull'unico nodo? Non dovrebbe Kubernetes garantire l'alta disponibilità (HA)?!
Sfortunatamente, il pianificatore di Kubernetes non applica autonomamente le regole di coesistenza separata (anti-affinity) per i pod. Devono essere specificate esplicitamente:
// опущено для краткости
labels:
app: zk
// опущено для краткости
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: "app"
operator: In
values:
- zk
topologyKey: "kubernetes.io/hostname" Ecco fatto. Ora i pod saranno pianificati su nodi diversi (questa condizione viene verificata solo durante la pianificazione, non durante la loro esecuzione — da qui anche requiredDuringSchedulingIgnoredDuringExecution).
Qui parliamo di podAntiAffinity su nodi diversi: topologyKey: "kubernetes.io/hostname", — e non su diverse zone di disponibilità. Per implementare una vera alta disponibilità, sarà necessario approfondire l'argomento.
7. Ignorare i PodDisruptionBudget
Immaginate di avere un carico di produzione in un cluster Kubernetes. Periodicamente, i nodi e il cluster stesso devono essere aggiornati (o decommissionati). Il PodDisruptionBudget (PDB) è una sorta di accordo di garanzia di servizio tra gli amministratori del cluster e gli utenti.
Il PDB aiuta a evitare interruzioni nei servizi causate dalla mancanza di nodi:
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
name: zk-pdb
spec:
minAvailable: 2
selector:
matchLabels:
app: zookeeperIn questo esempio, voi come utente del cluster dichiarate agli amministratori: «Ehi, ho un servizio zookeeper, e indipendentemente da ciò che fate, vorrei che almeno 2 repliche di questo servizio fossero sempre disponibili».
Maggiore informazione si trova .
8. Più utenti o ambienti in un cluster condiviso
Gli spazi dei nomi Kubernetes (namespaces) non garantiscono un'isolamento robusto.
C'è una comune malinteso che se si distribuisce un carico non-prod in uno spazio dei nomi e un carico prod in un altro, essi non si influenzeranno a vicenda… Tuttavia, un certo livello di isolamento può essere raggiunto tramite richieste/limiti delle risorse, assegnando quote, impostando priorityClass. Una qualche forma di isolamento 'fisico' nel data plane è garantita da affinities, tolerations, taints (o nodeselectors), ma tale separazione è piuttosto difficile da attuare.
Coloro che hanno bisogno di mescolare entrambi i tipi di carico di lavoro in un unico cluster dovranno accettare la complessità. Se non c'è tale necessità e potete permettervi di crearne un altro cluster (ad esempio, nel cloud pubblico), allora è meglio farlo. Questo porterà a un livello di isolamento molto più elevato.
9. externalTrafficPolicy: Cluster
Spesso osserviamo che tutto il traffico all'interno del cluster entra attraverso un servizio di tipo NodePort, per il quale per impostazione predefinita è stabilita la politica externalTrafficPolicy: Cluster. Questo significa che NodePort è aperto su ogni nodo del cluster, e si può utilizzare uno di essi per interagire con il servizio richiesto (set di pod).

Tuttavia, i veri pod collegati al servizio NodePort sopra menzionato sono solitamente presenti solo su un certo sottoinsieme di questi nodi.In altre parole, se mi connetto a un nodo che non ha il pod richiesto, questo reindirizzerà il traffico a un altro nodo, aggiungendo un passaggio intermedio (hop) e aumentando la latenza (se i nodi si trovano in diverse zone di disponibilità/data center, la latenza può essere piuttosto alta; inoltre, aumenteranno i costi per il traffico egress).
D'altra parte, se per un certo servizio Kubernetes è stata assegnata la politica externalTrafficPolicy: Local, il NodePort viene aperto solo su quei nodi dove sono effettivamente in esecuzione i pod richiesti. Quando si utilizza un bilanciatore di carico esterno, che effettua controlli di stato (healthchecking) degli endpoint (come fa AWS ELB), questo invierà traffico solo ai nodi giusti, il che influisce positivamente sulla latenza, sui requisiti computazionali e sui costi per il traffico egress (e il buon senso suggerisce lo stesso).
È probabile che stiate già utilizzando qualcosa come traefik o nginx-ingress-controller come punto finale NodePort (o LoadBalancer, che utilizza anche NodePort) per la gestione del traffico HTTP ingress, e impostare questa opzione può ridurre significativamente la latenza nelle richieste simili.
In è possibile saperne di più su externalTrafficPolicy, i suoi vantaggi e svantaggi.
10. Non legarti ai cluster e non abusare del control plane
In passato, i server venivano chiamati con nomi propri: , HAL9000 e Colossus… Oggi, sono stati sostituiti da identificatori generati casualmente. Tuttavia, l'abitudine è rimasta e ora i nomi propri vengono assegnati ai cluster.
Una storia tipica (basata su eventi reali): tutto è iniziato con una prova di concetto, quindi il cluster aveva un nome orgoglioso testing… Sono passati anni, e viene ANCORA utilizzato in produzione, e tutti temono di toccarlo.
Non c'è nulla di divertente nel fatto che i cluster diventino animali domestici, quindi ti consigliamo di eliminarli periodicamente mentre pratichi la recupero dopo guasti (in questo ti aiuterà — nota di traduzione)). Inoltre, non sarebbe male occuparsi anche del layer di gestione (control plane). La paura di toccarlo non è un buon segno. Etcd è morto? Ragazzi, siete nei guai seri!
D'altra parte, non è bene esagerare con le manipolazioni. Col tempo il layer di gestione può diventare lento. Probabilmente, ciò è dovuto al gran numero di oggetti creati senza rotazione (una situazione comune quando si utilizza Helm con impostazioni predefinite, che non aggiornano il loro stato nelle configmap/segreti — come risultato, il layer di gestione accumula migliaia di oggetti) o alla modifica continua degli oggetti kube-api (per il ridimensionamento automatico, per CI/CD, per il monitoraggio, registri eventi, controller, ecc.).
Inoltre, ti consigliamo di controllare gli accordi SLA/SLO con il fornitore di Kubernetes gestito e prestare attenzione alle garanzie. Il fornitore può garantire la disponibilità del layer di gestione (o dei suoi sottocomponenti), ma non la latenza p99 delle richieste che gli invii. In altre parole, potresti inviare kubectl get nodes, e ricevere una risposta solo dopo 10 minuti, e questo non costituirà una violazione dei termini dell'accordo di servizio.
11. Bonus: l'uso dell'etichetta latest
Questa è già una classica. Ultimamente ci imbattiamo in questa tecnica non così spesso, poiché molti, dopo esperienze negative, hanno smesso di utilizzare l'etichetta :latest e hanno iniziato a fissare (pin) le versioni. Evviva!
ECR ; ti consigliamo di scoprire questa caratteristica notevole.
Riepilogo
Non aspettarti che tutto funzioni con un semplice comando: Kubernetes non è una panacea. Una cattiva applicazione (e, forse, diventerà anche peggiore). La leggerezza porterà a una complessità eccessiva, a un funzionamento lento e teso del layer di gestione. Inoltre, rischi di rimanere senza una strategia di recupero d'emergenza. Non contare sul fatto che Kubernetes "out of the box" si occuperà della garanzia di isolamento e alta disponibilità. Dedica un po' di tempo a rendere la tua applicazione veramente cloud native.
Puoi conoscere le esperienze sfortunate di vari team in di Henning Jacobs.
Coloro che desiderano aggiungere all'elenco degli errori menzionato in questo articolo possono contattarci su Twitter (, ).
P.S. dal traduttore
Leggete anche nel nostro blog:
- «»;
- «»;
- «» (recensione e video della presentazione);
- «».
Fonte: habr.com
