Docker swarm

Qué es Swarm

Características principales:

  • Se utiliza Docker Engine CLI para administrar swarm, por lo que no necesitamos ningún software adicional
  • Diseño descentralizado: la direferenciación de los roles dentro del cluster se realiza en tiempo de ejecución, por lo que podemos utilizar la misma imagen para los diferentes roles
  • Modelo declarativo: se utiliza el modo declarativo para describir los servicios.
  • Escalado: por cada servicio se puede indicar el número de tareas o instancias a ejecutar. Gestión automatica tanto del aumento como la reducción de número de instancias por servicio
  • DSC: el nodo de gestión monitoriza constantemente el estado del cluster y los nodos para corregir las posibles desviaciones del estado deseado declarado. Si, por ejemplo, se declara que un servicio tiene que ejecutar 4 instancias y se cae un nodo que ejecuta 2 de esas instancias, el manager se encarga de crear otras 2 instancias en el resto de los nodos disponibles.
  • Red multi-host: swarm permite crear una red para los servicios que es común a todos los hosts (overlay network)
  • Descubrimiento de servicios: para cada servicio que se ejecuta se asigna un nombre DNS único utilizando un servidor DNS embebido en swarm
  • Balanceo de carga: se pueden exponer los puertos de los servicios a un balanceador de carga externo. Swarm permite definir como distribuir la carga de los servicios entre los nodos.
  • Seguro por defecto: swarm utiliza TLS para la autenticación y encriptación de la comunicación entre los nodos, permitiendo la opción de utilizar certificados auto-firmados o de una CA personalizada.

 

Conceptos clave

Es el módulo de Docker encargado de gestionar clusters y orquestar servicios. Está basado en swarmkit

Swarm está formado por múltiples hosts Docker que se ejecutan con uno de los dos roles:

  • Swarm Manager: gestión y administración
  • Swarm Worker: ejecución de los servicios

Un host Docker puede ser manager, worker o los 2 roles.

Cuando creamos un servicio, definimos su estado óptimo:

  • Número de replicas
  • Red
  • Recursos de almacenamiento
  • Puertos expuestos

Swarm se encarga de mantener ese estado óptimo gestionando la ejecución de los contenedores que mantienen esa configuración deseada.

A la ejecución de un contenedor que es parte de un servicio swarm se le denomina tareas (task). Hay que diferenciar los contenedores que se ejecutan de forma individuarl (standalone) o los que se ejecutan como parte de un servicio swarm:

  • En un host podemos ejecutar contenedores standalone y tasks
  • Los cambios de configuración los gestiona swarm. Por ejemplo, ante un cambio de configuración de un servicio, es swarm quien actualiza la información, detiene las tareas que no están actualizadas y crea las nuevas con la nueva configuración. En un contenedor standalone estos pasos hay que realizarlos de forma manual.
  • Los servicios swarm sólo se pueden ejecutar en un cluster swarm

 

Nodos (Nodes)

Un nodo es na instancia de Docker participando en swarm. Se pueden ejecutar uno o más nodos en un equipo físico, pero en un entorno típico se distribuyen los nodos por diferentes máquinas físicas.

Al desplegar una aplicación en swarm, indicamos la definición del servicio en un manager node. El manager node se encarga de que las unidades de trabajo (tasks) se ejecuten en los worker nodes.

El Manager node tambén se encarga de ejecutar las tareas destinadas a mantener el cluster y el estado deseado de los servicios. Pueden existir varios nodos manager en un cluster, y entre ellos se elegirá un lider encargado de las tareas de orquestación.

Los Worker nodes reciben y ejecutan las tareas indicadas por los manager nodes. Por defecto los manager nodes tambíen ejecutan tareas como worker nodes, pero se puede configurar que funcionen como manager nodes de forma exclusiva. Los worker nodes ejecutan un agente encargado de informar al manager del estado de las tareas que se le ha asignado.

 

Servicios y tareas (Services and tasks)

Un servicio (service) es la definición de las tareas que se ejecutan en un worker node. Es el objeto principal de la estructura que se define en un sistema swarm.

