Saltar al contenido principal

Docker Swarm

Introducción

Docker Swarm es el sistema de orquestación de Docker, igual que Kubernetes, es capaz de desplegar los contenedores repartidos entre los diferentes nodos y gestionar la red entre ellos.

Se puede desplegar tanto en services independientes (similar al despliegue de un contenedor) o stack que es lo que yo utilizaré, este último es el más similar a Kubernetes: un fichero yaml que contiene todos los contenedores, redes y configuraciones para su despliegue.

El formato de los stack es casi idéntico al de Docker-Compose, que es su versión para desarrollo (y sin cluster), stack puede hacer prácticamente lo mismo descartando los builds, debido a que stack está pensado para entornos de producción donde los contenedores ya están creados.

En Swarm, todos los puertos publicados por los servicios son accesibles en todos los nodos, no importa si es un manager o un worker, la diferencia entre ellos es que un manager puede controlar los stack y servicios mientras que un worker no puede.

Antes de desplegar un Swarm, hay que tener en cuenta las recomendaciones de cantidad de managers, simplificando mucho: debería haber un número impar de managers, en este caso son 3.

Instalación

Empezamos instalando las dependencias.

sudo apt-get update
sudo apt-get install -y \
apt-transport-https \
ca-certificates \
curl \
gnupg-agent \
software-properties-common

Añadimos el repositorio oficial.

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) \
stable"
caution

Existe un script rápido de instalación desde https://get.docker.com, pero este no está recomendado para instalaciones de producción, únicamente para desarrollo y test.

Instalamos el servicio.

sudo apt-get update
sudo apt-get -y install docker-ce docker-ce-cli containerd.io

Esto es algo opcional (y peligroso), permite que el usuario pueda utilizar docker sin permisos de root, únicamente deberíamos utilizarlo si no creemos que sea un problema para la seguridad.

sudo usermod -aG docker your-user
important

Debemos reiniciar la sesión para que se aplique este cambio.

Iniciar el clúster

Iniciamos el clúster en uno de los nodos indicando la IP desde la que se recibirá a los demás.

sudo docker swarm init --advertise-addr 192.168.1.100

Con esto ya tendremos el clúster iniciado, Swarm únicamente necesita un nodo para funcionar, esto nos permite empezar fácilmente con Swarm, y ya crecerá con nuevos nodos cuando sea posible.

caution

Un Swarm con un único nodo no tiene el problema de los volúmenes compartidos, así que GlusterFS y NFS pueden no ser necesarios, pero si se añade un nuevo nodo, se necesitará resolver este problema.

Encriptación de redes

Al iniciar Swarm, este genera una nueva red ingress por la que compartirá internamente datos, cuando preguntamos por un puerto a un nodo, y este no tiene ese contenedor, Swarm utilizará esta red para comunicar con quien tenga el contenedor y así poder publicar la información.

Por defecto esta red no está encriptada, el mejor momento para modificarla es antes de añadir el resto de los nodos (aunque es posible hacerlo a posterior).

Borramos la red actual y la volvemos a crear encriptada.

sudo docker network rm ingress
sudo docker network create \
--driver overlay \
--opt encrypted \
--ingress \
--subnet=10.0.0.0/24 \
--gateway=10.0.0.1 \
--opt com.docker.network.driver.mtu=1200 \
ingress

Añadir nodos

Al iniciar el nodo, nos muestra el comando a utilizar para añadir otros nodos con el rol de workers, no es necesario anotarlo ya que podemos volver a generar un token para manager o worker en cualquier momento.

sudo docker swarm join-token manager
sudo docker swarm join-token worker

También es posible cambiar el rol de un nodo ya añadido, pero es más práctico añadirlos con el rol final.

sudo docker swarm join --token <token> 192.168.1.100:2377

Una vez unidos, podremos ver todos los nodos y sus roles.

sudo docker node ls

Repositorios privados

