Skip to main content

Volúmenes Docker

https://docs.docker.com/storage/

Cuando hablamos de volúmenes estamos hablando de storage que es el almacenamiento de datos.

  • Los datos creados en un contenedor se almacenan en la capa read write que es considerada una capa grabable, es decir, no persistente. Cuando el contenedor muere los datos se pierden.
  • La capa grabable está acoplada a la máquina host y no puede ser movida fácilmente a otro lugar.
  • El uso de volúmenes graba directamente en el sistema de archivos del host donde el contenedor está siendo ejecutado.
  • La grabación de datos en la capa superior necesita una abstracción de sistema de archivos que reduce el rendimiento comparado al uso de volúmenes.

Storage Drivers Soportados

https://docs.docker.com/storage/storagedriver/select-storage-driver/

DriverDescription
overlay2Es el preferido y no requiere ninguna configuración extra. Es la evolución del aufs.
fuse-overlayfsSolamente usado cuando overlay2 no es soportado
btrfs and zfsUtiliza el sistema Copy on Write (CoW) que utiliza el mismo archivo cuando hace lectura, pero cuando necesita escribir crea un archivo nuevo. Permite opciones más avanzadas, pero también más configuración y mantenimiento. Posibilita combinar varios dispositivos físicos para formar un único sistema de archivos
vfsEl rendimiento de este driver es malo y generalmente no es recomendado para uso en producción. Graba directo en el disco y no tiene soporte a CoW
aufsUsado solamente en versiones muy antiguas del kernel basado en debian que no soportan overlay2.
devicemapperRendimiento muy malo y solamente usado en versiones antiguas del kernel basado en RedHat
overlayObsoleto y legado, ni pierdas tiempo usándolo

Para saber cuál es el sistema de archivos que está siendo usado en el momento

docker system info
# o
vagrant@worker1:~$ docker system info | grep Storage
WARNING: No swap limit support
Storage Driver: overlay2
vagrant@worker1:~$

Modificar el Storage Drive requiere un cambio en el daemon en /etc/docker/daemon.json

Vamos a utilizar el json abajo para hacer un cambio de prueba

{
"storage-driver": "devicemapper"
}
vagrant@worker1:~$ sudo tee  /etc/docker/daemon.json << EOF
{
"storage-driver": "devicemapper"
}
EOF
vagrant@worker1:~$ sudo systemctl restart docker
vagrant@worker1:~$ sudo docker system info | grep Storage
WARNING: No swap limit support
WARNING: the devicemapper storage-driver is deprecated, and will be removed in a future release.
WARNING: devicemapper: usage of loopback devices is strongly discouraged for production use.
Use `--storage-opt dm.thinpooldev` to specify a custom block storage device.
# observe cómo cambió el driver de nuestro storage
Storage Driver: devicemapper
vagrant@worker1:~$

vuelve a overlay2 para continuar el resto de nuestro lab.

vagrant@worker1:~$ sudo tee  /etc/docker/daemon.json << EOF
{
"storage-driver": "overlay2"
}
EOF
{
"storage-driver": "overlay2"
}
vagrant@worker1:~$ sudo systemctl restart docker
vagrant@worker1:~$ sudo docker system info | grep Storage
WARNING: No swap limit support
Storage Driver: overlay2
vagrant@worker1:~$

Volúmenes

El volumen es básicamente un directorio que está designado para trabajar en uno o varios contenedores. Es una forma de compartir los datos para que puedas tener varios procesos leyendo y escribiendo del mismo lugar. Si el contenedor muere, los datos quedan persistentes en el volumen, entonces cuando el contenedor vuelva a ejecutarse los datos no se pierden. Cuando un contenedor muere no elimina el volumen automáticamente. Es un recurso separado y gestionado.

3 tipos básicos:

