martes, 4 de agosto de 2020

Kubernetes - Notas adicionales sobre PODs y Services

En esta nueva entrada sobre Kubernetes profundizaremos un poco más en los detalles de la relación entre los PODs y los objetos de tipo service, que ya vimos en la anterior entrada de esta serie sobre Kubernetes.

Resumiendo lo visto hasta ahora, Kubernetes controla nuestra infraestructura de contenedores asegurándose que el estado del cluster coincide exactamente con el estado que definamos. Esto quiere decir que se asegurará de que existan los objetos en el cluster que hayamos establecido en nuestra configuración, arrancando y parando los contenedores que sean necesarios.

También hemos visto que un POD es la unidad mínima de ejecución en Kubernetes y que es la encapsulación de un contenedor, por ejemplo de Docker, que le permite al cluster de Kubernetes el control del mismo. Los PODs, por defecto, no son accesibles desde el exterior del cluster con lo cual, para publicar sus servicios, tendremos que crear un objeto de tipo service el cual "conectará" los PODs con el exterior, haciendo así accesibles los servicios a clientes externos.

Como vimos, esta conexión entre un objeto de tipo service y un POD se hace en función del campo selector del service y de las etiquetas del POD, campos que se definen dentro de la sección spec del objecto service y en el caso de los objetos POD, en la seción metadata de los mismos.

Sabemos que un objeto de Kubernetes se define con un fichero que sigue la siguiente estructura:

Spec de un objeto de Kubernetes.
Definición de un objeto de Kubernetes.

Por tanto, para definir un POD básico que ejecute la imagen oficial del servidor web Apache, podemos establecer la siguiente definición:

Definición POD simple
Definición POD simple.

Como sabemos, basta con aplicar este fichero de definición al cluster para arrancar un POD con estas características:

Ejecución de POD simple
Ejecución de POD simple.

Este POD, aunque controla un contenedor ejecutando una imagen de un servidor web que expone el puerto 80, no es accesible desde el exterior del cluster y sabemos que necesitamos un servicio para poder conectarlo o publicarlo al exterior.

La definición de este servicio será tan simple como la siguiente:

Definición de un servicio simple
Definición de un servicio simple.

Como podemos ver, he creado la definición de este servicio sin especificar el campo selector a pesar de lo cual podemos aplicarlo perfectamente al cluster:

Aplicación de la definición de servicio
Aplicación de la definición de servicio.

Comprobamos que efectivamente el servicio se ha creado en el cluster con el comando kubectl get all:

El objeto servicio aplicado al cluster
El objeto servicio aplicado al cluster.

Como es lógico y ya vimos, ahora mismo tenemos un POD que no es accesible y un servicio que no está redirigiendo conexiones a ningún POD de nuestro cluster, ya que no hay ningún selector definido en el mismo.

Podemos realizar esta comprobación usando el subcomando describe, el cual sabemos que nos proprociona mucha información, o bien podemos usar el comando get sobre objectos específicos y además indicar que nos muestre las etiquetas de los objetos. Así, por ejemplo, podemos mostrar solo los PODs existentes en el cluster con sus etiquetas usando el siguiente comando:

Mostrando las etiquetas de los PODs
Mostrando las etiquetas de los PODs.

Como podemos ver, la definición de este POD no contiene ninguna etiqueta. Para solucionar esto podemos modificar el fichero con la definición del POD, añadir las etiquetas necesarias y volver a aplicar la configuración al cluster o bien, podemos usar la opción --overwrite para modificar dicho campo. Esto último lo haríamos más o menos del siguiente modo:

Modificación de la etiqueta de un POD
Modificación de la etiqueta de un POD.

Como vemos, este cambio se realiza en caliente y es aditivo, es decir, podemos añadir tantas etiquetas como necesitemos al POD. Es importante tener en cuenta que, al hacer el cambio del campo label por comando, debemos cambiar el fichero de definición del POD si estas etiquetas son necesarias ya que el cambio no se aplica al fichero de definición del POD.

Para modificar nuestro servicio tendremos que editar el fichero, añadir el campo selector con la etiqueta necesaria y aplicar de nuevo la configuración al cluster.

