失敗した「docker build」のファイルシステムを検査するにはどうすればよいですか?


272

私はcpanm、さまざまなプロジェクトの基本イメージとして一連のPerlモジュールをインストールするために使用して、開発プロセス用の新しいDockerイメージを構築しようとしています。

Dockerfileの開発中に、cpanm一部のモジュールが正常にインストールされなかったため、失敗コードを返します。

私はaptいくつかの追加のものをインストールする必要があるとかなり確信しています。

私の質問は、/.cpanm/workログを検査するために、出力で引用されているディレクトリをどこで見つけることができるかです。一般的なケースでは、失敗したdocker buildコマンドのファイルシステムをどのように検査できますか?

朝の編集弾丸を噛んで実行したfind後、私は発見しました

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

これは信頼できますか、それとも、必要なものがすべて揃うまで、「裸の」コンテナを作成して手動で実行する方が良いですか?


/var/lib/docker/aufs/diff/3afa404e[...]/.cpanmそれらについてはDockerの内部であり、私はそれらをいじりません
Thomasleveil

回答:


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

あなたは今から新しいコンテナを開始することができ00f017a8c2a6044e1532c690そして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

もちろん、シェルを起動してファイルシステムを探索し、コマンドを試すこともできます。

$ 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コマンドの1つが失敗した場合は、前のレイヤーのIDを探し、そのIDから作成されたコンテナーでシェルを実行する必要があります。

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

コンテナに入ったら:

  • 失敗したコマンドを試して、問題を再現します
  • 次に、コマンドを修正してテストします
  • 最後に、修正されたコマンドでDockerfileを更新します

最後の作業レイヤーから作業するのではなく、失敗した実際のレイヤーを実際に試す必要がある場合は、Drewの回答を参照してください。


2
はい、そうです。コンテナーを自由に再作成できるときに、Dockerfileをデバッグすることのみを目的としたコンテナーを保持しても意味がありません。
Thomasleveil 2014年

1
これは実際には非常に便利ですが、コンテナーのビルドが失敗した場合、それが機能していたと言われているコンテナーのハッシュでこのトリックを使用できないという問題があります。RUNが失敗した場合、イメージは作成されません。クリーンアップされていない中間コンテナにアタッチできますか?
Altreus 2014年

6
Dockerfileコマンドの1つが失敗した場合は、前のレイヤーのIDを探し、そのIDのシェルを使用してコンテナーを実行する必要があります。コンテナー内でdocker run --rm -it <id_last_working_layer> bash -il、問題を再現できなかったコマンドを実行してから、コマンドを修正してテストし、最後に修正したコマンドでDockerfileを更新します。
Thomasleveil、2014年

2
また、docker diff <container>その特定のレイヤーで行われた特定のファイルシステムの変更(そのイメージのファイルシステム全体で追加、削除、または変更されたファイル)の完全なリストを取得できます。
L0j1k 2015年

14
それが言っUnable to find image 'd5219f1ffda9:latest' locallyたので、これは機能していないと思いました。しかし、私は複数の種類のIDに戸惑っていました。矢印の直後にあるIDを使用する必要があることがわかりました。「実行中...」と書かれたIDではありません。
rspeer

201

一番上の答えは、失敗したコマンドの直前の状態を調べたい場合に機能します。

ただし、問題は、失敗したコンテナー自体の状態を調べる方法を尋ねます。私の状況では、失敗したコマンドはビルドに数時間かかるため、失敗したコマンドの前に巻き戻して再度実行すると時間がかかり、あまり役に立ちません。

ここでの解決策は、失敗したコンテナを見つけることです。

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

@nmh失敗したコマンドを再度実行しなくても、失敗した状態のコンテナーをキャプチャして検査できるためです。失敗したコマンドの実行には数分以上かかる場合があるため、失敗した状態にタグを付けるのに便利です。たとえば、私は現在、このアプローチを使用して、数分かかる失敗したC ++ライブラリビルドのログを検査しています。編集-Drew が[彼の]状況では、失敗したコマンドは数時間かかるビルドであるため、失敗したコマンドの前に巻き戻して再度実行するのに長い時間がかかり、あまり役に立た
Jaime Soto

@nmh失敗したコンテナーを開始しようとする際の問題は、コンテナーの開始コマンドを通常は役立つように変更する必要があることです。失敗したコンテナをもう一度起動しようとすると、再び失敗したコマンドが実行され、開始した場所に戻ります。イメージを作成することにより、別の開始コマンドでコンテナーを開始できます。
Centimane 2018年

2
これを使用DOCKER_BUILDKIT=1してビルドしている場合は機能しませんDockerfile
Clintm

@nmhの要点-ビルド出力の直後であれば、イメージをコミットする必要はありません。docker container cpを使用して、失敗したビルドコンテナーからファイルの結果を抽出できます。
whoisthemachine

7

Docker は、1RUN行成功するたびにファイルシステム全体の状態をキャッシュします

知っています:

  • 失敗したRUNコマンドの前の最新の状態を調べるには、Dockerfileでコメントアウトし(その後のすべてのRUNコマンドも同様)、次に実行docker buildしてdocker run再度実行します。
  • 失敗したコマンドのの状態を調べるにはRUN、単に|| trueそれを追加して、強制的に成功させます。その後、上記のように続行します(後続のRUNコマンドはすべてコメントアウトして実行しdocker builddocker run

多田、Dockerの内部やレイヤーIDをいじる必要はありません。おまけとして、Dockerは再実行する必要がある作業の量を自動的に最小化します。


1
これは、DOCKER_BUILDKITを使用する場合に特に役立ちます。ビルドキットは上記と同じソリューションをサポートしていないようです。
M.アンソニーアイエロ

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

これは、このスレッドを見つける前に私がやっていたことでもあります。ビルドを再実行する必要がないより良い方法があります。
アーロン・マクミリン

@アーロン。この答えを思い出してくれてありがとう。長い間見ていません。実用的な観点から、受け入れられた回答がこれよりも優れている理由を説明してください。ドリューの答えの方が良い理由は確実にわかります。受け入れられた答えはまだ再実行を必要とするようです。
seanmcl 2016年

私は実際にはドリューの答えに投票し、受け入れられませんでした。どちらもビルドを再実行せずに機能します。受け入れられた回答では、失敗したコマンドの直前にシェルにジャンプできます(すばやく実行すると、エラーを確認するためにもう一度実行できます)。または、Drewの回答を使用すると、失敗したコマンドが実行された後にシェルを取得できます(彼の場合、失敗したコマンドは長時間実行されていて、検査できる状態になっています)。
アーロンマクミリン2016年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.