Reconstruire le conteneur Docker sur les modifications de fichier

86

Pour exécuter une application ASP.NET Core, j'ai généré un fichier docker qui construit l'application et copie le code source dans le conteneur, qui est récupéré par Git à l'aide de Jenkins. Donc, dans mon espace de travail, je fais ce qui suit dans le dockerfile:

WORKDIR /app
COPY src src

Alors que Jenkins met correctement à jour les fichiers sur mon hôte avec Git, Docker ne l'applique pas à mon image.

Mon script de base pour la construction:

#!/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

J'ai essayé différentes choses comme --rmet --no-cacheparamètre pour docker runet aussi arrêter / supprimer le conteneur avant la construction du nouveau. Je ne sais pas ce que je fais de mal ici. Il semble que docker met correctement à jour l'image, car l'appel de COPY src srcentraînerait un identifiant de couche et aucun appel de cache:

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

Quelle est la méthode recommandée pour mettre à jour un conteneur?

Mon scénario typique serait: l'application s'exécute sur le serveur dans un conteneur Docker. Désormais, certaines parties de l'application sont mises à jour, par exemple en modifiant un fichier. Maintenant, le conteneur doit exécuter la nouvelle version. Docker semble recommander de créer une nouvelle image au lieu de modifier un conteneur existant, donc je pense que la manière générale de reconstruire comme je le fais est correcte, mais certains détails de l'implémentation doivent être améliorés.

Lion
la source
Pouvez-vous énumérer les étapes exactes que vous avez suivies pour créer votre conteneur, y compris votre commande de construction et la sortie complète de chaque commande?
BMitch

Réponses:

136

Après quelques recherches et tests, j'ai constaté que j'avais des malentendus sur la durée de vie des conteneurs Docker. Le simple fait de redémarrer un conteneur ne permet pas à Docker d'utiliser une nouvelle image, lorsque l'image a été reconstruite entre-temps. Au lieu de cela, Docker récupère l'image uniquement avant de créer le conteneur. Ainsi, l'état après l'exécution d'un conteneur est persistant.

Pourquoi la suppression est nécessaire

Par conséquent, la reconstruction et le redémarrage ne suffisent pas. Je pensais que les conteneurs fonctionnaient comme un service: arrêter le service, faire vos modifications, le redémarrer et ils s'appliqueraient. C'était ma plus grosse erreur.

Les conteneurs étant permanents, vous devez d'abord les supprimer docker rm <ContainerName>. Une fois qu'un conteneur est supprimé, vous ne pouvez pas simplement le démarrer docker start. Cela doit être fait en utilisant docker run, qui utilise elle-même la dernière image pour créer une nouvelle instance de conteneur.

Les conteneurs doivent être aussi indépendants que possible

Avec cette connaissance, on comprend pourquoi le stockage de données dans des conteneurs est qualifié de mauvaise pratique et Docker recommande plutôt des volumes de données / le montage de répertoires hôtes : puisqu'un conteneur doit être détruit pour mettre à jour les applications, les données stockées à l'intérieur seraient également perdues. Cela entraîne un travail supplémentaire pour arrêter les services, sauvegarder les données, etc.

C'est donc une solution intelligente pour exclure complètement ces données du conteneur: nous n'avons pas à nous soucier de nos données, lorsqu'elles sont stockées en toute sécurité sur l'hôte et que le conteneur ne contient que l'application elle-même.

Pourquoi -rfne peut pas vraiment vous aider

La docker runcommande a un commutateur de nettoyage appelé -rf. Cela arrêtera le comportement de garder les conteneurs docker de manière permanente. En utilisant -rf, Docker détruira le conteneur après sa sortie. Mais ce commutateur a deux problèmes:

  1. Docker supprime également les volumes sans nom associé au conteneur, ce qui peut tuer vos données
  2. En utilisant cette option, il n'est pas possible d'exécuter des conteneurs en arrière-plan à l'aide du -dcommutateur

Bien que le -rfcommutateur soit une bonne option pour économiser du travail pendant le développement pour des tests rapides, il est moins adapté en production. Surtout à cause de l'option manquante pour exécuter un conteneur en arrière-plan, ce qui serait principalement nécessaire.

