Come posso ispezionare il file system di una `docker build` fallita?


272

Sto cercando di creare una nuova immagine Docker per il nostro processo di sviluppo, usando cpanml'installazione di un gruppo di moduli Perl come immagine di base per vari progetti.

Durante lo sviluppo del Dockerfile, cpanmrestituisce un codice di errore poiché alcuni dei moduli non sono stati installati correttamente.

Sono abbastanza sicuro di dover aptinstallare altre cose.

La mia domanda è: dove posso trovare la /.cpanm/workdirectory citata nell'output, al fine di ispezionare i log? Nel caso generale, come posso controllare il file system di un docker buildcomando non riuscito ?

Modifica del mattino Dopo aver morso il proiettile ed eseguito un findho scoperto

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

È affidabile o sto meglio costruendo un contenitore "nudo" e facendo funzionare le cose manualmente finché non avrò tutto ciò di cui ho bisogno?


di /var/lib/docker/aufs/diff/3afa404e[...]/.cpanmquelli sono interni di Docker e non mi farei un casino
Thomasleveil il

Risposte:


356

Ogni volta che la finestra mobile esegue correttamente un RUNcomando da un file Docker, viene eseguito il commit di un nuovo livello nel file system immagine . Convenientemente puoi usare questi ID di layer come immagini per avviare un nuovo contenitore.

Prendi il seguente Dockerfile:

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

e costruiscilo:

$ 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

È ora possibile avviare un nuovo contenitore da 00f017a8c2a6, 044e1532c690e 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

ovviamente potresti voler avviare una shell per esplorare il filesystem e provare i comandi:

$ 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

Quando uno dei comandi Dockerfile fallisce, quello che devi fare è cercare l' id del livello precedente ed eseguire una shell in un contenitore creato da quell'id:

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

Una volta nel contenitore:

  • provare il comando non riuscito e riprodurre il problema
  • quindi correggere il comando e testarlo
  • infine aggiorna il tuo Dockerfile con il comando fisso

Se hai davvero bisogno di sperimentare il livello effettivo che ha fallito invece di lavorare dall'ultimo livello di lavoro, vedi la risposta di Drew .


2
si lo fa. Non ha senso conservare contenitori che sono pensati per eseguire il debug del file Docker quando è possibile ricrearli a piacimento.
Thomasleveil,

1
OK, questo è stato davvero molto utile, ma ho il problema che se la compilazione di un contenitore fallisce, non posso usare questo trucco con l'hash del contenitore in cui ha detto che stava funzionando. Nessuna immagine viene creata se il RUN fallisce. Posso attaccare al contenitore intermedio che non è mai stato ripulito?
Altreus

6
quando uno dei comandi Dockerfile fallisce, quello che devi fare è cercare l'id del livello precedente ed eseguire un contenitore con una shell di quell'id: docker run --rm -it <id_last_working_layer> bash -ile una volta nel contenitore provare il comando che non è riuscito a riprodurre il problema, quindi correggi il comando e testalo, infine aggiorna il tuo Dockerfile con il comando fisso.
Thomasleveil,

2
Inoltre, è possibile docker diff <container>ottenere un elenco completo delle modifiche specifiche del file system apportate su quel particolare livello (file aggiunti, eliminati o modificati nell'intero file system per quell'immagine).
L0j1k,

14
Pensavo che non funzionasse perché diceva Unable to find image 'd5219f1ffda9:latest' locally. Tuttavia, ero confuso dai molteplici tipi di ID. Si scopre che devi usare gli ID che sono subito dopo le frecce, non quelli che dicono "In esecuzione in ...".
rspeer,

201

La risposta migliore funziona nel caso in cui si desideri esaminare lo stato immediatamente prima del comando non riuscito.

Tuttavia, la domanda chiede come esaminare lo stato del contenitore stesso non riuscito. Nella mia situazione, il comando non riuscito è una build che richiede diverse ore, quindi il riavvolgimento prima del comando non riuscito e l'esecuzione successiva richiede molto tempo e non è molto utile.

La soluzione qui è trovare il contenitore che non è riuscito:

$ 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

Commettilo a un'immagine:

$ docker commit 6934ada98de6
sha256:7015687976a478e0e94b60fa496d319cdf4ec847bcd612aecf869a72336e6b83

E quindi esegui l'immagine [se necessario, eseguendo bash]:

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

Ora stai effettivamente osservando lo stato della build nel momento in cui ha fallito, anziché nel momento prima di eseguire il comando che ha causato l'errore.


