Docker Multi-Arch: M1/M2やARMクラウドでの「exec format error」を完全に解決する方法

Docker tutorial - IT technology blog
Docker tutorial - IT technology blog

「exec format error」という悪夢

M1/M2チップのMacBookや、コスト削減のためにARMベースのVPS(AWS Graviton、Oracle Cloudなど)を使用しているなら、この皮肉な状況に直面したことがあるはずです。自分のPCで精一杯作り上げたDockerイメージをビルドしてDocker Hubにプッシュし、いざサーバーでプルして実行しようとすると、次のメッセージが表示されます:standard_init_linux.go:211: exec user process caused "exec format error"

このエラーは、CPUアーキテクチャの不一致によって発生します。自分のPCはARMチップかもしれませんが、サーバーはIntel/AMD (x86_64)チップを使用している場合があります。以前は、2台の異なるマシンで手動でビルドし、my-app:v1-arm64my-app:v1-amd64といったタグを付けていました。しかし、この方法は非常に手間がかかり、大規模なシステムを展開する際にはミスが発生しやすくなります。

何度かの「苦い経験」を経て、私はDocker Buildxに完全に切り替えました。これは、1つのイメージで、あらゆる種類のチップ上でスムーズに動作させることができる強力なプラグインです。以下に、それを最も効果的にセットアップして使用するための実戦的な経験を共有します。

なぜBuildxは通常のDocker buildよりも優れているのか?

デフォルトのdocker buildコマンドは、現在のマシンのアーキテクチャ用のイメージのみを作成します。Intelマシンを使っていればx86が、M1を使っていればARMがビルドされます。

対照的に、BuildxはMoby BuildKitQEMUを活用して、異なるハードウェア環境をエミュレートします。私が最も気に入っている点は、**Manifest List**を作成できる機能です。docker pullを実行すると、Dockerは自動的にマシンのハードウェアを認識し、最適なバージョンを選択します。CPUの種類ごとに特定のタグを指定して悩む必要はもうありません。

インストールと環境準備

現在、WindowsやMacのDocker Desktopの多くにはBuildxが標準で組み込まれています。念のため、次のコマンドで確認してください:docker buildx version

バージョンが返ってくれば、ツールの準備は完了です。次のステップはQEMUのインストールです。このツールは、ビルドプロセス中に別のチップであるかのように「振る舞う」ことで、命令セットを実行できるようにします。

docker run --privileged --rm tonistiigi/binfmt --install all

私は通常、新しいマシンをセットアップする際にこのコマンドを一度だけ実行します。これにより、異なるアーキテクチャ用のバイナリインタプリタがLinuxカーネルに登録されます。

Builderインスタンスの詳細設定

Dockerのデフォルトドライバーは、通常、複数アーキテクチャの同時ビルドをサポートしていません。そのため、新しい「仮想ビルドマシン」(builder instance)を作成する必要があります。

ステップ1:新しいBuilderの作成

管理しやすいように、名前をmybuilderにしましょう:

docker buildx create --name mybuilder --use

ステップ2:Builderの起動

次のコマンドで有効化します:

docker buildx inspect --bootstrap

サポートされているプラットフォームのリストが表示されます(例:linux/amd64, linux/arm64, linux/riscv64...)。amd64arm64の両方が表示されていれば、準備完了です。

Multi-Archイメージのビルド実践

基本的なDockerfileを持つNode.jsプロジェクトを例にします。IntelとARMの両方向けにビルドするには、次のコマンドを使用します:

docker buildx build --platform linux/amd64,linux/arm64 -t your-username/my-app:latest --push .

実戦での注意点: --pushフラグを使用して、レジストリに直接プッシュすることをお勧めします。現時点では、--loadはマルチアーキテクチャイメージをDockerのローカルストレージに直接保存することを十分にサポートしていません。Buildxは、Hubにプッシュする前に、各バージョンを自動的に1つのManifestに統合します。

実行中、Buildxが2つのストリームを並列処理しているのがわかります。コードにコンパイルが必要なライブラリ(node-gypなど)が含まれている場合、IntelマシンでのARM向けのビルドは、QEMUエミュレーションを経由するため、5〜10倍遅くなることがあります。気長に待ちましょう!

結果の確認と検証

イメージがマルチプラットフォームをサポートしていることをどうやって確認すればよいでしょうか? 私は通常、次の2つの方法を使います:

  • 方法1:UIで確認: Docker HubのTagsセクションに移動します。latestタグにlinux/amd64linux/arm64の両方のアイコンが表示されているはずです。
  • 方法2:ターミナルを使用: docker buildx imagetools inspect your-username/my-app:latestを実行します。各CPUタイプごとの詳細なチェックサムが返されます。

トラブルを避けるためのヒント

  • ベースイメージ: FROM行のイメージがマルチアーキテクチャをサポートしているか常に確認してください。node、python、alpineなどの公式イメージはすでに対応しています。
  • ビルド速度: 大規模なプロジェクトでは、エミュレーションは非常に遅くなります。エンタープライズ環境では、1台のIntelマシンと1台の本物のARMマシンを同じ builderインスタンスに接続して、ネイティブビルドを行う(速度が大幅に向上します)ことがよくあります。
  • バイナリダウンロードのコマンド: tool-linux-x64のような特定のファイルをcurlで直接ダウンロードするのは避けましょう。代わりに、環境変数${TARGETARCH}を使用して、Dockerが適切なダウンロード版を自動的に選択するようにします。

Docker Buildxをマスターすることで、CI/CDプロセスがよりプロフェッショナルになります。互換性の問題を心配することなく、コストを最適化するためにクラウドプロバイダーを柔軟に切り替えることができます。ぜひ試してみてください!

Share: