Notas de estudio de Kubernetes: mejores prácticas para desarrollar aplicaciones (2) 20230604

3. Asegúrese de que todas las solicitudes de los clientes se manejen correctamente

Cómo asegurarse de que todas las conexiones se manejen correctamente cuando se inicia el pod

1. Evite la desconexión de la conexión del cliente cuando se inicia el pod

Cuando se inicia un pod, expone todos los servicios como puntos finales de servicio cuyos selectores de etiquetas coinciden con la etiqueta del pod. El pod debe enviar una señal a kubernetes de que está listo. Una vez que el pod está listo, puede convertirse en un punto final de servicio; de lo contrario, no puede aceptar ninguna solicitud de conexión de cliente.

Un pod siempre se considera listo si no se especifica ningún sondeo de preparación en la especificación del pod. Cuando el primer kube-proxy actualice las reglas de iptables en su nodo y el primer pod de cliente comience a conectarse al servicio, el pod, que se considera listo de forma predeterminada, comenzará a aceptar solicitudes casi de inmediato. Si la aplicación no está lista para aceptar conexiones en este momento, el cliente recibirá un mensaje de error como "conexión rechazada".

Todo lo que necesita hacer es hacer que la sonda de preparación devuelva correctamente si y solo si el mensaje está listo para manejar la solicitud entrante. El primer paso en la práctica es agregar una sonda de preparación para solicitudes HTTP GET a la URL raíz de la aplicación.

2. Cuando se cierra el pod, la conexión del cliente se desconecta

Cuando se elimina el pod y se termina el contenedor del pod, ¿cómo se cierra limpiamente el contenedor del pod cuando recibe la señal SIGTERM? ¿Cómo garantizar que todas las solicitudes de los clientes se manejen correctamente?

Comprender la cadena de eventos que ocurre cuando se elimina un pod

1) Cuando el servidor API recibe una solicitud para eliminar un pod, primero modifica el estado en etcd y notifica al observador del evento de eliminación. Dos de los observadores son el kubelet y el Endpoint Controller.

Cuando el kubelet recibe la notificación de que la aplicación del módulo ha finalizado, ejecuta el enlace de predetención, envía la señal SIGTERM, espera un período de tiempo y luego elimina por la fuerza el contenedor si el contenedor no finaliza automáticamente.

Si la aplicación entiende que debe dejar de aceptar solicitudes de clientes en respuesta a la señal SIGTERM, cualquier intento de conectarse a la aplicación recibirá un error de conexión rechazada. El tiempo desde que se elimina el pod hasta que se produce esta solicitud es relativamente breve. Porque esta es una comunicación directa entre el servidor API y el kubelet.

2) Antes de que el pod se elimine de las reglas de iptables, cuando el controlador de punto final recibe la notificación de que el pod está a punto de eliminarse, elimina el punto final de servicio del pod de todos los servicios donde se encuentra el pod. Modifica el objeto API de Endpoit enviando una solicitud REST al servidor API. Luego, el servidor API notificará a todos los clientes que presten atención a este objeto Endpoint. Algunos de estos observadores son servicios de proxy kube que se ejecutan en nodos trabajadores. Cada servicio kube-proxy actualiza las reglas de iptables en su propio nodo para evitar que se reenvíen nuevas conexiones a estos pods detenidos.

Las dos series de tiempos anteriores ocurren en paralelo. Lo más probable es que el tiempo necesario para cerrar el proceso de la aplicación en el pod sea un poco más corto que el tiempo requerido para completar la actualización de las reglas de iptables y la serie de eventos que conducen a la La actualización de las reglas de iptables es relativamente larga. Debido a que estos eventos primero deben llegar al controlador de punto final, luego el controlador de punto final envía una nueva solicitud al servidor api, luego el servidor api debe controlar el kube-proxy y, finalmente, el kube-proxy modifica las reglas de iptables. Existe una gran posibilidad de que la señal SIGTERM se envíe antes de que las reglas de iptables se actualicen en todos los nodos.

El resultado final es que después de enviar una señal de interrupción al pod, este aún puede recibir solicitudes de clientes. Si la aplicación entiende cerrar el socket del servidor y dejar de recibir solicitudes, esto hará que el cliente reciba un error del tipo "conexión rechazada".

Resolver el problema

Agregar una sonda de preparación al pod resuelve el problema. Si todo lo que se requiere es que las sondas de preparación comiencen a fallar cuando el pod recibe una señal SIGTERM, esto hará que el pod se elimine del extremo del servicio. Pero esta acción de eliminación solo ocurrirá después de que la sonda de preparación continúe fallando durante un período de tiempo (se puede configurar en la especificación de la sonda de preparación), y esta acción de eliminación debe llegar primero a kube-proxy y luego la regla de iptables será eliminó esta cápsula.

