Wie kann ich das Dateisystem eines fehlgeschlagenen Docker-Builds überprüfen?

272

Ich versuche, ein neues Docker-Image für unseren Entwicklungsprozess zu erstellen, indem ich cpanmeine Reihe von Perl-Modulen als Basis-Image für verschiedene Projekte installiere.

Gibt während der Entwicklung der Docker-Datei cpanmeinen Fehlercode zurück, da einige der Module nicht sauber installiert wurden.

Ich bin mir ziemlich sicher, dass ich noch apteinige Dinge installieren muss.

Meine Frage ist, wo finde ich das /.cpanm/workin der Ausgabe angegebene Verzeichnis, um die Protokolle zu überprüfen? Wie kann ich im allgemeinen Fall das Dateisystem eines fehlgeschlagenen docker buildBefehls überprüfen ?

Morgen bearbeiten Nachdem findich die Kugel gebissen und einen ausgeführt hatte, entdeckte ich

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

Ist das zuverlässig oder ist es besser, einen "nackten" Container zu bauen und Dinge manuell auszuführen, bis ich alle Dinge habe, die ich brauche?

Altreus
quelle
darüber /var/lib/docker/aufs/diff/3afa404e[...]/.cpanmsind Interna von Docker und ich würde mich nicht mit ihnen
anlegen

Antworten:

355

Jedes RUNMal, wenn Docker einen Befehl aus einer Docker-Datei erfolgreich ausführt , wird eine neue Ebene im Image-Dateisystem festgeschrieben. Praktischerweise können Sie diese Ebenen-IDs als Bilder verwenden, um einen neuen Container zu starten.

Nehmen Sie die folgende Docker-Datei:

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

und baue es:

$ 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

Sie können nun einen neuen Container aus starten 00f017a8c2a6, 044e1532c690und 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

Natürlich möchten Sie vielleicht eine Shell starten, um das Dateisystem zu erkunden und Befehle auszuprobieren:

$ 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

Wenn einer der Dockerfile-Befehle fehlschlägt, müssen Sie nach der ID der vorhergehenden Ebene suchen und eine Shell in einem Container ausführen, der aus dieser ID erstellt wurde:

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

Einmal im Container:

  • Versuchen Sie den fehlgeschlagenen Befehl und reproduzieren Sie das Problem
  • Korrigieren Sie dann den Befehl und testen Sie ihn
  • Aktualisieren Sie schließlich Ihre Docker-Datei mit dem festen Befehl

Wenn Sie wirklich in der tatsächlichen Ebene experimentieren müssen, die fehlgeschlagen ist, anstatt in der letzten Arbeitsebene zu arbeiten, lesen Sie Drews Antwort .

Thomasleveil
quelle
2
ja tut es. Es macht keinen Sinn, Container zu behalten, die nur dazu gedacht sind, Ihre Docker-Datei zu debuggen, wenn Sie sie nach Belieben neu erstellen können.
Thomasleveil
1
OK, das war wirklich sehr nützlich, aber ich habe das Problem, dass ich bei einem fehlgeschlagenen Container-Build diesen Trick nicht mit dem Hash des Containers verwenden kann, in dem er funktioniert hat. Wenn der RUN fehlschlägt, wird kein Image erstellt. Kann ich an dem Zwischenbehälter befestigen, der nie gereinigt wurde?
Altreus
6
Wenn einer der Dockerfile-Befehle fehlschlägt, müssen Sie nach der ID der vorhergehenden Ebene suchen und einen Container mit einer Shell dieser ID ausführen. docker run --rm -it <id_last_working_layer> bash -ilVersuchen Sie dann im Container den Befehl, der das Problem nicht reproduzieren konnte Korrigieren Sie den Befehl und testen Sie ihn. Aktualisieren Sie schließlich Ihre Docker-Datei mit dem festen Befehl.
Thomasleveil
2
Außerdem können Sie docker diff <container>eine gründliche Liste der spezifischen Dateisystemänderungen erhalten, die auf dieser bestimmten Ebene vorgenommen wurden (Dateien, die für dieses Image im gesamten Dateisystem hinzugefügt, gelöscht oder geändert wurden).
L0j1k
14
Ich dachte, das würde nicht funktionieren, weil es hieß Unable to find image 'd5219f1ffda9:latest' locally. Ich war jedoch durch die verschiedenen Arten von IDs verwirrt. Es stellt sich heraus, dass Sie die IDs verwenden müssen, die direkt hinter den Pfeilen stehen, nicht die, die "Running in ..." sagen.
rspeer
200