Cuando se crea un servicio se indica que imagen del contenedor se va a utilizar y que comandos se van a ejecutar en el contenedor.

En servicios replicados, se indca un número de tareas réplica que son necesarias en la ejecución y el manager node se encarga de que los worker nodes ejecuten ese número de instancias como estado deseado de ejecución.

Hay servicios globales de los cuales swarm se encarga de que se ejecute una instancia en cada uno de los nodos del cluster disponible. Pueden ser servicios globales agentes de monitorización, antivirus …

Una vez que una tarea se ha asignado a un nodo, no puede ser movida a otro nodo. O se ejecuta en ese nodo o muere.

 

Balanceo de carga (Load balancing)

Swarm manager utiliza balanceo de carga en el ingreso de tráfico para exponer los servicios que queremos que estén disponibles externamente. Swarm asigna automáticamente un puerto publicado al servicio (PublishedPort) o se puede configurar de forma manual. Si no se indica el puerto de forma manual, swarm manager asigna un puerto del rango 30000-32767

Componentes externos, como un balanceador de carga, pueden dar acceso al servicio a través del puerto publicado, accediendo a cualquiera de los nodos del cluster, indipendientemente de que ese nodo tenga tareas del servicio ejecutándose. Todos los nodos se encargan de enrutar las conexiones entrantes a una instancia de una tarea en ejecución.

Swarm tiene un servidor DNS embebido internamente que asigna de forma automática nombres a cada servicio swarm. Swarm manager utiliza estos nombres para distribuir las peticiones de forma interna.

 

 

Laboratorio

Vamos a utilizar 3 nodos:

  • srv-dck-001.lab-01.local (192.168.3.11)
  • srv-dck-002.lab-01.local (192.168.3.12)
  • srv-dck-003.lab-01.local (192.168.3.13)

Cada una de ellas con la siguiente configuración:

  • CentOS 7
  • 2 vCPUs
  • 8 GB RAM
  • 8GB de disco
  • Docker CE 18.02 instalado

 

Vamos a distribuir los roles de la siguiente forma:

  • 1 Manager:
    • srv-dck-001: 192.168.1.51
  • 2 Worker
    • srv-dck-002: 192.168.1.52
    • srv-dck-003: 192.168.1.53

 

Vamos ha habilitar los siguientes puertos en el cortafuegos:

  • TCP port 2377 for cluster management communications
  • TCP and UDP port 7946 for communication among nodes
  • UDP port 4789 for overlay network traffic

 

 

 

Crear un Swarm

En el manager node ejecutamos

en mi caso:

El resto de nodos tienen que tener acceso a la IP 192.168.3.11

Para ver el estado del nodo ejecutamos docker info

 

Comprobamos los nodos del cluster con docker node ls

 

Añadimos nodos a swarm

Al crear el cluster swarm en el manager, como resultado del comando docker swarm init se mostró el comando que debemos utilizar para añadir nodos al cluster

Si no tenemos a mano el comando a utilizar, en el manager ejecutamos docker swarm join-token worker

Comprobamos los nodos que pertenecen al cluster, volviendo a ejecutar docker node ls en el manager

Si ejecutamos este comando (docker node ls) en un worker node nos muestra el siguiente mensaje:

 

Desplegamos un servicio

Como ejemplo de servicio vamos a desplegar un contenedor helloworld

  • docker service create: es el comando para crear un servicio
  • –replicas 1: indica que el estado deseado es la ejecución de 1 intancia
  • –name:  indica el nombre del servicio (helloworld)
  • alpine: indica que se va a utilizar una imagen del contenedor Alpine Linux
  • ping docker.com: indica el comando que va a ejecutar el contenedor

 

Comprobamos el listado de servicios que se están ejecutando

Tras unos segundos

Inspeccionamos el servicio creado con el comando docker service inspect –pretty <SERVICE-ID>

Si ejecutamos el mismo comando sin el parámetro –pretty obtenemos la información en fomato json

Para ver en que nodo se están ejecutando los contenedores asociados al servicio ejecutamos docker service ps <SERVICE-ID>

