Noções básicas sobre a instrução "VOLUME" no DockerFile

136

Abaixo está o conteúdo do meu "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" ]

Neste arquivo, estou esperando a instrução "VOLUME. / Usr / src / app" para montar o conteúdo do diretório de trabalho atual no host a ser montado na pasta / usr / src / app do contêiner.

Por favor, deixe-me saber se esta é a maneira correta?

refatorar
fonte

Respostas:

88

O tutorial oficial do docker diz:

Um volume de dados é um diretório especialmente designado em um ou mais contêineres que ignoram o Union File System. Os volumes de dados fornecem vários recursos úteis para dados persistentes ou compartilhados:

  • Os volumes são inicializados quando um contêiner é criado. Se a imagem base do contêiner contiver dados no ponto de montagem especificado,
    esses dados existentes serão copiados no novo volume após a
    inicialização do volume . (Observe que isso não se aplica ao montar um
    diretório host .)
  • Os volumes de dados podem ser compartilhados e reutilizados entre contêineres.

  • As alterações em um volume de dados são feitas diretamente.

  • As alterações em um volume de dados não serão incluídas quando você atualizar uma imagem.

  • Os volumes de dados persistem mesmo se o próprio contêiner for excluído.

Em Dockerfilevocê pode especificar apenas o destino de um volume dentro de um contêiner. por exemplo /usr/src/app.

Quando você executa um contêiner, por exemplo docker run --volume=/opt:/usr/src/app my_image, você pode, mas não precisa especificar seu ponto de montagem ( / opt ) na máquina host. Se você não especificar um --volumeargumento, o ponto de montagem será escolhido automaticamente, geralmente em /var/lib/docker/volumes/.

Bukharov Sergey
fonte
275

Em resumo: não, suas VOLUMEinstruções não estão corretas.

Os Dockerfile VOLUMEespecificam um ou mais volumes, conforme os caminhos do contêiner. Mas não permite que o autor da imagem especifique um caminho de host. No lado do host, os volumes são criados com um nome muito semelhante ao ID dentro da raiz do Docker. Na minha máquina é isso /var/lib/docker/volumes.

Nota: Como o nome gerado automaticamente é extremamente longo e não faz sentido da perspectiva de um ser humano, esses volumes geralmente são chamados de "sem nome" ou "anônimo".

Seu exemplo que usa um '.' O caractere nem será executado na minha máquina, não importa se eu coloco o ponto no primeiro ou no segundo argumento. Recebo esta mensagem de erro:

janela de encaixe: Resposta de erro do daemon: oci erro de tempo de execução: container_linux.go: 265: processo de contêiner inicial causado "process_linux.go: 368: init do contêiner causado \" open / dev / ptmx: arquivo ou diretório \ "".

Sei que o que foi dito até este ponto provavelmente não é muito valioso para alguém que está tentando entender VOLUMEe -vcertamente não fornece uma solução para o que você tenta realizar. Portanto, esperamos que os exemplos a seguir possam esclarecer um pouco mais essas questões.

Minitutorial: Especificando volumes

Dado este arquivo Docker:

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

(Para o resultado deste minitutorial, não faz diferença se especificarmos vol1 vol2ou /vol1 /vol2- não me pergunte por que)

Construa:

docker build -t my-openjdk

Corre:

docker run --rm -it my-openjdk

Dentro do contêiner, execute lsna linha de comando e você notará que existem dois diretórios; /vol1e /vol2.

A execução do contêiner também cria dois diretórios, ou "volumes", no lado do host.

Enquanto o contêiner estiver em execução, execute docker volume lsna máquina host e você verá algo assim (substituí a parte do meio do nome por três pontos por questões de concisão):

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

De volta ao contêiner , execute touch /vol1/weird-ass-file(cria um arquivo em branco no local mencionado).

Este arquivo está agora disponível na máquina host, em um dos volumes não nomeados, lol. Foram necessárias duas tentativas porque tentei primeiro o primeiro volume listado, mas, eventualmente, encontrei meu arquivo no segundo volume listado, usando este comando na máquina host:

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

Da mesma forma, você pode tentar excluir este arquivo no host e ele também será excluído no contêiner.

