
All'inizio del lavoro con Kubernetes si tende spesso a dimenticare di configurare le risorse dei container. In questa fase è sufficiente assicurarsi che l'immagine Docker funzioni e che possa essere distribuita nel cluster Kubernetes.
Ma in seguito l'applicazione deve essere distribuita nel cluster di produzione insieme ad altre applicazioni. Per fare ciò è necessario allocare risorse per il container e assicurarsi che siano sufficienti per l'avvio e il funzionamento dell'applicazione, senza causare problemi ad altre applicazioni in esecuzione.
Team ha tradotto un articolo sulle risorse dei container (CPU & MEM), richieste e limitazioni delle risorse. Scoprirete quali vantaggi offrono queste configurazioni e cosa succede se non vengono impostate.
Risorse di calcolo
Abbiamo due tipi di risorse con le seguenti unità:
- Processore centrale (CPU) — core;
- Memoria (MEM) — byte.
Le risorse vengono specificate per ogni container. Nel seguente file YAML del Pod vedrete una sezione risorse, che contiene le risorse richieste e quelle massime:
- Risorse richieste del Pod = somma delle risorse richieste di tutti i container;
- Risorse massime del Pod = somma delle risorse massime di tutti i container.
apiVersion: v1
kind: Pod
metadata:
name: backend-pod-name
labels:
application: backend
spec:
containers:
- name: main-container
image: my-backend
tag: v1
ports:
- containerPort: 8080
resources:
requests:
cpu: 0.2 # CPU RICHIESTA: 200m core
memory: "1Gi" # MEM RICHIESTA: 1Gi
limits:
cpu: 1 # UTILIZZO MASSIMO CPU: 1 core
memory: "1Gi" # UTILIZZO MASSIMO MEM: 1Gi
- name: other-container
image: other-app
tag: v1
ports:
- containerPort: 8000
resources:
requests:
cpu: "200m" # CPU RICHIESTA: 200m core
memory: "0.5Gi" # MEM RICHIESTA: 0.5Gi
limits:
cpu: 1 # UTILIZZO MASSIMO CPU: 1 core
memory: "1Gi" # UTILIZZO MASSIMO MEM: 1GiEsempio di risorse richieste e massime
Campo resources.requested dalla specifica del Pod — uno degli elementi utilizzati per cercare il nodo corretto. Già su di esso si può pianificare la distribuzione del Pod. Come viene cercato il nodo adatto?
Kubernetes è composto da diversi componenti, tra cui il nodo principale o nodo master (Kubernetes Control Plane). Nel nodo master ci sono diversi processi: kube-apiserver, kube-controller-manager e kube-scheduler.
Il processo kube-scheduler è responsabile della revisione dei nuovi pod creati e della ricerca dei nodi di lavoro idonei che soddisfano tutte le richieste dei pod, comprese quelle relative alla quantità di risorse richieste. L'elenco dei nodi trovati da kube-scheduler viene classificato. Il pod è pianificato su un nodo con il punteggio più alto.
Dove sarà collocato il pod viola?
Nell'immagine si può vedere che kube-scheduler deve pianificare un nuovo pod viola. Il cluster Kubernetes contiene due nodi: A e B. Come si può notare, kube-scheduler non può pianificare il pod sul nodo A — le risorse disponibili (non richieste) non soddisfano le richieste del pod viola. Pertanto, la memoria richiesta dal pod viola di 1 GB non può essere allocata sul nodo A, poiché la memoria disponibile è di 0,5 GB. Ma il nodo B ha risorse sufficienti. Infine, kube-scheduler decide che la destinazione del pod viola è il nodo B.
Ora sappiamo come le risorse richieste influenzano la scelta del nodo per l'esecuzione del pod. Ma come influiscono le risorse limite?
Le risorse limite sono il confine che CPU/MEM non possono oltrepassare. Tuttavia, la risorsa CPU è flessibile, quindi i container che raggiungono i limiti della CPU non porteranno alla chiusura del pod. Al contrario, si attiverà il throttling della CPU. Se invece viene raggiunto il limite di utilizzo della MEM, il container sarà fermato per OOM-Killer e riavviato, se consentito dalla configurazione RestartPolicy.
Risorse richieste e limiti in dettagli
Relazione tra risorse Docker e Kubernetes
Il modo migliore per spiegare come funzionano le risorse richieste e quelle limite è rappresentare la relazione tra Kubernetes e Docker. Nell'immagine sopra puoi vedere come sono collegate le aree di Kubernetes e i flag di avvio di Docker.
Memoria: richiesta e limite
containers:
...
resources:
requests:
memory: "0.5Gi"
limits:
memory: "1Gi"
Come accennato sopra, la memoria è misurata in byte. Basandoci sulla , possiamo specificare la memoria come un numero. Di solito è un intero, ad esempio 2678 — cioè 2678 byte. Si possono anche usare suffissi. G e Gi, l'importante è ricordare che non sono equivalenti. Il primo è decimale, mentre il secondo è binario. Come esempio, citato nella documentazione di k8s: 128974848, 129e6, 129M, 123Mi — sono praticamente equivalenti.
L'opzione Kubernetes limits.memory corrisponde al flag --memory di Docker. Nel caso di request.memory la freccia per Docker non è presente, poiché Docker non utilizza questo campo. Puoi chiederti se ne hai bisogno? Sì, ne hai bisogno. Come ho già detto, il campo è importante per Kubernetes. Basandosi sulle informazioni in esso contenute, kube-scheduler decide a quale nodo pianificare il Pod.
Cosa succede se si richiede troppa poca memoria?
Se il contenitore raggiunge i limiti di memoria richiesti, il Pod viene inserito in un gruppo di Pod che vengono bloccati in caso di insufficienza di memoria nel nodo.
Cosa succede se si imposta un limite di memoria troppo basso?
Se il contenitore supera il limite di memoria, verrà terminato per motivo di OOM-Killed. Sarà riavviato, se possibile, in base a RestartPolicy, dove il valore predefinito è Always.
Cosa succede se non si specifica la memoria richiesta?
Kubernetes prenderà il limite come valore predefinito.
Cosa può succedere se non si specifica la memoria limite?
Il contenitore non ha restrizioni, può utilizzare tutta la memoria che vuole. Se inizia a utilizzare tutta la memoria disponibile nel nodo, verrà ucciso da OOM. Poi il contenitore verrà riavviato, se possibile, in base a RestartPolicy.
Cosa succede se non si specificano i limiti di memoria?
Questo è il peggior scenario: lo scheduler non sa quante risorse sono necessarie al contenitore, e questo può causare seri problemi nel nodo. In questo caso, sarebbe utile avere dei limiti predefiniti nello spazio dei nomi (impostati da LimitRange). Non ci sono limiti predefiniti: il Pod non ha restrizioni, può utilizzare tutta la memoria che desidera.
Se la memoria richiesta è superiore a quella disponibile nel nodo, il Pod non verrà pianificato. È importante ricordare che Requests.memory non è un valore minimo. È una descrizione della quantità di memoria necessaria per il funzionamento continuo del contenitore.
Di solito si consiglia di impostare lo stesso valore per request.memory e limit.memory. In questo modo, Kubernetes non pianificherà un Pod su un nodo che ha abbastanza memoria per avviare il Pod, ma non abbastanza per farlo funzionare. Tieni presente: nella pianificazione dei Pod, Kubernetes considera solo requests.memory, ma limits.memory non considera.
CPU: richiesta e limite
containers:
...
resources:
requests:
cpu: 1
limits:
cpu: "1200m"
Per la CPU è tutto un po' più complicato. Ritornando all'immagine della relazione tra Kubernetes e Docker, si può notare che request.cpu corrisponde a --cpu-shares, mentre limit.cpu corrisponde al flag cpus in Docker.
La CPU, richiesta da Kubernetes, viene moltiplicata per 1024 — la proporzione dei cicli CPU. Se desideri richiedere 1 core completo, devi aggiungere cpu: 1, come mostrato sopra.
Richiedere un core intero (proporzione = 1024) non significa che il tuo container lo riceverà. Se il tuo host ha solo un core, e stai utilizzando più di un container, tutti i container devono condividere la CPU disponibile tra loro. Come funziona? Diamo un'occhiata all'immagine.

