Comment puis-je inspecter le système de fichiers d'une «construction de docker» qui a échoué?

272

J'essaie de construire une nouvelle image Docker pour notre processus de développement, en utilisant cpanmpour installer un tas de modules Perl comme image de base pour divers projets.

Lors du développement du Dockerfile, cpanmrenvoie un code d'échec car certains modules ne se sont pas installés correctement.

Je suis assez sûr que je dois aptinstaller d'autres choses.

Ma question est, où puis-je trouver le /.cpanm/workrépertoire cité dans la sortie, afin d'inspecter les journaux? Dans le cas général, comment puis-je inspecter le système de fichiers d'une docker buildcommande ayant échoué ?

Montage du matin Après avoir mordu la balle et exécuté un findje découvre

/var/lib/docker/aufs/diff/3afa404e[...]/.cpanm

Est-ce fiable, ou est-ce que je ferais mieux de construire un conteneur "nu" et d'exécuter des choses manuellement jusqu'à ce que j'aie tout ce dont j'ai besoin?

Altreus
la source
à propos de /var/lib/docker/aufs/diff/3afa404e[...]/.cpanmceux-ci sont des internes de Docker et je ne voudrais pas
jouer

Réponses:

357

Chaque fois que docker exécute avec succès une RUNcommande à partir d'un Dockerfile, une nouvelle couche dans le système de fichiers image est validée . Idéalement, vous pouvez utiliser ces identifiants de calques comme images pour démarrer un nouveau conteneur.

Prenez le Dockerfile suivant:

FROM busybox
RUN echo 'foo' > /tmp/foo.txt
RUN echo 'bar' >> /tmp/foo.txt

et le construire:

$ docker build -t so-2622957 .
Sending build context to Docker daemon 47.62 kB
Step 1/3 : FROM busybox
 ---> 00f017a8c2a6
Step 2/3 : RUN echo 'foo' > /tmp/foo.txt
 ---> Running in 4dbd01ebf27f
 ---> 044e1532c690
Removing intermediate container 4dbd01ebf27f
Step 3/3 : RUN echo 'bar' >> /tmp/foo.txt
 ---> Running in 74d81cb9d2b1
 ---> 5bd8172529c1
Removing intermediate container 74d81cb9d2b1
Successfully built 5bd8172529c1

Vous pouvez maintenant lancer un nouveau conteneur à partir 00f017a8c2a6, 044e1532c690et 5bd8172529c1:

$ docker run --rm 00f017a8c2a6 cat /tmp/foo.txt
cat: /tmp/foo.txt: No such file or directory

$ docker run --rm 044e1532c690 cat /tmp/foo.txt
foo

$ docker run --rm 5bd8172529c1 cat /tmp/foo.txt
foo
bar

bien sûr, vous voudrez peut-être démarrer un shell pour explorer le système de fichiers et essayer les commandes:

$ docker run --rm -it 044e1532c690 sh      
/ # ls -l /tmp
total 4
-rw-r--r--    1 root     root             4 Mar  9 19:09 foo.txt
/ # cat /tmp/foo.txt 
foo

Lorsque l'une des commandes Dockerfile échoue, ce que vous devez faire est de rechercher l' id de la couche précédente et d'exécuter un shell dans un conteneur créé à partir de cet id:

docker run --rm -it <id_last_working_layer> bash -il

Une fois dans le conteneur:

  • essayez la commande qui a échoué et reproduisez le problème
  • puis corrigez la commande et testez-la
  • enfin mettre à jour votre Dockerfile avec la commande fixed

Si vous avez vraiment besoin d'expérimenter le calque réel qui a échoué au lieu de travailler à partir du dernier calque de travail, voir la réponse de Drew .