Nota: A _datapasta também é chamada de "ponto de montagem".

Saia do contêiner e liste os volumes no host. Eles se foram. Usamos o --rmsinalizador ao executar o contêiner e essa opção efetivamente elimina não apenas o contêiner na saída, mas também os volumes.

Execute um novo contêiner, mas especifique um volume usando -v:

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

Isso adiciona um terceiro volume e todo o sistema acaba tendo três volumes sem nome. O comando teria travado se tivéssemos especificado apenas -v vol3. O argumento deve ser um caminho absoluto dentro do contêiner. No lado do host, o novo terceiro volume é anônimo e reside junto com os outros dois volumes /var/lib/docker/volumes/.

Foi declarado anteriormente que Dockerfilenão é possível mapear para um caminho de host que meio que representa um problema para nós ao tentar trazer arquivos do host para o contêiner durante o tempo de execução. Uma -vsintaxe diferente resolve esse problema.

Imagine que eu tenho uma subpasta no diretório do projeto ./srcque desejo sincronizar /srcdentro do contêiner. Este comando faz o truque:

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

Ambos os lados do :personagem esperam um caminho absoluto. O lado esquerdo é um caminho absoluto na máquina host, o lado direito é um caminho absoluto dentro do contêiner. pwdé um comando que "imprime o diretório atual / ativo". Colocar o comando $()leva o comando entre parênteses, o executa em um subshell e retorna o caminho absoluto para o diretório de nosso projeto.

Juntando tudo, suponha que temos ./src/Hello.javaem nossa pasta de projeto na máquina host com o seguinte conteúdo:

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

Nós construímos este Dockerfile:

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

Nós executamos este comando:

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

Isso imprime "Olá, mundo!".

A melhor parte é que estamos completamente livres para modificar o arquivo .java com uma nova mensagem para outra saída em uma segunda execução - sem precisar reconstruir a imagem =)

Considerações finais

Eu sou bastante novo no Docker, e o "tutorial" mencionado acima reflete as informações que reuni em um hackathon de três dias na linha de comando. Estou quase envergonhado por não ter conseguido fornecer links para uma documentação clara em inglês que respalda minhas declarações, mas sinceramente acho que isso se deve à falta de documentação e não a esforços pessoais. Sei que os exemplos funcionam conforme anunciado usando minha configuração atual, que é "Windows 10 -> Vagrant 2.0.0 -> Docker 17.09.0-ce".

O tutorial não resolve o problema "como podemos especificar o caminho do contêiner no Dockerfile e permitir que o comando run especifique apenas o caminho do host". Pode haver uma maneira, simplesmente não a encontrei.

Finalmente, tenho a sensação de que especificar VOLUMEno Dockerfile não é apenas incomum, mas provavelmente é uma prática recomendada para nunca ser usada VOLUME. Por duas razões. A primeira razão pela qual já identificamos: Não podemos especificar o caminho do host - o que é bom, pois os Dockerfiles devem ser muito independentes dos detalhes de uma máquina host. Mas o segundo motivo é que as pessoas podem esquecer de usar a --rmopção ao executar o contêiner. Pode-se lembrar de remover o recipiente, mas esqueça de remover o volume. Além disso, mesmo com o melhor da memória humana, pode ser uma tarefa assustadora descobrir qual de todos os volumes anônimos é seguro remover.

Martin Andersson
fonte
2
Quando devemos usar volumes anônimos / anônimos?
Searene
10
@ Martin muito obrigado. Seu hackathon e seu tutorial resultante aqui são muito apreciados.
Beezer 31/03
6
"Não consegui fornecer links para limpar a documentação em inglês ... honestamente, acho que isso se deve à falta de documentação". Eu posso confirmar. Esta é a documentação mais completa e atualizada que eu encontrei e estou procurando há horas.
User697576
4
docker volume prunepode ser usado para limpar os volumes que não estão anexados à execução de contêineres. Não quer dizer que ele vai ser fácil de distingiush os potencialmente importantes por id sozinho ...
Jeremy
4
"Para o resultado deste minitutorial, não faz diferença se especificarmos vol1 vol2 ou / vol1 / vol2 - não me pergunte por que". @MartinAndersson é porque o diretório de trabalho atual é /, portanto, vol1é relativo a /, o que resolve /vol1. Se você usar WORKDIRpara especificar um diretório de trabalho diferente /, vol1e /vol1não iria apontar para o mesmo diretório.
sebastian
41

