【问题标题】:How can I inspect the file system of a failed `docker build`?如何检查失败的“docker build”的文件系统?
【发布时间】:2014-11-30 23:48:09
【问题描述】:

我正在尝试为我们的开发过程构建一个新的 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 的内部结构,我不会惹他们

标签: debugging docker cpanm


【解决方案1】:

就我而言,我必须:

DOCKER_BUILDKIT=1 docker build ...

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

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

... --progress=plain ...

然后在现有RUN ... 上添加各种RUN ... 或附加行以调试特定命令。这让你觉得我可以完全访问(至少在你的构建速度相对较快的情况下)。

例如,您可以像这样检查变量:

RUN echo "Variable NAME = [$NAME]"

如果您想知道文件是否安装正确,请这样做:

RUN find /

等等

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

【讨论】:

    【解决方案2】:

    每当 docker 从 Dockerfile 成功执行 RUN 命令时,a new layer in the image filesystem 就会被提交。您可以方便地使用这些图层 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
    

    在容器中一次:

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

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

    【讨论】:

    • 是的。当您可以随意重新创建容器时,保留仅用于调试 Dockerfile 的容器是没有意义的。
    • 当一个 Dockerfile 命令失败时,你需要做的是查找上一层的 id 并运行一个具有该 id 的外壳的容器:docker run --rm -it &lt;id_last_working_layer&gt; bash -il 并在容器中运行一次尝试无法重现问题的命令,然后修复命令并测试它,最后使用修复的命令更新您的 Dockerfile。
    • 我认为这不起作用,因为它说Unable to find image 'd5219f1ffda9:latest' locally。但是,我对多种 ID 感到困惑。事实证明,您必须使用直接在箭头之后的 ID,而不是那些显示“正在运行...”的 ID。
    • 当我运行docker build 时,它不会给我每一层的哈希 ID。我没有看到任何启用此功能的命令选项。
    • @ADJenks 非常烦人不是吗!在这里找到答案:stackoverflow.com/questions/65614378/… 基本上你需要在超级机密选项设置中将 buildkit 更改为 false。也许他们应该在上面放一个“小心豹子”的牌子来确定。
    【解决方案3】:

    更新 docker 版本 20.10 及以后

    Linux 或 macOS

    DOCKER_BUILDKIT=0 docker build ...
    

    窗口

    # Command line
    set DOCKER_BUILDKIT=0 docker build ...
    # PowerShell
    $env:DOCKER_BUILDKIT=0
    

    使用 DOCKER_BUILDKIT=0 docker build ... 获取旧版本中已知的中间容器哈希。

    在较新的版本中,默认情况下会激活 Buildkit。建议仅将其用于调试目的。 Build Kit 可以让您的构建速度更快。

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

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

    【讨论】:

    • 我怀疑这个问题很久了,你的回答成功了!它还在多阶段期间移除中间容器。
    • 或者在 Windows 上,运行命令set DOCKER_BUILDKIT=0,然后运行docker build ... 命令。
    • 或者在 Windows 上使用 PowerShell 时:$env:DOCKER_BUILDKIT=0
    【解决方案4】:

    目前使用最新的 docker-desktop,没有办法选择退出新的 Buildkit,它还不支持调试(请关注此 GitHub 线程上的最新更新:https://github.com/moby/buildkit/issues/1472)。

    • 首先让 docker 尝试构建,并找出失败的 Dockerfile 中的哪一行。
    • 接下来,在您的 Dockerfile 中,您可以在顶部添加构建目标:FROM xxx as debug
    • 然后,在你的 Dockerfile 中添加一个额外的目标FROM xxx as next,就在失败的命令前一行(因为你不想构建那个部分)。示例:
    FROM xxx as debug
    # Working command
    RUN echo "working command"
    
    FROM xxx as next
    # Example of failing command
    RUN echoo "failing command"
    
    
    • 然后运行docker build -f Dockerfile --target debug --tag debug .
    • 接下来你可以运行docker run -it debug /bin/sh

    您可以通过按 CTRL P + CTRL Q 退出 shell

    如果您想使用docker compose build 而不是docker build,可以在docker-compose.yml under build 中添加target: debug
    然后通过docker compose run xxxYourServiceNamexxx 启动容器并使用:

    • second top answer 了解如何在容器内运行 shell。
    • 或在 Dockerfile 中的 FROM xxx as next 行之前添加 ENTRYPOINT /bin/sh

    【讨论】:

      【解决方案5】:

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

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

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

      【讨论】:

        【解决方案6】:

        当您想检查失败命令之前的状态时,最佳答案有效。

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

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

        $ 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 因为它允许您捕获和检查处于失败状态的容器,而无需再次运行失败的命令。有时,失败的命令需要几分钟或更长时间才能执行,因此这是标记失败状态的便捷方式。例如,我目前正在使用这种方法来检查失败的 C++ 库构建的日志,这需要几分钟时间。编辑 - 刚刚注意到 Drew 说 在 [他] 的情况下,失败的命令是一个需要几个小时的构建,因此在失败的命令之前倒带并再次运行它需要很长时间并且不是很有帮助。
        • @nmh 我认为尝试启动失败容器的问题是容器的启动命令通常需要更改才能使用。如果您尝试再次启动失败的容器,它将再次运行失败的命令,并且您将回到您开始的位置。通过创建映像,您可以使用不同的启动命令启动容器。
        • 如果您使用DOCKER_BUILDKIT=1 来构建您的Dockerfile,这将不起作用
        • 对于@nmh 的观点-如果您只是在构建输出之后,则不需要提交图像。您可以使用docker container cp 从失败的构建容器中提取文件结果。
        【解决方案7】:

        Docker caches the entire filesystem state 在每个成功的RUN 行之后。

        知道:

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

        Tada,无需弄乱 Docker 内部结构或层 ID,作为奖励,Docker 会自动最小化需要重新完成的工作量。

        【讨论】:

        • 在使用 DOCKER_BUILDKIT 时这是一个特别有用的答案,因为 buildkit 似乎不支持与上面列出的相同的解决方案。
        【解决方案8】:

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

        【讨论】:

        • 在找到这个帖子之前我也会这样做。有更好的方法虽然不需要重新运行构建。
        • @Aaron。谢谢你提醒我这个答案。我已经很久没有看它了。您能否从实际的角度解释为什么接受的答案比这个更好。我绝对明白为什么德鲁的答案更好。似乎接受的答案仍然需要重新运行。
        • 我实际上投票支持德鲁的答案,而不是接受。它们都无需重新运行构建即可工作。在接受的答案中,您可以在失败的命令之前跳入 shell(如果它很快,您可以再次运行它以查看错误)。或者通过 Drew 的回答,您可以在运行失败的命令后获得一个 shell(在他的情况下,失败的命令运行时间很长,并且留下了可以检查的状态)。
        猜你喜欢
        • 2012-04-12
        • 2021-03-06
        • 2018-12-18
        • 2023-03-21
        • 1970-01-01
        • 2020-11-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多