Thomasleveil
la source
2
Oui. Il est inutile de conserver des conteneurs qui sont uniquement destinés à déboguer votre Dockerfile lorsque vous pouvez les recréer à volonté.
Thomasleveil
1
OK, cela a été en fait super utile, mais j'ai le problème où si une construction de conteneur échoue, je ne peux pas utiliser cette astuce avec le hachage du conteneur dans lequel il a dit qu'il fonctionnait. Aucune image n'est créée si le RUN échoue. Puis-je attacher au conteneur intermédiaire qui n'a jamais été nettoyé?
Altreus
6
lorsque l'une des commandes Dockerfile échoue, ce que vous devez faire est de rechercher l'id de la couche précédente et d'exécuter un conteneur avec un shell de cet id: docker run --rm -it <id_last_working_layer> bash -ilet une fois dans le conteneur, essayez la commande qui n'a pas pu reproduire le problème, puis corrigez la commande et testez-la, enfin mettez à jour votre Dockerfile avec la commande fixed.
Thomasleveil
2
En outre, vous pouvez docker diff <container>obtenir une liste complète des modifications spécifiques du système de fichiers effectuées sur cette couche particulière (fichiers ajoutés, supprimés ou modifiés sur l'ensemble du système de fichiers pour cette image).
L0j1k
14
Je pensais que cela ne fonctionnait pas parce que c'était dit Unable to find image 'd5219f1ffda9:latest' locally. Cependant, j'étais confus par les multiples types d'identifiants. Il s'avère que vous devez utiliser les identifiants qui se trouvent directement après les flèches, pas ceux qui disent "Running in ...".
rspeer
201

La première réponse fonctionne dans le cas où vous souhaitez examiner l'état immédiatement avant l'échec de la commande.

Cependant, la question demande comment examiner l'état du conteneur défaillant lui-même. Dans ma situation, la commande ayant échoué est une génération qui prend plusieurs heures, donc le rembobinage avant la commande qui a échoué et la relancer prend beaucoup de temps et n'est pas très utile.

La solution ici est de trouver le conteneur qui a échoué:

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                          PORTS               NAMES
6934ada98de6        42e0228751b3        "/bin/sh -c './utils/"   24 minutes ago      Exited (1) About a minute ago                       sleepy_bell

Validez-le sur une image:

$ docker commit 6934ada98de6
sha256:7015687976a478e0e94b60fa496d319cdf4ec847bcd612aecf869a72336e6b83

Et puis exécutez l'image [si nécessaire, exécutez bash]:

$ docker run -it 7015687976a4 [bash -il]

Vous regardez maintenant l'état de la génération au moment où elle a échoué, plutôt qu'au moment avant d'exécuter la commande à l'origine de l'échec.

A dessiné
la source
Par intérêt, pourquoi auriez-vous besoin de créer une nouvelle image à partir du conteneur? Pourquoi ne pas simplement démarrer le conteneur? Si une image créée à partir du conteneur défaillant est capable de s'exécuter, alors le conteneur arrêté / défaillant est sûrement également capable de s'exécuter? Ou est-ce que je manque quelque chose?
nmh
@nmh Parce qu'il vous permet de capturer et d'inspecter un conteneur en état d'échec sans avoir à réexécuter la commande ayant échoué. Parfois, l'exécution de la commande qui échoue prend quelques minutes ou plus, c'est donc un moyen pratique de marquer l'état ayant échoué. Par exemple, j'utilise actuellement cette approche pour inspecter les journaux d'une génération de bibliothèque C ++ qui a échoué et qui prend plusieurs minutes. Modifier - Je viens de remarquer que Drew a dit que dans [sa] situation, la commande échouée était une génération qui prenait plusieurs heures, donc rembobiner avant la commande échouée et l'exécuter à nouveau prend beaucoup de temps et n'est pas très utile.
Jaime Soto
@nmh Je pense que le problème avec le démarrage du conteneur défectueux est que la commande de démarrage du conteneur doit normalement être modifiée pour être utile. Si vous essayez de redémarrer le conteneur ayant échoué, il exécutera à nouveau la commande qui a échoué et vous serez de retour là où vous avez commencé. En créant une image, vous pouvez démarrer un conteneur avec une commande de démarrage différente.
Centimane
2
Cela ne fonctionne pas si vous utilisez DOCKER_BUILDKIT=1pour construire votreDockerfile
Clintm
Au point de @ nmh - vous n'avez pas besoin de valider l'image si vous êtes juste après la sortie de la construction. Vous pouvez utiliser docker container cp pour extraire les résultats du fichier à partir du conteneur de génération ayant échoué.
whoisthemachine
7

Docker met en cache l'intégralité de l'état du système de fichiers après chaque RUNligne réussie .

Sachant que:

  • pour examiner le dernier état avant votre RUNcommande en échec , commentez-le dans le Dockerfile (ainsi que toutes les RUNcommandes suivantes ), puis exécutez docker buildet docker runrecommencez.
  • pour examiner l'état après la RUNcommande défaillante , ajoutez-y simplement || truepour le forcer à réussir; puis procédez comme ci-dessus (laissez toutes les RUNcommandes suivantes commentées, exécutez docker buildet docker run)

Tada, pas besoin de jouer avec les internes de Docker ou les identifiants de couche, et en bonus Docker minimise automatiquement la quantité de travail à refaire.

DomQ
la source
1
C'est une réponse particulièrement utile lorsque vous utilisez DOCKER_BUILDKIT, car buildkit ne semble pas prendre en charge les mêmes solutions que celles répertoriées ci-dessus.
M. Anthony Aiello
3

Le débogage des échecs des étapes de génération est en effet très ennuyeux.

La meilleure solution que j'ai trouvée est de s'assurer que chaque étape qui fait un vrai travail réussit, et en ajoutant une vérification après celles qui échouent. De cette façon, vous obtenez une couche validée qui contient les sorties de l'étape ayant échoué que vous pouvez inspecter.

Un Dockerfile, avec un exemple après la # Run DB2 silent installerligne:

#
# DB2 10.5 Client Dockerfile (Part 1)
#
# Requires
#   - DB2 10.5 Client for 64bit Linux ibm_data_server_runtime_client_linuxx64_v10.5.tar.gz
#   - Response file for DB2 10.5 Client for 64bit Linux db2rtcl_nr.rsp 
#
#
# Using Ubuntu 14.04 base image as the starting point.
FROM ubuntu:14.04

MAINTAINER David Carew <[email protected]>

# DB2 prereqs (also installing sharutils package as we use the utility uuencode to generate password - all others are required for the DB2 Client) 
RUN dpkg --add-architecture i386 && apt-get update && apt-get install -y sharutils binutils libstdc++6:i386 libpam0g:i386 && ln -s /lib/i386-linux-gnu/libpam.so.0 /lib/libpam.so.0
RUN apt-get install -y libxml2


# Create user db2clnt
# Generate strong random password and allow sudo to root w/o password
#
RUN  \
   adduser --quiet --disabled-password -shell /bin/bash -home /home/db2clnt --gecos "DB2 Client" db2clnt && \
   echo db2clnt:`dd if=/dev/urandom bs=16 count=1 2>/dev/null | uuencode -| head -n 2 | grep -v begin | cut -b 2-10` | chgpasswd && \
   adduser db2clnt sudo && \
   echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers

# Install DB2
RUN mkdir /install
# Copy DB2 tarball - ADD command will expand it automatically
ADD v10.5fp9_linuxx64_rtcl.tar.gz /install/
# Copy response file
COPY  db2rtcl_nr.rsp /install/
# Run  DB2 silent installer
RUN mkdir /logs
RUN (/install/rtcl/db2setup -t /logs/trace -l /logs/log -u /install/db2rtcl_nr.rsp && touch /install/done) || /bin/true
RUN test -f /install/done || (echo ERROR-------; echo install failed, see files in container /logs directory of the last container layer; echo run docker run '<last image id>' /bin/cat /logs/trace; echo ----------)
RUN test -f /install/done

# Clean up unwanted files
RUN rm -fr /install/rtcl

# Login as db2clnt user
CMD su - db2clnt
mikaraento
la source
0

Ce que je ferais, c'est commenter le Dockerfile ci-dessous et y compris la ligne incriminée. Ensuite, vous pouvez exécuter le conteneur et exécuter les commandes du docker à la main, et consulter les journaux de la manière habituelle. Par exemple, si le Dockerfile est

RUN foo
RUN bar
RUN baz

et il se meurt au bar je ferais

RUN foo
# RUN bar
# RUN baz

ensuite

$ docker build -t foo .
$ docker run -it foo bash
container# bar
...grep logs...
seanmcl
la source
C'est ce que j'aurais fait aussi avant de trouver ce fil. Il existe de meilleures façons, qui ne nécessitent pas de réexécuter la génération.
Aaron McMillin
@Aaron. Merci de me rappeler cette réponse. Je ne l'ai pas regardé depuis longtemps. Pourriez-vous expliquer pourquoi la réponse acceptée est meilleure que celle-ci d'un point de vue pratique. Je comprends vraiment pourquoi la réponse de Drew est meilleure. Il semble que la réponse acceptée nécessite encore une nouvelle exécution.
seanmcl
En fait, j'ai voté pour la réponse de Drew et non pour l'acceptation. Ils fonctionnent tous les deux sans relancer la génération. Dans la réponse acceptée, vous pouvez sauter dans un shell juste avant l'échec de la commande (vous pouvez l'exécuter à nouveau pour voir l'erreur si c'est rapide). Ou avec la réponse de Drew, vous pouvez obtenir un shell après l'exécution de la commande échouée (dans son cas, la commande échouée était longue et laissait un état qui pouvait être inspecté).
Aaron McMillin