Comprender la instrucción "VOLUME" en DockerFile

136

A continuación se muestra el contenido de mi "Dockerfile"

FROM node:boron

# Create app directory
RUN mkdir -p /usr/src/app

# change working dir to /usr/src/app
WORKDIR /usr/src/app

VOLUME . /usr/src/app

RUN npm install

EXPOSE 8080

CMD ["node" , "server" ]

En este archivo espero la instrucción "VOLUME. / Usr / src / app" para montar el contenido del directorio de trabajo actual en el host que se montará en la carpeta / contenedor / usr / src / app.

Por favor, avíseme si esta es la forma correcta?

refactor
fuente

Respuestas:

88

El tutorial oficial de Docker dice:

Un volumen de datos es un directorio especialmente designado dentro de uno o más contenedores que omite el Sistema de archivos de la Unión. Los volúmenes de datos proporcionan varias características útiles para datos persistentes o compartidos:

  • Los volúmenes se inicializan cuando se crea un contenedor. Si la imagen base del contenedor contiene datos en el punto de montaje especificado,
    los datos existentes se copian en el nuevo volumen tras la
    inicialización del volumen . (Tenga en cuenta que esto no se aplica al montar un
    directorio de host ).
  • Los volúmenes de datos se pueden compartir y reutilizar entre contenedores.

  • Los cambios en un volumen de datos se realizan directamente.

  • Los cambios en un volumen de datos no se incluirán cuando actualice una imagen.

  • Los volúmenes de datos persisten incluso si se elimina el contenedor en sí.

En Dockerfilepuede especificar solo el destino de un volumen dentro de un contenedor. por ej /usr/src/app.

Cuando ejecuta un contenedor, por ejemplo docker run --volume=/opt:/usr/src/app my_image, es posible que no tenga que especificar su punto de montaje ( / opt ) en la máquina host. Si no especifica un --volumeargumento, el punto de montaje se elegirá automáticamente, generalmente debajo /var/lib/docker/volumes/.

Bukharov Sergey
fuente
277

En resumen: No, su VOLUMEinstrucción no es correcta.

Los archivos Docker VOLUMEespecifican uno o más volúmenes dados las rutas del lado del contenedor. Pero no permite que el autor de la imagen especifique una ruta de host. En el lado del host, los volúmenes se crean con un nombre de ID muy largo dentro de la raíz de Docker. En mi máquina esto es /var/lib/docker/volumes.

Nota: Debido a que el nombre autogenerado es extremadamente largo y no tiene sentido desde la perspectiva humana, estos volúmenes a menudo se denominan "sin nombre" o "anónimo".

Su ejemplo que usa un '.' el personaje ni siquiera se ejecutará en mi máquina, no importa si hago que el punto sea el primer o segundo argumento. Me sale este mensaje de error:

docker: Respuesta de error del daemon: error de tiempo de ejecución oci: container_linux.go: 265: el proceso de inicio del contenedor causó "process_linux.go: 368: el inicio del contenedor causó \" abrir / dev / ptmx: no existe tal archivo o directorio \ "".

Sé que lo que se ha dicho hasta este punto probablemente no sea muy valioso para alguien que intente comprenderlo VOLUMEy -vciertamente no proporciona una solución para lo que intenta lograr. Entonces, con suerte, los siguientes ejemplos arrojarán algo más de luz sobre estos temas.

Minitutorial: especificación de volúmenes

Dado este Dockerfile:

FROM openjdk:8u131-jdk-alpine
VOLUME vol1 vol2

(Para el resultado de este minitutorial, no hay diferencia si especificamos vol1 vol2o /vol1 /vol2no me preguntan por qué)

Constrúyelo:

docker build -t my-openjdk

Correr:

docker run --rm -it my-openjdk

Dentro del contenedor, ejecute lsen la línea de comando y notará que existen dos directorios; /vol1y /vol2.

La ejecución del contenedor también crea dos directorios, o "volúmenes", en el lado del host.

Mientras se ejecuta el contenedor, ejecute docker volume lsen la máquina host y verá algo como esto (he reemplazado la parte central del nombre con tres puntos por brevedad):

DRIVER    VOLUME NAME
local     c984...e4fc
local     f670...49f0

De vuelta en el contenedor , ejecutar touch /vol1/weird-ass-file(crea un archivo en blanco en dicha ubicación).

Este archivo ahora está disponible en la máquina host, en uno de los volúmenes sin nombre jajaja. Me tomó dos intentos porque probé por primera vez el primer volumen listado, pero finalmente encontré mi archivo en el segundo volumen listado, usando este comando en la máquina host:

