Versión imprimible multipagina. Haga click aquí para imprimir.
Controladores
- 1: ReplicaSet
- 2: ReplicationController
- 3: Deployment
- 4: StatefulSets
- 5: DaemonSet
- 6: Recolección de Basura
- 7: Controlador TTL para Recursos Finalizados
- 8: Jobs - Ejecución hasta el final
- 9: CronJob
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.
.spec.selector
, pero los campos
.spec.template.metadata.labels
y .spec.template.spec
diferentes, cada ReplicaSet
ignora los Pods creados por el otro ReplicaSet.
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
Deployment
que configura un ReplicaSet
.
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 para desplegar un ReplicaSet. El ReplicaSet crea los Pods en segundo plano. Comprueba el estado del despliegue para comprobar si es satisfactorio o no.
- Declarar el nuevo estado de los Pods actualizando el PodTemplateSpec del Deployment. Ello crea un nuevo ReplicaSet y el Deployment gestiona el cambio de los Pods del viejo ReplicaSet al nuevo de forma controlada. Cada nuevo ReplicaSet actualiza la revisión del Deployment.
- Retroceder a una revisión anterior del Deployment si el estado actual de un Deployment no es estable. Cada retroceso actualiza la revisión del Deployment.
- Escalar horizontalmente el Deployment para soportar más carga.
- Pausar el Deployment para aplicar múltiples arreglos a su PodTemplateSpec y, a continuación, reanúdalo para que comience un nuevo despliegue.
- Usar el estado del Deployment como un indicador de que el despliegue se ha atascado.
- Limpiar los viejos ReplicaSets que no necesites más.
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.Nota:matchLabels
es un mapa de entradas {clave,valor}. Una entrada simple {clave,valor} en el mapamatchLabels
es equivalente a un elemento dematchExpressions
cuyo campo sea la "clave", el operador sea "In", y la matriz de valores contenga únicamente un "valor". Todos los requisitos se concatenan con AND. -
El campo
template
contiene los siguientes sub-campos:- Los Pods se etiquetan como
app: nginx
usando el campolabels
. - 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 denginx
de Docker Hub. - Crea un contenedor y lo llamar
nginx
usando el camponame
. - Ejecuta la imagen
nginx
en su versión1.7.9
. - Abre el puerto
80
para que el contenedor pueda enviar y recibir tráfico.
- Los Pods se etiquetan como
Para crear este Deployment, ejecuta el siguiente comando:
kubectl apply -f https://k8s.io/examples/controllers/nginx-deployment.yaml
--record
para registrar el comando ejecutado en la anotación de recurso kubernetes.io/change-cause
.
Esto es útil para futuras introspecciones, por ejemplo para comprobar qué comando se ha ejecutado en cada revisión del Deployment.
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éplicaslistas/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=3123191453
nginx-deployment-75675f5897-kzszj 1/1 Running 0 18s app=nginx,pod-template-hash=3123191453
nginx-deployment-75675f5897-qqcnn 1/1 Running 0 18s app=nginx,pod-template-hash=3123191453
El ReplicaSet creado garantiza que hay tres Pods de nginx
ejecutándose en todo momento.
app: nginx
). No entremezcles etiquetas o selectores con otros controladores (incluyendo otros Deployments y StatefulSets).
Kubernetes no te impide que lo hagas, pero en el caso de que múltiples controladores tengan selectores mezclados, dichos controladores pueden entrar en conflicto y provocar resultados inesperados.
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
.spec.template
)
se cambia, por ejemplo si se actualiza las etiquetas o las imágenes de contenedor de la plantilla.
Otras actualizaciones, como el escalado del Deployment, no conllevan un lanzamiento de despliegue.
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.
apps/v1
de la API, el selector de etiquetas del Deployment es inmutable una vez se ha creado.
- 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).
.spec.template
) se cambia;
por ejemplo, si cambias las etiquetas o la imagen del contenedor de la plantilla.
Otras actualizaciones, como escalar el Deployment,
no generan una nueva revisión del Deployment, para poder facilitar el escalado manual simultáneo - o auto-escalado.
Esto significa que cuando reviertes a una versión anterior, sólo la parte de la plantilla Pod del Deployment se revierte.
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
maxUnavailable
específicamente) que hayas configurado.
Kubernetes por defecto establece el valor en el 25%.
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 comandokubectl
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.
Reason=ProgressDeadlineExceeded
. Los orquestradores de alto nivel pueden aprovecharse y actuar consecuentemente, por ejemplo,
retrocediendo el Deployment a su versión previa.
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: k8s.gcr.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} |
cluster.local
a menos que
se configure de otra forma.
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
- Sigue el ejemplo de cómo desplegar un aplicación con estado.
- Sigue el ejemplo de cómo desplegar Cassandra con StatefulSets.
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)
Kubernetes v1.25 [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 estadoPending
. 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
...
No se recomienda el uso de OwnerReferences entre Namespaces por diseño. Esto quiere decir que:
- Los subordinados dentro del ámbito de Namespaces sólo pueden definir propietarios en ese mismo Namespace, y propietarios dentro del ámbito de clúster.
- Los subordinados dentro del ámbito del clúster sólo pueden definir propietarios dentro del ámbito del clúster, pero no propietarios dentro del ámbito de Namespaces.
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
7 - Controlador TTL para Recursos Finalizados
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
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:
- 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.
- 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
.
- 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
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 spec
idé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.
horarios
CronJob se basan en la zona horaria del máster donde se inicia el trabajo.
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.