Contenedores en Docker Swarm con volúmenes de datos persistentes usando REX-Ray y VirtualBox

Escrito por el , actualizado el .
blog blog-stack planeta-codigo planeta-linux
Comentarios

Salvo que un servicio sea sin estado o stateless los contenedores de Docker necesitan persistir datos y que estos sobrevivan a su terminación, como es el caso de un contenedor de una base de datos. Además en un cluster de nodos Docker hay que tener en cuenta que los datos deben estar accesibles para todos los nodos ya que un contenedor que usase los datos podría ser lanzado en cualquiera de ellos. REX-Ray es un sistema de almacenamiento en red que cubre estas necesidades, es simple de instalar, configurar y de iniciar. En el artículo muestro un ejemplo usando REX-Ray junto con Docker Swarm y VirtualBox.

Docker

Los contenedores de datos son efímeros, se crean y se destruyen y con ellos los datos que tuviesen en su sistema de archivos de modo que cualquier dato que queramos que sobreviva a la vida del contenedor ha de almacenarse de forma externa, este es el caso de los datos de una base de datos como PostgreSQL o MongoDB. Además usando Docker Swarm se plantea el problema de que hay varios nodos formando un cluster por lo que los datos han de estar accesibles independientemente del nodo en el que sea iniciado el contenedor que los utilice y significa que los datos no pueden estar almacenados en el nodo ya que un contenedor podría ser iniciado en cualquiera de ellos.

Así que los contenedores iniciados en un cluster de Docker Swarm que usen datos persistentes necesitan un sistema de almacenamiento en red externo a los contenedores y nodos. Una de las opciones disponibles es REX-Ray que ofrece una configuración sencilla y múltiples proveedores de computación entre las que están las más populares como Amazon EC2, Digital Ocean, Google Compute Engine, microsoft-azure e incluso VirtualBox.

En el siguiente ejemplo uso un cluster de nodos Docker, VirtualBox y REX-Ray para proporcionar volúmenes de datos persistentes para un contenedor que tiene una base de datos postgres basándome en el artículo previo Crear un cluster de contenedores Docker donde explicaba como crear un cluster de nodos con Docker Swarm.

Para la integración entre VirtualBox y REX-Ray hay que iniciar primero un servidor en el host que permite a REX-Ray hacer llamadas remotas a VirtualBox para que gestione los volúmenes de datos.

1
2
3
4
#!/usr/bin/env bash

VBoxManage setproperty websrvauthlibrary null
vboxwebsrv -H 0.0.0.0 -v

Si hay un firewall hay que permitir el tráfico para el puerto 18083, en mi caso que uso ufw creando la siguiente regla.

1
$ sudo ufw allow to any port 18083 from 192.168.0.0/16

Con el cluster creado debemos instalar y configurar REX-Ray en cada uno de los nodos ejecutando varios comandos, un comando instala REX-Ray, otro crea el archivo de configuración en /etc/rexray/config.yml y finalmente otro inicia el servicio de REX-Ray. Algunas opciones que se indican en el archivo de configuración de REX-Ray es la ubicación en el host donde se guardan los volúmenes con el parámetro volumePath.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/env bash

#export MACHINE_STORAGE_PATH="/run/media/picodotdev/BMOVE ROJO/docker-machine/"

#sudo ufw allow to any port 18083 from 192.168.0.0/16  

for i in "01" "02" "03"; do
    docker-machine ssh node-$i "curl -sSL https://dl.bintray.com/emccode/rexray/install | sh -"
    docker-machine ssh node-$i "cat > /tmp/rexray-config.yml << EOF
rexray:
  logLevel: info
libstorage:
  service: virtualbox
virtualbox:
  endpoint: http://192.168.99.1:18083
  volumePath: /run/media/picodotdev/BMOVE ROJO/docker-machine/volumes/
  controllerName: SATA
EOF
"
    docker-machine ssh node-$i "sudo mkdir -p /etc/rexray/"
    docker-machine ssh node-$i "sudo mv /tmp/rexray-config.yml /etc/rexray/config.yml"
    docker-machine ssh node-$i "sudo rexray start"
done
Instalación de REX-Ray en nodos de Docker Swarm con VirtualBox

Para probar la persistencia de datos usaré un stack iniciado de la misma forma que en artículo Iniciar un stack de servicios en un cluster de Docker Swarm pero con un contenedor de postgres que guarda los datos en un volumen de REX-Ray en /var/lib/postgresql/data. Para iniciar el stack el custer de Docker Swarm uso un archivo de Docker Compose con la definición del stack en formato YAML.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
version: "3.1"