sudo ls /var/lib/docker/volumes/f670...49f0/_data

Del mismo modo, puede intentar eliminar este archivo en el host y también se eliminará en el contenedor.

Nota: La _datacarpeta también se conoce como "punto de montaje".

Salga del contenedor y enumere los volúmenes en el host. Se han ido. Usamos la --rmbandera cuando ejecutamos el contenedor y esta opción elimina efectivamente no solo el contenedor al salir, sino también los volúmenes.

Ejecute un nuevo contenedor, pero especifique un volumen usando -v:

docker run --rm -it -v /vol3 my-openjdk

Esto agrega un tercer volumen y todo el sistema termina teniendo tres volúmenes sin nombre. El comando habría fallado si hubiéramos especificado solo -v vol3. El argumento debe ser una ruta absoluta dentro del contenedor. En el lado del host, el nuevo tercer volumen es anónimo y reside junto con los otros dos volúmenes /var/lib/docker/volumes/.

Se dijo anteriormente que Dockerfileno se puede asignar a una ruta de host, lo que nos plantea un problema al intentar traer archivos desde el host al contenedor durante el tiempo de ejecución. Una -vsintaxis diferente resuelve este problema.

Imagine que tengo una subcarpeta en el directorio de mi proyecto ./srcque deseo sincronizar /srcdentro del contenedor. Este comando hace el truco:

docker run -it -v $(pwd)/src:/src my-openjdk

Ambos lados del :personaje esperan un camino absoluto. El lado izquierdo es una ruta absoluta en la máquina host, el lado derecho es una ruta absoluta dentro del contenedor. pwdes un comando que "imprime el directorio actual / de trabajo". Poner el comando $()toma el comando entre paréntesis, lo ejecuta en un subshell y devuelve la ruta absoluta a nuestro directorio de proyecto.

En conjunto, supongamos que tenemos ./src/Hello.javaen nuestra carpeta de proyecto en la máquina host con el siguiente contenido:

public class Hello {
    public static void main(String... ignored) {
        System.out.println("Hello, World!");
    }
}

Construimos este Dockerfile:

FROM openjdk:8u131-jdk-alpine
WORKDIR /src
ENTRYPOINT javac Hello.java && java Hello

Ejecutamos este comando:

docker run -v $(pwd)/src:/src my-openjdk

Esto imprime "¡Hola, mundo!".

La mejor parte es que somos completamente libres de modificar el archivo .java con un nuevo mensaje para otra salida en una segunda ejecución, sin tener que reconstruir la imagen =)

Observaciones finales

Soy bastante nuevo en Docker, y el "tutorial" mencionado anteriormente refleja la información que obtuve de un hackathon de línea de comandos de 3 días. Estoy casi avergonzado de no haber podido proporcionar enlaces a documentación clara en inglés que respalde mis declaraciones, pero honestamente creo que esto se debe a la falta de documentación y no al esfuerzo personal. Sé que los ejemplos funcionan según lo anunciado usando mi configuración actual que es "Windows 10 -> Vagrant 2.0.0 -> Docker 17.09.0-ce".

El tutorial no resuelve el problema "cómo especificamos la ruta del contenedor en el Dockerfile y dejamos que el comando de ejecución solo especifique la ruta del host". Puede haber una manera, simplemente no la he encontrado.

Finalmente, tengo el presentimiento de que especificar VOLUMEen el Dockerfile no solo es infrecuente, sino que probablemente sea una mejor práctica para nunca usarlo VOLUME. Por dos razones. La primera razón que ya hemos identificado: no podemos especificar la ruta del host, lo cual es bueno porque los Dockerfiles deben ser muy independientes de los detalles de una máquina host. Pero la segunda razón es que las personas podrían olvidarse de usar la --rmopción al ejecutar el contenedor. Uno podría recordar quitar el contenedor pero olvidarse de quitar el volumen. Además, incluso con la mejor memoria humana, podría ser una tarea desalentadora descubrir cuál de todos los volúmenes anónimos es seguro eliminar.

