如何在Dockerfile本身中设置断点?

14

搜索上述内容会展示很多关于如何为在docker容器中运行的应用程序设置断点的结果,但我想要在 Dockerfile 中设置一个断点,以便在断点处暂停 docker build。 以下是一个 Dockerfile 示例:


FROM ubuntu:20.04

RUN echo "hello"
RUN echo "bye"

我正在寻找一种方法在RUN echo "bye"上设置断点,使得当我调试这个Dockerfile时,镜像将在RUN echo "bye"点之前以非交互方式构建。在此之后,我将能够与容器交互式运行命令。在我的实际Dockerfile中,有一些RUN命令在断点之前会更改正在构建的镜像的文件系统。我希望通过在断点时能够交互式地运行命令,如cd / ls / find来分析图像的文件系统。

5个回答

23

你并不能单纯地设置一个断点,但你可以在构建序列的任意点(步骤之间)获取交互式 shell。

让我们构建你的镜像:

Sending build context to Docker daemon  2.048kB
Step 1/3 : FROM ubuntu:20.04
 ---> 1e4467b07108
Step 2/3 : RUN echo "hello"
 ---> Running in 917b34190e35
hello
Removing intermediate container 917b34190e35
 ---> 12ebbdc1e72d
Step 3/3 : RUN echo "bye"
 ---> Running in c2a4a71ae444
bye
Removing intermediate container c2a4a71ae444
 ---> 3c52993b0185
Successfully built 3c52993b0185

每一行带有十六进制ID的---> 0123456789ab都有有效的图像ID。所以您可以从这里开始。

docker run --rm -it 12ebbdc1e72d sh

这个命令将为您提供一个交互式 shell,让您对第一个 RUN 命令生成的部分镜像进行操作。

整个构建过程并不要求全部成功。如果一个 RUN 步骤失败,您可以使用这种技术,在该步骤之前的镜像上立即获得一个交互式 shell,并手动重新运行命令。如果您有一个非常长的 RUN 命令,您可能需要将其拆分成两个命令,以便在命令序列中特定位置获取调试 shell。


1
真是不可思议,我从来没有想到过这是可能的。 - Nick ODell
1
这在使用DOCKER_BUILDKIT=1时不再那么容易。 - rerx
1
从Docker Buildkit获取容器ID以进行交互式调试的方法有一个替代方案。 - David Maze
对于Windows容器,请将sh替换为cmd或powershell。 - brandav

6

我认为直接实现这一点是不可能的 - 这个功能已经被讨论并拒绝了。

通常,我用以下方法调试Dockerfile:在“断点”之后注释掉所有步骤,然后运行docker build,然后运行docker run -it image bashdocker run -it image sh(取决于容器内是否安装了bash)。
然后,我就有了一个交互式shell,可以运行命令来调试后面的阶段为何失败。

虽然如此,我同意设置断点并进行调试的能力将是一个方便的功能。


2
您可以使用远程 shell 调试技巧在中间容器中运行命令。
请确保您的容器镜像包括基本工具,如 netcat(nc)和fuser。这些实用程序使任何中间容器镜像都能够“呼叫主页”。在主页上,您将使用 netcat(或 socat)来回答呼叫。这个 netcat 将向容器发送您的命令,并打印它们的结果。即使是在云中某个未知的工作节点上构建的 Dockerfile,这种调试方法也可以工作。
例如:
FROM debian:testing-slim

# Set environment variables for calling home from breakpoints (BP)
ENV BP_HOME=<IP-ADDRESS-OF-YOUR-HOST>
ENV BP_PORT=33720
ENV BP_CALLHOME='BP_FIFO=/tmp/$BP.$BP_HOME.$BP_PORT; (rm -f $BP_FIFO; mkfifo $BP_FIFO) && (echo "\"c\" continues"; echo -n "($BP) "; tail -f $BP_FIFO) | nc $BP_HOME $BP_PORT | while read cmd; do if test "$cmd" = "c" ; then echo -n "" >$BP_FIFO; sleep 0.1; fuser -k $BP_FIFO >/dev/null 2>&1; break; else eval $cmd >$BP_FIFO 2>&1; echo -n "($BP) "  >$BP_FIFO; fi; done'