En este caso el contenedor se está ejecutando en srv-dck-001, que es nuestro nodo manager. Esto es así, porque por defecto el nodo manager también funciona con el rol de worker.

Podemos ejecutar docker ps en el nodo donde se ejecuta la tarea para comprobar la ejecución del contenedor

En este primer ejemplo hemos desplegado un servicio con 1 instancia del contenedor alpine. De una forma muy sencilla vamos a aumentar el número de instancias de las tareas que ejecutan el servicio con el comando docker service scale <SERVICE-ID>=<NUMBER-OF-TASKS>

Vemos el estado del servicio

 

Y las tareas que se están ejecutando y su ubicación:

Como podemos ver se han creado 4 nuevas tareas, de forma que ahora tenemos 5 intancias de alpine ejecutandose en los 3 nodos: 2 instancias en el nodo 1 (aunque sea el manager) 1 instancia en el nodo 2 y 2 instancias en el nodo 3.

Podemos ver los contenedores por cada uno de los nodos

 

Ahora vamos a ver como reducir el número de instancias de una forma igual de sencilla

Comprobamos el estado del servicio

Vemos que el sistema está eliminando 2 de las instancias que se estaban ejecutando, y tras unos segundos vemos que el número de replicas en ejecución y las deseadas coinciden.

 

Para finalizar con este ejemplo vamos a borrar el servicio con docker service rm <SERVICE-ID>

Y comprobamos que efectivamente se ha borrado

Y ya no tenemos ningún contenedor ejecutandose en los diferentes nodos

 

 

 

Actualizaciones del servicio

Vamos a crear un nuevo servicio y ver como es el proceso de actualización de las versiones de los contenedores que se ejecutan en el servicio.

Creamos un servicio con la versión 5.0.0 de grafana y 3 instancias

  • –replicas: es el número de tareas
  • –name: es el nombre del servicio
  • –update-delay: es el tiempo que transcurre entre la actualziación de cada una de las tareas.
  • grafana/grafana:5.0.0: es la imagen que vamos a utilizar

Por defecto, se actualiza una tarea cada vez. Podemos modificar este comportamiento con el parámetro –update-parallelism para indicar el número máximo de tareas que se pueden ejecutar de forma simultánea.

 

El proceso de actualización es el siguiente:

  • El manager planifica la actualización de una de las tareas (o varias si se ha indicado un valor mayor que 1 para el paralelismo de las actualizaciones)
    • Se para el contenedor de la tarea
    • Se actualiza el contenedor
    • Se inicia el contenedor actualizado con la nueva imagen
  • Cuando esta tarea de actualización devuelve el estado RUNNING, se planifica la actualización de la siguiente tarea tras el tiempo configurado de espera
  • Si alguna de las tareas devuelve el estado FAILED, se pausan las tareas de actualización. Para reiniciar una tarea pausada ejecutamos docker service update <SERVICE-ID>

Comprobamos e inspeccionamos el estado del servicio

 

Vamos a actualizar el servicio a la versión 5.0.1 de grafana

 

Durante la ejecución podemos ver el estado de las tareas

Durante el tiempo que dura la actualización, podemos ver que algunas instancias ejecutan la versión 5.0.0 mientras otras ya han sido actualizadas a la versión 5.0.1

 

Al terminar la tarea comprobamos el estado del servicio

 

 

Podemos ver que la tarea ya ha terminado y que todas las instancias ejecutan la versión 5.0.1

 

Poner un nodo en modo mantenimiento (drain a node)

Un manager asigna tareas a los nodos que estén en estado ACTIVE. En ocasiones, es necesario realizar tareas de mantenimiento en un nodo de forma que no queremos que se ejecuten tareas en el mismo.

Vamos a seguir con el ejemplo anterior y comprobamos el estado de los nodos:

Vemos que todos los nodos tenemos Active en la columna AVAILABILITY

Comprobamos la ejecución de los servicios

Vemos que tenemos instancias ejecutándose en cada uno de los nodos.

