我如何检查一个失败的“docker构建”的文件系统?

我试图为我们的开发过程构建一个新的Docker映像,使用cpanm来安装一堆Perl模块作为各种项目的基本映像。

在开发Dockerfile时,cpanm返回一个失败代码,因为一些模块没有干净地安装。

我相当肯定我需要得到apt来安装更多的东西。

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

早上编辑咬紧牙关后,运行一个find我发现

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

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

164276 次浏览

我要做的是注释掉下面的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...

每次docker从Dockerfile中成功执行RUN命令时,映像文件系统中的一个新层就会被提交。你可以方便地使用这些图层id作为图像来启动一个新容器。

以以下Dockerfile为例:

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

并建立它:

$ docker build -t so-26220957 .
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

一旦进入容器:

  • 尝试失败的命令,并重现问题
  • 然后修复该命令并测试它
  • 最后用fixed命令更新Dockerfile

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

在每个成功的RUN行之后,Docker 缓存整个文件系统状态

知道:

  • 为了检查你失败的RUN命令之前的最新状态,在Dockerfile中注释掉它(以及任何和所有后续的RUN命令),然后再次运行docker builddocker run
  • 检查状态的失败RUN命令,只需添加|| true,迫使它成功;然后像上面那样进行(保留所有后续RUN命令的注释,运行docker builddocker run)

也就是说,无需修改Docker内部代码或层id,而且作为额外的奖励,Docker会自动最小化需要重新完成的工作量。

如果您希望在命令失败之前立即检查状态,上面的答案是有效的。

但是,问题询问如何检查失败容器本身的状态。在我的情况下,失败的命令是一个需要几个小时的构建,所以在失败的命令之前倒回并再次运行它需要很长时间,而且没有太大帮助。

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

$ 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]

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

调试构建步骤失败确实非常烦人。

我发现的最好的解决方案是确保每一个真正工作的步骤都成功,并在那些失败的步骤之后添加检查。通过这种方式,您可以获得一个包含您可以检查的失败步骤的输出的已提交层。

一个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

更新更新的docker版本20.10以上

Linux或macOS

DOCKER_BUILDKIT=0 docker build ...

窗户

# Command line
set DOCKER_BUILDKIT=0 docker build ...
# PowerShell
$env:DOCKER_BUILDKIT=0
< p >使用 DOCKER_BUILDKIT=0 docker build ...

.

.

.

在较新的版本上,Buildkit默认是激活的。建议仅将其用于调试目的。Build Kit可以使您的构建更快。

< p >供参考: Buildkit不支持中间容器哈希:https://github.com/moby/buildkit/issues/1053

感谢@David Callanan和@MegaCookie的投入。

目前最新的docker-desktop,没有办法选择退出 的新Buildkit,它还不支持调试(请参考 在这个GitHub线程上的最新更新: https://github.com/moby/buildkit/issues/1472)。< / p >
  1. 找出Dockerfile中的哪一行出错了。
  2. 添加到Dockerfile的顶部:FROM xxx as debug
  3. 在失败的命令(因为你不想构建那个部分)之前添加一个额外的目标:FROM xxx as next。例子:
FROM xxx as debug
RUN echo "working command"


FROM xxx as next
RUN echoo "failing command"


  • 运行docker build -f Dockerfile --target debug --tag debug .
  • 然后你可以用:docker run -it debug /bin/sh来调试容器

可以通过CTRL p + CTRL Q退出shell
< br > < br >

如果你想使用docker compose build而不是docker build,可以在你的docker-compose。build下的yml中添加target: debug
然后通过docker compose run xxxYourServiceNamexxx启动容器,并使用其中之一:

  • 第二个顶部答案来找出如何在容器中运行一个shell。
  • 或者在Dockerfile中的FROM xxx as next行之前添加ENTRYPOINT /bin/sh

在我的情况下,我必须:

DOCKER_BUILDKIT=1 docker build ...

正如Jannis Schönleber在他的回答中提到的,目前在这种情况下没有可用的调试(即没有创建中间映像/容器)。

我发现我可以使用以下选项:

... --progress=plain ...

然后在现有的RUN ...上添加各种RUN ...或额外的行来调试特定的命令。在我看来,这给了你完全的访问权限(至少如果你的构建相对较快)。

例如,你可以这样检查一个变量:

RUN echo "Variable NAME = [$NAME]"

如果你想知道文件是否正确安装,你可以这样做:

RUN find /

等。

在我的情况下,我必须调试一个带有私有存储库的Go应用程序的docker构建,并且很难进行调试。我有关于在这里的其他细节。

我的解决方案是查看docker文件中的哪一步失败,在我的情况下是RUN bundle install

把它改成

RUN bundle install || cat <path to the file containing the error>

这有双重效果,打印出失败的原因,并且这个中间步骤不会被docker构建认为是失败的。所以它不会被删除,并且可以通过以下方式进行检查:

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

在那里,您甚至可以重新运行失败的命令并进行实时测试。

仍然使用BuildKit,如在亚历克西斯Wilke回答中,您可以使用< >强ktock/buildg < / >强

看到“Dockerfile的交互式调试器"从Tokunaga浩平表示

buildg是一个基于BuildKit交互调试Dockerfile的工具。

  • 源代码级检验
  • 断点和步骤执行
  • 交互式shell的一步与您自己的调试工具
  • 基于BuildKit(需要未合并的补丁)
  • 支持无根的

例子:

$ buildg.sh debug --image=ubuntu:22.04 /tmp/ctx
WARN[2022-05-09T01:40:21Z] using host network as the default
#1 [internal] load .dockerignore
#1 transferring context: 2B done
#1 DONE 0.1s


#2 [internal] load build definition from Dockerfile
#2 transferring dockerfile: 195B done
#2 DONE 0.1s


#3 [internal] load metadata for docker.io/library/busybox:latest
#3 DONE 3.0s


#4 [build1 1/2] FROM docker.io/library/busybox@sha256:d2b53584f580310186df7a2055ce3ff83cc0df6caacf1e3489bff8cf5d0af5d8
#4 resolve docker.io/library/busybox@sha256:d2b53584f580310186df7a2055ce3ff83cc0df6caacf1e3489bff8cf5d0af5d8 0.0s done
#4 sha256:50e8d59317eb665383b2ef4d9434aeaa394dcd6f54b96bb7810fdde583e9c2d1 772.81kB / 772.81kB 0.2s done
Filename: "Dockerfile"
2| RUN echo hello > /hello
3|
4| FROM busybox AS build2
=>   5| RUN echo hi > /hi
6|
7| FROM scratch
8| COPY --from=build1 /hello /
>>> break 2
>>> breakpoints
[0]: line 2
>>> continue
#4 extracting sha256:50e8d59317eb665383b2ef4d9434aeaa394dcd6f54b96bb7810fdde583e9c2d1 0.0s done
#4 DONE 0.3s
...

如果你正在使用docker-compose来构建docker映像,尝试在命令之前添加DOCKER_BUILDKIT=0,以查看最后一个成功的层id

DOCKER_BUILDKIT=0 docker-compose ...

这将暂时禁用命令的DOCKER_BUILDKIT。

有了最后一个层id,您可以使用从顶部答案的命令连接到它

docker run --rm -it LAST_LAYER_ID sh