Almacenamiento en contenedores

Este es uno de los capítulos del tutorial Docker. Introducción y primeros pasos. Encontrarás los enlaces a todos los de capítulos, al final de este artículo.

Tienes que tener en cuenta que los archivos que creas en el interior de un contenedor se perderá cuando el contenedor deje de existir. Pero, no solo esto, sino además, ¿como compartir un directorio en un contenedor con otros contenedores?. ¿Como mover sacar la información de ese contenedor?¿O como mover la información de un contenedor a otro?. Y lo que es peor todavía, guardar información o archivos en el interior del contenedor reduce el rendimiento, comparado con un contenedor que utilice otro tipo de solución, como puede ser escribir directamente en el sistema de archivos del anfitrión. Así ¿que opciones de almacenamiento en contenedores tienes con docker?

En este capítulo del tutorial sobre docker, encontrarás algunas opciones y posibilidades para el almacenamiento en contenedores docker.

Almacenamiento en contenedores

Almacenamiento en contenedores

Existen diferentes soluciones para guardar archivos,

  • volumes. Docker es el que se encarga de gestionar el almacenamiento. En el caso de Linux, este se realiza en /var/lib/docker/volumes/.
  • bind mounts. En este caso el almacenamiento se puede encontrar en cualquier parte del sistema anfitrión.
  • tmpfs. En este caso el almacenamiento solo se encuentra en la memoria del sistema anfitrión y nunca se guarda en el sistema de archivos.

Para verlo con mas claridad voy a levantar dos contenedores de Nginx que compartirán el mismo directorio, que montaré siguiendo alguna de las soluciones que has visto en los puntos anteriores.. Al menos en los dos primeros tipos de almacenamiento. El tercero no es posible, pero lo haré de igual forma para verlo con claridad.

Gestión con bind mount

Este sistema de almacenamiento en contenedores, consiste en que sustituyes un directorio del contenedor por un directorio del equipo anfitrión. Y cuando te hablo de sustituir lo hago de forma literal, en el sentido que da lo mismo lo que tengas en ese directorio del contenedor por que se va a ver reemplazado por el directorio del anfitrión.

Así por ejemplo, si quieres montar un directorio, es tan sencillo como ejecutar la siguiente instrucción,

docker run -d --name web3 --mount type=bind,src=/home/lorenzo/temporal,dst=/temporal -p 80:80 nginx

Si entras en el contenedor verás que lo que tienes en el directorio del anfitrión, /home/lorenzo/temporal, ahora también se encuentra en el directorio /temporal del contenedor. Recuerda que para entrar al contenedor, tan solo tienes que ejecutar la instrucción,

docker exec -it web3 bash

Otra opción para definir el montaje es utilizar la nomenclatura -v. Así, puedes definir el mismo contenedor que en el caso anterior, pero un poco mas simplificada,

docker run -d --name web3 -v /home/lorenzo/temporal:/temporal -p 80:80 nginx

Donde la parte de la izquierda se refiere al anfitrión y la parte de la derecha al contenedor. Recordarte que esto la definición de los directorios debe ser absoluta. Si lo quieres hacer relativa puedes hacerlo utilizando una solución como la que te indico a continuación,

docker run -d --name web3 -v "$(pwd)"/temporal:/temporal -p 80:80 nginx

Indicarte que los desarrolladores de docker recomiendan que utilices la nomenclatura --mount en lugar de -v.

Compartiendo directorios

Evidentemente ya te has imaginado lo sencillo que es compartir un directorio con el anfitrión y entre dos contenedores. Así por ejemplo,

for i in {1..2}
do
    echo "=== web$i ==="
    docker run -d \
        --name web$i \
        --mount type=bind,src="$(pwd)"/temporal,dst=/temporal \
        -p 8$i:80 \
        nginx
done

Así, cualquier cambio o modificación que realices en el directorio ./temporal lo verán los contenedores y por supuesto el anfitrión.

Ahora bien, tienes que tener en cuenta que los cambios que realices en el anfitrión, se verán reflejados en cada uno de los contenedores. Y esto será con el usuario con el que estés trabajando en el anfitrión. Por defecto será 1000 en los contenedores. Si el cambio lo haces desde el lado del contenedor, pasará exactamente lo mismo. Si lo haces por defecto, el cambio realizado en el contenedor, se verá reflejado en el directorio del anfitrión como que ha sido realizado por root.

En este sentido, una solución sería crear tu usuario dentro del contenedor, y de esta f orma los cambios que realices, tanto en el anfitrión como en el contenedor, aparecerá que han sido realizados por tu usuario.

Una observación

Por último recordarte que debes tener cuidado porque puedes montar sobre un directorio y que el contenedor no funcione. Por ejemplo,

docker run -d --name web3 --mount type=bind,src=/home/lorenzo/temporal,dst=/usr -p 80:80 nginx

Esto te devolverá un bonito mensaje de error como el siguiente,

docker: Error response from daemon: OCI runtime create failed: container_linux.go:346: starting container process caused "exec: \"nginx\": executable file not found in $PATH": unknown.

Es decir, que no puede ejecutar nginx porque básicamente no lo encuentra. Claro has sobreescrito la ruta.

Gestión con volumes

La siguiente de las opciones para gestionar el almacenamiento en contenedores es el uso de volumes. Esta es la opción recomendada por docker para hacerlo.