volumes

  • bind mount (volumen que reside dentro del mismo host que está ejecutando docker) es cuando el contenedor está usando archivos directo del filesystem del host. Un directorio dentro del contenedor se refleja en un directorio en el host.

  • volume (tipo nombrado o anónimo) es un sistema de archivos que docker gestiona para disponibilizar dentro del contenedor. ¿una máquina virtual no tiene un archivo que representa su disco duro? Es lo mismo.

    • Es mejor que utilizar el bind
    • Mejor para hacer backup
    • Puede ser gestionado por el cli de docker
    • Es compartido por múltiples contenedores de manera más segura
    • Puede ser habilitado almacenamiento externo en cloud por ejemplo.
    • volúmenes quedan dentro de /var/lib/docker
  • tmpfs mount es un volumen de tipo temporal, reside en la memoria ram de la máquina, si el host por ejemplo crashea pierdes el contenido. Es interesante para uso en ambiente de mucha performance. Muy pocos casos de uso.

Comandos

el comando docker volume

vagrant@worker1:~$ docker volume --help
Commands:
create Create a volume
inspect Display detailed information on one or more volumes
ls List volumes
prune Remove all unused local volumes
rm Remove one or more volumes

en el comando de manipulación de contenedor docker container si tiene el parámetro -v o --volume está pasando el volumen para el contenedor. La opción --mount es una opción más explícita y más verbosa.

  • -v : funciona para bind mount

  • --mount : funciona para el bind mount y para los volúmenes inclusive para servicios cloud, etc

Creando un contenedor con bind mount y verificando

                                           #host:container
docker container run -dit --name servidor -v /srv:/srv debian

# Ejecutando en el contenedor
vagrant@worker1:~$ docker container exec servidor df -Th
Filesystem Type Size Used Avail Use% Mounted on
# vea el overlay aquí dentro del contenedor
overlay overlay 39G 2.3G 37G 6% /
tmpfs tmpfs 64M 0 64M 0% /dev
tmpfs tmpfs 489M 0 489M 0% /sys/fs/cgroup
shm tmpfs 64M 0 64M 0% /dev/shm
/dev/sda1 ext4 39G 2.3G 37G 6% /srv
tmpfs tmpfs 489M 0 489M 0% /proc/acpi
tmpfs tmpfs 489M 0 489M 0% /proc/scsi
tmpfs tmpfs 489M 0 489M 0% /sys/firmware
# Ejecutando ahora en el host
vagrant@worker1:~$ df -Th
Filesystem Type Size Used Avail Use% Mounted on
udev devtmpfs 472M 0 472M 0% /dev
tmpfs tmpfs 98M 1.1M 97M 2% /run
# Vea que el sda1 tiene el mismo tamaño que el overlay del contenedor, o sea, es el mismo
/dev/sda1 ext4 39G 2.3G 37G 6% /
tmpfs tmpfs 489M 0 489M 0% /dev/shm
tmpfs tmpfs 5.0M 0 5.0M 0% /run/lock
tmpfs tmpfs 489M 0 489M 0% /sys/fs/cgroup
/dev/loop0 squashfs 62M 62M 0 100% /snap/core20/1518
/dev/loop1 squashfs 47M 47M 0 100% /snap/snapd/16010
/dev/loop2 squashfs 68M 68M 0 100% /snap/lxd/22753
vagrant vboxsf 440G 48G 392G 11% /vagrant
tmpfs tmpfs 98M 0 98M 0% /run/user/1000

Simplemente montó el camino /srv en el host dentro de /srv en el contenedor. Si creamos cualquier archivo allí dentro en cualquiera de los lados aparecerá.

# Vea que apareció el srv en /
vagrant@worker1:~$ ls /
bin boot dev etc home lib lib32 lib64 libx32 lost+found media mnt opt proc root run sbin snap srv sys tmp usr vagrant var
vagrant@worker1:~$ cd /srv/
vagrant@worker1:~$ cd /srv/
vagrant@worker1:/srv$ sudo touch arquivo1.txt
vagrant@worker1:/srv$ docker container exec servidor ls -lR /srv
/srv:
total 0
-rw-r--r-- 1 root root 0 Jun 23 17:11 arquivo1.txt
vagrant@worker1:/srv$ docker container exec servidor touch /srv/arquivo2.txt
# después de creado dentro del contenedor, se muestra en el host
vagrant@worker1:/srv$ ls
arquivo1.txt arquivo2.txt

##
vagrant@worker1:/srv$ docker container rm -f servidor
servidor
vagrant@worker1:/srv$ docker container ls -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
vagrant@worker1:/srv$

Creando un contenedor con volumen anónimo

Cuando paso solamente el volumen sin apuntar origen y destino, crea un volumen dentro de /var/lib/docker/volumes con un hash de 64 caracteres para el volumen.

vagrant@worker1:~$ docker container run -dit --name servidor -v /srv debian
vagrant@worker1:~$ docker volume ls
DRIVER VOLUME NAME
local 7f17a7c16d36d501b49da3cf2d7a7fda9c384ac145387377fa4cf099556ae5ec
vagrant@worker1:~$ docker container inspect servidor | grep volume
"Type": "volume",
"Source": "/var/lib/docker/volumes/7f17a7c16d36d501b49da3cf2d7a7fda9c384ac145387377fa4cf099556ae5ec/_data",
vagrant@worker1:~$

Observando el inspect vea que el hash del volumen es el mismo del volumen que tenemos.

obs: Los volúmenes de docker quedan en /var/lib/docker/volumes

Un volumen siempre tiene el _data que es de donde de hecho están los datos, pero en el mismo nivel puede tener otros archivos de metadata del volumen, tipo permisos.

también es posible inspeccionar el volumen

vagrant@worker1:~$ docker volume inspect 7f17a7c16d36d501b49da3cf2d7a7fda9c384ac145387377fa4cf099556ae5ec
[
{
"CreatedAt": "2022-06-23T17:48:15Z",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/7f17a7c16d36d501b49da3cf2d7a7fda9c384ac145387377fa4cf099556ae5ec/_data",
"Name": "7f17a7c16d36d501b49da3cf2d7a7fda9c384ac145387377fa4cf099556ae5ec",
"Options": null,
"Scope": "local"
}
]
vagrant@worker1:~$ docker container exec servidor touch /srv/arquivoVolume.txt
vagrant@worker1:~$ docker container inspect servidor | grep volume
"Type": "volume",
"Source": "/var/lib/docker/volumes/7f17a7c16d36d501b49da3cf2d7a7fda9c384ac145387377fa4cf099556ae5ec/_data",
vagrant@worker1:~$ sudo ls /var/lib/docker/volumes/7f17a7c16d36d501b49da3cf2d7a7fda9c384ac145387377fa4cf099556ae5ec/_data
arquivoVolume.txt

Si se crea un volumen pasando el nombre sin el / al frente, entenderá que es solo un nombre no el origen

vagrant@worker1:~$ docker container run -dit --name servidor -v volumedebian:/srv debian
76540b187448053054b227f20ba5a1bdab43185857c35dabf2c248f2aa42968e
vagrant@worker1:~$ docker volume list
DRIVER VOLUME NAME
local 7f17a7c16d36d501b49da3cf2d7a7fda9c384ac145387377fa4cf099556ae5ec
local volumedebian

Creando un contenedor usando el --mount

Como mencionado anteriormente el --mount requiere más parámetros.

vagrant@worker1:~$ docker container run -dit --name servidor --mount source=volume2,target=/meuvolume debian
be42b33fb8b6f928ceac632ec791c3da632b1a24ab36b239bab5e1e2c6eed597
vagrant@worker1:~$ docker volume list
DRIVER VOLUME NAME
local 7f17a7c16d36d501b49da3cf2d7a7fda9c384ac145387377fa4cf099556ae5ec
local volume2
local volumedebian
vagrant@worker1:~$

vagrant@worker1:~$ docker container inspect servidor --format '{{json .Mounts}}' | jq
[
{
"Type": "volume",
"Name": "volume2",
"Source": "/var/lib/docker/volumes/volume2/_data",
"Destination": "/meuvolume",
"Driver": "local",
# este mode z (minúscula) significa que comparte entre múltiples contenedores, si fuera Z (mayúscula) significa que no comparte
"Mode": "z",
"RW": true,
"Propagation": ""
}
]

