¿Por qué K8S mata mi aplicación?

Primera cuenta pública: Comunidad binaria, contacto de reimpresión: [email protected]

Guía

"K8S nos brinda la capacidad de implementar y programar aplicaciones automáticamente, y reiniciar automáticamente las aplicaciones fallidas a través de la interfaz de verificación de estado para garantizar la disponibilidad del servicio. Sin embargo, esta operación y mantenimiento automáticos hará que nuestras aplicaciones entren en un proceso de programación continuo bajo ciertas circunstancias especiales. Esto provocó daños comerciales. Este artículo analiza el problema de programación de reinicios frecuentes de una aplicación de plataforma central en la línea de producción y lo analiza paso a paso desde el sistema hasta la aplicación, y finalmente localiza el nivel de código para resolver el problema ".

fenómeno

Después de que se construye la infraestructura de DevOps, la empresa se ha colocado en contenedores por completo y se ha implementado en base a k8s, pero las empresas individuales se reiniciarán automáticamente mediante k8s después de funcionar durante un período de tiempo, y el reinicio es irregular, a veces por la tarde, a veces por la tarde Temprano en la mañana, desde la interfaz de k8s, algunos se han reiniciado cientos de veces:

¿Por qué K8S mata mi aplicación?

análisis

Análisis del lado de la plataforma: comprenda la estrategia de reinicio de la plataforma

K8s se reinicia de acuerdo con la estrategia de reinicio definida en pod yaml. Esta estrategia se establece mediante: .spec.restartPolicy y admite las siguientes tres estrategias:

  • Siempre: cuando el contenedor termina y sale, siempre reinicie el contenedor, la política predeterminada.
  • En caso de falla: cuando la plantación de contenedores sale de manera anormal (el código de salida no es 0), reinicie el contenedor.
  • Nunca: No reinicie el contenedor cuando termina y sale.

La aplicación problemática se empaqueta y libera automáticamente usando CICD. Yaml también se genera automáticamente en el enlace del CD y no muestra la estrategia de reinicio especificada, por lo que la estrategia Siempre se adopta de forma predeterminada. Luego, bajo qué circunstancias k8s activará un reinicio, principalmente en los siguientes escenarios:

    1. POD sale normalmente
    1. POD salió anormalmente
    1. POD usa la CPU esclava para exceder el límite de CPU establecido en yaml, o exceder el límite de CPU configurado en el espacio de nombres donde se encuentra el contenedor
    1. El uso de POD excede el límite superior de memoria establecido en yaml o excede el límite superior de memoria configurado en el espacio de nombres donde se encuentra el contenedor
    1. Cuando los recursos de la máquina host en tiempo de ejecución no pueden cumplir con los recursos del POD (CPU de memoria), se programará automáticamente en otras máquinas y el número de reinicios también se producirá +1
    1. No se puede encontrar la imagen especificada al crear el POD o no hay ningún nodo que cumpla con los requisitos de recursos (memoria y CPU) del POD, y continuará reiniciándose

La aplicación problemática se reinicia después de ejecutarse normalmente durante un período de tiempo, y el archivo Yaml del POD en sí y el espacio de nombres donde se encuentra no tiene un límite superior de CPU, entonces se puede descartar: 1 3 4 6, el negocio es desarrollado por Springboot. Si sale sin ningún motivo, JVM El archivo de volcado en sí se genera, pero el comportamiento de reinicio lo activa el propio K8. Incluso si el archivo de volcado se genera en el POD, debido a que el directorio del archivo de volcado no se asigna al exterior del contenedor durante el tiempo de ejecución, es imposible verificar si el archivo de volcado se generó cuando se reinició la última vez. dump, por lo que 2 5 puede hacer que k8s reinicie el servicio, pero k8s proporciona comandos para verificar el motivo del último lanzamiento de POD. Los comandos específicos son los siguientes:

NAMESPACE=prod
SERVER=dts
POD_ID=$(kubectl get pods -n ${NAMESPACE} |grep ${SERVER}|awk '{print $1}')
kubectl describe pod $POD_ID -n ${NAMESPACE}

