Aplicaciones multi-contenedor en un clúster con Swarm

Ya has pasado de la fase de tener una aplicación en un solo contenedor a trabajar con aplicaciones multi-contenedor con docker-compose. Sin embargo, hasta ahora, todo se está ejecutando en una sola máquina y con una sola réplica por contenedor, lo cual hace que tu aplicación no sea ni lo suficientemente escalable ni tolerante a fallos. Hoy vamos a dar un paso más viendo cómo puedes trabajar con aplicaciones en Docker repartidas por varias máquinas de un clúster, utilizando Swarm.

Swarm o “Enjambre”

Swarm, en español enjambre, se trata de una herramienta que nos ayuda a gestionar y orquestar contenedores en un clúster. Gracias a que en Docker también podemos trabajar con este concepto, podremos asegurarnos de que nuestras aplicaciones soportan la demanda, además de ser tolerantes a fallos, teniendo más de una copia del mismo contenedor en más de una máquina o nodo.
Dentro de este clúster, existirán dos roles: manager y worker. El primero de ellos es el que es capaz de mandar órdenes al resto, para administrar el clúster. Los workers simplemente hospedan los contendores.
Como es posible que los nodos sean físicos o virtuales, lo primero que vamos a preparar son máquinas con estos roles de una forma muy sencilla con docker-machine.

Máquinas en swarm mode con docker-machine

Para poder disponer de un conjunto de máquinas de manera súper rápida podemos hacer uso de docker-machine. Lo que hace, básicamente, es crear y gestionar máquinas virtuales que, dependiendo del sistema operativo que estés utilizando, pueden ser sobre VirtualBox o Hyper-V, si estás en Windows 10. Además, como en los volúmenes, aquí también existe el concepto de drivers, por lo que también es posible crear estas máquinas en un entorno remoto. En este ejemplo usaré VirtualBox para que sea independiente del sistema operativo. Los comandos a lanzar serán los siguiente:

docker-machine create --driver virtualbox vm-manager
docker-machine create --driver virtualbox vm-worker1
docker-machine create --driver virtualbox vm-worker2

Cuando termine la creación de cada una de las máquinas es posible ver un listado de todas ellas a través del siguiente comando:

docker-machine ls
docker-machine ls para ver las máquinas que tienes disponibles

Como te puedes imaginar, vm-manager será a la que le otorgarás el rol de manager. Para hacerlo puedes lanzar el siguiente comando, que a través de docker-machine ssh ejecuta docker swarm init en dicha máquina, que lo que hace es inicializar un nuevo clúster.

docker-machine ssh vm-manager "docker swarm init --advertise-addr 192.168.99.102"

Además, te devolverá el comando que necesitas para que otras máquinas se unan al mismo.

docker swarm init

Cópialo y, de la misma manera que ejecutaste el comando en vm-manager, ejecuta este otro en vm-worker1 y vm-worker 2 para que se unan.

docker-machine ssh vm-worker1 "docker swarm join \
--token SWMTKN-1-0jmngpcawsd089d88lihjsfmztg6fqakq0cl251v0ccjugy9ra-az2kvtarkzhj3ajk98ckgd38b \
192.168.99.102:2377"

Una vez que lo hayas hecho con las dos máquinas, desde vm-manager puedes listar todos los nodos disponibles:

docker-machine ssh vm-manager "docker node ls"

El resultado debería de ser: un nodo como manager y dos como workers.

Con docker node ls puede ver todos los nodos que están registrados en tu clúster

Desplegar una aplicación en el clúster

Para finalizar, vamos a retomar nuestra aplicación multi-contenedor que utilicé en el artículo de aplicaciones multi-contenedor con docker-compose” y  la  vamos a desplegar en nuestro nuevo clúster.

Al igual que cuando trabajábamos con docker compose, aquí también es necesario un archivo docker-compose.yml donde se define la configuración de nuestra aplicación, pero con algunas propiedades más:

version: '3.7'
services:
  frontend:    
    image: 0gis0/frontend
    deploy:
      replicas: 5
    ports:
      - 4000:3000
    depends_on:
      - backend
    networks:
      - webnet
  backend:
    image: 0gis0/backend
    deploy:
      replicas: 2
    ports:
      - 8080:8080
    depends_on:
      - mongodb
    networks:
      - webnet
  mongodb:
    image: mongo:latest
    networks:
      - webnet
networks:
  webnet:
  • replicas: se utiliza para decir cuántos contenedores quieres de una misma imagen.
  • networks: se utiliza para definir y para establecer en qué red estarán mis contenedores. Esta propiedad es muy importante ya que sin ella los distintos tipos de contenedores no se podrían ver entre sí internamente.

Obviamente hay muchas otras opciones que podemos configurar pero, en este caso, estas son las mínimas que necesitarás para que todo funcione correctamente.

Antes de poder ejecutar esta configuración, debemos hacer dos cosas:

Las imagenes de backend y frontend deben de estar alojadas en un registro, para que los nodos de mi clúster puedan acceder a ellas (sino tendrías que ir nodo por nodo construyendo la imagen a partir del código ¿te imaginas?). Hace unos días te enseñe cómo hacerlo para Docker Hub y Azure Container Registry. Para este ejemplo utilizaré Docker Hub, renombrado mis imágenes locales de la siguiente forma:

docker tag frontend 0gis0/frontend
docker tag backend 0gis0/backend

Para subirlas al registro solo debes hacer push de cada una de ellas.

docker push 0gis0/frontend
docker push 0gis0/backend

Como es lógico, el archivo docker-compose.yml lo has creado en tu máquina local, pero realmente necesitas lanzarlo desde vm-manager, que es quien puede mandar órdenes a los nodos que tiene tu clúster. Existe una forma muy cómoda para mapear el terminal de vm-manger con el terminal de nuestro equipo local:

docker-machine env vm-manager

Al lanzar este comando se generan unas variables de entorno que nos permiten hacer esta conexión. Ahora ejecuta el siguiente para terminar con la configuración.

eval $(docker-machine env vm-manager)

Sin ningún tipo de aviso, ahora ya puedes lanzar comandos en el terminal como si estuvieras dentro de vm-manager, pero además puedes utilizar todo de lo que dispones en tu máquina local, como el archivo docker-compose.yml. Para materializar todo lo descrito en él debes aprender otro comando más, docker stack, que funciona de la siguiente manera:

docker stack deploy -c docker-compose.yml myapp

Al cabo de unos segundos todos los contenedores con todas sus replicas serán repartidos entre todos los nodos de tu clúster. Para ver el estado usa este comando:

docker stack ps myapp

Podrás ver que tu aplicación, o stack, está desplegado correctamente (Running) y podrás acceder al frontend a través del puerto 4000 y al backend a través del puerto 8080 ¿pero de qué IP? Pues en realidad de cualquiera de las IP de cualquiera de los nodos, por ejemplo http://192.168.99.102:4000, ya que todos ellos tienen por delante lo que se conoce como el swarm load balancer, el cual conoce dónde hay réplicas del contenedor al que quieres acceder y nos hace transparente esta gestión.

swarm load balancer
swarm load balancer

Imagen de portada por kyohei ito.

¡Saludos!