Introducción
Docker es una popular herramienta de contenedorización utilizada para proporcionar aplicaciones de software con un sistema de archivos que contiene todo lo que necesitan para ejecutarse. El uso de contenedores Docker garantiza que el software se comportará de la misma manera. Esto independientemente de dónde se implemente, ya que su entorno de tiempo de ejecución es coherente.
En general, los contenedores Docker son efímeros y se ejecutan todo el tiempo necesario para que se complete el comando emitido en el contenedor. A veces, sin embargo, las aplicaciones necesitan compartir el acceso a los datos o persistir después de eliminar un contenedor. Las bases de datos, el contenido generado por el usuario para un sitio web y los archivos de registro. Estos son algunos ejemplos de datos que no son prácticos o imposibles de incluir en una imagen de Docker, pero a qué aplicaciones necesitan acceder. El acceso persistente a los datos se proporciona con volúmenes en Docker.
Los Docker Volumes (volúmenes de docker) pueden crearse y adjuntarse en el mismo comando que crea un contenedor, o pueden crearse independientemente de cualquier contenedor y adjuntarse más tarde. En este artículo, veremos cuatro formas diferentes de compartir datos entre contenedores.
Prerrequisitos
Para seguir este artículo, necesitarás un servidor Ubuntu 18.04 con lo siguiente:
- Un usuario no root con privilegios de sudo. La guía Configuración básica del servidor con Ubuntu 18.04 explica cómo configurar esto.
- Docker se debe instalar con las instrucciones del Paso 1 y el Paso 2 de Cómo instalar y usar Docker en Ubuntu 18.04
Nota: Los requisitos previos dan instrucciones para instalar Docker en Ubuntu 18.04. Empero, los comandos docker para los volúmenes de datos de Docker en este artículo deberían funcionar en otros sistemas operativos. Esto siempre que Docker esté instalado y el usuario de sudo haya sido agregado al grupo docker.
Paso 1: creación de un volumen independiente
Introducido en la versión 1.9 de Docker, el comando docker volume create te permite crear un volumen sin relacionarlo con ningún contenedor en particular. Usaremos este comando para agregar un volumen llamado DataVolume1:
1 |
$ docker volume create --name DataVolume1 |
Se muestra el nombre, lo que indica que el comando se realizó correctamente:
1 2 |
Output DataVolume1 |
Para hacer uso del volumen, crearemos un nuevo contenedor a partir de la imagen de Ubuntu, utilizando el indicador –rm para eliminarlo automáticamente cuando salgamos. También usaremos –v para montar el nuevo volumen. –v requiere el nombre del volumen, dos puntos, luego la ruta absoluta a donde debe aparecer el volumen dentro del contenedor. Si los directorios en la ruta no existen como parte de la imagen, se crearán cuando se ejecute el comando. Si existen, el volumen montado ocultará el contenido existente:
1 |
$ docker run -ti --rm -v DataVolume1:/datavolume1 ubuntu |
Mientras estés en el contenedor, escribe algunos datos en el volumen:
1 |
root@802b0a78f2ef:/#echo "Example1" > /datavolume1/Example1.txt |
Debido a que usamos el indicador –rm, nuestro contenedor se eliminará automáticamente cuando salgamos. Nuestro volumen, sin embargo, seguirá siendo accesible.
1 |
root@802b0a78f2ef:/# exit |
Podemos verificar que el volumen esté presente en nuestro sistema con docker volume inspect:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
$ docker volume inspect DataVolume1 Output [ { "CreatedAt": "2018-07-11T16:57:54Z", "Driver": "local", "Labels": {}, "Mountpoint": "/var/lib/docker/volumes/DataVolume1/_data", "Name": "DataVolume1", "Options": {}, "Scope": "local" } ] |
Nota: Incluso podemos ver los datos en el host en la ruta indicada como Mountpoint. Sin embargo, debemos evitar alterarlo, ya que puede causar daños en los datos si las aplicaciones o los contenedores no son conscientes de los cambios.
A continuación, comencemos un nuevo contenedor y adjuntemos DataVolume1:
Para verificar los contenidos:
1 2 3 4 |
root@d73eca0365fc:/# cat /datavolume1/Example1.txt Output Example1 |
Debes salir del contenedor:
1 |
root@d73eca0365fc:/# exit |
En este ejemplo, creamos un volumen, lo adjuntamos a un contenedor y verificamos su persistencia.
Paso 2: creación de un volumen que persiste cuando se elimina el contenedor
En nuestro próximo ejemplo, crearemos un volumen al mismo tiempo que el contenedor, eliminaremos el contenedor y luego adjuntaremos el volumen a un nuevo contenedor.
Utilizaremos el comando run docker para crear un nuevo contenedor utilizando la imagen base de Ubuntu. –t nos dará un terminal e –i nos permitirá interactuar con él. Para mayor claridad, usaremos –name para identificar el contenedor.
El indicador –v nos permitirá crear un nuevo volumen, al que llamaremos DataVolume2. Usaremos dos puntos para separar este nombre de la ruta donde se debe montar el volumen en el contenedor. Por último, vamos a especificar la imagen de Ubuntu base y confiar en el comando por defecto en el docker de Ubuntu. Usaremos bash para utilizar una shell.
1 |
$ docker run -ti --name=Container2 -v DataVolume2:/datavolume2 ubuntu |
Nota:
El indicador -v es muy flexible. Puedes enlazar o nombrar un volumen con solo un ligero ajuste en la sintaxis. Si el primer argumento comienza con a / o ~/ estás creando un bindmount. Elimina eso y estarás nombrando el volumen. Por ejemplo:
- -v /path:/path/in/container monta el directorio de host, /path en el /path/in/container
- -v path:/path/in/container crea un volumen nombrado path sin relación con el host.
Para obtener más información sobre cómo montar un directorio desde el host, puedes consultar cómo compartir datos entre un contenedor Docker y el host.
Mientras estés en el contenedor, escribiremos algunos datos en el volumen:
1 2 |
root@87c33b5ae18a:/# echo "Example2" > /datavolume2/Example2.txt root@87c33b5ae18a:/# cat /datavolume2/Example2.txt |
1 2 |
Output Example2 |
Salgamos del contenedor:
1 |
root@87c33b5ae18a:/# exit |
Cuando reiniciamos el contenedor, el volumen se montará automáticamente:
1 |
$ docker start -ai Container2 |
Verifiquemos que el volumen se haya montado y nuestros datos aún estén en su lugar:
1 |
root@87c33b5ae18a:/# cat /datavolume2/Example2.txt |
1 2 |
Output Example2 |
Finalmente, salgamos y limpiemos:
1 |
root@87c33b5ae18a:/# exit |
Docker no nos permitirá eliminar un volumen si un contenedor hace referencia a él. Veamos qué sucede cuando intentamos:
1 |
$ docker volume rm DataVolume2 |
El mensaje nos dice que el volumen todavía está en uso y proporciona la versión larga del ID del contenedor:
1 2 |
Output Error response from daemon: unable to remove volume: remove DataVolume2: volume is in use - [d0d2233b668eddad4986313c7a4a1bc0d2edaf0c7e1c02a6a6256de27db17a63] |
Podemos usar este ID para eliminar el contenedor:
1 2 3 4 |
$ docker rm d0d2233b668eddad4986313c7a4a1bc0d2edaf0c7e1c02a6a6256de27db17a63 Output d0d2233b668eddad4986313c7a4a1bc0d2edaf0c7e1c02a6a6256de27db17a63 |
Remover el contenedor no afectará el volumen. Podemos ver que todavía está presente en el sistema al enumerar los volúmenes con docker volume ls:
1 2 3 4 5 |
$ docker volume ls Output DRIVER VOLUME NAME local DataVolume2 |
Y podemos usar docker volume rm para eliminarlo:
1 |
$ docker volume rm DataVolume2 |
En este ejemplo, creamos un volumen de datos vacío al mismo tiempo que creamos un contenedor. En nuestro próximo ejemplo, exploraremos lo que sucede cuando creamos un volumen con un directorio contenedor que ya contiene datos.
Paso 3: crear un volumen a partir de un directorio existente con datos
Generalmente, crear un volumen independientemente con docker volume create y crear uno mientras se crea un contenedor son equivalentes, con una excepción. Si creamos un volumen al mismo tiempo que creamos un contenedor. Luego proporcionamos la ruta a un directorio que contiene datos en la imagen base, esos datos se copiarán en el volumen.
Como ejemplo, crearemos un contenedor y agregaremos el volumen de datos en /var, un directorio que contiene datos en la imagen base:
1 |
$ docker run -ti --rm -v DataVolume3:/var ubuntu |
Todo el contenido del directorio /var de la imagen base se copia en el volumen, y podemos montar ese volumen en un nuevo contenedor.
Debes salir del contenedor actual:
1 |
root@87c33b5ae18a:/# exit |
Esta vez, en lugar de depender del comando bash predeterminado de la imagen base, emitiremos nuestro propio comando ls. Este mostrará el contenido del volumen sin ingresar a la shell:
1 |
$ docker run --rm -v DataVolume3:/datavolume3 ubuntu ls datavolume3 |
El directorio datavolume3 ahora tiene una copia de los contenidos del directorio /var de la imagen base:
1 2 3 4 5 6 7 8 9 10 11 12 |
Output backups cache lib local lock log mail opt run spool tmp |
Es poco probable que queramos montar /var/ de esta manera. Empero esto puede ser útil si hemos creado nuestra propia imagen y queremos una manera fácil de preservar los datos. En nuestro próximo ejemplo, demostraremos cómo se puede compartir un volumen entre varios contenedores.
Paso 4: compartir datos entre múltiples contenedores Docker
Hasta ahora, hemos adjuntado un volumen a un contenedor a la vez. A menudo, queremos que se adjunten varios contenedores al mismo volumen de datos. Esto es relativamente sencillo de lograr, pero hay una advertencia crítica: en este momento, Docker no maneja el bloqueo de archivos. Si necesitas que varios contenedores escriban en el volumen, las aplicaciones que se ejecutan en esos contenedores deben estar diseñadas para escribir en almacenes de datos compartidos. Esto para evitar la corrupción de datos.
Crear Container4 y DataVolume4
Debes usar docker run para crear un nuevo contenedor llamado Container4 con un volumen de datos adjunto:
1 |
$ docker run -ti --name=Container4 -v DataVolume4:/datavolume4 ubuntu |
A continuación, crearemos un archivo y agregaremos texto:
1 |
root@db6aaead532b:/# echo "This file is shared between containers" > /datavolume4/Example4.txt |
Luego, saldremos del contenedor:
1 |
root@db6aaead532b:/# exit |
Esto nos devuelve al símbolo del sistema host, donde crearemos un nuevo contenedor desde el que se monta el volumen de datos Container4.
Crear Container5 y montar volúmenes desde Container4
Vamos a crear Container5 y montar los volúmenes desde Container4:
1 |
$ docker run -ti --name=Container5 --volumes-from Container4 ubuntu |
Veamos la persistencia de datos:
1 |
root@81e7a6153d28:/# cat /datavolume4/Example4.txt |
1 2 |
Output This file is shared between containers |
Ahora agreguemos un texto de Container5:
1 |
root@81e7a6153d28:/# echo "Both containers can write to DataVolume4" >> /datavolume4/Example4.txt |
Finalmente, saldremos del contenedor:
1 |
root@81e7a6153d28:/# exit |
A continuación, verificaremos que nuestros datos aún estén presentes Container4.
Ver los cambios realizados en Container5
Revisemos los cambios que se escribieron en el volumen de datos Container5 reiniciando Container4:
1 |
$ docker start -ai Container4 |
Verificar los cambios:
1 |
root@db6aaead532b:/# cat /datavolume4/Example4.txt |
1 2 3 |
Output This file is shared between containers Both containers can write to DataVolume4 |
Ahora que hemos verificado que ambos contenedores pudieron leer y escribir desde el volumen de datos, saldremos del contenedor:
1 |
root@db6aaead532b:/# exit |
Nuevamente, Docker no maneja ningún bloqueo de archivos, por lo que las aplicaciones deben tener en cuenta el bloqueo de archivos ellos mismos. Es posible montar un volumen Docker como de solo lectura para garantizar que la corrupción de datos no ocurra por accidente. Esto se puede lograr cuando un contenedor requiera acceso de solo lectura mediante la adición de :ro. Veamos cómo funciona esto.
Iniciar el contenedor 6 y montar el volumen de solo lectura
Una vez que se ha montado un volumen en un contenedor, en lugar de desmontarlo como lo haríamos con un sistema de archivos Linux típico, podemos crear un nuevo contenedor montado de la manera que queramos y, si es necesario, eliminar el contenedor anterior. Para hacer que el volumen sea de solo lectura, agregamos :ro al final del nombre del contenedor:
1 |
$ docker run -ti --name=Container6 --volumes-from Container4:ro ubuntu |
Verificaremos el estado de solo lectura intentando eliminar nuestro archivo de ejemplo:
1 |
root@81e7a6153d28:/# rm /datavolume4/Example4.txt |
1 2 |
Output rm: cannot remove '/datavolume4/Example4.txt': Read-only file system |
Finalmente, saldremos del contenedor y limpiaremos nuestros contenedores y volúmenes de prueba:
1 |
root@81e7a6153d28:/# exit |
Ahora que hemos terminado, limpiemos nuestros contenedores y volumen:
1 2 |
$ docker rm Container4 Container5 Container6 $ docker volume rm DataVolume4 |
En este ejemplo, mostramos cómo compartir datos entre dos contenedores usando un volumen de datos y cómo montar un volumen de datos como solo lectura.
Conclusión
En este tutorial, creamos un volumen de datos que permitió que los datos persistieran mediante la eliminación de un contenedor. Compartimos volúmenes de datos entre contenedores. Esto con la advertencia de que las aplicaciones deberán diseñarse para manejar el bloqueo de archivos para evitar la corrupción de datos. Finalmente, mostramos cómo montar un volumen compartido en modo de solo lectura.