Primeros pasos en orquestación de containers con Docker Swarm

Docker es una herramienta indispensable hoy en día. A medida que la arquitectura crece el proceso se vuelve complicado si no poseemos herramientas específicas. La solución: Swarm.

Primeros pasos en orquestación de containers con Docker Swarm

Traducido por Nicole Blasco

If want to see this post in English, click here.

Introducción

Docker se ha convertido en una herramienta indispensable en mi día a día como desarrollador. Desde un principio he intentado incorporarlo en los proyectos en los que participo y, con el tiempo, logré usarlo progresivamente para:

  • Levantar mis "componentes auxiliares" de desarrollo (motores de bases de datos, proxies inversos, servidores redis, etc.)
  • Crear definiciones estáticas de mis componentes auxiliares con docker-compose para compartir entre entornos de desarrollo.
  • Scriptear la creación de mis propios componentes en Dockerfiles e incluirlos en el docker-compose.yml para facilitar el desarrollo de arquitecturas distribuidas.
  • Usar un docker registry privado para publicar mis imágenes y utilizarlas en el docker-compose.yml.
  • Crear un docker-compose.yml alternativo para realizar el despliegue a distintos ambientes.
  • Simplificar los despliegues apoyandome en docker-machine para provisionar o manejar servidores.

Si bien docker-compose y docker-machine ayudan en la administración de servidores, a medida que la infraestructura crece horizontal y verticalmente, el proceso se empieza a volver complicado si no poseemos herramientas específicas para este fin.

La solución que propone Docker para esta problemática se llama Swarm.

Qué es Docker Swarm?

Swarm es una funcionalidad de Docker usada para la organización de clústers de services, que sirve para facilitar la distribución y administración de sistemas dockerizados en infraestructuras multi-host. Para utilizarla no hace falta más que tener instalado el engine de docker ya que desde la versión 1.12 está incluído nativamente en el mismo. Algunos de sus beneficios son:

  • CLI API para creación y administración de clústers de servidores.
  • Modelo de Service simple y declarativo.
  • Escalamiento de Services según la carga.
  • Actualizaciones continuas.
  • Facil y seguras redes distribuídas.
  • Balanceamiento de carga.
  • Descubrimiento de Services.

A continuación voy a hacer un repaso sobre la configuración de un Swarm y el despliegue de una aplicación de micro-servicios en una infraestructura de 3 servidores.

Pre-requisitos
Para seguir este paso a paso hay que partir de un host con las herramientas principales de docker ya instaladas (docker / docker-compose / docker-machine). Para el ejemplo, voy a usar un host Ubuntu (aunque se puede realizar lo mismo en Windows, Mac u otros distros de Linux, con pequeñas adaptaciones). También es necesario tener instalado Virtualbox para la creación de maquinas virtuales.

A su vez, voy a intentar enfocarme en los comandos referentes a Swarm, por lo que es recomendado tener una noción básica de las herramientas mencionadas.

Preparación de las máquinas

El primer paso para lograr un Swarm consiste en la creación y provisión de las máquinas que servirán de nodos para el cluster. En nuestro caso vamos a usar docker-machine para provisionar 3 máquinas virtuales en nuestro host. Vale aclarar que ésto no es necesario para la creación de un Swarm. Basta con tener hosts que puedan comunicarse entre si a través de los puertos que docker requiere (por ejemplo, diferentes máquinas EC2 en AWS).

$ docker-machine create --driver virtualbox manager
$ docker-machine create --driver virtualbox nodo1
$ docker-machine create --driver virtualbox nodo2

Nótese que hemos creado una máquina con el nombre manager. Ésto constituye una de las definiciones principales de Docker Swarm:

Al menos uno de los nodos de un Swarm deberá tener el rol de Manager, mientras que los demás serán Trabajadores.

El nodo manager será el punto de entrada para todas las operaciones que hagamos con el Swarm. Vale aclarar que los nodos Manager funcionan también como Trabajadores.

Una vez creadas, confirmamos la operación y el estado de los nodos ejecutando

$ docker-machine ls

NOMBRE  ACTIVO DRIVER     ESTADO  URL                       SWARM DOCKER ERRORES
manager -      virtualbox Running tcp://192.168.99.100:2376 v17.11.0-ce
nodo1   -      virtualbox Running tcp://192.168.99.101:2376 v17.11.0-ce
nodo2   -      virtualbox Running tcp://192.168.99.102:2376 v17.11.0-ce

Es importante que guardemos el IP del nodo manager (192.168.99.100) ya que lo usaremos pronto.

Creación del Swarm

El siguiente paso consiste en inicializar el Swarm desde el nodo manager y subscribir a los demás Trabajadores.

Para esto vamos a conectarnos primero con el manager utilizando docker-machine.

$ eval $(docker-machine env manager)

Y luego correr el comando que inicializa a docker (del manager) en modo Swarm.

$ docker swarm init --advertise-addr 192.168.99.100