Die oberste Antwort funktioniert in dem Fall, dass Sie den Status unmittelbar vor dem fehlgeschlagenen Befehl überprüfen möchten.

In der Frage wird jedoch gefragt, wie der Status des ausgefallenen Containers selbst untersucht werden soll. In meiner Situation ist der fehlgeschlagene Befehl ein Build, der mehrere Stunden dauert. Das Zurückspulen vor dem fehlgeschlagenen Befehl und das erneute Ausführen dauert daher lange und ist nicht sehr hilfreich.

Die Lösung besteht darin, den fehlgeschlagenen Container zu finden:

$ 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

Übertragen Sie es auf ein Bild:

$ docker commit 6934ada98de6
sha256:7015687976a478e0e94b60fa496d319cdf4ec847bcd612aecf869a72336e6b83

Führen Sie dann das Image aus [falls erforderlich, führen Sie bash aus]:

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

Jetzt sehen Sie sich tatsächlich den Status des Builds zum Zeitpunkt des Fehlschlags an, anstatt zu dem Zeitpunkt, bevor der Befehl ausgeführt wurde, der den Fehler verursacht hat.

Drew
quelle
Warum sollten Sie aus Interesse ein neues Bild aus dem Container erstellen müssen? Warum nicht einfach den Container starten? Wenn ein aus dem ausgefallenen Container erstelltes Image ausgeführt werden kann, kann der angehaltene / ausgefallene Container sicherlich auch ausgeführt werden? Oder fehlt mir etwas?
nmh
@nmh Weil Sie damit einen Container im fehlgeschlagenen Zustand erfassen und untersuchen können, ohne den fehlgeschlagenen Befehl erneut ausführen zu müssen. Manchmal dauert die Ausführung des fehlgeschlagenen Befehls Minuten oder länger, sodass dies eine bequeme Möglichkeit ist, den fehlgeschlagenen Status zu kennzeichnen. Zum Beispiel verwende ich derzeit diesen Ansatz, um die Protokolle eines fehlgeschlagenen C ++ - Bibliotheksaufbaus zu überprüfen, der mehrere Minuten dauert. Bearbeiten - Ich habe gerade bemerkt, dass Drew gesagt hat, dass der fehlgeschlagene Befehl in [seiner] Situation ein Build ist, der mehrere Stunden dauert. Das Zurückspulen vor dem fehlgeschlagenen Befehl und das erneute Ausführen dauert daher lange und ist nicht sehr hilfreich.
Jaime Soto
@nmh Ich denke, das Problem beim Versuch, den ausgefallenen Container zu starten, ist, dass der Startbefehl des Containers normalerweise geändert werden muss, um nützlich zu sein. Wenn Sie versuchen, den fehlgeschlagenen Container erneut zu starten, wird der Befehl ausgeführt, der erneut fehlgeschlagen ist, und Sie befinden sich wieder dort, wo Sie begonnen haben. Durch Erstellen eines Bildes können Sie einen Container mit einem anderen Startbefehl starten.
Centimane
2
Dies funktioniert nicht, wenn Sie verwenden DOCKER_BUILDKIT=1, um IhreDockerfile
Clintm
Auf den Punkt von @ nmh - Sie müssen das Image nicht festschreiben, wenn Sie sich direkt nach der Build-Ausgabe befinden. Sie können Docker Container CP verwenden , um die Dateiergebnisse aus dem fehlgeschlagenen Build-Container zu extrahieren.
whoisthemachine
7