Lo único razonable que puede hacer es esperar lo suficiente para que todos los kube-proxy terminen su trabajo, entonces, ¿cuánto tiempo es suficiente? En la mayoría de los escenarios, unos pocos segundos deberían ser suficientes, pero no hay garantía de que sea suficiente cada vez. Cuando el servidor API o el controlador de punto final están sobrecargados, las notificaciones tardarán más en llegar a kube-proxy. Es importante comprender que no existe una solución perfecta para este problema, pero agregar un retraso de 5 o 10 segundos en el tiempo también puede mejorar mucho la experiencia del usuario. Puede usar un tiempo de retraso más largo, pero no debe ser demasiado largo, ya que hará que el contenedor no se apague normalmente y hará que el pod se elimine durante mucho tiempo y aún se muestre en la lista. lo que causará problemas al usuario que eliminó el pod.

resumen:

Cerrar correctamente una aplicación implica los siguientes pasos:

  • Espere unos segundos, luego deje de aceptar nuevas conexiones
  • Cerrar todas las conexiones largas que no hayan sido solicitadas
  • Espere a que se completen todas las solicitudes
  • luego cierra la aplicación por completo

4. Deje que la aplicación se ejecute y gestione cómodamente en kubernetes

1. Cree una imagen de contenedor manejable

Al empaquetar una aplicación en una imagen, puede incluir el archivo binario de la aplicación y sus bibliotecas dependientes, o empaquetar un sistema operativo completo y la aplicación juntos. ¿Se necesitan todos los archivos del sistema operativo en la imagen? No, la mayoría de los archivos nunca se usan y solo harán que su imagen sea más grande de lo que debe ser. Cuando un pod se programa en un nodo por primera vez, lleva mucho tiempo. Las imágenes de compilación mínima son muy difíciles de depurar. Cuando necesite ejecutar algunas herramientas, como ping, dig, curl u otros comandos similares en el contenedor, se dará cuenta de lo importante que es que el contenedor contenga al menos el conjunto mínimo de estas herramientas.

Qué herramientas están incluidas en la imagen y qué herramientas no están incluidas depende de sus propias necesidades.

2. Etiquete razonablemente la imagen y use ImagePullPolicypod correctamente

Es mejor no usar la última versión en el manifiesto, de lo contrario, no podrá volver a la versión especificada.

Debe usar una etiqueta que pueda especificar la versión específica. Si usa una etiqueta modificable, debe configurar la política de extracción de imagen para que siempre esté en la especificación del pod. Pero si usa este método en un entorno de producción, debe prestar atención a sus instrucciones adicionales. Si la política de extracción de la imagen se establece en siempre, la operación del contenedor se comunicará con el registro de la imagen cuando encuentre un nuevo pod que deba implementarse. Esto ralentizará la velocidad de inicio del pod, porque el nodo necesita verificar si la imagen se ha modificado. Peor aún, esta estrategia evita que se inicien nuevos pods cuando no se puede acceder al registro espejo.

3. Use etiquetas multidimensionales en lugar de etiquetas unidimensionales

Las etiquetas pueden contener lo siguiente:

  • El nombre de la aplicación (o microservicio) a la que pertenece el recurso
  • Nivel de aplicación (frontend, backend, etc.)
  • Entorno de tiempo de ejecución (desarrollo, prueba, puesta en escena, producción, etc.)
  • número de versión
  • tipo de liberación
  • Tipo de lanzamiento (estable, canario, verde o azul en desarrollo azul-verde, etc.)
  • Arrendatarios (si ejecuta diferentes pods en cada arrendatario en lugar de usar espacios de nombres)
  • Fragmentación (sistema con fragmentación)

La administración de etiquetas le permite administrar los recursos en grupos en lugar de hacerlo de forma aislada, lo que facilita la comprensión de dónde pertenecen los recursos

4. Describe cada recurso a través de anotaciones

Las anotaciones se pueden utilizar para agregar información adicional a sus recursos. Un recurso debe incluir al menos una anotación que describa el recurso y una anotación que describa al propietario del recurso.

En un marco de microservicios, un pod debe contener una anotación que describa los nombres de otros servicios de los que depende el pod. Esto facilita mostrar las dependencias entre los pods. Otras anotaciones pueden incluir información de compilación y versión, así como metainformación (nombres de íconos, etc.) utilizada por otras herramientas o GUI.

