Mejores prácticas para contenedores de Kubernetes: controles de estado

Mejores prácticas para contenedores de Kubernetes: controles de estado

TL; DR

  • Para lograr una alta observabilidad de contenedores y microservicios, los registros y las métricas primarias no son suficientes.
  • Para una recuperación más rápida y una mayor resiliencia, las aplicaciones deben aplicar el Principio de Alta Observabilidad (HOP).
  • A nivel de aplicación, NOP requiere: registro adecuado, supervisión estrecha, controles de integridad y seguimiento del rendimiento/transición.
  • Utilice cheques como elemento de NOR sonda de preparación и sonda de vida Kubernetes

¿Qué es una plantilla de control de salud?

Al diseñar una aplicación de misión crítica y de alta disponibilidad, es muy importante pensar en un aspecto como la tolerancia a fallos. Una aplicación se considera tolerante a fallos si se recupera rápidamente de un fallo. Una aplicación en la nube típica utiliza una arquitectura de microservicios, donde cada componente se coloca en un contenedor separado. Y para asegurarse de que la aplicación en k8s tenga alta disponibilidad al diseñar un clúster, debe seguir ciertos patrones. Entre ellos se encuentra la Plantilla de control de salud. Define cómo la aplicación comunica a k8s que está en buen estado. Esto no es solo información sobre si el pod se está ejecutando, sino también sobre cómo recibe y responde las solicitudes. Cuanto más sepa Kubernetes sobre el estado del pod, más decisiones inteligentes tomará sobre el enrutamiento del tráfico y el equilibrio de carga. Por lo tanto, el Principio de Alta Observabilidad permite que la aplicación responda a las solicitudes de manera oportuna.

Principio de alta observabilidad (HOP)

El principio de alta observabilidad es uno de principios para el diseño de aplicaciones en contenedores. En una arquitectura de microservicios, a los servicios no les importa cómo se procesa su solicitud (y con razón), lo que importa es cómo reciben las respuestas de los servicios receptores. Por ejemplo, para autenticar a un usuario, un contenedor envía una solicitud HTTP a otro, esperando una respuesta en un formato determinado, eso es todo. PythonJS también puede procesar la solicitud y Python Flask puede responder. Los contenedores son como cajas negras con contenidos ocultos entre sí. Sin embargo, el principio NOP requiere que cada servicio exponga múltiples puntos finales de API que indiquen su estado, así como su estado de preparación y tolerancia a fallas. Kubernetes solicita estos indicadores para pensar en los próximos pasos para el enrutamiento y el equilibrio de carga.

Una aplicación en la nube bien diseñada registra sus eventos principales utilizando los flujos de E/S estándar STDERR y STDOUT. Luego viene un servicio auxiliar, por ejemplo filebeat, logstash o fluentd, que entrega registros a un sistema de monitoreo centralizado (por ejemplo, Prometheus) y un sistema de recopilación de registros (paquete de software ELK). El siguiente diagrama muestra cómo funciona una aplicación en la nube según el patrón de prueba de estado y el principio de alta observabilidad.

Mejores prácticas para contenedores de Kubernetes: controles de estado

¿Cómo aplicar el patrón Health Check en Kubernetes?

Fuera de la caja, k8s monitorea el estado de los pods usando uno de los controladores (Los despliegues, ReplicaSets, Conjuntos de demonios, Conjuntos con estado etcétera etcétera.). Al descubrir que el pod se ha caído por algún motivo, el controlador intenta reiniciarlo o moverlo a otro nodo. Sin embargo, un pod puede informar que está en funcionamiento, pero en sí mismo no funciona. Pongamos un ejemplo: su aplicación utiliza Apache como servidor web, instaló el componente en varios pods del clúster. Dado que la biblioteca se configuró incorrectamente, todas las solicitudes a la aplicación responden con el código 500 (error interno del servidor). Al verificar la entrega, verificar el estado de las cápsulas da un resultado exitoso, pero los clientes piensan de manera diferente. Describiremos esta situación indeseable de la siguiente manera:

Mejores prácticas para contenedores de Kubernetes: controles de estado

