Mi ODROID-C2 Docker Swarm - Parte 2: Implementando una pila en un Swarm

Deploying Stack Swarm in Docker Swarm

En la Parte 1, puse en práctica servicios en mi clúster ODROID-C2 utilizando la línea de comando Docker. Funciona, pero debería haber una mejor forma de llevar a cabo la implementación, especialmente cuando una aplicación necesita varios componentes que trabajen conjuntamente. Docker 1.13.x ha introducido la nueva función de implementación de pila Docker que permite la utilización de una pila completa de aplicaciones en el Swarm. Una pila es un conjunto de servicios que componen una aplicación. Esta nueva función implementa automáticamente múltiples servicios que están vinculados entre sí, eliminando así la necesidad de definir cada uno por separado. En otras palabras, esto es docker-componer en modo swarm. Para hacer esto, tengo que actualizar mi Docker Engine V1.12.6 que instalé usando apt-get desde el repositorio de software de Ubuntu a la V1.13.x. Teniendo ya compilado la V1.13.1 en mi ODROID-C2 cuando experimentaba sin éxito con el modo swarm hace algunos meses, tal y como comenté en mi anterior artículo, sólo es cuestión de actualizar todos mis nodos ODROID-C2 a la V1.13.1 y estaré listo para trabajar.

La pila httpd-visualizer

Lo primero que hice fue utilizar las mismas aplicaciones (httpd y Visualizer) que en mi anterior artículo usando 'docker stack deploy’. Para hacer esto, necesitaba crear un archivo yaml. En realidad esta es la versión “3” del archivo yaml de docker-compose. Esto era relativamente fácil de hacer ya que no es necesaria la persistencia de datos. Aquí tienes el archivo yaml:

version: "3"
services:
 httpd:
   # simple httpd demo
   image: mrdreambot/arm64-busybox-httpd
   deploy:
     replicas: 3
     restart_policy:
       condition: on-failure
     resources:
       limits:
         cpus: "0.1"
         memory: 20M
   ports:
     - "80:80"
   networks:
     - httpd-net
 visualizer:
   image: mrdreambot/arm64-docker-swarm-visualizer
   ports:
     - "8080:8080"
   volumes:
     - "/var/run/docker.sock:/var/run/docker.sock"
   deploy:
     placement:
       constraints: [node.role == manager]
   networks:
     - httpd-net
networks:
 httpd-net:
Ten en cuenta que el uso de "Networks" en el archivo yaml no es estrictamente necesario. Si lo omitimos, dDocker creará una red de superposición por defecto como verás en una sección más adelante. ¡Las 2 aplicaciones, en este caso, no necesitan hablar entre ellas de todos modos! Para implementarlo, simplemente cambia al directorio donde se encuentra el archivo yaml y ejecuta el comando:
$ docker stack deploy -c simple-stacks.yml httpd-dsv
Esto crea una pila llamada httpd-dsv. Puedes conocer el estado de la pila usando varios comandos de pila, tal y como se muestra en la Figura 1.

Figura 1: Comandos de pila httpd dsv

Puedes apuntar tu navegador al nodo gestor swarm o a cualquier otro nodo del swarm en el puerto 8080 para visualizar la implementación utilizando el Visualizer. La figura 2 muestra un pantallazo de la pantalla de VuShell con la visualización tomada de una implementación de pila anterior:

Figura 2: Visualizador VuShell

Para desmontar la pila, introduce el siguiente comando:

$ docker stack rm httpd-dsv

Migrando mi blog de WordPress a swarm

Para hacer un ejemplo más realista de una implementación de pila, he decidido realizar una prueba migrando mi blog al swarm. Esto es muy útil para mí, ya que me permite iniciar mi blog fácilmente en otro entorno cuando ocurre un desastre. Para hacer esto, tenía que preparar unas cuantas cosas:

  • Crear una copia de la base de datos de WordPress usando mysqldump para crear: mysql.dmp.
  • Utilizar un editor de texto para reemplazar todas las referencias de mi dominio (mrdreambot.ddns.net) en el archivo .dmp por la dirección IP del gestor swarm que es 192.168.1.100.
  • Hacer una copia (comandos Tar) del directorio /var/www/html que contiene scripts y recursos cargados
  • Elegir las imágenes de docker a utilizar: mrdreambot/arm64-mysql y arm64v8/wordpress.
  • Con todo lo anterior, podía crear una implementación de pila docker para mi blog de WordPress.