services:
  postgres:
    image: postgres:latest
    ports:
      - "5432:5432"
    volumes:
      - postgres:/var/lib/postgresql/data
volumes:
  postgres:
    external: true
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/env bash

#export MACHINE_STORAGE_PATH="/run/media/picodotdev/BMOVE ROJO/docker-machine/"
eval $(docker-machine env node-01)

docker stack deploy -c docker-compose-stack-postgres.yml postgres

sleep 30s

echo -e "\n# Cluster services"
docker service ls
echo -e "\n# Nginx service tasks"
docker service ps nginx_nginx

for i in "01" "02" "03"; do
    echo -e "\n# Node $i containers"
    eval $(docker-machine env node-$i)
    docker ps
done

En la siguiente captura de pantalla se observa en que nodo ha sido iniciado el contenedor de postgres y que identificativo se le ha asignado.

Deploy del stack de postgres

En el stack el volumen de datos postgres está declarado y creado de forma externa. Usando VirtualBox con REX-Ray en el host o anfitrión se crea un archivo que contiene los datos del volumen. Al listar los volúmenes de datos además de los creados postgres y app están los de los discos duros de cada uno de los nodos identificados como disk.vmdk. El parámetro opt=size=5 indica que el volumen de datos es de una tamaño de 5GiB.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#!/usr/bin/env bash

#export MACHINE_STORAGE_PATH="/run/media/picodotdev/BMOVE ROJO/docker-machine/"
eval $(docker-machine env node-01)

docker volume create --driver=rexray --name=postgres --opt=size=5
docker volume create --driver=rexray --name=app --opt=size=5

echo -e "\n# Volumes"
docker volume ls
Volúmenes de datos

Para crear algunos datos en la base de datos hay que conectarse al contenedor y lanzar algunas sentencias SQL. Hay que obtener el identificativo del contenedor de postgres, iniciar un proceso bash, realizar la conexión a la base de datos con el cliente psql y lanzar las sentencias SQL.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
$ docker ps -q --filter label=com.docker.swarm.service.name=postgres_postgres
ac90f7e1e7b5
$ docker exec -it ac90f7e1e7b5 /bin/bash
postgres-# psql --username=postgres
postgres=# CREATE TABLE COMPANY(                                                                                   
postgres(#    ID             SERIAL PRIMARY KEY     NOT NULL,
postgres(#    NAME           TEXT                   NOT NULL,
postgres(#    AGE            INT                    NOT NULL,
postgres(#    ADDRESS        CHAR(50),
postgres(#    SALARY         REAL
postgres(# );
CREATE TABLE
postgres=# \dt
          List of relations
 Schema |  Name   | Type  |  Owner   
--------+---------+-------+----------
 public | company | table | postgres
(1 row)
postgres=# \d+ company
                                                 Table "public.company"
 Column  |     Type      |                      Modifiers                       | Storage  | Stats target | Descrip
tion 
---------+---------------+------------------------------------------------------+----------+--------------+--------
-----
 id      | integer       | not null default nextval('company_id_seq'::regclass) | plain    |              | 
 name    | text          | not null                                             | extended |              | 
 age     | integer       | not null                                             | plain    |              | 
 address | character(50) |                                                      | extended |              | 
 salary  | real          |                                                      | plain    |              | 
Indexes:
    "company_pkey" PRIMARY KEY, btree (id)
postgres=# INSERT INTO COMPANY (name, age, address, salary) VALUES ('Company A', 21, '13, Rue del Percebe', 45000);
INSERT 0 1
postgres=# SELECT * FROM company;
 id |   name    | age |                      address                       | salary
----+-----------+-----+----------------------------------------------------+--------
  3 | Company A |  21 | 13, Rue del Percebe                                |  45000
(1 row)

Destruyendo el stack y volviéndolo a arrancar posiblemente Docker Swarm iniciará el contenedor en otro nodo del cluster pero los datos seguirán estando presentes en la base de datos postgres, se puede comprobar iniciando una nueva sesión bash en el nuevo contenedor, iniciando el cliente de psql y lanzando la consulta select de SQL o con el comando \dt para obtener las tablas de la base de datos, \d+ company para obtener una descripción de la tabla y la consulta SQL SELECT * FROM company;.

1
2
3
4
5
6
#!/usr/bin/env bash

#export MACHINE_STORAGE_PATH="/run/media/picodotdev/BMOVE ROJO/docker-machine/"
eval $(docker-machine env node-01)

docker stack rm postgres

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub.