Cómo trabajar con el objeto Deployment en Kubernetes

En uno de los primeros artículos que escribí sobre Kubernetes te dije, sin entrar en detalle, que la forma recomendada de desplegar los pods sobre tu clúster era a través del objeto Deployment, debido a que este gestiona los objetos ReplicaSet por nosotros y nos ayuda no solo en el despliegue sino también es posteriores actualizaciones. En este artículo quiero contarte todo lo que deberías saber sobre este objeto.
Cómo funciona un Deployment
Los deployments se definen como objetos de alto nivel que te ayudan no solo en el despliegue de tus aplicaciones en Kubernetes sino que también te permiten hacer actualizaciones de forma declarativa. En versiones anteriores, esta tarea se realizaba gestionando directamente el objeto ReplicationController, de forma más manual, pero ahora se ha delegado en estos y los ReplicaSets, de los que ya te hablé en un articulo anterior.
Como siempre, vamos a verlo con un ejemplo que siempre se entiende mejor . En este caso voy a desplegar un servidor web con tres replicas, inicialmente de Nginx:
#web-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-deployment
labels:
app: web
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- name: http
containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: web-service
spec:
type: LoadBalancer
selector:
app: web
ports:
- protocol: TCP
port: 80
targetPort: http
Además del Deployment voy a crear un objeto de tipo Service para poder acceder al servidor web desde fuera del clúster, y ver de forma sencilla lo que va ocurriendo con él mismo durante el artículo.
Por lo tanto, cuando despliegues este manifiesto tendrás 6 objetos nuevos en tu entorno: un Deployment, un ReplicaSet, tres pods y un servicio de tipo LoadBalancer.
kubectl apply -f web-deployment.yaml --record
En el comando anterior he añadido el parámetro –record, el cual verás para qué sirve más adelante
En esta primera fase la salida esperada será la siguiente:
deployment.apps/web-deployment created
service/web-service created
Puedes comprobar todos los objetos creados a través del siguiente comando:
kubectl get deploy,rs,po,svc
#or kubectl get deployments,replicasets,pods,services
y el resultado será parecido a este:
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.extensions/web-deployment 3/3 3 3 70s
NAME DESIRED CURRENT READY AGE
replicaset.extensions/web-deployment-6646b5b476 3 3 3 70s
NAME READY STATUS RESTARTS AGE
pod/web-deployment-6646b5b476-6f6sz 1/1 Running 0 70s
pod/web-deployment-6646b5b476-m9hjh 1/1 Running 0 70s
pod/web-deployment-6646b5b476-mglhs 1/1 Running 0 70s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.0.0.1 none 443/TCP 70s
service/web-service LoadBalancer 10.0.82.126 52.155.177.3 80:31717/TCP 70s
En mi caso, si accedo a través del navegador a la IP 52.155.177.3 veré el servidor de Nginx funcionando.
Ahora bien, cuando se dice que gracias a los deployments tenemos una actualización de manera declarativa ¿esto qué significa? Pues básicamente que podemos hacer modificaciones en el YAML que define el despliegue, ya sea volviendo a enviar todo él o a través de kubectl patch/edit, y el controlador de los deployments determinará si dicho cambio conlleva una actualización de los pods (esto es, cuando se ha modificado la sección template del pod dentro del objeto Deployment). Para comprobar esto, cambia la imagen nginx utilizada en la creación por mcr.microsoft.com/dotnet/core/samples:aspnetapp:
#web-deployment.v2.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-deployment
labels:
app: web
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
#image: nginx:latest
image: mcr.microsoft.com/dotnet/core/samples:aspnetapp
ports:
- name: http
containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: web-service
spec:
type: LoadBalancer
selector:
app: web
ports:
- protocol: TCP
port: 80
targetPort: http
y aplica los nuevos cambios:
kubectl apply -f web-deployment.v2.yaml --record
Al tratarse del mismo objeto Deployment creado anteriormente, el resultado ya no será created sino configured esta vez:
deployment.apps/web-deployment configured
service/web-service configured
Como es la segunda vez que mandas información acerca del mismo Deployment, si vuelves a comprobar el listado de ReplicaSets comprobarás que ahora tienes dos para el mismo despliegue:
$kubectl get rs
NAME DESIRED CURRENT READY AGE
web-deployment-5b5f4f975 3 3 3 92s
web-deployment-6646b5b476 0 0 0 4m24s
Pero sigues teniendo el mismo número de pods:
$kubectl get po
NAME READY STATUS RESTARTS AGE
web-deployment-5b5f4f975-kvh9k 1/1 Running 0 2m3s
web-deployment-5b5f4f975-mflx9 1/1 Running 0 2m5s
web-deployment-5b5f4f975-r8c9p 1/1 Running 0 2m7s
En el artículo sobre los ReplicaSet expliqué que estos crean los pods con el nombre del ReplicaSet (el cual está compuesto por el nombre del despliegue más un literal aleatorio) y un literal aleatorio por cada pod. Si comparas el nombre que tenían tus pods la primera vez y el nombre que tienen ahora podemos deducir que el ReplicaSet que está gestionando los pods no es el mismo que al principio, sino el nuevo que vemos con el literal 5b5f4f975. Esto es así porque el Deployment utiliza un nuevo ReplicaSet cada vez que haces una actualización, el cual tiene la versión actualizada de la plantilla que tiene que utilizar para los pods.
¿Pero por qué se queda el anterior con cero pods? Muy sencillo: en el caso de que necesitemos dar marcha atrás, porque la actualización no esté funcionando correctamente por ejemplo, el Deployment volverá a utilizar dicho ReplicaSet para recrear los pods con la configuración anterior. Puedes comprobarlo utilizando el comando rollout undo:
kubectl rollout undo deployment web-deployment
Kubernetes responderá con el siguiente mensaje:
deployment.extensions/web-deployment rolled back
y podrás ver que vuelves a tener un Nginx funcionando, en lugar de la aplicación de ejemplo de ASP.NET Core. De hecho, si vuelves a revisar los ReplicaSets comprobarás que ahora el que controla los pods es, de nuevo, el primero que se generó:
$kubectl get rs
NAME DESIRED CURRENT READY AGE
web-deployment-5b5f4f975 0 0 0 10m
web-deployment-6646b5b476 3 3 3 13m
Llegados a este punto es posible que te preguntes ¿entonces voy a tener tantos ReplicaSet como todas las actualizaciones que esta aplicación haya tenido en su vida? La respuesta es no Por defecto sólo se guardan las 10 últimas revisiones, aunque es configurable a través de revisionHistoryLimit, dentro del apartado spec del deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-deployment
labels:
app: web
spec:
revisionHistoryLimit: 5
replicas: 3
selector:
[...]
Por otro lado, si quisieras ir a una revisión en concreto podrías hacerlo añadiendo al comando el parámetro –to-revision=N, siempre dentro de las revisiones disponibles:
kubectl rollout undo deployment web-deployment --to-revision=2
Otro dato importante es poder ver el estado en el que se encuentra una actualización. En este ejemplo los cambios son demasiado rápidos, por lo que voy a ralentizar el tiempo que tardan los pods en estar listos.
#Slow downkubectl patch deployment web-deployment -p '{"spec": {"minReadySeconds" : 5}}'
El resultado de esta acción será el siguiente:
deployment.extensions/web-deployment patched
Nota: Este cambio en el deployment no hará que se actualicen los pods, al estar fuera del apartado template.
Ahora voy a actualizar de nuevo el objeto Deployment, pero esta vez a través de set image:
#Update the image
kubectl set image deployment web-deployment nginx=httpd:latest --record
El resultado será que la imagen ha sido actualizada:
deployment.extensions/web-deployment image updated
En este caso, sí generará un redespliegue de pods con la nueva imagen. Para conocer el estado de esta actualización puedes utilizar rollout status:
#See the status of the rollout
kubectl rollout status deployment web-deployment
Este último se quedará a la espera hasta que finalice la actualización, mostrando el avance de la misma:
Waiting for deployment "web-deployment" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "web-deployment" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "web-deployment" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "web-deployment" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "web-deployment" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "web-deployment" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "web-deployment" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "web-deployment" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "web-deployment" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "web-deployment" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "web-deployment" rollout to finish: 1 old replicas are pending termination...
deployment "web-deployment" successfully rolled out
Una vez que termine el proceso, comprobarás que ahora lo que tienes es un servidor Apache ejecutándose en tus pods
Para ver el histórico de las últimas actualizaciones que has llevado a cabo sobre tu despliegue inicial utiliza el comando rollout history:
kubectl rollout history deployment web-deployment
El resultado debería de ser como el que sigue:
REVISION CHANGE-CAUSE
3 kubectl apply --filename=web-deployment.yaml --record=true
4 kubectl apply --filename=web-deployment.v2.yaml --record=true
5 kubectl set image deployment web-deployment nginx=httpd:latest --record=true
Gracias a el parámetro –record puedes ver qué comando se lanzó para este deployment cada vez que se actualizó, de tal manera que será más sencillo poder determinar a qué versión tienes que ir si fuera necesario. Sin embargo, lo habitual es que utilicemos el mismo nombre de archivo, si estamos trabajando con un despliegue automatizado, pero este tendrá diferentes versiones del contenido. Si es el caso, podemos ver el detalle de cada actualización indicando la revisión:
kubectl rollout history deployment web-deployment --revision=4
De hecho, en la misma podrás ver que solo se muestra el contenido de la plantilla a utilizar para los pods:
deployment.extensions/web-deployment with revision #4
deployment.extensions/web-deployment with revision #4
Pod Template:
Labels: app=web
pod-template-hash=5b5f4f975
Annotations: kubernetes.io/change-cause: kubectl apply --filename=web-deployment.v2.yaml --record=true
Containers:
nginx:
Image: mcr.microsoft.com/dotnet/core/samples:aspnetapp
Port: 80/TCP
Host Port: 0/TCP
Environment: none
Mounts: none
Volumes: none
Diferentes estrategias de despliegue
Por último, es importante saber que los Deployments nos permiten llevar a cabo la actualización a través de dos estrategias. Por defecto se utiliza la llamada RollingUpdate la cual va remplazando los pods, evitando la pérdida de servicio, basándose en los valores maxSurge (cuantos pods puede haber por encima del valor deseado) y maxUnavailable (cuántos como máximo pueden no estar disponibles). Por defecto, ambas propiedades tienen un valor del 25%, aunque se puede especificar un número exacto en lugar de un porcentaje. Debes tener en cuenta que para poder utilizar esta estrategia tu aplicación debe de ser capaz de convivir con versiones diferentes.
Por otro lado, también es posible utilizar el modo Recreate que lo que haces es eliminar primero todos los pods antiguos y después crea los nuevos. En este caso sí que hay pérdida de servicio.
Esta configuración se hace a través del apartado strategy:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-deployment
labels:
app: web
spec:
revisionHistoryLimit: 5
strategy:
type: Recreate
[...]
Escalar un deployment
Por último, si solo quieres aumentar o disminuir el número de réplicas puedes hacerlo a través de scale:
kubectl scale deployment web-deployment --replicas=50
En un próximo artículo te mostraré cómo hacer esto de manera automática.
¡Saludos!