5. Proporcionar más información para la terminación del proceso

Para facilitar el diagnóstico, mostrar el motivo de la terminación del contenedor en el estado del pod permite que el proceso en el contenedor escriba un mensaje de terminación en un archivo específico en el sistema del contenedor. Kubelet leerá el contenido de este archivo después de que finalice el contenedor y luego se mostrará en kubectl describe pod. La ruta predeterminada al archivo que este proceso necesita para escribir mensajes de finalización es /dev/termination-log. Esta ruta también se puede personalizar configurando el campo terminaciónMessagePath en la parte de definición del contenedor de la especificación del pod.

Nota: Si el contenedor no escribe mensajes en ningún archivo, el campo terminaciónMessagePolicy solo se puede establecer en FallbackToLogsOnError. En este caso, las últimas líneas del registro del contenedor se tratarán como mensajes de finalización (solo si el contenedor no finalizó correctamente).

6. Procesamiento de registros de aplicaciones

La aplicación escribe el registro en la interrupción de salida estándar en lugar del archivo, y el registro de la aplicación se puede ver fácilmente mediante el comando kubectl log.

Sugerencia: si un contenedor falla y lo reemplaza un nuevo contenedor, verá los registros del nuevo contenedor. Si desea ver los registros del contenedor anterior, al usar el comando kubectl logs, agregue la opción --provious

Si la aplicación escribe registros en un archivo en lugar del terminal de salida estándar, hay otra forma de ver los registros:

$kubectl exec <pod> cat <archivo de registro>        

Este comando ejecuta el comando cat dentro del contenedor, devuelve el flujo de registros a kubectl y kubectl los muestra en su terminal.

Copie registros u otros archivos hacia y desde el contenedor

Transfiera el archivo a la máquina local:

$kubectl cp foo-pod:/var/log/foo.log foo.log

Para copiar archivos desde su máquina local a un pod, puede especificar el nombre del pod como segundo argumento:

$kubectl cp localfile foo-pod:/etc/remotefile

Usar registro centralizado

Kubectl en sí mismo no proporciona ningún registro centralizado y debe admitir el almacenamiento centralizado y el análisis de todos los registros de contenedores a través de otros componentes, que normalmente se ejecutan como pods normales en el clúster.

Implementar una solución de registro centralizado es muy simple. Todo lo que necesita hacer es implementar algunos archivos de manifiesto YAML/JSON, y eso es todo.

En el motor kubernetes de Google, esto es aún más simple, simplemente seleccione la opción "Habilitar registro de Strackdriver" al configurar el clúster.

Quizás haya oído hablar de la pila ELK que consta de ElasticSearch, Logstash y Kibanna, una variante ligeramente modificada de la pila EFK, donde Logstash se reemplaza por FluentD.

Al usar EFK como registro centralizado, cada nodo del clúster de Kubernetes ejecutará un agente FluentD (implementado mediante el uso de DaemonSet como un pod).Este agente es responsable de recopilar registros de contenedores, marcar los registros con información relacionada con el pod y luego enviarlos a ElasticSearch y ElasticSearch los almacenará de forma permanente. ElasticSearch también se implementa como un pod en el clúster. Estos registros se pueden ver y analizar en un navegador web a través de kubana, una herramienta para visualizar datos de ElasticSearch, que a menudo se ejecuta como un pod y se expone como un servicio. Los tres componentes de EFK se muestran en la siguiente figura:

Manejar la entrada de registro de varias líneas

El agente de FluentD almacena cada línea del archivo de registro como una entrada en el almacenamiento de datos de ElasticSearch. Cuando la salida del registro abarca varias líneas, como la pila de excepción de Java, se almacenará en el sistema de registro centralizado como entradas diferentes.

Para resolver este problema, puede hacer que el contenido de salida del registro de la aplicación esté en formato JSON en lugar de texto sin formato. De esta manera, una salida de registro de varias líneas se puede almacenar como una entrada. También se puede mostrar como una entrada en Kibana, pero este enfoque hará que el comando kubectl log para ver el registro sea menos fácil de usar.

La solución es que los registros enviados al terminal de salida estándar sigan siendo registros escalados por el usuario, pero los registros escritos en el archivo de registro para que FluentD los procese están en formato JSON. Esto requiere una configuración razonable del agente de FluentD a nivel de nodo o agregar un contenedor de registro liviano a cada pod.

 

Supongo que te gusta

Origin blog.csdn.net/wwxsoft/article/details/131034972
Recomendado
Clasificación