viernes, enero 31, 2020

Kubernetes: Cómo comprobar la salud de los pods con “probes”

Pues sí amigos y amigas, en este 2020 también vamos a seguir hablando de Docker y sobre todo, de Kubernetes. Cuanto más lo utilizamos, más vemos las grandes posibilidades que ofrece para todo tipo de entornos. Por este motivo es importante también centrarnos especialmente en su seguridad, integridad y disponibilidad.

Figura 1: Kubernetes: Cómo comprobar la salud de los pods con “probes”

De esto trata esta serie, no sólo de mostraros cómo funciona sino, además, de intentar aplicar buenas prácticas para minimizar al máximo todo tipo de riesgos asociados a esta arquitectura. No olvidéis que en nuestro libro Docker: SecDevOps podéis comenzar a adentraros en el mundo de Docker y los contenedores.

Figura 2: Libro Docker: SecDevOps

Kubernetes es una plataforma que nos permite definir el estado de nuestro clúster en profundidad. Por ejemplo, cuando desplegamos un servicio, especificamos cómo queremos correrlo, el número de réplicas o copias que queremos, entre otras muchas acciones.

En ese momento, Kubernetes se encargará de desplegar nuestro servicio e intentará cumplir con “nuestros deseos”. Es decir, levantará el número de copias que hayamos especificado, y si una de dichas copias se cae, levantará una nueva. En otras palabras, Kubernetes nos permite definir el estado de nuestro clúster de forma declarativa e intentará realizar todo lo posible por mantener dicho estado.

Pero ¿cómo sabe cuando un servicio está listo para recibir tráfico? ¿Y si un servicio tiene problemas y necesita ser reiniciado o reemplazado? Para responder a esas preguntas, Kubernetes nos ofrece tres tipos de puntos de comprobación o probes.

Readiness Probe

Esta es la forma en la que podemos decir a Kubernetes que nuestro pod está listo para recibir peticiones. Un ejemplo típico de uso de este tipo probe puede ser que por ejemplo tu pod necesite conectarse a un servicio externo, y éste por el motivo que sea no se encuentra disponible. Asumiendo que tu pod requiera, de forma imperativa, dicho servicio para poder funcionar, no tiene sentido que reciba peticiones nuevas.

Liveness Probe

Es usado por Kubernetes para saber si el pod está en un estado digamos, saludable. En caso contrario, Kubernetes lo reiniciará de forma automática. Un ejemplo de uso podría ser que nuestro pod tuviera algún tipo de problemas de recursos como la memoria, o el disco (volumen). Supongamos que escribimos los logs de pod en un volumen (no persistente) y, bueno, a veces por algún motivo la limpieza de esos logs falla y éste se llena.

El resultado es que nos hemos quedamos sin espacio y la mejor forma de recuperarnos del problema es reiniciando el pod. Normalmente, tiramos de este probe para recuperarnos de excepciones de las que nuestro pod no es capaz de recuperarse. Si un contenedor muere por algún tipo de error, Kubernetes reiniciaría el mismo de la misma forma que lo haría si el liveness probe fallara.

Startup Probe

Este apareció en la versión 1.16 de Kubernetes y actualmente está en versión alpha. El uso ideal del mismo es aplicarlo cuando nuestro pod tarda algún tiempo antes de arrancar por completo. Por ejemplo, hemos movido una aplicación antigua a un contenedor, y ésta tarda demasiado tiempo en levantarse. En este caso, podría ser problemático si no especificamos correctamente nuestro liveness probe ya que podríamos acabar en un reinicio constante del pod, debido a que Kubernetes podría pensar que el pod no responde propiamente y necesita reiniciarlo, cuando en realidad, el problema es que el pod está justo durante su proceso de arranque.

Cuando definimos el startup probe, Kubernetes no habilita el liveness probe para que le indique si el pod ya está arrancado. Si este probe fallara, Kubernetes reiniciaría el pod. Se recomienda que este se ejecute en el mismo chequeo que definamos para nuestro liveness probe. Para ello asignaremos el valor failureThreshold lo suficientemente alto para que nuestro pod tenga el tiempo suficiente para arrancar.

