Límites da CPU e limitación agresiva en Kubernetes

Nota. transl.: Esta historia reveladora de Omio, un agregador de viaxes europeo, leva aos lectores desde a teoría básica ata as fascinantes complejidades prácticas da configuración de Kubernetes. A familiaridade con estes casos axuda non só a ampliar os seus horizontes, senón tamén a evitar problemas non triviais.

Límites da CPU e limitación agresiva en Kubernetes

Algunha vez tivo unha aplicación atascada no seu sitio, deixou de responder aos controis de saúde e non puido descubrir por que? Unha posible explicación está relacionada cos límites da cota de recursos da CPU. Isto é do que falaremos neste artigo.

TL; DR:
Recomendamos encarecidamente desactivar os límites de CPU en Kubernetes (ou desactivar as cotas CFS en Kubelet) se estás a usar unha versión do núcleo de Linux cun erro de cota CFS. No núcleo está dispoñible grave e ben coñecido un erro que leva a estrangulamentos e atrasos excesivos
.

En Omio toda a infraestrutura está xestionada por Kubernetes. Todas as nosas cargas de traballo con estado e sen estado execútanse exclusivamente en Kubernetes (utilizamos Google Kubernetes Engine). Nos últimos seis meses, comezamos a observar desaceleracións aleatorias. As aplicacións conxélanse ou deixan de responder aos controis de saúde, perden a conexión á rede, etc. Este comportamento deixounos desconcertado durante moito tempo e, finalmente, decidimos tomar o problema en serio.

Resumo do artigo:

  • Unhas palabras sobre contedores e Kubernetes;
  • Como se implementan as solicitudes e os límites da CPU;
  • Como funciona o límite da CPU en ambientes multinúcleo;
  • Como rastrexar a limitación da CPU;
  • Solución do problema e matices.

Algunhas palabras sobre contenedores e Kubernetes

Kubernetes é esencialmente o estándar moderno no mundo das infraestruturas. A súa tarefa principal é a orquestración de contedores.

Contenedores

No pasado, tiñamos que crear artefactos como Java JAR/WAR, Python Eggs ou executables para executalos en servidores. Non obstante, para facelos funcionar houbo que facer un traballo adicional: instalar o entorno de execución (Java/Python), colocar os ficheiros necesarios nos lugares axeitados, garantir a compatibilidade cunha versión concreta do sistema operativo, etc. Noutras palabras, houbo que prestar especial atención á xestión da configuración (que a miúdo era unha fonte de disputa entre desenvolvedores e administradores de sistemas).

Os contedores cambiaron todo. Agora o artefacto é unha imaxe de recipiente. Pódese representar como unha especie de ficheiro executable estendido que contén non só o programa, senón tamén un ambiente de execución completo (Java/Python/...), así como os ficheiros/paquetes necesarios, preinstalados e listos para correr. Os contedores pódense implantar e executar en diferentes servidores sen ningún paso adicionais.

Ademais, os contedores funcionan no seu propio entorno sandbox. Teñen o seu propio adaptador de rede virtual, o seu propio sistema de ficheiros con acceso limitado, a súa propia xerarquía de procesos, as súas propias limitacións de CPU e memoria, etc. Todo isto implícase grazas a un subsistema especial do núcleo de Linux: espazos de nomes.

Kubernetes

Como se dixo anteriormente, Kubernetes é un orquestrador de contedores. Funciona así: dáslle un conxunto de máquinas e despois dis: "Ola, Kubernetes, imos facer dez instancias do meu contedor con 2 procesadores e 3 GB de memoria cada un, e mantemos funcionando!" Kubernetes encargarase do resto. Atopará capacidade libre, lanzará contedores e reinicialos se é necesario, lanzará unha actualización ao cambiar de versión, etc. Esencialmente, Kubernetes permítelle abstraer o compoñente de hardware e fai unha gran variedade de sistemas axeitados para implementar e executar aplicacións.

Límites da CPU e limitación agresiva en Kubernetes
Kubernetes desde o punto de vista do profano

Cales son as solicitudes e os límites en Kubernetes

Está ben, cubrimos os contedores e Kubernetes. Tamén sabemos que varios recipientes poden residir na mesma máquina.

Pódese facer unha analoxía cun apartamento comunitario. Un amplo local (máquinas/unidades) é tomado e alugado a varios inquilinos (contedores). Kubernetes actúa como corretor de inmobles. Xorde a pregunta, como evitar que os inquilinos poidan conflitos entre eles? E se un deles, por exemplo, decide pedir prestado o baño para a metade do día?

