Como inspecionar o sistema de arquivos de uma falha na `docker build`?

272

Estou tentando criar uma nova imagem do Docker para o nosso processo de desenvolvimento, usando cpanmpara instalar um monte de módulos Perl como uma imagem base para vários projetos.

Ao desenvolver o Dockerfile, cpanmretorna um código de falha porque alguns dos módulos não foram instalados corretamente.

Tenho certeza de que preciso aptinstalar mais algumas coisas.

Minha pergunta é: onde posso encontrar o /.cpanm/workdiretório citado na saída para inspecionar os logs? No caso geral, como posso inspecionar o sistema de arquivos de um docker buildcomando com falha ?

Manhã editar Depois de morder a bala e executar um findeu descobri

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

Isso é confiável ou é melhor criar um contêiner "vazio" e executar as coisas manualmente até ter todas as coisas de que preciso?

Altreus
fonte
sobre /var/lib/docker/aufs/diff/3afa404e[...]/.cpanmaqueles que são internos do Docker e eu não iria mexer com eles
Thomasleveil

Respostas:

355

Sempre que o docker executa com êxito um RUNcomando de um Dockerfile, uma nova camada no sistema de arquivos de imagem é confirmada. Convenientemente, você pode usar esses IDs de camadas como imagens para iniciar um novo contêiner.

Tome o seguinte Dockerfile:

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

e construa-o:

$ 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

Agora você pode começar um novo recipiente de 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

é claro que você pode querer iniciar um shell para explorar o sistema de arquivos e experimentar os 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

Quando um dos comandos do Dockerfile falha, o que você precisa fazer é procurar o ID da camada anterior e executar um shell em um contêiner criado a partir desse ID:

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

Uma vez no recipiente:

  • tente o comando que falhou e reproduza o problema
  • corrija o comando e teste-o
  • finalmente atualize seu Dockerfile com o comando fixo

Se você realmente precisa experimentar a camada real que falhou em vez de trabalhar a partir da última camada de trabalho, consulte a resposta de Drew .

Thomasleveil
fonte
2
sim. Não faz sentido manter contêineres destinados apenas a depurar seu Dockerfile quando você pode recriá-los à vontade.
7898 Thomasleveil
1
OK, isso realmente foi super útil, mas tenho o problema de que, se uma construção de contêiner falhar, não posso usar esse truque com o hash do contêiner em que ele estava trabalhando. Nenhuma imagem será criada se o RUN falhar. Posso conectar ao contêiner intermediário que nunca foi limpo?
Altreus
6
quando um dos comandos do Dockerfile falha, o que você precisa fazer é procurar o ID da camada anterior e executar um contêiner com um shell desse ID: docker run --rm -it <id_last_working_layer> bash -ile uma vez no contêiner, tente o comando que falhou em reproduzir o problema e, em seguida, corrija o comando e teste-o, finalmente atualize seu Dockerfile com o comando corrigido.
21416 Thomasleveil
2
Além disso, você pode docker diff <container>obter e obter uma lista completa das alterações específicas do sistema de arquivos feitas nessa camada específica (arquivos adicionados, excluídos ou alterados em todo o sistema de arquivos da imagem).
L0j1k
14
Eu pensei que isso não estava funcionando porque dizia Unable to find image 'd5219f1ffda9:latest' locally. No entanto, fiquei confuso com os vários tipos de IDs. Acontece que você precisa usar os IDs que estão diretamente após as setas, não os que dizem "Executando em ...".
Rspeer 18/05/19
201

A resposta superior funciona no caso em que você deseja examinar o estado imediatamente antes do comando com falha.

No entanto, a pergunta pergunta como examinar o estado do contêiner que falhou. Na minha situação, o comando com falha é uma compilação que leva várias horas; portanto, retroceder antes do comando com falha e executá-lo novamente leva muito tempo e não é muito útil.

A solução aqui é encontrar o contêiner que falhou:

$ 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

Confirme com uma imagem:

$ docker commit 6934ada98de6
sha256:7015687976a478e0e94b60fa496d319cdf4ec847bcd612aecf869a72336e6b83

E, em seguida, execute a imagem [se necessário, executando o bash]:

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

Agora você está realmente olhando o estado da construção no momento em que ela falhou, em vez de antes de executar o comando que causou a falha.