Figura 3: Esquema de un nodo y las "probe"

Vale, ya conocemos los probe, pero ¿cómo los definimos? Existen básicamente tres formas de definir la comprobación de un probe:
· HTTP: quizás la forma más utilizada. En este caso, Kubernetes consulta a través de una petición GET el punto de acceso de nuestro servicio. Si la respuesta se encuentra en el rango de los 200s o 300s, le indica que el probe está saludable. Cualquier otro código de respuesta implica algún tipo de anomalía o error.
· Comando: en este caso, Kubernetes ejecuta el comando indicado. Si éste devuelve 0, el probe se considera saludable, en cualquier otro caso, el resultado se considera problemático .
· TCP: no todos los servicios no son necesariamente HTTP. Por eso, Kubernetes nos ofrece la oportunidad de definir probes basados en TCP. En esta caso, intentaría establecer una conexión TCP al puerto especificado. Si la conexión es satisfactoria, el probe se considera en buena salud, en caso contrario, existe algún problema.
Ok, entonces ¿cuál es la frecuencia de consulta a nuestros probes? ¿cuántos intentos son necesarios para considerar un probe en buen o mal estado? Existen varios parámetros que nos permite definir dicho comportamiento:
· initialDelaySeconds: número de segundos que Kubernetes espera después de que un contenedor se haya inicializado para activar los probes liveness y readiness. Por defecto su valor es 0.
· periodSeconds: Con qué frecuencia Kubernetes ejecuta el probe. Por defecto su valor es 10, y el valor mínimo es 1.
· timeoutSeconds: tiempo, en segundos, que Kubernetes espera cuando ejecuta el probe. El valor por defecto es 1, así como su valor mínimo.
· successThreshold: número de veces consecutivas en que el probe se ejecuta de manera satisfactoria después de un fallo, para que el pod se considere saludable de nuevo. Valor por defecto y mínimo es 1. Para un probe tipo liveness debe ser 1.
· failureThreshold: número de veces consecutivas que debe fallar un probe para que Kubernetes lo considere con problemas. En cuyo caso, si hablamos de liveness, reiniciará el pod, y el caso de readiness, cortará el tráfico a dicho pod. El valor por defecto es 3 y el mínimo 1.
Cada probe puede devolver uno de los tres valores siguientes:
· Success: el contenedor pasa el diagnóstico. 
· Failure: el diagnóstico ha fallado. 
· Unknown: el diagnóstico ha fallado, pero no se toma ninguna acción.
Ahora que ya tenemos la teoría, vamos a pasar a la práctica. Los probes se configuran dentro de la especificación del contenedor.

Ejemplo de Liveness Probe

Veamos algún ejemplo con liveness probe:
    apiVersion: v1
    kind: Pod
    metadata:
        labels:
            test: liveness
        name: liveness-http
        spec:
        containers:
        - name: liveness
            image: k8s.gcr.io/liveness
            args:
            - /server
            livenessProbe:
            httpGet:
                path: /healthz
                port: 8080
                httpHeaders:
                - name: Custom-Header
                  value: Awesome
            initialDelaySeconds: 3
            periodSeconds: 3
Como se puede ver en el ejemplo, el probe se define bajo “livenessProbe:”. En este caso definimos un probe del tipo HTTP, se mandará una petición GET a la IP_CONTENEDOR/healthz:8080, en dicha petición, además se manda una cabecera llamada Custom-Header con el valor Awesome.

Figura 4: PoC Kubernetes Liveness Probe

También se establece que Kubernetes que espere 3 segundos antes de que empiece a mandar dichas peticiones, y que estas se repitan cada 3 segundos. Como no definimos el valor failureThreshold, en el momento en el que Kubernetes reciba tres fallos (valor fuera del rango 200-399) reiniciará el pod.

Ejemplo de Readiness Probe