Vamos a remover los volúmenes

vagrant@worker1:~$ docker volume ls
DRIVER VOLUME NAME
local 7f17a7c16d36d501b49da3cf2d7a7fda9c384ac145387377fa4cf099556ae5ec
local volume2
local volumedebian
vagrant@worker1:~$ docker volume rm volume2 volumedebian 7f17a7c16d36d501b49da3cf2d7a7fda9c384ac145387377fa4cf099556ae5ec
volumedebian
7f17a7c16d36d501b49da3cf2d7a7fda9c384ac145387377fa4cf099556ae5ec
Error response from daemon: remove volume2: volume is in use - [be42b33fb8b6f928ceac632ec791c3da632b1a24ab36b239bab5e1e2c6eed597]
# observe que no es posible remover un volumen en uso por un contenedor
vagrant@worker1:~$ docker volume ls
DRIVER VOLUME NAME
local volume2
vagrant@worker1:~$ docker container rm -f servidor
servidor
# El comando prune remueve todos los volúmenes que no están en uso por ningún contenedor
vagrant@worker1:~$ docker volume prune
WARNING! This will remove all local volumes not used by at least one container.
Are you sure you want to continue? [y/N] y
Deleted Volumes:
volume2

Total reclaimed space: 0B
vagrant@worker1:~$ docker volume ls
DRIVER VOLUME NAME
vagrant@worker1:~$

tmpfs mounts

--tmppfs

Como mencionado anteriormente es utilizado en memoria ram. Solamente interesante para aumentar el input y output de aplicaciones necesarias. También es útil para datos sensibles que no quieres grabar en el disco.

  • No es posible pasar parámetros como tamaño límite, etc
  • No tiene origen, pues el origen es la memoria, solamente el destino.
  • Este parámetro no funciona en cluster, en ese caso sería necesario usar el --mount type=tmpfs
  • tmpfs en swap no es posible
vagrant@worker1:~$ docker container run -dit --name servidor --tmpfs /volumetpmfs debian
30ce1b88b693104ec78c0360ed87dad23da7da74e410d8784ae88db36244cf91
vagrant@worker1:~$ docker container run -dit --name servidor2 --mount type=tmpfs,destination=/volumetmpfs debian
6d85918d60734564df7a3ed8e5937c3f446a5a9d0158fa398a43a53643aa5998
vagrant@worker1:~$
# observe que no tiene mount cuando subimos con el --tmpfs
vagrant@worker1:~$ docker container inspect servidor -f '{{json .Mounts}}' | jq
[]
vagrant@worker1:~$ docker container inspect servidor -f '{{json .HostConfig.Tmpfs}}' | jq
{
"/volumetpmfs": ""
}
vagrant@worker1:~$ docker container inspect servidor2 -f '{{json .Mounts}}' | jq
[
{
"Type": "tmpfs",
"Source": "",
"Destination": "/volumetmpfs",
"Mode": "",
"RW": true,
"Propagation": ""
}
]
# No tiene en HostConfig.Tmpfs cuando subimos con el --mount
vagrant@worker1:~$ docker container inspect servidor2 -f '{{json .HostConfig.Tmpfs}}' | jq
null

vagrant@worker1:~$ docker container exec servidor df -Th
Filesystem Type Size Used Avail Use% Mounted on
overlay overlay 39G 2.4G 37G 6% /
tmpfs tmpfs 64M 0 64M 0% /dev
# creó un tmpfs aquí con el tamaño de 500M que es el predeterminado
tmpfs tmpfs 489M 0 489M 0% /sys/fs/cgroup
shm tmpfs 64M 0 64M 0% /dev/shm
tmpfs tmpfs 489M 0 489M 0% /volumetpmfs
/dev/sda1 ext4 39G 2.4G 37G 6% /etc/hosts
tmpfs tmpfs 489M 0 489M 0% /proc/acpi
tmpfs tmpfs 489M 0 489M 0% /proc/scsi
tmpfs tmpfs 489M 0 489M 0% /sys/firmware