Para poner un nodo en modo mantenimiento ejecutamos docker node update –availability drain <NODE-ID>

Volvemos a comprobar el estado de los nodos

Ahora comprobamos el estado de ejecución del servicio

Podemos ver que la tarea grafana.2 ha pasado de ejecutarse en el nodo 003 al nodo 001 manteniendo el número de instancias totales.

Si aumentamos el número de instancias, podemos comprobar que no se utiliza el nodo 003

 

Vemos que las nuevas instancias se ejecutan en los nodos 001 y 002

Para volver a activar el nodo 003 ejecutamos docker node update –availability active <NODE-ID>

Al volver a activar el nodo 003, las tareas no se vuelven a balancear entre los nodos disponibles. El nodo recibirá tareas:

  • Si se vuelve a dimensionar el número de réplicas
  • Se se lanza un proceso de actualización
  • Cuando otro nodo se ponga en estado drain
  • Cuando una tarea falle en otro nodo

Accedo al servicio

 

Para permitir el acceso a los servicios swarm utiliza una malla de red (routing mesh) que permite acceder a los servicios publicados utilizando la dirección de cada uno de los nodos, aunque ese nodo no tenga ninguna tarea del servicio ejecutándose, en este caso, swarm se encargará de redirigir la petición a una de las tareas que se ejecuta en otro nodo.

 

Nota: con CentOS 7 he tenido problemas para hacerlo funcionar abriendo los puertos necesarios, por lo que he optado por deshabilitar el servicio firewalld

Publicar un puerto para un servicio

Para permitir el acceso a un servicio tenemos que utilizar el parámetro –publish.

Al crear el servicio, utilizamos:

  • –publish: es el parámetro para publicar el puerto
  • published=<PUBLISHED-PORT>: es el puerto publicado y que utilizaremos para acceder al servicio. Si no se indica se utiliza un puerto aleatorio.
  • target=<CONTAINER-PORT>: es el puerto utilizado por el contenedor

Para modificar un servicio ya creado, utilizamos:

 

Podemos indicar si vamos a utilizar TCP o UDP para cada uno de los puertos:

  • TCP

  • TCP y UDP

  • UDP

 

Ejemplo de publicación

Siguiendo con el ejemplo que estabamos utilizando de grafana, vamos a recrear el servicio en el cluster swarm, pero esta vez vamos a crear 2 instancias

 

De esta forma, cuando accedemos al puerto 8080 de cualquiera de los hosts, Docker redirecciona la petición al puerto 3000 de alguno de los contenedores de las tareas que se ejecutan en los nodos.

Podemos comprobar donde se están ejecutando las tareas

 

Comprobamos los contenedores en cada uno de los nodos

 

La “malla” que se crea es la siguiente

 

 

Para comprobar los accesos, vamos a acceder a las ips de cada uno de los hosts y comprobar que accediendo a cualquiera de ellos, accedemos al servicio

 

Enrutamiento en modo host

Si no queremos utilizar el modo “malla” (mesh) podemos utilizar el modo “host” que nos permite:

  • Si accedemos a la IP de un nodo, accedemos siempre a una de las tareas que se ejecutan en ese nodo
  • Si un nodo no tiene tareas del servicio ejecutando, el servicio no escucha en ese puerto en el nodo
  • Si utilizamos más tareas que nodos, no podemos especificar un puerto específico de escucha.

Para cambiar el modo, utilizamos el parámetro mode, por ejemplo:

 

 

Utilizar un balanceador externo

La forma más común de acceder a un servicio del que tenemos varias tareas replicadas por varios nodos es utilizar un balanceador externo. Por ejemplo, podemos publicar con HAProxy en una dirección IP externa el virtual server que utiliza los servicios de los 3 nodos.

 

 

En mi ejemplo de configuración de HAProxy (lo utilizo en un appliance de OPNsense)

En una próxima entrada profundizaremos en otros aspectos de la ejecución de servicios con swarm, como pueden ser la asignación de recursos (CPU y memoria), preferencias de ubicación de los servicios, uso de volúmenes, uso de secrets…

 

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.