Comment retirer un conteneur

Nous pouvons contourner ces limitations en supprimant simplement le conteneur:

docker rm --force <ContainerName>

Le commutateur --force(ou -f) qui utilise SIGKILL sur des conteneurs en cours d'exécution. Au lieu de cela, vous pouvez également arrêter le conteneur avant:

docker stop <ContainerName>
docker rm <ContainerName>

Les deux sont égaux. docker stoputilise également SIGTERM . Mais l'utilisation de --forceswitch raccourcira votre script, en particulier lors de l'utilisation de serveurs CI: docker stopgénère une erreur si le conteneur n'est pas en cours d'exécution. Cela amènerait Jenkins et de nombreux autres serveurs CI à considérer à tort la construction comme un échec. Pour résoudre ce problème, vous devez d'abord vérifier si le conteneur fonctionne comme je l'ai fait dans la question (voir containerRunningvariable).

Script complet pour la reconstruction d'un conteneur Docker

En fonction de ces nouvelles connaissances, j'ai corrigé mon script de la manière suivante:

#!/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

Cela fonctionne parfaitement :)

Lion
la source
4
«J'ai découvert que j'avais des malentendus sur la durée de vie des conteneurs Docker», vous avez sorti les mots de ma bouche. Merci pour cette explication détaillée. Je recommanderais ceci aux débutants des dockers. Cela clarifie la différence entre VM et conteneur.
Vince Banzon
2
Après votre explication, ce que j'ai fait est de prendre note de ce que j'ai fait à mon image existante. Afin de conserver les modifications, j'ai créé un nouveau Dockerfile pour créer une nouvelle image qui inclut déjà les modifications que je souhaite ajouter. De cette façon, la nouvelle image créée est (quelque peu) mise à jour.
Vince Banzon
L' --force-recreateoption de composition de docker est-elle similaire à ce que vous décrivez ici? Et si oui, cela ne vaudrait pas la peine d'utiliser cette solution à la place (désolé si cette question est idiote mais je suis un docker noob ^^)
cglacet
2
@cglacet Oui, c'est similaire, pas directement comparable. Mais docker-composec'est plus intelligent que les dockercommandes simples . Je travaille régulièrement avec docker-composeet la détection de changement fonctionne bien, donc je l'utilise --force-recreatetrès rarement. C'est docker-compose up --buildimportant lorsque vous créez une image personnalisée ( builddirective dans le fichier de composition) au lieu d'utiliser une image provenant par exemple du hub Docker.
Lion
33

Chaque fois que des modifications sont apportées à dockerfile ou à composer ou aux exigences, réexécutez-le en utilisant docker-compose up --build. Pour que les images soient reconstruites et rafraîchies

yunus
la source
1
Ayant un conteneur de docker MySQL comme un service, la base de données serait-elle vide après cela si l'on utilisait un volume pour /opt/mysql/data:/var/lib/mysql?
Martin Thoma le
Pour moi, il ne semble pas y avoir d'inconvénient à toujours utiliser --builddans des environnements de développement locaux. La vitesse à laquelle docker recopie les fichiers qu'il pourrait supposer ne pas avoir besoin d'être copiés ne prend que quelques millisecondes et enregistre un grand nombre de moments WTF.
Danack le
0

Vous pouvez exécuter buildpour un service spécifique en exécutant docker-compose up --build <service name>où le nom du service doit correspondre à la façon dont vous l'avez appelé dans votre fichier docker-compose.

Exemple Supposons que votre fichier docker-compose contient de nombreux services (application .net - base de données - chiffrons ... etc) et que vous souhaitez mettre à jour uniquement l'application .net nommée comme applicationdans le fichier docker-compose. Vous pouvez alors simplement exécuterdocker-compose up --build application

Paramètres supplémentaires Si vous souhaitez ajouter des paramètres supplémentaires à votre commande, par exemple -dpour une exécution en arrière-plan, le paramètre doit être avant le nom du service: docker-compose up --build -d application

Aamer
la source