Estado de persistencia utilizando volúmenes

bind-mount

El primer método por el que opte fue el de usar directorios del host como volúmenes de datos (también llamados volúmenes bind-mount) para la persistencia de datos. El contenido del archivo yaml se muestra a continuación:

version: '3'
services:
  db:
    image: mrdreambot/arm64-mysql
    volumes:
      - /nfs/common/services/wordpress/db_data:/u01/my3306/data
      - /nfs/common/services/wordpress/db_root:/root
    environment:
      MYSQL_ROOT_PASSWORD: Password456
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpressuser
      MYSQL_PASSWORD: Password456
    deploy:
      restart_policy:
        condition: on-failure
      placement:
        constraints: [node.role == manager]
  wordpress:
    depends_on:
      - db
    image: arm64v8/wordpress
    volumes:
      - /nfs/common/services/wordpress/www_src/html:/usr/src/wordpress
      - /nfs/common/services/wordpress/www_data:/var/www/html
    ports:
      - 80:80
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wordpressuser
      WORDPRESS_DB_PASSWORD: Password456
      WORDPRESS_DB_NAME: wordpress
    deploy:
      replicas: 3
      restart_policy:
        condition: on-failure
             placement:
        constraints: [node.role == manager]
Las figuras 3 y 4 muestran las capturas de pantalla para la implementación de la pila.

Figura 3: Implementación del volumen bind-mount de WordPress

Figura 4: WordPress ejecutándose

Posiblemente habrás notado que el sitio de WordPress ha perdido parte de su apariencia personalizada, ya que la imagen del docker arm64v8/wordpress no ofrece ninguna librería o personalización PHP. Como he comentado anteriormente, si no defines las Redes en tu archivo yaml, docker crea automáticamente una red de superposición 'wordpress_default' para la puesta en funcionamiento automática. La red de superposición es necesaria para que WordPress pueda hacer referencia a la base de datos MySQL utilizando su nombre "db" tal y como se define en el archivo yaml:

WORDPRESS_DB_HOST: db: 3306
Los volúmenes de datos justifican ciertas explicaciones. Lo primero a tener en cuenta es que todos los directorios de host utilizados como volúmenes de datos están montados en NFS y accesibles a todos los nodos del swarm.

/nfs/common/services/wordpress/db_data:/u01/my3306/data

El directorio de host /nfs/common/services/wordpress/db_data es un directorio vacío. Es el equivalente al directorio del contenedor /u01/my3306/data donde se encuentra la base de datos MySQL. A continuación, se describe como se crea su contenido.

/nfs/common/services/wordpress/db_root:/root

He completado previamente el directorio del host /nfs/common/services/wordpress/db_root con 2 archivos:

  • run.sh: el script de inicio de MySQL que reemplaza al que se encuentra en el directorio /root del contenedor. Este script es el punto de entrada al contenedor MySQL. Cambié el script para buscar el archivo mysql.dmp ubicado también en /root. Si está, importa el archivo de copia en MySQL que completará el directorio /u01/my3306/data con los datos. Si no hay un archivo mysql.dmp, no se hace nada adicional al proceso habitual.
  • mysql.dmp: el archivo de volcado de la base de datos MySQL de mi blog

Los cambios en el archivo run.sh en comparación con el que viene con la imagen docker MySQL se muestran a continuación:

...
DMP_FILE=/root/mysql.dmp
...
if [ "$MYSQL_DATABASE" ]; then
   mysql -uroot -e "CREATE DATABASE IF NOT EXISTS \`$MYSQL_DATABASE\`"
   if [ -f "$DMP_FILE" ]; then
      mysql -uroot $MYSQL_DATABASE < $DMP_FILE
   fi