Martin Andersson
fuente
2
¿Cuándo debemos usar volúmenes sin nombre / anónimos?
Searene
10
@ Martin muchas gracias. Su hackathon y su tutorial resultante aquí son muy apreciados.
Beezer
66
"No he podido proporcionar enlaces a una documentación clara similar al inglés ... Sinceramente, creo que esto se debe a la falta de documentación". Puedo confirmar. Esta es la documentación más completa y actualizada que he encontrado y que he estado buscando durante horas.
user697576
44
docker volume prunese puede usar para limpiar los volúmenes sobrantes que no están conectados a los contenedores en ejecución. No quiere decir que será fácil distinguir los potencialmente importantes solo por identificación ...
Jeremy
44
"Para el resultado de este minitutorial, no hay diferencia si especificamos vol1 vol2 o / vol1 / vol2, no me pregunten por qué". @MartinAndersson eso se debe a que el directorio de trabajo actual es /, por lo que vol1es relativo a /, lo que se resuelve /vol1. Si utiliza WORKDIRpara especificar un directorio de trabajo distinto de /, vol1y /vol1ya no apuntaría al mismo directorio.
Sebastian
41

Especificar una VOLUMElínea en un Dockerfile configura un poco de metadatos en su imagen, pero es importante cómo se usan esos metadatos.

Primero, qué hicieron estas dos líneas:

WORKDIR /usr/src/app
VOLUME . /usr/src/app

La WORKDIRlínea allí crea el directorio si no existe, y actualiza algunos metadatos de imagen para especificar todas las rutas relativas, junto con el directorio actual para los comandos como RUNestarán en esa ubicación. La VOLUMElínea allí especifica dos volúmenes , uno es la ruta relativa .y el otro es /usr/src/app, ambos resultan ser el mismo directorio. La mayoría de las veces, la VOLUMElínea solo contiene un único directorio, pero puede contener múltiples como lo ha hecho, o puede ser una matriz con formato json.

No puede especificar una fuente de volumen en el Dockerfile : una fuente común de confusión cuando se especifican volúmenes en un Dockerfile está tratando de hacer coincidir la sintaxis de tiempo de ejecución de una fuente y un destino en el momento de la creación de la imagen, esto no funcionará . El Dockerfile solo puede especificar el destino del volumen. Sería una vulnerabilidad de seguridad trivial si alguien pudiera definir la fuente de un volumen, ya que podría actualizar una imagen común en el Docker Hub para montar el directorio raíz en el contenedor y luego iniciar un proceso en segundo plano dentro del contenedor como parte de un punto de entrada que agrega inicios de sesión a / etc / passwd, configura systemd para lanzar un minero de bitcoin en el próximo reinicio, o busca en el sistema de archivos tarjetas de crédito, SSN y claves privadas para enviar a un sitio remoto.

¿Qué hace la línea VOLUME? Como se mencionó, establece algunos metadatos de imagen para decir que un directorio dentro de la imagen es un volumen. ¿Cómo se usan estos metadatos? Cada vez que cree un contenedor a partir de esta imagen, Docker forzará ese directorio a ser un volumen. Si no proporciona un volumen en su comando de ejecución, o redacta un archivo, la única opción para Docker es crear un volumen anónimo. Este es un volumen con nombre local con una identificación única larga para el nombre y ninguna otra indicación de por qué se creó o qué datos contiene (los volúmenes anónimos se pierden). Si anula el volumen, apuntando a un volumen con nombre o de host, sus datos irán allí.

VOLUME rompe cosas: no puede deshabilitar un volumen una vez definido en un Dockerfile. Y lo más importante, el RUNcomando en Docker se implementa con contenedores temporales. Esos contenedores temporales obtendrán un volumen anónimo temporal. Ese volumen anónimo se inicializará con el contenido de su imagen. Cualquier escritura dentro del contenedor desde su RUNcomando se realizará en ese volumen. Cuando RUNfinaliza el comando, se guardan los cambios en la imagen y se descartan los cambios en el volumen anónimo. Debido a esto, recomiendo no definir un VOLUMEDockerfile dentro. Resulta en un comportamiento inesperado para los usuarios intermedios de su imagen que desean extender la imagen con datos iniciales en la ubicación del volumen.

¿Cómo debe especificar un volumen? Para especificar dónde desea incluir volúmenes con su imagen, proporcione a docker-compose.yml. Los usuarios pueden modificar eso para ajustar la ubicación del volumen a su entorno local, y captura otras configuraciones de tiempo de ejecución como puertos de publicación y redes.

¡Alguien debería documentar esto! Ellos tienen. Docker incluye advertencias sobre el uso de VOLUME en su documentación en el Dockerfile junto con consejos para especificar la fuente en tiempo de ejecución:

  • Cambio del volumen desde el Dockerfile: si alguno de los pasos de compilación cambia los datos dentro del volumen después de que se haya declarado, esos cambios se descartarán.