Swarm initialized: current node (lf2kfkj0442vplsxmr05lxjq) is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join \
    --token SWMTKN-1-5g6cl3sw2k76t76o19tc45cnk2b90f9e6vl7rj9iovdj0328o8-a1d8uz0jpya9ijhw2788bqb2p \
    192.168.99.100:2377
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

El flag --advertise-addr especifica la dirección que utilizarán los Trabajadores para registrarse al Swarm. Nosotros usaremos la IP que nos dió previamente docker-machine, aunque en una red interna, es mejor utilizar IPs privadas.

Copiamos el comando. Si necesitamos volver a obtenerlo podemos correr uno de estos dos comandos (siempre desde el manager), dependiendo de si queremos subscribir un Trabajador o un Manager.

$ docker swarm join-token worker
$ docker swarm join-token manager

Subscripción de nodos Trabajadores

Una vez que tenemos creado el Swarm en nuestro nodo Manager, debemos pasar a registrar a los nodos Trabajadores.

Para esto nos conectaremos a uno de los Trabajadores utilizando docker-machine

$ eval $(docker-machine env nodo1)

Y correremos el comando obtenido previamente, exactamente como lo copiamos.

$ docker swarm join \
    --token SWMTKN-1-5g6cl3sw2k76t76o19tc45cnk2b90f9e6vl7rj9iovdj0328o8-a1d8uz0jpya9ijhw2788bqb2p \
    192.168.99.100:2377

This node joined a swarm as a worker

Una vez que repitamos estos dos pasos para cada uno de los Trabajadores, podremos verificar nuestro Swarm volviendo a conectarnos al Manager (con docker-machine) y corriendo:

$ docker node ls

ID                       HOSTNAME ESTADO DISPONIBILIDAD MANAGER
lf2kfkj0442vplsxmr05lxjq manager  Ready  Active         Leader
1m9rdh9hlkktiwaz3vweeppp nodo1    Ready  Active
pes38pa6w8xteze6dj9lxx54 nodo2    Ready  Active

Si todo funcionó correctamente, vamos a obtener una vista de todos los nodos que componen nuestro cluster, indicando cuál es el Manager (Leader) y el estado de cada uno.

Despliegue de la aplicación

En este punto ya tenemos el Swarm creado, compuesto por 3 nodos, uno de ellos Manager y los demás Trabajadores. Como comenté anteriormente, el Manager será el nodo desde el cual operaremos sobre el cluster, de modo que nos conectamos al mismo.

$ eval $(docker-machine env manager)

¡Ya estamos listos para desplegar nuestro Stack! Pero antes de hacerlo, vamos a repasar un poco de la nueva terminología introducida por Swarm:

  • un Stack es una colección de Services que componen nuestro sistema. Se escribe en un archivo YML de forma similar a un archivo docker-compose.yml, con services, networks, volumes, etc.
  • un Service es un componente concreto de nuestro sistema. Éste define, al igual que un docker-compose.yml, su imagen base, variables de entorno, comando a correr, etc. En el contexto de un Swarm, los Services además pueden incluir detalles de deploy para un entorno específico (cantidad de réplicas, restart_policy, etc.).
  • las Task se ubican un nivel por debajo de los Services, y representan instancias concretas de los mismos, son containers en ejecución dentro de un Nodo compatible. Swarm iniciará una Task por cada réplica que requiera cada Service.
  • los Nodos son las unidades de tabajo que dispondrá Swarm para correr las Tasks. Al ser Manager, un Nodo también será el host docker desde donde podremos interactuar con el Swarm.

Si bien Docker permite la creación de Swarm Services de forma manual, aquí vamos a enfocarnos crearlos a través de archivos .yml de stack (similar a docker-compose vs docker run).

Para nuestro ejemplo, vamos a tomar la clasica App de votación de dockersamples que consiste en 5 services (algunas propiedades fueron eliminadas para una mayor simplicidad):

version: "3"

services:
  redis:
    imagen: redis:alpine
    puertos:
      - "6379"
    despliegue:
      replicas: 1
      politica_de_reinicio:
        condicion: en_falla
  db:
    imagen: postgres:9.4
    deploy:
      colocacion:
        restricciones: [modo.rol == manager]
  app:
    imagen: private-repository/image-name:latest
    puertos:
      - 5000:80
    depende_de:
      - redis
      - db
    despliegue:
      replicas: 2
      politica_de_reinicio:
        condicion: en_falla
  visualizer:
    imagen: dockersamples/visualizer:stable
    puertos:
      - "8080"
    volumenes:
      - "/var/run/docker.sock:/var/run/docker.sock"
    despliegue:
      colocacion:
        restricciones: [modo.rol == manager]

Como se puede ver, un docker-stack.yml es muy parecido al tradicional docker-compose.yml. La mayor diferencia es el nuevo elemento deploy, que es utilizado por Docker para customizar las opciones de despliegue para nuestros Services en el contexto de un Swarm.

En nuestro ejemplo solo utilizamos unas pocas opciones de deploy, pero vale la pena detenerse en la documentación sobre este punto ya que aquí se encuentra el mayor poder de Swarm.

