Skip to main content

Comprendiendo Volúmenes

7.1. Introducción a volúmenes en Docker

Bueno, los volúmenes no son más que directorios externos al container, que se montan directamente en él, y de esta forma bypasean su filesystem, es decir, no siguen ese patrón de capas del que hablamos. ¿Te decepcioné? Qué bien, señal de que es muy simple y no vas a tener problemas para entender. :)

La función principal del volumen es persistir los datos. A diferencia del filesystem del container, que es volátil y toda información escrita en él se pierde cuando el container muere, cuando escribes en un volumen ese dato continúa allí, independientemente del estado del container.

Existen algunas particularidades entre los volúmenes y containers que vale la pena mencionar:

  • El volumen se inicializa cuando el container es creado.

  • En caso de que ya haya datos en el directorio que estás montando como volumen, es decir, si el directorio ya existe y está "poblado" en la imagen base, esos datos serán copiados al volumen.

  • Un volumen puede ser reutilizado y compartido entre containers.

  • Las modificaciones en un volumen se hacen directamente en el volumen.

  • Las modificaciones en un volumen no irán con la imagen cuando hagas una copia o snapshot de un container.

  • Los volúmenes continúan existiendo incluso si eliminas el container.

Dicho esto, basta de charla. Vamos a aprender a agregar un volumen en un container.

Primero, vamos a ver cómo funciona de la manera antigua, que aún está soportada, pero no es elegante. :)

Esta manera es muy utilizada cuando se quiere montar un directorio específico del host dentro del container. Esto es malo cuando estamos trabajando en cluster, ya que tendríamos que garantizar ese directorio creado en todos los hosts del cluster. No sería bueno.

Sin embargo, podemos aprender cómo funciona y utilizarlo en algún momento, en caso de que sea necesario. Para evitar errores, primero crea el directorio "/volume" en tu máquina.

# mkdir /volume
# docker container run -ti --mount type=bind,src=/volume,dst=/volume ubuntu
root@7db02e999bf2:/# df -h

Filesystem Size Used Avail Use% Mounted on
none 13G 6.8G 5.3G 57% /
tmpfs 999M 0 999M 0% /dev
tmpfs 999M 0 999M 0% /sys/fs/cgroup
/dev/mapper/ubuntu--vg-root 13G 6.8G 5.3G 57% /volume
shm 64M 0 64M 0% /dev/shm

root@7db02e999bf2:/# ls
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var volume

root@7db02e999bf2:/#

En el ejemplo anterior, conocimos un nuevo parámetro del comando "docker container run", el "--mount".

El parámetro "--mount" es el responsable de indicar el volumen, que en nuestro ejemplo es el "/volume", y dónde será montado en el container. Observa que, cuando pasamos el parámetro "--mount type=bind,src=/volume,dst=/volume", Docker montó este directorio en el container, pero sin ningún contenido.

También podemos montar un volumen en el container vinculándolo con un directorio del host que ya tiene algún contenido. Para ejemplificar, vamos a compartir el directorio "/root/primeiro_container", que utilizamos para guardar nuestro primer dockerfile, y montarlo en el container en un volumen llamado "/volume" de la siguiente forma:

# docker container run -ti --mount type=bind,src=/root/primeiro_container,dst=/volume ubuntu

root@3d372a410ea2:/# df -h
Filesystem Size Used Avail Use% Mounted on
none 13G 6.8G 5.3G 57% /
tmpfs 999M 0 999M 0% /dev
tmpfs 999M 0 999M 0% /sys/fs/cgroup
/dev/mapper/ubuntu--vg-root 13G 6.8G 5.3G 57% /volume
shm 64M 0 64M 0% /dev/shm

root@3d372a410ea2:/#

Con esto, estamos montando el directorio "/root/primeiro_dockerfile" del host dentro del container con el nombre de "/volume".

En el container:

root@3d372a410ea2:/# ls /volume/
Dockerfile

root@3d372a410ea2:/#

En el host:

root@linuxtips:~# ls /root/primeiro_dockerfile/
Dockerfile

root@linuxtips:~#

En caso de que quiera dejar el volumen en el container solo como read-only, es posible. Basta pasar el parámetro "ro" después del destino donde será montado el volumen:

# docker container run -ti --mount type=bind,src=/root/primeiro_container,dst=/volume,ro ubuntu
root@8d7863b1d9af:/# df -h

Filesystem Size Used Avail Use% Mounted on
none 13G 6.8G 5.3G 57% /
tmpfs 999M 0 999M 0% /dev
tmpfs 999M 0 999M 0% /sys/fs/cgroup
/dev/mapper/ubuntu--vg-root 13G 6.8G 5.3G 57% /volume
shm 64M 0 64M 0% /dev/shm