A especificação de uma VOLUMElinha em um Dockerfile configura um pouco de metadados na sua imagem, mas é importante como esses metadados são usados.

Primeiro, o que essas duas linhas fizeram:

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

A WORKDIRlinha lá cria o diretório, se ele não existir, e atualiza alguns metadados da imagem para especificar todos os caminhos relativos, juntamente com o diretório atual para comandos como os RUNque estarão nesse local. A VOLUMElinha lá especifica dois volumes , um é o caminho relativo .e o outro é /usr/src/app, ambos por acaso são do mesmo diretório. Na maioria das vezes, a VOLUMElinha contém apenas um único diretório, mas pode conter vários como você fez, ou pode ser uma matriz formatada em json.

Você não pode especificar uma fonte de volume no Dockerfile : uma fonte comum de confusão ao especificar volumes em um Dockerfile está tentando corresponder à sintaxe de tempo de execução de uma fonte e destino no momento da criação da imagem; isso não funcionará . O Dockerfile pode especificar apenas o destino do volume. Seria uma exploração de segurança trivial se alguém pudesse definir a origem de um volume, pois poderia atualizar uma imagem comum no hub docker para montar o diretório raiz no contêiner e iniciar um processo em segundo plano dentro do contêiner como parte de um ponto de entrada que adiciona logins ao / etc / passwd, configura o systemd para iniciar um minerador de bitcoin na próxima reinicialização ou pesquisa no sistema de arquivos por cartões de crédito, SSNs e chaves privadas para enviar para um site remoto.

O que a linha VOLUME faz? Como mencionado, ele define alguns metadados da imagem para dizer que um diretório dentro da imagem é um volume. Como esses metadados são usados? Sempre que você cria um contêiner a partir dessa imagem, o docker força esse diretório a ser um volume. Se você não fornecer um volume em seu comando de execução ou compor arquivo, a única opção para o docker é criar um volume anônimo. Este é um volume nomeado local com um ID exclusivo longo para o nome e nenhuma outra indicação do motivo pelo qual ele foi criado ou quais dados ele contém (volumes anônimos são onde os dados se perdem). Se você substituir o volume, apontando para um volume nomeado ou host, seus dados serão enviados para lá.

VOLUME interrompe as coisas: você não pode desativar um volume depois de definido em um arquivo de encaixe. E mais importante, o RUNcomando na janela de encaixe é implementado com contêineres temporários. Esses contêineres temporários receberão um volume anônimo temporário. Esse volume anônimo será inicializado com o conteúdo da sua imagem. Quaisquer gravações dentro do contêiner do seu RUNcomando serão feitas nesse volume. Quando o RUNcomando termina, as alterações na imagem são salvas e as alterações no volume anônimo são descartadas. Por esse VOLUMEmotivo , eu recomendo fortemente não definir um arquivo Dockerfile interno. Isso resulta em um comportamento inesperado para usuários a jusante da sua imagem que desejam estender a imagem com dados iniciais no local do volume.

Como você deve especificar um volume? Para especificar onde você deseja incluir volumes com sua imagem, forneça a docker-compose.yml. Os usuários podem modificar isso para ajustar o local do volume ao ambiente local e capturar outras configurações de tempo de execução, como portas de publicação e rede.

Alguém deve documentar isso! Eles têm. O Docker inclui avisos sobre o uso do VOLUME em sua documentação no Dockerfile, juntamente com conselhos para especificar a origem no tempo de execução:

  • Alterando o volume de dentro do Dockerfile: Se alguma etapa de compilação alterar os dados dentro do volume após a declaração, essas alterações serão descartadas.

...

  • O diretório do host é declarado no tempo de execução do contêiner: O diretório do host (o ponto de montagem) é, por natureza, dependente do host. Isso é para preservar a portabilidade da imagem, pois não é possível garantir que um determinado diretório de host esteja disponível em todos os hosts. Por esse motivo, você não pode montar um diretório host a partir do Dockerfile. A VOLUME instrução não suporta a especificação de um host-dirparâmetro. Você deve especificar o ponto de montagem ao criar ou executar o contêiner.
