Límits de la CPU i acceleració agressiva a Kubernetes

Nota. transl.: Aquesta història reveladora d'Omio, un agregador de viatges europeu, porta els lectors des de la teoria bàsica fins a les fascinants complexitats pràctiques de la configuració de Kubernetes. Conèixer aquests casos no només ajuda a ampliar els teus horitzons, sinó que també evita problemes no trivials.

Límits de la CPU i acceleració agressiva a Kubernetes

Alguna vegada has tingut una aplicació bloquejada, deixat de respondre als controls de salut i no has pogut esbrinar per què? Una possible explicació està relacionada amb els límits de quota de recursos de la CPU. Això és del que parlarem en aquest article.

TL; DR:
Recomanem fermament desactivar els límits de CPU a Kubernetes (o desactivar les quotes CFS a Kubelet) si utilitzeu una versió del nucli de Linux amb un error de quota CFS. Al nucli està disponible seriós i ben conegut un error que provoca una acceleració excessiva i retards
.

A Omio tota la infraestructura està gestionada per Kubernetes. Totes les nostres càrregues de treball amb estat i sense estat s'executen exclusivament a Kubernetes (utilitzem Google Kubernetes Engine). En els últims sis mesos, vam començar a observar desacceleraments aleatoris. Les aplicacions es bloquegen o deixen de respondre a les comprovacions de salut, perden la connexió a la xarxa, etc. Aquest comportament ens va desconcertar durant molt de temps, i finalment vam decidir prendre el problema seriosament.

Resum de l'article:

  • Unes paraules sobre contenidors i Kubernetes;
  • Com s'implementen les peticions i els límits de la CPU;
  • Com funciona el límit de la CPU en entorns multinucli;
  • Com fer un seguiment de l'acceleració de la CPU;
  • Solució del problema i matisos.

Unes paraules sobre contenidors i Kubernetes

Kubernetes és essencialment l'estàndard modern en el món de la infraestructura. La seva tasca principal és l'orquestració de contenidors.

contenidors

En el passat, havíem de crear artefactes com JAR/WAR de Java, ous de Python o executables per executar-los als servidors. Tanmateix, per fer-los funcionar, calia fer una feina addicional: instal·lar l'entorn d'execució (Java/Python), col·locar els fitxers necessaris als llocs adequats, garantir la compatibilitat amb una versió concreta del sistema operatiu, etc. En altres paraules, s'havia de prestar molta atenció a la gestió de la configuració (que sovint era una font de controvèrsia entre desenvolupadors i administradors de sistemes).

Els contenidors ho van canviar tot. Ara l'artefacte és una imatge de contenidor. Es pot representar com una mena de fitxer executable estès que conté no només el programa, sinó també un entorn d'execució complet (Java/Python/...), així com els fitxers/paquets necessaris, preinstal·lats i preparats per correr. Els contenidors es poden desplegar i executar en diferents servidors sense cap pas addicional.

A més, els contenidors funcionen en el seu propi entorn sandbox. Tenen el seu propi adaptador de xarxa virtual, el seu propi sistema de fitxers amb accés limitat, la seva pròpia jerarquia de processos, les seves pròpies limitacions de CPU i memòria, etc. Tot això s'implementa gràcies a un subsistema especial del nucli de Linux: els espais de noms.

Kubernetes

Com s'ha dit anteriorment, Kubernetes és un orquestrador de contenidors. Funciona així: li doneu un grup de màquines i després digueu: "Hola, Kubernetes, llancem deu instàncies del meu contenidor amb 2 processadors i 3 GB de memòria cadascun, i mantinguem-les en funcionament!" Kubernetes s'encarregarà de la resta. Trobarà capacitat lliure, llançarà contenidors i els reiniciarà si cal, llançarà una actualització quan canviï de versions, etc. Bàsicament, Kubernetes us permet abstraure el component de maquinari i fa que una gran varietat de sistemes siguin adequats per desplegar i executar aplicacions.

Límits de la CPU i acceleració agressiva a Kubernetes
Kubernetes des del punt de vista del profà

Quines són les peticions i els límits a Kubernetes

D'acord, hem cobert els contenidors i Kubernetes. També sabem que diversos contenidors poden residir a la mateixa màquina.

Es pot fer una analogia amb un apartament comunitari. S'agafa un ampli local (màquines/unitats) i es lloga a diversos llogaters (contenidors). Kubernetes actua com a agent immobiliari. Sorgeix la pregunta, com mantenir els llogaters dels conflictes entre ells? Què passa si un d'ells, per exemple, decideix demanar prestat el bany per a la meitat del dia?

