1 - Pods

1.1 - Pods

Los Pods son las unidades de computación desplegables más pequeñas que se pueden crear y gestionar en Kubernetes.

¿Qué és un Pod?

Un Pod (como en una vaina de ballenas o vaina de guisantes) es un grupo de uno o más contenedores (como contenedores Docker), con almacenamiento/red compartidos, y unas especificaciones de cómo ejecutar los contenedores. Los contenidos de un Pod son siempre coubicados, coprogramados y ejecutados en un contexto compartido. Un Pod modela un "host lógico" específico de la aplicación: contiene uno o más contenedores de aplicaciones relativamente entrelazados. Antes de la llegada de los contenedores, ejecutarse en la misma máquina física o virtual significaba ser ejecutado en el mismo host lógico.

Mientras que Kubernetes soporta más runtimes de contenedores a parte de Docker, este último es el más conocido y ayuda a describir Pods en términos de Docker.

El contexto compartido de un Pod es un conjunto de namespaces de Linux, cgroups y, potencialmente, otras facetas de aislamiento, las mismas cosas que aíslan un contenedor Docker. Dentro del contexto de un Pod, las aplicaciones individuales pueden tener más subaislamientos aplicados.

Los contenedores dentro de un Pod comparten dirección IP y puerto, y pueden encontrarse a través de localhost. También pueden comunicarse entre sí mediante comunicaciones estándar entre procesos, como semáforos de SystemV o la memoria compartida POSIX. Los contenedores en diferentes Pods tienen direcciones IP distintas y no pueden comunicarse por IPC sin configuración especial. Estos contenedores normalmente se comunican entre sí a través de las direcciones IP del Pod.

Las aplicaciones dentro de un Pod también tienen acceso a volúmenes compartidos, que se definen como parte de un Pod y están disponibles para ser montados en el sistema de archivos de cada aplicación.

En términos de Docker, un Pod se modela como un grupo de contenedores de Docker con namespaces y volúmenes de sistemas de archivos compartidos.

Al igual que los contenedores de aplicaciones individuales, los Pods se consideran entidades relativamente efímeras (en lugar de duraderas). Como se explica en ciclo de vida del pod, los Pods se crean, se les asigna un identificador único (UID) y se planifican en nodos donde permanecen hasta su finalización (según la política de reinicio) o supresión. Si un nodo muere, los Pods programados para ese nodo se programan para su eliminación después de un período de tiempo de espera. Un Pod dado (definido por su UID) no se "replanifica" a un nuevo nodo; en su lugar, puede reemplazarse por un Pod idéntico, con incluso el mismo nombre si lo desea, pero con un nuevo UID (consulte controlador de replicación para obtener más detalles).

Cuando se dice que algo tiene la misma vida útil que un Pod, como un volumen, significa que existe mientras exista ese Pod (con ese UID). Si ese Pod se elimina por cualquier motivo, incluso si se crea un reemplazo idéntico, el recurso relacionado (por ejemplo, el volumen) también se destruye y se crea de nuevo.

diagrama de Pod

Un Pod de múltiples contenedores que contiene un extractor de archivos y un servidor web que utiliza un volumen persistente para el almacenamiento compartido entre los contenedores.

Motivación para los Pods

Gestión

Los Pods son un modelo del patrón de múltiples procesos de cooperación que forman una unidad de servicio cohesiva. Simplifican la implementación y la administración de las aplicaciones proporcionando una abstracción de mayor nivel que el conjunto de las aplicaciones que lo constituyen. Los Pods sirven como unidad de despliegue, escalado horizontal y replicación. La colocación (coprogramación), el destino compartido (por ejemplo, la finalización), la replicación coordinada, el uso compartido de recursos y la gestión de dependencias se controlan automáticamente para los contenedores en un Pod.

Recursos compartidos y comunicación

Los Pods permiten el intercambio de datos y la comunicación entre los contenedores que lo constituyen.

Todas las aplicaciones en un Pod utilizan el mismo namespace de red (la misma IP y puerto) y, por lo tanto, pueden "encontrarse" entre sí y comunicarse utilizando localhost. Debido a esto, las aplicaciones en un Pod deben coordinar su uso de puertos. Cada Pod tiene una dirección IP en un espacio de red compartido que tiene comunicación completa con otros servidores físicos y Pods a través de la red.

Los contenedores dentro del Pod ven que el hostname del sistema es el mismo que el nombre configurado para el Pod. Hay más información sobre esto en la sección networking.

Además de definir los contenedores de aplicaciones que se ejecutan en el Pod, el Pod especifica un conjunto de volúmenes de almacenamiento compartido. Los volúmenes permiten que los datos sobrevivan a reinicios de contenedores y se compartan entre las aplicaciones dentro del Pod.

Usos de Pods

Los Pods pueden ser usados para alojar pilas de aplicaciones integradas (por ejemplo, LAMP), pero su objetivo principal es apoyar los programas de ayuda coubicados y coadministrados, como:

  • sistemas de gestión de contenido, loaders de datos y archivos, gestores de caché locales, etc.
  • copia de seguridad de registro y punto de control, compresión, rotación, captura de imágenes, etc.
  • observadores de cambio de datos, adaptadores de registro y monitoreo, publicadores de eventos, etc.
  • proxies, bridges y adaptadores.
  • controladores, configuradores y actualizadores.

Los Pods individuales no están diseñados para ejecutar varias instancias de la misma aplicación, en general.

Para una explicación más detallada, ver El sistema distribuido ToolKit: Patrones para Contenedores multiaplicación.

Alternativas

¿Por qué simplemente no ejecutar múltiples programas en un solo contenedor de Docker?

  1. Transparencia. Hacer visibles los contenedores dentro del Pod a la infraestructura permite que esta brinde servicios, como gestión de procesos y monitoreo de recursos, a los contenedores, facilitando una serie de comodidades a los usuarios.
  2. Desacople de dependencias de software. Los contenedores individuales pueden ser versionados, reconstruidos y redistribuidos independientemente. Kubernetes podría incluso apoyar actualizaciones en vivo de contenedores individuales en un futuro.
  3. Facilidad de uso. Los usuarios no necesitan ejecutar sus propios administradores de procesos, para propagación de señales, códigos de salida, etc.
  4. Eficiencia. Debido a que la infraestructura asume más responsabilidad, los contenedores pueden ser más livianos.

¿Por qué no admitir la planificación conjunta de contenedores por afinidad?

Ese enfoque proporcionaría la ubicación conjunta, pero no la mayor parte de beneficios de los Pods, como compartir recursos, IPC, compartir el destino garantizado y gestión simplificada.

Durabilidad de pods (o su ausencia)

Los Pods no están destinados a ser tratados como entidades duraderas. No sobrevivirán a errores de planificación, caídas de nodo u otros desalojos, ya sea por falta de recursos o en el caso de mantenimiento de nodos.

En general, los usuarios no deberían necesitar crear Pods directamente, deberían usar siempre controladores incluso para Pods individuales, como por ejemplo, los Deployments. Los controladores proporcionan autorecuperación con un alcance de clúster, así como replicación y gestión de despliegue. Otros controladores como los StatefulSet pueden tambien proporcionar soporte para Pods que necesiten persistir el estado.

El uso de API colectivas como la principal primitiva de cara al usuario es relativamente común entre los sistemas de planificación de clúster, incluyendo Borg, Marathon, Aurora, y Tupperware.

El Pod se expone como primitiva para facilitar:

  • planificación y capacidad de conexión del controlador
  • soporte para operaciones a nivel de Pod sin la necesidad de "proxy" a través de las API del controlador
  • desacople de la vida útil del Pod de la vida útil del controlador, como para el arranque
  • desacople de controladores y servicios, el endpoint del controlador solo mira Pods
  • composición limpia de funcionalidad a nivel de Kubelet con funcionalidad a nivel de clúster, Kubelet es efectivamente el "controlador de Pod"
  • aplicaciones en alta disponibilidad, que esperan que los Pods sean reemplazados antes de su finalización y ciertamente antes de su eliminación, como en el caso de desalojos planificados o descarga previa de imágenes.

Finalización de Pods

Debido a que los Pods representan procesos en ejecución en los nodos del clúster, es importante permitir que esos procesos finalicen de forma correcta cuando ya no se necesiten (en lugar de ser detenidos bruscamente con una señal de KILL). Los usuarios deben poder solicitar la eliminación y saber cuándo finalizan los procesos, pero también deben poder asegurarse de que las eliminaciones finalmente se completen. Cuando un usuario solicita la eliminación de un Pod, el sistema registra el período de gracia previsto antes de que el Pod pueda ser eliminado de forma forzada, y se envía una señal TERM al proceso principal en cada contenedor. Una vez que el período de gracia ha expirado, la señal KILL se envía a esos procesos y el Pod se elimina del servidor API. Si se reinicia Kubelet o el administrador de contenedores mientras se espera que finalicen los procesos, la terminación se volverá a intentar con el período de gracia completo.

Un ejemplo del ciclo de terminación de un Pod:

  1. El usuario envía un comando para eliminar Pod, con un período de gracia predeterminado (30s)
  2. El Pod en el servidor API se actualiza con el tiempo a partir del cual el Pod se considera "muerto" junto con el período de gracia.
  3. El Pod aparece como "Terminando" cuando aparece en los comandos del cliente
  4. (simultáneo con 3) Cuando el Kubelet ve que un Pod se ha marcado como terminado porque se ha configurado el tiempo en 2, comienza el proceso de apagado del Pod.
    1. Si uno de los contenedores del Pod ha definido un preStop hook, se invoca dentro del contenedor. Si el hook preStop todavía se está ejecutando después de que expire el período de gracia, el paso 2 se invoca con un pequeño período de gracia extendido (2s).
    2. El contenedor recibe la señal TERM. Tenga en cuenta que no todos los contenedores en el Pod recibirán la señal TERM al mismo tiempo y cada uno puede requerir un hook preStop si el orden en el que se cierra es importante.
  5. (simultáneo con 3) Pod se elimina de la lista de endponts del servicio, y ya no se considera parte del conjunto de Pods en ejecución para controladores de replicación. Los Pods que se apagan lentamente no pueden continuar sirviendo el tráfico ya que los balanceadores de carga (como el proxy de servicio) los eliminan de sus rotaciones.
  6. Cuando expira el período de gracia, todos los procesos que todavía se ejecutan en el Pod se eliminan con SIGKILL.
  7. El Kubelet terminará de eliminar el Pod en el servidor API configurando el período de gracia 0 (eliminación inmediata). El Pod desaparece de la API y ya no es visible desde el cliente.

Por defecto, todas las eliminaciones se realizan correctamente en 30 segundos. El comando kubectl delete admite la opción--grace-period = <seconds>que permite al usuario anular el valor predeterminado y especificar su propio valor. El valor 0 forzar eliminación del Pod. Debe especificar un indicador adicional --force junto con --grace-period = 0 para realizar eliminaciones forzadas.

Forzar destrucción de Pods

La eliminación forzada de un Pod se define como la eliminación de un Pod del estado del clúster y etcd inmediatamente. Cuando se realiza una eliminación forzada, el apiserver no espera la confirmación del kubelet de que el Pod ha finalizado en el nodo en el que se estaba ejecutando. Elimina el Pod en la API inmediatamente para que se pueda crear un nuevo Pod con el mismo nombre. En el nodo, los Pods que están configurados para terminar de inmediato recibirán un pequeño período de gracia antes de ser forzadas a matar.

Estas eliminaciones pueden ser potencialmente peligrosas para algunos Pods y deben realizarse con precaución. En el caso de Pods de StatefulSets, consulte la documentación de la tarea para eliminando Pods de un StatefulSet.

Modo privilegiado para Pods

Cualquier contenedor en un Pod puede habilitar el modo privilegiado, utilizando el indicador privilegiado en el contexto de seguridad de la especificación del contenedor. Esto es útil para contenedores que desean usar capacidades de Linux como manipular la pila de red y acceder a dispositivos. Los procesos dentro del contenedor obtienen casi los mismos privilegios que están disponibles para los procesos fuera de un contenedor. Con el modo privilegiado, debería ser más fácil escribir complementos de red y volumen como Pods separados que no necesitan compilarse en el kubelet.

API

Pod es un recurso de nivel superior en la API REST de Kubernetes. La definición de objeto de API Pod describe el objeto en detalle.

1.2 - Ciclo de vida de un Pod

Esta página describe el ciclo de vida de un Pod. Los Pods siguen un ciclo de vida definido, comenzando en la fase Pending, y luego pasando a "en ejecución" Running si al menos uno de sus contenedores primarios se inicia correctamente, y luego pasando a "exitoso" (Succeeded) o "fallido" (Failed) si uno de los contenedores de un Pod termina en error.

Al igual que contenedores de aplicaciones, los Pods se consideran entidades relativamente efímeras. Los Pods se crean y se les asigna un identificador único (UID), y se programan para ejecutarse en nodos donde se mantienen hasta que se terminan (de acuerdo con las políticas de reinicio) o se eliminan.

Si un nodo muere, los Pods programados para ejecutarse en ese Nodo se programan para eliminarse. El plano de control marca los Pods para ser eliminados luego de un periodo de tiempo.

Ciclo de vida de un Pod

Mientras un Pod se está ejecutando, el kubelet puede reiniciar contenedores para manejar algunos tipos de fallos. Dentro de un Pod, Kubernetes rastrea distintos estados del contenedor y determina qué acción realizar para que el Pod esté sano nuevamente.

En la API de Kubernetes, los Pods tienen una especificación y un estatus actual. El estatus de un objeto Pod consiste en un conjunto de condiciones del Pod. También puedes inyectar información de readiness personalizada a los datos de condición de un Pod, si es útil para tu aplicación.

Los Pods solo se programan una vez en su ciclo de vida; asignar un Pod a un nodo específico se llama vincular (binding, en inglés), y el proceso de seleccionar cuál Pod usar se llama programar. Una vez que un Pod está vinculado a un nodo, Kubernetes intenta ejecutar el Pod en ese nodo. El Pod se ejecuta en ese nodo hasta que termina, o hasta que es terminado; si Kubernetes no es capaz de iniciar el Pod en el nodo seleccionado (por ejemplo, si el nodo falla antes que el Pod inicie), entonces ese Pod en particular nunca inicia.

Puedes usar readiness de programación del Pod para retrasar la programación de un Pod hasta que todas sus puertas de programación sean removidas. Por ejemplo, podrías querer definir un conjunto de Pods, pero solo lanzar la programación una vez que todos los Pods hayan sido creados.

Recuperación de fallos en los Pods

Si falla uno de los contenedores en el Pod, Kubernetes puede intentar reiniciar ese contenedor en específico. Para saber más, lea cómo los Pods manejan los errores del contenedor.

Sin embargo, los Pods pueden fallar de una manera que el clúster no puede recuperar, y en ese caso Kubernetes no intenta más sanar el Pod; en su lugar, Kubernetes elimina el Pod y confía en otros componentes para proporcionar una curación automática.

Si un Pod está programado para un nodo y ese nodo luego falla, el Pod se trata como no saludable y Kubernetes eventualmente elimina el Pod. Un Pod no sobrevivirá a una evacuación debido a la falta de recursos o al mantenimiento del Nodo.

Kubernetes utiliza una abstracción de nivel superior, llamada controlador, que maneja el trabajo de gestionar las instancias de Pods relativamente desechables.

Un Pod dado (como se define por un UID) nunca es "reprogramado" a un nodo diferente; en su lugar, ese Pod puede ser reemplazado por un nuevo Pod casi idéntico. Si hace un Pod de reemplazo, incluso puede tener el mismo nombre (como en .metadata.name) que tenía el Pod antiguo, pero el reemplazo tendría un .metadata.uid diferente del Pod antiguo.

Kubernetes no garantiza que un reemplazo de un Pod existente sea programado en el mismo nodo en el que el antiguo Pod estaba siendo reemplazado.

Ciclo de vida asociados

Cuando se dice que algo tiene la misma vida útil que un Pod, como un volúmen, eso significa que el objeto existe mientras ese Pod específico (con ese UID exacto) exista. Si ese Pod se elimina por cualquier razón, e incluso si se crea un reemplazo idéntico, el objeto relacionado (un volumen, en este ejemplo) también se destruye y se crea nuevamente.

Un Pod de varios contenedores que contiene un extractor de archivos sidecar y un servidor web. El Pod utiliza un volumen efímero emptyDir para almacenamiento compartido entre los contenedores.

Figura 1.

Un Pod de varios contenedores que contiene un extractor de archivos sidecar y un servidor web. El Pod utiliza un volumen efímero emptyDir para almacenamiento compartido entre los contenedores.

Fase del Pod

El campo status de un Pod es un objeto PodStatus de Kubernetes que tiene un campo phase.

La fase de un Pod es un resumen simple y de alto nivel de dónde se encuentra el Pod en su ciclo de vida. La fase no pretende ser un resumen completo de observaciones del estado del contenedor o Pod, ni tampoco pretende ser una máquina de estado completa.

El número y los significados de los valores de fase de un Pod están estrechamente guardados. Aparte de lo que se documenta aquí, no se debe asumir nada acerca de los Pods que tienen un valor de phase determinado.

Aquí están los posibles valores de phase:

ValorDescripción
PendingEl clúster de Kubernetes aceptó el Pod, pero uno o más contenedores no se configuraron ni prepararon para ejecutarse. Esto incluye el tiempo que pasa un Pod esperando ser programado, así como el tiempo dedicado a descargar imágenes de contenedores a través de la red.
RunningEl Pod se vinculó a un nodo y se crearon todos los contenedores. Al menos un contenedor todavía se está ejecutando o está en proceso de iniciarse o reiniciarse.
SucceededTodos los contenedores del Pod finalizaron con éxito y no se reiniciarán.
FailedTodos los contenedores del Pod han finalizado y al menos un contenedor ha finalizado con error. Es decir, el contenedor salió con un estado distinto de cero o el sistema lo canceló.
UnknownPor alguna razón no se pudo obtener el estado del Pod. Esta fase generalmente ocurre debido a un error en la comunicación con el nodo donde debería ejecutarse el Pod.

A partir de la versión 1.27 de Kubernetes, el kubelet aplica una transición de los Pods borrados, excepto por Pods estáticos y Pods borrados por la fuerza sin un finalizador, a una fase terminal (Failed o Succeeded dependiendo de los códigos de salida de los contenedores del Pod) antes de su eliminación del servidor API.

Si un Nodo muere o se desconecta del resto del clúster, Kubernetes aplica una política para establecer la phase de todos los Pods en Failed.

Estados del contenedor

Así como la fase del Pod en general, Kubernetes rastrea el estado de cada contenedor dentro de un Pod. Puedes usar hooks del ciclo de vida de un contenedor para lanzar eventos en ciertos puntos en el ciclo de vida de un contenedor.

Una vez que el programador asigna un Pod a un Nodo, el kubelet inicia creando los contenedores para ese Pod usando un runtime del contenedor. Hay 3 estados posibles para un contenedor: Waiting(esperando), Running(en ejecución), y Terminated(terminado).

Para revisar el estado de los contenedores de un Pod, puedes usar kubectl describe pod <name-of-pod>. La salida muestra el estado de cada contenedor dentro del Pod.

Cada estado tiene un significado específico:

Waiting

Si un contenedor no está en el estado Running o Terminated, está Waiting. Un contenedor en el estado Waiting aún está ejecutando las operaciones que requiere para completar su arranque: por ejemplo, descargar la imagen del contenedor de un registro de imágenes de un contenedor, o aplicando datos secretos.

Running

El estado Running indica que el contenedor se está ejecutando sin problemas. Si hay un hook postStart configurado, ya se ha ejecutado y finalizado. Cuando utilizas el comando kubectl para consultar un Pod con un contenedor que está Running, también puedes ver información sobre cuando el contenedor entró en estado Running.

Terminated

Un contenedor en el estado Terminated comenzó su ejecución y luego se terminó con éxito o falló por alguna razón. Cuando usas kubectl para consultar un Pod con un contenedor que está Terminated, puedes ver un motivo, y un código de salida, y la hora de inicio y de finalización del contenedor.

Si un contenedor tiene un hook preStop configurado, el hook se ejecuta antes de que el contenedor entre en estado Terminated.

Cómo los Pods manejan los problemas con los contenedores

Kubernetes maneja los fallos de los contenedores dentro de los Pods usando una política de reinicio, restartPolicy en inglés definida en la especificación spec del Pod. Esta política determina cómo reacciona Kubernetes cuando los contenedores salen debido a errores u otras razones, que sigue la siguiente secuencia:

  1. Fallo inicial: Kubernetes intenta un reinicio inmediato basado en la restartPolicy del Pod.

  2. Fallos repetidos: Después del fallo inicial, Kubernetes aplica un retraso exponencial para los reinicios subsiguientes, descrito en restartPolicy. Esto evita que los intentos de reinicio rápidos y repetidos sobrecarguen el sistema.

  3. Estado de CrashLoopBackOff: Esto indica que el mecanismo de retraso exponencial está actualmente en efecto para un contenedor dado que está en un bucle de fallos, fallando y reiniciando repetidamente.

  4. Reinicio del retraso: Si un contenedor funciona correctamente durante un cierto período (por ejemplo, 10 minutos), Kubernetes reinicia el retraso, tratando cualquier nuevo fallo como el primero.

  5. En la práctica, un CrashLoopBackOff es una condición o evento que podría verse como salida del comando kubectl, al describir o listar Pods, cuando un contenedor en el Pod no arranca correctamente y luego intenta y falla continuamente en un bucle.

En otras palabras, cuando un contenedor entra en el bucle de fallos, Kubernetes aplica el retraso exponencial mencionado en la Política de reinicio del contenedor. Este mecanismo evita que un contenedor defectuoso sobrecargue el sistema con intentos de inicio fallidos continuos.

El CrashLoopBackOff puede ser causado por problemas como los siguientes:

  • Errores de la aplicación que hacen que el contenedor salga.
  • Errores de configuración, como variables de entorno incorrectas o archivos de configuración faltantes.
  • Restricciones de recursos, donde el contenedor puede no tener suficiente memoria o CPU para arrancar correctamente.
  • Fallos en los chequeos de salud si la aplicación no comienza a servir dentro del tiempo esperado.
  • Las sondas de liveness o de arranque del contenedor devuelven un resultado de Failure como se menciona en la sección de sondas. Para investigar la causa raíz de un problema de CrashLoopBackOff, un usuario puede:
  1. Revisar los registros: Use kubectl logs <nombre-del-pod> para revisar los registros del contenedor. Esta es a menudo la forma más directa de diagnosticar el problema que causa los fallos.
  2. Inspeccionar eventos: Use kubectl describe pod <nombre-del-pod> para ver eventos para el Pod, lo que puede proporcionar pistas sobre problemas de configuración o recursos.
  3. Revisar la configuración: Asegúrese de que la configuración del Pod, incluidas las variables de entorno y los volúmenes montados, sea correcta y que todos los recursos externos necesarios estén disponibles.
  4. Verificar los límites de recursos: Asegúrese de que el contenedor tenga suficiente CPU y memoria asignada. A veces, aumentar los recursos en la definición del Pod puede resolver el problema.
  5. Depurar la aplicación: Pueden existir errores o configuraciones incorrectas en el código de la aplicación. Ejecutar esta imagen de contenedor localmente o en un entorno de desarrollo puede ayudar a diagnosticar problemas específicos de la aplicación.

Política de reinicio del contenedor

La especificación (spec en inglés) de un Pod tiene un campo restartPolicy con los posibles valores Always, OnFailure, y Never. El valor por defecto es Always.

La política de reinicio (restartPolicy en inglés) para un Pod aplica a contenedores de apps en el Pod para contenedores de inicialización regulares. Los contenedores sidecar ignoran el campo restartPolicy: en Kubernetes, un sidecar se define como una entrada dentro de initContainers que tiene su restartPolicy a nivel del contenedor establecido en Always. Para contenedores de inicio que finalizan con un error, el kubelet reinicia el contenedor de inicio si el nivel del Pod restartPolicy es OnFailure o Always:

  • Always: Automáticamente reinicia el contenedor luego de alguna terminación.
  • OnFailure: Solo reinicia el contenedor si finaliza con un error (estado de salida distinto de cero).
  • Never: No reinicia el contenedor automáticamente.

Cuando el kubelet está manejando el contenedor, se reinicia de acuerdo con la política de reinicio configurada, que solo se aplica a los reinicios que realizan contenedores de reemplazo dentro del mismo Pod y ejecutándose en el mismo nodo. Después de que los contenedores en un Pod terminan, el kubelet los reinicia con un retraso de retroceso exponencial (10s, 20s, 40s,...), que está limitado a cinco minutos. Una vez que un contenedor se ha ejecutado durante 10 minutos sin ningún problema, el kubelet restablece el temporizador de reinicio para ese contenedor. Ciclo de vida de contenedores Sidecar y el Pod explica el comportamiento de init containers cuando especifica una restartPolicy.

Condiciones del Pod

Un Pod tiene un PodStatus, que tiene un listado de PodConditions a través de los cuales el Pod ha pasado o no. El kubelet administra las siguientes condiciones del Pod:

  • PodScheduled: El Pod está programado para un nodo.
  • PodReadyToStartContainers: (característica beta; habilitada por defecto) La zona de pruebas del Pod se creó correctamente y se configuró la red.
  • ContainersReady: todos los contenedores en el Pod están listos.
  • Initialized: todos los contenedores de inicio han terminado exitosamente.
  • Ready: el Pod es capaz de recibir peticiones y debería ser agregado a los grupos de equilibrio de carga de todos los Services que coincidan.
Nombre del campoDescripción
typeNombre de esta condición del Pod.
statusIndica si la condición es aplicable, con valores posibles "True", "False", ó "Unknown".
lastProbeTimeMarca de tiempo de cuando se probó por última vez la condición del Pod.
lastTransitionTimeMarca de tiempo de cuando el Pod hizo transición de un estado a otro.
reasonTexto legible por máquina que indica el motivo de la última transición de la condición.
messageMensaje legible por humanos indicando detalles acerca de la última transición de estado.

Preparación del Pod

FEATURE STATE: Kubernetes v1.14 [stable]