...

  • El directorio del host se declara en tiempo de ejecución del contenedor: el directorio del host (el punto de montaje) depende, por su naturaleza, del host. Esto es para preservar la portabilidad de la imagen, ya que no se puede garantizar que un directorio de host determinado esté disponible en todos los hosts. Por esta razón, no puede montar un directorio de host desde el Dockerfile. La VOLUME instrucción no admite la especificación de un host-dirparámetro. Debe especificar el punto de montaje cuando cree o ejecute el contenedor.
BMitch
fuente
36

El VOLUMEcomando en a Dockerfilees bastante legítimo, totalmente convencional, absolutamente bueno de usar y no está en desuso de ninguna manera. Solo necesito entenderlo.

Lo usamos para señalar cualquier directorio en el que la aplicación del contenedor escribirá mucho. No usamos VOLUMEsolo porque queremos compartir entre el host y el contenedor como un archivo de configuración.

El comando simplemente necesita un parámetro; una ruta a una carpeta, relativa a WORKDIRsi está establecida, desde dentro del contenedor. Luego, docker creará un volumen en su gráfico (/ var / lib / docker) y lo montará en la carpeta del contenedor. Ahora el contenedor tendrá un lugar para escribir con alto rendimiento. Sin el VOLUMEcomando, la velocidad de escritura en la carpeta especificada será muy lenta porque ahora el contenedor está usando su copy on writeestrategia en el contenedor mismo. La copy on writeestrategia es una razón principal por la que existen volúmenes.

Si monta sobre la carpeta especificada por el VOLUMEcomando, el comando nunca se ejecuta porque VOLUMEsolo se ejecuta cuando se inicia el contenedor, algo así comoENV .

Básicamente con el VOLUMEcomando obtienes rendimiento sin montar externamente ningún volumen. Los datos también se guardarán en las ejecuciones de contenedores sin ningún montaje externo. Luego, cuando esté listo, simplemente monte algo sobre él.

Algunos buenos ejemplos de casos de uso:
- registros
- carpetas temporales

Algunos casos de mal uso:
- archivos estáticos
- configuraciones
- código

señor haven
fuente
2
Con respecto a los casos de uso de buenos y malos ejemplos, la página de "mejores prácticas de dockerfile" de Docker dice: "Se recomienda encarecidamente que use VOLUME para cualquier parte mutable y / o reparable por el usuario de su imagen". Creo que las configuraciones están ahí.
OmerSch
2
Está bien ser explícito sobre los VOLUMEdirectorios para las configuraciones. Sin embargo, una vez que realmente monte una configuración, tendrá que montar sobre ese directorio y, por lo tanto, el VOLUMEcomando no se ejecutará. Por lo tanto, no tiene sentido utilizar el VOLUMEcomando en un directorio especificado para una configuración. También inicializar un gráfico de volumen con un solo archivo estático de solo lectura es una exageración grave. Así que mantengo lo que dije, no hay necesidad de VOLUMEcomando en las configuraciones.
Sr. refugio el
Los volúmenes pueden traer diferentes características de rendimiento debido a los detalles de implementación. Los archivos de datos de la base de datos encajan en este caso de uso, pero ¿cuál sería el punto de almacenar datos junto con el almacenamiento del contenedor (efímero) de todos modos? Es decir, atribuir la existencia de volúmenes al rendimiento es incorrecto.
André Werlang
33

Para comprender mejor las volumeinstrucciones en dockerfile, aprendamos el uso de volumen típico en la implementación del archivo docker oficial mysql.

VOLUME /var/lib/mysql

Referencia: https://github.com/docker-library/mysql/blob/3362baccb4352bcf0022014f67c1ec7e6808b8c5/8.0/Dockerfile

La /var/lib/mysqles la ubicación predeterminada de MySQL que almacena archivos de datos.

Cuando ejecuta el contenedor de prueba solo para fines de prueba, es posible que no especifique su punto de montaje, p. Ej.

docker run mysql:8

entonces la instancia del contenedor mysql usará la ruta de montaje predeterminada que se especifica mediante la volumeinstrucción en dockerfile. los volúmenes se crean con un nombre de ID muy largo dentro de la raíz de Docker, esto se llama volumen "sin nombre" o "anónimo". En la carpeta del sistema host subyacente / var / lib / docker / volume.

/var/lib/docker/volumes/320752e0e70d1590e905b02d484c22689e69adcbd764a69e39b17bc330b984e4