En nuestro ejemplo, k8s no verificación de funcionalidad. En este tipo de verificación, el kubelet verifica continuamente el estado del proceso en el contenedor. Una vez que entienda que el proceso se ha detenido, lo reiniciará. Si el error se puede resolver simplemente reiniciando la aplicación y el programa está diseñado para cerrarse ante cualquier error, entonces todo lo que necesita es una verificación del estado del proceso para seguir el NOP y el patrón de prueba de estado. La única lástima es que no todos los errores se eliminan reiniciando. En este caso, k8s ofrece 2 formas más profundas de identificar problemas con el pod: sonda de vida и sonda de preparación.

LivenessProbe

Es hora sonda de vida kubelet realiza 3 tipos de comprobaciones: no solo determina si el pod se está ejecutando, sino también si está listo para recibir y responder adecuadamente a las solicitudes:

  • Configure una solicitud HTTP al pod. La respuesta debe contener un código de respuesta HTTP en el rango de 200 a 399. Por lo tanto, los códigos 5xx y 4xx indican que el pod está teniendo problemas, aunque el proceso se esté ejecutando.
  • Para probar pods con servicios que no son HTTP (por ejemplo, el servidor de correo Postfix), debe establecer una conexión TCP.
  • Ejecute un comando arbitrario para un pod (internamente). La verificación se considera exitosa si el código de finalización del comando es 0.

Un ejemplo de cómo funciona esto. La siguiente definición de pod contiene una aplicación NodeJS que arroja un error 500 en las solicitudes HTTP. Para asegurarnos de que el contenedor se reinicie al recibir dicho error, usamos el parámetro livenessProbe:

apiVersion: v1
kind: Pod
metadata:
 name: node500
spec:
 containers:
   - image: magalix/node500
     name: node500
     ports:
       - containerPort: 3000
         protocol: TCP
     livenessProbe:
       httpGet:
         path: /
         port: 3000
       initialDelaySeconds: 5

Esto no es diferente de cualquier otra definición de pod, pero estamos agregando un objeto .spec.containers.livenessProbe. Parámetro httpGet acepta la ruta a la que se envía la solicitud HTTP GET (en nuestro ejemplo esto es /, pero en escenarios de combate puede haber algo como /api/v1/status). Otro livenessProbe acepta un parámetro initialDelaySeconds, que indica a la operación de verificación que espere un número específico de segundos. El retraso es necesario porque el contenedor necesita tiempo para iniciarse y, cuando se reinicie, no estará disponible durante algún tiempo.

Para aplicar esta configuración a un clúster, utilice:

kubectl apply -f pod.yaml

Después de unos segundos, puede verificar el contenido del pod usando el siguiente comando:

kubectl describe pods node500

Al final de la salida, encuentre eso es lo que.

Como puede ver, livenessProbe inició una solicitud HTTP GET, el contenedor generó un error 500 (que es para lo que fue programado) y kubelet lo reinició.

Si se pregunta cómo se programó la aplicación NideJS, aquí está el app.js y el Dockerfile que se utilizaron:

aplicación.js

var http = require('http');

var server = http.createServer(function(req, res) {
    res.writeHead(500, { "Content-type": "text/plain" });
    res.end("We have run into an errorn");
});

server.listen(3000, function() {
    console.log('Server is running at 3000')
})

Dockerfile

FROM node
COPY app.js /
EXPOSE 3000
ENTRYPOINT [ "node","/app.js" ]

Es importante tener en cuenta esto: livenessProbe solo reiniciará el contenedor si falla. Si un reinicio no corrige el error que impide que el contenedor se ejecute, kubelet no podrá tomar medidas para corregir el problema.

sonda de preparación

