Nove consigli per migliorare le prestazioni di Kubernetes

Nove consigli per migliorare le prestazioni di Kubernetes

Ciao a tutti! Mi chiamo Oleg Sidorenko, lavoro per l'azienda DomClick come responsabile del team infrastrutturale. Da più di tre anni utilizziamo 'Kubik' in produzione e in questo periodo abbiamo attraversato molti momenti interessanti con esso. Oggi vi racconterò come, con un approccio corretto, si può estrarre ancora più prestazioni da un Kubernetes 'vanilla' per il vostro cluster. Pronti, partenza, via!

Tutti voi sapete bene che Kubernetes è un sistema scalabile open source per l'orchestrazione di container; o meglio, 5 binari che fanno magia gestendo il ciclo di vita dei vostri microservizi in un ambiente server. Inoltre, è uno strumento piuttosto flessibile, che può essere assemblato come un costruttore Lego, per una massima personalizzazione in base a diverse esigenze.

E sembrerebbe tutto a posto: aggiungi i server al cluster, come legna da ardere in un caminetto, e non preoccuparti. Ma se ti preme l'ecologia, potresti pensare: "Come posso mantenere il fuoco acceso nel camino e allo stesso tempo preservare la foresta?". In altre parole, come trovare modi per migliorare l'infrastruttura e ridurre i costi.

1. Monitora le risorse dei team e delle applicazioni

Nove consigli per migliorare le prestazioni di Kubernetes

Uno dei metodi più banali ma efficaci è l'introduzione di requests/limits. Suddividi le applicazioni per namespaces, e i namespaces per team di sviluppo. Imposta i valori di consumo di tempo CPU, memoria e storage effimero per l'applicazione prima della distribuzione.

resources:
   requests:
     memory: 2Gi
     cpu: 250m
   limits:
     memory: 4Gi
     cpu: 500m

Sperimentando, abbiamo concluso che non vale la pena gonfiare le richieste dai limiti di oltre due volte. Il volume del cluster è calcolato in base alle richieste, e se imposti per le applicazioni differenze nelle risorse, ad esempio, da 5 a 10 volte, immagina cosa succederà al tuo nodo quando si riempirà di pod e riceverà improvvisamente un carico. Niente di buono. Al minimo, throttling; al massimo, ti dirai addio al worker e avrai un carico ciclico sugli altri nodi dopo che i pod inizieranno a migrare.

Inoltre, utilizzando limitranges puoi impostare fin da subito i valori delle risorse per il container — minimi, massimi e predefiniti:

➜  ~ kubectl descrivi limitranges --namespace ops
Nome:       limit-range
Namespace:  ops
Tipo       Risorsa              Min   Max   Richiesta Predefinita  Limite Predefinito  Rapporto Massimo Limite/Richiesta
----       --------              ---   ---   -------------------  ----------------  --------------------------
Container   cpu                   50m   10    100m                100m              2
Container   ephemeral-storage     12Mi  8Gi   128Mi               4Gi               -
Container   memory                64Mi  40Gi  128Mi               128Mi             2

Non dimenticate di limitare le risorse del namespace affinché un team non possa utilizzare tutte le risorse del cluster:

➜  ~ kubectl descrivi resourcequotas --namespace ops
Nome:                   resource-quota
Namespace:              ops
Risorsa                Utilizzato       Massimo
--------                ----             ----
limits.cpu              77250m         80
limits.memory           124814367488   150Gi
pods                    31             45
requests.cpu            53850m         80
requests.memory         75613234944    150Gi
services                26             50
services.loadbalancers  0              0
services.nodeports      0              0

Come si evince dalla descrizione resourcequotas, se il team ops desidera distribuire pod che consumeranno altri 10 cpu, il pianificatore non consentirà di farlo e restituirà un errore:

Errore durante la creazione: i pod "nginx-proxy-9967d8d78-nh4fs" sono vietati: superato il limite: resource-quota, richiesto: limits.cpu=5, requests.cpu=5, utilizzato: limits.cpu=77250m, requests.cpu=53850m, limitato: limits.cpu=10, requests.cpu=10

Per risolvere un problema simile si può scrivere uno strumento, ad esempio, come questo, in grado di memorizzare e impegnare lo stato delle risorse dei team.

2. Scegliete uno storage ottimale

