Lima で vz + rosetta を使って ARM VM 上で x86_64 バイナリを実行する

この記事は、[3-shake Advent Calendar 2023](https://qiita.com/advent-calendar/2023/3-shake) 2日目のエントリ記事です。

2023年10月に Docker Desktop for Apple silicon での Rosetta for Linux が GA となり、Apple Silicon の Mac 上で x86_64 バイナリ実行のパフォーマンスや安定性が向上したとありました。

https://www.docker.com/ja-jp/blog/docker-desktop-4-25/

https://matsuand.github.io/docs.docker.jp.onthefly/desktop/mac/apple-silicon/

Docker Desktop は2021年の有償化以降、[Lima](https://github.com/lima-vm/lima) が代替ソフトウェアのプラットフォームとして広く使われています。Amazon の [Finch](https://github.com/runfinch/finch) や [Rancher Desktop](https://rancherdesktop.io/) の内部でも使われています。

https://aws.amazon.com/jp/blogs/news/introducing-finch-an-open-source-client-for-container-development/

私も Apple silicon の MacBook Air で Lima を docker の実行環境として利用していました。Lima はデフォルトでは qemu で VM の仮想化を行なっていますが、`vmType` に `vz` を指定することで Apple の Virtualization Framework を使うようになっており、さらに rosetta を有効にすることで Docker Desktop のように ARM の VM 上で docker daemon を実行しつつ、x86_64 のコンテナを実行することができます。

https://lima-vm.io/docs/releases/experimental/

## docker daemon 用 VM の定義の作成

`vz` で rosetta を有効にした aarch64 の VM を定義します。

```bash
limactl create --name=default \
   --cpus=2 --memory=2 \
  --vm-type=vz --mount-type=virtiofs --rosetta \
  template://docker
```

`~/.lima/default` 配下に必要なファイルが作成されます

```
$ limactl ls
NAME       STATUS     SSH            VMTYPE    ARCH       CPUS    MEMORY    DISK      DIR
default    Stopped    127.0.0.1:0    vz        aarch64    2       2GiB      100GiB    ~/.lima/default
```

template の一覧は `limactl create --list-templates` で確認できます。Homebrew でインストールしている場合は `ls $(brew --prefix)/opt/lima/share/lima/templates` でファイルも確認できます。

## docker daemon 用 VM の起動

次に `limactl start NAME` (NAME が default の場合は省略可能) を実行することで VM が起動されます。

docker テンプレートを使った場合、次のような出力がされるはずです、`create` は初回のみ実行すればよく、`context use` も `~/.docker/config.json` に書き込まれるので切り替えが不要であれば1度実行しておけば良いです。([docker context](https://docs.docker.com/engine/context/working-with-contexts/))

```
docker context create lima-default --docker "host=unix:///Users/teraoka/.lima/default/sock/docker.sock"
docker context use lima-default
```

これで docker コマンドはこの VM 上の docker daemon と通信することになります。

`lima` コマンドの引数が VM 内で実行されます。`uname -m` で architecture を確認すると `aarch64` で ARM であることが確認できます。

```
$ lima uname -m
aarch64
```

## docker コンテナの実行

先ほどの結果からデフォルトでは arm64 のコンテナイメージをダウンロードして実行しようとします。

```
$ docker run --rm debian uname -m
Unable to find image 'debian:latest' locally
latest: Pulling from library/debian
df2021ddb7d6: Pull complete 
Digest: sha256:133a1f2aa9e55d1c93d0ae1aaa7b94fb141265d0ee3ea677175cdb96f5f990e5
Status: Downloaded newer image for debian:latest
aarch64
```

今度は `--platform=linux/amd64` を指定して実行してみます。

```
$ docker run --rm --platform=linux/amd64 debian uname -m
Unable to find image 'debian:latest' locally
latest: Pulling from library/debian
90e5e7d8b87a: Pull complete 
Digest: sha256:133a1f2aa9e55d1c93d0ae1aaa7b94fb141265d0ee3ea677175cdb96f5f990e5
Status: Downloaded newer image for debian:latest
x86_64
```

指定した image 名は同じですが、platform が異なるため新たにダウンロードされました。`uname -m` コマンドのの出力も `x86_64` となっています。

ダウンロードされている image を確認してみます。

```
$ docker image ls
REPOSITORY   TAG       IMAGE ID       CREATED      SIZE
debian       <none>    a588e7890234   9 days ago   139MB
debian       latest    0ce03c8a15ec   9 days ago   117MB
```

`ls` では architecture が確認できないので `inspect` で確認してみます。

```bash
$ docker image inspect $(docker image ls --format '{{.ID}}') | jq '.[] | {id:.Id[7:19],arch:.Architecture,tag:.RepoTags[0]}' -c     
{"id":"a588e7890234","arch":"arm64","tag":null}
{"id":"0ce03c8a15ec","arch":"amd64","tag":"debian:latest"}
```

## パフォーマンス比較

私は今のところ手元の docker 環境でたいした処理を行わないので誰かやってみてください。
次のようにして複数の VM を作成し、切り替えて試すことができます。

### aarch64 (rosetta 無し)

```bash
limactl create --name=arm64 \
  --arch=aarch64 \
  --cpus=2 --memory=2 \
  --vm-type=vz --mount-type=virtiofs \
  template://docker
```

この環境で `--platform=linux/amd64` を指定してコンテナを実行すると次のようなエラーで実行できないはずです。

```
exec /usr/bin/uname: exec format error
```


### x86_64 (qemu)

```bash
limactl create --name=amd64 \
  --arch=x86_64 \
  --cpus=2 --memory=2 \
  --vm-type=qemu --mount-type=9p \
  template://docker
```

qemu の場合、`mountType` に `virtiofs` を指定することができません。(9p と virtiofs は[以前簡単に比較してみました](https://blog.1q77.com/2022/12/lima-vz/)。)

```
$ limactl ls
NAME       STATUS     SSH            VMTYPE    ARCH       CPUS    MEMORY    DISK      DIR
amd64      Stopped    127.0.0.1:0    qemu      x86_64     2       2GiB      100GiB    ~/.lima/amd64
arm64      Stopped    127.0.0.1:0    vz        aarch64    2       2GiB      100GiB    ~/.lima/arm64
default    Stopped    127.0.0.1:0    vz        aarch64    2       2GiB      100GiB    ~/.lima/default
```