Per interesse, perché dovresti creare una nuova immagine dal contenitore? Perché non avviare semplicemente il contenitore? Se è possibile eseguire un'immagine creata dal contenitore non riuscito, è sicuramente in grado di eseguire anche il contenitore arrestato / non riuscito? Oppure mi sfugge qualcosa?
nmh

@nmh Perché consente di acquisire e ispezionare un contenitore nello stato non riuscito senza dover eseguire nuovamente il comando non riuscito. A volte l'esecuzione del comando non riuscito richiede minuti o più, quindi questo è un modo conveniente per contrassegnare lo stato non riuscito. Ad esempio, sto attualmente usando questo approccio per ispezionare i log di una build di librerie C ++ non riuscita che richiede diversi minuti. Modifica - Ho appena notato che Drew ha detto che nella [sua] situazione, il comando fallito è una build che richiede diverse ore, quindi riavvolgere prima del comando fallito ed eseguirlo di nuovo richiede molto tempo e non è molto utile.
Jaime Soto,

@nmh Penso che il problema con il tentativo di avviare il contenitore non riuscito sia che il comando di avvio del contenitore deve normalmente essere modificato per essere utile. Se si tenta di riavviare il contenitore non riuscito, verrà eseguito nuovamente il comando che non è riuscito e si tornerà al punto di partenza. Creando un'immagine è possibile avviare un contenitore con un comando di avvio diverso.
Centimane,

2
Questo non funziona se stai usando DOCKER_BUILDKIT=1per costruire il tuoDockerfile
Clintm

Al punto @ nmh: non è necessario eseguire il commit dell'immagine se si è appena dopo l'output di generazione. È possibile utilizzare la finestra mobile contenitore cp per estrarre i risultati del file dal contenitore build non riuscito.
whoisthemachine,

7

Docker memorizza nella cache l'intero stato del filesystem dopo ogni RUNriga corretta .

Sapendo ciò:

  • per esaminare l'ultimo stato prima del tuo RUNcomando non riuscito , commentalo nel Dockerfile (così come tutti i RUNcomandi successivi ), quindi esegui docker builde di docker runnuovo.
  • per esaminare lo stato dopo il RUNcomando non riuscito , è sufficiente aggiungerlo || trueper forzarlo ad avere successo; quindi procedere come sopra (mantenere RUNcommentati tutti i comandi successivi , eseguirli docker builde docker run)

Tada, non c'è bisogno di scherzare con gli interni Docker o gli ID layer e come bonus Docker minimizza automaticamente la quantità di lavoro che deve essere rifatto.


1
Questa è una risposta particolarmente utile quando si utilizza DOCKER_BUILDKIT, poiché buildkit non sembra supportare le stesse soluzioni di quelle sopra elencate.
M. Anthony Aiello,

3

Il debugging degli errori di step di build è davvero molto fastidioso.

La migliore soluzione che ho trovato è quella di assicurarsi che ogni passaggio che fa il vero lavoro abbia successo, e l'aggiunta di un controllo dopo quelli che falliscono. In questo modo si ottiene un livello di commit che contiene gli output del passaggio non riuscito che è possibile controllare.

Un file Docker, con un esempio dopo la # Run DB2 silent installerriga:

#
# 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 <carew@us.ibm.com>

# 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

0

Quello che vorrei fare è commentare il Dockerfile qui sotto e includere la linea offensiva. Quindi è possibile eseguire il contenitore ed eseguire manualmente i comandi della finestra mobile e guardare i registri nel solito modo. Ad esempio, se il file Docker è

RUN foo
RUN bar
RUN baz

ed è morire al bar che vorrei fare

RUN foo
# RUN bar
# RUN baz

Poi

$ docker build -t foo .
$ docker run -it foo bash
container# bar
...grep logs...

È quello che avrei fatto anch'io prima di trovare questa discussione. Esistono modi migliori, tuttavia, che non richiedono la riesecuzione della build.
Aaron McMillin,

@Aaron. Grazie per avermi ricordato questa risposta. Non lo guardo da molto tempo. Potresti spiegare perché la risposta accettata è migliore di questa dal punto di vista pratico. Sicuramente capisco perché la risposta di Drew sia migliore. Sembra che la risposta accettata richieda ancora una nuova esecuzione.
seanmcl,

In realtà ho votato per la risposta di Drew e non per quella accettata. Entrambi funzionano senza rieseguire la build. Nella risposta accettata puoi saltare in una shell appena prima del comando fallito (potresti eseguirlo di nuovo per vedere l'errore se è veloce). O con la risposta di Drew puoi ottenere una shell dopo che il comando fallito è stato eseguito (nel suo caso il comando fallito era in esecuzione a lungo e ha lasciato lo stato dietro che poteva essere ispezionato).
Aaron McMillin,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.