Nove consigli per migliorare le prestazioni di Kubernetes

Qui vorrei affrontare il tema dei volumi persistenti e del sottosistema di archiviazione delle worker node di Kubernetes. Spero che nessuno utilizzi "Cube" su HDD in produzione, ma a volte anche un SSD comune non è sufficiente. Ci siamo imbattuti in problemi in cui i log saturavano il disco per le operazioni di input/output, e le possibilità di risoluzione non sono molte:

  • Utilizzare SSD ad alte prestazioni o passare a NVMe (se gestite il vostro hardware).

  • Ridurre il livello di registrazione.

  • Effettuare un bilanciamento "intelligente" dei pod che sovraccaricano il disco (podAntiAffinity).

Lo screenshot sopra mostra cosa succede al disco del nginx-ingress-controller quando è attivata la registrazione dei access_logs (~12 mila registrazioni/sec.). Questo stato, naturalmente, può portare a una degradazione di tutte le applicazioni su questa node.

Per quanto riguarda i PV, purtroppo, non ho testato tutti tipi Volumi Persistenti. Utilizzate la soluzione migliore che si adatta a voi. Storicamente, una piccola parte dei servizi ha bisogno di volumi RWX, e da tempo si è cominciato a utilizzare il storage NFS per questa esigenza. Economico e... sufficiente. Certo, abbiamo avuto le nostre esperienze negative con esso — e come, ma abbiamo imparato a ottimizzarlo, e ora non abbiamo più mal di testa. Se possibile, passate allo storage a oggetti S3.

3. Raccogliete immagini ottimizzate

Nove consigli per migliorare le prestazioni di Kubernetes

È meglio utilizzare immagini ottimizzate per i container, affinché Kubernetes possa recuperarle più rapidamente ed eseguirle in modo più efficiente. 

Ottimizzazione significa che le immagini:

  • contengono solo una applicazione o svolgono solo una funzione;

  • sono di dimensioni ridotte, perché le immagini grandi si trasferiscono peggio attraverso la rete;

  • hanno endpoint per il controllo dello stato e della disponibilità, con i quali Kubernetes può intraprendere azioni in caso di inattività;

  • utilizzano sistemi operativi friendly per i container (come Alpine o CoreOS), che sono più resistenti agli errori di configurazione;

  • utilizzano build multistep, affinché possiate distribuire solo le applicazioni compile e non i relativi sorgenti.

Ci sono molti strumenti e servizi che consentono di controllare e ottimizzare le immagini al volo. È importante mantenerli sempre aggiornati e controllati per la sicurezza. Alla fine si ottiene:

  1. Riduzione del carico di rete su tutto il cluster.

  2. Diminuzione del tempo di avvio del container.

  3. Volume ridotto del vostro intero Docker registry.

4. Utilizzate la cache DNS

Nove consigli per migliorare le prestazioni di Kubernetes

Quando si tratta di carichi pesanti, senza ottimizzazione del sistema DNS del cluster la situazione è piuttosto difficile. Un tempo, gli sviluppatori di Kubernetes supportavano la loro soluzione kube-dns. È stata implementata anche da noi, ma questo software non è stato ottimizzato e non forniva la performance richiesta, anche se, a prima vista, sembrava un compito semplice. Poi è arrivato coredns, al quale ci siamo trasferiti, e non abbiamo avuto più problemi, diventando poi il servizio DNS predefinito in K8s. A un certo punto siamo arrivati a 40.000 rps per il sistema DNS, e anche questa soluzione è diventata insufficiente. Ma, per un caso fortunato, è stato introdotto Nodelocaldns, conosciuto anche come cache locale del nodo, noto anche come NodeLocal DNSCache.

Perché lo usiamo? Nel kernel di Linux c'è un bug che, a causa di più richieste tramite conntrack NAT su UDP, porta a una condizione di race nella scrittura delle tabelle conntrack, e parte del traffico attraverso il NAT va persa (ogni accesso tramite il servizio è un NAT). Nodelocaldns risolve questo problema eliminando il NAT e potenziando la connessione a TCP verso i DNS upstream, oltre a effettuare un caching locale delle richieste DNS verso gli upstream (incluso un breve caching negativo di 5 secondi).

5. Scala automaticamente i pod orizzontalmente e verticalmente