Tu aplicación puede inyectar retroalimentación adicional o señales al PodStatus: Pod readiness. Para usar esto, establece readinessGates en la spec del Pod para especificar una lista de condiciones adicionales que el kubelet evalúa para la preparación del Pod.

Las condiciones de preparación están determinadas por el estado actual de los campos status.conditions de un Pod. Si Kubernetes no puede encontrar una condición en el campo status.conditions de un Pod, el estado de la condición se establece en "False".

Aquí hay un ejemplo:

kind: Pod
...
spec:
  readinessGates:
    - conditionType: "www.example.com/feature-1"
status:
  conditions:
    - type: Ready                              # una PodCondition construida
      status: "False"
      lastProbeTime: null
      lastTransitionTime: 2018-01-01T00:00:00Z
    - type: "www.example.com/feature-1"        # una PodCondition extra
      status: "False"
      lastProbeTime: null
      lastTransitionTime: 2018-01-01T00:00:00Z
  containerStatuses:
    - containerID: docker://abcd...
      ready: true
...

Las condiciones del Pod que incluyas deben tener nombres que sean válidos para los formatos de etiqueta de Kubernetes.

Estado de preparación del Pod

El comando kubectl patch no admite actualizar el estado del objeto. Para establecer estas status.conditions para el Pod, las aplicaciones y los operadores deberían utilizar la acción Patch.

Puedes utilizar una librería cliente de Kubernetes para escribir código que establece condiciones personalizadas de un Pod para su preparación.

Para los Pods que utilizan condiciones personalizadas, ese Pod es evaluado para estar listo solamente cuando ambas afirmaciones aplican:

  • Todos los contenedores del Pod están listos.
  • Todas las condiciones personalizadas especificadas en readinessGates están True.

Cuando los contenedores de un Pod están listos, pero al menos una condición personalizada está ausente o False, el kubelet establece la condición del Pod en ContainersReady.

Preparación de la red del Pod

FEATURE STATE: Kubernetes v1.29 [beta]

Después de que un Pod es programado en un nodo, necesita ser admitido por el kubelet y tener cualquier volumen de almacenamiento requerido montado. Una vez que estas fases se completan, el kubelet trabaja con un runtime de contenedores (usando Container runtime interface (CRI)) para configurar un sandbox de runtime y configurar la red para el Pod. Si la puerta de características PodReadyToStartContainersCondition está habilitada (está habilitada por defecto para Kubernetes 1.31), la condición PodReadyToStartContainers se agregará al campo status.conditions de un Pod.

La condición PodReadyToStartContainers se establece en False por el kubelet cuando detecta que un Pod no tiene un sandbox de runtime con red configurada. Esto ocurre en los siguientes escenarios:

  • Al principio del ciclo de vida del Pod, cuando el kubelet aún no ha comenzado a configurar un sandbox para el Pod usando el runtime de contenedores.
  • Más adelante en el ciclo de vida del Pod, cuando el sandbox del Pod ha sido destruido debido a:
    • el nodo reiniciándose, sin que el Pod sea desalojado.
    • para runtimes de contenedores que usan máquinas virtuales para aislamiento, la máquina virtual del sandbox del Pod reiniciándose, lo que luego requiere crear un nuevo sandbox y una nueva configuración de red para el contenedor.

La condición PodReadyToStartContainers se establece en True por el kubelet después de la finalización exitosa de la creación del sandbox y la configuración de la red para el Pod por el plugin de runtime. El kubelet puede comenzar a extraer imágenes de contenedores y crear contenedores después de que la condición PodReadyToStartContainers se haya establecido en True.

Para un Pod con contenedores de inicialización, el kubelet establece la condición Initialized en True después de que los contenedores de inicialización se hayan completado exitosamente (lo que ocurre después de la creación exitosa del sandbox y la configuración de la red por el plugin de runtime). Para un Pod sin contenedores de inicialización, el kubelet establece la condición Initialized en True antes de que comience la creación del sandbox y la configuración de la red.

Sondeos del contenedor

Una sonda es un diagnóstico realizado periódicamente por el kubelet en un contenedor. Para ejecutar este diagnóstico, el kubelet ejecuta código dentro del contenedor o realiza una solicitud de red.

Mecanismos de revisión

Existen cuatro maneras diferentes de revisar un contenedor usando una sonda. Cada sonda debe definir exactamente una de estas cuatro maneras:

exec: Ejecuta un comando especificado dentro del contenedor. El diagnóstico se considera exitoso si el comando termina con un código de estado 0.

grpc
Realiza una llamada de procedimiento remoto usando gRPC. El destino debe implementar revisión de estado de gRPC. El diagnóstico se considera exitoso si el status de la respuesta es SERVING.
httpGet
Realiza una petición HTTP GET contra la dirección IP en la ruta y puerto especificado. El diagnóstico se considera exitoso si la respuesta tiene un código de estado mayor o igual que 200 y menor que 400.
tcpSocket
Realiza una revisión TCP contra la dirección IP del Pod en un puerto específico. El diagnóstico se considera exitoso si el puerto está abierto. Si el sistema remoto (el contenedor) cierra la conexión inmediatamente después de abrir la conexión, el diagnóstico se considera exitoso.

Resultados de sondeos

Cada sondeo puede tener uno de tres resultados:

Success
El contenedor ha pasado el diagnóstico.
Failure
El contenedor ha fallado el diagnóstico.
Unknown
El diagnóstico ha fallado (no se debe tomar ninguna acción, y el kubelet hará más revisiones adicionales).

Tipos de sondeo

Opcionalmente, el kubelet puede ejecutar y reaccionar a tres tipos de sondeos en contenedores en ejecución:

livenessProbe
Indica si el contenedor se está ejecutando. Si el sondeo falla, el kubelet mata el contenedor, y el contenedor está sujeto a su política de reinicio. Si un contenedor no tiene un sondeo de liveness, el estado por defecto es Success.
readinessProbe
Indica si un contenedor está preparado para responder a peticiones. Si el sondeo falla, el controlador de endpoints elimina las direcciones IP del Pod de los endpoints de todos los Services que coinciden con el Pod. El estado por defecto de readiness antes del retraso inicial es Failure. Si un contenedor no tiene un sondeo de readiness, el estado por defecto es Success.
startupProbe
Indica si la aplicación dentro del contenedor ha iniciado. El resto de los sondeos están deshabilitados si un sondeo de inicio se proporciona, hasta que se complete. Si el sondeo falla, el kubelet mata el contenedor y el contenedor está sujeto a su política de reinicio. Si un contenedor no tiene un sondeo de inicio, el estado por defecto es Success.

Para mayor información sobre como configurar un sondeo liveness, readiness o de startup, mira la sección Configurar una sonda Liveness, Readiness y Startup.

¿Cuándo debería utilizar un sondeo liveness?

Si el proceso en tu contenedor es capaz de terminar por sí mismo cuando encuentra un error o deja de estar sano, no necesitas un sondeo liveness; el kubelet automáticamente realizará la acción adecuada de acuerdo con la política de reinicio restartPolicy del Pod.

Si te gustaría que tu contenedor fuese destruido y reiniciado si falla un sondeo, especifica un sondeo liveness y especifica una restartPolicy de Always o OnFailure.

¿Cuándo debería utilizar un sondeo readiness?

Se te gustaría enviar tráfico al Pod solo cuando una sonda sea exitosa, especifica un sondeo readiness. En este caso, el sondeo readiness podría ser el mismo que el liveness, pero la existencia del sondeo readines en la especificación significa que el Pod iniciará sin recibir ningún tráfico y solo iniciará cuando el sondeo readiness sea exitoso.

Si quieres que tu contenedor sea capaz de darse de baja por mantenimiento por sí mismo, puedes especificar un sondeo de readiness que revisa un endpoint específico de readiness que es distinto del sondeo liveness.

Si tu aplicación tiene una dependencia estricta con servicios de trasfondo, puedes implementar ambos sondeos de liveness y readiness. El sondeo de liveness pasa cuando la aplicación por sí misma está sana, pero el sondeo de readiness revisa adicionalmente que cada servicio de trasfondo está disponible. Esto ayuda a evitar enviar a Pods que solo pueden responder con errores.

Si tu contenedor necesita trabajar cargando grandes datos, ficheros de configuración, o migraciones durante el inicio, puedes usar un sondeo de inicio. Sin embargo, si quieres detectar la diferencia entre una aplicación que ha fallado y una aplicación que todavía está procesando datos de inicialización, puedes usar un sondeo de readiness.

¿Cuándo debería utilizar un sondeo de inicialización?

Los sondeos de inicialización son útiles para Pods que tienen contenedores que se toman un largo tiempo para estar en servicio. En lugar de especificar un intérvalo largo de liveness, puedes crear una configuración separada para sondear el contenedor en el inicio, permitiendo un tiempo mayor que el intervalo de liveness.

Si tu contenedor usualmente inicia en más de initialDelaySeconds + failureThreshold × periodSeconds, deberías especificar un sondeo de inicialización que revise el mismo endpoint que la sonda liveness. El periodo por defecto periodSeconds es de 10 segundos. Deberías especificar el campo failureThreshold lo suficientemente alto para permitir al contenedor arrancar, sin cambiar los valores por defecto de la sonda liveness. Esto ayuda a proteger contra puntos muertos.

Finalización de Pods

Ya que los Pods representan procesos ejecutándose en nodos de un clúster, es importante permitir que esos procesos terminen con gracia cuando no se necesitan (en lugar de detenerse abruptamente con una señal Kill y sin oportunidad de limpiarse).

El diseño está orientado a permitir que puedas solicitar la eliminación de un Pod y saber cuándo finalizan los procesos, pero también para asegurar que la eliminación se completa eventualmente. Cuando solicitas la eliminación de un Pod, el clúster registra y rastrea el periodo de gracia antes de que el Pod se elimine por la fuerza. Con este rastreo de detención forzada en marcha, el kubelet intenta pararlo con gracia.

Típicamente, con esta finalización con gracia del Pod, el kubelet hace peticiones al tiempo de ejecución del contenedor para intentar detener los contenedores en el Pod, primeramente enviando una señal Term (ej. SIGTERM), con un período de tiempo de gracia, al proceso principal de cada contenedor. Las peticiones para parar los contenedores se procesan de forma asíncrona en el tiempo de ejecución del contenedor. No hay garantía del orden de procesamiento de estas peticiones. Muchos contenedores respetan el valor STOPSIGNAL definido en la imagen del contenedor y, si es diferente, envían el valor de STOPSIGNAL en lugar de TERM.

Una vez que el período de gracia ha acabado, se envía la señal KILL a cualquier proceso restante, y luego el Pod se elimina del Servidor API. Si el kubelet o el tiempo de ejecución del contenedor del servicio que lo administra se reinicia mientras espera que los procesos terminen, el kubelet reintenta de nuevo el proceso incluyendo el periodo original de gracia.

Un flujo de finalización de un Pod, ilustrado con un ejemplo:

  1. Utilizas la herramienta kubectl para eliminar manualmente un Pod específico, con un periodo de gracia por defecto (30 segundos).

  2. El Pod en el servidor API se actualiza con el tiempo más allá del cual el Pod se considera "muerto" junto con el periodo de gracia. Si utilizas kubectl describe para revisar el Pod que estás borrando, ese Pod se mostrará como Terminating. En el nodo donde se ejecuta el Pod: tan pronto como el kubelet observa que el Pod se ha marcado como terminando (se ha definido una duración de parada con gracia), el kubelet comienza el proceso local de parar el Pod.

    1. Si uno de los contenedores del Pod tiene definido un hook preStop y el terminationGracePeriodSeconds en la especificación del Pod no está definido en 0, el kubelet ejecuta ese hook dentro del contenedor. El terminationGracePeriodSeconds por defecto es 30 segundos.

    Si el hook preStop todavía se está ejecutando luego de la expiración del período de gracia, el kubelet solicita una extensión 2 segundos del periodo de gracia.

    1. El kubelet lanza el tiempo de ejecución del contenedor para enviar una señal TERM al proceso 1 dentro de cada contenedor.
  3. Al mismo tiempo que el kubelet inicia la finalización con gracia del Pod, el panel de control evalúa si quitar este Pod en finalización de los objetos EndpointSlice (y Endpoints), donde aquellos objetos representan un Service con un selector configurado.

    Los ReplicaSets y otros recursos de carga de trabajo ya no consideran al Pod como réplica válida, en servicio.

    Los Pods que finalizan lentamente no servirían tráfico regular y debería iniciar la finalización de procesamiento de conexiones abiertas. Algunas aplicaciones necesitan ir más allá de finalizar las conexiones abiertas y necesitan finalización aún con más gracia, por ejemplo, drenar y completar una sesión.

    Cualquier endpoint que representa los Pods en finalización no son removidos inmediatamente de EndpointSlices y se expone un estatus indicando el estado de terminación de la API de EndpointSlice (y la API de Endpoint legada). Los endpoints que están terminando siempre tienen su estatus ready como false (para compatibilidad con versiones anteriores a 1.26), por lo que los balanceadores de carga no los usarán para tráfico regular.

    Si se necesita drenar el tráfico en un Pod que está terminando, el readiness se puede revisar con la condición serving. Puedes encontrar más detalles en cómo implementar drenado de conexiones en el tutorial Pods y flujo de terminación de Endpoints

  1. El kubelet se asegura que el Pod se ha apagado y terminado
    1. Cuando finaliza el tiempo de gracia, si aún existe algún contenedor ejecutándose en el Pod, el kubelet lanza un apagado forzado. El runtime del contenedor envía una señal SIGKILL a cualquier proceso ejecutándose en cualquier contenedor en el Pod. El kubelet también limpia un contenedor pause oculto si ese contenedor usa uno.
    2. El kubelet hace la transición del Pod a una fase terminal (Failed ó Succeeded dependiendo del estado final de sus contenedores).
    3. El Kubelet lanza la eliminación forzosa de los objetos del Pod del servidor API, estableciendo el periodo de gracia a 0 (detención inmediata).
    4. El servidor API elimina el objeto API del Pod, que ya no es visible desde ningún cliente.

Terminación Forzada del Pod

Por defecto, todas las eliminaciones tienen un tiempo de gracia de 30 segundos. El comando kubelet delete soporta la opción --grace-period=<segundos> que permite sobreescribir el valor por defecto y especificar tu propio valor.

Establecer el período de gracia a 0 elimina de forma forzada e inmediata el Pod del servidor API. Si el Pod aún se está ejecutando en un nodo, esa eliminación forzada hace que el kubelet inicie una limpieza inmediata.

Usando kubectl, debes especificar una opción adicional --force junto con --grace-period=0 para realizar eliminaciones forzadas.

Cuando se realiza una eliminación forzada, el servidor API no espera la confirmación del kubelet de que el Pod ha terminado en el nodo en que se está ejecutando. Este elimina el Pod en la API inmediatamente para que se pueda crear un Pod con el mismo nombre. En el nodo, los Pods que están por terminar inmediatamente aún pueden tener un pequeño período de gracia antes de ser eliminados de forma forzada.

Si necesitas eliminar Pods por la fuerza y son parte de un StatefulSet, mira la documentación para borrar Pods de un StatefulSet.

Terminación del Pod y contenedores sidecar

Si tus Pods incluyen uno o más contenedores sidecar (contenedores de inicialización con una política de reinicio Always), el kubelet retrasará el envío de la señal TERM a estos contenedores sidecar hasta que el último contenedor principal se haya terminado completamente. Los contenedores sidecar serán eliminados en orden inverso al que se han definido en la especificación del Pod. Esto asegura que los contenedores sidecar continúan sirviendo a los otros contenedores en el Pod hasta que ya no se necesiten.

Esto significa que la terminación lenta de un contenedor principal también retrasará la terminación de los contenedores sidecar.

Si el periodo de gracia expira antes que se complete el proceso de terminación, el Pod podría entrar en terminación forzada. En este caso, todos los contenedores restantes en el Pod serán terminados simultáneamente con un periodo de gracia corto.

De forma similar, si el Pod tiene un hook preStop que excede el periodo de gracia de finalización, puede ocurrir una terminación de emergencia. En general, si has usado hooks de preStop para controlar el orden de terminación sin contenedores sidecar, puedes quitarlos y permitir que el kubelet maneje la terminación de sidecars automáticamente.

Recolección de elementos no utilizados de los Pods

Cuando los Pods fallan, los objetos API permanecen en el clúster hasta que un humano o el proceso de controlador los elimine explícitamente.

El recolector de elementos no utilizados (PodGC en inglés) es un controlador en el plano de control que elimina los Pods que se han terminado (con una fase de Succeeded o Failed), cuando el número de Pods excede el umbral configurado (determinado por terminated-pod-gc-threshold en el controlador de kube-controller-manager). Esto evita la fuga de recursos mientras que los Pods se crean y se eliminan en el tiempo.

Adicionalmente, el PodGC limpia cualquier Pod que satisfaga cualquiera de las siguientes condiciones:

  1. Pods huérfanos - asociados a un Nodo que ya no existe,
  2. Pods que están finalizando y no están programados,
  3. Pods que están finalizando, asociados a un nodo que no está listo, contaminado con node.kubernetes.io/out-of-service, cuando la condición NodeOutOfServiceVolumeDetach está habilitada.

Cuando la condición PodDisruptionCondition está habilitada, además de limpiar los Pods, el PodGC también los marcará como fallidos si están en una fase no terminal. También, el PodGC agrega una condición de disrupción del Pod cuando realiza la limpieza de un Pod huérfano. Mira condiciones de disrupción del Pod para más detalles.

Siguientes pasos

1.3 - Contenedores de Inicialización

Esta página proporciona una descripción general de los contenedores de inicialización (init containers): contenedores especializados que se ejecutan antes de los contenedores de aplicación en un Pod. Los contenedores de inicialización pueden contener utilidades o scripts de instalación no presentes en una imagen de aplicación.

Tú puedes especificar contenedores de inicialización en la especificación del Pod junto con el arreglo de containers (el cual describe los contenedores de aplicación).

Entendiendo los contenedores de inicialización

Un Pod puede tener múltiples contenedores ejecutando aplicaciones dentro de él, pero también puede tener uno o más contenedores de inicialización que se ejecutan antes de que se inicien los contenedores de aplicación.

Los contenedores de inicialización son exactamente iguales a los contenedores regulares excepto por:

  • Los contenedores de inicialización siempre se ejecutan hasta su finalización.
  • Cada contenedor de inicialiación debe completarse correctamente antes de que comience el siguiente.

Si el contenedor de inicialización de un Pod falla, kubelet reinicia repetidamente ese contenedor de inicialización hasta que tenga éxito. Sin embargo, si el Pod tiene una restartPolicy de Never y un contenedor de inicialización falla durante el inicio de ese Pod, Kubernetes trata al Pod en general como fallido.

Para especificar un contenedor de inicialización para un Pod, agrega el campo initContainers en la especificación del Pod, como un arreglo de elementos container (similar al campo containers de aplicación y su contenido). Consulta Container en la referencia de API para más detalles.

El estado de los contenedores de inicialización se devuelve en el campo .status.initContainerStatuses como un arreglo de los estados del contenedor (similar al campo .status.containerStatuses).

Diferencias con los contenedores regulares

Los contenedores de inicialización admiten todos los campos y características de los contenedores de aplicaciones, incluidos los límites de recursos, los volúmenes y la configuración de seguridad. Sin embargo, las solicitudes de recursos y los límites para un contenedor de inicialización se manejan de manera diferente, como se documenta en Recursos.

Además, los contenedores de inicialización no admiten lifecycle, livenessProbe, readinessProbe o startupProbe porque deben de ejecutarse hasta su finalización antes de que el Pod pueda estar listo.

Si especificas varios contenedores de inicialización para un Pod, kubelet ejecuta cada contenedor de inicialización secuencialmente. Cada contenedor de inicialización debe tener éxito antes de que se pueda ejecutar el siguiente. Cuando todos los contenedores de inicialización se hayan ejecutado hasta su finalización, kubelet inicializa los contenedores de aplicación para el Pod y los ejecuta como de costumbre.

Usando contenedores de inicialización

Dado que los contenedores de inicialización tienen imágenes separadas de los contenedores de aplicaciones, estos tienen algunas ventajas sobre el código relacionado de inicio:

  • Los contenedores de inicialización pueden contener utilidades o código personalizado para la configuración que no están presentes en una imagen de aplicación. Por ejemplo, no hay necesidad de hacer una imagen FROM de otra imagen solo para usar una herramienta como sed, awk, python o dig durante la instalación.
  • Los roles de constructor e implementador de imágenes de aplicación pueden funcionar de forma independiente sin la necesidad de construir conjuntamente una sola imagen de aplicación.
  • Los contenedores de inicialización pueden ejecutarse con una vista diferente al sistema de archivos que los contenedores de aplicaciones en el mismo Pod. En consecuencia, se les puede dar acceso a Secrets a los que los contenedores de aplicaciones no pueden acceder.
  • Debido a que los contenedores de inicialización se ejecutan hasta su finalización antes de que se inicien los contenedores de aplicaciones, los contenedores de inicialización ofrecen un mecanismo para bloquear o retrasar el inicio del contenedor de aplicación hasta que se cumplan una serie de condiciones previas. Una vez que las condiciones previas se cumplen, todos los contenedores de aplicaciones de un Pod pueden iniciarse en paralelo.
  • Los contenedores de inicialización pueden ejecutar de forma segura utilidades o código personalizado que de otro modo harían a una imagen de aplicación de contenedor menos segura. Si mantiene separadas herramientas innecesarias, puede limitar la superficie de ataque a la imagen del contenedor de aplicación.

Ejemplos

A continuación, se muestran algunas ideas sobre cómo utilizar los contenedores de inicialización:

  • Esperar a que se cree un Service usando una sola linea de comando de shell:

    for i in {1..100}; do sleep 1; if nslookup myservice; then exit 0; fi; done; exit 1
    
  • Registrar este Pod con un servidor remoto desde la downward API con un comando como:

    curl -X POST http://$MANAGEMENT_SERVICE_HOST:$MANAGEMENT_SERVICE_PORT/register -d 'instance=$(<POD_NAME>)&ip=$(<POD_IP>)'
    
  • Esperar algo de tiempo antes de iniciar el contenedor de aplicación con un comando como:

    sleep 60
    
  • Clonar un repositorio de Git en un Volume

  • Colocar valores en un archivo de configuración y ejecutar una herramienta de plantilla para generar dinámicamente un archivo de configuración para el contenedor de aplicación principal. Por ejemplo, colocar el valor POD_IP en una configuración y generar el archivo de configuración de la aplicación principal usando Jinja.

Contenedores de inicialización en uso

Este ejemplo define un simple Pod que tiene dos contenedores de inicialización. El primero espera por myservice y el segundo espera por mydb. Una vez que ambos contenedores de inicialización se completen, el Pod ejecuta el contenedor de aplicación desde su sección spec.

apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod
  labels:
    app.kubernetes.io/name: MyApp
spec:
  containers:
  - name: myapp-container
    image: busybox:1.28
    command: ['sh', '-c', 'echo ¡La aplicación se está ejecutando! && sleep 3600']
  initContainers:
  - name: init-myservice
    image: busybox:1.28
    command: ['sh', '-c', "until nslookup myservice.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo esperando a myservice; sleep 2; done"]
  - name: init-mydb
    image: busybox:1.28
    command: ['sh', '-c', "until nslookup mydb.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo esperando a mydb; sleep 2; done"]

Puedes iniciar este Pod ejecutando:

kubectl apply -f myapp.yaml

El resultado es similar a esto:

pod/myapp-pod created

Y verificar su estado con:

kubectl get -f myapp.yaml

El resultado es similar a esto:

NAME        READY     STATUS     RESTARTS   AGE
myapp-pod   0/1       Init:0/2   0          6m

o para más detalles:

kubectl describe -f myapp.yaml

El resultado es similar a esto:

Name:          myapp-pod
Namespace:     default
[...]
Labels:        app.kubernetes.io/name=MyApp
Status:        Pending
[...]
Init Containers:
  init-myservice:
[...]
    State:         Running
[...]
  init-mydb:
[...]
    State:         Waiting
      Reason:      PodInitializing
    Ready:         False
[...]
Containers:
  myapp-container:
[...]
    State:         Waiting
      Reason:      PodInitializing
    Ready:         False
[...]
Events:
  FirstSeen    LastSeen    Count    From                      SubObjectPath                           Type          Reason        Message
  ---------    --------    -----    ----                      -------------                           --------      ------        -------
  16s          16s         1        {default-scheduler }                                              Normal        Scheduled     Successfully assigned myapp-pod to 172.17.4.201
  16s          16s         1        {kubelet 172.17.4.201}    spec.initContainers{init-myservice}     Normal        Pulling       pulling image "busybox"
  13s          13s         1        {kubelet 172.17.4.201}    spec.initContainers{init-myservice}     Normal        Pulled        Successfully pulled image "busybox"
  13s          13s         1        {kubelet 172.17.4.201}    spec.initContainers{init-myservice}     Normal        Created       Created container with docker id 5ced34a04634; Security:[seccomp=unconfined]
  13s          13s         1        {kubelet 172.17.4.201}    spec.initContainers{init-myservice}     Normal        Started       Started container with docker id 5ced34a04634

Para ver los logs de los contenedores de inicialización en este Pod ejecuta:

kubectl logs myapp-pod -c init-myservice # Inspecciona el primer contenedor de inicialización
kubectl logs myapp-pod -c init-mydb      # Inspecciona el segundo contenedor de inicialización

En este punto, estos contenedores de inicialización estarán esperando para descubrir los Servicios denominados mydb y myservice.

Aquí hay una configuración que puedes usar para que aparezcan esos Servicios:

---
apiVersion: v1
kind: Service
metadata:
  name: myservice
spec:
  ports:
  - protocol: TCP
    port: 80
    targetPort: 9376
---
apiVersion: v1
kind: Service
metadata:
  name: mydb
spec:
  ports:
  - protocol: TCP
    port: 80
    targetPort: 9377

Para crear los servicios de mydb y myservice:

kubectl apply -f services.yaml

El resultado es similar a esto:

service/myservice created
service/mydb created

Luego verás que esos contenedores de inicialización se completan y que el Pod myapp-pod pasa al estado Running:

kubectl get -f myapp.yaml

El resultado es similar a esto:

NAME        READY     STATUS    RESTARTS   AGE
myapp-pod   1/1       Running   0          9m