root@8d7863b1d9af:/# cd /volume/
root@8d7863b1d9af:/volume# ls
Dockerfile

root@8d7863b1d9af:/volume# mkdir teste
mkdir: cannot create directory 'teste': Read-only file system

root@8d7863b1d9af:/volume#

Así como es posible montar un directorio como volumen, también es posible montar un archivo:

# docker container run -ti --mount type=bind,src=/root/primeiro_container/Dockerfile,dst=/Dockerfile ubuntu

root@df0e3e58280a:/# df -h

Filesystem Size Used Avail Use% Mounted on
none 13G 6.8G 5.3G 57% /
tmpfs 999M 0 999M 0% /dev
tmpfs 999M 0 999M 0% /sys/fs/cgroup
/dev/mapper/ubuntu--vg-root 13G 6.8G 5.3G 57% /Dockerfile
shm 64M 0 64M 0% /dev/shm

root@df0e3e58280a:/# cat Dockerfile
FROM debian
RUN /bin/echo "HELLO DOCKER"

root@df0e3e58280a:/#

Esto hace que el archivo "/root/primeiro_dockerfile/Dockerfile" sea montado en "/Dockerfile" en el container.

7.2. Creando volúmenes

Ahora vamos a crear los volúmenes de la manera más elegante y actual. Hoy tenemos la posibilidad de realizar la gestión de volúmenes de manera muy simple e inteligente.

Siempre que creamos un volumen, crea un directorio con el mismo nombre dentro de "/var/lib/docker/volumes/".

En el ejemplo siguiente, el volumen "giropops" sería entonces creado en "/var/lib/docker/volumes/giropops"; con esto, todos los archivos disponibles en ese directorio también estarían disponibles en el lugar indicado en el container. ¡Vamos a los ejemplos! :D

Es posible hacer la creación de volúmenes y toda su administración a través del comando:

# docker volume create giropops

Es posible eliminarlo a través del comando:

# docker volume rm giropops

Para verificar detalles sobre ese volumen:

# docker volume inspect giropops

Para eliminar los volúmenes que no están siendo utilizados (¡usa con extrema moderación! :D):

# docker volume prune

Para que puedas montar el volumen creado en algún container/service, basta ejecutar el siguiente comando:

# docker container run -d --mount type=volume,source=giropops,destination=/var/opa nginx

Donde:

  • --mount -- Comando utilizado para montar volúmenes.

  • type=volume -- Indica que el tipo es "volume". Aún existe el tipo "bind", donde, en vez de indicar un volumen, indicarías un directorio como source.

  • source=giropops -- Cuál es el volumen que pretendo montar.

  • destination=/var/opa -- Dónde en el container montaré ese volumen.

Simple como volar, ¿no?

7.3. Localizando volúmenes

En caso de que quieras obtener la ubicación de tu volumen, es simple. Pero para eso necesitas conocer el comando "docker volume inspect".

Con el "docker volume inspect" consigues obtener detalles de tu container, como, por ejemplo, detalles del volumen.

La salida del comando "docker volume inspect" retorna más información de que solamente el path del directorio en el host. Vamos a usar la opción "--format" o "-f" para filtrar la salida del "inspect".

docker volume inspect --format {% raw %}'{{ .Mountpoint }}'{% endraw %} giropops
/var/lib/docker/volumes/giropopos/_data

7.3. Creando y montando un data-only container

Una opción bastante interesante en relación a los volúmenes se refiere al data-only container, cuya principal función es proveer volúmenes para otros containers. ¿Recuerdas el NFS server y el Samba? Ambos centralizaban directorios con la finalidad de compartir entre otros servidores. Pues bien, el data-only container tiene la misma finalidad: proveer volúmenes a otros containers.

Una de las grandes ventajas de Docker es la portabilidad. Un container creado en tu laptop debe ser portable al punto de ejecutarse en cualquier otro ambiente que utilice Docker, ¡en todos los rincones del universo!

Siendo así, ¿qué sucede si creo un ambiente en Docker que dice a los containers que monten un directorio del host local? Depende. Depende de cómo está ese tal host local. Preguntas como "¿el directorio existe?" "¿los permisos están ajustados?", entre otras más, definirán el éxito en la ejecución de los containers, lo que escapa completamente del alcance de Docker.

¡Vamos a ver cómo funciona esto en la práctica! :)

Para nuestro ejemplo, primero vamos a crear un container llamado "dbdados", con un volumen llamado "/data", que guardará los datos de una base de datos PostgreSQL.