Nove consigli per migliorare le prestazioni di Kubernetes

Puoi dire con sicurezza che tutti i tuoi microservizi sono pronti per una crescita del carico di lavoro di due o tre volte? Come allocare correttamente le risorse alle tue applicazioni? Mantenere attivi alcuni pod oltre il carico di lavoro può risultare superfluo, mentre averne pochi potrebbe comportare un'interruzione a causa di un'improvvisa crescita del traffico sul servizio. La soluzione a questo problema è fornita da servizi come Horizontal Pod Autoscaler e Vertical Pod Autoscaler.

VPA permette di aumentare automaticamente i requests/limits dei tuoi container nel pod in base all'uso effettivo. Come può essere utile? Se hai pod che non possono essere scalati orizzontalmente per qualche motivo (il che non è proprio affidabile), puoi provare a delegare la modifica delle loro risorse al VPA. Il suo punto forte è un sistema di raccomandazioni basato su dati storici e attuali dal metric-server, quindi, se non vuoi cambiare automaticamente i requests/limits, puoi semplicemente monitorare le risorse consigliate per i tuoi container e ottimizzare le impostazioni per risparmiare CPU e memoria nel cluster.

Nove consigli per migliorare le prestazioni di KubernetesImmagine presa da https://levelup.gitconnected.com/kubernetes-autoscaling-101-cluster-autoscaler-horizontal-pod-autoscaler-and-vertical-pod-2a441d9ad231

Il pianificatore in Kubernetes si basa sempre sui requests. Qualunque valore tu imposti, il pianificatore cercherà un nodo adatto basandosi su di esso. I valori limits sono necessari per il kubelet per capire quando limitare o terminare un pod. E poiché l'unico parametro importante è il valore dei requests, il VPA funzionerà con esso. Ogni volta che imposti il ridimensionamento verticale di un'applicazione, stai definendo quali dovrebbero essere i requests. E cosa succede ai limits? Questo parametro sarà anch'esso scalato in proporzione.

Ad esempio, ecco le impostazioni tipiche di un pod:

risorse:
   richieste:
     memoria: 250Mi
     cpu: 200m
   limiti:
     memoria: 500Mi
     cpu: 350m

Il meccanismo di raccomandazione stabilisce che alla tua applicazione servono 300m di CPU e 500Mi per funzionare normalmente. Riceverai queste impostazioni:

risorse:
   richieste:
     memoria: 500Mi
     cpu: 300m
   limiti:
     memoria: 1000Mi
     cpu: 525m

Come accennato precedentemente, questa è una scalabilità proporzionale basata sul rapporto tra richieste e limiti nel manifesto:

  • CPU: 200m → 300m: rapporto 1:1.75;

  • Memoria: 250Mi → 500Mi: rapporto 1:2.

Per quanto riguarda HPA, quindi qui il meccanismo di lavoro è più trasparente. Vengono impostati valori soglia delle metriche, ad esempio, per CPU e memoria, e se la media di tutte le repliche supera la soglia, l'applicazione si scala di +1 pod fino a quando il valore non scende al di sotto della soglia, o fino a quando non viene raggiunto il numero massimo di repliche.

Nove consigli per migliorare le prestazioni di KubernetesImmagine presa da https://levelup.gitconnected.com/kubernetes-autoscaling-101-cluster-autoscaler-horizontal-pod-autoscaler-and-vertical-pod-2a441d9ad231

Oltre alle metriche standard, come CPU e memoria, puoi impostare soglie sulle tue metriche personalizzate da Prometheus e lavorare con esse se ritieni che sia la definizione più precisa di quando scalare la tua applicazione. Una volta che l'applicazione si stabilizza al di sotto del limite di soglia delle metriche, HPA inizierà a ridurre i pod fino al numero minimo di repliche o fino a quando il carico soddisferà il limite stabilito.

6. Non dimenticare l'affinità tra nodi e pod

Nove consigli per migliorare le prestazioni di Kubernetes

Non tutti i nodi operano su hardware identico e non tutti i pod devono eseguire applicazioni che richiedono intensivi calcoli. Kubernetes consente di definire la specializzazione di nodi e pod tramite affinità del nodo e affinità del pod.