Modificamos la definición del servicio y aplicamos al cluster
Modificamos la definición del servicio y aplicamos al cluster.

De este modo, al obtener la descripción del servicio podremos comprobar que en el campo endpoints aparece la IP del POD webserver y que este es accesible desde el exterior:

Servicio configurado y con endpoint disponible
Servicio configurado y con endpoint disponible.

Servicio web accesible
Servicio web accesible.

Hasta aquí hemos revisado y ampliado un poco lo que ya sabíamos y vimos en el post anterior pero, ¿que ventajas nos proporciona desacoplar el servicio del POD?

Supongamos que el equipo de desarrollo ha creado una versión 0 de la aplicación que debemos desplegar. Para esto han modificado la imagen oficial del servidor Apache, han incluido todos los cambios necesarios y han subido la imagen al repositorio interno de imágenes. Partiendo de este caso, podemos hacer un despliegue como el siguiente donde usamos la misma configuración que hemos establecido hasta ahora, es decir, identificamos el POD con la etiqueta application=webapp y establecemos el selector del service para seleccionar los PODs con dicha etiqueta. En este caso hariamos algo parecido a lo siguiente:

Creación del POD con version 0
Creación del POD con version 0.

En el caso de no disponer de un repositorio de imágenes, y estar usando el repositorio de imágenes de Docker local del propio host, debemos añadir la opción imagePullPolicy: Never en la definición del POD ya que por defecto Kubernetes siempre intentará descargar la imagen de Docker Hub.

Con esto podemos comprobar que el objeto service tiene un endpoint y que podemos acceder al servicio web:

Descripción del servicio webservice
Descripción del servicio webservice.

Acceso al servicio web V0
Acceso al servicio web V0.

Si ahora tenemos que desplegar una nueva versión del servicio web, supongamos que nos proporcionan la imagen de la versión 1 del mismo servicio web, podemos definir y aplicar al cluster el siguiente POD:

Definición del nuevo POD v1
Definición del nuevo POD v1.

Aplicamos la definición del POD v1
Aplicamos la definición del POD v1.
 
Con esto vemos que, en un mismo fichero, podemos incluir la definición de diferentes objetos. Al aplicar la definición al cluster, Kubernetes comprueba la configuración de todos los objetos ya existentes y aplica los cambios que puedan existir o indica que los objetos no han cambiado.

Al llegar a este punto, y teniendo en cuenta cual es el selector del servicio webservice, ¿cual es el resultado obtenido? Como ambos PODs tienen la misma etiqueta y el selector del servicio redirige el tráfico a cualquier pod con la etiqueta application=webapp estaríamos sirviendo ambas versiones del servicio web simultaneamente. Esto podemos verlo al hacer la descripción del servicio y comprobar que hay dos endpoints, uno correspondiente a cada POD. Además, al acceder al servicio accederíamos a ambas versiones:

Servicio con endpoints en PODs v0 y v1
Servicio con endpoints en PODs v0 y v1.

Acceso al servicio web v1
Acceso al servicio web v1.

Como es lógico esta situación no es la que queremos, ya que serviríamos ambas versiones simultaneamente a los clientes. Por tanto, para evitar esto, es necesario que definamos correctamente las labels de nuestros PODs y especifiquemos correctamente el selector del objeto service. Para corregirlo haríamos algo como lo siguiente:

Añadimos etiquetas a cada POD con la versión
Añadimos etiquetas a cada POD con la versión.

Modificacióon del selector del objeto service
Modificacióon del selector del objeto service.

Con esta modificación, y como el campo selector de un objeto service hace un and de todas las etiquetas que especifiquemos, ahora solamente hay un endpoint disponible para el servicio y por tanto solo servimos el servicio web correcto.

Servicio web con el endpoint correcto
Servicio web con el endpoint correcto.

En resumen, está claro que desacoplar el servicio del POD nos proporciona mucha flexibilidad a la hora de hacer despliegues de servicios.

Como veremos más adelante, lo habitual no es trabajar directamente con PODs y services de este modo, pero es importante entender como funcionan y se relacionan entre ellos. Esto nos permitirá comprender objetos más complejos, que se basan en estos y que veremos que proporcionan muchas características y funcionalidades adicionales.