readinessProbe funciona de manera similar a livenessProbes (solicitudes GET, comunicaciones TCP y ejecución de comandos), excepto para las acciones de resolución de problemas. El contenedor en el que se detecta el error no se reinicia, sino que se aísla del tráfico entrante. Imagine que uno de los contenedores está realizando muchos cálculos o tiene una carga pesada, lo que aumenta los tiempos de respuesta. En el caso de livenessProbe, se activa la verificación de disponibilidad de respuesta (a través del parámetro de verificación timeoutSeconds), después de lo cual kubelet reinicia el contenedor. Cuando se inicia, el contenedor comienza a realizar tareas que consumen muchos recursos y se reinicia nuevamente. Esto puede ser fundamental para aplicaciones que necesitan velocidad de respuesta. Por ejemplo, un automóvil mientras está en la carretera espera una respuesta del servidor, la respuesta se retrasa y el automóvil sufre un accidente.

Escribamos una definición de redinessProbe que establezca el tiempo de respuesta de la solicitud GET en no más de dos segundos, y la aplicación responderá a la solicitud GET después de 5 segundos. El archivo pod.yaml debería verse así:

apiVersion: v1
kind: Pod
metadata:
 name: nodedelayed
spec:
 containers:
   - image: afakharany/node_delayed
     name: nodedelayed
     ports:
       - containerPort: 3000
         protocol: TCP
     readinessProbe:
       httpGet:
         path: /
         port: 3000
       timeoutSeconds: 2

Implementemos un pod con kubectl:

kubectl apply -f pod.yaml

Esperemos un par de segundos y luego veamos cómo funcionó readinessProbe:

kubectl describe pods nodedelayed

Al final del resultado puede ver que algunos de los eventos son similares. este.

Como puede ver, kubectl no reinició el pod cuando el tiempo de verificación superó los 2 segundos. En cambio, canceló la solicitud. Las comunicaciones entrantes se redirigen a otros pods que funcionan.

Tenga en cuenta que ahora que el pod está descargado, kubectl enruta las solicitudes nuevamente: las respuestas a las solicitudes GET ya no se retrasan.

A modo de comparación, a continuación se muestra el archivo app.js modificado:

var http = require('http');

var server = http.createServer(function(req, res) {
   const sleep = (milliseconds) => {
       return new Promise(resolve => setTimeout(resolve, milliseconds))
   }
   sleep(5000).then(() => {
       res.writeHead(200, { "Content-type": "text/plain" });
       res.end("Hellon");
   })
});

server.listen(3000, function() {
   console.log('Server is running at 3000')
})

TL; DR
Antes de la llegada de las aplicaciones en la nube, los registros eran el medio principal para monitorear y verificar el estado de las aplicaciones. Sin embargo, no había medios para tomar ninguna medida correctiva. Los registros siguen siendo útiles hoy en día; deben recopilarse y enviarse a un sistema de recopilación de registros para analizar situaciones de emergencia y tomar decisiones. [Todo esto se podría hacer sin aplicaciones en la nube usando monit, por ejemplo, pero con k8s se volvió mucho más fácil :) – nota del editor. ]

Hoy en día, las correcciones deben realizarse casi en tiempo real, por lo que las aplicaciones ya no tienen por qué ser cajas negras. No, deberían mostrar puntos finales que permitan a los sistemas de monitoreo consultar y recopilar datos valiosos sobre el estado de los procesos para que puedan responder instantáneamente si es necesario. Esto se denomina patrón de diseño de pruebas de rendimiento y sigue el principio de alta observabilidad (HOP).

Kubernetes ofrece 2 tipos de comprobaciones de estado de forma predeterminada: readinessProbe y livenessProbe. Ambos utilizan los mismos tipos de comprobaciones (solicitudes HTTP GET, comunicaciones TCP y ejecución de comandos). Se diferencian en las decisiones que toman en respuesta a los problemas en los grupos. livenessProbe reinicia el contenedor con la esperanza de que el error no vuelva a ocurrir y readinessProbe aísla el pod del tráfico entrante hasta que se resuelva la causa del problema.

El diseño adecuado de la aplicación debe incluir ambos tipos de verificación y garantizar que recopilen suficientes datos, especialmente cuando se genera una excepción. También debe mostrar los puntos finales API necesarios que proporcionan al sistema de monitoreo (Prometheus) métricas de salud importantes.

Fuente: habr.com

Añadir un comentario