Para desplegar nuestro Stack en el Swarm, correremos el siguiente comando:

$ docker stack deploy -c docker-stack.yml mystack

Creating network mystack_default
Creating service mystack_result
Creating service mystack_worker
Creating service mystack_visualizer
Creating service mystack_redis
Creating service mystack_db
Creating service mystack_vote

El último argumento es el nombre del Stack, el cual servirá de prefijo para todos los Services y para usar en ciertos comandos.

A partir de este momento, Swarm comenzará el despliegue creando una Task por cada una de las réplicas de nuestros Services, lo cual terminará siendo sencillamente un container en alguno de los nodos disponibles.

Monitoreo del Swarm

El proceso de despliegue puede tomar un tiempo en terminar, dependiendo del tamaño del stack, pero Swarm ofrece varios comandos para monitoreo, como por ejemplo (siempre conectado al Manager):

  • Listar los Stacks creados:
$ docker stack ls

NOMBRE    SERVICES
mystack   6
  • Listar Services del Stack:
$ docker stack services mystack

ID            NOMBRE          MODO        REPLICAS  IMAGEN                                           PUERTOS
edq3ekyi2fm0  mystack_worker  replicated  0/1       dockersamples/examplevotingapp_worker
o6c2uzibyjsn  mystack_result  replicated  1/1       dockersamples/examplevotingapp_result:before    *:5001->80/tcp
qkvq214nnrcq  mystack_vote    replicated  2/2       dockersamples/examplevotingapp_vote:before      *:5000->80/tcp
r8xx2yfeckeg  mystack_redis   replicated  1/2       redis:alpine                                    *:0->6379/tcp
ui42d228wsem  mystack_visu... replicated  0/1       dockersamples/visualizer:stable                 *:0->8080/tcp
w6qmnih17ucs  mystack_db      replicated  0/1       postgres:9.4

Aqui podremos ver la cantidad de réplicas levantadas para cada Service. También podemos ver que algunas de ellas no están levantadas todavía. Por ejemplo, el Service app todavía no levantó la segunda réplica.

  • Listar Tasks del Stack:
$ docker service ps mystack


ID            NOMBRE                 IMAGEN                                       NODO     ESTADO DESEADO ESTADO ACTUAL            ERROR  PUERTOS
tvbmsi6fdrnf  mystack_worker.1      dockersamples/examplevotingapp_worker         node1    Running        Running 6 minutes ago
pcahy7rhiw5d  mystack_vote.1        dockersamples/examplevotingapp_vote:before    node1    Running        Running 13 minutes ago
s4qtcjypq69l  mystack_vote.2        dockersamples/examplevotingapp_vote:before    manager  Running        Running 9 minutes ago
tobpx1qa14k9  mystack_db.1          postgres:9.4                                  node2    Running        Running 5 minutes ago
taszc0iqdudj  mystack_redis.1       redis:alpine                                  node2    Running        Running 7 minutes ago
t2uoqi4lnt15  mystack_result.1      dockersamples/examplevotingapp_result:before  manager  Running        Running 11 minutes ago
kil75smj8jtk  mystack_redis.2       redis:alpine                                  node1    Running        Running 12 minutes ago
nrug50wf3r5i  mystack_visualizer.1  dockersamples/visualizer:stable               manager  Running        Running 7 minutes ago

Lo que es interesante de este listado es que se puede observar en qué nodo se encuentra cada Task específica, además de si hay algún error.

  • Ver el log de un Service en particular (compuesto por todas las réplicas):
$ docker service logs mystack_app

Habrán notado que en el stack había un Service llamado visualizer que no era parte de nuestro sistema. Este componente expone una página web con una representación gráfica del Swarm, actualizada en tiempo real, junto con detalles relevantes de cada Task.

visualizer

Routing Mesh y acceso a los Services

Swarm ofrece dos estrategias alternativas para resolver el acceso a los Services desde el exterior. La forma más simple se denomina Routing Mesh y funciona de la siguiente manera:

Si tomamos el Service app de ejemplo, veremos que tiene el siguiente port-mapping 5000:80. Esto significa que podremos acceder al puerto 5000 apuntando a la dirección IP de cualquiera de los nodos del Swarm, y Docker resolverá al Task correspondiente de forma transparente (¡Que cool!).

Para el caso de un Service con más de una réplica (como por ejemplo app), Swarm resolverá el balanceo de carga también de forma transparente, de modo que cada vez que accedamos a 192.168.99.100:5000, resolverá el request contra alguno de los Task de app rotativamente.

El Routing Mesh es una de las funciones más atractivas de Swarm, y probablemente sirva para la mayoría de los casos. Para los que no, Swarm ofrece otras formas de configuración manual de Service Discovery y Load Balancing.

Conclusión

La configuración y puesta en marcha de un Docker Swarm ha resultado una experiencia muy positiva. Se encuentra guiado por una excelente documentación y permite configurar una infraestructura compleja, con capacidades avanzadas de administración, monitoreo, y troubleshooting sin poseer conocimientos profundos sobre manejo de servidores, configuración de redes, seguridad, etc.