Reconstruir o contêiner do Docker nas alterações de arquivo


86

Para executar um aplicativo ASP.NET Core, gerei um dockerfile que constrói o aplicativo e copia o código-fonte no contêiner, que é buscado por Git usando Jenkins. Portanto, em meu espaço de trabalho, faço o seguinte no dockerfile:

WORKDIR /app
COPY src src

Embora o Jenkins atualize os arquivos em meu host corretamente com Git, o Docker não aplica isso à minha imagem.

Meu script básico para construção:

#!/bin/bash
imageName=xx:my-image
containerName=my-container

docker build -t $imageName -f Dockerfile  .

containerRunning=$(docker inspect --format="{{ .State.Running }}" $containerName 2> /dev/null)

if [ "$containerRunning" == "true" ]; then
        docker stop $containerName
        docker start $containerName
else
        docker run -d -p 5000:5000 --name $containerName $imageName
fi

Eu tentei coisas diferentes, como --rme --no-cacheparâmetro para docker rune também parar / retirar o recipiente antes de o novo é de criação. Não tenho certeza do que estou fazendo de errado aqui. Parece que o docker está atualizando a imagem corretamente, pois a chamada de COPY src srcresultaria em um ID de camada e nenhuma chamada de cache:

Step 6 : COPY src src
 ---> 382ef210d8fd

Qual é a maneira recomendada de atualizar um contêiner?

Meu cenário típico seria: O aplicativo está sendo executado no servidor em um contêiner do Docker. Agora, partes do aplicativo são atualizadas, por exemplo, modificando um arquivo. Agora, o contêiner deve executar a nova versão. O Docker parece recomendar a construção de uma nova imagem em vez de modificar um contêiner existente, então acho que a maneira geral de reconstruir como eu faço é a correta, mas alguns detalhes na implementação precisam ser melhorados.


Você pode listar as etapas exatas que realizou para construir seu contêiner, incluindo o comando de construção e toda a saída de cada comando?
— BMitch

Respostas:


136

Depois de algumas pesquisas e testes, descobri que tinha alguns mal-entendidos sobre a vida útil dos contêineres Docker. Simplesmente reiniciar um contêiner não faz com que o Docker use uma nova imagem, quando a imagem foi reconstruída nesse meio tempo. Em vez disso, o Docker busca a imagem apenas antes de criar o contêiner. Portanto, o estado após a execução de um contêiner é persistente.

Por que a remoção é necessária

Portanto, reconstruir e reiniciar não é suficiente. Achei que os containers funcionassem como um serviço: parando o serviço, faça suas alterações, reinicie-o e eles seriam aplicados. Esse foi meu maior erro.

Como os contêineres são permanentes, você deve removê-los usando docker rm <ContainerName>primeiro. Depois que um contêiner é removido, você não pode simplesmente iniciá-lo por docker start. Isso deve ser feito usando docker run, que por si só usa a imagem mais recente para criar uma nova instância de contêiner.

Os recipientes devem ser o mais independentes possível

Com esse conhecimento, é compreensível por que o armazenamento de dados em contêineres é qualificado como uma prática ruim, e o Docker recomenda volumes de dados / montagem de diretórios de host em vez disso: Como um contêiner deve ser destruído para atualizar aplicativos, os dados armazenados dentro dele também seriam perdidos. Isso causa trabalho extra para desligar serviços, fazer backup de dados e assim por diante.

Portanto, é uma solução inteligente excluir completamente esses dados do contêiner: não precisamos nos preocupar com nossos dados, quando estão armazenados com segurança no host e o contêiner contém apenas o próprio aplicativo.

Por -rfque não pode realmente ajudá-lo

O docker runcomando tem um switch de limpeza chamado -rf. Isso interromperá o comportamento de manter os containers docker permanentemente. Usando -rf, o Docker destruirá o contêiner após sua saída. Mas essa mudança tem dois problemas:

  1. O Docker também remove os volumes sem um nome associado ao contêiner, o que pode matar seus dados
  2. Usando esta opção, não é possível executar contêineres em segundo plano usando a -dchave