fi
...
Ten en cuenta que esto sólo es necesario cuando ejecutas el contenedor por primera vez. La implementación posterior no requerirá de esta distribución de volúmenes ya que la base de datos ha sido configurada durante la primera ejecución. Esto significa que puede comentar esta línea en el archivo yaml tras haber implementado correctamente una vez esta pila:
# - /nfs/common/services/wordpress/db_root:/root

/nfs/common/services/wordpress/www_src/html:/usr/src/wordpress

arm64v8/wordpress activa WordPress copiando los contenidos en su directorio /usr/src/wordpress al directorio /var/www/html en el arranque si /var/www/html no tiene contenido. Al rellenar previamente el directorio del host /nfs/common/services/wordpress/www_src /html con el contenido del archivo tar creado anteriormente, arm64v8/wordpress iniciará WordPress con el contenido de mi blog. Esto solo es necesario cuando ejecutes el contenedor por primera vez. Esto significa que puede comentar esta línea en el archivo yaml tras haber implementado correctamente esta pila una vez:

# - /nfs/common/services/wordpress/www_src/html:/usr/src/wordpress

/nfs/common/services/wordpress/www_data:/var/www/html

El directorio de host /nfs/common/services/wordpress/www_data es un directorio vacío cuyo contenido se activará mediante el script arm64v8/wordpress tal y como se ha descrito anteriormente.

¿Por qué no usar docker-componer?

Es posible que te pregunte por qué no utilice docker-compose para ejecutar el archivo yaml, por ejemplo, usando comandos de una sola vez como sugiere la documentación de docker. La razón es que el docker-compose que instalé usando apt-get es la versión 1.8.0, que no entiende la versión 3 del archivo yaml del docker-compose, que es necesario para "docker stack deploy". Intenté compilar la última versión de docker-compose desde la fuente pero no tuve éxito. Esta es la razón por la que no estoy usando docker-componer.

Persistencia del estado usando volúmenes de almacenamiento compartido

El uso de volúmenes bind-mount depende del host. El uso de volúmenes compartidos tiene la ventaja de ser independientes del host. Puede haber un volumen compartido disponible en cualquier host en el que se inicie un contenedor siempre que tenga acceso al back-end del almacenamiento compartido y tenga instalado el plugin (controlador) del volumen adecuado, el cual te permita usar diferentes back-end de almacenamiento, como, por ejemplo: Amazon EC2, GCE, Isilon, ScaleIO, Glusterfs, solo por nombrar unos cuantos. Existes muchos drivers o plugins de volumen disponibles como Flocker, Rex-Ray, etc. Desafortunadamente, no hay binarios para esos complementos disponibles para máquinas ARM64 como ODROID-C2. Afortunadamente, el driver 'local' integrado soporta NFS. Es el driver que yo estoy usando para la implementación de volumen compartido. El archivo yaml para esto es el siguiente:

version: '3'
services:
  db:
    image: mrdreambot/arm64-mysql
    volumes:
      - db_data:/u01/my3306/data
#       - /nfs/common/services/wordpress/db_root:/root
    environment:
      MYSQL_ROOT_PASSWORD: Password456
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpressuser
      MYSQL_PASSWORD: Password456
    deploy:
      placement:
        constraints: [node.role == manager]
      replicas: 1
      restart_policy:
        condition: on-failure
  wordpress:
    depends_on:
      - db
    image: arm64v8/wordpress
    volumes:
#       - /nfs/common/services/wordpress/www_src/html:/usr/src/wordpress
      - www_html:/var/www/html
    ports:
      - "80:80"
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wordpressuser
      WORDPRESS_DB_PASSWORD: Password456
      WORDPRESS_DB_NAME: wordpress
    deploy:
#       placement:
#         constraints: [node.role == manager]
      replicas: 3
      restart_policy:
        condition: on-failure
volumes:
    db_data:
      external:
        name: db_data
    www_html:
      external:
        name: www_html
Una vez más, los volúmenes justifican alguna explicación:

/nfs/common/services/wordpress/db_root:/root

Sirve para el mismo propósito que en el caso del volumen bind-mount. Solo es necesario cuando ejecutas la pila por primera vez para iniciar la base de datos MySQL.

/nfs/common/services/wordpress/www_src/html:/usr/src/wordpress

