¿Cómo puedo inspeccionar el sistema de archivos de una 'construcción de docker' fallida?

272

Estoy tratando de construir una nueva imagen de Docker para nuestro proceso de desarrollo, utilizando cpanmpara instalar un montón de módulos Perl como imagen base para varios proyectos.

Mientras desarrolla el Dockerfile, cpanmdevuelve un código de falla porque algunos de los módulos no se instalaron limpiamente.

Estoy bastante seguro de que necesito aptinstalar algunas cosas más.

Mi pregunta es, ¿dónde puedo encontrar el /.cpanm/workdirectorio citado en la salida para inspeccionar los registros? En el caso general, ¿cómo puedo inspeccionar el sistema de archivos de un docker buildcomando fallido ?

Editar mañana Después de morder la bala y ejecutar un finddescubrí

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

¿Es esto confiable, o es mejor construir un contenedor "desnudo" y ejecutar cosas manualmente hasta que tenga todo lo que necesito?

Altreus
fuente
sobre /var/lib/docker/aufs/diff/3afa404e[...]/.cpanmesos son
aspectos

Respuestas:

356

Cada vez que Docker ejecuta con éxito un RUNcomando desde un Dockerfile, se confirma una nueva capa en el sistema de archivos de imagen . Convenientemente, puede usar esos identificadores de capas como imágenes para iniciar un nuevo contenedor.

Tome el siguiente Dockerfile:

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

y construirlo:

$ 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

Ahora puede comenzar un nuevo contenedor de 00f017a8c2a6, 044e1532c690y 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

por supuesto, es posible que desee iniciar un shell para explorar el sistema de archivos y probar comandos:

$ 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

Cuando falla uno de los comandos de Dockerfile, lo que debe hacer es buscar la identificación de la capa anterior y ejecutar un shell en un contenedor creado a partir de esa identificación:

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

Una vez en el contenedor:

  • pruebe el comando que falló y reproduzca el problema
  • luego arregla el comando y pruébalo
  • finalmente actualice su Dockerfile con el comando fijo

Si realmente necesita experimentar en la capa real que falló en lugar de trabajar desde la última capa de trabajo, vea la respuesta de Drew .

Thomasleveil
fuente
2
si lo hace No tiene sentido mantener contenedores que solo están destinados a depurar su Dockerfile cuando puede recrearlos a voluntad.
Thomasleveil
1
OK, esto realmente ha sido muy útil, pero tengo el problema de que si falla la construcción de un contenedor, no puedo usar este truco con el hash del contenedor en el que dijo que estaba trabajando. No se crea ninguna imagen si falla el RUN. ¿Puedo conectarlo al contenedor intermedio que nunca se limpió?
Altreus
66
cuando falla uno de los comandos de Dockerfile, lo que debe hacer es buscar la identificación de la capa anterior y ejecutar un contenedor con un shell de esa identificación: docker run --rm -it <id_last_working_layer> bash -ily una vez en el contenedor, pruebe el comando que no pudo reproducir el problema, luego arregle el comando y pruébelo, finalmente actualice su Dockerfile con el comando fijo.
Thomasleveil
2
Además, puede docker diff <container>obtener una lista completa de los cambios específicos del sistema de archivos realizados en esa capa en particular (archivos agregados, eliminados o cambiados en todo el sistema de archivos para esa imagen).
L0j1k
14
Pensé que esto no estaba funcionando porque decía Unable to find image 'd5219f1ffda9:latest' locally. Sin embargo, estaba confundido por los múltiples tipos de ID. Resulta que tienes que usar las ID que están directamente después de las flechas, no las que dicen "Ejecutando en ...".
rspeer
201

La respuesta principal funciona en el caso de que desee examinar el estado inmediatamente anterior al comando fallido.

Sin embargo, la pregunta pregunta cómo examinar el estado del contenedor fallido. En mi situación, el comando fallido es una compilación que lleva varias horas, por lo que rebobinar antes del comando fallido y ejecutarlo nuevamente lleva mucho tiempo y no es muy útil.

La solución aquí es encontrar el contenedor que falló:

$ 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

Compromételo con una imagen:

$ docker commit 6934ada98de6
sha256:7015687976a478e0e94b60fa496d319cdf4ec847bcd612aecf869a72336e6b83

Y luego ejecute la imagen [si es necesario, ejecutando bash]:

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

Ahora realmente está mirando el estado de la compilación en el momento en que falló, en lugar de en el momento antes de ejecutar el comando que causó la falla.

