Gestionar los datos de tus contenedores de Docker

Antes de que empezara con Docker, una de mis dudas era ¿Y qué pasa con los datos que generan las aplicaciones en contenedores? Las bases de datos, los gestores de contenido, ¿cómo hago copias de seguridad de todo eso? ¿cómo accedo a ello? Obviamente, me imaginaba que alguna forma habría, así que hoy quiero ver contigo qué opciones de almacenamiento tienes en Docker.

Existen tres tipos de almacenamiento:

  • volumes: Docker almacena los datos dentro de un área que él controla del sistema de ficheros. Es el mecanismo preferido para persistir los datos a día de hoy. Los volúmenes se almacenarán en /var/lib/docker/volumes/ y solo Docker tiene permisos sobre esta ubicación. Un volumen puede ser montado por diferentes contenedores a la vez.
  • bind mounts: Se utiliza para mapear cualquier sitio del sistema de ficheros dentro de tu contenedor. A diferencia de los volúmenes, a través de este mecanismo es posible acceder a la ruta mapeada y modificar los ficheros.
  • tmpfs: Se trata de un almacenamiento temporal en memoria. Se suele utilizar para el almacenamiento de configuraciones y espacios efímeros que desparecerán cada vez que el contenedor se pare.
Imagen de la documentación de Docker que explica la diferencia entre los tipos

A nivel de contenedor, no importa qué tipo utilices ya que dentro del mismo se verá de la misma forma, como un directorio o archivo. Vamos a ver un ejemplo con cada uno de ellos.

volumes

Como te decía, es la forma preferida para persistir los datos de los contenedores. El espacio donde se almacenan, /var/lib/docker/volumes/, lo gestiona Docker y ni siquiera tú puedes entrar en esa ubicación para echar un vistazo. Estos volúmenes pueden ser o anónimos o pueden tener un nombre. Lo que significa que podemos asignarle un nombre descriptivo, lo cual nos hace más sencillo localizar el volumen dentro de los que hay creados, para hacer una copiar de seguridad por ejemplo, o bien anónimo, que lo que significa es que Docker se encarga de darle un nombre aleatorio que es realmente indescriptible.

Para crear un volumen se puede hacer de tres formas: a través de la línea de comandos de manera explícita:

docker volume create my-data

O bien durante la creación del contenedor:

docker run -d \
  --name=mongo \
  --mount source=mongo-data,target=/data/db \
  mongo

o del servicio:

mongo-express:
  image: mongo-express
  ports:
    - 80:8081
  environment:
    ME_CONFIG_BASICAUTH_USERNAME: "gis"
    ME_CONFIG_BASICAUTH_PASSWORD: "thisisatest"
  links:
    - mongo
mongo:
  image: mongo
  volumes:
    - mongo-data:/data/db

Como ves, en las dos últimas lineas se ha definido un espacio llamado volumes donde se indica, a la izquierda, el nombre que se le quiere dar al volumen (mongo-data) y a la derecha el directorio del contenedor con el que se le quiere mapear (/data/db). Si este directorio dentro del contenedor tuviera contenido este se copiaría dentro del volumen.

Al ser una zona del sistema de ficheros restringida para nosotros, para hacer backups de los volúmenes se puede hacer a través de los siguientes pasos, explicados en la documentación de Docker.

  1. Crea un nuevo contenedor y monta el volumen del que quieres hacer el backup.
  2. Monta un directorio local, como por ejemplo /backup
  3. Ejecuta el siguiente comando que comprime el contenido del volumen y especificas como destino el directorio /backup.
docker run --rm --volumes-from dbstore -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar /dbdata

Volumes es la forma preferida de almacenar los datos de tus aplicaciones ya que te aporta mayor seguridad que los otros tipos y además tiene funcionalidades extra que el resto no tiene. Una muy importante es la existencia de drivers que te permiten almacenar los volúmenes en sitios remotos, cifrarlos, etcétera.

bind mounts

Históricamente esta era la única opción que existía en las primeras fases de Docker. En este caso, la información no se almacena en /var/lib/docker/volumes sino que eres tú el que elige qué sitio de tu sistema de ficheros quieres compartir con el contenedor. Estamos más limitados al host, a su sistema de ficheros, y a que con volumes puedes utilizar drivers para almacenar en remoto. Sin embargo, este sistema está genial cuando necesitas mapear algún directorio que quieres que sea modificado fuera de Docker. Por ejemplo, imagínate que estás desarrollando un sitio web y no quieres tener que crear la imagen de tu contenedor cada vez para que se reflejen los cambios.

Para ejecutar un contenedor con un directorio local montado podemos lanzar el siguiente comando:

docker run -d \ 
  --name webapp \
  --mount type=bind,source="/.web:/usr/share/nginx/html \
  nginx:latest

Con esto lo que conseguimos es que podamos modificar el contenido que está sirviendo nginx dentro del contenedor desde el host.

La forma de hacer lo mismo dentro de docker-compose.yml sería así:

web:
  image: nginx:alpine
  ports: 
    - 80:80
  volumes:
    - './web:/usr/share/nginx/html'

En este caso puedes ver que en el apartado volumes estamos especificando la ruta del host a la izquierda (./web) donde queremos mapear la ruta por defecto que sirve ngix (/usr/share/nginx/html) dentro del contenedor. Por lo que yo podría modificar el contenido de la ruta /web y se vería reflejado en lo que está sirviendo nginx desde el contenedor en /usr/share/nginx/html. Antes de ejecutar docker-compose up, crea el directorio web en la ruta especificada para que tengas permisos de escritura en el mismo. Luego, ejecuta eldocker-compose up -d para ver que el servidor se lanza correctamente y por último puedes intentar modificar el contenido. En mi caso he probado a crear un nuevo archivo index.html dentro del directorio para ver si nginx deja de servir la información por defecto por esta nueva.

echo '<h1>Hello from the host!</h1>' > web/index.html

tmpfs

El último caso se utiliza para almacenar información en memoria. Esta solo funciona si estás ejecutando Docker en Linux. A diferencia de las otras dos opciones, esta tercera sólo mantiene la información mientras el contenedor se está ejecutando, por lo que es el lugar perfecto para almacenar temporalmente información sensible. Si bien con los volúmenes y con los bind mounts es posible compartir la información entre contenedores, en este caso no lo es. La forma de declararlo a través de la consola es con el siguiente comando:

docker run -d \  
  --name webapp \
  --mount type=tmpfs,destination=/var/tmp \
  nginx:latest

Todo lo que almacenemos en /var/tmp en realidad se estará almacenando en memoria y desaparecerá cuando el contenedor se pare.

Imagen de portada por kyohei ito.

¡Saludos!