Este sencillo ejemplo debería servirte de inspiración para crear tus propios contenedores de inicialización. ¿Qué es lo que sigue? contiene un enlace a un ejemplo más detallado.

Comportamiento detallado

Durante el inicio del Pod, kubelet retrasa la ejecución de contenedores de inicialización hasta que la red y el almacenamiento estén listos. Después, kubelet ejecuta los contenedores de inicialización del Pod en el orden que aparecen en la especificación del Pod.

Cada contenedor de inicialización debe salir correctamente antes de que comience el siguiente contenedor. Si un contenedor falla en iniciar debido al tiempo de ejecución o sale con una falla, se vuelve a intentar de acuerdo con el restartPolicy del Pod. Sin embargo, si el restartPolicy del Pod se establece en Always, los contenedores de inicialización usan el restartPolicy como OnFailure.

Un Pod no puede estar Ready sino hasta que todos los contenedores de inicialización hayan tenido éxito. Los puertos en un contenedor de inicialización no se agregan a un Servicio. Un Pod que se está inicializando, está en el estado de Pending, pero debe tener una condición Initialized configurada como falsa.

Si el Pod se reinicia o es reiniciado, todos los contenedores de inicialización deben ejecutarse de nuevo.

Los cambios en la especificación del contenedor de inicialización se limitan al campo de la imagen del contenedor. Alterar un campo de la imagen del contenedor de inicialización equivale a reiniciar el Pod.

Debido a que los contenedores de inicialización se pueden reiniciar, reintentar o volverse a ejecutar, el código del contenedor de inicialización debe ser idempotente. En particular, el código que escribe en archivos en EmptyDirs debe estar preparado para la posibilidad de que ya exista un archivo de salida.

Los contenedores de inicialización tienen todos los campos de un contenedor de aplicaciones. Sin embargo, Kubernetes prohíbe el uso de readinessProbe porque los contenedores de inicialización no pueden definir el readiness distinto de la finalización. Esto se aplica durante la validación.

Usa activeDeadlineSeconds en el Pod para prevenir que los contenedores de inicialización fallen por siempre. La fecha límite incluye contenedores de inicialización. Sin embargo, se recomienda utilizar activeDeadlineSeconds si el usuario implementa su aplicación como un Job porque activeDeadlineSeconds tiene un efecto incluso después de que initContainer finaliza. El Pod que ya se está ejecutando correctamente sería eliminado por activeDeadlineSeconds si lo estableces.

El nombre de cada aplicación y contenedor de inicialización en un Pod debe ser único; un error de validación es arrojado para cualquier contenedor que comparta un nombre con otro.

Recursos

Dado el orden y la ejecución de los contenedores de inicialización, las siguientes reglas para el uso de recursos se aplican:

  • La solicitud más alta de cualquier recurso o límite particular definido en todos los contenedores de inicialización es la solicitud/límite de inicialización efectiva. Si algún recurso no tiene un límite de recursos especificado éste se considera como el límite más alto.
  • La solicitud/límite efectiva para un recurso es la más alta entre:
    • la suma de todas las solicitudes/límites de los contenedores de aplicación, y
    • la solicitud/límite de inicialización efectiva para un recurso
  • La planificación es hecha con base en las solicitudes/límites efectivos, lo que significa que los contenedores de inicialización pueden reservar recursos para la inicialización que no se utilizan durante la vida del Pod.
  • El nivel de QoS (calidad de servicio) del nivel de QoS efectivo del Pod es el nivel de QoS tanto para los contenedores de inicialización como para los contenedores de aplicación.

La cuota y los límites son aplicados con base en la solicitud y límite efectivos de Pod.

Los grupos de control de nivel de Pod (cgroups) se basan en la solicitud y el límite de Pod efectivos, al igual que el planificador de Kubernetes (kube-scheduler).

Razones de reinicio del Pod

Un Pod puede reiniciarse, provocando la re-ejecución de los contenedores de inicialización por las siguientes razones:

  • Se reinicia el contenedor de infraestructura del Pod. Esto es poco común y debería hacerlo alguien con acceso de root a los nodos.
  • Todos los contenedores en un Pod son terminados mientras restartPolicy esté configurado en Always, forzando un reinicio y el registro de finalización del contenedor de inicialización se ha perdido debido a la recolección de basura.

El Pod no se reiniciará cuando se cambie la imagen del contenedor de inicialización o cuando se pierda el registro de finalización del contenedor de inicialización debido a la recolección de basura. Esto se aplica a Kubernetes v1.20 y posteriores. Si estás utilizando una versión anterior de Kubernetes, consulta la documentación de la versión que estás utilizando.

Siguientes pasos

1.4 - Interrupciones

Esta guía es para los dueños de aplicaciones que quieren crear aplicaciones con alta disponibilidad y que necesitan entender qué tipos de interrupciones pueden suceder en los Pods.

También es para los administradores de clústeres que quieren aplicar acciones automatizadas en sus clústeres, como actualizar o autoescalar los clústeres.

Interrupciones voluntarias e involuntarias

Los Pods no desaparecen hasta que algo (una persona o un controlador) los destruye o hay problemas de hardware o software que son inevitables.

Nosotros llamamos a esos casos inevitables interrupciones involuntarias de una aplicación. Algunos ejemplos:

  • Una falla en el hardware de la máquina física del nodo.
  • Un administrador del clúster borra una VM (instancia) por error.
  • El proveedor de la nube o el hipervisor falla y hace desaparecer la VM.
  • Un kernel panic.
  • El nodo desaparece del clúster por un problema de red que lo separa del clúster.
  • Una remoción del Pod porque el nodo no tiene recursos suficientes.

A excepción de la condición sin recursos suficientes, todas estas condiciones deben ser familiares para la mayoría de los usuarios, no son específicas de Kubernetes.

Nosotros llamamos a los otros casos interrupciones voluntarias. Estas incluyen las acciones iniciadas por el dueño de la aplicación y aquellas iniciadas por el Administrador del Clúster. Las acciones típicas de los dueños de la aplicación incluyen:

  • borrar el Deployment u otro controlador que maneja el Pod
  • actualizar el Deployment del Pod que causa un reinicio
  • borrar un Pod (por ejemplo, por accidente)

Las acciones del administrador del clúster incluyen:

  • Drenar un nodo para reparar o actualizar.
  • Drenar un nodo del clúster para reducir el clúster (aprenda acerca de Autoescalamiento de Clúster).
  • Remover un Pod de un nodo para permitir que otra cosa pueda ingresar a ese nodo.

Estas acciones pueden ser realizadas directamente por el administrador del clúster, por tareas automatizadas del administrador del clúster o por el proveedor del clúster.

Consulte al administrador de su clúster, a su proveedor de la nube o a la documentación de su distribución para determinar si alguna de estas interrupciones voluntarias está habilitada en su clúster. Si ninguna se encuentra habilitada, puede omitir la creación del presupuesto de Interrupción de Pods.

Tratando con las interrupciones

