Windows(x64)上でArm版Dockerを起動してAArch64のCodeBuildコンテナをローカル実行
TL;DR
ビルドロジックの確認程度の動作ならArmマシンを立ち上げてのDocker使用は不要。
ホストがx64でもqemu-user-staticをインストールし、codebuild_build.sh
でAArch64のCodeBuildイメージを指定するだけで良い。
以下はホストをArm64(AArch64)とするための内容となる。
- QEMUでArm64版Ubuntu(Cloud image版)を動かす
-
-machine
はvirt-9.0
以上を指定する (CodeBuild用Amazon Linux向け) - Arm版Ubuntu内のDockerでコンテナを実行する
背景
ビルドコマンドのロジック確認程度の動作なら、ホストマシンがWindowsのx64アーキテクチャであってもWSL(Ubuntu)でsudo apt install -y qemu-user-static
でqemu-user-staticをインストールした後、
codebuild_build.sh
での起動イメージ-i
で
public.ecr.aws/codebuild/amazonlinux2-aarch64-standard:3.0-2024.03.21
といったようにAArch64のリポジトリのイメージタグを指定するだけでも一応動作はするようで、速度はある程度遅くなるが手間などを考えればこうするだけでも十分と思われる。
ただしアーキテクチャ差異による副作用が発生するリスクは当然あるので、必ずしも正しい動作やビルドオブジェクトの起動が保証できるわけではない。
- x64環境上での単体での起動例アーキテクチャはAArch64と認識できている。
docker run \ --rm \ -it \ --entrypoint bash \ public.ecr.aws/codebuild/amazonlinux2-aarch64-standard:3.0-2024.03.21 \ -vc \ "uname -a" WARNING: The requested image's platform (linux/arm64/v8) does not match the detected host platform (linux/amd64/v3) and no specific platform was requested uname -a Linux *** *** ** SMP *** *** ** **:**:** UTC **** aarch64 aarch64 aarch64 GNU/Linux
そこで、ホストもArm64とさせるためにQEMUを用いてみる。
QEMUはWindows版でも良いが、起動ログ出力ターミナルのLinux向けフォント指定コマンドがコマンドプロンプトで解釈されず、かなり読みにくくなるので体感でもそれほど速度が変わらなかったWSL(Ubuntu)でLinux版を起動する。
ただしQEMUで別アーキテクチャのマシンを起動するとかなり遅くなるので、ホストのアーキテクチャ依存なエラーが発生して先に進めないようなビルドコマンドを記述している
などといったよほどの事情がない限り、qemu-user-static
を使用する方法で良いと考えている。
内容
Arm64対応QEMUのインストール
もしsudo apt install -y qemu-system-arm
でインストールした場合、
qemu-system-aarch64 -machine help
コマンドでvirt-9.0 QEMU 9.0 ARM Virtual Machine
がなければDocker版のCodeBuild用Amazon Linuxが動かない(補足)ので、自前でビルドする必要がある。
※CodeBuildではないコンテナはvirt-7.2
等でも動くものもある。
以下のような場合はCodeBuild用Amazon Linux向けに自前ビルドが「必要」
qemu-system-aarch64 -machine help
・・・
virt-7.0 QEMU 7.0 ARM Virtual Machine
virt-7.1 QEMU 7.1 ARM Virtual Machine
virt QEMU 7.2 ARM Virtual Machine (alias of virt-7.2)
virt-7.2 QEMU 7.2 ARM Virtual Machine
witherspoon-bmc OpenPOWER Witherspoon BMC (ARM1176)
・・・
以下のような場合はCodeBuild用Amazon Linux向けの自前ビルドは「不要」
qemu-system-aarch64 -machine help
・・・
virt-7.2 QEMU 7.2 ARM Virtual Machine
virt-8.0 QEMU 8.0 ARM Virtual Machine
virt-8.1 QEMU 8.1 ARM Virtual Machine
virt-8.2 QEMU 8.2 ARM Virtual Machine
virt QEMU 9.0 ARM Virtual Machine (alias of virt-9.0)
virt-9.0 QEMU 9.0 ARM Virtual Machine
witherspoon-bmc OpenPOWER Witherspoon BMC (ARM1176)
・・・
自前ビルド手順
基本は以下のページの通り。
-
作業ディレクトリ作成
commandmkdir -p ./qemu_build cd ./qemu_build
-
ソースコードの取得
commandwget https://download.qemu.org/qemu-9.0.0-rc1.tar.xz tar xvJf qemu-9.0.0-rc1.tar.xz cd ./qemu-9.0.0-rc1
リンク切れやビルドがうまくいかない場合は最新のコードをgitリポジトリから取得。
commandgit clone https://gitlab.com/qemu-project/qemu.git . \ --depth 1 git submodule init git submodule update \ --recursive \ --depth 1
-
ビルド
ホスト環境を汚したくない場合はコンテナに入ってビルド。commanddocker run \ --rm \ -it \ -v ./:/work \ -w /work \ ubuntu:22.04 \ bash
必要なパッケージなどをインストール。
commandapt update apt install -y \ python3 \ python3-pip \ libglib2.0-dev \ git pip3 install \ sphinx==5.3.0 \ sphinx_rtd_theme==1.1.1 \ ninja==1.11.1.1
ターゲットを指定して
configure
実行。./configure \ --target-list=aarch64-softmmu \ --enable-slirp
--target
指定しないと全アーキテクチャがフルビルドされる。
ビルド実行。commandmake -j8
-j
は指定しないと並列処理されないのでかなり時間がかかる。
数字はマシンのCPU数に合わせる。ビルドが成功すると
./build/qemu-system-aarch64
が生成される。
コンテナに入ってビルドした場合は抜ける。commandexit
-
qemu-system-aarch64
の配置
すでにqemu-system-aarch64
がある場合は移動させておく。commandsudo mv "$(which qemu-system-aarch64)" \ "$(dirname "$(which qemu-system-aarch64)")/qemu-system-aarch64.old"
commandsudo cp ./build/qemu-system-aarch64 /usr/local/bin sudo chmod +x /usr/local/bin/qemu-system-aarch64
-
対応マシンの確認
virt-9.0
以上があることを確認する。qemu-system-aarch64 -machine help ・・・ virt QEMU 9.0 ARM Virtual Machine (alias of virt-9.0) virt-9.0 QEMU 9.0 ARM Virtual Machine ・・・
version `SLIRP_4.7' not foundエラーが発生する場合
gitで取得したソースからコンテナでビルドした場合などで、環境によってはqemu-system-aarch64
実行時に以下のようなエラーが出る場合がある。
qemu-system-aarch64: /lib/x86_64-linux-gnu/libslirp.so.0: version `SLIRP_4.7' not found (required by qemu-system-aarch64)
libslirp
の特定バージョン(上記は4.7.0)をビルドしてインストールする必要がある。
コンテナ内でビルドしてもインストールがうまくいかないので、下記ページの通りホスト環境で各種パッケージを入れてビルドする。
wget https://gitlab.freedesktop.org/slirp/libslirp/-/archive/v4.7.0/libslirp-v4.7.0.tar.bz2
tar jxvf ./libslirp-v4.7.0.tar.bz2
cd ./libslirp-v4.7.0
mkdir ./build
cd ./build
sudo apt update
sudo apt install -y meson libglib2.0-dev
meson setup --prefix=/usr --buildtype=release ..
ninja
ninja install
QEMU向けArm64版Ubuntuの準備
以下参考。
-
イメージのダウンロード
commandwget https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-arm64.img
※このimgファイルはインストーラーの役割だけでなくそのまま仮想マシンの使用ディスクとして消費されていくので、複数環境を作る場合ややり直しをしたい場合はオリジナルを複製しておく。
-
EFIボリューム作成
commandwget https://releases.linaro.org/components/kernel/uefi-linaro/latest/release/qemu64/QEMU_EFI.fd dd if=/dev/zero of=flash0.img bs=1M count=64 dd if=QEMU_EFI.fd of=flash0.img conv=notrunc dd if=/dev/zero of=flash1.img bs=1M count=64
-
設定ファイルの準備
ユーザー名はデフォルトのubuntu
。
パスワードはubuntu
。commandsudo apt install -y cloud-image-utils echo "#cloud-config password: ubuntu chpasswd: { expire: False } ssh_pwauth: True" >user-data cloud-localds --disk-format qcow2 ./cloud.img ./user-data
-
仮想マシン用ディスクのリサイズ
適当に大きめのサイズにしておく。commandqemu-img resize ./jammy-server-cloudimg-arm64.img 40G
即時でサイズが変わるわけではなく可変で使用した分だけ増える。後から再拡張することも可能。
QEMU向けArm64版Ubuntuの起動
以下のようなスクリプトファイルを作成する。SSHはローカルの22222
にマッピング。
-machine
はvirt-8.2
でも良いかもしれないが未確認。
#!/bin/bash
set -e
qemu-system-aarch64 \
-m 4096 \
-smp 8 \
-cpu cortex-a710 \
-machine virt-9.0 \
-nographic \
-drive file=flash0.img,format=raw,if=pflash \
-drive file=flash1.img,format=raw,if=pflash \
-drive if=none,file=jammy-server-cloudimg-arm64.img,id=hd0 \
-device virtio-blk-device,drive=hd0 \
-drive if=none,id=cloud,file=cloud.img \
-device virtio-blk-device,drive=cloud \
-device virtio-net-device,netdev=user0 \
-netdev user,id=user0,hostfwd=tcp::22222-:22
起動
bash ./run.sh
※ここから先は処理が重く・遅くなるので注意。
初回起動時は以下のようなログが出てからSSHでログインできるようになる。
[ OK ] Finished Apply the settings specified in cloud-config.
Starting Execute cloud user/final scripts...
[ 398.467441] cloud-init[1421]: Cloud-init v. 23.4.4-0ubuntu0~22.04.1 running 'modules:final' at ***, ** *** **** **:**:** +****. Up 397.37 seconds.
ci-info: no authorized SSH keys fingerprints found for user ubuntu.
<14>*** ** **:**:** cloud-init: #############################################################
<14>*** ** **:**:** cloud-init: -----BEGIN SSH HOST KEY FINGERPRINTS-----
<14>*** ** **:**:** cloud-init: 1024 SHA256:*** root@ubuntu (DSA)
<14>*** ** **:**:** cloud-init: 256 SHA256:*** root@ubuntu (ECDSA)
<14>*** ** **:**:** cloud-init: 256 SHA256:*** root@ubuntu (ED25519)
<14>*** ** **:**:** cloud-init: 3072 SHA256:*** root@ubuntu (RSA)
<14>*** ** **:**:** cloud-init: -----END SSH HOST KEY FINGERPRINTS-----
<14>*** ** **:**:** cloud-init: #############################################################
-----BEGIN SSH HOST KEY KEYS-----
***
-----END SSH HOST KEY KEYS-----
[ 403.145587] cloud-init[1421]: Cloud-init v. 23.4.4-0ubuntu0~22.04.1 finished at ***, ** *** **** **:**:** +****. Datasource DataSourceNoCloud [seed=/dev/vda][dsmode=net]. Up 402.97 seconds
[ OK ] Finished Execute cloud user/final scripts.
[ OK ] Reached target Cloud-init target.
このログを出力しているターミナルからもログインできるが、入力がおかしくなるので別途ターミナルやコマンドプロンプトを立ち上げてSSHログインして操作することを推奨。
Arm64版UbuntuにDockerをインストール
通常のDockerインストールと同じ。
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USER
ターミナルを一回終了して再度ログインし、docker
コマンドが正しく実行されるかを確認する。
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS
AArch64向けCodeBuildとlocal-builds、ビルドスクリプトを取得
ここからが本題。以下を実行してイメージを取得する。
docker pull public.ecr.aws/codebuild/amazonlinux2-aarch64-standard:3.0-2024.03.21
docker pull public.ecr.aws/codebuild/local-builds:aarch64
wget https://raw.githubusercontent.com/aws/aws-codebuild-docker-images/master/local_builds/codebuild_build.sh
環境によっては完了までに2,3時間以上かかる場合もあるが、ネットワーク・ディスクエラーや空き容量の圧迫等が発生しない限りは成功するはずなので終わるまで待つ。
local-buildsの修正
起動時に以下と同じエラーが出る場合はlocal-buildsを修正する必要がある。
FROM public.ecr.aws/codebuild/local-builds:aarch64
COPY "./docker-compose.yml" \
"/LocalBuild/agent-resources/docker-compose.yml"
COPY "./docker-compose-mount-src-dir.yml" \
"/LocalBuild/agent-resources/docker-compose-mount-src-dir.yml"
export LOCALBUILD_CUSTOM_IMAGE_TAG="my-local-builds:aarch64" && \
export LOCALBUILD_WORK_DIR="/workdir" && \
docker run \
-it \
-v ./:"${LOCALBUILD_WORK_DIR}" \
--rm \
--entrypoint=/bin/bash \
public.ecr.aws/codebuild/local-builds:aarch64 \
-c "curl \
-L \
-o /usr/bin/yq \
https://github.com/mikefarah/yq/releases/latest/download/yq_linux_arm64 2>/dev/null&& \
chmod +x /usr/bin/yq && \
cp /LocalBuild/agent-resources/{docker-compose.yml,docker-compose-mount-src-dir.yml} ${LOCALBUILD_WORK_DIR} && \
for file in \"docker-compose.yml\" \"docker-compose-mount-src-dir.yml\"; do \
yq -i \
'.version=\"3\" | \
.services.build.environment[0] = \"NO_PROXY=agent:3000\" |
.services.build.environment[2] = \"CODEBUILD_AGENT_PORT=http://agent:3000\" |
del(.services.build.links)' \
${LOCALBUILD_WORK_DIR}/\${file}; \
done" && \
docker build -t "${LOCALBUILD_CUSTOM_IMAGE_TAG}" . && \
sed -i "s/public\.ecr\.aws\/codebuild\/local-builds:latest/${LOCALBUILD_CUSTOM_IMAGE_TAG}/g" ./codebuild_build.sh
CodeBuildのローカル実行
- ビルド対象と
buildspec.yml
の作成
適当に作成。commandmkdir -p ./src echo "Hello world" >./src/source.txt
src
以下にbuildspec.yml
を作成。./src/buildspec.ymlversion: 0.2 env: variables: aaaa: "bbbb" phases: build: commands: - echo "${aaaa}" - uname -m - cat ./source.txt - echo "Build Test" >./result.txt artifacts: files: - ./result.txt
- 実行command
bash ./codebuild_build.sh \ -i public.ecr.aws/codebuild/amazonlinux2-aarch64-standard:2.0-2024.03.21 \ -s ./src \ -a ./results \ -l my-local-builds:aarch64
-l
を指定をしないと内側のagentが正しく起動しない。 - 実行結果x64版と同様に動作し、
Removing network agent-resources_default WARNING: Network agent-resources_default not found. Removing volume agent-resources_source_volume WARNING: Volume agent-resources_source_volume not found. Removing volume agent-resources_user_volume WARNING: Volume agent-resources_user_volume not found. Creating network "agent-resources_default" with the default driver Creating volume "agent-resources_source_volume" with local driver Creating volume "agent-resources_user_volume" with local driver Creating agent-resources_build_1 ... done Creating agent-resources_agent_1 ... done Attaching to agent-resources_build_1, agent-resources_agent_1 agent_1 | [Container] ****/**/** **:**:** Waiting for agent ping agent_1 | [Container] ****/**/** **:**:** Waiting for DOWNLOAD_SOURCE agent_1 | [Container] ****/**/** **:**:** Phase is DOWNLOAD_SOURCE agent_1 | [Container] ****/**/** **:**:** CODEBUILD_SRC_DIR=/codebuild/output/***/src agent_1 | [Container] ****/**/** **:**:** YAML location is /codebuild/output/srcDownload/src/buildspec.yml agent_1 | [Container] ****/**/** **:**:** Processing environment variables agent_1 | [Container] ****/**/** **:**:** Moving to directory /codebuild/output/***/src agent_1 | [Container] ****/**/** **:**:** Registering with agent agent_1 | [Container] ****/**/** **:**:** Phases found in YAML: 1 agent_1 | [Container] ****/**/** **:**:** BUILD: 4 commands agent_1 | [Container] ****/**/** **:**:** Phase complete: DOWNLOAD_SOURCE State: SUCCEEDED agent_1 | [Container] ****/**/** **:**:** Phase context status code: Message: agent_1 | [Container] ****/**/** **:**:** Entering phase INSTALL agent_1 | [Container] ****/**/** **:**:** Phase complete: INSTALL State: SUCCEEDED agent_1 | [Container] ****/**/** **:**:** Phase context status code: Message: agent_1 | [Container] ****/**/** **:**:** Entering phase PRE_BUILD agent_1 | [Container] ****/**/** **:**:** Phase complete: PRE_BUILD State: SUCCEEDED agent_1 | [Container] ****/**/** **:**:** Phase context status code: Message: agent_1 | [Container] ****/**/** **:**:** Entering phase BUILD agent_1 | [Container] ****/**/** **:**:** Running command echo "${aaaa}" agent_1 | bbbb agent_1 | agent_1 | [Container] ****/**/** **:**:** Running command uname -m agent_1 | aarch64 agent_1 | agent_1 | [Container] ****/**/** **:**:** Running command cat source.txt agent_1 | Hello world agent_1 | agent_1 | [Container] ****/**/** **:**:** Running command echo "Build Test" >result.txt agent_1 | agent_1 | [Container] ****/**/** **:**:** Phase complete: BUILD State: SUCCEEDED agent_1 | [Container] ****/**/** **:**:** Phase context status code: Message: agent_1 | [Container] ****/**/** **:**:** Entering phase POST_BUILD agent_1 | [Container] ****/**/** **:**:** Phase complete: POST_BUILD State: SUCCEEDED agent_1 | [Container] ****/**/** **:**:** Phase context status code: Message: agent_1 | [Container] ****/**/** **:**:** Expanding base directory path: . agent_1 | [Container] ****/**/** **:**:** Assembling file list agent_1 | [Container] ****/**/** **:**:** Expanding . agent_1 | [Container] ****/**/** **:**:** Expanding file paths for base directory . agent_1 | [Container] ****/**/** **:**:** Assembling file list agent_1 | [Container] ****/**/** **:**:** Expanding result.txt agent_1 | [Container] ****/**/** **:**:** Found 1 file(s) agent_1 | [Container] ****/**/** **:**:** Preparing to copy secondary artifacts agent_1 | [Container] ****/**/** **:**:** No secondary artifacts defined in buildspec agent_1 | [Container] ****/**/** **:**:** Phase complete: UPLOAD_ARTIFACTS State: SUCCEEDED agent_1 | [Container] ****/**/** **:**:** Phase context status code: Message: agent-resources_build_1 exited with code 0 Aborting on container exit...
./results/artifacts.zip
が生成されて中にアーティファクトがあることが確認できる。cd ./results sudo unzip ./artifacts.zip cat ./result.txt Build Test
補足
virt-7.2
でのエラー
-machine
にvirt-7.2
を指定してUbuntuを起動した場合はCodeBuild実行時に以下のようなエラーとなる。
Fatal glibc error: This version of Amazon Linux requires a newer ARM64 processor
compliant with at least ARM architecture 8.2-a with Cryptographic
extensions. On EC2 this is Graviton 2 or later.
Windows版QEMUでのArm64版Ubuntu起動スクリプト
run.shと同じようなバッチファイルで起動できる。
ただし各種imgファイルをWindowsプラットフォームで作成する方法は未確認なので、そこだけはWSLやDocker、Ubuntu等で作成すれば良いと思われる。
qemu-system-aarch64 ^
-m 4096 ^
-smp 8 ^
-cpu cortex-a710 ^
-machine virt-9.0 ^
-nographic ^
-drive file=flash0.img,format=raw,if=pflash ^
-drive file=flash1.img,format=raw,if=pflash ^
-drive if=none,file=jammy-server-cloudimg-arm64.img,id=hd0 ^
-device virtio-blk-device,drive=hd0 ^
-drive if=none,id=cloud,file=cloud.img ^
-device virtio-blk-device,drive=cloud ^
-device virtio-net-device,netdev=user0 ^
-netdev user,id=user0,hostfwd=tcp::22222-:22
Podmanについて
Dockerの代わりにPodmanで--arch aarch64
を指定すればできるのでは?というコメントも見受けられたが、内容を把握していないので今回は検証対象外としている。
結論
- 動きはするが、とにかく重い・遅いし時間がもったいないのであくまで暫定的な検証環境として扱ったほうが良い。
- Armマシンを立ち上げずに
qemu-user-static
を使用する方法のほうが遅くならないし簡単。 - どうしてもArmマシンで動かしたいなら、おとなしく同アーキテクチャのラズベリーパイやM1/M2 Mac等を購入してそちらで動かすことを推奨。
Discussion