Se hai nodi adatti per operazioni ad alta intensità di calcolo, è meglio associare le applicazioni ai nodi appropriati per massimizzare l'efficienza. A tal fine, utilizza nodeSelector con etichetta del nodo.

Supponiamo di avere due nodi: uno con CPUType=HIGHFREQ e un gran numero di core veloci, l'altro con MemoryType=HIGHMEMORY una grande quantità di memoria e una maggiore rapidità. Il modo più semplice per assegnare il deployment del pod al nodo HIGHFREQ, aggiungendo nella sezione spec il seguente selettore:

…
nodeSelector:
	CPUType: HIGHFREQ

Un modo più costoso e specifico per farlo è utilizzare nodeAffinity nel campo affinity della sezione spec. Ci sono due opzioni:

  • requiredDuringSchedulingIgnoredDuringExecution: impostazione rigida (il pianificatore distribuirà i pod solo su nodi specifici (e non altrove));

  • preferredDuringSchedulingIgnoredDuringExecution: configurazione morbida (il pianificatore cercherà di distribuire su nodi specifici, e se non ci riesce, cercherà di distribuire sul nodo disponibile successivo).

Puoi specificare una sintassi particolare per il controllo delle etichette dei nodi, ad esempio, In, NotIn, Esiste, NonEsiste, Gt o Lt. Tuttavia, tieni presente che metodi complessi in lunghi elenchi di etichette rallenteranno le decisioni in situazioni critiche. In altre parole, non appesantirli.

Come accennato sopra, Kubernetes consente di specificare l'affinità dei pod attuali. Cioè, puoi fare in modo che determinati pod lavorino insieme ad altri pod nella stessa zona di disponibilità (rilevante per il cloud) o nodi.

In podAffinity campi affinity della sezione spec sono disponibili gli stessi campi come nel caso di nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution e preferredDuringSchedulingIgnoredDuringExecution. L'unica differenza è che matchExpressions collegherà i pod al nodo su cui è già in esecuzione un pod con quella etichetta.

Inoltre, Kubernetes offre un campo podAntiAffinity, che invece non collega il pod a nodi con determinati pod.

Per quanto riguarda le espressioni nodeAffinity puoi dare lo stesso consiglio: cerca di mantenere la semplicità e la coerenza delle regole, non cercare di sovraccaricare la specifica dei pod con un insieme complesso di regole. È molto facile creare una regola che non soddisferà le condizioni del cluster, causando un carico eccessivo sul pianificatore e riducendo le prestazioni complessive.

7. Taints & Tolerations

C'è un altro modo per gestire il pianificatore. Se hai un grande cluster con centinaia di nodi e migliaia di microservizi, è molto difficile non consentire a determinati pod di essere collocati in determinati nodi.

Questo è assistito dal meccanismo dei taints — regole restrittive. Ad esempio, in determinati scenari, puoi vietare a determinati nodi di eseguire pod al loro interno. Per applicare un taint a un nodo specifico, devi usare l'opzione taint in kubectl. Specifica la chiave e il valore, e poi il taint come NoSchedule o NoExecute:

$ kubectl taint nodes node10 node-role.kubernetes.io/ingress=true:NoSchedule

Vale la pena notare che il meccanismo dei taint supporta tre effetti principali: NoSchedule, NoExecute e PreferNoSchedule.

  • NoSchedule significa che finché non ci sarà una voce corrispondente nella specifica del pod tolerations, non potrà essere distribuito su un nodo (in questo esempio node10).

  • PreferNoSchedule — una versione semplificata NoSchedule. In questo caso, il pianificatore cercherà di non distribuire i pod che non hanno la voce corrispondente tolerations sul nodo, ma non è un vincolo rigido. Se nel cluster non ci saranno risorse, i pod inizieranno a distribuire su quel nodo.

  • NoExecute — questo effetto attiva l'evacuazione immediata dei pod che non hanno una registrazione corrispondente tolerations.

È curioso notare che questo comportamento può essere annullato tramite meccanismi di tolerations. Questo è comodo quando esiste un nodo "vietato" e hai bisogno di posizionare solo servizi infrastrutturali su di esso. Come fare? Consentire solo i pod per i quali esiste una toleration adeguata.

Ecco come apparirà la specifica del pod:

spec:
   tolerations:
     - key: "node-role.kubernetes.io\/ingress"
        operator: "Equal"
        value: "true"
        effect: "NoSchedule"