Esto es muy conveniente para fines de prueba rápida sin la necesidad de especificar el punto de montaje, pero aún puede obtener el mejor rendimiento al usar Volumen para el almacenamiento de datos, no la capa de contenedor.

Para un uso formal, deberá especificar la ruta de montaje utilizando un volumen con nombre o un montaje de enlace, p. Ej.

docker run  -v /my/own/datadir:/var/lib/mysql mysql:8

El comando monta el directorio / my / own / datadir desde el sistema host subyacente como / var / lib / mysql dentro del contenedor. El directorio de datos / my / own / datadir no se eliminará automáticamente, incluso el contenedor se eliminará.

Uso de la imagen oficial de mysql (consulte la sección "Dónde almacenar datos"):

Referencia: https://hub.docker.com/_/mysql/

Li-Tian
fuente
2
Me gusta mucho tu explicación.
LukaszTaraszka
Pero Docker guarda los cambios de todos modos. También puede configurar la ruta de montaje para -vusarla sin configurar el volumen en el Dockerfile
Alex78191
1

No considero que el uso de VOLUME sea bueno en ningún caso, excepto si está creando una imagen para usted y nadie más la va a usar.

Me impactó negativamente debido al VOLUMEN expuesto en las imágenes base que extendí y solo me enteré del problema después de que la imagen ya se estaba ejecutando, como wordpress que declara la /var/www/htmlcarpeta como VOLUMEN , y esto significa que cualquier archivo agregado o cambiado durante la etapa de construcción no se considera y los cambios en vivo persisten, incluso si no lo sabe. Hay una solución fea para definir el directorio web en otro lugar, pero esta es solo una mala solución a una más simple: simplemente elimine la directiva VOLUME.

Puede lograr la intención de volumen fácilmente usando la -vopción, esto no solo deja en claro cuáles serán los volúmenes del contenedor (sin tener que mirar el Dockerfile y los Dockerfiles principales), sino que también le da al consumidor la opción de usa el volumen o no.

Es básicamente malo usar VOLUMES debido a las siguientes razones, como se dice en esta respuesta :

Sin embargo, la instrucción VOLUME tiene un costo.

  • Es posible que los usuarios no estén al tanto de los volúmenes sin nombre que se están creando y continúen ocupando espacio de almacenamiento en su host Docker después de que se eliminan los contenedores.
  • No hay forma de eliminar un volumen declarado en un Dockerfile. Las imágenes posteriores no pueden agregar datos a las rutas donde existen volúmenes.

El último problema resulta en problemas como estos.

Sería útil tener la opción de anular la declaración de un volumen, pero solo si conoce los volúmenes definidos en el dockerfile que generó la imagen (¡y los dockerfiles primarios!). Además, se podría agregar un VOLUMEN en versiones más nuevas de un Dockerfile y romper cosas inesperadamente para los consumidores de la imagen.

Otra buena explicación ( sobre la imagen del oráculo con VOLUMEN , que fue eliminada ): https://github.com/oracle/docker-images/issues/640#issuecomment-412647328

Más casos en los que VOLUME rompió cosas para las personas:

Se cerró una solicitud de extracción para agregar opciones para restablecer las propiedades de la imagen principal (incluido VOLUMEN) y se está discutiendo aquí (y puede ver varios casos de personas afectadas negativamente debido a los volúmenes definidos en dockerfiles), que tiene un comentario con un buen explicación contra VOLUMEN:

Usar VOLUME en el Dockerfile no tiene valor. Si un usuario necesita persistencia, se asegurará de proporcionar una asignación de volumen cuando ejecute el contenedor especificado. Fue muy difícil rastrear que mi problema de no poder establecer la propiedad de un directorio (/ var / lib / influxdb) se debió a la declaración de VOLUMEN en el Dockerfile de InfluxDB. Sin un tipo de opción UNVOLUME, o deshaciéndolo por completo, no puedo cambiar nada relacionado con la carpeta especificada. Esto es menos que ideal, especialmente cuando conoce la seguridad y desea especificar un cierto UID, la imagen debe ejecutarse para evitar que un usuario aleatorio, con más permisos de los necesarios, ejecute software en su host.

También considero que EXPOSE es malo, pero tiene menos efectos secundarios. Lo único bueno que puedo ver sobre VOLUME y EXPOSE es sobre la documentación, y los consideraría buenos si solo sirvieran para eso (sin efectos secundarios).

TL; DR

Considero que el mejor uso de VOLUME es ser obsoleto.

Lucas Basquerotto
fuente