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.

Leão
fonte
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 :)

Leão
fonte
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

Yunus
fonte
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

Aamer
fonte