Questo non significa che al prossimo ridistribuzione il pod andrà su questo nodo, non è un meccanismo di Node Affinity e nodeSelector. Ma combinando più funzionalità, puoi ottenere una configurazione molto flessibile del pianificatore.

8. Configura la priorità dei deployment dei pod

Il fatto che tu abbia configurato il binding dei pod ai nodi non significa che tutti i pod debbano essere elaborati con la stessa priorità. Ad esempio, potresti voler distribuire alcuni pod prima di altri.

Kubernetes offre diversi modi per configurare la priorità dei pod (Pod Priority and Preemption). La configurazione consiste in diverse parti: l'oggetto PriorityClass e la descrizione del campo priorityClassName nella specifica del pod. Vediamo un esempio:

apiVersion: scheduling.k8s.io\/v1
kind: PriorityClass
metadata:
  name: high-priority
value: 99999
globalDefault: false
description: "Questa classe di priorità dovrebbe essere utilizzata solo per pod molto importanti"

Stiamo creando PriorityClass, impostando un nome, una descrizione e un valore. Più alto è value, maggiore sarà la priorità. Il valore può essere qualsiasi numero intero a 32 bit, minore o uguale a 1.000.000.000. Valori più elevati sono riservati per pod di sistema critici, che di norma non possono essere espulsi. L'espulsione avverrà solo se il pod ad alta priorità non ha altro posto dove distribuirsi, allora alcuni pod di un determinato nodo verranno evacuati. Se questo meccanismo è troppo rigido per te, puoi aggiungere l'opzione preemptionPolicy: Never, e così non ci sarà espulsione, il pod sarà il primo in coda e attenderà che il pianificatore trovi risorse libere per lui.

Successivamente, creiamo un pod in cui specifichiamo il nome priorityClassName:

apiVersion: v1
kind: Pod
metadata:
  name: static-web
  labels:
    role: myrole
 spec:
  containers:
    - name: web
      image: nginx
      ports:
        - name: web
          containerPort: 80
          protocol: TCP
  priorityClassName: high-priority
          

È possibile creare un numero illimitato di classi di priorità, anche se si consiglia di non esagerare (ad esempio, limitarsi a priorità bassa, media e alta).

In questo modo, se necessario, potrai aumentare l'efficienza del deployment dei servizi critici, come nginx-ingress-controller, coredns, ecc.

9. Ottimizza il cluster ETCD

Nove consigli per migliorare le prestazioni di Kubernetes

L'ETCD può essere considerato il cervello dell'intero cluster. È molto importante mantenere questo DB funzionante a un alto livello, poiché da esso dipende la velocità delle operazioni nel "Kube". Una soluzione abbastanza standard e, allo stesso tempo, valida è mantenere il cluster ETCD sui nodi master, per avere una latenza minima fino al kube-apiserver. Se non è possibile farlo, posiziona l'ETCD il più vicino possibile, assicurandoti una buona larghezza di banda tra i partecipanti. Fai anche attenzione a quante nodi dell'ETCD possono cadere senza danneggiare il cluster.

Nove consigli per migliorare le prestazioni di Kubernetes

Tieni presente che l'aumento eccessivo del numero di partecipanti nel cluster può aumentare la tolleranza ai guasti a scapito delle prestazioni; tutto deve essere fatto con moderazione.

Parlando della configurazione del servizio, le raccomandazioni sono poche:

  1. Avere un buon hardware, a seconda delle dimensioni del cluster (puoi leggere qui).

  2. Regolare alcuni parametri, se hai distribuito il cluster tra un paio di DC o se la tua rete e i tuoi dischi lasciano a desiderare (puoi leggere qui).

Conclusione

In questo articolo sono descritti i punti che il nostro team cerca di seguire. Non si tratta di una descrizione passo passo delle azioni, ma di opzioni che possono essere utili per ottimizzare i costi operativi del cluster. È chiaro che ogni cluster è unico a modo suo e le soluzioni di configurazione possono variare notevolmente, quindi sarebbe interessante ricevere da voi dei feedback: come monitorate il vostro cluster Kubernetes, con cosa migliorate le sue prestazioni? Condividete la vostra esperienza nei commenti, sarà interessante conoscerla.

Fonte: habr.com

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