Embora a -rfopção seja uma boa opção para economizar trabalho durante o desenvolvimento para testes rápidos, é menos adequada na produção. Especialmente por causa da falta da opção de executar um contêiner em segundo plano, o que na maioria das vezes seria necessário.

Como remover um recipiente

Podemos contornar essas limitações simplesmente removendo o contêiner:

docker rm --force <ContainerName>

A opção --force(ou -f) que usa SIGKILL em contêineres em execução. Em vez disso, você também pode parar o contêiner antes de:

docker stop <ContainerName>
docker rm <ContainerName>

Ambos são iguais. docker stoptambém está usando SIGTERM . Mas usar a --forceopção encurtará seu script, especialmente ao usar servidores CI: docker stopgera um erro se o contêiner não estiver em execução. Isso faria com que o Jenkins e muitos outros servidores de CI considerassem erroneamente o build como com falha. Para corrigir isso, você deve primeiro verificar se o contêiner está funcionando como eu fiz na pergunta (consulte a containerRunningvariável).

Script completo para reconstruir um contêiner Docker

De acordo com esse novo conhecimento, corrigi meu script da seguinte maneira:

#!/bin/bash
imageName=xx:my-image
containerName=my-container

docker build -t $imageName -f Dockerfile  .

echo Delete old container...
docker rm -f $containerName

echo Run new container...
docker run -d -p 5000:5000 --name $containerName $imageName

Isso funciona perfeitamente :)


4
'Descobri que tinha alguns mal-entendidos sobre a vida útil dos contêineres Docker', você tirou as palavras da minha boca. Obrigado por essa explicação detalhada. Eu recomendaria isso para iniciantes do docker. Isso esclarece a diferença entre VM e contêiner.
— Vince Banzon

2
Após sua explicação, o que fiz foi anotar o que fiz com minha imagem existente. Para manter as alterações, criei um novo Dockerfile para criar uma nova imagem que já inclui as alterações que desejo adicionar. Dessa forma, a nova imagem criada é (um pouco) atualizada.
— Vince Banzon,

A --force-recreateopção no docker compose é semelhante ao que você descreve aqui? E se sim, não valeria a pena usar esta solução em vez disso (desculpe se essa pergunta for idiota, mas eu sou um docker noob ^^)
— cglacet

2
@cglacet Sim, é semelhante, não diretamente comparável. Mas docker-composeé mais inteligente do que os dockercomandos simples . Eu trabalho regularmente com docker-composee a detecção de alterações funciona bem, então eu uso --force-recreatemuito raramente. Só docker-compose up --buildé importante quando você está construindo uma imagem personalizada ( builddiretiva no arquivo de composição) em vez de usar uma imagem, por exemplo, do hub do Docker.
— Leão

33

Sempre que alterações forem feitas no dockerfile ou composição ou requisitos, execute-o novamente usando docker-compose up --build. Para que as imagens sejam reconstruídas e atualizadas


1
Tendo um contêiner docker do MySQL como um serviço, o banco de dados estaria vazio depois disso se fosse usado um volume /opt/mysql/data:/var/lib/mysql?
— Martin Thoma

Para mim, não parece haver nenhuma desvantagem em apenas usar sempre --buildem ambientes de desenvolvimento locais. A velocidade na qual o docker copia novamente os arquivos que, de outra forma, poderia assumir que não precisam ser copiados leva apenas alguns milissegundos e economiza um grande número de momentos WTF.
— Danack

0

Você pode executar buildpara um serviço específico executando docker-compose up --build <service name>onde o nome do serviço deve corresponder a como você o chamou no arquivo docker-compose.

Exemplo Vamos supor que seu arquivo docker-compose contenha muitos serviços (aplicativo .net - banco de dados - vamos criptografar ... etc) e você deseja atualizar apenas o aplicativo .net nomeado como applicationno arquivo docker-compose. Você pode então simplesmente executardocker-compose up --build application

Parâmetros extras Caso você deseje adicionar parâmetros extras ao seu comando, como -dpara execução em segundo plano, o parâmetro deve estar antes do nome do serviço: docker-compose up --build -d application

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.