Estas son algunas de las maneras para mitigar las interrupciones involuntarias:

  • Asegurarse que el Pod solicite los recursos que necesita.
  • Replique su aplicación si usted necesita alta disponibilidad. (Aprenda sobre correr aplicaciones replicadas stateless y stateful
  • Incluso, para una alta disponibilidad mayor cuando se corren aplicaciones replicadas, propague las aplicaciones por varios racks (usando anti-affinity) o usando zonas (si usa un clúster multi-zona.)

La frecuencia de las interrupciones voluntarias varía. En un clúster basico de Kubernetes, no hay interrupciones voluntarias automáticas (solo el usuario las genera). Sin embargo, su administrador del clúster o proveedor de alojamiento puede correr algun servicio adicional que pueda causar estas interrupciones voluntarias. Por ejemplo, desplegando una actualización de software en los nodos puede causar interrupciones. También, algunas implementaciones de clústers con autoescalamiento de nodos puede causar interrupciones para defragmentar o compactar los nodos. Su administrador de clúster o proveedor de alojamiento debe tener documentado cuál es el nivel de interrupciones voluntarias esperadas, sí es que las hay. Ciertas opciones de configuración, como ser usar PriorityClasses en las especificaciones de su Pod pueden también causar interrupciones voluntarias (o involuntarias).

Presupuesto de Interrupción de Pods

FEATURE STATE: Kubernetes v1.21 [stable]

Kubernetes ofrece carácteristicas para ayudar a ejecutar aplicaciones con alta disponibliidad, incluso cuando usted introduce interrupciones voluntarias frecuentes.

Como dueño de la aplicación, usted puede crear un presupuesto de interrupción de Pods (PDB por sus siglas en inglés) para cada aplicación. Un PDB limita el numero de Pods de una aplicación replicada, que estan caídos de manera simultánea por interrupciones voluntarias. Por ejemplo, una aplicación basada en quórum puede asegurarse que el número de réplicas corriendo nunca es menor al número necesitado para obtener el quórum. Una web de tipo front end puede querer asegurarse que el número de réplicas atendiendo al tráfico nunca puede caer bajo un cierto porcentaje del total.

Los administradores del clúster y proveedores de hosting pueden usar herramientas que respeten el presupuesto de interrupción de Pods utilizando la API de Desalojo en vez de directamente borrar Pods o Deployments.

Por ejemplo, el subcomando kubectl drain le permite marcar un nodo a un modo fuera de servicio. Cuando se ejecuta kubectl drain, la herramienta trata de quitar a todos los Pods en el nodo que se esta dejando fuera de servicio. La petición de desalojo que kubectl solicita en su nombre puede ser temporalmente denegado, entonces la herramienta periodicamente reintenta todas las peticiones fallidas hasta que todos los Pods en el nodo afectado son terminados o hasta que el tiempo de espera, que puede ser configurado, es alcanzado.

Un PDB especifica el número de réplicas que una aplicación puede tolerar, relativo a cuantas se pretende tener. Por ejemplo, un Deployment que tiene un .spec.replicas: 5 se supone que tiene 5 Pods en cualquier momento. Si su PDB permite tener 4 a la vez, entonces la API de Desalojo va a permitir interrupciones voluntarias de uno (pero no de dos) Pod a la vez.

El grupo de Pods que comprende a la aplicación está especificado usando una etiqueta selectora, la misma que es usada por el controlador de aplicación (deployment, stateful-set, etc).

El número de Pods "deseado" es calculado a partir de .spec.replicas del recurso de Workload que es manejado para esos Pods. El plano de control descubre el recurso Workload perteneciente al examinar las .metadata.ownerReferences del Pod.

Las Interrupciones Involuntarias no pueden ser prevenidas por los PDB; pero si son contabilizadas a partir de este presupuesto.

Los Pods que son borrados o no están disponibles debido a una actualización continua de una aplicación forman parte del presupuesto de interrupciones, pero los recursos Workload (como los Deployments y StatefulSet) no están limitados por los PDBs cuando se hacen actualizaciones continuas. En cambio, la administración de fallas durante la actualización de la aplicación está configurada en la especificación para este recurso Workload específico.

Cuando un Pod es eliminado usando la API de desalojo, este es terminado correctamente, haciendo honor al terminationGracePeriodSeconds configurado en su PodSpec.

Ejemplo de Presupuesto de Interrupción de POD

Considere un clúster con 3 nodos, nodo-1 hasta nodo-3. El clúster está ejecutando varias aplicaciones. Uno de ellos tiene 3 replicas, que llamaremos pod-a, pod-b, y pod-c. Otro Pod no relacionado y sin PDB, llamado pod-x, también se muestra.

Inicialmente los Pods están distribuidos de esta manera:

nodo-1nodo-2nodo-3
pod-a availablepod-b availablepod-c available
pod-x available

Los 3 Pods son parte de un Deployment, ellos colectivamente tienen un PDB que requiere que por lo menos 2 de los 3 Pods estén disponibles en todo momento.

Por ejemplo, supongamos que el administrador del clúster quiere reiniciar para actualizar el kernel y solucionar un error. El administrador del clúster primero intenta drenar el nodo-1 usando el comando kubectl drain. La herramienta intenta drenar los Pods pod-a y pod-x. Esto tiene éxito inmediatamente. Ambos Pods pasan al estado terminating al mismo tiempo. Esto pone al clúster en el siguiente estado:

nodo-1 drainingnodo-2nodo-3
pod-a terminatingpod-b availablepod-c available
pod-x terminating

El Deployment detecta que uno de los Pods está terminando, entonces crea un reemplazo llamado pod-d. Dado que el nodo-1 está bloqueado, el pod se inicia en otro nodo. Además, se crea el pod pod-y como reemplazo de pod-x.

(Nota: para un StatefulSet, pod-a, que debería llamarse algo como pod-0, debe terminar completamente antes de su reemplazo, que también se llama pod-0 pero tiene un UID diferente, puede ser creado. De lo contrario, el ejemplo también se aplica a un StatefulSet).

Ahora el clúster está en este estado:

nodo-1 drainingnodo-2nodo-3
pod-a terminatingpod-b availablepod-c available
pod-x terminatingpod-d startingpod-y starting

En algún momento, los Pods terminan y el clúster se ve así:

nodo-1 drainednodo-2nodo-3
pod-b availablepod-c available
pod-d startingpod-y starting

En este estado, si un administrador del clúster impaciente intenta drenar el nodo-2 o el nodo-3, el comando de drenado será bloqueado, porque solo hay 2 Pods disponibles para el Deployment y el PDB requiere al menos 2. Después de un tiempo, pod-d y pod-y están disponibles.

El estado del clúster ahora se ve así:

nodo-1 drainednodo-2nodo-3
pod-b availablepod-c available
pod-d availablepod-y available

Ahora, el administrador del clúster drena el nodo-2. El comando de drenado intentará drenar los 2 Pods en algún orden, digamos primero pod-b y luego pod-d. Tendrá éxito en eliminar pod-b. Pero cuando intente drenar pod-d, será rechazado porque eso dejará solo un Pod disponible para el Deployment.

El Deployment crea un reemplazo para pod-b llamado pod-e. Dado que no hay suficientes recursos disponibles en el clúster para programar pod-e, el drenado será bloqueado nuevamente. El clúster terminará en este estado:

nodo-1 drainednodo-2nodo-3no node
pod-b terminatingpod-c availablepod-e pending
pod-d availablepod-y available

Ahora, el administrador del clúster necesita agregar un nuevo nodo en el clúster para continuar con la actualización.

Usted puede ver cómo Kubernetes varía la tasa a la que ocurren las interrupciones, según:

  • cuántas réplicas necesita una aplicación
  • cuánto tiempo lleva apagar una instancia correctamente
  • cuánto tiempo lleva iniciar una nueva instancia
  • el tipo de controlador
  • la capacidad de recursos del clúster

Separación entre el dueño del Clúster y los roles de dueños de la Aplicación

Muchas veces es útil pensar en el Administrador del Clúster y al dueño de la aplicación como roles separados con conocimiento limitado el uno del otro. Esta separación de responsabilidades puede tener sentido en estos escenarios:

  • Cuando hay muchos equipos con aplicaciones compartiendo un clúster de Kubernetes y hay una especialización natural de roles
  • Cuando se usa una herramienta de terceros o un servicio para automatizar el control del clúster

El presupuesto de interrupción de Pods respalda esta separación de roles, proporcionando una interfaz entre los roles.

Si no hay tal separación de responsabilidades en la organización, es posible que no sea necesario el Presupuesto de Interrupción de Pods.

Cómo realizar Acciones Disruptivas en el Clúster

Si usted es el Administrador del Clúster y necesitas realizar una acción disruptiva en todos los nodos del clúster, como una actualización de nodo o de software, estas son algunas de las opciones:

  • Aceptar el tiempo de inactividad mientras dura la actualización.
  • Cambiar a otra réplica completa del clúster.
    • No hay tiempo de inactividad, pero puede ser costoso tener duplicados los nodos y también se requiere esfuerzo humano para orquestar dicho cambio.
  • Diseñar la tolerancia a fallas en la aplicación y usar PDBs.
    • No hay tiempo de inactividad.
    • Duplicación mínima de recursos.
    • Permite mucha más automatización en la administración del clúster.
    • Diseñar aplicaciones para tolerar fallas es complicado, pero el trabajo para tolerar interrupciones involuntarias a menudo vale la pena en comparación con el trabajo de admitir autoescalado y tolerar interrupciones involuntarias.

Siguientes pasos

1.5 - Containers Efímeros

FEATURE STATE: Kubernetes v1.31 [alpha]

Esta página proporciona una descripción general de los Containers efímeros: un tipo especial de Container que se ejecuta temporalmente en un Pod ya existente para cumplir las acciones iniciadas por el usuario, como por ejemplo, la solución de problemas. En vez de ser utilizadas para crear aplicaciones, los Containers efímeros se utilizan para examinar los servicios.

Entendiendo los Containers efímeros

Pods son el componente fundamental de las aplicaciones de Kubernetes. Puesto que los Pods están previstos para ser desechables y reemplazables, no se puede añadir un Container a un Pod una vez creado. Sin embargo, por lo general se eliminan y se reemplazan los Pods de manera controlada utilizando Deployments.

En ocasiones es necesario examinar el estado de un Pod existente, como por ejemplo, para poder solucionar un error difícil de reproducir. Puede ejecutar en estos casos un Container efímero en un Pod ya existente para examinar su estado y para ejecutar comandos de manera arbitraria.

Qué es un Container efímero?

Los Containers efímeros se diferencian de otros Containers en que no garantizan ni los recursos ni la ejecución, y en que nunca se reiniciarán automáticamente, de modo que no son aptos para la construcción de aplicaciones. Los Containers efímeros se describen usando la misma ContainerSpec que los Containers regulares, aunque muchos campos son incompatibles y no están habilitados para los Containers efímeros.

  • Los Containers efímeros no pueden tener puertos, por lo que campos como ports, livenessProbe, readinessProbe no están habilitados.
  • Las asignaciones de recursos del Pod son inmutables, por lo que no esta habilitado configurar "resources".
  • Para obtener una lista completa de los campos habilitados, consulte la documentación de referencia [EphemeralContainer] (/docs/reference/generated/kubernetes-api/v1.31/#ephemeralcontainer-v1-core).

En vez de añadirlos de forma directa al pod.spec, los Containers efímeros se crean usando un controlador especial de la API, ephemeralcontainers, por lo tanto no es posible añadir un Container efímero utilizando kubectl edit.

Al igual en el caso de los Containers regulares, no se puede modificar o remover un Container efímero después de haberlo agregado a un Pod.

Casos de uso para los Containers efímeros

Los Containers efímeros resultan útiles para la solución interactiva de incidencias cuando kubectl exec es insuficiente tanto porque un container se ha caído, como porque la imagen de un Container no incluye las utilidades de depuración.

En particular, las imágenes distroless le permiten desplegar imágenes de Containers mínimos que disminuyen la superficie de ataque y la exposición a errores y vulnerabilidades. Ya que las imágenes distroless no contienen un shell ni ninguna utilidad de depuración, resulta difícil solucionar los problemas de las imágenes distroless usando solamente kubectl exec.

Cuando utilice Containers efímeros, es conveniente habilitar el proceso Namespace de uso compartido para poder ver los procesos en otros containers.

Ejemplos

En los ejemplos de esta sección muestran la forma en que los Containers efímeros se presentan en la API. Los usuarios normalmente usarían un plugin kubectl para la solución de problemas que automatizaría estos pasos.

Los Containers efímeros son creados utilizando el subrecurso ephemeralcontainers del Pod, que puede ser visto utilizando kubectl --raw. En primer lugar describa el Container efímero a añadir como una lista de EphemeralContainers:

{
    "apiVersion": "v1",
    "kind": "EphemeralContainers",
    "metadata": {
        "name": "example-pod"
    },
    "ephemeralContainers": [{
        "command": [
            "sh"
        ],
        "image": "busybox",
        "imagePullPolicy": "IfNotPresent",
        "name": "debugger",
        "stdin": true,
        "tty": true,
        "terminationMessagePolicy": "File"
    }]
}

Para actualizar los Containers efímeros de los example-pod en ejecución:

kubectl replace --raw /api/v1/namespaces/default/pods/example-pod/ephemeralcontainers  -f ec.json

Esto devolverá una nueva lista de Containers efímeros:

{
   "kind":"EphemeralContainers",
   "apiVersion":"v1",
   "metadata":{
      "name":"example-pod",
      "namespace":"default",
      "selfLink":"/api/v1/namespaces/default/pods/example-pod/ephemeralcontainers",
      "uid":"a14a6d9b-62f2-4119-9d8e-e2ed6bc3a47c",
      "resourceVersion":"15886",
      "creationTimestamp":"2019-08-29T06:41:42Z"
   },
   "ephemeralContainers":[
      {
         "name":"debugger",
         "image":"busybox",
         "command":[
            "sh"
         ],
         "resources":{

         },
         "terminationMessagePolicy":"File",
         "imagePullPolicy":"IfNotPresent",
         "stdin":true,
         "tty":true
      }
   ]
}

Se puede ver el estado del Container efímero creado usando kubectl describe:

kubectl describe pod example-pod
...
Ephemeral Containers:
  debugger:
    Container ID:  docker://cf81908f149e7e9213d3c3644eda55c72efaff67652a2685c1146f0ce151e80f
    Image:         busybox
    Image ID:      docker-pullable://busybox@sha256:9f1003c480699be56815db0f8146ad2e22efea85129b5b5983d0e0fb52d9ab70
    Port:          <none>
    Host Port:     <none>
    Command:
      sh
    State:          Running
      Started:      Thu, 29 Aug 2019 06:42:21 +0000
    Ready:          False
    Restart Count:  0
    Environment:    <none>
    Mounts:         <none>
...

Se puede conectar al nuevo Container efímero usando kubectl attach:

kubectl attach -it example-pod -c debugger

Si el proceso Namespace de uso compartido está habilitado, se pueden visualizar los procesos de todos los Containers de ese Pod. Por ejemplo, después de haber conectado, ejecute ps en el debugger del container:

ps auxww

La respuesta es semejante a:

PID   USER     TIME  COMMAND
    1 root      0:00 /pause
    6 root      0:00 nginx: master process nginx -g daemon off;
   11 101       0:00 nginx: worker process
   12 101       0:00 nginx: worker process
   13 101       0:00 nginx: worker process
   14 101       0:00 nginx: worker process
   15 101       0:00 nginx: worker process
   16 101       0:00 nginx: worker process
   17 101       0:00 nginx: worker process
   18 101       0:00 nginx: worker process
   19 root      0:00 /pause
   24 root      0:00 sh
   29 root      0:00 ps auxww

2 - Controladores

2.1 - ReplicaSet

El objeto de un ReplicaSet es el de mantener un conjunto estable de réplicas de Pods ejecutándose en todo momento. Así, se usa en numerosas ocasiones para garantizar la disponibilidad de un número específico de Pods idénticos.

Cómo funciona un ReplicaSet

Un ReplicaSet se define con campos, incluyendo un selector que indica cómo identificar a los Pods que puede adquirir, un número de réplicas indicando cuántos Pods debería gestionar, y una plantilla pod especificando los datos de los nuevos Pods que debería crear para conseguir el número de réplicas esperado. Un ReplicaSet alcanza entonces su propósito mediante la creación y eliminación de los Pods que sea necesario para alcanzar el número esperado. Cuando un ReplicaSet necesita crear nuevos Pods, utiliza su plantilla Pod.

El enlace que un ReplicaSet tiene hacia sus Pods es a través del campo del Pod denominado metadata.ownerReferences, el cual indica qué recurso es el propietario del objeto actual. Todos los Pods adquiridos por un ReplicaSet tienen su propia información de identificación del ReplicaSet en su campo ownerReferences. Y es a través de este enlace cómo el ReplicaSet conoce el estado de los Pods que está gestionando y actúa en consecuencia.

Un ReplicaSet identifica los nuevos Pods a adquirir usando su selector. Si hay un Pod que no tiene OwnerReference o donde OwnerReference no es un controlador, pero coincide con el selector del ReplicaSet, este será inmediatamente adquirido por dicho ReplicaSet.

Cuándo usar un ReplicaSet

Un ReplicaSet garantiza que un número específico de réplicas de un pod se está ejecutando en todo momento. Sin embargo, un Deployment es un concepto de más alto nivel que gestiona ReplicaSets y proporciona actualizaciones de forma declarativa de los Pods junto con muchas otras características útiles. Por lo tanto, se recomienda el uso de Deployments en vez del uso directo de ReplicaSets, a no ser que se necesite una orquestración personalizada de actualización o no se necesite las actualizaciones en absoluto.

En realidad, esto quiere decir que puede que nunca necesites manipular los objetos ReplicaSet: en vez de ello, usa un Deployment, y define tu aplicación en la sección spec.

Ejemplo

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: frontend
  labels:
    app: guestbook
    tier: frontend
spec:
  # modifica las réplicas según tu caso de uso
  replicas: 3
  selector:
    matchLabels:
      tier: frontend
  template:
    metadata:
      labels:
        tier: frontend
    spec:
      containers:
      - name: php-redis
        image: gcr.io/google_samples/gb-frontend:v3

Si guardas este manifiesto en un archivo llamado frontend.yaml y lo lanzas en un clúster de Kubernetes, se creará el ReplicaSet definido y los Pods que maneja.

kubectl apply -f http://k8s.io/examples/controllers/frontend.yaml

Puedes ver los ReplicaSets actuales desplegados:

kubectl get rs

Y ver el frontend que has creado:

NAME       DESIRED   CURRENT   READY   AGE
frontend   3         3         3       6s

También puedes comprobar el estado del replicaset:

kubectl describe rs/frontend

Y verás una salida parecida a la siguiente:

Name:		frontend
Namespace:	default
Selector:	tier=frontend,tier in (frontend)
Labels:		app=guestbook
		tier=frontend
Annotations:	<none>
Replicas:	3 current / 3 desired
Pods Status:	3 Running / 0 Waiting / 0 Succeeded / 0 Failed
Pod Template:
  Labels:       app=guestbook
                tier=frontend
  Containers:
   php-redis:
    Image:      gcr.io/google_samples/gb-frontend:v3
    Port:       80/TCP
    Requests:
      cpu:      100m
      memory:   100Mi
    Environment:
      GET_HOSTS_FROM:   dns
    Mounts:             <none>
  Volumes:              <none>
Events:
  FirstSeen    LastSeen    Count    From                SubobjectPath    Type        Reason            Message
  ---------    --------    -----    ----                -------------    --------    ------            -------
  1m           1m          1        {replicaset-controller }             Normal      SuccessfulCreate  Created pod: frontend-qhloh
  1m           1m          1        {replicaset-controller }             Normal      SuccessfulCreate  Created pod: frontend-dnjpy
  1m           1m          1        {replicaset-controller }             Normal      SuccessfulCreate  Created pod: frontend-9si5l

Y por último, puedes comprobar los Pods que ha arrancado:

kubectl get Pods

Deberías ver la información de cada Pod similar a:

NAME             READY     STATUS    RESTARTS   AGE
frontend-9si5l   1/1       Running   0          1m
frontend-dnjpy   1/1       Running   0          1m
frontend-qhloh   1/1       Running   0          1m

También puedes verificar que la referencia de propietario de dichos pods está puesta al ReplicaSet frontend. Para ello, obtén el yaml de uno de los Pods ejecutándose:

kubectl get pods frontend-9si5l -o yaml

La salida será parecida a esta, donde la información sobre el ReplicaSet aparece en el campo ownerReferences de los metadatos:

apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: 2019-01-31T17:20:41Z
  generateName: frontend-
  labels:
    tier: frontend
  name: frontend-9si5l
  namespace: default
  ownerReferences:
  - apiVersion: extensions/v1beta1
    blockOwnerDeletion: true
    controller: true
    kind: ReplicaSet
    name: frontend
    uid: 892a2330-257c-11e9-aecd-025000000001
...

Adquisiciones de Pods fuera de la plantilla

Aunque puedes crear Pods simples sin problemas, se recomienda encarecidamente asegurarse de que dichos Pods no tienen etiquetas que puedan coincidir con el selector de alguno de tus ReplicaSets. La razón de esta recomendación es que un ReplicaSet no se limita a poseer los Pods especificados en su plantilla -- sino que puede adquirir otros Pods como se explicó en secciones anteriores.

Toma el ejemplo anterior del ReplicaSet frontend, y los Pods especificados en el siguiente manifiesto:

apiVersion: v1
kind: Pod
metadata:
  name: pod1
  labels:
    tier: frontend
spec:
  containers:
  - name: hello1
    image: gcr.io/google-samples/hello-app:2.0

---

apiVersion: v1
kind: Pod
metadata:
  name: pod2
  labels:
    tier: frontend
spec:
  containers:
  - name: hello2
    image: gcr.io/google-samples/hello-app:1.0

Como estos Pods no tienen un Controlador (o cualquier otro objeto) como referencia de propietario y como además su selector coincide con el del ReplicaSet frontend, este último los terminará adquiriendo de forma inmediata.

Supón que creas los Pods después de que el ReplicaSet frontend haya desplegado los suyos para satisfacer su requisito de cuenta de réplicas:

kubectl apply -f http://k8s.io/examples/pods/pod-rs.yaml

Los nuevos Pods serán adquiridos por el ReplicaSet, e inmediatamente terminados ya que el ReplicaSet estaría por encima del número deseado.

Obtener los Pods:

kubectl get Pods

La salida muestra que los nuevos Pods se han terminado, o están en el proceso de terminarse:

NAME             READY   STATUS        RESTARTS   AGE
frontend-9si5l   1/1     Running       0          1m
frontend-dnjpy   1/1     Running       0          1m
frontend-qhloh   1/1     Running       0          1m
pod2             0/1     Terminating   0          4s

Si creas primero los Pods:

kubectl apply -f http://k8s.io/examples/pods/pod-rs.yaml

Y entonces creas el ReplicaSet:

kubectl apply -f http://k8s.io/examples/controllers/frontend.yaml

Verás que el ReplicaSet ha adquirido dichos Pods y simplemente ha creado tantos nuevos como necesarios para cumplir con su especificación hasta que el número de sus nuevos Pods y los originales coincidan con la cuenta deseado. Al obtener los Pods:

kubectl get Pods

Veremos su salida:

NAME             READY   STATUS    RESTARTS   AGE
frontend-pxj4r   1/1     Running   0          5s
pod1             1/1     Running   0          13s
pod2             1/1     Running   0          13s

De esta forma, un ReplicaSet puede poseer un conjunto no homogéneo de Pods

Escribir un manifiesto de ReplicaSet

Al igual que con el esto de los objeto de la API de Kubernetes, un ReplicaSet necesita los campos apiVersion, kind, y metadata. Para los ReplicaSets, el tipo es siempre ReplicaSet. En la versión 1.9 de Kubernetes, la versión apps/v1 de la API en un tipo ReplicaSet es la versión actual y está habilitada por defecto. La versión apps/v1beta2 de la API se ha desaprobado. Consulta las primeras líneas del ejemplo frontend.yaml como guía.

Un ReplicaSet también necesita una sección .spec.

Plantilla Pod

El campo .spec.template es una plantilla pod que es también necesita obligatoriamente tener etiquetas definidas. En nuestro ejemplo frontend.yaml teníamos una etiqueta: tier: frontend. Lleva cuidado de que no se entremezcle con los selectores de otros controladores, no sea que traten de adquirir este Pod.

Para el campo de regla de reinicio de la plantilla, .spec.template.spec.restartPolicy, el único valor permitido es Always, que es el valor predeterminado.

Selector de Pod

El campo .spec.selector es un selector de etiqueta. Como se explicó anteriormente, estas son las etiquetas que se usan para identificar los Pods potenciales a adquirir. En nuestro ejemplo frontend.yaml, el selector era:

matchLabels:
	tier: frontend

El el ReplicaSet, .spec.template.metadata.labels debe coincidir con spec.selector, o será rechazado por la API.

Réplicas

Puedes configurar cuántos Pods deberían ejecutarse de forma concurrente indicando el campo .spec.replicas. El ReplicaSet creará/eliminará sus Pods para alcanzar este número.

Si no indicas el valor del campo .spec.replicas, entonces por defecto se inicializa a 1.

Trabajar con ReplicaSets

Eliminar un ReplicaSet y sus Pods

Para eliminar un ReplicaSet y todos sus Pods, utiliza el comando kubectl delete. El Recolector de basura eliminará automáticamente todos los Pods subordinados por defecto.

Cuando se usa la API REST o la librería client-go, se debe poner el valor de propagationPolicy a Background o Foreground en la opción -d. Por ejemplo:

kubectl proxy --port=8080
curl -X DELETE  'localhost:8080/apis/extensions/v1beta1/namespaces/default/replicasets/frontend' \
> -d '{"kind":"DeleteOptions","apiVersion":"v1","propagationPolicy":"Foreground"}' \
> -H "Content-Type: application/json"

Eliminar sólo un ReplicaSet

Se puede eliminar un ReplicaSet sin afectar a ninguno de sus Pods usando el comando kubectl delete con la opción --cascade=false. Cuando se usa la API REST o la librería client-go, se debe poner propagationPolicy a Orphan. Por ejemplo:

kubectl proxy --port=8080
curl -X DELETE  'localhost:8080/apis/extensions/v1beta1/namespaces/default/replicasets/frontend' \
> -d '{"kind":"DeleteOptions","apiVersion":"v1","propagationPolicy":"Orphan"}' \
> -H "Content-Type: application/json"

Una vez que se ha eliminado el original, se puede crear un nuevo ReplicaSet para sustituirlo. Mientras el viejo y el nuevo .spec.selector sean el mismo, el nuevo adoptará a los viejos Pods. Sin embargo, no se esforzará en conseguir que los Pods existentes coincidan con una plantilla pod nueva, diferente. Para actualizar dichos Pods a la nueva especificación de forma controlada, usa una actualización en línea.

Aislar Pods de un ReplicaSet

Es posible aislar Pods de un ReplicaSet cambiando sus etiquetas. Esta técnica puede usarse para eliminar Pods de un servicio para poder depurar, recuperar datos, etc. Los Pods que se eliminar de esta forma serán sustituidos de forma automática (siempre que el número de réplicas no haya cambiado).

Escalar un ReplicaSet

Se puede aumentar o reducir fácilmente un ReplicaSet simplemente actualizando el campo .spec.replicas. El controlador del ReplicaSet se asegura de que el número deseado de Pods con un selector de etiquetas coincidente está disponible y operacional.

ReplicaSet como blanco de un Horizontal Pod Autoscaler

Un ReplicaSet puede también ser el blanco de un Horizontal Pod Autoscalers (HPA). Esto es, un ReplicaSet puede auto-escalarse mediante un HPA. Aquí se muestra un ejemplo de HPA dirigido al ReplicaSet que creamos en el ejemplo anterior.

apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: frontend-scaler
spec:
  scaleTargetRef:
    kind: ReplicaSet
    name: frontend
  minReplicas: 3
  maxReplicas: 10
  targetCPUUtilizationPercentage: 50

Si guardas este manifiesto en un archivo hpa-rs.yaml y lo lanzas contra el clúster de Kubernetes, debería crear el HPA definido que auto-escala el ReplicaSet destino dependiendo del uso de CPU de los Pods replicados.

kubectl apply -f https://k8s.io/examples/controllers/hpa-rs.yaml

Alternativamente, puedes usar el comando kubectl autoscale para conseguir el mismo objetivo (¡y mucho más fácil!)

kubectl autoscale rs frontend --max=10

Alternativas al ReplicaSet

Deployment (recomendado)

UnDeployment es un objeto que puede poseer ReplicaSets y actualizar a estos y a sus Pods mediante actualizaciones en línea declarativas en el servidor. Aunque que los ReplicaSets puede usarse independientemente, hoy en día se usan principalmente a través de los Deployments como el mecanismo para orquestrar la creación, eliminación y actualización de los Pods. Cuando usas Deployments no tienes que preocuparte de gestionar los ReplicaSets que crean. Los Deployments poseen y gestionan sus ReplicaSets. Por tanto, se recomienda que se use Deployments cuando se quiera ReplicaSets.

Pods simples

A diferencia del caso en que un usuario creaba Pods de forma directa, un ReplicaSet sustituye los Pods que se eliminan o se terminan por la razón que sea, como en el caso de un fallo de un nodo o una intervención disruptiva de mantenimiento, como una actualización de kernel. Por esta razón, se recomienda que se use un ReplicaSet incluso cuando la aplicación sólo necesita un único Pod. Entiéndelo de forma similar a un proceso supervisor, donde se supervisa múltiples Pods entre múltiples nodos en vez de procesos individuales en un único nodo. Un ReplicaSet delega los reinicios del contenedor local a algún agente del nodo (por ejemplo, Kubelet o Docker).

Job

Usa un Job en vez de un ReplicaSet para aquellos Pods que se esperan que terminen por ellos mismos (esto es, trabajos por lotes).

DaemonSet

Usa un DaemonSet en vez de un ReplicaSet para aquellos Pods que proporcionan funcionalidad a nivel de servidor, como monitorización de servidor o logging de servidor. Estos Pods tienen un ciclo de vida asociado al del servidor mismo: el Pod necesita ejecutarse en el servidor antes de que los otros Pods comiencen, y es seguro que terminen cuando el servidor esté listo para ser reiniciado/apagado.

ReplicationController

Los ReplicaSets son los sucesores de los ReplicationControllers. Los dos sirven al mismo propósito, y se comportan de forma similar, excepto porque un ReplicationController no soporta los requisitos del selector basado en conjunto, como se describe en la guía de usuario de etiquetas. Por ello, se prefiere los ReplicaSets a los ReplicationControllers.

2.2 - ReplicationController

Un ReplicationController garantiza que un número determinado de réplicas se estén ejecutando en todo momento. En otras palabras, un ReplicationController se asegura que un pod o un conjunto homogéneo de pods siempre esté arriba y disponible.

Cómo Funciona un ReplicationController

Si hay muchos pods, el ReplicationController termina los pods extra. Si hay muy pocos, el ReplicationController arranca más pods. A difrencia de los pods creados manualmente, los pods mantenidos por un ReplicationController se sustituyen de forma automática si fallan, se borran, o se terminan. Por ejemplo, tus pods se re-crean en un nodo durante una intervención disruptiva de mantenimiento como una actualización del kernel. Por esta razón, deberías usar un ReplicationController incluso cuando tu aplicación sólo necesita un único pod. Un ReplicationController es parecido a un supervisor de procesos, pero en vez de supervisar procesos individuales en un único nodo, el ReplicationController supervisa múltiples pods entre múltiples nodos.

A menudo nos referimos a un ReplicationController de forma abreviada como "rc" o "rcs", así como atajo en los comandos de kubectl.

Un caso simple es crear un objeto ReplicationController para ejecutar de manera fiable una instancia de un Pod indefinidamente. Un caso de uso más complejo es ejecutar varias réplicas idénticas de un servicio replicado, como los servidores web.

Ejecutar un ejemplo de ReplicationController

Esta configuración de un ReplicationController de ejemplo ejecuta tres copias del servidor web nginx.

apiVersion: v1
kind: ReplicationController
metadata:
  name: nginx
spec:
  replicas: 3
  selector:
    app: nginx
  template:
    metadata:
      name: nginx
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80

Ejecuta el ejemplo descargando el archivo de ejemplo y ejecutando este comando:

kubectl apply -f https://k8s.io/examples/controllers/replication.yaml
replicationcontroller/nginx created

Comprueba el estado del ReplicationController con este comando:

kubectl describe replicationcontrollers/nginx
Name:        nginx
Namespace:   default
Selector:    app=nginx
Labels:      app=nginx
Annotations:    <none>
Replicas:    3 current / 3 desired
Pods Status: 0 Running / 3 Waiting / 0 Succeeded / 0 Failed
Pod Template:
  Labels:       app=nginx
  Containers:
   nginx:
    Image:              nginx
    Port:               80/TCP
    Environment:        <none>
    Mounts:             <none>
  Volumes:              <none>
Events:
  FirstSeen       LastSeen     Count    From                        SubobjectPath    Type      Reason              Message
  ---------       --------     -----    ----                        -------------    ----      ------              -------
  20s             20s          1        {replication-controller }                    Normal    SuccessfulCreate    Created pod: nginx-qrm3m
  20s             20s          1        {replication-controller }                    Normal    SuccessfulCreate    Created pod: nginx-3ntk0
  20s             20s          1        {replication-controller }                    Normal    SuccessfulCreate    Created pod: nginx-4ok8v

Como se puede observar, se han creado tres pods, pero ninguno se está ejecutándose todavía, puede que porque la imagen todavía se está descargando. Unos momentos después, el mismo comando puede que muestre:

Pods Status:    3 Running / 0 Waiting / 0 Succeeded / 0 Failed

Para listar todos los pods que pertenecen al ReplicationController de forma legible, puedes usar un comando como el siguiente:

pods=$(kubectl get pods --selector=app=nginx --output=jsonpath={.items..metadata.name})
echo $pods
nginx-3ntk0 nginx-4ok8v nginx-qrm3m

Como se puede ver, el selector es el mismo que el selector del ReplicationController (mostrado en la salida de kubectl describe), y con una forma diferente a lo definido en el archivo replication.yaml. La opción --output=jsonpath especifica una expresión que simplemente muestra el nombre de cada pod en la lista devuelta.

Escribir una especificación de ReplicationController

Al igual que con el resto de configuraciones de Kubernetes, un ReplicationController necesita los campos apiVersion, kind, y metadata. Para información general acerca del trabajo con archivos de configuración, ver la gestión de objetos.

Un ReplicationController también necesita un sección .spec.

Plantilla Pod

El campo .spec.template es el único campo requerido de .spec.

El campo .spec.template es una plantilla pod. Tiene exactamente el mismo esquema que un pod, excepto por el hecho de que está anidado y no tiene los campos apiVersion ni kind.

Además de los campos obligatorios de un Pod, una plantilla pod de un ReplicationController debe especificar las etiquetas apropiadas y la regla de reinicio apropiada. En el caso de las etiquetas, asegúrate que no se entremezclan con otros controladores. Ver el selector de pod.

Sólo se permite el valor Always para el campo .spec.template.spec.restartPolicy, que es el valor predeterminado si no se indica.

Para los reinicios locales de los contenedores, los ReplicationControllers delegan en los agentes del nodo, por ejmplo el Kubelet o Docker.

Etiquetas en los ReplicationController

los ReplicationController puede tener sus propias (.metadata.labels). Normalmente, se indicaría dichas etiquetas con los mismos valores que el campo .spec.template.metadata.labels; si el campo .metadata.labels no se indica, entonces se predetermina al valor de .spec.template.metadata.labels. Sin embargo, se permite que sean diferentes, y el valor de .metadata.labels no afecta al comportamiento del ReplicationController.

Selector de Pod

El campo .spec.selector es un selector de etiqueta. Un ReplicationController gestiona todos los pods con etiquetas que coinciden con el selector. No distingue entre pods que creó o eliminó, y pods que otra persona o proceso creó o eliminó. Esto permite sustituir al ReplicationController sin impactar a ninguno de sus pods que se esté ejecutando.

Si se indica, el valor de .spec.template.metadata.labels debe ser igual al de .spec.selector, o será rechazado por la API. Si no se indica el valor de .spec.selector, se tomará como predeterminado el de .spec.template.metadata.labels.

Tampoco deberías crear ningún pod cuyas etiquetas coincidan con las de este selector, ni directamente con otro ReplicationController, ni con otro controlador como un Job. Si lo haces, el ReplicationController piensa que el creó también los otros pods. Kubernetes no te impide hacerlo.

Si al final terminas con múltiples controladores que tienen selectores que se entremezclan, tendrás que gestionar la eliminación tú mismo (ver abajo).

Múltiples Réplicas

Puedes configurar cuántos pods deberían ejecutarse de forma concurrente poniendo el valor de .spec.replicas al número de pods que te gustaría tener ejecutándose a la vez. El número de ejecuciones en cualquier momento puede que sea superior o inferior, dependiendo de si las réplicas se han incrementado o decrementado, o si un pod se ha apagado de forma controlada, y su sustituto arranca más pronto.

Si no se indica el valor de .spec.replicas, entonces se predetermina a 1.

Trabajar con ReplicationControllers

Eliminar un ReplicationController y sus Pods

Para eliminar un ReplicationController y todos sus pods, usa el comando kubectl delete. Kubectl reducirá el ReplicationController a cero y esperará que elimine cada pod antes de eliminar al ReplicationController mismo. Si este comando kubectl se interrumpe, puede ser reiniciado.

Cuando uses la API REST o la librería Go, necesitas realizar los pasos de forma explícita (reducir las réplicas a cero, esperar a que se eliminen los pods, y entonces eliminar el ReplicationController).

Eliminar sólo el ReplicationController

Puedes eliminar un ReplicationController sin impactar a ninguno de sus Pods.

Usando kubectl, indica la opción --cascade=false en el comando kubectl delete.

Cuando uses la API REST o la librería Go, simplemente elimina objeto ReplicationController.

Una vez que el objeto original se ha eliminado, puedes crear un nuevo ReplicationController para sustituirlo. Mientras el viejo y el nuevo valor del .spec.selector sea el mismo, el nuevo adoptará a los viejos pods. Sin embargo, no se molestará en hacer que los pods actuales coincidan con una plantilla pod nueva, diferente. Para actualizar los pods con una nueva especificación de forma controlada, utiliza la actualización en línea.

Aislar pods de un ReplicationController

Se puede aislar Pods del conjunto destino de un ReplicationController cambiando sus etiquetas. Esta técnica puede usarse para eliminar pods de un servicio para poder depurarlos, recuperar datos, etc. Los Pods que se eliminan de esta forma serán sustituidos de forma automática (asumiendo que el número de réplicas no ha cambiado tampoco).

Patrones comunes de uso

Reprogramación

Como se comentó arriba, cuando tienes 1 pod que quieres mantener ejecutándose, o 1000, un ReplicationController se asegura de que el número indicado de pods exista, incluso si falla un nodo o se termina algún pod (por ejemplo, debido a alguna acción de otro agente de control).

Escalado

El ReplicationController facilita el escalado del número de réplicas tanto para su aumento como para su disminución, bien manualmente o mediante un agente de auto-escalado, simplemente actualizando el campo replicas.

Actualizaciones en línea

El ReplicationController se ha diseñado para facilitar las actualizaciones en línea de un servicio mediante la sustitución de sus pods uno por uno.

Cómo se explicó en #1353, la estrategia recomendada es crear un nuevo ReplicationController con 1 réplica, escalar el nuevo (+1) y el viejo (-1) controlador uno por uno, y entonces eliminar el viejo controlador una vez que alcanza las 0 réplicas. Esto actualiza de forma predecible el conjunto de pods independientemente de que se produzcan fallos inesperados.

De forma ideal, el controlador de actualización en línea tendrá en cuenta si la aplicación está lista, y se asegurará de que un número suficiente de pods está en servicio en todo momento.

Los dos ReplicationControllers necesitarán crear pods con al menos una etiqueta diferenciadora, como la etiqueta de imagen del contenedor primario del pod, ya que las actualizaciones de imagen son las que normalmente desencadenan las actualizaciones en línea.

La actualización en línea se implementa a través de la herramienta cliente mediante kubectl rolling-update. Echa un vistazo a la tarea kubectl rolling-update para más ejemplos concretos.

Múltiples operaciones de despliegue

Además de llevar a cabo múltiples despliegues de una aplicación cuando una actualización en línea está en progreso, es común ejecutar varios despliegues durante un período extendido de tiempo, o incluso de forma contínua, usando múltiples operaciones de despliegue. Dichas operaciones se diferenciarían por etiquetas.

Por ejemplo, un servicio puede que exponga todos los pods con etiquetas tier in (frontend), environment in (prod). Ahora digamos que tenemos 10 pods replicados que forman este grupo. Pero queremos poder desplegar una nueva versión 'canary' de este component. Se podría configurar un ReplicationController con el valor de replicas puesto a 9 para la mayor parte de las réplicas, con etiquetas tier=frontend, environment=prod, track=stable, y otro ReplicationController con el valor de replicas puesto a 1 para el 'canary', con las etiquetas tier=frontend, environment=prod, track=canary. Así el servicio cubriría tanto los pods canary como el resto. Pero también es posible trastear con los ReplicationControllers de forma separada para probar cosas, monitorizar los resultados, etc.

Usar ReplicationControllers con servicios

Un único servicio puede exponer múltiples ReplicationControllers, de forma que, por ejemplo, algo de tráfico vaya a la versión vieja, y otro tanto vaya a la versión nueva.

Un ReplicationController nunca se terminará por sí mismo, pero tampoco se espera que se ejecute permanentemente como los servicios. Los servicios puede que estén compuestos de pods controlados por múltiples ReplicationControllers, y se espera que muchos ReplicationControllers se creen y se destruyan durante el ciclo de vida de un servicio (por ejemplo, para realizar una actualización de los pods que ejecutan el servicio). Ambos servicios mismos y sus clientes deberían permanecer ajenos a los ReplicationControllers que mantienen los pods que proporcionan los servicios.

Escribir aplicaciones que se repliquen

Los Pods creados por un ReplicationController están pensados para que sean intercambiables y semánticamente idénticos, aunque sus configuraciones puede que sean heterogéneas a lo largo del tiempo. Este es un ajuste obvio para los servidores sin estado replicados, pero los ReplicationControllers también pueden utilizarse para mantener la disponibilidad de aplicaciones que se elijen por un maestro, las particionadas, y las de grupos de trabajadores. Dichas aplicaciones deberían usar los mecanismos de asignación dinámica de trabajo, como las colas de trabajo RabbitMQ, en vez de la personalización estática/de una sola vez en la configuración de cada pod, ya que se considera un anti-patrón. Cualquier personalización de pod que se haga, como el calibrado vertical automático de recursos (por ejemplo, cpu o memoria), debería realizarse a través de otro proceso de controlador en línea, no con el mismo ReplicationController.

Responsabilidades de un ReplicationController

El ReplicationController simplemente garantiza que el número deseado de pods coincide con su selector de etiqueta y que son operacionales. Actualmente, sólo los pods que han terminado se excluyen de la cuenta. En el futuro, la disponibilidad y otra información disponible en el sistema se tendrá en cuenta, se añadirá más controles sobre la regla de sussitución, y se está planificando emitir eventos que podrían ser aprovechados por clientes externos para implementar reglas complejas de sustitución y escalado de forma arbitraria.

El ReplicationController está siempre condicionado a esta reducida responsabilidad. Él mismo no llevará a cabo ni pruebas de estar listo ni vivo. En vez de aplicar el auto-escalado, se pretende que este sea realizado por un auto-escalador externo (como se vio en #492), que sería el encargado de cambiar su campo replicas. No se añadirá reglas de programación (por ejemplo, propagación) al ReplicationController. Ni se debería validar que los pods controlados coincidan con la plantilla actual especificada, ya que eso obstruiría el auto-calibrado y otros procesos automáticos. De forma similar, los vencimientos de término, las dependencias de orden, la extensión de la configuración, y otras características se aplican en otro lado. Incluso se plantea excluir el mecanismo de creación de pods a granel (#170).

El ReplicationController está pensado para ser una primitiva de bloques is intended to be a composable building-block primitive. We expect higher-level APIs and/or tools to be built on top of it and other complementary primitives for user convenience in the future. The "macro" operations currently supported by kubectl (run, scale, rolling-update) are proof-of-concept examples of this. For instance, we could imagine something like Asgard managing ReplicationControllers, auto-scalers, services, scheduling policies, canaries, etc.

Objeto API

El ReplicationController es un recurso de alto nivel en la API REST de Kubernetes. Más detalles acerca del objeto API se pueden encontrar aquí: Objeto API ReplicationController.

Alternativas al ReplicationController

ReplicaSet

El ReplicaSet es el ReplicationController de nueva generación que soporta el nuevo selector de etiqueta basado en conjunto. Se usa principalmente por el Deployment como un mecanismo para orquestrar la creación de pods, la eliminación y las actualizaciones. Nótese que se recomienda usar Deployments en vez de directamente usar los ReplicaSets, a menos que necesites una orquestración personalizada de actualizaciones o no quieras actualizaciones en absoluto.

Deployment (Recomendado)

El Deployment es un objeto de alto nivel de la API que actualiza sus ReplicaSets subyacenetes y sus Pods de forma similar a cómo lo hace el comando kubectl rolling-update. Se recomienda el uso de Deployments si se quiere esta functionalidad de actualización en línea, porque a diferencia del comando kubectl rolling-update, son declarativos, se ejecutan del lado del servidor, y tienen características adicionales.

Pods simples

A diferencia del caso en que un usuario ha creado directamente pods, un ReplicationController sustituye los pods que han sido eliminador o terminados por cualquier motivo, como en el caso de un fallo de un nodo o una intervención disruptiva de mantenimiento, como la actualización del kernel. Por esta razón, se recomienda que se usa un ReplicationController incluso si tu aplicación sólo necesita un único pod. Piensa que es similar a un supervisor de proceso, sólo que supervisa múltiples pods entre múltiples nodos en vez de procesos individuales en un único nodo. Un ReplicationController delega los reinicios locales de los contenedores a algún agente del nodo (por ejemplo, Kubelet o Docker).

Job

Usa un Job en vez de un ReplicationController para aquellos pods que se espera que terminen por sí mismos (esto es, trabajos por lotes).

DaemonSet

Usa un DaemonSet en vez de un ReplicationController para aquellos pods que proporcionan una función a nivel de servidor, como la monitorización o el loggin de servidor. Estos pods tienen un ciclo de vida que está asociado al del servidor: el pod necesita ejecutarse en el servidor antes que los otros pods arranquen, y es seguro terminarlo cuando el servidor está listo para reiniciarse/apagarse.

Para más información

Lee Ejecutar Aplicaciones sin Estado con un ReplicationController.

2.3 - Deployment

Un controlador de Deployment proporciona actualizaciones declarativas para los Pods y los ReplicaSets.

Cuando describes el estado deseado en un objeto Deployment, el controlador del Deployment se encarga de cambiar el estado actual al estado deseado de forma controlada. Puedes definir Deployments para crear nuevos ReplicaSets, o eliminar Deployments existentes y adoptar todos sus recursos con nuevos Deployments.

Casos de uso

A continuación se presentan los casos de uso típicos de los Deployments:

Crear un Deployment

El siguiente ejemplo de un Deployment crea un ReplicaSet para arrancar tres Pods con nginx:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80

En este ejemplo:

  • Se crea un Deployment denominado nginx-deployment, indicado a través del campo .metadata.name.

  • El Deployment crea tres Pods replicados, indicado a través del campo replicas.

  • El campo selector define cómo el Deployment identifica los Pods que debe gestionar. En este caso, simplemente seleccionas una etiqueta que se define en la plantilla Pod (app: nginx). Sin embargo, es posible definir reglas de selección más sofisticadas, siempre que la plantilla Pod misma satisfaga la regla.

  • El campo template contiene los siguientes sub-campos:

    • Los Pods se etiquetan como app: nginx usando el campo labels.
    • La especificación de la plantilla Pod, o el campo .template.spec, indica que los Pods ejecutan un contenedor, nginx, que utiliza la versión 1.7.9 de la imagen de nginx de Docker Hub.
    • Crea un contenedor y lo llamar nginx usando el campo name.
    • Ejecuta la imagen nginx en su versión 1.7.9.
    • Abre el puerto 80 para que el contenedor pueda enviar y recibir tráfico.

Para crear este Deployment, ejecuta el siguiente comando:

kubectl apply -f https://k8s.io/examples/controllers/nginx-deployment.yaml

A continuación, ejecuta el comando kubectl get deployments. La salida debe ser parecida a la siguiente:

NAME               READY   UP-TO-DATE   AVAILABLE   AGE 
nginx-deployment   3/3     3            3           1s  

Cuando inspeccionas los Deployments de tu clúster, se muestran los siguientes campos:

  • NAME enumera los nombre de los Deployments del clúster.
  • READY muestra cuántas réplicas de la aplicación están disponibles para sus usuarios. Sigue el patrón número de réplicas listas/deseadas.
  • UP-TO-DATE muestra el número de réplicas que se ha actualizado para alcanzar el estado deseado.
  • AVAILABLE muestra cuántas réplicas de la aplicación están disponibles para los usuarios.
  • AGE muestra la cantidad de tiempo que la aplicación lleva ejecutándose.

Nótese cómo los valores de cada campo corresponden a los valores de la especificación del Deployment:

  • El número de réplicas deseadas es 3 de acuerdo con el campo .spec.replicas.
  • El número de réplicas actuales es 0 de acuerdo con el campo .status.replicas.
  • El número de réplicas actualizadas es 0 de acuerdo con el campo .status.updatedReplicas.
  • El número de réplicas disponibles es 0 de acuerdo con el campo .status.availableReplicas.

Si deseamos obtener más información del Deployment utilice el parámetro '-o wide', ejecutando el comando 'kubectl get deployments -o wide'. La salida será parecida a la siguiente:

NAME               READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS   IMAGES         SELECTOR
nginx-deployment   3/3     3            3           10s   nginx        nginx:1.7.9   app=nginx

Ejecutando el comando anterior se muestran los siguientes campos adicionales:

  • CONTAINERS muestra los nombres de los contenedores declarados en .spec.template.spec.containers.[name].
  • IMAGES muestra los nombres de las imágenes declaradas en .spec.template.spec.containers.[image].
  • 'SELECTOR' muestra el Label selector que se declaró en matchLabels o matchExpressions.

Para ver el estado del Deployment, ejecuta el comando kubectl rollout status deployment.v1.apps/nginx-deployment. Este comando devuelve el siguiente resultado:

Waiting for rollout to finish: 2 out of 3 new replicas have been updated...
deployment "nginx-deployment" successfully rolled out

Ejecuta de nuevo el comando kubectl get deployments unos segundos más tarde:

NAME               READY   UP-TO-DATE   AVAILABLE   AGE 
nginx-deployment   3/3     3            3           18s  

Fíjate que el Deployment ha creado todas las tres réplicas, y que todas las réplicas están actualizadas (contienen la última plantilla Pod) y están disponibles (el estado del Pod tiene el valor Ready al menos para el campo .spec.minReadySeconds del Deployment).

Para ver el ReplicaSet (rs) creado por el Deployment, ejecuta el comando kubectl get rs:

NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-75675f5897   3         3         3       18s

Fíjate que el nombre del ReplicaSet siempre se formatea con el patrón [DEPLOYMENT-NAME]-[RANDOM-STRING]. La cadena aleatoria se genera de forma aleatoria y usa el pod-template-hash como semilla.

Para ver las etiquetas generadas automáticamente en cada pod, ejecuta el comando kubectl get pods --show-labels. Se devuelve la siguiente salida:

NAME                                READY     STATUS    RESTARTS   AGE       LABELS
nginx-deployment-75675f5897-7ci7o   1/1       Running   0          18s       app=nginx,pod-template-hash=75675f5897
nginx-deployment-75675f5897-kzszj   1/1       Running   0          18s       app=nginx,pod-template-hash=75675f5897
nginx-deployment-75675f5897-qqcnn   1/1       Running   0          18s       app=nginx,pod-template-hash=75675f5897

El ReplicaSet creado garantiza que hay tres Pods de nginx ejecutándose en todo momento.

Etiqueta pod-template-hash

La etiqueta pod-template-hash es añadida por el controlador del Deployment a cada ReplicaSet que el Deployment crea o adopta.

Esta etiqueta garantiza que todos los hijos ReplicaSets de un Deployment no se entremezclan. Se genera mediante una función hash aplicada al PodTemplate del ReplicaSet y usando el resultado de la función hash como el valor de la etiqueta que se añade al selector del ReplicaSet, en las etiquetas de la plantilla Pod, y en cualquier Pod existente que el ReplicaSet tenga.

Actualizar un Deployment

Asumiendo que ahora quieres actualizar los Pods nginx para que usen la imagen nginx:1.9.1 en vez de la imagen nginx:1.7.9.

kubectl --record deployment.apps/nginx-deployment set image deployment.v1.apps/nginx-deployment nginx=nginx:1.9.1
image updated

De forma alternativa, puedes editar el Deployment y cambiar el valor del campo .spec.template.spec.containers[0].image de nginx:1.7.9 a nginx:1.9.1:

kubectl edit deployment.v1.apps/nginx-deployment
deployment.apps/nginx-deployment edited

Para ver el estado del despliegue, ejecuta:

kubectl rollout status deployment.v1.apps/nginx-deployment
Waiting for rollout to finish: 2 out of 3 new replicas have been updated...
deployment "nginx-deployment" successfully rolled out

Cuando el despliegue funciona, puede que quieras obtener el Deployment:

kubectl get deployments
NAME               READY   UP-TO-DATE   AVAILABLE   AGE 
nginx-deployment   3/3     3            3           36s 

El número de réplicas actualizadas indica que el Deployment ha actualizado las réplicas según la última configuración. Las réplicas actuales indican el total de réplicas que gestiona este Deployment, y las réplicas disponibles indican el número de réplicas actuales que están disponibles.

Puedes ejecutar el comando kubectl get rs para ver que el Deployment actualizó los Pods creando un nuevo ReplicaSet y escalándolo hasta las 3 réplicas, así como escalando el viejo ReplicaSet a 0 réplicas.

kubectl get rs
NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-1564180365   3         3         3       6s
nginx-deployment-2035384211   0         0         0       36s

Si ejecutas el comando get pods deberías ver los nuevos Pods:

kubectl get pods
NAME                                READY     STATUS    RESTARTS   AGE
nginx-deployment-1564180365-khku8   1/1       Running   0          14s
nginx-deployment-1564180365-nacti   1/1       Running   0          14s
nginx-deployment-1564180365-z9gth   1/1       Running   0          14s

La próxima vez que quieras actualizar estos Pods, sólo necesitas actualizar la plantilla Pod del Deployment otra vez.

El Deployment permite garantizar que sólo un número determinado de Pods puede eliminarse mientras se están actualizando. Por defecto, garantiza que al menos el 25% menos del número deseado de Pods se está ejecutando (máx. 25% no disponible).

El Deployment también permite garantizar que sólo un número determinado de Pods puede crearse por encima del número deseado de Pods. Por defecto, garantiza que al menos el 25% más del número deseado de Pods se está ejecutando (máx. 25% de aumento).

Por ejemplo, si miras detenidamente el Deployment de arriba, verás que primero creó un Pod, luego eliminó algunos viejos Pods y creó otros nuevos. No elimina los viejos Pods hasta que un número suficiente de nuevos Pods han arrancado, y no crea nuevos Pods hasta que un número suficiente de viejos Pods se han eliminado. De esta forma, asegura que el número de Pods disponibles siempre es al menos 2, y el número de Pods totales es cómo máximo 4.

kubectl describe deployments
Name:                   nginx-deployment
Namespace:              default
CreationTimestamp:      Thu, 30 Nov 2017 10:56:25 +0000
Labels:                 app=nginx
Annotations:            deployment.kubernetes.io/revision=2
Selector:               app=nginx
Replicas:               3 desired | 3 updated | 3 total | 3 available | 0 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  25% max unavailable, 25% max surge
Pod Template:
  Labels:  app=nginx
  Containers:
   nginx:
    Image:        nginx:1.9.1
    Port:         80/TCP
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Available      True    MinimumReplicasAvailable
  Progressing    True    NewReplicaSetAvailable
OldReplicaSets:  <none>
NewReplicaSet:   nginx-deployment-1564180365 (3/3 replicas created)
Events:
  Type    Reason             Age   From                   Message
  ----    ------             ----  ----                   -------
  Normal  ScalingReplicaSet  2m    deployment-controller  Scaled up replica set nginx-deployment-2035384211 to 3
  Normal  ScalingReplicaSet  24s   deployment-controller  Scaled up replica set nginx-deployment-1564180365 to 1
  Normal  ScalingReplicaSet  22s   deployment-controller  Scaled down replica set nginx-deployment-2035384211 to 2
  Normal  ScalingReplicaSet  22s   deployment-controller  Scaled up replica set nginx-deployment-1564180365 to 2
  Normal  ScalingReplicaSet  19s   deployment-controller  Scaled down replica set nginx-deployment-2035384211 to 1
  Normal  ScalingReplicaSet  19s   deployment-controller  Scaled up replica set nginx-deployment-1564180365 to 3
  Normal  ScalingReplicaSet  14s   deployment-controller  Scaled down replica set nginx-deployment-2035384211 to 0

Aquí puedes ver que cuando creaste por primera vez el Deployment, este creó un ReplicaSet (nginx-deployment-2035384211) y lo escaló a 3 réplicas directamente. Cuando actualizaste el Deployment, creó un nuevo ReplicaSet (nginx-deployment-1564180365) y lo escaló a 1 y entonces escaló el viejo ReplicaSet a 2, de forma que al menos hubiera 2 Pods disponibles y como mucho 4 Pods en total en todo momento. Entonces, continuó escalando el nuevo y el viejo ReplicaSet con la misma estrategia de actualización continua. Finalmente, el nuevo ReplicaSet acaba con 3 réplicas disponibles, y el viejo ReplicaSet se escala a 0.

Sobrescritura (o sea, múltiples actualizaciones a la vez)

Cada vez que el controlador del Deployment observa un nuevo objeto de despliegue, se crea un ReplicaSet para arrancar los Pods deseados si es que no existe otro ReplicaSet haciéndolo. Los ReplicaSet existentes que controlan los Pods cuyas etiquetas coinciden con el valor del campo .spec.selector, pero cuya plantilla no coincide con el valor del campo .spec.template se reducen. Al final, el nuevo ReplicaSet se escala hasta el valor del campo .spec.replicas y todos los viejos ReplicaSets se escalan a 0.

Si actualizas un Deployment mientras otro despliegue está en curso, el Deployment creará un nuevo ReplicaSet como consecuencia de la actualización y comenzará a escalarlo, y sobrescribirá al ReplicaSet que estaba escalando anteriormente -- lo añadirá a su lista de viejos ReplicaSets y comenzará a reducirlos.

Por ejemplo, supongamos que creamos un Deployment para crear 5 réplicas de nginx:1.7.9, pero entonces actualizamos el Deployment para crear 5 réplicas de nginx:1.9.1 cuando sólo se ha creado 3 réplicas de nginx:1.7.9. En este caso, el Deployment comenzará automáticamente a matar los 3 Pods de nginx:1.7.9 que había creado, y empezará a crear los Pods de nginx:1.9.1. Es decir, no esperará a que se creen las 5 réplicas de nginx:1.7.9 antes de aplicar la nueva configuración.

Actualizaciones del selector de etiquetas

No se recomienda hacer cambios al selector del etiquetas y, por ello, se aconseja encarecidamente planificar el valor de dichos selectores por adelantado. En cualquier caso, si necesitas cambiar un selector de etiquetas, hazlo con mucho cuidado y asegúrate que entiendes todas sus implicaciones.

  • Las adiciones posteriores al selector obligan también a actualizar las etiquetas de la plantilla Pod en la especificación del Deployment con los nuevos valores, ya que de lo contrario se devolvería un error. Este cambio no es de superposición, es decir, que el nuevo selector no selecciona los ReplicaSets y Pods creados con el viejo selector, lo que provoca que todos los viejos ReplicaSets se marquen como huérfanos y la creación de un nuevo ReplicaSet.
  • Las actualizaciones de selector -- esto es, cambiar el valor actual en una clave de selector -- provocan el mismo comportamiento que las adiciones.
  • Las eliminaciones de selector -- esto es, eliminar una clave actual del selector del Deployment -- no necesitan de cambios en las etiquetas de la plantilla Pod. No se marca ningún ReplicaSet existente como huérfano, y no se crea ningún ReplicaSet nuevo, pero debe tenerse en cuenta que la etiqueta eliminada todavía existe en los Pods y ReplicaSets que se están ejecutando.

Revertir un Deployment

En ocasiones necesitas revertir un Deployment; por ejemplo, cuando el Deployment no es estable, como cuando no para de reiniciarse. Por defecto, toda la historia de despliegue del Deployment se mantiene en el sistema de forma que puedes revertir en cualquier momento (se puede modificar este comportamiento cambiando el límite de la historia de revisiones de modificaciones).

Vamos a suponer que hemos cometido un error al actualizar el Deployment, poniendo como nombre de imagen nginx:1.91 en vez de nginx:1.9.1:

kubectl set image deployment.v1.apps/nginx-deployment nginx=nginx:1.91 --record=true
deployment.apps/nginx-deployment image updated

El despliegue se atasca y no progresa.

kubectl rollout status deployment.v1.apps/nginx-deployment
Waiting for rollout to finish: 1 out of 3 new replicas have been updated...

Presiona Ctrl-C para detener la monitorización del despliegue de arriba. Para obtener más información sobre despliegues atascados, lee más aquí.

Verás que el número de réplicas viejas (nginx-deployment-1564180365 y nginx-deployment-2035384211) es 2, y el número de nuevas réplicas (nginx-deployment-3066724191) es 1.

kubectl get rs
NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-1564180365   3         3         3       25s
nginx-deployment-2035384211   0         0         0       36s
nginx-deployment-3066724191   1         1         0       6s

Echando un vistazo a los Pods creados, verás que uno de los Pods creados por el nuevo ReplicaSet está atascado en un bucle intentando bajar la imagen:

kubectl get pods
NAME                                READY     STATUS             RESTARTS   AGE
nginx-deployment-1564180365-70iae   1/1       Running            0          25s
nginx-deployment-1564180365-jbqqo   1/1       Running            0          25s
nginx-deployment-1564180365-hysrc   1/1       Running            0          25s
nginx-deployment-3066724191-08mng   0/1       ImagePullBackOff   0          6s
kubectl describe deployment
Name:           nginx-deployment
Namespace:      default
CreationTimestamp:  Tue, 15 Mar 2016 14:48:04 -0700
Labels:         app=nginx
Selector:       app=nginx
Replicas:       3 desired | 1 updated | 4 total | 3 available | 1 unavailable
StrategyType:       RollingUpdate
MinReadySeconds:    0
RollingUpdateStrategy:  25% max unavailable, 25% max surge
Pod Template:
  Labels:  app=nginx
  Containers:
   nginx:
    Image:        nginx:1.91
    Port:         80/TCP
    Host Port:    0/TCP
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Available      True    MinimumReplicasAvailable
  Progressing    True    ReplicaSetUpdated
OldReplicaSets:     nginx-deployment-1564180365 (3/3 replicas created)
NewReplicaSet:      nginx-deployment-3066724191 (1/1 replicas created)
Events:
  FirstSeen LastSeen    Count   From                    SubobjectPath   Type        Reason              Message
  --------- --------    -----   ----                    -------------   --------    ------              -------
  1m        1m          1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled up replica set nginx-deployment-2035384211 to 3
  22s       22s         1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled up replica set nginx-deployment-1564180365 to 1
  22s       22s         1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled down replica set nginx-deployment-2035384211 to 2
  22s       22s         1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled up replica set nginx-deployment-1564180365 to 2
  21s       21s         1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled down replica set nginx-deployment-2035384211 to 1
  21s       21s         1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled up replica set nginx-deployment-1564180365 to 3
  13s       13s         1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled down replica set nginx-deployment-2035384211 to 0
  13s       13s         1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled up replica set nginx-deployment-3066724191 to 1

Para arreglar este problema, necesitas volver a una revisión previa del Deployment que sea estable.

Comprobar la Historia de Despliegues de un Deployment

Primero, comprobemos las revisiones de este despliegue:

kubectl rollout history deployment.v1.apps/nginx-deployment
deployments "nginx-deployment"
REVISION    CHANGE-CAUSE
1           kubectl apply --filename=https://k8s.io/examples/controllers/nginx-deployment.yaml --record=true
2           kubectl set image deployment.v1.apps/nginx-deployment nginx=nginx:1.9.1 --record=true
3           kubectl set image deployment.v1.apps/nginx-deployment nginx=nginx:1.91 --record=true

En el momento de la creación, el mensaje en CHANGE-CAUSE se copia de la anotación kubernetes.io/change-cause del Deployment a sus revisiones. Podrías indicar el mensaje CHANGE-CAUSE:

  • Anotando el Deployment con el comando kubectl annotate deployment.v1.apps/nginx-deployment kubernetes.io/change-cause="image updated to 1.9.1"
  • Añadiendo el parámetro --record para registrar el comando kubectl que está haciendo cambios en el recurso.
  • Manualmente editando el manifiesto del recursos.

Para ver más detalles de cada revisión, ejecuta:

kubectl rollout history deployment.v1.apps/nginx-deployment --revision=2
deployments "nginx-deployment" revision 2
  Labels:       app=nginx
          pod-template-hash=1159050644
  Annotations:  kubernetes.io/change-cause=kubectl set image deployment.v1.apps/nginx-deployment nginx=nginx:1.9.1 --record=true
  Containers:
   nginx:
    Image:      nginx:1.9.1
    Port:       80/TCP
     QoS Tier:
        cpu:      BestEffort
        memory:   BestEffort
    Environment Variables:      <none>
  No volumes.

Retroceder a una Revisión Previa

Ahora has decidido que quieres deshacer el despliegue actual y retrocederlo a la revisión previa:

kubectl rollout undo deployment.v1.apps/nginx-deployment
deployment.apps/nginx-deployment

Alternativamente, puedes retroceder a una revisión específica con el parámetro --to-revision:

kubectl rollout undo deployment.v1.apps/nginx-deployment --to-revision=2
deployment.apps/nginx-deployment

Para más detalles acerca de los comandos relacionados con las revisiones de un Deployment, echa un vistazo a kubectl rollout.

El Deployment se ha revertido ahora a una revisión previa estable. Como se puede comprobar, el controlador del Deployment genera un evento DeploymentRollback al retroceder a la revisión 2.

kubectl get deployment nginx-deployment
NAME               READY   UP-TO-DATE   AVAILABLE   AGE 
nginx-deployment   3/3     3            3           30m 
kubectl describe deployment nginx-deployment
Name:                   nginx-deployment
Namespace:              default
CreationTimestamp:      Sun, 02 Sep 2018 18:17:55 -0500
Labels:                 app=nginx
Annotations:            deployment.kubernetes.io/revision=4
                        kubernetes.io/change-cause=kubectl set image deployment.v1.apps/nginx-deployment nginx=nginx:1.9.1 --record=true
Selector:               app=nginx
Replicas:               3 desired | 3 updated | 3 total | 3 available | 0 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  25% max unavailable, 25% max surge
Pod Template:
  Labels:  app=nginx
  Containers:
   nginx:
    Image:        nginx:1.9.1
    Port:         80/TCP
    Host Port:    0/TCP
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Available      True    MinimumReplicasAvailable
  Progressing    True    NewReplicaSetAvailable
OldReplicaSets:  <none>
NewReplicaSet:   nginx-deployment-c4747d96c (3/3 replicas created)
Events:
  Type    Reason              Age   From                   Message
  ----    ------              ----  ----                   -------
  Normal  ScalingReplicaSet   12m   deployment-controller  Scaled up replica set nginx-deployment-75675f5897 to 3
  Normal  ScalingReplicaSet   11m   deployment-controller  Scaled up replica set nginx-deployment-c4747d96c to 1
  Normal  ScalingReplicaSet   11m   deployment-controller  Scaled down replica set nginx-deployment-75675f5897 to 2
  Normal  ScalingReplicaSet   11m   deployment-controller  Scaled up replica set nginx-deployment-c4747d96c to 2
  Normal  ScalingReplicaSet   11m   deployment-controller  Scaled down replica set nginx-deployment-75675f5897 to 1
  Normal  ScalingReplicaSet   11m   deployment-controller  Scaled up replica set nginx-deployment-c4747d96c to 3
  Normal  ScalingReplicaSet   11m   deployment-controller  Scaled down replica set nginx-deployment-75675f5897 to 0
  Normal  ScalingReplicaSet   11m   deployment-controller  Scaled up replica set nginx-deployment-595696685f to 1
  Normal  DeploymentRollback  15s   deployment-controller  Rolled back deployment "nginx-deployment" to revision 2
  Normal  ScalingReplicaSet   15s   deployment-controller  Scaled down replica set nginx-deployment-595696685f to 0

Escalar un Deployment

Puedes escalar un Deployment usando el siguiente comando:

kubectl scale deployment.v1.apps/nginx-deployment --replicas=10
deployment.apps/nginx-deployment scaled

Asumiendo que se ha habilitado el escalado horizontal de pod en tu clúster, puedes configurar un auto-escalado para tu Deployment y elegir el mínimo y máximo número de Pods que quieres ejecutar en base al uso de CPU de tus Pods actuales.

kubectl autoscale deployment.v1.apps/nginx-deployment --min=10 --max=15 --cpu-percent=80
deployment.apps/nginx-deployment scaled

Escalado proporcional

La actualización continua de los Deployments permite la ejecución de múltiples versiones de una aplicación al mismo tiempo. Cuando tú o un auto-escalado escala un Deployment con actualización continua que está en medio de otro despliegue (bien en curso o pausado), entonces el controlador del Deployment balanceará las réplicas adicionales de los ReplicaSets activos (ReplicaSets con Pods) para así poder mitigar el riesgo. Esto se conoce como escalado proporcional.

Por ejemplo, imagina que estás ejecutando un Deployment con 10 réplicas, donde maxSurge=3, y maxUnavailable=2.

kubectl get deploy
NAME               READY   UP-TO-DATE   AVAILABLE   AGE 
nginx-deployment   10/10   10           10          50s 

Si actualizas a una nueva imagen que no puede descargarse desde el clúster:

kubectl set image deployment.v1.apps/nginx-deployment nginx=nginx:sometag
deployment.apps/nginx-deployment image updated

La actualización de la imagen arranca un nuevo despliegue con el ReplicaSet nginx-deployment-1989198191, pero se bloquea debido al requisito maxUnavailable indicado arriba:

kubectl get rs
NAME                          DESIRED   CURRENT   READY     AGE
nginx-deployment-1989198191   5         5         0         9s
nginx-deployment-618515232    8         8         8         1m

Y entonces se origina una nueva petición de escalado para el Deployment. El auto-escalado incrementa las réplicas del Deployment a 15. El controlador del Deployment necesita ahora decidir dónde añadir esas nuevas 5 réplicas. Si no estuvieras usando el escalado proporcional, las 5 se añadirían al nuevo ReplicaSet. Pero con el escalado proporcional, las réplicas adicionales se distribuyen entre todos los ReplicaSets. Las partes más grandes van a los ReplicaSets con el mayor número de réplicas y las partes más pequeñas van a los ReplicaSets con menos réplicas. Cualquier resto sobrante se añade al ReplicaSet con mayor número de réplicas. Aquellos ReplicaSets con 0 réplicas no se escalan.

En nuestro ejemplo anterior, se añadirán 3 réplicas al viejo ReplicaSet y 2 réplicas al nuevo ReplicaSet. EL proceso de despliegue debería al final mover todas las réplicas al nuevo ReplicaSet, siempre que las nuevas réplicas arranquen positivamente.

kubectl get deploy
NAME               READY   UP-TO-DATE   AVAILABLE   AGE 
nginx-deployment   18/15   7            8           7m 
kubectl get rs
NAME                          DESIRED   CURRENT   READY     AGE
nginx-deployment-1989198191   7         7         0         7m
nginx-deployment-618515232    11        11        11        7m

Pausar y Reanudar un Deployment

Puedes pausar un Deployment antes de arrancar una o más modificaciones y luego reanudarlo. Esto te permite aplicar múltiples arreglos entre la pausa y la reanudación sin necesidad de arrancar despliegues innecesarios.

Por ejemplo, con un Deployment que acaba de crearse:

kubectl get deploy
NAME               READY   UP-TO-DATE   AVAILABLE   AGE 
nginx-deployment   3/3     3            3           1m 
kubectl get rs
NAME               DESIRED   CURRENT   READY     AGE
nginx-2142116321   3         3         3         1m

Lo pausamos ejecutando el siguiente comando:

kubectl rollout pause deployment.v1.apps/nginx-deployment
deployment.apps/nginx-deployment paused

Y luego actualizamos la imagen del Deployment:

kubectl set image deployment.v1.apps/nginx-deployment nginx=nginx:1.9.1
deployment.apps/nginx-deployment image updated

Nótese que no se arranca ningún despliegue nuevo:

kubectl rollout history deployment.v1.apps/nginx-deployment
deployments "nginx"
REVISION  CHANGE-CAUSE
1   <none>
kubectl get rs
NAME               DESIRED   CURRENT   READY     AGE
nginx-2142116321   3         3         3         2m

Puedes realizar tantas modificaciones como quieras, por ejemplo, para actualizar los recursos a utilizar:

kubectl set resources deployment.v1.apps/nginx-deployment -c=nginx --limits=cpu=200m,memory=512Mi
deployment.apps/nginx-deployment resource requirements updated

El estado inicial del Deployment anterior a la pausa continuará su función, pero las nuevas modificaciones del Deployment no tendrán efecto ya que el Deployment está pausado.

Al final, reanuda el Deployment y observa cómo se genera un nuevo ReplicaSet con todos los cambios:

kubectl rollout resume deployment.v1.apps/nginx-deployment
deployment.apps/nginx-deployment resumed
kubectl get rs -w
NAME               DESIRED   CURRENT   READY     AGE
nginx-2142116321   2         2         2         2m
nginx-3926361531   2         2         0         6s
nginx-3926361531   2         2         1         18s
nginx-2142116321   1         2         2         2m
nginx-2142116321   1         2         2         2m
nginx-3926361531   3         2         1         18s
nginx-3926361531   3         2         1         18s
nginx-2142116321   1         1         1         2m
nginx-3926361531   3         3         1         18s
nginx-3926361531   3         3         2         19s
nginx-2142116321   0         1         1         2m
nginx-2142116321   0         1         1         2m
nginx-2142116321   0         0         0         2m
nginx-3926361531   3         3         3         20s
kubectl get rs
NAME               DESIRED   CURRENT   READY     AGE
nginx-2142116321   0         0         0         2m
nginx-3926361531   3         3         3         28s

Estado del Deployment

Un Deployment pasa por varios estados a lo largo de su ciclo de vida. Así, puede estar progresando mientras se despliega un nuevo ReplicaSet, puede estar completo, o puede quedar en estado fallido.

Progresar un Deployment

Kubernetes marca un Deployment como progresando cuando se realiza cualquiera de las siguientes tareas:

  • El Deployment crea un nuevo ReplicaSet.
  • El Deployment está escalando su ReplicaSet más nuevo.
  • El Deployment está reduciendo su(s) ReplicaSet(s) más antiguo(s).
  • Hay nuevos Pods disponibles y listos (listo por lo menos MinReadySeconds).

Puedes monitorizar el progreso de un Deployment usando el comando kubectl rollout status.

Completar un Deployment

Kubernetes marca un Deployment como completado cuando presenta las siguientes características:

  • Todas las réplicas asociadas con el Deployment han sido actualizadas a la última versión indicada, lo cual quiere decir que todas las actualizaciones se han completado.
  • Todas las réplicas asociadas con el Deployment están disponibles.
  • No están ejecutándose viejas réplicas del Deployment.

Puedes comprobar si un Deployment se ha completado usando el comando kubectl rollout status. Si el despliegue se ha completado de forma satisfactoria, el comando kubectl rollout status devuelve un código 0 de salida.

kubectl rollout status deployment.v1.apps/nginx-deployment
Waiting for rollout to finish: 2 of 3 updated replicas are available...
deployment "nginx-deployment" successfully rolled out
$ echo $?
0

Deployment fallido

Tu Deployment puede quedarse bloqueado intentando desplegar su nuevo ReplicaSet sin nunca completarse. Esto puede ocurrir debido a algunos de los factores siguientes:

  • Cuota insuficiente
  • Fallos en la prueba de estar listo
  • Errores en la descarga de imágenes
  • Permisos insuficientes
  • Rangos de límites de recursos
  • Mala configuración del motor de ejecución de la aplicación

Una forma de detectar este tipo de situación es especificar un parámetro de vencimiento en la especificación de tu Deployment: (.spec.progressDeadlineSeconds). .spec.progressDeadlineSeconds denota el número de segundos que el controlador del Deployment debe esperar antes de indicar (en el estado del Deployment) que el Deployment no avanza.

El siguiente comando kubectl configura el campo progressDeadlineSeconds para forzar al controlador a informar de la falta de avance de un Deployment después de 10 minutos:

kubectl patch deployment.v1.apps/nginx-deployment -p '{"spec":{"progressDeadlineSeconds":600}}'
deployment.apps/nginx-deployment patched

Una vez que se ha excedido el vencimiento, el controlador del Deployment añade una DeploymentCondition con los siguientes atributos al campo .status.conditions del Deployment:

  • Type=Progressing
  • Status=False
  • Reason=ProgressDeadlineExceeded

Ver las convenciones de la API de Kubernetes para más información acerca de las condiciones de estado.

Puede que notes errores transitorios en tus Deployments, bien debido a un tiempo de vencimiento muy pequeño que hayas configurado o bien a cualquier otro tipo de error que puede considerarse como transitorio. Por ejemplo, supongamos que no tienes suficiente cuota. Si describes el Deployment, te darás cuenta de la sección siguiente:

kubectl describe deployment nginx-deployment
<...>
Conditions:
  Type            Status  Reason
  ----            ------  ------
  Available       True    MinimumReplicasAvailable
  Progressing     True    ReplicaSetUpdated
  ReplicaFailure  True    FailedCreate
<...>

Si ejecutas el comando kubectl get deployment nginx-deployment -o yaml, el estado del Deployment puede parecerse a:

status:
  availableReplicas: 2
  conditions:
  - lastTransitionTime: 2016-10-04T12:25:39Z
    lastUpdateTime: 2016-10-04T12:25:39Z
    message: Replica set "nginx-deployment-4262182780" is progressing.
    reason: ReplicaSetUpdated
    status: "True"
    type: Progressing
  - lastTransitionTime: 2016-10-04T12:25:42Z
    lastUpdateTime: 2016-10-04T12:25:42Z
    message: Deployment has minimum availability.
    reason: MinimumReplicasAvailable
    status: "True"
    type: Available
  - lastTransitionTime: 2016-10-04T12:25:39Z
    lastUpdateTime: 2016-10-04T12:25:39Z
    message: 'Error creating: pods "nginx-deployment-4262182780-" is forbidden: exceeded quota:
      object-counts, requested: pods=1, used: pods=3, limited: pods=2'
    reason: FailedCreate
    status: "True"
    type: ReplicaFailure
  observedGeneration: 3
  replicas: 2
  unavailableReplicas: 2

Al final, una vez que se supera el vencimiento del progreso del Deployment, Kubernetes actualiza el estado y la razón de el estado de progreso:

Conditions:
  Type            Status  Reason
  ----            ------  ------
  Available       True    MinimumReplicasAvailable
  Progressing     False   ProgressDeadlineExceeded
  ReplicaFailure  True    FailedCreate

Puedes solucionar un problema de cuota insuficiente simplemente reduciendo el número de réplicas de tu Deployment, reduciendo otros controladores que puedas estar ejecutando, o incrementando la cuota en tu espacio de nombres. Si una vez satisfechas las condiciones de tu cuota, el controlador del Deployment completa el despliegue, entonces verás que el estado del Deployment se actualiza al estado satisfactorio (Status=True y Reason=NewReplicaSetAvailable).

Conditions:
  Type          Status  Reason
  ----          ------  ------
  Available     True    MinimumReplicasAvailable
  Progressing   True    NewReplicaSetAvailable

Type=Available con Status=True significa que tu Deployment tiene disponibilidad mínima. La disponibilidad mínima se prescribe mediante los parámetros indicados en la estrategia de despligue. Type=Progressing con Status=True significa que tu Deployment está bien en medio de un despliegue y está progresando o bien que se ha completado de forma satisfactoria y el número mínimo requerido de nuevas réplicas ya está disponible (ver la Razón del estado para cada caso particular - en nuestro caso Reason=NewReplicaSetAvailable significa que el Deployment se ha completado).

Puedes comprobar si un Deployment ha fallado en su progreso usando el comando kubectl rollout status. kubectl rollout status devuelve un código de salida distinto de 0 si el Deployment ha excedido su tiempo de vencimiento.

kubectl rollout status deployment.v1.apps/nginx-deployment
Waiting for rollout to finish: 2 out of 3 new replicas have been updated...
error: deployment "nginx" exceeded its progress deadline
$ echo $?
1

Actuar ante un despliegue fallido

Todas las acciones que aplican a un Deployment completado también aplican a un Deployment fallido. Puedes escalarlo/reducirlo, retrocederlo a una revisión previa, o incluso pausarlo si necesitas realizar múltiples cambios a la plantilla Pod del Deployment.

Regla de Limpieza

Puedes configurar el campo .spec.revisionHistoryLimit de un Deployment para especificar cuántos ReplicaSets viejos quieres conservar para este Deployment. El resto será eliminado en segundo plano. Por defecto, es 10.

Casos de Uso

Despligue Canary

Si quieres desplegar nuevas versiones a un sub-conjunto de usuarios o servidores usando el Deployment, puedes hacerlo creando múltiples Deployments, uno para cada versión nueva, siguiendo el patrón canary descrito en gestionar recursos.

Escribir una especificación de Deployment

Al igual que con el resto de configuraciones de Kubernetes, un Deployment requiere los campos apiVersion, kind, y metadata. Para información general acerca de cómo trabajar con ficheros de configuración, ver los documentos acerca de desplegar aplicaciones, configurar contenedores, y usar kubectl para gestionar recursos.

Un Deployment también necesita una sección .spec.

Plantilla Pod

Tanto .spec.template como .spec.selector sin campos obligatorios dentro de .spec.

El campo .spec.template es una plantilla Pod. Tiene exactamente el mismo esquema que un Pod, excepto por el hecho de que está anidado y no tiene apiVersion ni kind.

Junto con los campos obligatorios de un Pod, una plantilla Pod de un Deployment debe indicar las etiquetas y las reglas de reinicio apropiadas. Para el caso de las etiquetas, asegúrate que no se entremezclan con otros controladores. Ver selector).

Únicamente se permite una .spec.template.spec.restartPolicy igual a Always, que es el valor por defecto si no se indica.

Réplicas

.spec.replicas es un campo opcional que indica el número de Pods deseados. Su valor por defecto es 1.

Selector

.spec.selector es un campo opcional que indica un selector de etiquetas para los Pods objetivo del deployment.

.spec.selector debe coincidir con .spec.template.metadata.labels, o será descartado por la API.

A partir de la versión apps/v1 de la API, .spec.selector y .metadata.labels no toman como valor por defecto el valor de .spec.template.metadata.labels si no se indica. Por ello, debe especificarse de forma explícita. Además hay que mencionar que .spec.selector es inmutable tras la creación del Deployment en apps/v1.

Un Deployment puede finalizar aquellos Pods cuyas etiquetas coincidan con el selector si su plantilla es diferente de .spec.template o si el número total de dichos Pods excede .spec.replicas. Arranca nuevos Pods con .spec.template si el número de Pods es menor que el número deseado.

Si tienes múltiples controladores que entremezclan sus selectores, dichos controladores competirán entre ellos y no se comportarán de forma correcta.

Estrategia

.spec.strategy especifica la estrategia usada para remplazar los Pods viejos con los nuevos. .spec.strategy.type puede tener el valor "Recreate" o "RollingUpdate". "RollingUpdate" el valor predeterminado.

Despliegue mediante recreación

Todos los Pods actuales se eliminan antes de que los nuevos se creen cuando .spec.strategy.type==Recreate.

Despliegue mediante actualización continua

El Deployment actualiza los Pods en modo de actualización continua cuando .spec.strategy.type==RollingUpdate. Puedes configurar los valores de maxUnavailable y maxSurge para controlar el proceso de actualización continua.

Número máximo de pods no disponibles

.spec.strategy.rollingUpdate.maxUnavailable es un campo opcional que indica el número máximo de Pods que pueden no estar disponibles durante el proceso de actualización. El valor puede ser un número absoluto (por ejemplo, 5) o un porcentaje de los Pods deseados (por ejemplo, 10%). El número absoluto se calcula a partir del porcentaje con redondeo a la baja. El valor no puede ser 0 si .spec.strategy.rollingUpdate.maxSurge es 0. El valor predeterminado es 25%.

Por ejemplo, cuando este valor es 30%, el ReplicaSet viejo puede escalarse al 70% de los Pods deseados de forma inmediata tras comenzar el proceso de actualización. Una vez que los Pods están listos, el ReplicaSet viejo puede reducirse aún mas, seguido de un escalado del nuevo ReplicaSet, asegurándose que el número total de Pods disponibles en todo momento durante la actualización es de al menos el 70% de los Pods deseados.

Número máximo de pods por encima del número deseado

.spec.strategy.rollingUpdate.maxSurge es un campo opcional que indica el número máximo de Pods que puede crearse por encima del número deseado de Pods. El valor puede ser un número absoluto (por ejemplo, 5) o un porcentaje de los Pods deseados (por ejemplo, 10%). El valor no puede ser 0 si MaxUnavailable es 0. El número absoluto se calcula a partir del porcentaje con redondeo al alza. El valor predeterminado es 25%.

Por ejemplo, cuando este valor es 30%, el nuevo ReplicaSet puede escalarse inmediatamente cuando comienza la actualización continua, de forma que el número total de Pods viejos y nuevos no excede el 130% de los Pods deseados. Una vez que los viejos Pods se han eliminado, el nuevo ReplicaSet puede seguir escalándose, asegurándose que el número total de Pods ejecutándose en todo momento durante la actualización es como mucho del 130% de los Pods deseados.

Segundos para vencimiento del progreso

.spec.progressDeadlineSeconds es un campo opcional que indica el número de segundos que quieres esperar a que tu Deployment avance antes de que el sistema reporte que dicho Deployment ha fallado en su avance - expresado como un estado con Type=Progressing, Status=False. y Reason=ProgressDeadlineExceeded en el recurso. El controlador del Deployment seguirá intentando el despliegue. En el futuro, una vez que se implemente el retroceso automático, el controlador del Deployment retrocederá el despliegue en cuanto detecte ese estado.

Si se especifica, este campo debe ser mayor que .spec.minReadySeconds.

Tiempo mínimo para considerar el Pod disponible

.spec.minReadySeconds es un campo opcional que indica el número mínimo de segundos en que un Pod recién creado debería estar listo sin que falle ninguno de sus contenedores, para que se considere disponible. Por defecto su valor es 0 (el Pod se considera disponible en el momento que está listo). Para aprender más acerca de cuándo un Pod se considera que está listo, ver las pruebas de contenedor.

Vuelta atrás

El campo .spec.rollbackTo se ha quitado de las versiones extensions/v1beta1 y apps/v1beta1 de la API, y ya no se permite en las versiones de la API a partir de apps/v1beta2. En su caso, se debería usar kubectl rollout undo, tal y como se explicó en Retroceder a una Revisión Previa.

Límite del histórico de revisiones

La historia de revisiones de un Deployment se almacena en los ReplicaSets que este controla.

.spec.revisionHistoryLimit es un campo opcional que indica el número de ReplicaSets viejos a retener para permitir los retrocesos. Estos ReplicaSets viejos consumen recursos en etcd y rebosan la salida de kubectl get rs. La configuración de cada revisión de Deployment se almacena en sus ReplicaSets; por lo tanto, una vez que se elimina el ReplicaSet viejo, se pierde la posibilidad de retroceder a dicha revisión del Deployment. Por defecto, se retienen hasta 10 ReplicaSets viejos; pero su valor ideal depende de la frecuencia y la estabilidad de los nuevos Deployments.

De forma más específica, si ponemos este campo a cero quiere decir que todos los ReplicaSets viejos con 0 réplicas se limpiarán. En este caso, el nuevo despliegue del Deployment no se puede deshacer, ya que su historia de revisiones se habrá limpiado.

Pausa

.spec.paused es un campo booleano opcional para pausar y reanudar un Deployment. La única diferencia entre un Deployment pausado y otro que no lo está es que cualquier cambio al PodTemplateSpec del Deployment pausado no generará nuevos despliegues mientras esté pausado. Un Deployment se pausa de forma predeterminada cuando se crea.

Alternativa a los Deployments

kubectl rolling update

kubectl rolling update actualiza los Pods y los ReplicationControllers de forma similar. Pero se recomienda el uso de Deployments porque se declaran del lado del servidor, y proporcionan características adicionales como la posibilidad de retroceder a revisiones anteriores incluso después de haber terminado una actualización continua.

2.4 - StatefulSets

Un StatefulSet es el objeto de la API workload que se usa para gestionar aplicaciones con estado.

Gestiona el despliegue y escalado de un conjunto de Pods, y garantiza el orden y unicidad de dichos Pods.

Al igual que un Deployment, un StatefulSet gestiona Pods que se basan en una especificación idéntica de contenedor. A diferencia de un Deployment, un StatefulSet mantiene una identidad asociada a sus Pods. Estos pods se crean a partir de la misma especificación, pero no pueden intercambiarse; cada uno tiene su propio identificador persistente que mantiene a lo largo de cualquier re-programación.

Un StatefulSet opera bajo el mismo patrón que cualquier otro controlador. Se define el estado deseado en un objeto StatefulSet, y el controlador del StatefulSet efectúa las actualizaciones que sean necesarias para alcanzarlo a partir del estado actual.

Usar StatefulSets

Los StatefulSets son valiosos para aquellas aplicaciones que necesitan uno o más de los siguientes:

  • Identificadores de red estables, únicos.
  • Almacenamiento estable, persistente.
  • Despliegue y escalado ordenado, controlado.
  • Actualizaciones en línea ordenadas, automatizadas.

De los de arriba, estable es sinónimo de persistencia entre (re)programaciones de Pods. Si una aplicación no necesita ningún identificador estable o despliegue, eliminación, o escalado ordenado, deberías desplegar tu aplicación con un controlador que proporcione un conjunto de réplicas sin estado, como un Deployment o un ReplicaSet, ya que están mejor preparados para tus necesidades sin estado.

Limitaciones

  • El almacenamiento de un determinado Pod debe provisionarse por un Provisionador de PersistentVolume basado en la storage class requerida, o pre-provisionarse por un administrador.
  • Eliminar y/o reducir un StatefulSet no eliminará los volúmenes asociados con el StatefulSet. Este comportamiento es intencional y sirve para garantizar la seguridad de los datos, que da más valor que la purga automática de los recursos relacionados del StatefulSet.
  • Los StatefulSets actualmente necesitan un Servicio Headless como responsable de la identidad de red de los Pods. Es tu responsabilidad crear este Service.
  • Los StatefulSets no proporcionan ninguna garantía de la terminación de los pods cuando se elimina un StatefulSet. Para conseguir un término de los pods ordenado y controlado en el StatefulSet, es posible reducir el StatefulSet a 0 réplicas justo antes de eliminarlo.
  • Cuando se usan las Actualizaciones en línea con la Regla de Gestión de Pod (OrderedReady) por defecto, es posible entrar en un estado inconsistente que requiere de una intervención manual para su reparación.

Componentes

El ejemplo de abajo demuestra los componentes de un StatefulSet:

  • Un servicio Headless, llamado nginx, se usa para controlar el dominio de red.
  • Un StatefulSet, llamado web, que tiene una especificación que indica que se lanzarán 3 réplicas del contenedor nginx en Pods únicos.
  • Un volumeClaimTemplate que proporciona almacenamiento estable por medio de PersistentVolumes provisionados por un provisionador de tipo PersistentVolume.
apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  selector:
    matchLabels:
      app: nginx # tiene que coincidir con .spec.template.metadata.labels
  serviceName: "nginx"
  replicas: 3 # por defecto es 1
  template:
    metadata:
      labels:
        app: nginx # tiene que coincidir con .spec.selector.matchLabels
    spec:
      terminationGracePeriodSeconds: 10
      containers:
      - name: nginx
        image: registry.k8s.io/nginx-slim:0.8
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "my-storage-class"
      resources:
        requests:
          storage: 1Gi

Selector de Pod

Debes poner el valor del campo .spec.selector de un StatefulSet para que coincida con las etiquetas de su campo .spec.template.metadata.labels. Antes de Kubernetes 1.8, el campo .spec.selector se predeterminaba cuando se omitía. A partir de la versión 1.8, si no se especifica un selector de coincidencia de Pods, se produce un error de validación durante la creación del StatefulSet.

Identidad de Pod

Los Pods de un StatefulSet tienen una identidad única que está formada por un ordinal, una identidad estable de red, y almacenamiento estable. La identidad se asocia al Pod, independientemente del nodo en que haya sido (re)programado.

Índice Ordinal

Para un StatefulSet con N réplicas, a cada Pod del StatefulSet se le asignará un número entero ordinal, desde 0 hasta N-1, y que es único para el conjunto.

ID estable de Red

El nombre de anfitrión (hostname) de cada Pod de un StatefulSet se deriva del nombre del StatefulSet y del número ordinal del Pod. El patrón para construir dicho hostname es $(statefulset name)-$(ordinal). Así, el ejemplo de arriba creará tres Pods denominados web-0,web-1,web-2. Un StatefulSet puede usar un Servicio Headless para controlar el nombre de dominio de sus Pods. El nombre de dominio gestionado por este Service tiene la forma: $(service name).$(namespace).svc.cluster.local, donde "cluster.local" es el nombre de dominio del clúster. Conforme se crea cada Pod, se le asigna un nombre DNS correspondiente de subdominio, que tiene la forma: $(podname).$(governing service domain), donde el servicio en funciones se define por el campo serviceName del StatefulSet.

Como se indicó en la sección limitaciones, la creación del Servicio Headless encargado de la identidad de red de los pods es enteramente tu responsabilidad.

Aquí se muestran algunos ejemplos de elecciones de nombres de Cluster Domain, nombres de Service, nombres de StatefulSet, y cómo impactan en los nombres DNS de los Pods del StatefulSet:

Cluster DomainService (ns/nombre)StatefulSet (ns/nombre)StatefulSet DomainPod DNSPod Hostname
cluster.localdefault/nginxdefault/webnginx.default.svc.cluster.localweb-{0..N-1}.nginx.default.svc.cluster.localweb-{0..N-1}
cluster.localfoo/nginxfoo/webnginx.foo.svc.cluster.localweb-{0..N-1}.nginx.foo.svc.cluster.localweb-{0..N-1}
kube.localfoo/nginxfoo/webnginx.foo.svc.kube.localweb-{0..N-1}.nginx.foo.svc.kube.localweb-{0..N-1}

Almacenamiento estable

Kubernetes crea un PersistentVolume para cada VolumeClaimTemplate. En el ejemplo de nginx de arriba, cada Pod recibirá un único PersistentVolume con una StorageClass igual a my-storage-class y 1 GiB de almacenamiento provisionado. Si no se indica ninguna StorageClass, entonces se usa la StorageClass por defecto. Cuando un Pod se (re)programa en un nodo, sus volumeMounts montan los PersistentVolumes asociados con sus PersistentVolume Claims. Nótese que los PersistentVolumes asociados con los PersistentVolume Claims de los Pods no se eliminan cuando los Pods, o los StatefulSet se eliminan. Esto debe realizarse manualmente.

Etiqueta de Nombre de Pod

Cuando el controlador del StatefulSet crea un Pod, añade una etiqueta, statefulset.kubernetes.io/pod-name, que toma el valor del nombre del Pod. Esta etiqueta te permite enlazar un Service a un Pod específico en el StatefulSet.

Garantías de Despliegue y Escalado

  • Para un StatefulSet con N réplicas, cuando los Pods se despliegan, se crean secuencialmente, en orden de {0..N-1}.
  • Cuando se eliminan los Pods, se terminan en orden opuesto, de {N-1..0}.
  • Antes de que una operación de escalado se aplique a un Pod, todos sus predecesores deben estar Running y Ready.
  • Antes de que se termine un Pod, todos sus sucesores deben haberse apagado completamente.

El StatefulSet no debería tener que indicar un valor 0 para el campo pod.Spec.TerminationGracePeriodSeconds. Esta práctica no es segura y se aconseja no hacerlo. Para una explicación más detallada, por favor echa un vistazo a cómo forzar la eliminación de Pods de un StatefulSet.

Cuando el ejemplo nginx de arriba se crea, se despliegan tres Pods en el orden web-0, web-1, web-2. web-1 no se desplegará hasta que web-0 no esté Running y Ready, y web-2 no se desplegará hasta que web-1 esté Running y Ready. En caso de que web-0 fallase, después de que web-1 estuviera Running y Ready, pero antes de que se desplegara web-2, web-2 no se desplegaría hasta que web-0 se redesplegase con éxito y estuviera Running y Ready.

Si un usuario fuera a escalar el ejemplo desplegado parcheando el StatefulSet de forma que replicas=1, web-2 se terminaría primero. web-1 no se terminaría hasta que web-2 no se hubiera apagado y eliminado por completo. Si web-0 fallase después de que web-2 se hubiera terminado y apagado completamente, pero antes del término de web-1, entonces web-1 no se terminaría hasta que web-0 estuviera Running y Ready.

Reglas de Gestión de Pods

En Kubernetes 1.7 y versiones posteriores, el StatefulSet permite flexibilizar sus garantías de ordenación al mismo tiempo que preservar su garantía de singularidad e identidad a través del campo .spec.podManagementPolicy.

Gestión de tipo OrderedReady de Pods

La gestión de tipo OrderedReady de pods es la predeterminada para los StatefulSets. Implementa el comportamiento descrito arriba.

Gestión de tipo Parallel de Pods

La gestión de tipo Parallel de pods le dice al controlador del StatefulSet que lance y termine todos los Pods en paralelo, y que no espere a que los Pods estén Running y Ready o completamente terminados antes de lanzar o terminar otro Pod.

Estrategias de Actualización

En Kubernetes 1.7 y a posteriori, el campo .spec.updateStrategy del StatefulSet permite configurar y deshabilitar las actualizaciones automátizadas en línea para los contenedores, etiquetas, peticiones/límites de recursos, y anotaciones de los Pods del StatefulSet.

On Delete

La estrategia de actualización OnDelete implementa el funcionamiento tradicional (1.6 y previo). Cuando el campo .spec.updateStrategy.type de un StatefulSet se pone al valor OnDelete, el controlador del StatefulSet no actualizará automáticamente los Pods del StatefulSet. Los usuarios deben eliminar manualmente los Pods para forzar al controlador a crear nuevos Pods que reflejen las modificaciones hechas al campo .spec.template del StatefulSet.

Rolling Updates

La estrategia de actualización RollingUpdate implementa una actualización automatizada en línea de los Pods del StatefulSet. Es la estrategia por defecto cuando el campo .spec.updateStrategy se deja sin valor. Cuando el campo .spec.updateStrategy.type de un StatefulSet se pone al valor RollingUpdate, el controlador del StatefulSet lo eliminará y recreará cada Pod en el StatefulSet. Procederá en el mismo orden en que ha terminado los Pod (del número ordinal más grande al más pequeño), actualizando cada Pod uno por uno. Esperará a que el Pod actualizado esté Running y Ready antes de actualizar su predecesor.

Particiones

La estrategia de actualización RollingUpdate puede particionarse, indicando el valor del campo .spec.updateStrategy.rollingUpdate.partition. Si se indica una partición, todos los Pods con un número ordinal mayor o igual que el de la partición serán actualizados cuando el campo .spec.template del StatefulSet se actualice. Todos los Pods con un número ordinal que sea menor que el de la partición no serán actualizados, e incluso si son eliminados, serán recreados con la versión anterior. Si el campo .spec.updateStrategy.rollingUpdate.partition de un StatefulSet es mayor que el valor del campo .spec.replicas, las modificaciones al campo .spec.template no se propagarán a sus Pods. En la mayoría de ocasiones, no necesitarás usar una partición, pero pueden resultar útiles si quieres preparar una actualización, realizar un despliegue tipo canary, o llevar a cabo un despliegue en fases.

Retroceso Forzado

Cuando se usa Actualizaciones en línea con el valor de la Regla de Gestión de Pod (OrderedReady) por defecto, es posible acabar en un estado inconsistente que requiera de una intervención manual para arreglarlo.

Si actualizas la plantilla Pod a una configuración que nunca llega a Running y Ready (por ejemplo, debido a un binario incorrecto o un error de configuración a nivel de aplicación), el StatefulSet detendrá el despliegue y esperará.

En este estado, no es suficiente con revertir la plantilla Pod a la configuración buena. Debido a un problema conocido, el StatefulSet seguirá esperando a que los Pod estropeados se pongan en Ready (lo que nunca ocurre) antes de intentar revertirla a la configuración que funcionaba.

Antes de revertir la plantilla, debes también eliminar cualquier Pod que el StatefulSet haya intentando ejecutar con la configuración incorrecta. El StatefulSet comenzará entonces a recrear los Pods usando la plantilla revertida.

Siguientes pasos

2.5 - DaemonSet

Un DaemonSet garantiza que todos (o algunos) de los nodos ejecuten una copia de un Pod. Conforme se añade más nodos al clúster, nuevos Pods son añadidos a los mismos. Conforme se elimina nodos del clúster, dichos Pods se destruyen. Al eliminar un DaemonSet se limpian todos los Pods que han sido creados.

Algunos casos de uso típicos de un DaemonSet son:

  • Ejecutar un proceso de almacenamiento en el clúster.
  • Ejecutar un proceso de recolección de logs en cada nodo.
  • Ejecutar un proceso de monitorización de nodos en cada nodo.

De forma básica, se debería usar un DaemonSet, cubriendo todos los nodos, por cada tipo de proceso. En configuraciones más complejas se podría usar múltiples DaemonSets para un único tipo de proceso, pero con diferentes parámetros y/o diferentes peticiones de CPU y memoria según el tipo de hardware.

Escribir una especificación de DaemonSet

Crear un DaemonSet

Un DaemonSet se describe por medio de un archivo YAML. Por ejemplo, el archivo daemonset.yaml de abajo describe un DaemonSet que ejecuta la imagen Docker de fluentd-elasticsearch:

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentd-elasticsearch
  namespace: kube-system
  labels:
    k8s-app: fluentd-logging
spec:
  selector:
    matchLabels:
      name: fluentd-elasticsearch
  template:
    metadata:
      labels:
        name: fluentd-elasticsearch
    spec:
      tolerations:
      - key: node-role.kubernetes.io/master
        operator: Exists
        effect: NoSchedule
      containers:
      - name: fluentd-elasticsearch
        image: gcr.io/fluentd-elasticsearch/fluentd:v2.5.1
        resources:
          limits:
            memory: 200Mi
          requests:
            cpu: 100m
            memory: 200Mi
        volumeMounts:
        - name: varlog
          mountPath: /var/log
        - name: varlibdockercontainers
          mountPath: /var/lib/docker/containers
          readOnly: true
      terminationGracePeriodSeconds: 30
      volumes:
      - name: varlog
        hostPath:
          path: /var/log
      - name: varlibdockercontainers
        hostPath:
          path: /var/lib/docker/containers
  • Crear un DaemonSet basado en el archivo YAML:
kubectl apply -f https://k8s.io/examples/controllers/daemonset.yaml

Campos requeridos

Como con cualquier otra configuración de Kubernetes, un DaemonSet requiere los campos apiVersion, kind, y metadata. Para información general acerca de cómo trabajar con ficheros de configuración, ver los documentos desplegar aplicaciones, configurar contenedores, y gestión de objetos usando kubectl.

Un DaemonSet también necesita un sección .spec.

Plantilla Pod

El campo .spec.template es uno de los campos obligatorios de la sección .spec.

El campo .spec.template es una plantilla Pod. Tiene exactamente el mismo esquema que un Pod, excepto por el hecho de que está anidado y no tiene los campos apiVersion o kind.

Además de los campos obligatorios de un Pod, la plantilla Pod para un DaemonSet debe especificar las etiquetas apropiadas (ver selector de pod).

Una plantilla Pod para un DaemonSet debe tener una RestartPolicy igual a Always, o no indicarse, lo cual asume por defecto el valor Always.

Selector de Pod

El campo .spec.selector es un selector de pod. Funciona igual que el campo .spec.selector de un Job.

A partir de Kubernetes 1.8, se debe configurar un selector de pod que coincida con las etiquetas definidas en el .spec.template. Así, el selector de pod ya no asume valores por defecto cuando no se indica. Dichos valores por defecto no eran compatibles con kubectl apply. Además, una vez que se ha creado el DaemonSet, su campo .spec.selector no puede alterarse porque, si fuera el caso, ello podría resultar en Pods huérfanos, lo cual confundiría a los usuarios.

El campo .spec.selector es un objeto que, a su vez, consiste en dos campos:

  • matchLabels - funciona igual que el campo .spec.selector de un ReplicationController.
  • matchExpressions - permite construir selectores más sofisticados indicando la clave, la lista de valores y un operador para relacionar la clave y los valores.

Cuando se configura ambos campos, el resultado es conjuntivo (AND).

Si se especifica el campo .spec.selector, entonces debe coincidir con el campo .spec.template.metadata.labels. Aquellas configuraciones que no coinciden, son rechazadas por la API.

Además, normalmente no se debería crear ningún Pod con etiquetas que coincidan con el selector, bien sea de forma directa, via otro DaemonSet, o via otro controlador como un ReplicaSet. De ser así, el controlador del DaemonSet pensará que dichos Pods fueron en realidad creados por él mismo. Kubernetes, en cualquier caso, no te impide realizar esta operación. Un caso donde puede que necesites hacer esto es cuando quieres crear manualmente un Pod con un valor diferente en un nodo para pruebas.

Ejecutar Pods sólo en Nodos seleccionados

Si se configura un .spec.template.spec.nodeSelector, entonces el controlador del DaemonSet creará los Pods en aquellos nodos que coincidan con el selector de nodo indicado. De forma similar, si se configura una .spec.template.spec.affinity, entonces el controlador del DaemonSet creará los Pods en aquellos nodos que coincidan con la afinidad de nodo indicada. Si no se configura ninguno de los dos, entonces el controlador del DaemonSet creará los Pods en todos los nodos.

Cómo se planifican los Pods procesos

Planificados por el controlador del DaemonSet (deshabilitado por defecto a partir de 1.12)

Normalmente, el planificador de Kubernetes determina la máquina donde se ejecuta un Pod. Sin embargo, los Pods creados por el controlador del DaemonSet ya tienen la máquina seleccionada (puesto que cuando se crea el Pod, se indica el campo .spec.nodeName, y por ello el planificador los ignora). Por lo tanto:

  • El controlador del DaemonSet no tiene en cuenta el campo unschedulable de un nodo.
  • El controlador del DaemonSet puede crear Pods incluso cuando el planificador no ha arrancado, lo cual puede ayudar en el arranque del propio clúster.

Planificados por el planificador por defecto de Kubernetes (habilitado por defecto desde 1.12)

FEATURE STATE: Kubernetes v1.31 [beta]

Un DaemonSet garantiza que todos los nodos elegibles ejecuten una copia de un Pod. Normalmente, es el planificador de Kubernetes quien determina el nodo donde se ejecuta un Pod. Sin embargo, los pods del DaemonSet son creados y planificados por el mismo controlador del DaemonSet. Esto introduce los siguientes inconvenientes:

  • Comportamiento inconsistente de los Pods: Los Pods normales que están esperando a ser creados, se encuentran en estado Pending, pero los pods del DaemonSet no pasan por el estado Pending. Esto confunde a los usuarios.
  • La prioridad y el comportamiento de apropiación de Pods se maneja por el planificador por defecto. Cuando se habilita la contaminación, el controlador del DaemonSet tomará la decisiones de planificación sin considerar ni la prioridad ni la contaminación del pod.

ScheduleDaemonSetPods permite planificar DaemonSets usando el planificador por defecto en vez del controlador del DaemonSet, añadiendo la condición NodeAffinity a los pods del DaemonSet, en vez de la condición .spec.nodeName. El planificador por defecto se usa entonces para asociar el pod a su servidor destino. Si la afinidad de nodo del pod del DaemonSet ya existe, se sustituye. El controlador del DaemonSet sólo realiza estas operaciones cuando crea o modifica los pods del DaemonSet, y no se realizan cambios al spec.template del DaemonSet.

nodeAffinity:
  requiredDuringSchedulingIgnoredDuringExecution:
    nodeSelectorTerms:
    - matchFields:
      - key: metadata.name
        operator: In
        values:
        - target-host-name

Adicionalmente, se añade de forma automática la tolerancia node.kubernetes.io/unschedulable:NoSchedule a los Pods del DaemonSet. Así, el planificador por defecto ignora los nodos unschedulable cuando planifica los Pods del DaemonSet.

Contaminaciones (taints) y Tolerancias (tolerations)

A pesar de que los Pods de proceso respetan las contaminaciones y tolerancias, la siguientes tolerancias son añadidas a los Pods del DaemonSet de forma automática según las siguientes características:

Clave de toleranciaEfectoVersiónDescripción
node.kubernetes.io/not-readyNoExecute1.13+Los pods del DaemonSet no son expulsados cuando hay problemas de nodo como una partición de red.
node.kubernetes.io/unreachableNoExecute1.13+Los pods del DaemonSet no son expulsados cuando hay problemas de nodo como una partición de red.
node.kubernetes.io/disk-pressureNoSchedule1.8+Los pods del DaemonSet no son expulsados cuando hay problemas de nodo como la falta de espacio en disco.
node.kubernetes.io/memory-pressureNoSchedule1.8+Los pods del DaemonSet no son expulsados cuando hay problemas de nodo como la falta de memoria.
node.kubernetes.io/unschedulableNoSchedule1.12+Los pods del DaemonSet toleran los atributos unschedulable del planificador por defecto.
node.kubernetes.io/network-unavailableNoSchedule1.12+Los pods del DaemonSet, que usan la red del servidor anfitrión, toleran los atributos network-unavailable del planificador por defecto.

Comunicarse con los Pods de los DaemonSets

Algunos patrones posibles para la comunicación con los Pods de un DaemonSet son:

  • Push: Los Pods del DaemonSet se configuran para enviar actualizaciones a otro servicio, como una base de datos de estadísticas. No tienen clientes.
  • NodeIP y Known Port: Los Pods del DaemonSet pueden usar un hostPort, de forma que se les puede alcanzar via las IPs del nodo. Los clientes conocen la lista de IPs del nodo de algún modo, y conocen el puerto acordado.
  • DNS: Se crea un servicio headless con el mismo selector de pod, y entonces se descubre a los DaemonSets usando los recursos endpoints o mediante múltiples registros de tipo A en el DNS.
  • Service: Se crea un servicio con el mismo selector de Pod, y se usa el servicio para llegar al proceso de uno de los nodos. (No hay forma de determinar el nodo exacto.)

Actualizar un DaemonSet

Si se cambian las etiquetas de nodo, el DaemonSet comenzará de forma inmediata a añadir Pods a los nuevos nodos que coincidan y a eliminar los Pods de aquellos nuevos nodos donde no coincidan.

Puedes modificar los Pods que crea un DaemonSet. Sin embargo, no se permite actualizar todos los campos de los Pods. Además, el controlador del DaemonSet utilizará la plantilla original la próxima vez que se cree un nodo (incluso con el mismo nombre).

Puedes eliminar un DaemonSet. Si indicas el parámetro --cascade=false al usar kubectl, entonces los Pods continuarán ejecutándose en los nodos. Así, puedes crear entonces un nuevo DaemonSet con una plantilla diferente. El nuevo DaemonSet con la plantilla diferente reconocerá a todos los Pods existentes que tengan etiquetas coincidentes y no modificará o eliminará ningún Pod aunque la plantilla no coincida con los Pods desplegados. Entonces, deberás forzar la creación del nuevo Pod eliminando el Pod mismo o el nodo.

A partir de las versión 1.6 de Kubernetes, puedes llevar a cabo una actualización continua en un DaemonSet.

Alternativas al DaemonSet

Secuencias de comandos de inicialización

Aunque es perfectamente posible ejecutar procesos arrancándolos directamente en un nodo (ej. usando init, upstartd, o systemd), existen numerosas ventajas si se realiza via un DaemonSet:

  • Capacidad de monitorizar y gestionar los logs de los procesos del mismo modo que para las aplicaciones.
  • Mismo lenguaje y herramientas de configuración (ej. plantillas de Pod, kubectl) tanto para los procesos como para las aplicaciones.
  • Los procesos que se ejecutan en contenedores con límitaciones de recursos aumentan el aislamiento entre dichos procesos y el resto de contenedores de aplicaciones. Sin embargo, esto también se podría conseguir ejecutando los procesos en un contenedor en vez de un Pod (ej. arrancarlos directamente via Docker).

Pods individuales

Es posible crear Pods directamente sin indicar el nodo donde ejecutarse. Sin embargo, la ventaja del DaemonSet es que sustituye los Pods que se eliminan o terminan por cualquier razón, como en el caso de un fallo del nodo o una intervención disruptiva de mantenimiento del nodo, como la actualización del kernel. Por esta razón, deberías siempre utilizar un DaemonSet en vez de crear Pods individuales.

Pods estáticos

Es posible crear Pods a partir de archivos en el directorio donde está escuchando el proceso Kubelet. Este tipo de Pods se denomina pods estáticos. A diferencia del DaemonSet, los Pods estáticos no se pueden gestionar con kubectl o cualquier otro cliente de la API de Kubernetes. Los Pods estáticos no dependen del apiserver, lo cual los hace convenientes para el arranque inicial del clúster. Además, puede que los Pods estáticos se deprecien en el futuro.

Deployments

Los DaemonSets son similares a los Deployments en el sentido que ambos crean Pods, y que dichos Pods tienen procesos que no se espera que terminen (ej. servidores web, servidores de almacenamiento).

Utiliza un Deployment para definir servicios sin estado, como las interfaces de usuario, donde el escalado vertical y horizontal del número de réplicas y las actualizaciones continuas son mucho más importantes que el control exacto del servidor donde se ejecuta el Pod. Utiliza un DaemonSet cuando es importante que una copia de un Pod siempre se ejecute en cada uno de los nodos, y cuando se necesite que arranque antes que el resto de Pods.

2.6 - Recolección de Basura

El papel del recolector de basura de Kubernetes es el de eliminar determinados objetos que en algún momento tuvieron un propietario, pero que ahora ya no.

Propietarios y subordinados

Algunos objetos de Kubernetes son propietarios de otros objetos. Por ejemplo, un ReplicaSet es el propietario de un conjunto de Pods. Los objetos que se poseen se denominan subordinados del objeto propietario. Cada objeto subordinado tiene un campo metadata.ownerReferences que apunta al objeto propietario.

En ocasiones, Kubernetes pone el valor del campo ownerReference automáticamente. Por ejemplo, cuando creas un ReplicaSet, Kubernetes automáticamente pone el valor del campo ownerReference de cada Pod en el ReplicaSet. A partir de la versión 1.8, Kubernetes automáticamente pone el valor de ownerReference para los objetos creados o adoptados por un ReplicationController, ReplicaSet, StatefulSet, DaemonSet, Deployment, Job y CronJob.

También puedes configurar las relaciones entre los propietarios y sus subordinados de forma manual indicando el valor del campo ownerReference.

Aquí se muestra un archivo de configuración para un ReplicaSet que tiene tres Pods:

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: my-repset
spec:
  replicas: 3
  selector:
    matchLabels:
      pod-is-for: garbage-collection-example
  template:
    metadata:
      labels:
        pod-is-for: garbage-collection-example
    spec:
      containers:
      - name: nginx
        image: nginx

Si se crea el ReplicaSet y entonces se muestra los metadatos del Pod, se puede observar el campo OwnerReferences:

kubectl apply -f https://k8s.io/examples/controllers/replicaset.yaml
kubectl get pods --output=yaml

La salida muestra que el propietario del Pod es el ReplicaSet denominado my-repset:

apiVersion: v1
kind: Pod
metadata:
  ...
  ownerReferences:
  - apiVersion: apps/v1
    controller: true
    blockOwnerDeletion: true
    kind: ReplicaSet
    name: my-repset
    uid: d9607e19-f88f-11e6-a518-42010a800195
  ...

Controlar cómo el recolector de basura elimina los subordinados

Cuando eliminas un objeto, puedes indicar si sus subordinados deben eliminarse también de forma automática. Eliminar los subordinados automáticamente se denomina borrado en cascada.
Hay dos modos de borrado en cascada: en segundo plano y en primer plano.

Si eliminas un objeto sin borrar sus subordinados de forma automática, dichos subordinados se convierten en huérfanos.

Borrado en cascada en primer plano

En el borrado en cascada en primer plano, el objeto raíz primero entra en un estado llamado "deletion in progress". En este estado "deletion in progress", se cumplen las siguientes premisas:

  • El objeto todavía es visible a través de la API REST
  • Se pone el valor del campo deletionTimestamp del objeto
  • El campo metadata.finalizers del objeto contiene el valor "foregroundDeletion".

Una vez que se pone el estado "deletion in progress", el recolector de basura elimina los subordinados del objeto. Una vez que el recolector de basura ha eliminado todos los subordinados "bloqueantes" (los objetos con ownerReference.blockOwnerDeletion=true), elimina el objeto propietario.

Cabe mencionar que usando "foregroundDeletion", sólo los subordinados con valor en ownerReference.blockOwnerDeletion bloquean la eliminación del objeto propietario. A partir de la versión 1.7, Kubernetes añadió un controlador de admisión que controla el acceso de usuario cuando se intenta poner el campo blockOwnerDeletion a true con base a los permisos de borrado del objeto propietario, de forma que aquellos subordinados no autorizados no puedan retrasar la eliminación del objeto propietario.

Si un controlador (como un Deployment o un ReplicaSet) establece el valor del campo ownerReferences de un objeto, se pone blockOwnerDeletion automáticamente y no se necesita modificar de forma manual este campo.

Borrado en cascada en segundo plano

En el borrado en cascada en segundo plano, Kubernetes elimina el objeto propietario inmediatamente y es el recolector de basura quien se encarga de eliminar los subordinados en segundo plano.

Configurar la regla de borrado en cascada

Para controlar la regla de borrado en cascada, configura el campo propagationPolicy del parámetro deleteOptions cuando elimines un objeto. Los valores posibles incluyen "Orphan", "Foreground", o "Background".

Antes de la versión 1.9 de Kubernetes, la regla predeterminada del recolector de basura para la mayoría de controladores era orphan. Esto incluía al ReplicationController, ReplicaSet, StatefulSet, DaemonSet, y al Deployment. Para los tipos dentro de las versiones de grupo extensions/v1beta1, apps/v1beta1, y apps/v1beta2, a menos que se indique de otra manera, los objetos subordinados se quedan huérfanos por defecto. En Kubernetes 1.9, para todos los tipos de la versión de grupo apps/v1, los objetos subordinados se eliminan por defecto.

Aquí se muestra un ejemplo que elimina los subordinados en segundo plano:

kubectl proxy --port=8080
curl -X DELETE localhost:8080/apis/apps/v1/namespaces/default/replicasets/my-repset \
-d '{"kind":"DeleteOptions","apiVersion":"v1","propagationPolicy":"Background"}' \
-H "Content-Type: application/json"

Aquí se muestra un ejemplo que elimina los subordinados en primer plano:

kubectl proxy --port=8080
curl -X DELETE localhost:8080/apis/apps/v1/namespaces/default/replicasets/my-repset \
-d '{"kind":"DeleteOptions","apiVersion":"v1","propagationPolicy":"Foreground"}' \
-H "Content-Type: application/json"

Aquí se muestra un ejemplo de subordinados huérfanos:

kubectl proxy --port=8080
curl -X DELETE localhost:8080/apis/apps/v1/namespaces/default/replicasets/my-repset \
-d '{"kind":"DeleteOptions","apiVersion":"v1","propagationPolicy":"Orphan"}' \
-H "Content-Type: application/json"

kubectl también permite el borrado en cascada. Para eliminar los subordinados automáticamente, utiliza el parámetro --cascade a true. Usa false para subordinados huérfanos. Por defecto, el valor de --cascade es true.

Aquí se muestra un ejemplo de huérfanos de subordinados de un ReplicaSet:

kubectl delete replicaset my-repset --cascade=false

Nota adicional sobre los Deployments

Antes de la versión 1.7, cuando se usaba el borrado en cascada con Deployments se debía usar propagationPolicy: Foreground para eliminar no sólo los ReplicaSets creados, sino también sus Pods correspondientes. Si este tipo de propagationPolicy no se usa, solo se elimina los ReplicaSets, y los Pods se quedan huérfanos. Ver kubeadm/#149 para más información.

Problemas conocidos

Seguimiento en #26120

Siguientes pasos

Documento de Diseño 1

Documento de Diseño 2

2.7 - Controlador TTL para Recursos Finalizados

FEATURE STATE: Kubernetes v1.12 [alpha]

El controlador TTL proporciona un mecanismo TTL para limitar el tiempo de vida de los objetos de recurso que ya han terminado su ejecución. El controlador TTL sólo se ocupa de los Jobs por el momento, y puede que se extienda para gestionar otros recursos que terminen su ejecución, como los Pods y los recursos personalizados.

Descargo de responsabilidad Alpha: esta característica está actualmente en versión alpha, y puede habilitarse mediante el feature gate TTLAfterFinished.

Controlador TTL

El controlador TTL sólo soporta los Jobs por ahora. Un operador del clúster puede usar esta funcionalidad para limpiar los Jobs terminados (bien Complete o Failed) automáticamente especificando el valor del campo .spec.ttlSecondsAfterFinished del Job, como en este ejemplo. El controlador TTL asumirá que un recurso es candidato a ser limpiado TTL segundos después de que el recurso haya terminado; dicho de otra forma, cuando el TTL haya expirado. Cuando el controlador TTL limpia un recursos, lo elimina en cascada, esto es, borra sus objetos subordinados juntos. Nótese que cuando se elimina un recurso, se respetan las garantías de su ciclo de vida, como con los finalizadores.

Los segundos TTL pueden ser configurados en cualquier momento. Aquí se muestran algunos ejemplos para poner valores al campo .spec.ttlSecondsAfterFinished de un Job:

  • Indicando este campo en el manifiesto de los recursos, de forma que se pueda limpiar un Job automáticamente un tiempo después de que haya finalizado.
  • Haciendo que el campo de los recursos existentes, ya finalizados, adopte esta nueva característica.
  • Usando un mutating admission webhook para poner el valor de este campo dinámicamente en el momento de la creación del recursos. Los administradores del clúster pueden usar este enfoque para forzar una regla TTL para los recursos terminados.
  • Usando un mutating admission webhook para poner el valor de este campo dinámicamente después de que el recurso haya terminado, y eligiendo diferentes valores TTL basados en los estados de los recursos, etiquetas, etc.

Advertencia

Actualizar los segundos TTL

Cabe señalar que el período TTL , ej. campo .spec.ttlSecondsAfterFinished de los Jobs, puede modificarse después de que el recurso haya sido creado o terminado. Sin embargo, una vez que el Job se convierte en candidato para ser eliminado (cuando el TTL ha expirado), el sistema no garantiza que se mantendrán los Jobs, incluso si una modificación para extender el TTL devuelve una respuesta API satisfactoria.

Diferencia horaria

Como el controlador TTL usa marcas de fecha/hora almacenadas en los recursos de Kubernetes para determinar si el TTL ha expirado o no, esta funcionalidad es sensible a las diferencias horarias del clúster, lo que puede provocar que el controlador TTL limpie recursos en momentos equivocados.

En Kubernetes, se necesita ejecutar NTP en todos los nodos (ver #6159) para evitar este problema. Los relojes no siempre son correctos, pero la diferencia debería ser muy pequeña. Ten presente este riesgo cuando pongas un valor distinto de cero para el TTL.

Siguientes pasos

Limpiar Jobs automáticamente

Documento de diseño

2.8 - Jobs - Ejecución hasta el final

Un Job crea uno o más Pods y se asegura de que un número específico de ellos termina de forma satisfactoria. Conforme los pods terminan satisfactoriamente, el Job realiza el seguimiento de las ejecuciones satisfactorias. Cuando se alcanza un número específico de ejecuciones satisfactorias, la tarea (esto es, el Job) se completa. Al eliminar un Job se eliminan los Pods que haya creado.

Un caso simple de uso es crear un objeto Job para que se ejecute un Pod de manera fiable hasta el final. El objeto Job arrancará un nuevo Pod si el primer Pod falla o se elimina (por ejemplo como consecuencia de un fallo de hardware o un reinicio en un nodo).

También se puede usar un Job para ejecutar múltiples Pods en paralelo.

Ejecutar un Job de ejemplo

Aquí se muestra un ejemplo de configuración de Job. Este ejemplo calcula los primeros 2000 decimales de π y los imprime por pantalla. Tarda unos 10s en completarse.

apiVersion: batch/v1
kind: Job
metadata:
  name: pi
spec:
  template:
    spec:
      containers:
      - name: pi
        image: perl
        command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
      restartPolicy: Never
  backoffLimit: 4

Puedes ejecutar el ejemplo con este comando:

kubectl apply -f https://k8s.io/examples/controllers/job.yaml
job "pi" created

Comprueba el estado del Job con kubectl:

kubectl describe jobs/pi
Name:             pi
Namespace:        default
Selector:         controller-uid=b1db589a-2c8d-11e6-b324-0209dc45a495
Labels:           controller-uid=b1db589a-2c8d-11e6-b324-0209dc45a495
                  job-name=pi
Annotations:      <none>
Parallelism:      1
Completions:      1
Start Time:       Tue, 07 Jun 2016 10:56:16 +0200
Pods Statuses:    0 Running / 1 Succeeded / 0 Failed
Pod Template:
  Labels:       controller-uid=b1db589a-2c8d-11e6-b324-0209dc45a495
                job-name=pi
  Containers:
   pi:
    Image:      perl
    Port:
    Command:
      perl
      -Mbignum=bpi
      -wle
      print bpi(2000)
    Environment:        <none>
    Mounts:             <none>
  Volumes:              <none>
Events:
  FirstSeen    LastSeen    Count    From            SubobjectPath    Type        Reason            Message
  ---------    --------    -----    ----            -------------    --------    ------            -------
  1m           1m          1        {job-controller }                Normal      SuccessfulCreate  Created pod: pi-dtn4q

Para ver los Pods de un Job que se han completado, usa kubectl get pods.

Para listar todos los Pods que pertenecen a un Job de forma que sea legible, puedes usar un comando como:

pods=$(kubectl get pods --selector=job-name=pi --output=jsonpath='{.items[*].metadata.name}')
echo $pods
pi-aiw0a

En este caso, el selector es el mismo que el selector del Job. La opción --output=jsonpath indica un expresión que simplemente obtiene el nombre de cada Pod en la lista devuelta.

Mira la salida estándar de uno de los Pods:

$ kubectl logs $pods
3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153643678925903600113305305488204665213841469519415116094330572703657595919530921861173819326117931051185480744623799627495673518857527248912279381830119491298336733624406566430860213949463952247371907021798609437027705392171762931767523846748184676694051320005681271452635608277857713427577896091736371787214684409012249534301465495853710507922796892589235420199561121290219608640344181598136297747713099605187072113499999983729780499510597317328160963185950244594553469083026425223082533446850352619311881710100031378387528865875332083814206171776691473035982534904287554687311595628638823537875937519577818577805321712268066130019278766111959092164201989380952572010654858632788659361533818279682303019520353018529689957736225994138912497217752834791315155748572424541506959508295331168617278558890750983817546374649393192550604009277016711390098488240128583616035637076601047101819429555961989467678374494482553797747268471040475346462080466842590694912933136770289891521047521620569660240580381501935112533824300355876402474964732639141992726042699227967823547816360093417216412199245863150302861829745557067498385054945885869269956909272107975093029553211653449872027559602364806654991198818347977535663698074265425278625518184175746728909777727938000816470600161452491921732172147723501414419735685481613611573525521334757418494684385233239073941433345477624168625189835694855620992192221842725502542568876717904946016534668049886272327917860857843838279679766814541009538837863609506800642251252051173929848960841284886269456042419652850222106611863067442786220391949450471237137869609563643719172874677646575739624138908658326459958133904780275901

Escribir una especificación de Job

Como con el resto de configuraciones de Kubernetes, un Job necesita los campos apiVersion, kind, y metadata.

Un Job también necesita la sección .spec.

Plantilla Pod

El campo .spec.template es el único campo obligatorio de .spec.

El campo .spec.template es una plantilla Pod. Tiene exactamente el mismo esquema que un pod, excepto por el hecho de que está anidado y no tiene el campo apiVersion o kind.

Además de los campos olbigatorios de un Pod, una plantilla Pod de un Job debe indicar las etiquetas apropiadas (ver selector de pod) y una regla de reinicio apropiada.

Sólo se permite los valores Never o OnFailure para RestartPolicy.

Selector de Pod

El campo .spec.selector es opcional. En la práctica mayoría de los casos no deberías configurarlo. Mira la sección sobre configurar tu propio selector de pod.

Jobs en paralelo

Hay tres tipos principales de tarea aptos para ejecutarse como un Job:

  1. Jobs no paralelos
  • normalmente, sólo se arranca un Pod, a menos que el Pod falle.
  • el Job se completa tan pronto como su Pod termine de forma satisfactoria.
  1. Jobs en paralelo con un cupo fijo de terminación:
  • se configura un valor positivo distinto de cero para el campo .spec.completions.
  • el Job representa la tarea en general, y se completa cuando hay una ejecución satisfactoria de un Pod por cada valor dentro del rango de 1 a .spec.completions.
  • no implementado todavía: A cada Pod se le pasa un índice diferenente dentro del rango de 1 a .spec.completions.
  1. Jobs en paralelo con una cola de trabajo:
  • no se especifica el campo .spec.completions, por defecto .spec.parallelism.
  • los Pods deben coordinarse entre ellos mismos o a través de un servicio externo que determine quién debe trabajar en qué. Por ejemplo, un Pod podría ir a buscar un lote de hasta N ítems de una cola de trabajo.
  • cada Pod es capaz de forma independiente de determinar si sus compañeros han terminado o no, y como consecuencia el Job entero ha terminado.
  • cuando cualquier Pod del Job termina con éxito, no se crean nuevos Pods.
  • una vez que al menos uno de los Pods ha terminado con éxito y todos los Pods han terminado, entonces el Job termina con éxito.
  • una vez que cualquier Pod ha terminado con éxito, ningún otro Pod debería continuar trabajando en la misma tarea o escribiendo ningún resultado. Todos ellos deberían estar en proceso de terminarse.

En un Job no paralelo, no debes indicar el valor de .spec.completions ni .spec.parallelism. Cuando ambos se dejan sin valor, ambos se predeterminan a 1.

En un Job con cupo fijo de terminación, deberías poner el valor de .spec.completions al número de terminaciones que se necesiten. Puedes dar un valor a .spec.parallelism, o dejarlo sin valor, en cuyo caso se predetermina a 1.

En un Job con cola de trabajo, no debes indicar el valor de .spec.completions, y poner el valor de .spec.parallelism a un entero no negativo.

Para más información acerca de cómo usar los distintos tipos de Job, ver la sección de patrones de job.

Controlar el paralelismo

El paralelismo solicitado (.spec.parallelism) puede usar cualquier valor no negativo. Si no se indica, se predeterminad a 1. Si se indica como 0, entonces el Job se pausa de forma efectiva hasta que se incremente.

El paralelismo actual (número de pods ejecutándose en cada momento) puede que sea mayor o menor que el solicitado, por los siguientes motivos:

  • Para los Jobs con cupo fijo de terminaciones, el número actual de pods ejecutándose en paralelo no excede el número de terminaciones pendientes. Los valores superiores de .spec.parallelism se ignoran.
  • Para los Jobs con cola de trabajo, no se arranca nuevos Pods después de que cualquier Pod se haya completado -- sin embargo, se permite que se completen los Pods pendientes.
  • Cuando el controlador no ha tenido tiempo para reaccionar.
  • Cuando el controlador no pudo crear los Pods por el motivo que fuera (falta de ResourceQuota, falta de permisos, etc.), entonces puede que haya menos pods que los solicitados.
  • El controlador puede que regule la creación de nuevos Pods debido al excesivo número de fallos anteriores en el mismo Job.
  • Cuando un Pod se para de forma controlada, lleva tiempo pararlo.

Gestionar Fallos de Pod y Contenedor

Un contenedor de un Pod puede fallar por cualquier motivo, como porque el proceso que se estaba ejecutando termina con un código de salida distinto de cero, o porque se mató el contenedor por exceder un límite de memoria, etc. Si esto ocurre, y se tiene .spec.template.spec.restartPolicy = "OnFailure", entonces el Pod permance en el nodo, pero el contenedor se vuelve a ejecutar. Por lo tanto, tu aplicación debe poder gestionar el caso en que se reinicia de forma local, o bien especificar .spec.template.spec.restartPolicy = "Never". Ver el ciclo de vida de un pod para más información sobre restartPolicy.

Un Pod entero puede también fallar por cualquier motivo, como cuando se expulsa al Pod del nodo (porque el nodo se actualiza, reinicia, elimina, etc.), o si un contenedor del Pod falla cuando .spec.template.spec.restartPolicy = "Never". Cuando un Pod falla, entonces el controlador del Job arranca un nuevo Pod. Esto quiere decir que tu aplicación debe ser capaz de gestionar el caso en que se reinicia en un nuevo pod. En particular, debe ser capaz de gestionar los ficheros temporales, los bloqueos, los resultados incompletos, y cualquier otra dependencia de ejecuciones previas.

Nótese que incluso si se configura .spec.parallelism = 1 y .spec.completions = 1 y .spec.template.spec.restartPolicy = "Never", el mismo programa puede arrancarse dos veces.

Si se especifica .spec.parallelism y .spec.completions con valores mayores que 1, entonces puede que haya múltiples pods ejecutándose a la vez. Por ello, tus pods deben tolerar la concurrencia.

Regla de retroceso de Pod por fallo

Hay situaciones en que quieres que el Job falle después de intentar ejecutarlo unas cuantas veces debido a un error lógico en la configuración, etc. Para hacerlo, pon el valor de .spec.backoffLimit al número de reintentos que quieres antes de considerar el Job como fallido. El límite de retroceso se predetermina a 6. Los Pods fallidos asociados al Job son recreados por el controlador del Job con un retroceso exponencial (10s, 20s, 40s ...) limitado a seis minutos. El contador de retroceso se resetea si no aparecen Pods fallidos antes del siguiente chequeo de estado del Job.

Terminación y Limpieza de un Job

Cuando un Job se completa, ya no se crea ningún Pod, pero tampoco se elimina los Pods. Guardarlos permite ver todavía los logs de los pods acabados para comprobar errores, avisos, o cualquier otro resultado de diagnóstico. El objeto job también se conserva una vez que se ha completado para que se pueda ver su estado. Es decisión del usuario si elimina los viejos jobs después de comprobar su estado. Eliminar el job con el comando kubectl (ej. kubectl delete jobs/pi o kubectl delete -f ./job.yaml). Cuando eliminas un job usando el comando kubectl, todos los pods que creó se eliminan también.

Por defecto, un Job se ejecutará de forma ininterrumpida a menos que uno de los Pods falle, en cuyo caso el Job se fija en el valor de .spec.backoffLimit descrito arriba. Otra forma de acabar un Job es poniéndole un vencimiento activo. Haz esto poniendo el valor del campo .spec.activeDeadlineSeconds del Job a un número de segundos.

El campo activeDeadlineSeconds se aplica a la duración del job, independientemente de cuántos Pods se hayan creado. Una vez que el Job alcanza activeDeadlineSeconds, se terminan todos sus Pods y el estado del Job se pone como type: Failed con reason: DeadlineExceeded.

Fíjate que el campo .spec.activeDeadlineSeconds de un Job tiene precedencia sobre el campo .spec.backoffLimit. Por lo tanto, un Job que está reintentando uno o más Pods fallidos no desplegará nuevos Pods una vez que alcance el límite de tiempo especificado por activeDeadlineSeconds, incluso si todavía no se ha alcanzado el backoffLimit.

Ejemplo:

apiVersion: batch/v1
kind: Job
metadata:
  name: pi-with-timeout
spec:
  backoffLimit: 5
  activeDeadlineSeconds: 100
  template:
    spec:
      containers:
      - name: pi
        image: perl
        command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
      restartPolicy: Never

Fíjate que tanto la especificación del Job como la especificación de la plantilla Pod dentro del Job tienen un campo activeDeadlineSeconds. Asegúrate que pones el valor de este campo de forma adecuada.

Limpiar los Jobs terminados automáticamente

Normalmente, los Jobs que han terminado ya no se necesitan en el sistema. Conservarlos sólo añade más presión al servidor API. Si dichos Jobs no se gestionan de forma directa por un controlador de más alto nivel, como los CronJobs, los Jobs pueden limpiarse por medio de CronJobs en base a la regla de limpieza basada en capacidad que se haya especificado.

Mecanismo TTL para Jobs terminados

FEATURE STATE: Kubernetes v1.12 [alpha]

Otra forma de limpiar los Jobs terminados (bien Complete o Failed) de forma automática es usando un mecanismo TTL proporcionado por un controlador TTL de recursos finalizados, indicando el valor .spec.ttlSecondsAfterFinished del Job.

Cuando el controlador TTL limpia el Job, lo eliminará en cascada, esto es, eliminará sus objetos subordinados, como Pods, junto con el Job. Nótese que cuando se elimina el Job, sus garantías de ciclo de vida, como los finalizadores, se tendrán en cuenta.

Por ejemplo:

apiVersion: batch/v1
kind: Job
metadata:
  name: pi-with-ttl
spec:
  ttlSecondsAfterFinished: 100
  template:
    spec:
      containers:
      - name: pi
        image: perl
        command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
      restartPolicy: Never

Aquí el Job pi-with-ttl será candidato a ser automáticamente eliminado, 100 segundos después de que termine.

Si el campo se pone a 0, el Job será candidato a ser automáticamente eliminado inmediatamente después de haber terminado. Si no se pone valor al campo, este Job no será eliminado por el controlador TTL una vez concluya.

Nótese que este mecanismo TTL está todavía en alpha, a través de la característica denominada TTLAfterFinished. Para más información, ver la documentación del controlador TTL para recursos terminados.

Patrones de Job

El objeto Job puede usarse para dar soporte a la ejecución fiable de Pods en paralelo. El objeto Job no se diseñó para dar soporte a procesos paralelos estrechamente comunicados, como los que comúnmente se encuentran en la computación científica. Eso sí, permite el proceso paralelo de un conjunto de ítems de trabajo independientes, pero relacionados entre sí. Estos pueden ser correos a enviar, marcos a renderizar, archivos a codificar, rangos de claves en una base de datos NoSQL a escanear, y demás.

En un sistema complejo, puede haber múltiples diferentes conjuntos de ítems de trabajo. Aquí sólo se está considerando un conjunto de ítems de trabajo que el usuario quiere gestionar de forma conjunta — un proceso por lotes.

Hay varios patrones diferentes para computación en paralelo, cada uno con sus fortalezas y sus debilidades. Los sacrificios a tener en cuenta son:

  • Un objeto Job para cada ítem de trabajo vs. un objeto Job simple para todos los ítems de trabajo. El último es mejor para grandes números de ítems de trabajo. El primero añade sobrecarga para el usuario y para el sistema al tener que gestionar grandes números de objetos Job.
  • El número de pods creados es igual al número de ítems de trabajo vs. cada Pod puede procesar múltiplese ítems de trabajo. El primero típicamente requiere menos modificaciones al código existente y a los contenedores. El último es mejor cuanto mayor sea el número de ítems de trabajo, por las mismas razones que antes..
  • Varios enfoques usan una cola de trabajo. Ello requiere ejecutar un servicio de colas, y modificaciones a las aplicaciones o contenedores existentes para que hagan uso de la cola de trabajo. Otras estrategias son más fáciles de adaptar a una aplicación ya usando contenedores.

Los sacrificios a tener en cuenta se indican a continuación, donde las columnas 2 a 4 representan los sacrificios de arriba. Los nombres de los patrones son también enlaces a ejemplos e información más detallada.

PatrónObjeto Job simple¿Menos pods que ítems de trabajo?¿No modificar la aplicación?¿Funciona en Kube 1.1?
Extensión de la Plantilla Job
Cola con Pod por Ítem de Trabajoa veces
Cola con Cuenta Variable de Pods
Job simple con Asignación Estática de Trabajo

Cuando se especifican terminaciones con .spec.completions, cada Pod creado por el controlado del Job tiene un specidéntico. Esto significa que todos los pods de una tarea tendrán la misma línea de comandos y la misma imagne, los mismo volúmenes, y (casi) las mismas variables de entorno. Estos patrones otorgan diferentes formas de organizar los pods para que trabajen en cosas distintas.

Esta tabla muestra la configuración necesaria para .spec.parallelism y .spec.completions para cada uno de los patrones. Aquí, T es el número de ítems de trabajo.

Patrón.spec.completions.spec.parallelism
Extensión de la Plantilla Job1debería ser 1
Cola con Pod por Ítem de TrabajoTcualquiera
Cola con Cuenta Variable de Pods1cualquiera
Job simple con Asignación Estática de TrabajoTcualquiera

Uso Avanzado

Especificar tu propio selector de pod

Normalmente, cuando creas un objeto Job, no especificas el campo .spec.selector. La lógica por defecto del sistema añade este campo cuando se crea el Job. Se elige un valor de selector que no se entremezcle con otras tareas.

Sin embargo, en algunos casos, puede que necesites sobreescribir este selector que se configura de forma automática. Para ello, puedes indicar el valor de .spec.selector en el Job.

Pero ten mucho cuidado cuando lo hagas. Si configuras un selector de etiquta que no es único para los pods de ese Job, y que selecciona Pods que no tienen que ver, entonces estos últimos pueden ser eliminados, o este Job puede contar los otros Pods para terminarse, o uno o ambos Jobs pueden negarse a crear Pods o ejecutarse hasta el final. Si se elige un selector que no es único, entonces otros controladores (ej. ReplicationController) y sus Pods puede comportarse de forma impredecibles también. Kubernetes no te impide cometer un error especificando el .spec.selector.

Aquí se muestra un ejemplo de un caso en que puede que necesites usar esta característica.

Digamos que el Job viejo todavía está ejeuctándose. Quieres que los Pods existentes sigan corriendo, pero quieres que el resto de los Pods que se creen usen una plantilla pod diferente y que el Job tenga un nombre nuevo. Como no puedes modificar el Job porque esos campos no son modificables, eliminas el Job old, pero dejas sus pods ejecutándose mediante el comando kubectl delete jobs/old --cascade=false. Antes de eliminarlo, apúntate el selector actual que está usando:

kind: Job
metadata:
  name: viejo
  ...
spec:
  selector:
    matchLabels:
      job-uid: a8f3d00d-c6d2-11e5-9f87-42010af00002
  ...

Entonces, creas un nuevo Job con el nombre nuevo y le configuras explícitamente el mismo selector. Puesto que los Pods existentes tienen la etiqueta job-uid=a8f3d00d-c6d2-11e5-9f87-42010af00002, son controlados por el Job nuevo igualmente.

Necesitas configurar manualSelector: true en el nuevo Job, ya qye no estás usando el selector que normalmente se genera de forma automática por el sistema.

kind: Job
metadata:
  name: nuevo
  ...
spec:
  manualSelector: true
  selector:
    matchLabels:
      job-uid: a8f3d00d-c6d2-11e5-9f87-42010af00002
  ...

El mismo Job nuevo tendrá un uid distinto a a8f3d00d-c6d2-11e5-9f87-42010af00002. Poniendo manualSelector: true le dice al sistema que sabes lo que estás haciendo y que te permita hacer este desajuste.

Alternativas

Pods simples

Cuando el nodo donde un Pod simple se estaba ejecutando se reinicia o falla, dicho pod se termina y no será reinicado. Sin embargo, un Job creará nuevos Pods para sustituir a los que se han terminando. Por esta razón, se recomienda que se use un Job en vez de un Pod simple, incluso si tu aplicación sólo necesita un único Pod.

Replication Controller

Los Jobs son complementarios a los Replication Controllers. Un Replication Controller gestiona aquellos Pods que se espera que no terminen (ej. servidores web), y un Job gestiona aquellos Pods que se espera que terminen (ej. tareas por lotes).

Como se discutió en el Ciclo de vida de un Pod, un Job sólo es apropiado para aquellos pods con RestartPolicy igual a OnFailure o Never. (Nota: Si RestartPolicy no se pone, el valor predeterminado es Always.)

Job simple arranca que arranca un controlador de Pod

Otro patrón es aquel donde un Job simple crea un Pod que, a su vez, crea otros Pods, actuando como una especie de controlador personalizado para esos Pods. Esto da la máxima flexibilidad, pero puede que cueste un poco más de entender y ofrece menos integración con Kubernetes.

Un ejemplo de este patrón sería un Job que arranca un Pod que ejecuta una secuencia de comandos que, a su vez, arranca un controlador maestro de Spark (ver el ejemplo de spark), ejecuta un manejador de spark, y a continuación lo limpia todo.

Una ventaja de este enfoque es que el proceso general obtiene la garantía del objeto Job, además del control completo de los Pods que se crean y cómo se les asigna trabajo.

Cron Jobs

Puedes utilizar un CronJob para crear un Job que se ejecute en una hora/fecha determinadas, de forma similar a la herramienta cron de Unix.

2.9 - CronJob

Un Cron Job ejecuta tareas, Jobs, a intervalos regulares.

Un objeto CronJob es como una línea de un archivo crontab (tabla cron). Ejecuta un trabajo de forma periódica según un horario programado escrito en formato Cron.

Para instrucciones sobre cómo crear y trabajar con trabajos programados, incluyendo definiciones de ejemplo, puedes consultar Ejecutar tareas automatizadas con trabajos programados.

Limitaciones de las tareas programados

Un trabajo programado crea un objeto job como mínimo una vez por cada ejecución de su programación. Decimos "como mínimo" porque hay determinadas circunstancias bajo las cuales dos trabajos pueden crearse, o ninguno de ellos se crea. Se intenta que estos casos sean residuales, pero no pueden evitarse completamente. Por lo tanto, los trabajos deberían ser idempotentes, es decir, que se pueden ejecutar más de una vez con el mismo resultado.

Si el valor de startingDeadlineSeconds se establece a un valor grande o se deja sin especificar (por defecto) y si el valor de concurrencyPolicy se establece a Allow, los trabajos siempre se ejecutarán por lo menos una vez.

Para cada CronJob, el controlador de CronJob verifica cuántas programaciones se han perdido desde la última programación hasta el momento actual. Si hay más de 100 programaciones perdidas, entonces ya no vuelve a ejecutar el trabajo y registra el error:

Cannot determine if job needs to be started. Too many missed start time (> 100). Set or decrease .spec.startingDeadlineSeconds or check clock skew.

Es importante destacar que si el campo startingDeadlineSeconds está configurado, es decir, no es nulo (nil), el controlador cuenta cuántos trabajos perdidos se produjeron desde el valor de startingDeadlineSeconds hasta el momento actual, en vez de la última programación. Por ejemplo, si startingDeadlineSeconds es 200, el controlador cuenta cuántos trabajos perdidos se produjeron en los últimos 200 segundos.

Se cuenta un CronJob como perdido si no se ha podido crear a la hora programada. Por ejemplo, si establecemos el valor de concurrencyPolicy a Forbid y se intentó programar un CronJob cuando otro previamente programado estaba todavía ejecutándose, entonces contará como perdido.

Por ejemplo, imagina que un CronJob se configura para programar un nuevo Job cada minuto a partir de las 08:30:00, y su campo startingDeadlineSeconds no se configura. Si el controlador del CronJob no estuviera disponible de 08:29:00 a 10:21:00, el trabajo no comenzaría porque el número de trabajos perdidos que se habría perdido en su programación sería superior a 100.

Para ilustrar este concepto mejor, vamos a suponer que programamos un CronJob para que ejecute un nuevo Job cada minuto comenzando a las 08:30:00, y establecemos el valor del campo startingDeadlineSeconds a 200 segundos. Si el controlador del CronJob no se encuentra disponible durante el mismo período que en el ejemplo anterior (08:29:00 a 10:21:00,) aún así el Job comenzará a las 10:22:00. Esto ocurre porque el controlador en este caso comprueba cuántas programaciones perdidas ha habido en los últimos 200 segundos (esto es, 3 programaciones que no se han ejecutado), en vez de comprobarlo a partir de la última programación hasta el momento actual.

El CronJob es únicamente responsable de crear los Jobs que coinciden con su programación, y el Job por otro lado es el responsable de gestionar los Pods que representa.