ローカルでビルドしたdockerイメージをECRにあげ、それを使ってECSを更新しようと思ったら、下記のエラーが返却されて失敗した時の話です。
こういうエラーが出て、タスクの起動に失敗したケースです。
Resource handler returned message: "Error occurred during operation 'ECS Deployment Circuit Breaker was triggered'."
環境
- Mac OS 12
- Typescript 4.8.4
- node 18.10.0
- CDK CLI 2.44.0
- 作業者 Docker、AWSたまごクラブ
■ 調査
AWSコンソールにてECS -> 該当のクラスタ -> タスクタブ -> Stoppedを開く。
この中から今回起動してほしかったけど停止してしまったtaskを探します。
コンテナの下の▼から展開すると詳細が見れます。
終了コード(exitCode) 0 なら問題ないですが、今回は1でした。異常終了しています。
「ログ」タブにいくと、そのタスクで生まれたログを見ることができます。
今回のエラー時には下記の内容が出ていました。
exec /opt/java/openjdk/bin/java: exec format error
■ 原因
こちらなどにあるように、実行する環境のCPUアーキテクチャと、イメージのビルド時に設定されたCPUアーキテクチャが違うことによって起きる問題でした。
今回はM1 Macのローカルでビルドしたイメージを使ってECS Fargateで起動しようとしたのですが、CPUアーキテクチャをARMに設定していませんでした。(デフォルトではAMDになります。)
なので「このイメージのフォーマットが、実行環境に適してないので実行できません」と言われている状態です。
■ 対応策
取りうる対応は下記の2つです。
- イメージをAMD用にビルドする
- コンテナ側をARMに変更する
ちなむと今回は2で解決しました。
1.イメージをAMD用にビルドする
ARM環境でAMD向けにイメージをビルドする際は、Dockerfile内で下記の様にplatform指定してやればOKです。
FROM --platform=linux/x86_64 eclipse-temurin:17-jdk-jammy AS build
ただ、自分の環境ではめちゃくちゃdocker buildに時間がかかってビルドできなかったので断念しました。
(試したプロジェクトではgradleのcleanやbuildをさせているのですが、starting gradle demonから10分15分待っても進まない…という状態でした。)
先達によると「異なるプラットフォーム向けのビルドはかなりの時間がかかる」とのことなので、ここの回避策はないようでした。
永遠のときをかければビルドできるかもしれないですが、カジュアルにビルドしたい気持ちがあるので、こちらの方法は断念しました。
余談)ビルドに時間がかかることに関して試行錯誤したメモ
□ Docker Desktopの確認
Docker DesktopにはApple Silliconチップ向けのものと、Intelチップ向けのものが用意されています。
そもそもここで異なるものをインストールしていると、Docker Desktop自体がちゃんと動かないので確認しました。
念の為インストールし直してみましたが、特に変化なしでした。
□ Rosetta 2のインストール
Dockerのドキュメントを見ていると、必須ではないがAMD用にビルドする等一部の機能を使用するには必要となるコマンドラインツールとしてRosetta 2の説明がされています。
最高の体験を得るには、Rosetta 2 のインストールを推奨します。
とのこと。
なのでドキュメントに従ってRosetta 2も入れて、platformオプションをつけたりしつつ試してみましたが、こちらも特に変化なしでした。
□ archコマンドでの実行
archコマンド経由で実行するというのも試しましたが、arch -x86_64 docker build ...
としたときにarch: posix_spawnp: docker: Bad CPU type in executable
と怒られてしまい、そもそも実行できませんでした。
2.コンテナ側をARM環境で実行する様に変更する
CDKでは下記のようにタスク定義にてCPUアーキテクチャを指定できます。
new TaskDefinition(this, `taskDef`, {
// 〜中略〜
runtimePlatform: {
cpuArchitecture: CpuArchitecture.of('ARM64')
}
});
こんな感じで指定して、環境側を変えちゃうことでM1 Macのローカルでビルドしたイメージでも動かすことができます。
尚、これをする場合、ARM64を指定していないタスク定義はinactiveにしておかないと、同じ理由で過去のタスク定義では動かない事象が発生するので注意しましょう。
またタスク定義とserviceが別スタックになっていたり、CDKでの更新以外でタスク定義が更新され売る場合、
CDKデプロイ失敗時のロールバックで少しややこしい事になりうるのでそこも注意です。
具体的には下記のような理由でECSのインスタンス起動が永遠に失敗する可能性があります。
- ロールバック先のスタック定義で使用されているタスク定義が存在しない
- ロールバック先のスタック定義ではタスク定義内で指定されているDockerイメージのCPUアーキテクチャとECSのCPUアーキテクチャが異なる
■ まとめ
以上、DockerイメージのCPUアーキテクチャ違いで困った話でした。
手段としては下記の2つがあって、今回は2を選びました。
- イメージをAMD用にビルドする
- コンテナ側をARMに変更する
今回はとりあえずイメージを上げて動作確認できればいいので2の手段を取りましたが、
「別にコンテナ側をARM64で動かしたくはない」という場合は解決になっていないので、別のマシンでビルドするなどして1の方向でなんとかするしかないです。