BMitch
fonte
36

O VOLUMEcomando a Dockerfileé bastante legítimo, totalmente convencional, absolutamente bom de usar e não é preterido de qualquer maneira. Só preciso entender isso.

Nós o usamos para apontar para quaisquer diretórios nos quais o aplicativo no contêiner gravará muito. Não usamos VOLUMEapenas porque queremos compartilhar entre host e contêiner como um arquivo de configuração.

O comando simplesmente precisa de um parâmetro; um caminho para uma pasta, em relação a WORKDIRse definido, de dentro do contêiner. O docker criará um volume em seu gráfico (/ var / lib / docker) e o montará na pasta no contêiner. Agora, o contêiner terá um local para gravar com alto desempenho. Sem o VOLUMEcomando, a velocidade de gravação na pasta especificada será muito lenta porque agora o contêiner está usando sua copy on writeestratégia no próprio contêiner. A copy on writeestratégia é a principal razão pela qual os volumes existem.

Se você montar sobre a pasta especificada pelo VOLUMEcomando, o comando nunca será executado porque VOLUMEsó é executado quando o contêiner é iniciado, mais ou menos como ENV.

Basicamente, com o VOLUMEcomando, você obtém desempenho sem montar externamente nenhum volume. Os dados também serão salvos nas execuções de contêiner, sem montagens externas. Então, quando estiver pronto, basta montar algo sobre ele.

Alguns bons exemplos de casos de uso:
- logs
- pastas temporárias

Alguns casos de uso ruins:
- arquivos estáticos
- configurações
- código

senhor refúgio
fonte
2
Em relação aos bons e maus exemplos de casos de uso, a página "práticas recomendadas do dockerfile" do Docker diz: "É altamente recomendável usar VOLUME para qualquer parte mutável e / ou reparável pelo usuário da sua imagem". Eu acho que as configurações estão lá.
OmerSch
2
Não há problema em ser explícito sobre os VOLUMEdiretórios para configurações. No entanto, depois de montar uma configuração, você precisará montar esse diretório e, portanto, o VOLUMEcomando não será executado. Portanto, não faz sentido usar o VOLUMEcomando em um diretório especificado para uma configuração. Também inicializar um gráfico de volume com um único arquivo estático de somente leitura é um sério exagero. Por isso, mantenho o que disse, sem necessidade de VOLUMEcomando nas configurações.
Sr. Haven
Os volumes podem trazer características de desempenho diferentes devido aos detalhes da implementação. Os arquivos de dados do banco de dados se encaixam nesse caso de uso, mas qual seria o sentido de armazenar dados juntamente com o armazenamento em contêiner (efêmero)? Ou seja, atribuir a existência de volumes ao desempenho está incorreto.
André Werlang 14/07
33

Para entender melhor as volumeinstruções no dockerfile, vamos aprender o uso típico do volume na implementação oficial do arquivo docker do mysql.

VOLUME /var/lib/mysql

Referência: https://github.com/docker-library/mysql/blob/3362baccb4352bcf0022014f67c1ec7e6808b8c5/8.0/Dockerfile

O /var/lib/mysqlé o local padrão do MySQL que armazena arquivos de dados.

Ao executar o contêiner de teste apenas para fins de teste, você não pode especificar seu ponto de montagem, por exemplo

docker run mysql:8

então a instância do contêiner mysql usará o caminho de montagem padrão especificado pela volumeinstrução em dockerfile. os volumes são criados com um nome muito semelhante ao ID dentro da raiz do Docker, isso é chamado de volume "sem nome" ou "anônimo". Na pasta do sistema host subjacente / var / lib / docker / volumes.

/var/lib/docker/volumes/320752e0e70d1590e905b02d484c22689e69adcbd764a69e39b17bc330b984e4

Isso é muito conveniente para fins de teste rápido, sem a necessidade de especificar o ponto de montagem, mas ainda pode obter o melhor desempenho usando o Volume para armazenamento de dados, não a camada de contêiner.

