Versión imprimible multipagina. Haga click aquí para imprimir.
Cargas de trabajo
- 1: Pods
- 1.1: Pods
- 1.2: Contenedores de Inicialización
- 1.3: Interrupciones
- 1.4: Containers Efímeros
- 2: Controladores
- 2.1: ReplicaSet
- 2.2: ReplicationController
- 2.3: Deployment
- 2.4: StatefulSets
- 2.5: DaemonSet
- 2.6: Recolección de Basura
- 2.7: Controlador TTL para Recursos Finalizados
- 2.8: Jobs - Ejecución hasta el final
- 2.9: CronJob
1 - Pods
1.1 - Pods
Los Pods son las unidades de computación desplegables más pequeñas que se pueden crear y gestionar en Kubernetes.
¿Qué és un Pod?
Un Pod (como en una vaina de ballenas o vaina de guisantes) es un grupo de uno o más contenedores (como contenedores Docker), con almacenamiento/red compartidos, y unas especificaciones de cómo ejecutar los contenedores. Los contenidos de un Pod son siempre coubicados, coprogramados y ejecutados en un contexto compartido. Un Pod modela un "host lógico" específico de la aplicación: contiene uno o más contenedores de aplicaciones relativamente entrelazados. Antes de la llegada de los contenedores, ejecutarse en la misma máquina física o virtual significaba ser ejecutado en el mismo host lógico.
Mientras que Kubernetes soporta más runtimes de contenedores a parte de Docker, este último es el más conocido y ayuda a describir Pods en términos de Docker.
El contexto compartido de un Pod es un conjunto de namespaces de Linux, cgroups y, potencialmente, otras facetas de aislamiento, las mismas cosas que aíslan un contenedor Docker. Dentro del contexto de un Pod, las aplicaciones individuales pueden tener más subaislamientos aplicados.
Los contenedores dentro de un Pod comparten dirección IP y puerto, y pueden encontrarse a través de localhost
. También pueden comunicarse entre sí mediante comunicaciones estándar entre procesos, como semáforos de SystemV o la memoria compartida POSIX. Los contenedores en diferentes Pods tienen direcciones IP distintas y no pueden comunicarse por IPC sin configuración especial.
Estos contenedores normalmente se comunican entre sí a través de las direcciones IP del Pod.
Las aplicaciones dentro de un Pod también tienen acceso a volúmenes compartidos, que se definen como parte de un Pod y están disponibles para ser montados en el sistema de archivos de cada aplicación.
En términos de Docker, un Pod se modela como un grupo de contenedores de Docker con namespaces y volúmenes de sistemas de archivos compartidos.
Al igual que los contenedores de aplicaciones individuales, los Pods se consideran entidades relativamente efímeras (en lugar de duraderas). Como se explica en ciclo de vida del pod, los Pods se crean, se les asigna un identificador único (UID) y se planifican en nodos donde permanecen hasta su finalización (según la política de reinicio) o supresión. Si un nodo muere, los Pods programados para ese nodo se programan para su eliminación después de un período de tiempo de espera. Un Pod dado (definido por su UID) no se "replanifica" a un nuevo nodo; en su lugar, puede reemplazarse por un Pod idéntico, con incluso el mismo nombre si lo desea, pero con un nuevo UID (consulte controlador de replicación para obtener más detalles).
Cuando se dice que algo tiene la misma vida útil que un Pod, como un volumen, significa que existe mientras exista ese Pod (con ese UID). Si ese Pod se elimina por cualquier motivo, incluso si se crea un reemplazo idéntico, el recurso relacionado (por ejemplo, el volumen) también se destruye y se crea de nuevo.
Un Pod de múltiples contenedores que contiene un extractor de archivos y un servidor web que utiliza un volumen persistente para el almacenamiento compartido entre los contenedores.
Motivación para los Pods
Gestión
Los Pods son un modelo del patrón de múltiples procesos de cooperación que forman una unidad de servicio cohesiva. Simplifican la implementación y la administración de las aplicaciones proporcionando una abstracción de mayor nivel que el conjunto de las aplicaciones que lo constituyen. Los Pods sirven como unidad de despliegue, escalado horizontal y replicación. La colocación (coprogramación), el destino compartido (por ejemplo, la finalización), la replicación coordinada, el uso compartido de recursos y la gestión de dependencias se controlan automáticamente para los contenedores en un Pod.
Recursos compartidos y comunicación
Los Pods permiten el intercambio de datos y la comunicación entre los contenedores que lo constituyen.
Todas las aplicaciones en un Pod utilizan el mismo namespace de red (la misma IP y puerto) y, por lo tanto, pueden "encontrarse" entre sí y comunicarse utilizando localhost
.
Debido a esto, las aplicaciones en un Pod deben coordinar su uso de puertos. Cada Pod tiene una dirección IP en un espacio de red compartido que tiene comunicación completa con otros servidores físicos y Pods a través de la red.
Los contenedores dentro del Pod ven que el hostname del sistema es el mismo que el nombre
configurado para el Pod. Hay más información sobre esto en la sección networking.
Además de definir los contenedores de aplicaciones que se ejecutan en el Pod, el Pod especifica un conjunto de volúmenes de almacenamiento compartido. Los volúmenes permiten que los datos sobrevivan a reinicios de contenedores y se compartan entre las aplicaciones dentro del Pod.
Usos de Pods
Los Pods pueden ser usados para alojar pilas de aplicaciones integradas (por ejemplo, LAMP), pero su objetivo principal es apoyar los programas de ayuda coubicados y coadministrados, como:
- sistemas de gestión de contenido, loaders de datos y archivos, gestores de caché locales, etc.
- copia de seguridad de registro y punto de control, compresión, rotación, captura de imágenes, etc.
- observadores de cambio de datos, adaptadores de registro y monitoreo, publicadores de eventos, etc.
- proxies, bridges y adaptadores.
- controladores, configuradores y actualizadores.
Los Pods individuales no están diseñados para ejecutar varias instancias de la misma aplicación, en general.
Para una explicación más detallada, ver El sistema distribuido ToolKit: Patrones para Contenedores multiaplicación.
Alternativas
¿Por qué simplemente no ejecutar múltiples programas en un solo contenedor de Docker?
- Transparencia. Hacer visibles los contenedores dentro del Pod a la infraestructura permite que esta brinde servicios, como gestión de procesos y monitoreo de recursos, a los contenedores, facilitando una serie de comodidades a los usuarios.
- Desacople de dependencias de software. Los contenedores individuales pueden ser versionados, reconstruidos y redistribuidos independientemente. Kubernetes podría incluso apoyar actualizaciones en vivo de contenedores individuales en un futuro.
- Facilidad de uso. Los usuarios no necesitan ejecutar sus propios administradores de procesos, para propagación de señales, códigos de salida, etc.
- Eficiencia. Debido a que la infraestructura asume más responsabilidad, los contenedores pueden ser más livianos.
¿Por qué no admitir la planificación conjunta de contenedores por afinidad?
Ese enfoque proporcionaría la ubicación conjunta, pero no la mayor parte de beneficios de los Pods, como compartir recursos, IPC, compartir el destino garantizado y gestión simplificada.
Durabilidad de pods (o su ausencia)
Los Pods no están destinados a ser tratados como entidades duraderas. No sobrevivirán a errores de planificación, caídas de nodo u otros desalojos, ya sea por falta de recursos o en el caso de mantenimiento de nodos.
En general, los usuarios no deberían necesitar crear Pods directamente, deberían usar siempre controladores incluso para Pods individuales, como por ejemplo, los Deployments. Los controladores proporcionan autorecuperación con un alcance de clúster, así como replicación y gestión de despliegue. Otros controladores como los StatefulSet pueden tambien proporcionar soporte para Pods que necesiten persistir el estado.
El uso de API colectivas como la principal primitiva de cara al usuario es relativamente común entre los sistemas de planificación de clúster, incluyendo Borg, Marathon, Aurora, y Tupperware.
El Pod se expone como primitiva para facilitar:
- planificación y capacidad de conexión del controlador
- soporte para operaciones a nivel de Pod sin la necesidad de "proxy" a través de las API del controlador
- desacople de la vida útil del Pod de la vida útil del controlador, como para el arranque
- desacople de controladores y servicios, el endpoint del controlador solo mira Pods
- composición limpia de funcionalidad a nivel de Kubelet con funcionalidad a nivel de clúster, Kubelet es efectivamente el "controlador de Pod"
- aplicaciones en alta disponibilidad, que esperan que los Pods sean reemplazados antes de su finalización y ciertamente antes de su eliminación, como en el caso de desalojos planificados o descarga previa de imágenes.
Finalización de Pods
Debido a que los Pods representan procesos en ejecución en los nodos del clúster, es importante permitir que esos procesos finalicen de forma correcta cuando ya no se necesiten (en lugar de ser detenidos bruscamente con una señal de KILL). Los usuarios deben poder solicitar la eliminación y saber cuándo finalizan los procesos, pero también deben poder asegurarse de que las eliminaciones finalmente se completen. Cuando un usuario solicita la eliminación de un Pod, el sistema registra el período de gracia previsto antes de que el Pod pueda ser eliminado de forma forzada, y se envía una señal TERM al proceso principal en cada contenedor. Una vez que el período de gracia ha expirado, la señal KILL se envía a esos procesos y el Pod se elimina del servidor API. Si se reinicia Kubelet o el administrador de contenedores mientras se espera que finalicen los procesos, la terminación se volverá a intentar con el período de gracia completo.
Un ejemplo del ciclo de terminación de un Pod:
- El usuario envía un comando para eliminar Pod, con un período de gracia predeterminado (30s)
- El Pod en el servidor API se actualiza con el tiempo a partir del cual el Pod se considera "muerto" junto con el período de gracia.
- El Pod aparece como "Terminando" cuando aparece en los comandos del cliente
- (simultáneo con 3) Cuando el Kubelet ve que un Pod se ha marcado como terminado porque se ha configurado el tiempo en 2, comienza el proceso de apagado del Pod.
- Si uno de los contenedores del Pod ha definido un preStop hook, se invoca dentro del contenedor. Si el hook
preStop
todavía se está ejecutando después de que expire el período de gracia, el paso 2 se invoca con un pequeño período de gracia extendido (2s). - El contenedor recibe la señal TERM. Tenga en cuenta que no todos los contenedores en el Pod recibirán la señal TERM al mismo tiempo y cada uno puede requerir un hook
preStop
si el orden en el que se cierra es importante.
- Si uno de los contenedores del Pod ha definido un preStop hook, se invoca dentro del contenedor. Si el hook
- (simultáneo con 3) Pod se elimina de la lista de endponts del servicio, y ya no se considera parte del conjunto de Pods en ejecución para controladores de replicación. Los Pods que se apagan lentamente no pueden continuar sirviendo el tráfico ya que los balanceadores de carga (como el proxy de servicio) los eliminan de sus rotaciones.
- Cuando expira el período de gracia, todos los procesos que todavía se ejecutan en el Pod se eliminan con SIGKILL.
- El Kubelet terminará de eliminar el Pod en el servidor API configurando el período de gracia 0 (eliminación inmediata). El Pod desaparece de la API y ya no es visible desde el cliente.
Por defecto, todas las eliminaciones se realizan correctamente en 30 segundos. El comando kubectl delete
admite la opción--grace-period = <seconds>
que permite al usuario anular el valor predeterminado y especificar su propio valor. El valor 0
forzar eliminación del Pod.
Debe especificar un indicador adicional --force
junto con --grace-period = 0
para realizar eliminaciones forzadas.
Forzar destrucción de Pods
La eliminación forzada de un Pod se define como la eliminación de un Pod del estado del clúster y etcd inmediatamente. Cuando se realiza una eliminación forzada, el apiserver no espera la confirmación del kubelet de que el Pod ha finalizado en el nodo en el que se estaba ejecutando. Elimina el Pod en la API inmediatamente para que se pueda crear un nuevo Pod con el mismo nombre. En el nodo, los Pods que están configurados para terminar de inmediato recibirán un pequeño período de gracia antes de ser forzadas a matar.
Estas eliminaciones pueden ser potencialmente peligrosas para algunos Pods y deben realizarse con precaución. En el caso de Pods de StatefulSets, consulte la documentación de la tarea para eliminando Pods de un StatefulSet.
Modo privilegiado para Pods
Cualquier contenedor en un Pod puede habilitar el modo privilegiado, utilizando el indicador privilegiado
en el contexto de seguridad de la especificación del contenedor. Esto es útil para contenedores que desean usar capacidades de Linux como manipular la pila de red y acceder a dispositivos. Los procesos dentro del contenedor obtienen casi los mismos privilegios que están disponibles para los procesos fuera de un contenedor. Con el modo privilegiado, debería ser más fácil escribir complementos de red y volumen como Pods separados que no necesitan compilarse en el kubelet.
API
Pod es un recurso de nivel superior en la API REST de Kubernetes. La definición de objeto de API Pod describe el objeto en detalle.
1.2 - Contenedores de Inicialización
Esta página proporciona una descripción general de los contenedores de inicialización (init containers): contenedores especializados que se ejecutan antes de los contenedores de aplicación en un Pod. Los contenedores de inicialización pueden contener utilidades o scripts de instalación no presentes en una imagen de aplicación.
Tú puedes especificar contenedores de inicialización en la especificación del Pod junto con el arreglo de containers
(el cual describe los contenedores de aplicación).
Entendiendo los contenedores de inicialización
Un Pod puede tener múltiples contenedores ejecutando aplicaciones dentro de él, pero también puede tener uno o más contenedores de inicialización que se ejecutan antes de que se inicien los contenedores de aplicación.
Los contenedores de inicialización son exactamente iguales a los contenedores regulares excepto por:
- Los contenedores de inicialización siempre se ejecutan hasta su finalización.
- Cada contenedor de inicialiación debe completarse correctamente antes de que comience el siguiente.
Si el contenedor de inicialización de un Pod falla, kubelet reinicia repetidamente ese contenedor de inicialización hasta que tenga éxito.
Sin embargo, si el Pod tiene una restartPolicy
de Never
y un contenedor de inicialización falla durante el inicio de ese Pod, Kubernetes trata al Pod en general como fallido.
Para especificar un contenedor de inicialización para un Pod, agrega el campo initContainers
en
la especificación del Pod,
como un arreglo de elementos container
(similar al campo containers
de aplicación y su contenido).
Consulta Container en la
referencia de API para más detalles.
El estado de los contenedores de inicialización se devuelve en el campo .status.initContainerStatuses
como un arreglo de los estados del contenedor (similar al campo .status.containerStatuses
).
Diferencias con los contenedores regulares
Los contenedores de inicialización admiten todos los campos y características de los contenedores de aplicaciones, incluidos los límites de recursos, los volúmenes y la configuración de seguridad. Sin embargo, las solicitudes de recursos y los límites para un contenedor de inicialización se manejan de manera diferente, como se documenta en Recursos.
Además, los contenedores de inicialización no admiten lifecycle
, livenessProbe
, readinessProbe
o
startupProbe
porque deben de ejecutarse hasta su finalización antes de que el Pod pueda estar listo.
Si especificas varios contenedores de inicialización para un Pod, kubelet ejecuta cada contenedor de inicialización secuencialmente. Cada contenedor de inicialización debe tener éxito antes de que se pueda ejecutar el siguiente. Cuando todos los contenedores de inicialización se hayan ejecutado hasta su finalización, kubelet inicializa los contenedores de aplicación para el Pod y los ejecuta como de costumbre.
Usando contenedores de inicialización
Dado que los contenedores de inicialización tienen imágenes separadas de los contenedores de aplicaciones, estos tienen algunas ventajas sobre el código relacionado de inicio:
- Los contenedores de inicialización pueden contener utilidades o código personalizado para la configuración que no están presentes en una
imagen de aplicación. Por ejemplo, no hay necesidad de hacer una imagen
FROM
de otra imagen solo para usar una herramienta comosed
,awk
,python
odig
durante la instalación. - Los roles de constructor e implementador de imágenes de aplicación pueden funcionar de forma independiente sin la necesidad de construir conjuntamente una sola imagen de aplicación.
- Los contenedores de inicialización pueden ejecutarse con una vista diferente al sistema de archivos que los contenedores de aplicaciones en el mismo Pod. En consecuencia, se les puede dar acceso a Secrets a los que los contenedores de aplicaciones no pueden acceder.
- Debido a que los contenedores de inicialización se ejecutan hasta su finalización antes de que se inicien los contenedores de aplicaciones, los contenedores de inicialización ofrecen un mecanismo para bloquear o retrasar el inicio del contenedor de aplicación hasta que se cumplan una serie de condiciones previas. Una vez que las condiciones previas se cumplen, todos los contenedores de aplicaciones de un Pod pueden iniciarse en paralelo.
- Los contenedores de inicialización pueden ejecutar de forma segura utilidades o código personalizado que de otro modo harían a una imagen de aplicación de contenedor menos segura. Si mantiene separadas herramientas innecesarias, puede limitar la superficie de ataque a la imagen del contenedor de aplicación.
Ejemplos
A continuación, se muestran algunas ideas sobre cómo utilizar los contenedores de inicialización:
-
Esperar a que se cree un Service usando una sola linea de comando de shell:
for i in {1..100}; do sleep 1; if nslookup myservice; then exit 0; fi; done; exit 1
-
Registrar este Pod con un servidor remoto desde la downward API con un comando como:
curl -X POST http://$MANAGEMENT_SERVICE_HOST:$MANAGEMENT_SERVICE_PORT/register -d 'instance=$(<POD_NAME>)&ip=$(<POD_IP>)'
-
Esperar algo de tiempo antes de iniciar el contenedor de aplicación con un comando como:
sleep 60
-
Clonar un repositorio de Git en un Volume
-
Colocar valores en un archivo de configuración y ejecutar una herramienta de plantilla para generar dinámicamente un archivo de configuración para el contenedor de aplicación principal. Por ejemplo, colocar el valor
POD_IP
en una configuración y generar el archivo de configuración de la aplicación principal usando Jinja.
Contenedores de inicialización en uso
Este ejemplo define un simple Pod que tiene dos contenedores de inicialización.
El primero espera por myservice
y el segundo espera por mydb
. Una vez que ambos
contenedores de inicialización se completen, el Pod ejecuta el contenedor de aplicación desde su sección spec
.
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
app.kubernetes.io/name: MyApp
spec:
containers:
- name: myapp-container
image: busybox:1.28
command: ['sh', '-c', 'echo ¡La aplicación se está ejecutando! && sleep 3600']
initContainers:
- name: init-myservice
image: busybox:1.28
command: ['sh', '-c', "until nslookup myservice.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo esperando a myservice; sleep 2; done"]
- name: init-mydb
image: busybox:1.28
command: ['sh', '-c', "until nslookup mydb.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo esperando a mydb; sleep 2; done"]
Puedes iniciar este Pod ejecutando:
kubectl apply -f myapp.yaml
El resultado es similar a esto:
pod/myapp-pod created
Y verificar su estado con:
kubectl get -f myapp.yaml
El resultado es similar a esto:
NAME READY STATUS RESTARTS AGE
myapp-pod 0/1 Init:0/2 0 6m
o para más detalles:
kubectl describe -f myapp.yaml
El resultado es similar a esto:
Name: myapp-pod
Namespace: default
[...]
Labels: app.kubernetes.io/name=MyApp
Status: Pending
[...]
Init Containers:
init-myservice:
[...]
State: Running
[...]
init-mydb:
[...]
State: Waiting
Reason: PodInitializing
Ready: False
[...]
Containers:
myapp-container:
[...]
State: Waiting
Reason: PodInitializing
Ready: False
[...]
Events:
FirstSeen LastSeen Count From SubObjectPath Type Reason Message
--------- -------- ----- ---- ------------- -------- ------ -------
16s 16s 1 {default-scheduler } Normal Scheduled Successfully assigned myapp-pod to 172.17.4.201
16s 16s 1 {kubelet 172.17.4.201} spec.initContainers{init-myservice} Normal Pulling pulling image "busybox"
13s 13s 1 {kubelet 172.17.4.201} spec.initContainers{init-myservice} Normal Pulled Successfully pulled image "busybox"
13s 13s 1 {kubelet 172.17.4.201} spec.initContainers{init-myservice} Normal Created Created container with docker id 5ced34a04634; Security:[seccomp=unconfined]
13s 13s 1 {kubelet 172.17.4.201} spec.initContainers{init-myservice} Normal Started Started container with docker id 5ced34a04634
Para ver los logs de los contenedores de inicialización en este Pod ejecuta:
kubectl logs myapp-pod -c init-myservice # Inspecciona el primer contenedor de inicialización
kubectl logs myapp-pod -c init-mydb # Inspecciona el segundo contenedor de inicialización
En este punto, estos contenedores de inicialización estarán esperando para descubrir los Servicios denominados
mydb
y myservice
.
Aquí hay una configuración que puedes usar para que aparezcan esos Servicios:
---
apiVersion: v1
kind: Service
metadata:
name: myservice
spec:
ports:
- protocol: TCP
port: 80
targetPort: 9376
---
apiVersion: v1
kind: Service
metadata:
name: mydb
spec:
ports:
- protocol: TCP
port: 80
targetPort: 9377
Para crear los servicios de mydb
y myservice
:
kubectl apply -f services.yaml
El resultado es similar a esto:
service/myservice created
service/mydb created
Luego verás que esos contenedores de inicialización se completan y que el Pod myapp-pod
pasa al estado Running
:
kubectl get -f myapp.yaml
El resultado es similar a esto:
NAME READY STATUS RESTARTS AGE
myapp-pod 1/1 Running 0 9m
Este sencillo ejemplo debería servirte de inspiración para crear tus propios contenedores de inicialización. ¿Qué es lo que sigue? contiene un enlace a un ejemplo más detallado.
Comportamiento detallado
Durante el inicio del Pod, kubelet retrasa la ejecución de contenedores de inicialización hasta que la red y el almacenamiento estén listos. Después, kubelet ejecuta los contenedores de inicialización del Pod en el orden que aparecen en la especificación del Pod.
Cada contenedor de inicialización debe salir correctamente antes de que
comience el siguiente contenedor. Si un contenedor falla en iniciar debido al tiempo de ejecución o
sale con una falla, se vuelve a intentar de acuerdo con el restartPolicy
del Pod. Sin embargo,
si el restartPolicy
del Pod se establece en Always
, los contenedores de inicialización usan
el restartPolicy
como OnFailure
.
Un Pod no puede estar Ready
sino hasta que todos los contenedores de inicialización hayan tenido éxito. Los puertos en un
contenedor de inicialización no se agregan a un Servicio. Un Pod que se está inicializando,
está en el estado de Pending
, pero debe tener una condición Initialized
configurada como falsa.
Si el Pod se reinicia o es reiniciado, todos los contenedores de inicialización deben ejecutarse de nuevo.
Los cambios en la especificación del contenedor de inicialización se limitan al campo de la imagen del contenedor. Alterar un campo de la imagen del contenedor de inicialización equivale a reiniciar el Pod.
Debido a que los contenedores de inicialización se pueden reiniciar, reintentar o volverse a ejecutar, el código del contenedor de inicialización
debe ser idempotente. En particular, el código que escribe en archivos en EmptyDirs
debe estar preparado para la posibilidad de que ya exista un archivo de salida.
Los contenedores de inicialización tienen todos los campos de un contenedor de aplicaciones. Sin embargo, Kubernetes
prohíbe el uso de readinessProbe
porque los contenedores de inicialización no pueden
definir el readiness
distinto de la finalización. Esto se aplica durante la validación.
Usa activeDeadlineSeconds
en el Pod para prevenir que los contenedores de inicialización fallen por siempre.
La fecha límite incluye contenedores de inicialización.
Sin embargo, se recomienda utilizar activeDeadlineSeconds
si el usuario implementa su aplicación
como un Job
porque activeDeadlineSeconds
tiene un efecto incluso después de que initContainer
finaliza.
El Pod que ya se está ejecutando correctamente sería eliminado por activeDeadlineSeconds
si lo estableces.
El nombre de cada aplicación y contenedor de inicialización en un Pod debe ser único; un error de validación es arrojado para cualquier contenedor que comparta un nombre con otro.
Recursos
Dado el orden y la ejecución de los contenedores de inicialización, las siguientes reglas para el uso de recursos se aplican:
- La solicitud más alta de cualquier recurso o límite particular definido en todos los contenedores de inicialización es la solicitud/límite de inicialización efectiva. Si algún recurso no tiene un límite de recursos especificado éste se considera como el límite más alto.
- La solicitud/límite efectiva para un recurso es la más alta entre:
- la suma de todas las solicitudes/límites de los contenedores de aplicación, y
- la solicitud/límite de inicialización efectiva para un recurso
- La planificación es hecha con base en las solicitudes/límites efectivos, lo que significa que los contenedores de inicialización pueden reservar recursos para la inicialización que no se utilizan durante la vida del Pod.
- El nivel de
QoS
(calidad de servicio) del nivel deQoS
efectivo del Pod es el nivel deQoS
tanto para los contenedores de inicialización como para los contenedores de aplicación.
La cuota y los límites son aplicados con base en la solicitud y límite efectivos de Pod.
Los grupos de control de nivel de Pod (cgroups) se basan en la solicitud y el límite de Pod efectivos, al igual que el planificador de Kubernetes (kube-scheduler).
Razones de reinicio del Pod
Un Pod puede reiniciarse, provocando la re-ejecución de los contenedores de inicialización por las siguientes razones:
- Se reinicia el contenedor de infraestructura del Pod. Esto es poco común y debería hacerlo alguien con acceso de root a los nodos.
- Todos los contenedores en un Pod son terminados mientras
restartPolicy
esté configurado enAlways
, forzando un reinicio y el registro de finalización del contenedor de inicialización se ha perdido debido a la recolección de basura.
El Pod no se reiniciará cuando se cambie la imagen del contenedor de inicialización o cuando se pierda el registro de finalización del contenedor de inicialización debido a la recolección de basura. Esto se aplica a Kubernetes v1.20 y posteriores. Si estás utilizando una versión anterior de Kubernetes, consulta la documentación de la versión que estás utilizando.
Siguientes pasos
- Lee acerca de creando un Pod que tiene un contenedor de inicialización
- Aprende cómo depurar contenedores de inicialización
1.3 - Interrupciones
Esta guía es para los dueños de aplicaciones que quieren crear aplicaciones con alta disponibilidad y que necesitan entender qué tipos de interrupciones pueden suceder en los Pods.
También es para los administradores de clústeres que quieren aplicar acciones automatizadas en sus clústeres, como actualizar o autoescalar los clústeres.
Interrupciones voluntarias e involuntarias
Los Pods no desaparecen hasta que algo (una persona o un controlador) los destruye o hay problemas de hardware o software que son inevitables.
Nosotros llamamos a esos casos inevitables interrupciones involuntarias de una aplicación. Algunos ejemplos:
- Una falla en el hardware de la máquina física del nodo.
- Un administrador del clúster borra una VM (instancia) por error.
- El proveedor de la nube o el hipervisor falla y hace desaparecer la VM.
- Un kernel panic.
- El nodo desaparece del clúster por un problema de red que lo separa del clúster.
- Una remoción del Pod porque el nodo no tiene recursos suficientes.
A excepción de la condición sin recursos suficientes, todas estas condiciones deben ser familiares para la mayoría de los usuarios, no son específicas de Kubernetes.
Nosotros llamamos a los otros casos interrupciones voluntarias. Estas incluyen las acciones iniciadas por el dueño de la aplicación y aquellas iniciadas por el Administrador del Clúster. Las acciones típicas de los dueños de la aplicación incluyen:
- borrar el Deployment u otro controlador que maneja el Pod
- actualizar el Deployment del Pod que causa un reinicio
- borrar un Pod (por ejemplo, por accidente)
Las acciones del administrador del clúster incluyen:
- Drenar un nodo para reparar o actualizar.
- Drenar un nodo del clúster para reducir el clúster (aprenda acerca de Autoescalamiento de Clúster).
- Remover un Pod de un nodo para permitir que otra cosa pueda ingresar a ese nodo.
Estas acciones pueden ser realizadas directamente por el administrador del clúster, por tareas automatizadas del administrador del clúster o por el proveedor del clúster.
Consulte al administrador de su clúster, a su proveedor de la nube o a la documentación de su distribución para determinar si alguna de estas interrupciones voluntarias está habilitada en su clúster. Si ninguna se encuentra habilitada, puede omitir la creación del presupuesto de Interrupción de Pods.
Tratando con las interrupciones
Estas son algunas de las maneras para mitigar las interrupciones involuntarias:
- Asegurarse que el Pod solicite los recursos que necesita.
- Replique su aplicación si usted necesita alta disponibilidad. (Aprenda sobre correr aplicaciones replicadas stateless y stateful
- Incluso, para una alta disponibilidad mayor cuando se corren aplicaciones replicadas, propague las aplicaciones por varios racks (usando anti-affinity) o usando zonas (si usa un clúster multi-zona.)
La frecuencia de las interrupciones voluntarias varía. En un clúster basico de Kubernetes, no hay interrupciones voluntarias automáticas (solo el usuario las genera). Sin embargo, su administrador del clúster o proveedor de alojamiento puede correr algun servicio adicional que pueda causar estas interrupciones voluntarias. Por ejemplo, desplegando una actualización de software en los nodos puede causar interrupciones. También, algunas implementaciones de clústers con autoescalamiento de nodos puede causar interrupciones para defragmentar o compactar los nodos. Su administrador de clúster o proveedor de alojamiento debe tener documentado cuál es el nivel de interrupciones voluntarias esperadas, sí es que las hay. Ciertas opciones de configuración, como ser usar PriorityClasses en las especificaciones de su Pod pueden también causar interrupciones voluntarias (o involuntarias).
Presupuesto de Interrupción de Pods
Kubernetes v1.21 [stable]
Kubernetes ofrece carácteristicas para ayudar a ejecutar aplicaciones con alta disponibliidad, incluso cuando usted introduce interrupciones voluntarias frecuentes.
Como dueño de la aplicación, usted puede crear un presupuesto de interrupción de Pods (PDB por sus siglas en inglés) para cada aplicación. Un PDB limita el numero de Pods de una aplicación replicada, que estan caídos de manera simultánea por interrupciones voluntarias. Por ejemplo, una aplicación basada en quórum puede asegurarse que el número de réplicas corriendo nunca es menor al número necesitado para obtener el quórum. Una web de tipo front end puede querer asegurarse que el número de réplicas atendiendo al tráfico nunca puede caer bajo un cierto porcentaje del total.
Los administradores del clúster y proveedores de hosting pueden usar herramientas que respeten el presupuesto de interrupción de Pods utilizando la API de Desalojo en vez de directamente borrar Pods o Deployments.
Por ejemplo, el subcomando kubectl drain
le permite marcar un nodo a un modo fuera de
servicio. Cuando se ejecuta kubectl drain
, la herramienta trata de quitar a todos los Pods en
el nodo que se esta dejando fuera de servicio. La petición de desalojo que kubectl
solicita en
su nombre puede ser temporalmente denegado, entonces la herramienta periodicamente reintenta todas las
peticiones fallidas hasta que todos los Pods en el nodo afectado son terminados o hasta que el tiempo de espera,
que puede ser configurado, es alcanzado.
Un PDB especifica el número de réplicas que una aplicación puede tolerar, relativo a cuantas
se pretende tener. Por ejemplo, un Deployment que tiene un .spec.replicas: 5
se
supone que tiene 5 Pods en cualquier momento. Si su PDB permite tener 4 a la vez,
entonces la API de Desalojo va a permitir interrupciones voluntarias de uno (pero no de dos) Pod a la vez.
El grupo de Pods que comprende a la aplicación está especificado usando una etiqueta selectora, la misma que es usada por el controlador de aplicación (deployment, stateful-set, etc).
El número de Pods "deseado" es calculado a partir de .spec.replicas
del recurso de Workload
que es manejado para esos Pods. El plano de control descubre el recurso Workload perteneciente al
examinar las .metadata.ownerReferences
del Pod.
Las Interrupciones Involuntarias no pueden ser prevenidas por los PDB; pero si son contabilizadas a partir de este presupuesto.
Los Pods que son borrados o no están disponibles debido a una actualización continua de una aplicación forman parte del presupuesto de interrupciones, pero los recursos Workload (como los Deployments y StatefulSet) no están limitados por los PDBs cuando se hacen actualizaciones continuas. En cambio, la administración de fallas durante la actualización de la aplicación está configurada en la especificación para este recurso Workload específico.
Cuando un Pod es eliminado usando la API de desalojo, este es
terminado correctamente, haciendo honor al
terminationGracePeriodSeconds
configurado en su PodSpec.
Ejemplo de Presupuesto de Interrupción de POD
Considere un clúster con 3 nodos, nodo-1
hasta nodo-3
.
El clúster está ejecutando varias aplicaciones. Uno de ellos tiene 3 replicas, que llamaremos
pod-a
, pod-b
, y pod-c
. Otro Pod no relacionado y sin PDB, llamado pod-x
, también se muestra.
Inicialmente los Pods están distribuidos de esta manera:
nodo-1 | nodo-2 | nodo-3 |
---|---|---|
pod-a available | pod-b available | pod-c available |
pod-x available |
Los 3 Pods son parte de un Deployment, ellos colectivamente tienen un PDB que requiere que por lo menos 2 de los 3 Pods estén disponibles en todo momento.
Por ejemplo, supongamos que el administrador del clúster quiere reiniciar para actualizar el kernel y solucionar un error.
El administrador del clúster primero intenta drenar el nodo-1
usando el comando kubectl drain
.
La herramienta intenta drenar los Pods pod-a
y pod-x
. Esto tiene éxito inmediatamente.
Ambos Pods pasan al estado terminating
al mismo tiempo.
Esto pone al clúster en el siguiente estado:
nodo-1 draining | nodo-2 | nodo-3 |
---|---|---|
pod-a terminating | pod-b available | pod-c available |
pod-x terminating |
El Deployment detecta que uno de los Pods está terminando, entonces crea un reemplazo
llamado pod-d
. Dado que el nodo-1
está bloqueado, el pod se inicia en otro nodo. Además,
se crea el pod pod-y
como reemplazo de pod-x
.
(Nota: para un StatefulSet, pod-a
, que debería llamarse algo como pod-0
, debe terminar completamente antes de su reemplazo, que también se llama pod-0
pero tiene un UID diferente, puede ser creado. De lo contrario, el ejemplo también se aplica a un StatefulSet).
Ahora el clúster está en este estado:
nodo-1 draining | nodo-2 | nodo-3 |
---|---|---|
pod-a terminating | pod-b available | pod-c available |
pod-x terminating | pod-d starting | pod-y starting |
En algún momento, los Pods terminan y el clúster se ve así:
nodo-1 drained | nodo-2 | nodo-3 |
---|---|---|
pod-b available | pod-c available | |
pod-d starting | pod-y starting |
En este estado, si un administrador del clúster impaciente intenta drenar el nodo-2
o el
nodo-3
, el comando de drenado será bloqueado, porque solo hay 2 Pods disponibles para
el Deployment y el PDB requiere al menos 2. Después de un tiempo, pod-d
y pod-y
están disponibles.
El estado del clúster ahora se ve así:
nodo-1 drained | nodo-2 | nodo-3 |
---|---|---|
pod-b available | pod-c available | |
pod-d available | pod-y available |
Ahora, el administrador del clúster drena el nodo-2
.
El comando de drenado intentará drenar los 2 Pods en algún orden, digamos
primero pod-b
y luego pod-d
. Tendrá éxito en eliminar pod-b
.
Pero cuando intente drenar pod-d
, será rechazado porque eso dejará
solo un Pod disponible para el Deployment.
El Deployment crea un reemplazo para pod-b
llamado pod-e
.
Dado que no hay suficientes recursos disponibles en el clúster para programar
pod-e
, el drenado será bloqueado nuevamente. El clúster terminará en este
estado:
nodo-1 drained | nodo-2 | nodo-3 | no node |
---|---|---|---|
pod-b terminating | pod-c available | pod-e pending | |
pod-d available | pod-y available |
Ahora, el administrador del clúster necesita agregar un nuevo nodo en el clúster para continuar con la actualización.
Usted puede ver cómo Kubernetes varía la tasa a la que ocurren las interrupciones, según:
- cuántas réplicas necesita una aplicación
- cuánto tiempo lleva apagar una instancia correctamente
- cuánto tiempo lleva iniciar una nueva instancia
- el tipo de controlador
- la capacidad de recursos del clúster
Separación entre el dueño del Clúster y los roles de dueños de la Aplicación
Muchas veces es útil pensar en el Administrador del Clúster y al dueño de la aplicación como roles separados con conocimiento limitado el uno del otro. Esta separación de responsabilidades puede tener sentido en estos escenarios:
- Cuando hay muchos equipos con aplicaciones compartiendo un clúster de Kubernetes y hay una especialización natural de roles
- Cuando se usa una herramienta de terceros o un servicio para automatizar el control del clúster
El presupuesto de interrupción de Pods respalda esta separación de roles, proporcionando una interfaz entre los roles.
Si no hay tal separación de responsabilidades en la organización, es posible que no sea necesario el Presupuesto de Interrupción de Pods.
Cómo realizar Acciones Disruptivas en el Clúster
Si usted es el Administrador del Clúster y necesitas realizar una acción disruptiva en todos los nodos del clúster, como una actualización de nodo o de software, estas son algunas de las opciones:
- Aceptar el tiempo de inactividad mientras dura la actualización.
- Cambiar a otra réplica completa del clúster.
- No hay tiempo de inactividad, pero puede ser costoso tener duplicados los nodos y también se requiere esfuerzo humano para orquestar dicho cambio.
- Diseñar la tolerancia a fallas en la aplicación y usar PDBs.
- No hay tiempo de inactividad.
- Duplicación mínima de recursos.
- Permite mucha más automatización en la administración del clúster.
- Diseñar aplicaciones para tolerar fallas es complicado, pero el trabajo para tolerar interrupciones involuntarias a menudo vale la pena en comparación con el trabajo de admitir autoescalado y tolerar interrupciones involuntarias.
Siguientes pasos
-
Siga los pasos para proteger su aplicación con configurar el Presupuesto de Interrupciones de Pods.
-
Aprenda más sobre drenar nodos.
-
Aprenda sobre actualizar un Deployment incluyendo los pasos para mantener su disponibilidad durante la actualización.
1.4 - Containers Efímeros
Kubernetes v1.29 [alpha]
Esta página proporciona una descripción general de los Containers efímeros: un tipo especial de Container que se ejecuta temporalmente en un Pod ya existente para cumplir las acciones iniciadas por el usuario, como por ejemplo, la solución de problemas. En vez de ser utilizadas para crear aplicaciones, los Containers efímeros se utilizan para examinar los servicios.
Entendiendo los Containers efímeros
Pods son el componente fundamental de las aplicaciones de Kubernetes. Puesto que los Pods están previstos para ser desechables y reemplazables, no se puede añadir un Container a un Pod una vez creado. Sin embargo, por lo general se eliminan y se reemplazan los Pods de manera controlada utilizando Deployments.
En ocasiones es necesario examinar el estado de un Pod existente, como por ejemplo, para poder solucionar un error difícil de reproducir. Puede ejecutar en estos casos un Container efímero en un Pod ya existente para examinar su estado y para ejecutar comandos de manera arbitraria.
Qué es un Container efímero?
Los Containers efímeros se diferencian de otros Containers en que no garantizan ni los recursos ni la ejecución, y en que nunca se reiniciarán automáticamente, de modo que no son aptos para la construcción de aplicaciones. Los Containers efímeros se describen usando la misma ContainerSpec que los Containers regulares, aunque muchos campos son incompatibles y no están habilitados para los Containers efímeros.
- Los Containers efímeros no pueden tener puertos, por lo que campos como
ports
,livenessProbe
,readinessProbe
no están habilitados. - Las asignaciones de recursos del Pod son inmutables, por lo que no esta habilitado configurar "resources".
- Para obtener una lista completa de los campos habilitados, consulte la documentación de referencia [EphemeralContainer] (/docs/reference/generated/kubernetes-api/v1.29/#ephemeralcontainer-v1-core).
En vez de añadirlos de forma directa al pod.spec
, los Containers efímeros se crean usando un
controlador especial de la API, ephemeralcontainers
, por lo tanto no es posible añadir un
Container efímero utilizando kubectl edit
.
Al igual en el caso de los Containers regulares, no se puede modificar o remover un Container efímero después de haberlo agregado a un Pod.
Casos de uso para los Containers efímeros
Los Containers efímeros resultan útiles para la solución interactiva de incidencias cuando
kubectl exec
es insuficiente tanto porque un container se ha caído, como porque la imagen de un
Container no incluye las utilidades de depuración.
En particular, las imágenes distroless
le permiten desplegar imágenes de Containers mínimos que disminuyen la superficie de ataque
y la exposición a errores y vulnerabilidades. Ya que las imágenes distroless no contienen un
shell ni ninguna utilidad de depuración, resulta difícil solucionar los problemas de las imágenes
distroless usando solamente kubectl exec
.
Cuando utilice Containers efímeros, es conveniente habilitar el proceso Namespace de uso compartido para poder ver los procesos en otros containers.
Ejemplos
EphemeralContainers
feature
gate estén habilitados
y que tanto el cliente como el servidor de Kubernetes tengan la version v1.16 o posterior.
En los ejemplos de esta sección muestran la forma en que los Containers efímeros se
presentan en la API. Los usuarios normalmente usarían un plugin kubectl
para la solución
de problemas que automatizaría estos pasos.
Los Containers efímeros son creados utilizando el subrecurso ephemeralcontainers
del Pod,
que puede ser visto utilizando kubectl --raw
. En primer lugar describa el Container
efímero a añadir como una lista de EphemeralContainers
:
{
"apiVersion": "v1",
"kind": "EphemeralContainers",
"metadata": {
"name": "example-pod"
},
"ephemeralContainers": [{
"command": [
"sh"
],
"image": "busybox",
"imagePullPolicy": "IfNotPresent",
"name": "debugger",
"stdin": true,
"tty": true,
"terminationMessagePolicy": "File"
}]
}
Para actualizar los Containers efímeros de los example-pod
en ejecución:
kubectl replace --raw /api/v1/namespaces/default/pods/example-pod/ephemeralcontainers -f ec.json
Esto devolverá una nueva lista de Containers efímeros:
{
"kind":"EphemeralContainers",
"apiVersion":"v1",
"metadata":{
"name":"example-pod",
"namespace":"default",
"selfLink":"/api/v1/namespaces/default/pods/example-pod/ephemeralcontainers",
"uid":"a14a6d9b-62f2-4119-9d8e-e2ed6bc3a47c",
"resourceVersion":"15886",
"creationTimestamp":"2019-08-29T06:41:42Z"
},
"ephemeralContainers":[
{
"name":"debugger",
"image":"busybox",
"command":[
"sh"
],
"resources":{
},
"terminationMessagePolicy":"File",
"imagePullPolicy":"IfNotPresent",
"stdin":true,
"tty":true
}
]
}
Se puede ver el estado del Container efímero creado usando kubectl describe
:
kubectl describe pod example-pod
...
Ephemeral Containers:
debugger:
Container ID: docker://cf81908f149e7e9213d3c3644eda55c72efaff67652a2685c1146f0ce151e80f
Image: busybox
Image ID: docker-pullable://busybox@sha256:9f1003c480699be56815db0f8146ad2e22efea85129b5b5983d0e0fb52d9ab70
Port: <none>
Host Port: <none>
Command:
sh
State: Running
Started: Thu, 29 Aug 2019 06:42:21 +0000
Ready: False
Restart Count: 0
Environment: <none>
Mounts: <none>
...
Se puede conectar al nuevo Container efímero usando kubectl attach
:
kubectl attach -it example-pod -c debugger
Si el proceso Namespace de uso compartido está habilitado, se pueden visualizar los procesos de todos los Containers de ese Pod.
Por ejemplo, después de haber conectado, ejecute ps
en el debugger del container:
ps auxww
La respuesta es semejante a:
PID USER TIME COMMAND
1 root 0:00 /pause
6 root 0:00 nginx: master process nginx -g daemon off;
11 101 0:00 nginx: worker process
12 101 0:00 nginx: worker process
13 101 0:00 nginx: worker process
14 101 0:00 nginx: worker process
15 101 0:00 nginx: worker process
16 101 0:00 nginx: worker process
17 101 0:00 nginx: worker process
18 101 0:00 nginx: worker process
19 root 0:00 /pause
24 root 0:00 sh
29 root 0:00 ps auxww
2 - Controladores
2.1 - ReplicaSet
El objeto de un ReplicaSet es el de mantener un conjunto estable de réplicas de Pods ejecutándose en todo momento. Así, se usa en numerosas ocasiones para garantizar la disponibilidad de un número específico de Pods idénticos.
Cómo funciona un ReplicaSet
Un ReplicaSet se define con campos, incluyendo un selector que indica cómo identificar a los Pods que puede adquirir, un número de réplicas indicando cuántos Pods debería gestionar, y una plantilla pod especificando los datos de los nuevos Pods que debería crear para conseguir el número de réplicas esperado. Un ReplicaSet alcanza entonces su propósito mediante la creación y eliminación de los Pods que sea necesario para alcanzar el número esperado. Cuando un ReplicaSet necesita crear nuevos Pods, utiliza su plantilla Pod.
El enlace que un ReplicaSet tiene hacia sus Pods es a través del campo del Pod denominado metadata.ownerReferences, el cual indica qué recurso es el propietario del objeto actual. Todos los Pods adquiridos por un ReplicaSet tienen su propia información de identificación del ReplicaSet en su campo ownerReferences. Y es a través de este enlace cómo el ReplicaSet conoce el estado de los Pods que está gestionando y actúa en consecuencia.
Un ReplicaSet identifica los nuevos Pods a adquirir usando su selector. Si hay un Pod que no tiene OwnerReference o donde OwnerReference no es un controlador, pero coincide con el selector del ReplicaSet, este será inmediatamente adquirido por dicho ReplicaSet.
Cuándo usar un ReplicaSet
Un ReplicaSet garantiza que un número específico de réplicas de un pod se está ejecutando en todo momento. Sin embargo, un Deployment es un concepto de más alto nivel que gestiona ReplicaSets y proporciona actualizaciones de forma declarativa de los Pods junto con muchas otras características útiles. Por lo tanto, se recomienda el uso de Deployments en vez del uso directo de ReplicaSets, a no ser que se necesite una orquestración personalizada de actualización o no se necesite las actualizaciones en absoluto.
En realidad, esto quiere decir que puede que nunca necesites manipular los objetos ReplicaSet: en vez de ello, usa un Deployment, y define tu aplicación en la sección spec.
Ejemplo
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: frontend
labels:
app: guestbook
tier: frontend
spec:
# modifica las réplicas según tu caso de uso
replicas: 3
selector:
matchLabels:
tier: frontend
template:
metadata:
labels:
tier: frontend
spec:
containers:
- name: php-redis
image: gcr.io/google_samples/gb-frontend:v3
Si guardas este manifiesto en un archivo llamado frontend.yaml
y lo lanzas en un clúster de Kubernetes,
se creará el ReplicaSet definido y los Pods que maneja.
kubectl apply -f http://k8s.io/examples/controllers/frontend.yaml
Puedes ver los ReplicaSets actuales desplegados:
kubectl get rs
Y ver el frontend que has creado:
NAME DESIRED CURRENT READY AGE
frontend 3 3 3 6s
También puedes comprobar el estado del replicaset:
kubectl describe rs/frontend
Y verás una salida parecida a la siguiente:
Name: frontend
Namespace: default
Selector: tier=frontend,tier in (frontend)
Labels: app=guestbook
tier=frontend
Annotations: <none>
Replicas: 3 current / 3 desired
Pods Status: 3 Running / 0 Waiting / 0 Succeeded / 0 Failed
Pod Template:
Labels: app=guestbook
tier=frontend
Containers:
php-redis:
Image: gcr.io/google_samples/gb-frontend:v3
Port: 80/TCP
Requests:
cpu: 100m
memory: 100Mi
Environment:
GET_HOSTS_FROM: dns
Mounts: <none>
Volumes: <none>
Events:
FirstSeen LastSeen Count From SubobjectPath Type Reason Message
--------- -------- ----- ---- ------------- -------- ------ -------
1m 1m 1 {replicaset-controller } Normal SuccessfulCreate Created pod: frontend-qhloh
1m 1m 1 {replicaset-controller } Normal SuccessfulCreate Created pod: frontend-dnjpy
1m 1m 1 {replicaset-controller } Normal SuccessfulCreate Created pod: frontend-9si5l
Y por último, puedes comprobar los Pods que ha arrancado:
kubectl get Pods
Deberías ver la información de cada Pod similar a:
NAME READY STATUS RESTARTS AGE
frontend-9si5l 1/1 Running 0 1m
frontend-dnjpy 1/1 Running 0 1m
frontend-qhloh 1/1 Running 0 1m
También puedes verificar que la referencia de propietario de dichos pods está puesta al ReplicaSet frontend. Para ello, obtén el yaml de uno de los Pods ejecutándose:
kubectl get pods frontend-9si5l -o yaml
La salida será parecida a esta, donde la información sobre el ReplicaSet aparece en el campo ownerReferences de los metadatos:
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: 2019-01-31T17:20:41Z
generateName: frontend-
labels:
tier: frontend
name: frontend-9si5l
namespace: default
ownerReferences:
- apiVersion: extensions/v1beta1
blockOwnerDeletion: true
controller: true
kind: ReplicaSet
name: frontend
uid: 892a2330-257c-11e9-aecd-025000000001
...
Adquisiciones de Pods fuera de la plantilla
Aunque puedes crear Pods simples sin problemas, se recomienda encarecidamente asegurarse de que dichos Pods no tienen etiquetas que puedan coincidir con el selector de alguno de tus ReplicaSets. La razón de esta recomendación es que un ReplicaSet no se limita a poseer los Pods especificados en su plantilla -- sino que puede adquirir otros Pods como se explicó en secciones anteriores.
Toma el ejemplo anterior del ReplicaSet frontend, y los Pods especificados en el siguiente manifiesto:
apiVersion: v1
kind: Pod
metadata:
name: pod1
labels:
tier: frontend
spec:
containers:
- name: hello1
image: gcr.io/google-samples/hello-app:2.0
---
apiVersion: v1
kind: Pod
metadata:
name: pod2
labels:
tier: frontend
spec:
containers:
- name: hello2
image: gcr.io/google-samples/hello-app:1.0
Como estos Pods no tienen un Controlador (o cualquier otro objeto) como referencia de propietario y como además su selector coincide con el del ReplicaSet frontend, este último los terminará adquiriendo de forma inmediata.
Supón que creas los Pods después de que el ReplicaSet frontend haya desplegado los suyos para satisfacer su requisito de cuenta de réplicas:
kubectl apply -f http://k8s.io/examples/pods/pod-rs.yaml
Los nuevos Pods serán adquiridos por el ReplicaSet, e inmediatamente terminados ya que el ReplicaSet estaría por encima del número deseado.
Obtener los Pods:
kubectl get Pods
La salida muestra que los nuevos Pods se han terminado, o están en el proceso de terminarse:
NAME READY STATUS RESTARTS AGE
frontend-9si5l 1/1 Running 0 1m
frontend-dnjpy 1/1 Running 0 1m
frontend-qhloh 1/1 Running 0 1m
pod2 0/1 Terminating 0 4s
Si creas primero los Pods:
kubectl apply -f http://k8s.io/examples/pods/pod-rs.yaml
Y entonces creas el ReplicaSet:
kubectl apply -f http://k8s.io/examples/controllers/frontend.yaml
Verás que el ReplicaSet ha adquirido dichos Pods y simplemente ha creado tantos nuevos como necesarios para cumplir con su especificación hasta que el número de sus nuevos Pods y los originales coincidan con la cuenta deseado. Al obtener los Pods:
kubectl get Pods
Veremos su salida:
NAME READY STATUS RESTARTS AGE
frontend-pxj4r 1/1 Running 0 5s
pod1 1/1 Running 0 13s
pod2 1/1 Running 0 13s
De esta forma, un ReplicaSet puede poseer un conjunto no homogéneo de Pods
Escribir un manifiesto de ReplicaSet
Al igual que con el esto de los objeto de la API de Kubernetes, un ReplicaSet necesita los campos
apiVersion
, kind
, y metadata
. Para los ReplicaSets, el tipo es siempre ReplicaSet.
En la versión 1.9 de Kubernetes, la versión apps/v1
de la API en un tipo ReplicaSet es la versión actual y está habilitada por defecto.
La versión apps/v1beta2
de la API se ha desaprobado.
Consulta las primeras líneas del ejemplo frontend.yaml
como guía.
Un ReplicaSet también necesita una sección .spec
.
Plantilla Pod
El campo .spec.template
es una plantilla pod que es
también necesita obligatoriamente tener etiquetas definidas. En nuestro ejemplo frontend.yaml
teníamos una etiqueta: tier: frontend
.
Lleva cuidado de que no se entremezcle con los selectores de otros controladores, no sea que traten de adquirir este Pod.
Para el campo de regla de reinicio de la plantilla,
.spec.template.spec.restartPolicy
, el único valor permitido es Always
, que es el valor predeterminado.
Selector de Pod
El campo .spec.selector
es un selector de etiqueta.
Como se explicó anteriormente, estas son las etiquetas que se usan para
identificar los Pods potenciales a adquirir. En nuestro ejemplo frontend.yaml
, el selector era:
matchLabels:
tier: frontend
El el ReplicaSet, .spec.template.metadata.labels
debe coincidir con spec.selector
, o será
rechazado por la API.
.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.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.
2.3 - Deployment
Un controlador de Deployment proporciona actualizaciones declarativas para los Pods y los ReplicaSets.
Cuando describes el estado deseado en un objeto Deployment, el controlador del Deployment se encarga de cambiar el estado actual al estado deseado de forma controlada. Puedes definir Deployments para crear nuevos ReplicaSets, o eliminar Deployments existentes y adoptar todos sus recursos con nuevos Deployments.
Casos de uso
A continuación se presentan los casos de uso típicos de los Deployments:
- Crear un Deployment 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=75675f5897
nginx-deployment-75675f5897-kzszj 1/1 Running 0 18s app=nginx,pod-template-hash=75675f5897
nginx-deployment-75675f5897-qqcnn 1/1 Running 0 18s app=nginx,pod-template-hash=75675f5897
El ReplicaSet creado garantiza que hay tres Pods de nginx
ejecutándose en todo momento.
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.
2.4 - StatefulSets
Un StatefulSet es el objeto de la API workload que se usa para gestionar aplicaciones con estado.
Gestiona el despliegue y escalado de un conjunto de Pods, y garantiza el orden y unicidad de dichos Pods.
Al igual que un Deployment, un StatefulSet gestiona Pods que se basan en una especificación idéntica de contenedor. A diferencia de un Deployment, un StatefulSet mantiene una identidad asociada a sus Pods. Estos pods se crean a partir de la misma especificación, pero no pueden intercambiarse; cada uno tiene su propio identificador persistente que mantiene a lo largo de cualquier re-programación.
Un StatefulSet opera bajo el mismo patrón que cualquier otro controlador. Se define el estado deseado en un objeto StatefulSet, y el controlador del StatefulSet efectúa las actualizaciones que sean necesarias para alcanzarlo a partir del estado actual.
Usar StatefulSets
Los StatefulSets son valiosos para aquellas aplicaciones que necesitan uno o más de los siguientes:
- Identificadores de red estables, únicos.
- Almacenamiento estable, persistente.
- Despliegue y escalado ordenado, controlado.
- Actualizaciones en línea ordenadas, automatizadas.
De los de arriba, estable es sinónimo de persistencia entre (re)programaciones de Pods. Si una aplicación no necesita ningún identificador estable o despliegue, eliminación, o escalado ordenado, deberías desplegar tu aplicación con un controlador que proporcione un conjunto de réplicas sin estado, como un Deployment o un ReplicaSet, ya que están mejor preparados para tus necesidades sin estado.
Limitaciones
- El almacenamiento de un determinado Pod debe provisionarse por un Provisionador de PersistentVolume basado en la
storage class
requerida, o pre-provisionarse por un administrador. - Eliminar y/o reducir un StatefulSet no eliminará los volúmenes asociados con el StatefulSet. Este comportamiento es intencional y sirve para garantizar la seguridad de los datos, que da más valor que la purga automática de los recursos relacionados del StatefulSet.
- Los StatefulSets actualmente necesitan un Servicio Headless como responsable de la identidad de red de los Pods. Es tu responsabilidad crear este Service.
- Los StatefulSets no proporcionan ninguna garantía de la terminación de los pods cuando se elimina un StatefulSet. Para conseguir un término de los pods ordenado y controlado en el StatefulSet, es posible reducir el StatefulSet a 0 réplicas justo antes de eliminarlo.
- Cuando se usan las Actualizaciones en línea con la
Regla de Gestión de Pod (
OrderedReady
) por defecto, es posible entrar en un estado inconsistente que requiere de una intervención manual para su reparación.
Componentes
El ejemplo de abajo demuestra los componentes de un StatefulSet:
- Un servicio Headless, llamado nginx, se usa para controlar el dominio de red.
- Un StatefulSet, llamado web, que tiene una especificación que indica que se lanzarán 3 réplicas del contenedor nginx en Pods únicos.
- Un volumeClaimTemplate que proporciona almacenamiento estable por medio de PersistentVolumes provisionados por un provisionador de tipo PersistentVolume.
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
selector:
matchLabels:
app: nginx # tiene que coincidir con .spec.template.metadata.labels
serviceName: "nginx"
replicas: 3 # por defecto es 1
template:
metadata:
labels:
app: nginx # tiene que coincidir con .spec.selector.matchLabels
spec:
terminationGracePeriodSeconds: 10
containers:
- name: nginx
image: registry.k8s.io/nginx-slim:0.8
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "my-storage-class"
resources:
requests:
storage: 1Gi
Selector de Pod
Debes poner el valor del campo .spec.selector
de un StatefulSet para que coincida con las etiquetas de su campo .spec.template.metadata.labels
. Antes de Kubernetes 1.8,
el campo .spec.selector
se predeterminaba cuando se omitía. A partir de la versión 1.8, si no se especifica un selector de coincidencia de Pods, se produce un error de validación
durante la creación del StatefulSet.
Identidad de Pod
Los Pods de un StatefulSet tienen una identidad única que está formada por un ordinal, una identidad estable de red, y almacenamiento estable. La identidad se asocia al Pod, independientemente del nodo en que haya sido (re)programado.
Índice Ordinal
Para un StatefulSet con N réplicas, a cada Pod del StatefulSet se le asignará un número entero ordinal, desde 0 hasta N-1, y que es único para el conjunto.
ID estable de Red
El nombre de anfitrión (hostname) de cada Pod de un StatefulSet se deriva del nombre del StatefulSet
y del número ordinal del Pod. El patrón para construir dicho hostname
es $(statefulset name)-$(ordinal)
. Así, el ejemplo de arriba creará tres Pods
denominados web-0,web-1,web-2
.
Un StatefulSet puede usar un Servicio Headless
para controlar el nombre de dominio de sus Pods. El nombre de dominio gestionado por este Service tiene la forma:
$(service name).$(namespace).svc.cluster.local
, donde "cluster.local" es el nombre de dominio del clúster.
Conforme se crea cada Pod, se le asigna un nombre DNS correspondiente de subdominio, que tiene la forma:
$(podname).$(governing service domain)
, donde el servicio en funciones se define por el campo
serviceName
del StatefulSet.
Como se indicó en la sección limitaciones, la creación del Servicio Headless encargado de la identidad de red de los pods es enteramente tu responsabilidad.
Aquí se muestran algunos ejemplos de elecciones de nombres de Cluster Domain, nombres de Service, nombres de StatefulSet, y cómo impactan en los nombres DNS de los Pods del StatefulSet:
Cluster 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.
2.5 - DaemonSet
Un DaemonSet garantiza que todos (o algunos) de los nodos ejecuten una copia de un Pod. Conforme se añade más nodos al clúster, nuevos Pods son añadidos a los mismos. Conforme se elimina nodos del clúster, dichos Pods se destruyen. Al eliminar un DaemonSet se limpian todos los Pods que han sido creados.
Algunos casos de uso típicos de un DaemonSet son:
- Ejecutar un proceso de almacenamiento en el clúster.
- Ejecutar un proceso de recolección de logs en cada nodo.
- Ejecutar un proceso de monitorización de nodos en cada nodo.
De forma básica, se debería usar un DaemonSet, cubriendo todos los nodos, por cada tipo de proceso. En configuraciones más complejas se podría usar múltiples DaemonSets para un único tipo de proceso, pero con diferentes parámetros y/o diferentes peticiones de CPU y memoria según el tipo de hardware.
Escribir una especificación de DaemonSet
Crear un DaemonSet
Un DaemonSet se describe por medio de un archivo YAML. Por ejemplo, el archivo daemonset.yaml
de abajo describe un DaemonSet que ejecuta la imagen Docker de fluentd-elasticsearch:
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluentd-elasticsearch
namespace: kube-system
labels:
k8s-app: fluentd-logging
spec:
selector:
matchLabels:
name: fluentd-elasticsearch
template:
metadata:
labels:
name: fluentd-elasticsearch
spec:
tolerations:
- key: node-role.kubernetes.io/master
operator: Exists
effect: NoSchedule
containers:
- name: fluentd-elasticsearch
image: gcr.io/fluentd-elasticsearch/fluentd:v2.5.1
resources:
limits:
memory: 200Mi
requests:
cpu: 100m
memory: 200Mi
volumeMounts:
- name: varlog
mountPath: /var/log
- name: varlibdockercontainers
mountPath: /var/lib/docker/containers
readOnly: true
terminationGracePeriodSeconds: 30
volumes:
- name: varlog
hostPath:
path: /var/log
- name: varlibdockercontainers
hostPath:
path: /var/lib/docker/containers
- Crear un DaemonSet basado en el archivo YAML:
kubectl apply -f https://k8s.io/examples/controllers/daemonset.yaml
Campos requeridos
Como con cualquier otra configuración de Kubernetes, un DaemonSet requiere los campos apiVersion
, kind
, y metadata
.
Para información general acerca de cómo trabajar con ficheros de configuración, ver los documentos desplegar aplicaciones,
configurar contenedores, y gestión de objetos usando kubectl.
Un DaemonSet también necesita un sección .spec
.
Plantilla Pod
El campo .spec.template
es uno de los campos obligatorios de la sección .spec
.
El campo .spec.template
es una plantilla Pod. Tiene exactamente el mismo esquema que un Pod,
excepto por el hecho de que está anidado y no tiene los campos apiVersion
o kind
.
Además de los campos obligatorios de un Pod, la plantilla Pod para un DaemonSet debe especificar las etiquetas apropiadas (ver selector de pod).
Una plantilla Pod para un DaemonSet debe tener una RestartPolicy
igual a Always
, o no indicarse, lo cual asume por defecto el valor Always
.
Selector de Pod
El campo .spec.selector
es un selector de pod. Funciona igual que el campo .spec.selector
de un Job.
A partir de Kubernetes 1.8, se debe configurar un selector de pod que coincida con las
etiquetas definidas en el .spec.template
. Así, el selector de pod ya no asume valores por defecto cuando no se indica.
Dichos valores por defecto no eran compatibles con kubectl apply
. Además, una vez que se ha creado el DaemonSet,
su campo .spec.selector
no puede alterarse porque, si fuera el caso, ello podría resultar
en Pods huérfanos, lo cual confundiría a los usuarios.
El campo .spec.selector
es un objeto que, a su vez, consiste en dos campos:
matchLabels
- funciona igual que el campo.spec.selector
de un ReplicationController.matchExpressions
- permite construir selectores más sofisticados indicando la clave, la lista de valores y un operador para relacionar la clave y los valores.
Cuando se configura ambos campos, el resultado es conjuntivo (AND).
Si se especifica el campo .spec.selector
, entonces debe coincidir con el campo .spec.template.metadata.labels
. Aquellas configuraciones que no coinciden, son rechazadas por la API.
Además, normalmente no se debería crear ningún Pod con etiquetas que coincidan con el selector, bien sea de forma directa, via otro DaemonSet, o via otro controlador como un ReplicaSet. De ser así, el controlador del DaemonSet pensará que dichos Pods fueron en realidad creados por él mismo. Kubernetes, en cualquier caso, no te impide realizar esta operación. Un caso donde puede que necesites hacer esto es cuando quieres crear manualmente un Pod con un valor diferente en un nodo para pruebas.
Ejecutar Pods sólo en Nodos seleccionados
Si se configura un .spec.template.spec.nodeSelector
, entonces el controlador del DaemonSet
creará los Pods en aquellos nodos que coincidan con el selector de nodo indicado.
De forma similar, si se configura una .spec.template.spec.affinity
,
entonces el controlador del DaemonSet creará los Pods en aquellos nodos que coincidan con la afinidad de nodo indicada.
Si no se configura ninguno de los dos, entonces el controlador del DaemonSet creará los Pods en todos los nodos.
Cómo se planifican los Pods procesos
Planificados por el controlador del DaemonSet (deshabilitado por defecto a partir de 1.12)
Normalmente, el planificador de Kubernetes determina la máquina donde se ejecuta un Pod. Sin embargo, los Pods
creados por el controlador del DaemonSet ya tienen la máquina seleccionada (puesto que cuando se crea el Pod,
se indica el campo .spec.nodeName
, y por ello el planificador los ignora). Por lo tanto:
- El controlador del DaemonSet no tiene en cuenta el campo
unschedulable
de un nodo. - El controlador del DaemonSet puede crear Pods incluso cuando el planificador no ha arrancado, lo cual puede ayudar en el arranque del propio clúster.
Planificados por el planificador por defecto de Kubernetes (habilitado por defecto desde 1.12)
Kubernetes v1.29 [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.
2.6 - Recolección de Basura
El papel del recolector de basura de Kubernetes es el de eliminar determinados objetos que en algún momento tuvieron un propietario, pero que ahora ya no.
Propietarios y subordinados
Algunos objetos de Kubernetes son propietarios de otros objetos. Por ejemplo, un ReplicaSet
es el propietario de un conjunto de Pods. Los objetos que se poseen se denominan subordinados del
objeto propietario. Cada objeto subordinado tiene un campo metadata.ownerReferences
que apunta al objeto propietario.
En ocasiones, Kubernetes pone el valor del campo ownerReference
automáticamente.
Por ejemplo, cuando creas un ReplicaSet, Kubernetes automáticamente pone el valor del campo
ownerReference
de cada Pod en el ReplicaSet. A partir de la versión 1.8, Kubernetes
automáticamente pone el valor de ownerReference
para los objetos creados o adoptados
por un ReplicationController, ReplicaSet, StatefulSet, DaemonSet, Deployment, Job
y CronJob.
También puedes configurar las relaciones entre los propietarios y sus subordinados
de forma manual indicando el valor del campo ownerReference
.
Aquí se muestra un archivo de configuración para un ReplicaSet que tiene tres Pods:
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: my-repset
spec:
replicas: 3
selector:
matchLabels:
pod-is-for: garbage-collection-example
template:
metadata:
labels:
pod-is-for: garbage-collection-example
spec:
containers:
- name: nginx
image: nginx
Si se crea el ReplicaSet y entonces se muestra los metadatos del Pod, se puede observar el campo OwnerReferences:
kubectl apply -f https://k8s.io/examples/controllers/replicaset.yaml
kubectl get pods --output=yaml
La salida muestra que el propietario del Pod es el ReplicaSet denominado my-repset
:
apiVersion: v1
kind: Pod
metadata:
...
ownerReferences:
- apiVersion: apps/v1
controller: true
blockOwnerDeletion: true
kind: ReplicaSet
name: my-repset
uid: d9607e19-f88f-11e6-a518-42010a800195
...
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
2.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
2.8 - Jobs - Ejecución hasta el final
Un Job crea uno o más Pods y se asegura de que un número específico de ellos termina de forma satisfactoria. Conforme los pods terminan satisfactoriamente, el Job realiza el seguimiento de las ejecuciones satisfactorias. Cuando se alcanza un número específico de ejecuciones satisfactorias, la tarea (esto es, el Job) se completa. Al eliminar un Job se eliminan los Pods que haya creado.
Un caso simple de uso es crear un objeto Job para que se ejecute un Pod de manera fiable hasta el final. El objeto Job arrancará un nuevo Pod si el primer Pod falla o se elimina (por ejemplo como consecuencia de un fallo de hardware o un reinicio en un nodo).
También se puede usar un Job para ejecutar múltiples Pods en paralelo.
Ejecutar un Job de ejemplo
Aquí se muestra un ejemplo de configuración de Job. Este ejemplo calcula los primeros 2000 decimales de π y los imprime por pantalla. Tarda unos 10s en completarse.
apiVersion: batch/v1
kind: Job
metadata:
name: pi
spec:
template:
spec:
containers:
- name: pi
image: perl
command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"]
restartPolicy: Never
backoffLimit: 4
Puedes ejecutar el ejemplo con este comando:
kubectl apply -f https://k8s.io/examples/controllers/job.yaml
job "pi" created
Comprueba el estado del Job con kubectl
:
kubectl describe jobs/pi
Name: pi
Namespace: default
Selector: controller-uid=b1db589a-2c8d-11e6-b324-0209dc45a495
Labels: controller-uid=b1db589a-2c8d-11e6-b324-0209dc45a495
job-name=pi
Annotations: <none>
Parallelism: 1
Completions: 1
Start Time: Tue, 07 Jun 2016 10:56:16 +0200
Pods Statuses: 0 Running / 1 Succeeded / 0 Failed
Pod Template:
Labels: controller-uid=b1db589a-2c8d-11e6-b324-0209dc45a495
job-name=pi
Containers:
pi:
Image: perl
Port:
Command:
perl
-Mbignum=bpi
-wle
print bpi(2000)
Environment: <none>
Mounts: <none>
Volumes: <none>
Events:
FirstSeen LastSeen Count From SubobjectPath Type Reason Message
--------- -------- ----- ---- ------------- -------- ------ -------
1m 1m 1 {job-controller } Normal SuccessfulCreate Created pod: pi-dtn4q
Para ver los Pods de un Job que se han completado, usa kubectl get pods
.
Para listar todos los Pods que pertenecen a un Job de forma que sea legible, puedes usar un comando como:
pods=$(kubectl get pods --selector=job-name=pi --output=jsonpath='{.items[*].metadata.name}')
echo $pods
pi-aiw0a
En este caso, el selector es el mismo que el selector del Job. La opción --output=jsonpath
indica un expresión
que simplemente obtiene el nombre de cada Pod en la lista devuelta.
Mira la salida estándar de uno de los Pods:
$ kubectl logs $pods
3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153643678925903600113305305488204665213841469519415116094330572703657595919530921861173819326117931051185480744623799627495673518857527248912279381830119491298336733624406566430860213949463952247371907021798609437027705392171762931767523846748184676694051320005681271452635608277857713427577896091736371787214684409012249534301465495853710507922796892589235420199561121290219608640344181598136297747713099605187072113499999983729780499510597317328160963185950244594553469083026425223082533446850352619311881710100031378387528865875332083814206171776691473035982534904287554687311595628638823537875937519577818577805321712268066130019278766111959092164201989380952572010654858632788659361533818279682303019520353018529689957736225994138912497217752834791315155748572424541506959508295331168617278558890750983817546374649393192550604009277016711390098488240128583616035637076601047101819429555961989467678374494482553797747268471040475346462080466842590694912933136770289891521047521620569660240580381501935112533824300355876402474964732639141992726042699227967823547816360093417216412199245863150302861829745557067498385054945885869269956909272107975093029553211653449872027559602364806654991198818347977535663698074265425278625518184175746728909777727938000816470600161452491921732172147723501414419735685481613611573525521334757418494684385233239073941433345477624168625189835694855620992192221842725502542568876717904946016534668049886272327917860857843838279679766814541009538837863609506800642251252051173929848960841284886269456042419652850222106611863067442786220391949450471237137869609563643719172874677646575739624138908658326459958133904780275901
Escribir una especificación de Job
Como con el resto de configuraciones de Kubernetes, un Job necesita los campos apiVersion
, kind
, y metadata
.
Un Job también necesita la sección .spec
.
Plantilla Pod
El campo .spec.template
es el único campo obligatorio de .spec
.
El campo .spec.template
es una plantilla Pod. Tiene exactamente el mismo esquema que un pod,
excepto por el hecho de que está anidado y no tiene el campo apiVersion
o kind
.
Además de los campos olbigatorios de un Pod, una plantilla Pod de un Job debe indicar las etiquetas apropiadas (ver selector de pod) y una regla de reinicio apropiada.
Sólo se permite los valores Never
o OnFailure
para RestartPolicy
.
Selector de Pod
El campo .spec.selector
es opcional. En la práctica mayoría de los casos no deberías configurarlo.
Mira la sección sobre configurar tu propio selector de pod.
Jobs en paralelo
Hay tres tipos principales de tarea aptos para ejecutarse como un Job:
- 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.
2.9 - CronJob
Un Cron Job ejecuta tareas, Jobs, a intervalos regulares.
Un objeto CronJob es como una línea de un archivo crontab (tabla cron). Ejecuta un trabajo de forma periódica según un horario programado escrito en formato Cron.
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.