Aquí é onde entran en xogo as peticións e os límites. CPU Solicitude necesarios exclusivamente para propósitos de planificación. Isto é algo así como unha "lista de desexos" do contedor e úsase para seleccionar o nodo máis axeitado. Ao mesmo tempo, a CPU Limitar pódese comparar cun contrato de aluguer - tan pronto como seleccionamos unha unidade para o recipiente, o non se pode superar os límites establecidos. E aquí é onde xorde o problema...

Como se implementan as solicitudes e os límites en Kubernetes

Kubernetes usa un mecanismo de limitación (omitir ciclos de reloxo) integrado no núcleo para implementar os límites da CPU. Se unha aplicación supera o límite, a limitación está habilitada (é dicir, recibe menos ciclos de CPU). As solicitudes e os límites de memoria organízanse de forma diferente, polo que son máis fáciles de detectar. Para iso, só tes que comprobar o estado do último reinicio do pod: se é "OOMKilled". A limitación da CPU non é tan sinxela, xa que K8s só fai que as métricas sexan dispoñibles por uso, non por cgroups.

Solicitude de CPU

Límites da CPU e limitación agresiva en Kubernetes
Como se implementa a solicitude da CPU

Para simplificar, vexamos o proceso usando unha máquina cunha CPU de 4 núcleos como exemplo.