Para um uso formal, você precisará especificar o caminho da montagem usando volume nomeado ou montagem de ligação, por exemplo

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

O comando monta o diretório / my / own / datadir do sistema host subjacente como / var / lib / mysql dentro do contêiner.O diretório de dados / my / own / datadir não será excluído automaticamente, até o contêiner será excluído.

Uso da imagem oficial do mysql (por favor, verifique a seção "Onde Armazenar Dados"):

Referência: https://hub.docker.com/_/mysql/

Li-Tian
fonte
2
Eu gosto muito da sua explicação.
LukaszTaraszka
Mas o docker salva as alterações de qualquer maneira. Além disso, você pode definir o caminho de montagem para -vusá-lo sem definir o volume no Dockerfile
Alex78191
1

De qualquer forma, não considero bom o uso do VOLUME, exceto se você estiver criando uma imagem para si e mais ninguém a usará.

Fui impactado negativamente devido ao VOLUME exposto nas imagens de base que estendi e só descobri o problema depois que a imagem já estava em execução, como o wordpress que declara a /var/www/htmlpasta como VOLUME , e isso significava que todos os arquivos foram adicionados ou alterados durante o estágio de construção não é considerado e as alterações ao vivo persistem, mesmo que você não saiba. Existe uma solução alternativa feia para definir o diretório da Web em outro local, mas esta é apenas uma solução ruim para uma necessidade mais simples: basta remover a diretiva VOLUME.

Você pode atingir a intenção do volume facilmente usando a -vopção, isso não apenas deixa claro quais serão os volumes do contêiner (sem precisar dar uma olhada no Dockerfile e nos Dockerfiles pai), mas também oferece ao consumidor a opção de use o volume ou não.

É basicamente ruim usar VOLUMES devido aos seguintes motivos, como dito por esta resposta :

No entanto, a instrução VOLUME tem um custo.

  • Os usuários podem não estar cientes dos volumes não nomeados sendo criados e continuar ocupando espaço de armazenamento em seu host Docker após a remoção dos contêineres.
  • Não há como remover um volume declarado em um Dockerfile. Imagens a jusante não podem adicionar dados a caminhos nos quais existem volumes.

O último problema resulta em problemas como estes.

Ter a opção de cancelar a declaração de um volume ajudaria, mas apenas se você conhecer os volumes definidos no arquivo docker que gerou a imagem (e os arquivos docker pai!). Além disso, um VOLUME pode ser adicionado em versões mais recentes de um Dockerfile e interromper as coisas inesperadamente para os consumidores da imagem.

Outra boa explicação ( sobre a imagem do oracle com VOLUME , que foi removida ): https://github.com/oracle/docker-images/issues/640#issuecomment-412647328

Mais casos em que VOLUME quebrou coisas para as pessoas:

Uma solicitação pull para adicionar opções para redefinir propriedades da imagem pai (incluindo VOLUME) foi encerrada e está sendo discutida aqui (e você pode ver vários casos de pessoas afetadas adversamente devido aos volumes definidos nos arquivos de encaixe), que possui um comentário com um bom explicação contra VOLUME:

Usar VOLUME no Dockerfile é inútil. Se um usuário precisar de persistência, ele fornecerá um mapeamento de volume ao executar o contêiner especificado. Foi muito difícil rastrear que meu problema de não conseguir definir a propriedade de um diretório (/ var / lib / influxdb) se devia à declaração VOLUME no Dockerfile do InfluxDB. Sem um tipo de opção UNVOLUME, ou sem me livrar dele, não consigo alterar nada relacionado à pasta especificada. Isso é menos que o ideal, especialmente quando você tem consciência de segurança e deseja especificar um determinado UID, a imagem deve ser executada como, a fim de evitar que um usuário aleatório, com mais permissões do que o necessário, execute o software em seu host.

Eu também considero EXPOSE ruim, mas tem menos efeitos colaterais. A única coisa boa que eu posso ver sobre VOLUME e EXPOSE é sobre documentação, e eu os consideraria bons se eles servissem apenas para isso (sem efeitos colaterais).

TL; DR

Considero que o melhor uso do VOLUME deve ser preterido.

Lucas Basquerotto
fonte