Solución:
En resumen: No, tu VOLUME
la instrucción no es correcta.
Dockerfile’s VOLUME
especificar uno o más volúmenes dadas 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 similar a un ID muy largo dentro de la raíz de Docker. En mi maquina esto es /var/lib/docker/volumes
.
Nota: Debido a que el nombre generado automáticamente es extremadamente largo y no tiene sentido desde la perspectiva de un ser humano, estos volúmenes a menudo se denominan “sin nombre” o “anónimos”.
Su ejemplo que usa un ‘.’ El carácter ni siquiera se ejecutará en mi máquina, sin importar si hago que el punto sea el primer o segundo argumento. Recibo este mensaje de error:
docker: Respuesta de error de daemon: oci runtime error: container_linux.go: 265: el proceso de inicio del contenedor provocó “process_linux.go: 368: el inicio del contenedor provocó” open / 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 comprender VOLUME
y -v
y ciertamente no proporciona una solución para lo que intenta lograr. Por lo tanto, es de esperar que los siguientes ejemplos arrojen más luz sobre estos problemas.
Minitutorial: especificar volúmenes
Dado este Dockerfile:
FROM openjdk:8u131-jdk-alpine
VOLUME vol1 vol2
(Para el resultado de este minitutorial, no importa si especificamos vol1 vol2
o /vol1 /vol2
– esto se debe a que el directorio de trabajo predeterminado dentro de un Dockerfile es /
)
Constrúyelo:
docker build -t my-openjdk
Correr:
docker run --rm -it my-openjdk
Dentro del contenedor, corre ls
en la línea de comandos y notará que existen dos directorios; /vol1
y /vol2
.
La ejecución del contenedor también crea dos directorios, o “volúmenes”, en el lado del host.
Mientras tiene el contenedor en ejecución, ejecute docker volume ls
sobre el máquina anfitriona y verá algo como esto (he reemplazado la parte central del nombre con tres puntos para abreviar):
DRIVER VOLUME NAME
local c984...e4fc
local f670...49f0
Regreso en el envase, 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 lol. Me tomó dos intentos porque primero probé 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 _data
La carpeta también se conoce como “punto de montaje”.
Salga del contenedor y enumere los volúmenes en el host. Se han ido. Usamos el --rm
marca al ejecutar el contenedor y esta opción elimina eficazmente no solo el contenedor a la salida, sino también los volúmenes.
Ejecute un nuevo contenedor, pero especifique un volumen usando -v
:
docker run --rm -it -v /vol3 my-openjdk
Esta agrega un tercer volumen y todo el sistema termina teniendo tres volúmenes sin nombre. El comando se habría bloqueado si hubiéramos especificado solo -v vol3
. El argumento debe ser un absoluto sendero dentro El contenedor. En el lado del host, el nuevo tercer volumen es anónimo y reside junto con los otros dos volúmenes en /var/lib/docker/volumes/
.
Se dijo anteriormente que el Dockerfile
no se puede asignar a una ruta de host, lo que nos plantea un problema cuando intentamos traer archivos desde el host al contenedor durante el tiempo de ejecución. Una diferente -v
la sintaxis resuelve este problema.
Imagina que tengo una subcarpeta en el directorio de mi proyecto. ./src
que deseo sincronizar /src
dentro del contenedor. Este comando hace el truco:
docker run -it -v $(pwd)/src:/src my-openjdk
Ambos lados del :
El personaje espera una ruta absoluta. El lado izquierdo es una ruta absoluta en la máquina host, el lado derecho es una ruta absoluta dentro del contenedor. pwd
es un comando que “imprime el directorio actual / de trabajo”. Poniendo el comando en $()
toma el comando entre paréntesis, lo ejecuta en una subcapa y devuelve la ruta absoluta al directorio de nuestro proyecto.
Poniéndolo todo junto, supongamos que tenemos ./src/Hello.java
en 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 recopilé de un hackathon de línea de comandos de 3 días. Casi me avergüenza 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 como se anuncia con 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 que haya una forma, pero no la he encontrado.
Finalmente, tengo el presentimiento de que especificar VOLUME
en el Dockerfile no solo es poco común, pero probablemente sea una mejor práctica no usar nunca VOLUME
. Por dos razones. La primera razón que ya hemos identificado: no podemos especificar la ruta del host, lo cual es bueno porque Dockerfiles debería ser muy independiente de las especificaciones de una máquina host. Pero la segunda razón es que la gente puede olvidarse de usar el --rm
opción al ejecutar el contenedor. Uno podría recordar quitar el recipiente pero olvidarse de quitar el volumen. Además, incluso con la mejor memoria humana, podría ser una tarea abrumadora averiguar cuál de todos los volúmenes anónimos es seguro eliminar.
El tutorial oficial de Docker dice:
Un volumen de datos es un directorio especialmente designado dentro de uno o más contenedores que pasa por alto el Union File System. Los volúmenes de datos proporcionan varias funciones ú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,
que los datos existentes se copian en el nuevo volumen en volumen
inicialización. (Tenga en cuenta que esto no se aplica al montar un host
directorio.)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 Dockerfile
puede especificar solo el destino de un volumen dentro un contenedor. p.ej /usr/src/app
.
Cuando ejecuta un contenedor, p. Ej. docker run --volume=/opt:/usr/src/app my_image
, usted mayo pero no es necesario especificar su punto de montaje (/opt
) en la máquina host. Si no especifica --volume
argumento, entonces el punto de montaje se elegirá automáticamente, generalmente bajo /var/lib/docker/volumes/
.
Especificando un VOLUME
line en un Dockerfile configura un poco de metadatos en su imagen, pero cómo se usan esos metadatos es importante.
Primero, ¿qué hicieron estas dos líneas?
WORKDIR /usr/src/app
VOLUME . /usr/src/app
los WORKDIR
La lí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 comandos como RUN
estará en esa ubicación. los VOLUME
línea allí especifica dos volúmenes, uno es el camino relativo .
y el otro es /usr/src/app
, ambos resultan ser el mismo directorio. La mayoría de las veces VOLUME
la línea solo contiene un único directorio, pero puede contener varios como lo hizo, o puede ser un formato json array.
No puede especificar una fuente de volumen en el Dockerfile: Una fuente común de confusión al especificar volúmenes en un Dockerfile es intentar hacer coincidir la sintaxis de tiempo de ejecución de una fuente y un destino en el momento de la construcció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 concentrador de la ventana acoplable 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 iniciar un minero de bitcoin en el próximo reinicio o busca en el sistema de archivos tarjetas de crédito, SSN y privados keys 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 utilizan estos metadatos? Cada vez que crea un contenedor a partir de esta imagen, la ventana acoplable obligará a ese directorio a ser un volumen. Si no proporciona un volumen en su comando de ejecución o redactar un archivo, la única opción para la ventana acoplable 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 son donde los datos se pierden). Si anula el volumen, apuntando a un volumen con nombre o de host, sus datos irán allí.
VOLUMEN rompe cosas: No puede deshabilitar un volumen una vez definido en un Dockerfile. Y lo que es más importante, RUN
El comando 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 de su RUN
se hará el comando a ese volumen. Cuando el RUN
finaliza el comando, los cambios en la imagen se guardan y los cambios en el volumen anónimo se descartan. Debido a esto, recomiendo encarecidamente no definir un VOLUME
dentro del Dockerfile. Da lugar a un comportamiento inesperado para los usuarios intermedios de su imagen que desean ampliar la imagen con los 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 un 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 publicar puertos y redes.
¡Alguien debería documentar esto! Ellos tienen. Docker incluye advertencias sobre el uso de VOLUME en su documentación en Dockerfile junto con consejos para especificar la fuente en tiempo de ejecución:
- Cambiar el volumen desde dentro del Dockerfile: Si algún paso de compilación cambia los datos dentro del volumen después de que se haya declarado, esos cambios se descartarán.
…
- El directorio de host se declara en tiempo de ejecución del contenedor: El directorio del host (el punto de montaje) es, por naturaleza, dependiente del host. Esto es para preservar la portabilidad de la imagen, ya que no se puede garantizar que un directorio de host dado esté disponible en todos los hosts. Por esta razón, no puede montar un directorio de host desde dentro del Dockerfile. los
VOLUME
instrucción no admite la especificación de unhost-dir
parámetro. Debe especificar el punto de montaje cuando crea o ejecuta el contenedor.
Si te animas, tienes la opción de dejar una sección acerca de qué te ha gustado de esta sección.