Hemos creado un contenedor a partir de una imagen, pero no es un contenedor cualquiera. Este contenedor maneja datos, ficheros y cambia constantemente. Tenemos un pequeño problema.
Hemos dicho en otras entradas, que los contenedores son objetos efímeros. Se crean y se destruyen. En su naturaleza está escrita su eventual parada por múltiples motivos: errores internos, balanceo de red, etc. Sin embargo los contenedores pueden exponer servicios que no tengan en cuanta la propia naturaleza de los contenedores. Si tenemos una aplicación que se conecta a una base de datos, no podemos permitir que el contenedor en el que está la base de datos desaparezca y con él todos los cambios.
Los contenedores crean una nueva capa sobre la imagen que utilizan y solo sobre esta ultima capa pueden escribir y modificar cosas, pero una vez el contenedor desaparece, esa capa no persiste, desaparece con él. ¿Cómo hacemos entonces si queremos guardar esos cambios?
Índice
Los volúmenes y los puntos de montaje
Es necesario conocer la naturaleza de Docker un mínimo para comprender este punto. Docker usa namespaces únicos para cada contenedor, y cada vez que lanzamos un contenedor, por defecto, crea una serie de namespaces propios ajeno a los del sistema host y un volumen. Uno de los namespaces es el namespace «mount».
El namespace mount es el encargado de manejar los puntos de montaje de un FileSystem. Esto nos permite que los contenedores estén completamente aislados y no puedan acceder a los puntos de montaje del host, pero tambien nos permite montar determinadas rutas del host en los contenedores (a esto se le denomina bind mount).
En esta imagen podemos apreciar cómo los volúmenes no dejan de ser áreas del FileSystem del host, el cual tambien puede tener rutas montadas en el contenedor. Los volúmenes son un objeto de Docker, igual que las redes, los contenedores, las imágenes o las etiquetas de contenedores, y se puede trabajar con ellos de forma aislada. Es importante tener en cuanta que con el borrado de un contenedor no se borra el volumen asociado.
Los volumenes
Existen determinadas imágenes que por los procesos que representarán sus contenedores, generan volúmenes automáticamente. Por ejemplo, si exploramos el dockerfile de mysql, veremos como contiene una sentencia VOLUME, que se encarga de crear un volumen y vinvularlo con una ruta del futuro contenedor.
Una vez creado el contenedor de la imagen de mysql, podemos inspeccionarlo (subcomandos container isnpect) y ver en su apartado Mounts, que tiene un volumen asociado.
El atributo source es la ruta de host y destination la ruta del contenedor. Si exploramos las carpetas desde el host y el contenedor respectivamente veremos exactamente los mismos datos.
El hecho de que un volumen sea un objeto separado de un contenedor en Docker, implica que un mismo volumen puede ser usado por más de un contenedor, incluso simultáneamente. Vamos a ver en un ejemplo este caso.
Como vemos arriba, no se ha creado un nuevo volumen al crear el segundo contenedor, ya que especificamos con el parámetro -v un volumen preexistente mediante el nombre, el cual se puede setear concatenando antes de la ruta destino de montaje del volumen en el contenedor y separado por dos puntos.
Los volúmenes, a diferencia de los bind mounts que veremos a continuacion pueden crearse desde el Dockerfile o en tiempo de creación del contenedor con el parámetro anteriormente mencionado.
Los binding mounts
Aparte de los directorios/volúmenes del host que podemos montar en los contenedores, podemos montar otras ubicaciones de nuestro host dentro del contenedor con los llamados binding mounts.
Estos binding mount funcionan de forma muy similar a los volúmenes del apartado anterior, pero tienen una serie de características propias.
- Como ya hemos dicho, los directorios están ubicados fuera de la ruta /var/lib/docker/volumes, en nuestro host.
- El montaje sobre un directorio del contenedor que no está vacío, reemplaza el contenido original del contenedor.
- Los cambios ejecutados desde el contenedor sobre los archivos del bind mount reemplazan, por defecto, los archivos originales del host. A todos los efectos, tanto Docker como el host están sincronizados en el acceso a una determinada ruta. Los puntos de montaje de Docker no se comportan como la capa superior que crean los contenedores durante su ejecución. No son una capa más de la imagen, son la propia ubicación del host.
- Mucho cuidado con el montaje de rutas críticas del host en contenedores, pues estos ejecutan procesos como usuario root, lo que implica un escalado de privilegios inmediato para cualquier usuario con permiso de ejecucion de Docker. Ver este artículo.
Como ejemplo, si quiero vincular una carpeta local del host que sirva como carpeta de un contenedor nginx, sólo tengo que usar el parámetro anteriormente visto para los volúmenes a la hora de crear dicho contenedor, con la salvedad de que tengo que usar la ruta completa o relativa a la izquierda de los dos puntos para mapear las localizaciones. Puedes ver cómo en la documentación de la imagen oficial de nginx en Docker Hub.
Otra forma de hacer los bind mounts
Además de usar el parámetro -v a la hora de lanzar el contenedor, podemos hilar más fino a la hora de crear estos puntos de montaje de los contenedores. El parámetro -v crea un punto de montaje con unos atributos por defecto, pero podemos cambiar estos atributos con un parámetro más verboso: –mount.
Dejo el enlace de la documentacion oficial de Docker para que podais bucear un poco más al respecto.