テクノロジー · 実用ガイド

Dockerのexec format error:再ビルド前にイメージ・バイナリ・起動設定を確認

exec format errorはイメージのタグだけでは診断できない。キャッシュ削除やQEMU追加、再ビルドの前に、選択されたマニフェスト、最終ファイルシステム、実行ファイルのアーキテクチャとインタープリターを追跡する。

実行ホスト、イメージの種類、実行ファイル、起動コマンドの4項目を示すオリジナル診断図。
編集部制作の図解イラスト:失敗したダイジェストを保ち、4つの関係を確認する。ラベルは証拠の一部。 Watch at Ease

Linuxコンテナの「exec format error」は、渡されたファイルをカーネルが実行できなかったことを示す。CPUアーキテクチャは原因の1つだが、有効なインタープリターヘッダーのないスクリプトなど、不正な実行形式でも失敗する。まず対象ファイルと失敗した段階を特定し、それから修正方法を選ぶ。

失敗したイメージとエラー出力を残そう。調査記録では、実行ホスト、選択されたイメージマニフェスト、最終ファイルシステムの実際の実行ファイル、起動を試みたコマンドという4つを結び付ける。アーキテクチャのラベルが一致するだけでは、この4項目の確認に及ばない。

1. 再ビルド前に失敗した実行を記録する

完全なエラー、イメージ参照とダイジェスト、存在すればコンテナID、指定プラットフォーム、使用したDocker contextを保存する。Dockerfileだけでなく、失敗したコンテナの設定とマウントを確認しよう。実行時のエントリーポイント上書きやマウントされたスクリプトで、実際の対象が変わるためだ。docker container inspect CONTAINERで設定を、docker image inspect IMAGEでローカルイメージ情報を取得する。

重要なのは実際にプロセスを動かすDockerデーモンまたはビルドワーカーだ。リモートの場合もDocker DesktopのLinux VM内の場合もあり、手元のノートPCのCPUだけでは特定できない。DockerfileのRUN命令中の失敗と完成コンテナの起動失敗も分ける。RUNで/bin/shが名指しされるならビルド段階の環境を、アプリのエントリーポイントなら最終実行環境のファイルシステムを調べる。

2. レジストリの種類とローカルイメージを比較する

docker buildx imagetools inspect IMAGE_REFERENCEでレジストリ参照のマニフェストとプラットフォーム別イメージを確認し、該当するローカルイメージと失敗時の指定プラットフォームを比較する。可変タグは失敗後に新しいイメージへ移動し得る。現在のタグを過去の証拠として扱わず、元のダイジェストを残したい。

必要な種類がなければ正しいプラットフォーム用のイメージを選択または作成する。存在するのに別のものが選ばれたなら、明示的なプラットフォーム指定とデプロイ設定を調べる。--platform=linux/amd64はターゲットを指定するだけで、すでにコピーされた任意のarm64ファイルを変換しない。対応マニフェストがない場合は実行前の選択段階で失敗し得るため、ENOEXECとは段階が異なる。

3. 失敗するコマンドを起動せずにファイルを調べる

停止した失敗コンテナが残っていれば、docker cp CONTAINER:/path/to/file ./artifact-to-inspectで対象を取り出せる。Dockerは停止中のコンテナからのコピーをサポートする。イメージしかない場合、docker create IMAGEは起動せずにコンテナを作る。返されたIDを記録して調査する。実行時のマウントファイルはイメージと異なり得るので別に確認する。

コピーした対象を、fileなど信頼できる解析ツールで調べる。ELFバイナリではreadelf -hが機械種別を、readelf -lが要求するプログラムインタープリターを示す。アーキテクチャを知るために対象を実行してはいけない。手元にツールがなければ既存の信頼できる解析環境を使う。最小構成やdistrolessイメージにshell、file、readelfがあるとは限らない。

4. コピーされたバイナリをビルド段階まで追う

最終イメージにコピーされる実行ファイルを作成・ダウンロードする全段階を洗い出す。BUILDPLATFORMはBuildKitワーカー、TARGETPLATFORMは要求された出力を表す。ビルダープラットフォームに固定した段階でネイティブのコンパイラーを動かすのは有効だが、コンパイラーの設定はターゲット用の成果物を生成しなければならない。

Dockerの定義済みターゲット引数はグローバルに存在するが、段階内で使うにはARG宣言が必要だ。TARGETOS、TARGETARCH、必要なTARGETVARIANTを確認したうえで、言語ツールチェーンの実際の出力を調べる。宣言だけであらゆるコンパイラーが自動的にクロスコンパイルするわけではない。最終FROMを対応するベースに変えても、別段階からコピーした非対応の実行ファイルは変換されない。

5. スクリプト、インタープリター不足、権限を分ける

直接実行するスクリプトは先頭バイトとインタープリター行を確認する。#!ヘッダーが意図したインタープリターを指し、その実体が実行環境に存在する必要がある。ヘッダー前の余分なバイトやWindows形式の改行も調べる。明示的なshell経由の実行は起動方法を変えるため、元の直接実行が直った証明にはならない。

正確なエラー分類を残す。Linuxでは認識できない形式や誤ったアーキテクチャがENOEXEC、パスやスクリプト・ELFインタープリターの不在がENOENT、実行権限拒否やnoexecマウントなどがEACCESだ。ファイルが見えても、インタープリターがなくて失敗し得る。chmodはCPUアーキテクチャを修正せず、エミュレーション追加も不足するインタープリターをインストールしない。

6. 原因を1つ直し、元の経路で確認する

証拠に合う分岐を選ぶ。種類が不足ならイメージ選択やビルド出力、内部バイナリが誤りならその成果物の再作成や交換、スクリプト形式ならヘッダーや文字コード、インタープリター不足なら対応ランタイム、権限拒否ならユーザー・ファイルモード・マウント方針を調べる。イメージ削除で原因特定を代用しない。

エミュレーションは非ネイティブ実行を意図的に選ぶ場合の方法だ。ホスト全体のbinfmt登録を変える前に、実際のワーカーの対応状況を確認する。Docker Desktopや公式BuildKitにすでに含まれることもある。修正後は使い捨ての非本番環境で、同じ意図したコマンド、プラットフォーム、ユーザー、関連マウントを試す。新しいダイジェストと変わった確認項目を記録する。まだ失敗するなら新しいエラーを残し、その層から調べ直す。

ENOEXECの形式・アーキテクチャ、ENOENTのパス・インタープリター不足、EACCESの権限を分ける図。
編集部制作の図解イラスト:実際のエラーに応じて次を調べる。chmodでCPUアーキテクチャは変わらない。 Watch at Ease