Dibujó
fuente
Por interés, ¿por qué necesitaría crear una nueva imagen del contenedor? ¿Por qué no simplemente iniciar el contenedor? Si una imagen creada a partir del contenedor fallido puede ejecutarse, entonces ¿seguramente el contenedor detenido / fallido también puede ejecutarse? ¿O me estoy perdiendo algo?
nmh
@nmh Porque le permite capturar e inspeccionar un contenedor en estado fallido sin tener que ejecutar nuevamente el comando que falla. A veces, el comando fallido tarda minutos o más en ejecutarse, por lo que esta es una manera conveniente de etiquetar el estado fallido. Por ejemplo, actualmente estoy usando este enfoque para inspeccionar los registros de una compilación de biblioteca C ++ fallida que lleva varios minutos. Editar: acabo de notar que Drew dijo que en [su] situación, el comando fallido es una compilación que lleva varias horas, por lo que rebobinar antes del comando fallido y ejecutarlo nuevamente lleva mucho tiempo y no es muy útil.
Jaime Soto
@nmh Creo que el problema al intentar iniciar el contenedor fallido es que el comando de inicio del contenedor normalmente necesita ser cambiado para ser útil. Si intentara iniciar el contenedor fallido nuevamente, ejecutaría el comando que falló nuevamente y volvería a donde comenzó. Al crear una imagen, puede iniciar un contenedor con un comando de inicio diferente.
Centimane
2
Esto no funciona si está utilizando DOCKER_BUILDKIT=1para construir suDockerfile
Clintm
Para el punto de @ nmh: no necesita confirmar la imagen si solo está después de la salida de compilación. Puede usar docker container cp para extraer los resultados del archivo del contenedor de compilación fallido.
whoisthemachine
7

Docker almacena en caché todo el estado del sistema de archivos después de cada RUNlínea exitosa .

Sabiendo que:

  • para examinar el último estado antes de su RUNcomando fallido , coméntelo en el Dockerfile (así como en cualquiera y todos los RUNcomandos posteriores ), luego ejecútelo una docker buildy docker runotra vez.
  • para examinar el estado después del RUNcomando fallido , simplemente agréguelo || truepara forzarlo a tener éxito; luego proceda como anteriormente (mantenga RUNcomentados todos y cada uno de los comandos posteriores , ejecute docker buildy docker run)

Tada, no es necesario meterse con elementos internos de Docker o ID de capa, y como beneficio adicional, Docker minimiza automáticamente la cantidad de trabajo que se debe volver a hacer.

DomQ
fuente
1
Esta es una respuesta especialmente útil cuando se usa DOCKER_BUILDKIT, ya que buildkit no parece admitir las mismas soluciones que las enumeradas anteriormente.
M. Anthony Aiello
3

La depuración de fallas de paso de compilación es realmente muy molesta.

La mejor solución que he encontrado es asegurarme de que cada paso que hace un trabajo real tenga éxito y agregar un cheque después de aquellos que fallan. De esa forma, obtiene una capa comprometida que contiene los resultados del paso fallido que puede inspeccionar.

Un Dockerfile, con un ejemplo después de la # Run DB2 silent installerlínea:

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

Lo que haría es comentar el Dockerfile a continuación e incluir la línea ofensiva. Luego puede ejecutar el contenedor y ejecutar los comandos de la ventana acoplable a mano, y mirar los registros de la manera habitual. Por ejemplo, si el Dockerfile es

RUN foo
RUN bar
RUN baz

y se está muriendo en el bar que haría

RUN foo
# RUN bar
# RUN baz

Luego

$ docker build -t foo .
$ docker run -it foo bash
container# bar
...grep logs...
seanmcl
fuente
Eso es lo que habría hecho también antes de encontrar este hilo. Sin embargo, hay mejores formas que no requieren volver a ejecutar la compilación.
Aaron McMillin
@Aaron. Gracias por recordarme esta respuesta. No lo he mirado en mucho tiempo. ¿Podría explicar por qué la respuesta aceptada es mejor que esta desde un punto de vista práctico? Definitivamente entiendo por qué la respuesta de Drew es mejor. Parece que la respuesta aceptada aún requiere volver a ejecutarse.
seanmcl
De hecho, voté por la respuesta de Drew y no por la aceptada. Ambos trabajan sin volver a ejecutar la compilación. En la respuesta aceptada, puede saltar a un shell justo antes del comando fallido (puede ejecutarlo nuevamente para ver el error si es rápido). O con la respuesta de Drew, puede obtener un shell después de que se haya ejecutado el comando fallido (en su caso, el comando fallido se ejecutó durante mucho tiempo y dejó un estado que podría ser inspeccionado).
Aaron McMillin