El primer paso será crear el volumen que compartiré con los dos contenedores. Este primer paso no es necesario, aunque lo he hecho para mas claridad. Quiero decir, que al crear el contenedor, si el volumen no existe, se crea. Para ello, ejecuta la siguiente instrucción,

docker volume create web

El siguiente paso será crear los dos contenedores. Para hacerlo de forma sencilla, utilizaré un for y así de paso recuerdas el tutorial sobre scripts en bash.

for i in {1..2};do echo === $i ===;docker run -d --name web$i --mount type=volume,src=web,dst=/usr/share/nginx/html -p 8$i:80 nginx;done

Al igual que hiciste en el caso de bind mount también es posible utilizar -v, y la sintaxis es muy parecida. Así por ejemoplo, para el caso que te ocupa ahora mismo, esto sería,

for i in {1..2};do echo === $i ===;docker run -d --name web$i -v web:/usr/share/nginx/html -p 8$i:80 nginx;done

Tanto en un caso como en otro, es posible definir un tercer parámetro. Este tercer parámetro son las opciones, como por ejemplo, que el volumen sea de solo lectura ro.

Por otro lado, también puedes hacer otras operaciones con volúmenes como son,

  • listar volúmenes con docker volume ls
  • inspeccionar un volumen docker volume inspect <nombre del volumen>
  • borrar un volumen docker volume rm <nombre del volumen>

Si ejecutas docker ps verás tus dos contenedores funcionando a pleno rendimiento,

CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                NAMES
e4f52f995745        nginx               "nginx -g 'daemon of…"   5 seconds ago       Up 4 seconds        0.0.0.0:82->80/tcp   web2
87579b1ba222        nginx               "nginx -g 'daemon of…"   6 seconds ago       Up 5 seconds        0.0.0.0:81->80/tcp   web1

¿Como ponemos nuestra página en el volumen web? Pues solo puedes hacerlo utilizando docker. Esto lo puedes hacer utilizando la instrucción cp. Por ejemplo,

docker cp data/index.html web1:/usr/share/nginx/html/

Igual que has copiado un archivo, también puedes copiar un directorio y todo el contenido de ese directorio. Por ejemplo,

docker cp data/. web1:/usr/share/nginx/html/

Si ahora te diriges con tu navegador a localhost:81 o localhost:82 encontrarás el mismo contenido. El del archivo data/index.html que hayas creado.

Una vez detenido y borrados los contenedores ya no tienes acceso al volumen. Bueno, realmente si tienes acceso, a través de otro contenedor. ¿Probamos? El primer paso será detener y borrar los contenedores. No te preocupes que el contenido de la página permanece en el volumen.

docker stop $(docker ps -q) && docker rm $(docker ps -aq)

Ahora vas a levantar un contenedor. Dado el uso que le vas a dar, lo mejor es un contenedor, muy pero que muy ligero. Así tiraremos del amigo Alpine, al que tanto cariño le estás cogiendo últimamente.

docker run --name ayudante --mount type=volume,src=web,dst=/temporal alpine

Y ahora lo tienes tan fácil como copiar del volumen web al directorio que quieras o necesites,

docker cp ayudante:/temporal/. data/

De tu contenedor al volumen

Una característica interesante de los volúmenes es que, cuando inicias un contenedor que crea un volumen, y en el directorio del contenedor que vas a montar existen directorios y archivos, estos directorios y archivos se copian del contenedor al volumen. Posteriormente el contenedor monta el volumen. Así de esta forma el resto de contenedores pueden utilizar los archivos que ha publicado y compartido inicialmente el primer contenedor.

tmpfs mounts

El tercero de las opciones para el almacenamiento en contenedores y que te permita guardar información es tmpfs mounts. Sin embargo esta tercera opción presenta ciertas diferencias con las anteriores. Así, mientras los volumes y bind mounts permanecen una vez se ha detenido el contenedor, esto no sucede con los tmpfs mounts. De forma que al detener el contenedor este almacenamiento desaparece.

Además de esta limitación, tmpfs mounts presenta otras limitaciones como son,

  • no es posible compartir tmpfs mounts, tal y como ya te he adelantado en un apartado anterior.
  • esta funcionalidad solo está disponible en Linux.

Existen dos opciones para montar contenedores del tipo tmpfs mounts, utilizando --tmpfs y --mount. La primera tiene una serie de limitaciones frente a la segunda, como es que con la primera no puedes especificar opciones, cosa que si puedes hacer con la segunda.

¿Y para que quiero utilizar un almacenamiento temporal? Simplemente por seguridad, para asegurarte que al tener el contenedor no queda ningún rastro de la actividad, o de la información que guardaba el mismo.

Así, para montar un contenedor con una unidad tmpfs, siguiendo los ejemplos anteriores, pero sin poder compartirlo claro, sería algo como esto,

docker run -d \
  -it \
  --name web1 \
  --mount type=tmpfs,destination=/usr/share/nginx/html/ \
  nginx

O utilizando la otra sintaxis,

docker run -d \
  -it \
  --name web1 \
  --tmpfs /usr/share/nginx/html/ \
  nginx

Aquí ojo cocina, porque todo lo que hagas en ese directorio /usr/share/nginx/html/ cuando detengas el contenedor lo vas a perder.


Mas información,

Imagen de portada de Jay Wennington en Unsplash

Deja un comentario

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