Docker speichert den gesamten Dateisystemstatus nach jeder erfolgreichen RUNZeile zwischen.

Wohl wissend, dass:

  • RUNUm den neuesten Status vor Ihrem fehlgeschlagenen Befehl zu überprüfen , kommentieren Sie ihn in der Docker-Datei (sowie in allen nachfolgenden RUNBefehlen) aus docker buildund führen Sie ihn docker runerneut aus.
  • Um den Status nach dem fehlgeschlagenen RUNBefehl zu überprüfen , fügen || trueSie ihn einfach hinzu , um den Erfolg zu erzwingen. Fahren Sie dann wie oben beschrieben fort (lassen Sie alle nachfolgenden RUNBefehle auskommentieren, ausführen docker buildund docker run).

Tada, Sie müssen sich nicht mit Docker-Interna oder Layer-IDs herumschlagen, und als Bonus minimiert Docker automatisch den Arbeitsaufwand, der erneut ausgeführt werden muss.

DomQ
quelle
1
Dies ist eine besonders hilfreiche Antwort bei Verwendung von DOCKER_BUILDKIT, da das Buildkit anscheinend nicht dieselben Lösungen wie die oben aufgeführten unterstützt.
M. Anthony Aiello
3

Das Debuggen von Build-Step-Fehlern ist in der Tat sehr ärgerlich.

Die beste Lösung, die ich gefunden habe, besteht darin, sicherzustellen, dass jeder Schritt, der echte Arbeit leistet, erfolgreich ist, und nach denjenigen, die fehlschlagen, eine Prüfung hinzuzufügen. Auf diese Weise erhalten Sie eine festgeschriebene Ebene, die die Ausgaben des fehlgeschlagenen Schritts enthält, die Sie überprüfen können.

Eine Docker-Datei mit einem Beispiel nach der # Run DB2 silent installerZeile:

#
# 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
quelle
0

Was ich tun würde, ist das Dockerfile unten und einschließlich der betreffenden Zeile zu kommentieren. Anschließend können Sie den Container ausführen und die Docker-Befehle von Hand ausführen und die Protokolle auf die übliche Weise anzeigen. ZB wenn das Dockerfile ist

RUN foo
RUN bar
RUN baz

und es stirbt an der Bar, die ich tun würde

RUN foo
# RUN bar
# RUN baz

Dann

$ docker build -t foo .
$ docker run -it foo bash
container# bar
...grep logs...
seanmcl
quelle
Das hätte ich auch getan, bevor ich diesen Thread gefunden hätte. Es gibt jedoch bessere Möglichkeiten, bei denen der Build nicht erneut ausgeführt werden muss.
Aaron McMillin
@ Aaron. Vielen Dank, dass Sie mich an diese Antwort erinnert haben. Ich habe es lange nicht mehr angeschaut. Könnten Sie bitte erklären, warum die akzeptierte Antwort aus praktischer Sicht besser ist als diese. Ich verstehe definitiv, warum Drews Antwort besser ist. Es scheint, dass die akzeptierte Antwort immer noch neu ausgeführt werden muss.
Seanmcl
Ich habe tatsächlich für Drews Antwort gestimmt und nicht für die akzeptierte. Beide arbeiten, ohne den Build erneut auszuführen. In der akzeptierten Antwort können Sie kurz vor dem fehlgeschlagenen Befehl in eine Shell springen (Sie können sie erneut ausführen, um den Fehler zu sehen, wenn er schnell ist). Oder mit Drews Antwort können Sie eine Shell erhalten, nachdem der fehlgeschlagene Befehl ausgeführt wurde (in seinem Fall war der fehlgeschlagene Befehl lange ausgeführt und der zurückgelassene Zustand konnte überprüft werden).
Aaron McMillin