Richiesta CPU — sistema con un core
Immaginiamo di avere un sistema host con un core, su cui sono in esecuzione dei container. Mamma (Kubernetes) ha cotto una torta (CPU) e vuole condividerla tra i suoi figli (container). Tre bambini vogliono una torta intera (proporzione = 1024), un altro bambino ne vuole metà (512). Mamma vuole essere giusta e fa un semplice calcolo.
# Сколько пирогов хотят дети?
# 3 ребенка хотят по целому пирогу и еще один хочет половину пирога
cakesNumberKidsWant = (3 * 1) + (1 * 0.5) = 3.5
# Выражение получается так:
3 (ребенка/контейнера) * 1 (целый пирог/полное ядро) + 1 (ребенок/контейнер) * 0.5 (половина пирога/половина ядра)
# Сколько пирогов испечено?
availableCakesNumber = 1
# Сколько пирога (максимально) дети реально могут получить?
newMaxRequest = 1 / 3.5 =~ 28%Secondo i calcoli, tre bambini riceveranno il 28% di un core, e non un core intero. Il quarto bambino riceverà il 14% di un core intero, e non metà. Ma tutto sarà diverso se hai un sistema multicore.

Richiesta CPU — sistema multicore (4)
Nell'immagine sopra, si vede che tre bambini vogliono una torta intera, mentre uno ne vuole metà. Poiché mamma ha cotto quattro torte, ciascuno dei suoi bambini riceverà quanto desidera. In un sistema multicore, le risorse della CPU sono distribuite su tutti i core disponibili. Se un container è limitato a meno di un core intero, può comunque utilizzarlo al 100%.
I calcoli sopra riportati sono semplificati per comprendere come la CPU venga distribuita tra i container. Certamente, oltre ai container stessi, ci sono anche altri processi che utilizzano risorse della CPU. Quando i processi all'interno di un container sono inattivi, altri possono utilizzare le sue risorse. CPU: "200m" corrisponde a CPU: 0,2, che equivale a circa il 20% di un core.
Ora parliamo di limit.cpu. La CPU, limitata da Kubernetes, viene moltiplicata per 100. Il risultato è il numero di tempo che il container può utilizzare ogni 100 µs (cpu-period).
limit.cpu corrisponde al flag Docker --cpus. Questa è una nuova combinazione di vecchi --cpu-period e --cpu-quota. Impostandolo, indichiamo quante risorse CPU disponibili il container può utilizzare al massimo fino a quando non inizia il throttling:
- cpus — combinazione
cpu-periodecpu-quota. cpus = 1.5è equivalente all'impostazionecpu-period = 100000ecpu-quota = 150000; - cpu-period — periodo , di default 100 microsecondi;
- cpu-quota — il numero di microsecondi all'interno
cpu-period, di cui è limitato il contenitore.
Cosa succede se si richiede una quantità di CPU insufficiente?
Se il contenitore ha bisogno di più risorse di quelle impostate, ruberà CPU ad altri processi.
Cosa succede se si imposta un limite CPU insufficiente?
Poiché la risorsa CPU è regolata, si attiverà il throttling.
Cosa succede se non si specifica la richiesta CPU?
Come nel caso della memoria, il valore della richiesta è uguale al limite.
Cosa succede se non si specifica il limite CPU?
Il contenitore utilizzerà tutto il CPU di cui ha bisogno. Se nella namespace è definita una politica CPU predefinita (LimitRange), verrà utilizzato anche per il contenitore.
Cosa succede se non si specifica né la richiesta né il limite CPU?
Come nel caso della memoria, questo è lo scenario peggiore. Il pianificatore non sa quante risorse sono necessarie per il vostro contenitore, e questo può causare gravi problemi nel nodo. Per evitarlo, è necessario impostare i limiti predefiniti per le namespaces (LimitRange).
Ricorda: se richiedi più CPU di quanta ne possano fornire i nodi, il Pod non sarà programmato. Requests.cpu — non un valore minimo, ma un valore sufficiente per avviare il Pod e funzionare senza interruzioni. Se l'applicazione non esegue calcoli complessi, la scelta migliore è impostare request.cpu <= 1 e avviare tutte le repliche necessarie.
La quantità ideale di risorse richieste o di limite delle risorse
Abbiamo appreso riguardo il limite delle risorse di elaborazione. Ora è giunto il momento di rispondere alla domanda: "Quante risorse necessita il mio Pod per eseguire l'applicazione senza problemi? Qual è la quantità ideale?".
Sfortunatamente, non ci sono risposte definitive a queste domande. Se non sai come funziona la tua applicazione, quanta CPU o memoria richiede, la scelta migliore è fornire all'applicazione molta memoria e CPU, e poi eseguire test di prestazioni.
Oltre ai test di prestazioni, osserva il comportamento dell'applicazione per una settimana attraverso il monitoraggio. Se dai grafici emerge che la tua applicazione consuma meno risorse di quelle richieste, puoi ridurre la quantità di CPU o memoria richiesta.
Ad esempio, guarda questo . Mostra la differenza tra le risorse richieste o il limite delle risorse e l'attuale utilizzo delle risorse.
Conclusione
La richiesta e il limite delle risorse aiutano a mantenere operativo il cluster Kubernetes. Una corretta configurazione dei limiti minimizza i costi e mantiene costantemente le applicazioni in funzione.
In sintesi, ci sono diversi aspetti da considerare:
- Le risorse richieste sono la configurazione che viene presa in considerazione durante l'avvio (quando Kubernetes pianifica il posizionamento dell'applicazione). Al contrario, il limite delle risorse è importante durante il funzionamento — quando l'applicazione è già in esecuzione su un nodo.
- Rispetto alla memoria, la CPU è una risorsa regolabile. In caso di carenza di CPU, il tuo Pod non terminerà l'esecuzione, sarà attivato il meccanismo di throttling.
- Le risorse richieste e il limite delle risorse non sono valori minimi e massimi! Definendo le risorse richieste, garantisci che l'applicazione funzioni senza problemi.
- Una buona prassi è impostare la richiesta di memoria pari al limite di memoria.
- È utile impostare la richiesta di
CPU <=1, se l'applicazione non esegue calcoli complessi. - Se richiedi più risorse di quelle disponibili sul nodo, il Pod non sarà mai pianificato su quel nodo.
- Per determinare il giusto numero di risorse richieste/lavorative, utilizza il collaudo delle prestazioni e il monitoraggio.
Spero che questo articolo ti aiuti a comprendere il concetto base dei limiti delle risorse. E potrai applicare queste conoscenze nel tuo lavoro.
Buona fortuna!
Cosa leggere ancora:
- .
- .
- .
Fonte: habr.com