Si no se indica lo contrario, Docker siempre va a Docker hub a buscar los contenedores, si se indica el nombre de domino del servidor antes del contenedor, irá a ese servidor a buscarlo, pero normalmente los servidores privados o internos utilizan certificados propios, al no ser válidos, no podrá acceder, así que se deben configurar.

En este punto aún no se ha configurado el registry de Nexus para ello, pero debido a que es uno de los servicios que se van a desplegar, vale la pena dejar todos los nodos configurados para utilizarlo cuando esté activo.

Para ello hay que crear una carpeta con el dominio de cada registry y copiar dentro el certificado CA.

Este es un ejemplo para los dominios internos, el externo debería utilizar un certificado válido, así que no es necesario incluirlo.

sudo mkdir -p /etc/docker/certs.d/docker-registry.domain.intranet
sudo mkdir /etc/docker/certs.d/docker-public.domain.intranet

Gluster y Docker

La gran mayoría de servicios de Docker requieren de una carpeta compartida para almacenar sus datos y sincronizarla entre todos los nodos, para ello hemos añadido Gluster. Esto significa que los servicios de Docker nunca deben iniciar si Gluster o sus carpetas compartidas no están preparadas. La experiencia me ha enseñado que la mitad de las veces se inicia incorrectamente, las carpetas compartidas no se inician y Docker arranca sin nada preparado.

Esto significa que los servicios que deberían apuntar a una carpeta compartida y sincronizada apunten a la carpeta del host, que está vacía. Debido a que esta carpeta existe, el servicio se iniciará correctamente, pero no contendrá nada, así que habrá "olvidado" todo. Lo peor es que funcionará, el servicio no se caerá por "perder" sus datos, no hay forma de detectar que el servicio está escribiendo en el lugar incorrecto y además empezará a escribir datos donde no debe.

Este error no es aceptable en un clúster, sea del entorno que sea.

important

Esto únicamente ocurre al iniciar el Linux, una vez está iniciado, el proceso funciona correctamente.

Para solucionar esto hay 3 opciones:

  • Modificar el arrancador de Docker para que espere por Gluster y las carpetas compartidas.
  • Delegar el montado de particiones a Docker en vez de al sistema.
  • Quitar el autoarranque de Docker e iniciar manualmente.

Esperar por Gluster

Tras varias pruebas, he llegado a la conclusión de que el iniciador de Docker no espera por nada ni nadie, y aun menos por carpetas compartidas.

El arrancador permite indicar que debe esperar por otros, pero hasta el momento no he conseguido que funcione, posiblemente por el docker.socket que también es capaz de iniciar el servicio, sea como sea, he descartado esta opción.

Gluster gestionado por Docker

Tanto Docker como Kubernetes permiten plugins para gestionar volúmenes. Existen algunos plugins para gestionar GlusterFS, pero todos ellos tienen errores con ciertas versiones de Docker y de GlusterFS, lo que los hace inestables.

Quizás hoy consigas que el plugin funcione, pero tras la siguiente actualización de Docker quizás ya nada funcione, inicialmente descarté el uso de plugins al ver este problema. Además, todos los plugins que he visto están abandonados.

Anular el arranque de Docker

Finalmente he llegado a la conclusión de que es mejor quitar el arrancador de Docker, más adelante se puede crear uno propio siguiendo el proceso manual de arranque.

Lo primero es quitar ambos servicios del autoarranque, es buena idea comprobar que siguen así tras cada actualización de Docker.

sudo systemctl disable docker.socket
sudo systemctl disable docker.service

Cada vez que se reinicia, se debe confirmar que Gluster esté sincronizado, montar las carpetas compartidas por si no lo estuvieran ya y comprobar que realmente estén todas.

sudo gluster pool list
sudo mount -a
mount | grep "/mnt"

Si todo es correcto, se inicia el servicio.

sudo systemctl start docker.socket
sudo systemctl start docker.service