Ahora veamos un ejemplo para readiness:
    ...
    containers:
    - name: readiness
        image: k8s.gcr.io/busybox
        args:
        - /bin/sh
        - -c
        - touch /tmp/healthy; sleep 10; rm -rf /tmp/healthy; sleep 10; touch /tmp/healthy; sleep 600
        readinessProbe:
          exec:
            command:
            - cat
            - /tmp/healthy
          initialDelaySeconds: 2
          periodSeconds: 2
En este caso, lo que hacemos es cuando arranca nuestro contenedor crea un fichero /tmp/healthy, espera 10 segundos, borra dicho fichero, espera otros 10 segundos y vuelve a crear el mismo fichero y luego espera 5 minutos (después de los 5 minutos, el contenedor morirá y Kubernetes reiniciará el pod).

En este caso nuestro probe lo que hace es ejecutar el comando `cat /tmp/healthy`. Este comando se ejecutará de forma satisfactoria cuando /tmp/healthy exista y fallará cuando éste no exista. El chequeo empieza 2 segundos después de que el contenedor se haya arrancado y lo efectúa cada 2 segundos. En el siguiente vídeo veremos como Kubernetes muestra cuando el pod está listo para recibir tráfico y cuando no.


Figura 5: Kuberneters: PoC Readiness Probe

Definir estos chequeos se consideran buenas prácticas y son aconsejables tenerlos definidos, pero hay que tener cuidado con los parámetros ya que podríamos crear un pequeño caos y hacernos a nosotros mismos un DoS ;)

Reflexión final

Para terminar, estos son algunos consejos sobre cómo utilizar los probes:
· Si defines startup probe, usa la misma comprobación que liveness. 
· No uses la misma comprobación para liveness y readiness. 
· No uses ninguna dependencia en liveness. Por ejemplo, no definas en liveness ninguna lógica conectando a una base de datos. Si la base de datos se cae, Kubernetes reiniciará tu pod y esto no arreglaría el problema y acabaría reiniciado el pod de forma constante. En esta caso readiness sería una mejor opción. 
· Si no tienes ninguna razón por la que reiniciar tu contenedor, no definas liveness. Si el contenedor por algún motivo se rompe, Kubernetes se encargará de reiniciarlo de todas formas. 
· Si tu contenedor tiene algún bug en el que hay veces que deja de funcionar, pero el contenedor sigue vivo, define liveness de forma que chequee esa condición extraordinaria para así poder indicarle a Kubernetes que reinicie el pod. Asegúrate de que implementas bien este chequeo. 
· Conoce los valores por defecto de tiempos. Para asegurarte y evitar confusiones declaralos de forma explícita.
Y esto ha sido todo en este artículo. Esperamos que os sean de utilidad estos ejemplos y consejos en vuestro trabajo con Docker y Kubernetes.

Happy Hacking Hackers!!!!

Autores:

Fran Ramírez, (@cyberhadesblog) es investigador de seguridad y miembro del equipo de Ideas Locas en CDO en Telefónica, co-autor del libro "Microhistorias: Anécdotas y Curiosidades de la historia de la informática (y los hackers)", del libro "Docker: SecDevOps", también de "Machine Learning aplicado a la Ciberseguridad” además del blog CyberHades. Puedes contactar con Fran Ramirez en MyPublicInbox.


Figura 6: Contactar con Fran Ramírez en MyPublicInbox

Rafael Troncoso (@tuxotron) es Senior Software Engineer en SAP Concur, co-autor del libro "Microhistorias: Anécdotas y Curiosidades de la historia de la informática (y los hackers)", del libro "Docker: SecDevOps" además del blog CyberHades. Puedes contactar con Rafael Troncoso en MyPublicInbox.


Figura 7: Contactar con Rafael Troncoso en MyPublicInbox

No hay comentarios:

Entrada destacada

Tu Latch "Hack Your Innovation Contest": Haz un PoC & Hack por 1.000 €

El pasado Telefónica Innovation Day 2024 lanzamos oficialmente el " Tu Latch Hack Your Innovation Contest " en el que repartimos ...

Entradas populares