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 - 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.

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.

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 Domain Service (ns/nombre) StatefulSet (ns/nombre) StatefulSet Domain Pod DNS Pod Hostname
cluster.local default/nginx default/web nginx.default.svc.cluster.local web-{0..N-1}.nginx.default.svc.cluster.local web-{0..N-1}
cluster.local foo/nginx foo/web nginx.foo.svc.cluster.local web-{0..N-1}.nginx.foo.svc.cluster.local web-{0..N-1}
kube.local foo/nginx foo/web nginx.foo.svc.kube.local web-{0..N-1}.nginx.foo.svc.kube.local web-{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

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 tolerancia Efecto Versión Descripción
node.kubernetes.io/not-ready NoExecute 1.13+ Los pods del DaemonSet no son expulsados cuando hay problemas de nodo como una partición de red.
node.kubernetes.io/unreachable NoExecute 1.13+ Los pods del DaemonSet no son expulsados cuando hay problemas de nodo como una partición de red.
node.kubernetes.io/disk-pressure NoSchedule 1.8+ Los pods del DaemonSet no son expulsados cuando hay problemas de nodo como la falta de espacio en disco.
node.kubernetes.io/memory-pressure NoSchedule 1.8+ Los pods del DaemonSet no son expulsados cuando hay problemas de nodo como la falta de memoria.
node.kubernetes.io/unschedulable NoSchedule 1.12+ Los pods del DaemonSet toleran los atributos unschedulable del planificador por defecto.
node.kubernetes.io/network-unavailable NoSchedule 1.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.

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

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

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ón Objeto 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 Trabajo a 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 Job 1 debería ser 1
Cola con Pod por Ítem de Trabajo T cualquiera
Cola con Cuenta Variable de Pods 1 cualquiera
Job simple con Asignación Estática de Trabajo T cualquiera

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.

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.