Para que podamos crear un container especificando un nombre para él, utilizamos el parámetro "--name", conforme veremos en el ejemplo siguiente:

# docker container create -v /data --name dbdados centos

Con esto, solo creamos el container y especificamos un volumen para él, pero aún no lo iniciamos.

Sabemos que en el container el volumen se encuentra montado en "/data". Sin embargo, ¿cuál es la ubicación de ese volumen en el host?

¿Recuerdas el "docker inspect"? Vamos a utilizarlo nuevamente:

root@linuxtips:~# docker inspect -f {% raw %}{{.Mounts}}{% endraw %} dbdados

[{46255137fe3f6d5f593e9ba9aaaf570b2f8b5c870f587c2fb34f29b79f97c30c /var/lib/docker/volumes/46255137fe3f6d5f593e9ba9aaaf570b2f8b5c870f587c2fb34f29b79f97c30c/_data /data local true }]

Observa que ahora utilizamos el nombre del container en vez del "CONTAINER ID". Totalmente posible y mucho más intuitivo.

Cuando ejecutamos el "docker inspect", nos retornó la ruta de nuestro volumen. Vamos a ver si existe algún contenido dentro de él:

root@linuxtips:~# ls \
/var/lib/docker/volumes/46255137fe3f6d5f593e9ba9aaaf570b2f8b5c870f587c2fb34f29b79f97c30c/_data

Como vimos, el directorio aún no posee contenido.

Ahora vamos a crear los containers que ejecutarán PostgreSQL utilizando el volumen "/data" del container "dbdados" para guardar los datos.

Para que podamos hacer el ejemplo, necesitamos conocer dos parámetros superimportantes:

  • --volumes-from -- Se utiliza cuando queremos montar un volumen disponibilizado por otro container.

  • -e -- Se utiliza para informar variables de entorno para el container. En el ejemplo, estamos pasando las variables de entorno de PostgreSQL.

¡Listo, ahora estamos preparados! Vamos a crear los containers con PostgreSQL:

# docker run -d -p 5432:5432 --name pgsql1 --volumes-from dbdados \
-e POSTGRESQL_USER=docker -e POSTGRESQL_PASS=docker \
-e POSTGRESQL_DB=docker kamui/postgresql

# docker run -d -p 5433:5432 --name pgsql2 --volumes-from dbdados \
-e POSTGRESQL_USER=docker -e POSTGRESQL_PASS=docker \
-e POSTGRESQL_DB=docker kamui/postgresql

Para verificar los dos containers con PostgreSQL en ejecución, utiliza el "docker container ls".

¡Listo, ahora tenemos los dos containers con PostgreSQL en ejecución! ¿Será que ya tenemos algún dato en el volumen "/data" del container "dbdados"?

Vamos a verificar nuevamente en el host si el volumen ahora posee algún dato:

root@linuxtips:~# ls /var/lib/docker/volumes/46255137fe3f6d5f593e9ba9aaaf570b2f8b5c870f587c2fb34f29b79f97c30c/_data

base pg_clog pg_ident.conf pg_notify pg_snapshots pg_stat_tmp pg_tblspc PG_VERSION postgresql.conf postmaster.pid server.key global pg_hba.conf pg_multixact pg_serial pg_stat pg_subtrans pg_twophase pg_xlog postmaster.opts server.crt

root@linuxtips:~#

¡Sensacional! Como percibimos, los dos containers de PostgreSQL están escribiendo sus datos en el volumen "/data" del container "dbdados". ¡Llega a ser conmovedor! :D

7.4. Siempre es bueno un backup

Otra cosa muy buena es la posibilidad de hacer backups de tus containers de datos de forma muy simple y rápida.

Digamos que quieras hacer el backup del directorio "/data" del container "dbdados" que creamos hace poco; ¿cómo lo haríamos?

root@linuxtips:~# cd backup/
root@linuxtips:~/backup# docker run -ti --volumes-from dbdados -v $(pwd):/backup debian tar -cvf /backup/backup.tar /data

Cuando ejecutamos el comando anterior, se crea un nuevo container montando el(los) volumen(es) del container "dbdados" (que en el caso es el "/data", ¿recuerdas?). Además de eso, será montado el directorio corriente del host en el volumen "/backup" del container, y en seguida será ejecutado el comando del tar para empaquetar el directorio "/data" dentro del directorio "/backup". Muy simple, ¿no?

root@linuxtips:~/backup# ls
backup.tar
root@linuxtips:~/backup#

Recordando que los volúmenes son siempre creados dentro de "/var/lib/docker/volumes". En caso de que quieras hacer el backup de todos los volúmenes, basta tratar ese directorio en tus rutinas de backup. ;)