K8s usa un mecanismo de grupo de control (cgroups) para controlar a asignación de recursos (memoria e procesador). Dispoñible dun modelo xerárquico: o fillo herda os límites do grupo principal. Os detalles da distribución gárdanse nun sistema de ficheiros virtual (/sys/fs/cgroup). No caso dun procesador isto é /sys/fs/cgroup/cpu,cpuacct/*.

K8s usa ficheiro cpu.share para asignar recursos do procesador. No noso caso, o cgroup raíz obtén 4096 partes de recursos da CPU: o 100% da potencia do procesador dispoñible (1 núcleo = 1024; este é un valor fixo). O grupo raíz distribúe os recursos proporcionalmente en función das participacións dos descendentes rexistrados cpu.share, e eles, á súa vez, fan o propio cos seus descendentes, etc. Nun nodo Kubernetes típico, o cgroup raíz ten tres fillos: system.slice, user.slice и kubepods. Os dous primeiros subgrupos utilízanse para distribuír recursos entre as cargas críticas do sistema e os programas de usuario fóra de K8s. Derradeiro - kubepods — creado por Kubernetes para distribuír recursos entre pods.

O diagrama anterior mostra que o primeiro e o segundo subgrupos recibiron cada un 1024 accións, co subgrupo kuberpod asignado 4096 accións Como é posible: despois de todo, o grupo raíz só ten acceso a 4096 accións, e a suma das accións dos seus descendentes supera significativamente este número (6144)? A cuestión é que o valor ten sentido lóxico, polo que o planificador de Linux (CFS) utilízao para asignar proporcionalmente os recursos da CPU. No noso caso, os dous primeiros grupos reciben 680 accións reais (16,6% de 4096), e kubepod recibe o resto 2736 accións En caso de inactividade, os dous primeiros grupos non utilizarán os recursos asignados.

Afortunadamente, o planificador ten un mecanismo para evitar o desperdicio de recursos da CPU non utilizados. Transfire capacidade "inactiva" a un pool global, desde o que se distribúe a grupos que necesitan potencia adicional do procesador (a transferencia prodúcese por lotes para evitar perdas de redondeo). Un método similar aplícase a todos os descendentes de descendentes.

Este mecanismo garante unha distribución xusta da potencia do procesador e asegura que ningún proceso "roube" recursos aos demais.

Límite de CPU

A pesar de que as configuracións de límites e solicitudes en K8 parecen similares, a súa implementación é radicalmente diferente: isto máis enganosa e a parte menos documentada.

K8s engancha Mecanismo de cotas do CFS para implementar límites. A súa configuración especifícase en ficheiros cfs_period_us и cfs_quota_us no directorio cgroup (o ficheiro tamén se atopa alí cpu.share).

Ao contrario cpu.share, a cota baséase período de tempo, e non na potencia do procesador dispoñible. cfs_period_us especifica a duración do período (época) - sempre é 100000 μs (100 ms). Hai unha opción para cambiar este valor en K8s, pero só está dispoñible en alfa polo momento. O planificador usa a época para reiniciar as cotas usadas. Segundo arquivo cfs_quota_us, especifica o tempo dispoñible (cota) en cada época. Teña en conta que tamén se especifica en microsegundos. A cota pode exceder a lonxitude da época; noutras palabras, pode ser superior a 100 ms.

Vexamos dous escenarios en máquinas de 16 núcleos (o tipo de ordenador máis común que temos en Omio):

Límites da CPU e limitación agresiva en Kubernetes
Escenario 1: 2 fíos e un límite de 200 ms. Sen estrangulamento

Límites da CPU e limitación agresiva en Kubernetes
Escenario 2: 10 fíos e límite de 200 ms. A limitación comeza despois de 20 ms, o acceso aos recursos do procesador retómase despois doutros 80 ms

Digamos que estableces o límite da CPU 2 núcleos; Kubernetes traducirá este valor a 200 ms. Isto significa que o contedor pode usar un máximo de 200 ms de tempo de CPU sen limitar.

E aquí é onde comeza a diversión. Como se mencionou anteriormente, a cota dispoñible é de 200 ms. Se está a traballar en paralelo dez fíos nunha máquina de 12 núcleos (consulte a ilustración para o escenario 2), mentres que todos os outros pods están inactivos, a cota esgotarase en só 20 ms (xa que 10 * 20 ms = 200 ms) e todos os fíos deste pod colgaranse » (acelerador) durante os próximos 80 ms. O xa mencionado erro do planificador, polo que se produce un estrangulamento excesivo e o contedor nin sequera pode cumprir a cota existente.

Como avaliar o estrangulamento en pods?

Só ten que iniciar sesión no pod e executar cat /sys/fs/cgroup/cpu/cpu.stat.

  • nr_periods — o número total de períodos do programador;
  • nr_throttled — número de períodos estrangulados na composición nr_periods;
  • throttled_time — tempo de estrangulamento acumulado en nanosegundos.

Límites da CPU e limitación agresiva en Kubernetes

Que está pasando realmente?

Como resultado, obtemos un alto estrangulamento en todas as aplicacións. Ás veces está dentro unha vez e media máis forte do esperado!

Isto leva a varios erros: fallos de verificación de preparación, conxelación do contedor, interrupcións da conexión de rede, tempo de espera nas chamadas de servizo. En definitiva, isto dá lugar a unha maior latencia e taxas de erro máis altas.

Decisión e consecuencias

Aquí todo é sinxelo. Abandonamos os límites da CPU e comezamos a actualizar o núcleo do sistema operativo en clústeres á última versión, na que se solucionou o erro. O número de erros (HTTP 5xx) nos nosos servizos baixou inmediatamente de forma significativa:

Erros HTTP 5xx

Límites da CPU e limitación agresiva en Kubernetes
Erros HTTP 5xx para un servizo crítico

Tempo de resposta p95

Límites da CPU e limitación agresiva en Kubernetes
Latencia de solicitude de servizo crítico, percentil 95

Custos de explotación

Límites da CPU e limitación agresiva en Kubernetes
Número de horas de instancia dedicadas

Cal é a captura?

Como se indica ao comezo do artigo:

Pódese facer unha analoxía cun apartamento comunitario... Kubernetes actúa como corretor de inmobles. Pero como evitar conflitos entre os inquilinos? E se un deles, por exemplo, decide pedir prestado o baño para a metade do día?

Aquí está a captura. Un recipiente descoidado pode consumir todos os recursos de CPU dispoñibles nunha máquina. Se tes unha pila de aplicacións intelixentes (por exemplo, JVM, Go, Node VM están configurados correctamente), isto non é un problema: podes traballar en tales condicións durante moito tempo. Pero se as aplicacións están mal optimizadas ou non están optimizadas en absoluto (FROM java:latest), a situación pode saír de control. En Omio temos Dockerfiles base automatizados cunha configuración predeterminada adecuada para a pila de idiomas principal, polo que este problema non existía.

Recomendamos supervisar as métricas USO (uso, saturación e erros), atrasos da API e taxas de erro. Asegúrese de que os resultados cumpran as expectativas.

referencias

Esta é a nosa historia. Os seguintes materiais axudaron moito a comprender o que estaba a suceder:

Informes de erros de Kubernetes:

Encontrou problemas similares na súa práctica ou ten experiencia relacionada coa limitación en ambientes de produción en contedores? Comparte a túa historia nos comentarios!

PS do tradutor

Lea tamén no noso blog:

Fonte: www.habr.com

Engadir un comentario