El resultado de la ejecución del comando muestra que el componente kubelet mata y reinicia automáticamente el POD porque el uso de la memoria excede el límite (si el motivo está vacío o se desconoce, puede ser el motivo anterior 2 o la memoria y la CPU no están limitadas, pero el sistema operativo mata el POD en casos extremos. En este momento, puede ver / var / log / message para analizar más a fondo el motivo) .CICD establece la memoria máxima para cada POD empresarial en 2G de forma predeterminada al crear una empresa, pero en el script de ejecución de la imagen básica, la JVM máxima y mínima se establecen en 2G :

exec java -Xmx2g -Xms2g -jar  ${WORK_DIR}/*.jar

Análisis del lado de la aplicación: analice el estado de ejecución de JVM

Al analizar el entorno en el que se ejecuta la aplicación, analizamos más a fondo el estado de la propia JVM utilizada por la aplicación, primero observe el comando de uso de memoria de la JVM: jmap -heap {PID} ¿Por qué K8S mata mi aplicación?
Memoria solicitada de la JVM: (eden) 675.5+ (desde) 3.5+ ( a) 3.5+ (OldGeneration) 1365.5 = 2048mb En teoría, la JVM será OOMKill tan pronto como se inicie, pero el hecho es que la empresa solo se mata después de ejecutarse durante un período de tiempo. ¿Por qué K8S mata mi aplicación?PD: La memoria que ven los comandos superior y libre en la ventana acoplable es la máquina host. Dependiendo del tamaño de la memoria y el uso dentro del contenedor, puede usar los siguientes comandos:

cat /sys/fs/cgroup/memory/memory.limit_in_bytes

Cuando está equipada con -Xmx2g -Xms2g, la máquina virtual solicita memoria 2G, pero la página enviada no consumirá ningún almacenamiento físico antes del primer acceso. La memoria real utilizada por el proceso empresarial en ese momento es de 1,1 g. A medida que la empresa funciona, después de un cierto período de tiempo La memoria utilizada por la JVM aumentará gradualmente hasta que alcance 2G y se elimine. Artículos recomendados sobre administración de memoria: reserva y asignación de memoria JvmMemoryUsage

Análisis a nivel de código: disección de la causa raíz del problema

Ejecución de una orden:

jmap -dump:format=b,file=./dump.hprof [pid]

Se importó el análisis de JvisualVM y se encontró que hay una gran cantidad de objetos Span que no se han reciclado. La razón por la que no se reciclan es que son referenciados por objetos de elementos en la cola: ¿Por qué K8S mata mi aplicación?ejecución a intervalos:

jmap -histo pid |grep Span

Se encuentra que la cantidad de objetos de intervalo ha aumentado. El intervalo pertenece al objeto en el sistema de seguimiento de la cadena de llamadas distribuidas DTS en el que se basa la ingeniería empresarial. DTS es un sistema básico transparente y no intrusivo, y la empresa no muestra la referencia al intervalo. En el diseño de DTS, Span se genera en el subproceso comercial y luego se coloca en la cola de bloqueo, esperando que el subproceso serializado se consuma de forma asincrónica. El código de producción y consumo es el siguiente: ¿Por qué K8S mata mi aplicación?A partir del código anterior, Span aumenta continuamente y debería ser el consumo del subproceso consumidor en sí. La velocidad es menor que la velocidad del productor. La lógica de consumo ejecutada por el hilo del consumidor es un disco de escritura secuencial de E / S. Calculado de acuerdo con las IOPS de 30-40 m de disco ECS ordinario, cada tramo puede ver a través del volcado, el tamaño promedio es de 150 bytes, teóricamente se puede escribir por segundo : 30 1024 1024/150 = 209715, por lo que no debería ser la lógica de consumo lo que hace que la tasa de consumo se ralentice, y hay una suspensión (50) en el código, es decir, se pueden escribir hasta 20 intervalos por segundo y la empresa tiene una tarea de sincronización en ejecución , Cada vez se generarán más objetos Span, y si se están ejecutando otros códigos comerciales en este momento, también se generará una gran cantidad de Spans, que es mucho mayor que la velocidad de consumo, por lo que hay una acumulación de objetos. Con el tiempo, el consumo de memoria aumenta gradualmente Grande, lo que lleva a OOMKill. Volcar la pila de hilos del negocio:

jstack  pid >stack.txt

Pero descubrí que hay dos subprocesos de escritura, un estado siempre está esperando en condición y el otro volcado está en reposo varias veces: ¿Por qué K8S mata mi aplicación?pero el código es a través de Executors.newSingleThreadExecutor (thf); el grupo de subprocesos único comenzó, ¿cómo puede haber dos consumidores? ? Mire más a fondo los registros de código, resulta que la lógica del backend de envío siempre está integrada en el código central cuando se modifica una vez en noviembre. Esta función fue ensamblada automáticamente por inyección de dependencia de jar externo en la versión anterior, por lo que aparecerá en la versión actual Dos objetos Sender, de los cuales el sistema DTS no hace referencia al objeto Sender creado automáticamente, y la cola nunca está vacía, lo que hace que el subproceso consumidor debajo de él siempre esté bloqueado, mientras que el objeto Sender incorporado aparece debido a Sleep (50), lo que hace que la velocidad de consumo disminuya. Está acumulado, es imposible capturar claramente su estado de ejecución durante el volcado, y parece haber estado inactivo. Al observar los archivos escritos por la serialización del hilo del consumidor, se encuentra que los datos se han escrito, lo que indica que el hilo del consumidor se está ejecutando realmente. ¿Por qué K8S mata mi aplicación?
Enviado por código El registro conoció que la última versión del negocio generará una gran cantidad de Span en algunas situaciones. El consumo de Span es muy rápido, lo que hará que la CPU de este hilo se dispare más. Para paliar esta situación se agrega sleep, y efectivamente se encuentra Después del problema, el código de negocio se ha optimizado. No es necesario modificar el sistema DTS. DTS debe descubrir problemas y promover la reparación y optimización del negocio. La modificación del sistema básico debe ser muy cautelosa porque el impacto es muy amplio. En vista del problema de que la memoria máxima del POD es igual a la memoria máxima de la máquina virtual, al modificar el código del CD, se agregarán 200M al tamaño de memoria de la configuración empresarial por defecto. ¿Por qué 200M no es más? Porque k8s calculará la memoria máxima del POD que se está ejecutando actualmente para evaluar ¿Cuántos POD puede tener el nodo actual? Si está configurado a + 500m o más, hará que K8S piense que los recursos del nodo son insuficientes y causan desperdicio, pero no pueden ser muy pocos o muy pocos, porque la aplicación dependerá de algunos terceros además de su propio código. Las bibliotecas compartidas, etc., también pueden hacer que Pod se reinicie con frecuencia.

para resumir

 上述问题的根因是人为降低了异步线程的消费速度,导致消息积压引起内存消耗持续增长导致OOM,但笔者更想强调的是,当我们把应用部署到K8S或者Docker时,**POD和Docker分配的内存需要比应用使用的最大内存适当大一些**,否则就会出现能正常启动运行,但跑着跑着就频繁重启的场景,如问题中的场景,POD指定里最大内存2G,理论上JVM启动如果立即使用里2G肯定立即OOM,开发或者运维能立即分析原因,代价会小很多,但是因为现代操作系统内存管理都是VMM(虚拟内存管理)机制,当JVM参数配置为: -Xmx2g -Xms2g时,**虚拟机会申请2G内存,但提交的页面在首次访问之前不会消耗任何物理存储,**所以就出现理论上启动就该OOM的问题延迟到应用慢慢运行直到内存达到2G时被kill,导致定位分析成本非常高。另外,对于JVM dump这种对问题分析非常重要的日志,一定要映射存储到主机目录且保证不被覆盖,不然容器销毁时很难去找到这种日志。

Artículos más detallados, siga: Comunidad binaria

Supongo que te gusta

Origin blog.51cto.com/14957687/2543829
Recomendado
Clasificación