Aquí és on entren en joc les peticions i els límits. CPU Sol · licitar necessàries únicament per a finalitats de planificació. Això és com una "llista de desitjos" del contenidor i s'utilitza per seleccionar el node més adequat. Al mateix temps, la CPU Limitar es pot comparar amb un contracte de lloguer, tan aviat com seleccionem una unitat per al contenidor, el no pot anar més enllà dels límits establerts. I aquí és on sorgeix el problema...

Com s'implementen les sol·licituds i els límits a Kubernetes

Kubernetes utilitza un mecanisme d'acceleració (ometre els cicles de rellotge) integrat al nucli per implementar els límits de la CPU. Si una aplicació supera el límit, s'habilita l'acceleració (és a dir, rep menys cicles de CPU). Les peticions i els límits de memòria s'organitzen de manera diferent, de manera que són més fàcils de detectar. Per fer-ho, només cal que comproveu l'últim estat de reinici del pod: si és "OOMKilled". L'acceleració de la CPU no és tan senzilla, ja que K8s només fa que les mètriques estiguin disponibles per ús, no per cgroups.

Sol·licitud de CPU

Límits de la CPU i acceleració agressiva a Kubernetes
Com s'implementa la sol·licitud de CPU

Per simplificar, mirem el procés amb una màquina amb una CPU de 4 nuclis com a exemple.

K8s utilitza un mecanisme de grup de control (cgroups) per controlar l'assignació de recursos (memòria i processador). Hi ha disponible un model jeràrquic: el fill hereta els límits del grup pare. Els detalls de distribució s'emmagatzemen en un sistema de fitxers virtual (/sys/fs/cgroup). En el cas d'un processador això és /sys/fs/cgroup/cpu,cpuacct/*.

K8s utilitza un fitxer cpu.share per assignar recursos del processador. En el nostre cas, l'arrel cgroup obté 4096 parts de recursos de CPU: el 100% de la potència del processador disponible (1 nucli = 1024; aquest és un valor fix). El grup arrel distribueix els recursos proporcionalment en funció de les quotes dels descendents registrats cpu.share, i ells, al seu torn, fan el mateix amb els seus descendents, etc. En un node Kubernetes típic, l'arrel cgroup té tres fills: system.slice, user.slice и kubepods. Els dos primers subgrups s'utilitzen per distribuir recursos entre les càrregues crítiques del sistema i els programes d'usuari fora de K8s. L'últim - kubepods — creat per Kubernetes per distribuir recursos entre pods.

El diagrama anterior mostra que el primer i el segon subgrups van rebre cadascun 1024 accions, amb el subgrup kuberpod assignat 4096 accions Com és possible: després de tot, el grup arrel només té accés a 4096 accions, i la suma de les accions dels seus descendents supera significativament aquest nombre (6144)? La qüestió és que el valor té sentit lògic, de manera que el planificador de Linux (CFS) l'utilitza per assignar proporcionalment els recursos de la CPU. En el nostre cas, els dos primers grups reben 680 accions reals (16,6% de 4096) i kubepod rep la resta 2736 accions En cas d'inactivitat, els dos primers grups no utilitzaran els recursos assignats.