Para alterar el tamaño predeterminado o permiso solamente a través del --mount


# removiendo los contenedores y crear nuevamente con un tamaño fijo
vagrant@worker1:~$ docker container rm -f $(docker container ls -aq)
6d85918d6073
30ce1b88b693

vagrant@worker1:~$ docker container run -dit --name servidor --mount type=tmpfs,destination=/volumetmpfs,tmpfs-size=100M debian
vagrant@worker1:~$ docker container inspect servidor -f '{{json .HostConfig.Mounts}}' | jq
[
{
"Type": "tmpfs",
"Target": "/volumetmpfs",
"TmpfsOptions": {
"SizeBytes": 104857600
}
}
]
vagrant@worker1:~$ docker container exec servidor df -Th
Filesystem Type Size Used Avail Use% Mounted on
overlay overlay 39G 2.4G 37G 6% /
tmpfs tmpfs 64M 0 64M 0% /dev
tmpfs tmpfs 489M 0 489M 0% /sys/fs/cgroup
shm tmpfs 64M 0 64M 0% /dev/shm
# Mostrando aquí los 100M
tmpfs tmpfs 100M 0 100M 0% /volumetmpfs
/dev/sda1 ext4 39G 2.4G 37G 6% /etc/hosts
tmpfs tmpfs 489M 0 489M 0% /proc/acpi
tmpfs tmpfs 489M 0 489M 0% /proc/scsi
tmpfs tmpfs 489M 0 489M 0% /sys/firmware

intenté descargar una imagen de ubuntu dentro del volumen de 100M para ver qué pasaba

vagrant@worker1:~$ docker attach servidor
root@467701c637fc:/ apt-get update && apt-get install wget
....
root@467701c637fc:/ cd /volumetmpfs
root@467701c637fc:/volumetmpfs# wget https://releases.ubuntu.com/22.04/ubuntu-22.04-desktop-amd64.iso
.........
......... # Parte eliminada
ubuntu-22.04-desktop-amd64.iso 2%[=> ] 100.00M 21.7MB/s in 5.4s

# vea que se quedó sin espacio
Cannot write to 'ubuntu-22.04-desktop-amd64.iso' (No space left on device).
root@467701c637fc:/volumetmpfs#

Otro escenario que probé fue crear un contenedor con el tmpfs mayor que la memoria del host y lo permitió, pero cuando hice el wget de la imagen de ubuntu, el mismo procedimiento de arriba... cuando alcanzó cerca de 620MB de descarga, la máquina se volvió extremadamente lenta y poco responsiva. Entonces use con sabiduría. No llegó a crashear la máquina, pero puede afectar otros contenedores el uso indebido del tmpfs.

vagrant@worker1:~$ free -h
total used free shared buff/cache available
Mem: 976Mi 207Mi 634Mi 0.0Ki 134Mi 627Mi
Swap: 0B 0B 0B
vagrant@worker1:~$ docker container run -dit --name servidor --mount type=tmpfs,destination=/volumetmpfs,tmpfs-size=2000M debian
7355006196d27418913d904dd7e5ea8c9bf3187c44e171b22bcb6ea13f36d463
vagrant@worker1:~$

Backup y restore

Vamos a levantar un contenedor y colocar un archivo dentro