Desenhou
fonte
Fora de interesse, por que você precisaria criar uma nova imagem a partir do contêiner? Por que não iniciar o contêiner? Se uma imagem criada a partir do contêiner com falha puder executar, certamente o contêiner parado / com falha também poderá executar? Ou eu estou esquecendo de alguma coisa?
nmh
@nmh Porque ele permite capturar e inspecionar um contêiner no estado de falha sem precisar executar o comando com falha novamente. Às vezes, o comando com falha leva alguns minutos ou mais para ser executado, portanto, é uma maneira conveniente de marcar o estado com falha. Por exemplo, atualmente estou usando essa abordagem para inspecionar os logs de uma compilação de biblioteca C ++ com falha que leva vários minutos. Editar - Acabei de notar que Drew disse que, em [sua] situação, o comando com falha é uma compilação que leva várias horas, portanto, retroceder antes do comando com falha e executá-lo novamente leva muito tempo e não ajuda muito.
Jaime Soto
@ nmh Acho que o problema ao tentar iniciar o contêiner com falha é que o comando start do contêiner normalmente precisa ser alterado para ser útil. Se você tentasse iniciar o contêiner com falha novamente, ele executaria o comando que falhou novamente e você voltaria aonde começou. Ao criar uma imagem, você pode iniciar um contêiner com um comando de início diferente.
Centimane 13/11/19
2
Isso não funciona se você estiver usando DOCKER_BUILDKIT=1para criar o seuDockerfile
Clintm
Para o ponto de @ nmh - você não precisa confirmar a imagem se estiver logo após a saída da compilação. Você pode usar o docker container cp para extrair os resultados do arquivo do container de construção que falhou.
Whoisthemachine 26/07/19
7

O Docker armazena em cache todo o estado do sistema de arquivos após cada RUNlinha bem-sucedida .

Sabendo que:

  • para examinar o último estado antes de sua falha RUNde comando, comentá-la na Dockerfile (bem como todas e quaisquer subseqüentes RUNcomandos), em seguida, executar docker builde docker runnovamente.
  • para examinar o estado após o RUNcomando com falha , basta adicionar || truea ele para forçá-lo a ter sucesso; então proceda como acima (mantenha todo e qualquer RUNcomando subsequente comentado, execute docker builde docker run)

Não há necessidade de mexer com os IDs de camada ou internos do Docker e, como bônus, o Docker minimiza automaticamente a quantidade de trabalho que precisa ser refeito.

DomQ
fonte
1
Esta é uma resposta especialmente útil ao usar DOCKER_BUILDKIT, pois o buildkit não parece suportar as mesmas soluções que as listadas acima.
M. Anthony Aiello
3

Depurar falhas na etapa de construção é realmente muito irritante.

A melhor solução que encontrei é garantir que cada etapa que faz um trabalho real seja bem-sucedida e adicione uma verificação após as que falharem. Dessa forma, você obtém uma camada confirmada que contém as saídas da etapa com falha que você pode inspecionar.

Um Dockerfile, com um exemplo após a # Run DB2 silent installerlinha:

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

O que eu faria é comentar o Dockerfile abaixo e incluir a linha incorreta. Em seguida, você pode executar o contêiner e executar os comandos da janela de encaixe manualmente e ver os logs da maneira usual. Por exemplo, se o Dockerfile for

RUN foo
RUN bar
RUN baz

e está morrendo no bar eu faria

RUN foo
# RUN bar
# RUN baz

Então

$ docker build -t foo .
$ docker run -it foo bash
container# bar
...grep logs...
seanmcl
fonte
Isso é o que eu teria feito também antes de encontrar este tópico. No entanto, existem maneiras melhores que não exigem a execução da compilação.
Aaron McMillin
@Aaron. Obrigado por me lembrar desta resposta. Não vejo há muito tempo. Você poderia, por favor, explicar por que a resposta aceita é melhor do que esta, do ponto de vista prático. Definitivamente, entendo por que a resposta de Drew é melhor. Parece que a resposta aceita ainda requer nova execução.
Seanmcl
Na verdade, votei na resposta de Drew e não na aceita. Ambos trabalham sem executar novamente a compilação. Na resposta aceita, você pode pular para um shell imediatamente antes do comando com falha (você pode executá-lo novamente para ver o erro, se for rápido). Ou, com a resposta de Drew, você pode obter um shell após a execução do comando com falha (no caso dele, o comando com falha estava em execução há muito tempo e deixava o estado para trás que poderia ser inspecionado).
Aaron McMillin