Afortunadament, el planificador té un mecanisme per evitar malgastar recursos de CPU no utilitzats. Transfereix capacitat "inactiva" a un grup global, des del qual es distribueix als grups que necessiten potència addicional del processador (la transferència es produeix per lots per evitar pèrdues d'arrodoniment). Un mètode similar s'aplica a tots els descendents de descendents.

Aquest mecanisme garanteix una distribució justa de la potència del processador i assegura que cap procés "robi" recursos als altres.

Límit de CPU

Malgrat que les configuracions de límits i sol·licituds en els K8 semblen semblants, la seva implementació és radicalment diferent: això més enganyós i la part menys documentada.

K8s enganxa Mecanisme de quotes del CFS per implementar límits. La seva configuració s'especifica als fitxers cfs_period_us и cfs_quota_us al directori cgroup (el fitxer també es troba allà cpu.share).

A diferència cpu.share, es basa la quota període de temps, i no a la potència del processador disponible. cfs_period_us especifica la durada del període (època): sempre és de 100000 μs (100 ms). Hi ha una opció per canviar aquest valor a K8s, però de moment només està disponible en alfa. El planificador utilitza l'època per reiniciar les quotes utilitzades. Segon arxiu cfs_quota_us, especifica el temps disponible (quota) en cada època. Tingueu en compte que també s'especifica en microsegons. La quota pot superar la durada de l'època; és a dir, pot ser superior a 100 ms.

Vegem dos escenaris en màquines de 16 nuclis (el tipus d'ordinador més comú que tenim a Omio):

Límits de la CPU i acceleració agressiva a Kubernetes
Escenari 1: 2 fils i un límit de 200 ms. Sense estrangulament

Límits de la CPU i acceleració agressiva a Kubernetes
Escenari 2: 10 fils i límit de 200 ms. L'acceleració comença després de 20 ms, l'accés als recursos del processador es reprèn després de 80 ms

Suposem que establiu el límit de la CPU 2 nuclis; Kubernetes traduirà aquest valor a 200 ms. Això vol dir que el contenidor pot utilitzar un màxim de 200 ms de temps de CPU sense accelerar.

I aquí és on comença la diversió. Com s'ha esmentat anteriorment, la quota disponible és de 200 ms. Si esteu treballant en paral·lel deu fils en una màquina de 12 nuclis (vegeu la il·lustració de l'escenari 2), mentre que tots els altres pods estan inactius, la quota s'esgotarà en només 20 ms (ja que 10 * 20 ms = 200 ms) i tots els fils d'aquest pod es penjaran » (accelerador) durant els propers 80 ms. El ja esmentat error del programador, per la qual cosa es produeix una limitació excessiva i el contenidor ni tan sols pot complir la quota existent.

Com avaluar l'acceleració a les beines?

Només heu d'iniciar sessió al pod i executar cat /sys/fs/cgroup/cpu/cpu.stat.

  • nr_periods — el nombre total de períodes de planificació;
  • nr_throttled — nombre de períodes estrangulats en la composició nr_periods;
  • throttled_time — temps d'acceleració acumulat en nanosegons.

Límits de la CPU i acceleració agressiva a Kubernetes

Què està passant realment?

Com a resultat, obtenim una acceleració elevada en totes les aplicacions. De vegades hi és una vegada i mitja més fort del que s'esperava!

Això provoca diversos errors: errors de comprovació de preparació, congelació de contenidors, interrupcions de la connexió de xarxa, temps d'espera en les trucades de servei. En última instància, això es tradueix en una latència més gran i majors taxes d'error.

Decisió i conseqüències

Aquí tot és senzill. Vam abandonar els límits de la CPU i vam començar a actualitzar el nucli del sistema operatiu en clústers a la darrera versió, en la qual es va solucionar l'error. El nombre d'errors (HTTP 5xx) als nostres serveis va disminuir de manera significativa immediatament:

Errors HTTP 5xx

Límits de la CPU i acceleració agressiva a Kubernetes
Errors HTTP 5xx per a un servei crític

Temps de resposta p95

Límits de la CPU i acceleració agressiva a Kubernetes
Latència de sol·licitud de servei crític, percentil 95

Costos operatius

Límits de la CPU i acceleració agressiva a Kubernetes
Nombre d'hores d'instància dedicades

Què és la captura?

Com s'indica a l'inici de l'article:

Es pot fer una analogia amb un apartament comunitari... Kubernetes actua com a agent immobiliari. Però, com evitar els conflictes entre llogaters? Què passa si un d'ells, per exemple, decideix demanar prestat el bany per a la meitat del dia?

Aquí està la captura. Un contenidor descuidat pot consumir tots els recursos de CPU disponibles en una màquina. Si teniu una pila d'aplicacions intel·ligents (per exemple, JVM, Go, Node VM estan configurats correctament), això no és un problema: podeu treballar en aquestes condicions durant molt de temps. Però si les aplicacions estan mal optimitzades o no estan optimitzades del tot (FROM java:latest), la situació es pot descontrolar. A Omio tenim Dockerfiles de base automatitzat amb la configuració predeterminada adequada per a la pila d'idiomes principal, de manera que aquest problema no existia.

Recomanem controlar les mètriques ÚS (ús, saturació i errors), retards de l'API i taxes d'error. Assegureu-vos que els resultats compleixin les expectatives.

Referències

Aquesta és la nostra història. Els materials següents van ajudar molt a entendre què estava passant:

Informes d'errors de Kubernetes:

Heu trobat problemes similars a la vostra pràctica o teniu experiència relacionada amb l'acceleració en entorns de producció en contenidors? Comparteix la teva història als comentaris!

PS del traductor

Llegeix també al nostre blog:

Font: www.habr.com

Afegeix comentari