vagrant@worker1:~$ docker container run -dit --name servidor -v /webdata  debian
e8d3713e08bb7c35346f5b230fbd322e04e82497f58cc1513861563e067d4c3a
vagrant@worker1:~$ mkdir -p ~/dockerfiles/images/
vagrant@worker1:~$ echo "{FROM debian} "~/dockerfiles/images/Dockerfile
{FROM debian} ~/dockerfiles/images/Dockerfile
#origen #destino maquina:path
vagrant@worker1:~$ docker container cp ./dockerfiles/ servidor:/webdata
vagrant@worker1:~$ docker container exec servidor ls -lha /webdata
total 12K
drwxr-xr-x 3 root root 4.0K Jun 23 20:50 .
drwxr-xr-x 1 root root 4.0K Jun 23 20:50 ..
drwxrwxr-x 3 1000 1000 4.0K Jun 23 20:47 dockerfiles
vagrant@worker1:~$ docker volume ls
DRIVER VOLUME NAME
local 4806a0c128952f1fc2cc3c1101c8c465a5233fc0203cd87fa294fbad19fe13da
vagrant@worker1:~$ docker container inspect servidor | grep volume
"Type": "volume",
"Source": "/var/lib/docker/volumes/4806a0c128952f1fc2cc3c1101c8c465a5233fc0203cd87fa294fbad19fe13da/_data",

Ahora vamos a levantar otro contenedor apuntando al mismo volumen

vagrant@worker1:~$ docker container run -dit --name servidor2 -v 4806a0c128952f1fc2cc3c1101c8c465a5233fc0203cd87fa294fbad19fe13da:/webdata  debian
e8ac0f51f6eaa42401da39ae6e2fec182970c5b04b8fa5456cdc216c9657e384
vagrant@worker1:~$ docker container exec servidor2 ls -lha /webdata
total 12K
drwxr-xr-x 3 root root 4.0K Jun 23 20:50 .
drwxr-xr-x 1 root root 4.0K Jun 23 20:56 ..
drwxrwxr-x 3 1000 1000 4.0K Jun 23 20:47 dockerfiles

Pero esto es medio inviable y existe una flag llamada --volumes-from que mejora mucho cómo hacer esto.

vagrant@worker1:~$ docker container run -dit --name servidor3 --volumes-from servidor  debian
c60e0d3f1f1ddc935414d98b424ff5964cfbaf540e257b3797b78ed7f44013f1
# Verificando si el volumen está allá de la forma que estaba en el servidor 1
vagrant@worker1:~$ docker container exec servidor3 ls -lha /webdata
total 12K
drwxr-xr-x 3 root root 4.0K Jun 23 20:50 .
drwxr-xr-x 1 root root 4.0K Jun 23 20:57 ..
drwxrwxr-x 3 1000 1000 4.0K Jun 23 20:47 dockerfiles

Aprovechando lo que aprendemos aquí vamos ahora a levantar un contenedor alpine para hacer un backup del volumen

vagrant@worker1:~$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c60e0d3f1f1d debian "bash" 31 minutes ago Up 31 minutes servidor3
e8ac0f51f6ea debian "bash" 33 minutes ago Up 33 minutes servidor2
e8d3713e08bb debian "bash" 43 minutes ago Up 43 minutes servidor
vagrant@worker1:~$ mkdir backup
vagrant@worker1:~$ docker container run -dit --rm --volumes-from servidor -v ./backup:/backup alpine tar cvf /backup/backup.tar /webdata
c6ffeaa65384af74738c81cea51504795c4361fad0fca52055856b21186ae08b
vagrant@worker1:~$ ls backup/
backup.tar
vagrant@worker1:~$ tar tvf backup/backup.tar
drwxr-xr-x root/root 0 2022-06-23 20:50 webdata/
drwxrwxr-x 1000/1000 0 2022-06-23 20:47 webdata/dockerfiles/
drwxrwxr-x 1000/1000 0 2022-06-23 20:47 webdata/dockerfiles/images/

Ahora vamos a crear otro contenedor con el archivo backup.tar que creamos pero antes vamos a eliminar todos los contenedores y volúmenes.

# como vimos en el último comando arriba existe la carpeta webdata que contiene el restante, el strip 1 retira ese nivel
docker container run -dit --name servidor -v ~/backup:/webdata debian bash -c "tar -xf /webdata/backup.tar --strip 1 --directory /webdata && rm /webdata/backup.tar"

En este caso eliminé el backup, pero si no quisiera es solo levantar una máquina con un volumen vacío y otra con el volumes from y hacer backup de un directorio en el otro.