Sirve para el mismo propósito que en el caso del volumen bind-mount. Solo es necesario cuando ejecutas la pila por primera vez para iniciar el contenido de WordPress.

db_data:/u01/my3306/data

db_data es un volumen compartido creado fuera de la implementación de la pila, lo que significa que es creado antes de que llegue a usarse el archivo yaml. Se utiliza para almacenar el contenido de la base de datos MySQL y no se activa en la creación.

www_html:/var/www/html

www_html es un volumen compartido creado fuera de la implementación de la pila, lo que significa que es creado antes de que llegue a utilizarse el archivo yaml. Se utiliza para almacenar el contenido de WordPress y no se activa en la creación.

Creando los volúmenes compartidos

Probablemente hayas observado una sección en el archivo yaml que dice:

volumes:
    db_data:
      external:
        name: db_data
    www_html:
      external:
        name: www_html
Los volúmenes compartidos db_data y www_html se crean utilizando los siguientes comandos:
docker volume create --driver local \
    --opt type=nfs \
    --opt o=addr=192.168.1.100,rw \
    --opt device=:/media/sata/nfsshare/www_html \
    www_html
docker volume create --driver local \
    --opt type=nfs \
    --opt o=addr=192.168.1.100,rw \
    --opt device=:/media/sata/nfsshare/db_data \
    db_data
Los directorios /media/sata/nfsshare/db_data y /media/sata/nfsshare/www_htm deben existir antes de crear los volúmenes. Mi archivo /etc/exports tiene una entrada:
/media/sata/nfsshare 192.168.1.0/255.255.255.0(rw,sync,no_root_squash,no_subtree_check,fsid=0)
Para probar que los volúmenes compartidos funcionan, inicialmente implementé solo 1 réplica de mySQL y 1 de WordPress en el gestor de Docker y les permití iniciar los volúmenes compartidos.

Implementación de volumen compartido de WordPress

Luego comenté las 2 líneas de la ubicación del WordPress:

#       placement:
#         constraints: [node.role == manager]
y los 2 volúmenes bind-mount
#       - /nfs/common/services/wordpress/db_root:/root
#       - /nfs/common/services/wordpress/www_src/html:/usr/src/wordpress
Después, quise implementar 3 réplicas de WordPress en varios nodos. Como estamos usando el driver "local", debemos crear los volúmenes en cada nodo. Tal y como se muestra en la Figura 5, usé "parallel ssh" para crearlos en todos los nodos usando solo 2 comandos. La figura 5 muestra el volumen y la implementación de la pila:

Figura 5: Creando los volúmenes en los nodos

Figura 6: Implementación del volumen compartido de WordPress

Verifiqué que todas las réplicas estuvieran usando los volúmenes compartidos utilizando "docker exec -it" para acceder a los contenedores de WordPress en los nodos en los que se estaban ejecutando y examiné el contenido del directorio /var/www/html para verificar que todo funcionaba correctamente.

En el fondo, ambos planteamientos usan NFS para compartir entre los nodos. Sin embargo, los volúmenes compartidos proporcionan una abstracción independiente del host de mayor nivel que los volúmenes bind-mount. Potencialmente, puedes volver a crear los volúmenes compartidos utilizando backends de almacenamiento distintos de NFS, como AWS EC2 y Glusterfs. Bind-mount, por otro lado, está vinculado a tu sistema de archivos del host, que puede ser difícil de migrar a otro entorno.

Conclusion

Aprendí algo nuevo explorando el uso de la denominada "implementación de pila docker". Espero que encuentres útil e informativo este artículo. Todavía hay muchas características, como las actualizaciones sucesivas, la Implementación continua/Integración continua (CI / CD), las implementaciones A/B y azul/verde, solo por nombrar unas cuantas, que aún no he explorado con mi clúster ODROID-C2 Swarm. Además, existen otros entornos de trabajo de planificación de servicios como Kubernetes y Openshift que son más frecuentes en el entorno empresarial que en el modo Docker Swarm. Exploraré otros usos del modo Swarm de Docker y alternativas al modo Swarm e informaré de mis hallazgos en el futuro cuando surja la oportunidad.

Be the first to comment

Leave a Reply