# Install needed utils (netcat, fuser)
RUN apt update && apt install -y netcat psmisc

# Now you are ready to run "eval $BP_CALLHOME" wherever you want to call home.

RUN BP=before-hello eval $BP_CALLHOME

RUN echo "hello"

RUN BP=after-hello eval $BP_CALLHOME

RUN echo "bye"

在启动Docker构建之前,从Dockerfile开始等待并回答来电。在主机上运行nc -k -l -p 33720(或者socat STDIN TCP-LISTEN:33720,reuseaddr,fork)。
以下是在家中的示例:
$ nc -k -l -p 33720
"c" continues
(before-hello) echo *
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
(before-hello) id
uid=0(root) gid=0(root) groups=0(root)
(before-hello) c
"c" continues
(after-hello)
...

2

最近(2022年5月)项目ktock/buildg提供断点

请参见Dockerfile的交互式调试器,作者为Kohei Tokunaga

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

  • 源代码级别的检查
  • 断点和步骤执行
  • 在步骤上使用自己的调试工具进行交互式shell
  • 基于BuildKit(需要未合并的补丁)
  • 支持无根用户

命令breakb LINE_NUMBER设置断点。

例如:

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

来自PR 24:

新增 --cache-reuse 选项,允许在调用buildg debug时共享构建缓存,使第二次调试更快。
这对于多次运行 buildg 来调试错误步骤非常有用。

请注意,目前忽略缓存步骤上的断点
由于此限制,此功能现在是可选的。 我们应该解决这个限制,并在将来使其成为默认行为。


0

哎呀,Docker 真的让事情变得 困难。这是我想出来的一个解决方法:

  1. 在你想要断点的位置插入 FROM scratch
  2. 运行 docker build . --target=<n-1>,其中 <n> 是你的“断点”之前的 FROM 命令数量。例如,如果它是单阶段构建,则使用 --target=0
    • 或者,如果你已经用 FROM <image> AS <stage> 命名了你想要断点的阶段,那么你可以使用 --target=<stage>

Docker 已经缓存了所有成功的层(即使你看不到它们),因为 FROM “断点”位于感兴趣的(可能失败的)位置之前,所以构建应该全部来自缓存,并且非常快速。

例如,如果我的 Dockerfile 如下所示:

FROM debian:bullseye AS build

RUN apt-get update && apt-get install -y \
    build-essential cmake ninja-build \
    libfontconfig1-dev libdbus-1-dev libfreetype6-dev libicu-dev libinput-dev libxkbcommon-dev libsqlite3-dev libssl-dev libpng-dev libjpeg-dev libglib2.0-dev

<SNIP lots of other setup commands>

ADD my_source.tar.xz /
WORKDIR /my_source

RUN ./configure -option1 -option2
RUN cmake --build . --parallel
RUN cmake --install .


FROM alpine
COPY --from=build /my_build /my_build

...

然后我可以像这样添加一个“断点”:

FROM debian:bullseye AS build

RUN apt-get update && apt-get install -y \
    build-essential cmake ninja-build \
    libfontconfig1-dev libdbus-1-dev libfreetype6-dev libicu-dev libinput-dev libxkbcommon-dev libsqlite3-dev libssl-dev libpng-dev libjpeg-dev libglib2.0-dev

<SNIP lots of other setup commands>

ADD my_source.tar.xz /
WORKDIR /my_source

#### BREAKPOINT ###
FROM scratch
#### BREAKPOINT ###

RUN ./configure -option1 -option2
RUN cmake --build . --parallel
RUN cmake --install .


FROM alpine
COPY --from=build /my_build /my_build

...

並使用docker build . --target=build觸發它


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接