如何检查失败的docker build的文件系统?


272

我正在尝试使用以下方法为我们的开发过程构建新的Docker映像 cpanm用于安装一堆Perl模块作为各种项目的基础映像。

开发Dockerfile时,cpanm返回失败代码,因为某些模块安装不干净。

我相当确定我需要apt安装更多东西。

我的问题是,在哪里可以找到/.cpanm/work输出中引用的目录,以便检查日志?在一般情况下,如何检查失败docker build命令的文件系统?

早上编辑咬住子弹并运行后,find我发现

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

这可靠吗?还是我最好还是建造一个“裸”容器并手动运行东西,直到我拥有所需的一切为止?


关于/var/lib/docker/aufs/diff/3afa404e[...]/.cpanm这些是Docker的内部知识,我不会与他们
打交道

Answers:


355

每次RUNdocker 成功从Dockerfile 执行命令时,都会提交映像文件系统中的新层。您可以方便地将这些图层ID用作图像来启动新容器。

采取以下Dockerfile:

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

并构建它:

$ 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

您现在可以开始从一个新的容器00f017a8c2a6044e1532c6905bd8172529c1

$ 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

当然,您可能想启动一个Shell来探索文件系统并尝试命令:

$ 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

当Dockerfile命令之一失败时,您需要做的是查找上一层ID,并在根据该ID创建的容器中运行Shell:

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

放入容器后:

  • 尝试失败的命令,并重现问题
  • 然后修复命令并对其进行测试
  • 最后使用固定命令更新您的Dockerfile

如果您真的需要在失败的实际层中进行实验,而不是从最后一个工作层开始工作,请参阅Drew的答案


2
是的,它确实。当您可以随意重新创建容器时,保留容器只是为了调试Dockerfile是没有意义的。
Thomasleveil 2014年

1
好的,这实际上是超级有用的,但是我确实有一个问题,如果容器构建失败,那么我就不能将它与正在使用的容器的哈希一起使用。如果RUN失败,则不会创建任何映像。我可以连接到从未清理过的中间容器上吗?
Altreus 2014年

6
当Dockerfile命令之一失败时,您需要查找上一层的id并运行具有该id的shell的容器:docker run --rm -it <id_last_working_layer> bash -il一旦进入该容器,请尝试执行失败以重现该问题的命令,然后修复该命令并对其进行测试,最后使用fixed命令更新您的Dockerfile。
Thomasleveil 2014年

2
此外,您可以docker diff <container>并获得在该特定层上进行的特定文件系统更改的完整列表(该映像在整个文件系统中添加,删除或更改的文件)。
L0j1k 2015年

14
我以为这没用,因为它说Unable to find image 'd5219f1ffda9:latest' locally。但是,我对多种ID感到困惑。事实证明,您必须使用箭头后面的ID,而不是显示“ Running in ...”的ID。
rspeer '17

200

如果您想在失败的命令之前立即检查状态,则最有效的答案是有效的。

但是,问题询问如何检查发生故障的容器本身的状态。在我的情况下,失败的命令是一个耗时数小时的构建,因此在失败的命令之前倒带并再次运行它会花费很长时间,并且不是很有帮助。

解决方案是找到失败的容器:

$ 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

提交给图像:

$ docker commit 6934ada98de6
sha256:7015687976a478e0e94b60fa496d319cdf4ec847bcd612aecf869a72336e6b83

然后运行映像[如果需要,运行bash]:

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

现在,您实际上是在查看构建失败时的状态,而不是运行导致失败的命令之前的状态。


出于兴趣,您为什么需要从容器中创建新图像?为什么不只是启动容器?如果从失败的容器创建的映像能够运行,那么确定停止/失败的容器是否也能够运行?还是我错过了什么?
nmh

@nmh因为它使您可以捕获并检查处于失败状态的容器,而不必再次运行失败的命令。有时,执行失败的命令需要花费几分钟或更长时间才能执行,因此这是标记失败状态的便捷方法。例如,我当前正在使用这种方法来检查失败的C ++库构建的日志,该日志需要几分钟。编辑-刚注意到Drew说过,在他的情况下,失败的命令是一个构建过程,需要花费几个小时,因此在失败的命令之前倒带并再次运行它会花费很长时间,并且不是很有帮助。
Jaime Soto

@nmh我认为尝试启动失败的容器的问题是通常需要更改容器的启动命令才能使用。如果您尝试再次启动失败的容器,它将运行再次失败的命令,您将回到开始的位置。通过创建映像,您可以使用其他启动命令来启动容器。
Centimane

2
如果您要DOCKER_BUILDKIT=1构建自己的设备,这将不起作用Dockerfile
Clintm

直说@nmh-如果您只是在生成输出之后,就不需要提交映像。您可以使用docker container cp从失败的构建容器中提取文件结果。
whoisthemachine

7

每次成功后,Docker都会缓存整个文件系统状态RUN一行。

知道:

  • 要在失败的RUN命令之前检查最新状态,请在Dockerfile中(以及任何和所有后续RUN命令)将其注释掉,然后运行docker builddocker run试。
  • 要检查失败命令的状态RUN,只需将|| true其添加以强制其成功;然后像上面一样继续操作(保留所有和所有后续RUN命令被注释掉,运行docker builddocker run

Tada,无需理会Docker内部或层ID,此外,Docker会自动将需要重做的工作量降至最低。


1
当使用DOCKER_BUILDKIT时,这是一个特别有用的答案,因为buildkit似乎不支持与上面列出的解决方案相同的解决方案。
M. Anthony Aiello

3

调试构建步骤故障确实很烦人。

我发现的最佳解决方案是确保完成实际工作的每个步骤都成功,并在失败的步骤之后添加检查。这样,您将获得一个提交层,其中包含您可以检查的失败步骤的输出。

Dockerfile,在下# Run DB2 silent installer一行示例:

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

我要做的是注释掉下面的Dockerfile,并包括违规行。然后,您可以运行容器并手动运行docker命令,并以通常的方式查看日志。例如,如果Dockerfile是

RUN foo
RUN bar
RUN baz

我快要死在酒吧了

RUN foo
# RUN bar
# RUN baz

然后

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

这也是我在找到该线程之前所做的事情。尽管有更好的方法,但不需要重新运行构建。
亚伦·麦克米林

@亚伦 感谢您提醒我这个答案。我好久没看了。您能否从实际角度解释为什么可接受的答案比该答案更好。我绝对明白为什么Drew的答案更好。似乎可接受的答案仍然需要重新运行。
seanmcl

我实际上投票赞成Drew的回答,而不是被接受。它们都可以工作,而无需重新运行构建。在接受的答案中,您可以跳到失败命令之前的外壳程序中(如果很快,您可以再次运行它以查看错误)。或者使用Drew的答案,您可以在执行失败的命令后获得外壳(在他的情况下,执行失败的命令长期运行,可以检查其后的状态)。
亚伦·麦克米林
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.