M1 Pro + Docker for Macが遅い

M1とDocker for Macの組み合わせが遅い[^1]。遅すぎる。ただでさえDocker for Macは遅いのにM1と組み合わせることで更に悪化する。大枚はたいて買ったM1 Proでアプリケーションのベンチマークとったら (3年前の) Intel Macbook Proの構成より遅くて衝撃を受けた。そしてDocker for Macを削除した。

# 検討したツール

dockerがないと仕事中やることないので代替ツールを探した。

|ツール名|仕組み|メリット|デメリット|Watch|Star|Issues|
|-|-|-|-|-|-|-|
|[Podman](https://github.com/containers/podman)|デーモンを必要とせず、コンテナを安全に稼働させることができる。|インストールが容易。|一部のdockerコマンド (networkなど) をサポートしていない。|178|11.2k|190|
|[Minikube](https://github.com/kubernetes/minikube)| HyperkitやVirtualBoxの中で動かす (別途セットアップが必要)。| Kubernetesを動かすことができる。|KubernetesはDockerを非推奨化してるので将来的には使えなくなるかも。|468|22.5k|607|
|[Multipass](https://github.com/canonical/multipass)|デフォルトでHyperKit、QEMU/KVMなどのハイパーバイザー経由で起動できる。|クロスプラットフォーム (Linux/Mac/Windows) に対応。 |(未調査)|77|3.8k|327|
|[Lima](https://github.com/lima-vm/lima)|Docker CLI + Docker REST APIでLima (VM) 上のDocker Engineを操作する。|Docker for Macに近い構成。|セットアップに少し手間がかかる。|48|5.1k|62|

今困っているのはローカル開発環境、かつ従来とセットアップ構成をなるべく変えたくないという理由から、比較的実績のあるLimaを検証対象とした。

# 調査

セットアップ方法は割愛。検証環境は次の通り。

* Docker Desktop 4.2.0
* Docker version 20.10.10
* 検証マシン
    * MacBook Pro 2019 16GB RAM 8 Intel Core i9 2.4Ghz (16インチ)
    * MacBook Pro 2021 16GB RAM 10 M1 Pro 10 Core (14インチ)

## ネイティブ実行

普段からよく使うTerraformのplan実行速度を比較した。操作対象リソースはAmazon ECSのサービス構築。評価対象リソースは約30ほど。

まずはネイティブでplan実行した結果。

* Intel Core i9
    * 9.761s
* Apple M1 Pro
    * 9.302s

僅差だけどこれは想定内。

## Docker for Mac

次に [hashicorp/terraform](https://hub.docker.com/r/hashicorp/terraform/) を利用してDocker for Macからplanを実行。

* Intel Core i9
    * 13.898s
* Apple M1 Pro
    * 33.557s

遅い。特にM1。

ただしこれには理由があって、hashicorpが公開しているイメージは現状AMD64版しかないため、Dockerビルド時に `--platform linux/amd64` を指定してエミュレートした。

* [Provide linux arm64 docker image](https://github.com/hashicorp/terraform/issues/29895)

試しに次のようなDockerfileを書いて自前ビルド (ARMビルド) したら15.980sまで短くできた。それでもIntelより遅い。

```dockerfile:Dockerfile
FROM golang:alpine

ENV TERRAFORM_VERSION=1.0.11

RUN apk add --update git bash openssh

ENV TF_DEV=true
ENV TF_RELEASE=true

WORKDIR $GOPATH/src/github.com/hashicorp/terraform
RUN git clone https://github.com/hashicorp/terraform.git ./ && \
    git checkout v${TERRAFORM_VERSION} && \
    /bin/bash scripts/build.sh

WORKDIR $GOPATH
ENTRYPOINT ["terraform"]
```

## Limaで実行

結果。

* Intel Core i9
    * 13.780s
* Apple M1 Pro
    * 9.733s

Intel版はDocker for Macとさほど実行速度は変わらないけど、M1 Proだとネイティブに近い速度までパフォーマンスが改善した。
後でググったら同じようなことが [Improve Docker performance on macOS by 20x](https://pen.so/2021/09/02/docker-on-macos/) に書かれてた。*"I ran benchmarks so you don’t have to."* と書かれてる。車輪の再発明感。

結果をまとめると下表のようになる。

|実行方法|CPU|Docker platform|Total|
|-|:-:|:-:|-:|
|Native|Intel Core i9||9.761s|
||Apple M1 Pro||9.302s|
|Docker for Mac|Intel Core i9|amd64|13.898s|
||Apple M1 Pro|amd64|33.557s|
||Apple M1 Pro|arm64|15.980s|
|Lima|Intel Core i9|amd64|13.780s|
||Apple M1 Pro|arm64|9.733s|

今回の検証ではDocker for Macの比較でIntelよりM1の方が1.14倍遅い結果に [^2]。
以前よりDocker for MacはファイルI/Oの問題が指摘されてたが、M1版はエミュレータにQemuが使用されているため余計遅くなってるという話も出てる [^3]。

[Hacker News](https://news.ycombinator.com/item?id=28401385)

> M1 was running arm images, but as far as I know Docker Desktop on M1 use Qemu which is super slow anyway.

今回はLimaで検証したが、恐らく他のツールでも同様の結果になると思われる。

# 結論

`brew uninstall docker --cask`

# トラブルシューティング

Limaを構築する上でハマったポイント。

### Lima起動時にFATA[0075] exiting, status={Running:false Degraded:false Exiting:true Errors:[] SSHLocalPort:0} (hint: see "/Users/xxx/.lima/default/ha.stderr.log")エラー

`/Users/xxx/.lima/default/ha.stderr.log` を確認すると、`Could not set up host forwarding rule 'tcp:127.0.0.1:60022-:22`というエラーが残っていた。ポート関連かと思い使用中のポート一覧を確認するとqemuが利用していた。
恐らくLimaの検証で起動や削除を繰り返していて残骸が残っていたものと思われる。

```zsh
COMMAND     PID     USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
qemu-syst 10256 naomichi   19u  IPv4 0xc863156b9180ca1d      0t0  TCP localhost:60022 (LISTEN)
qemu-syst 10256 naomichi   24u  IPv4 0xc863156b9180f4dd      0t0  TCP localhost:60022->localhost:50701 (ESTABLISHED)
qemu-syst 10256 naomichi   64u  IPv4 0xc863156b95a34a1d      0t0  TCP localhost:60022->localhost:59842 (ESTABLISHED)
ssh       10268 naomichi    3u  IPv4 0xc863156b91815f5d      0t0  TCP localhost:50701->localhost:60022 (ESTABLISHED)
ssh       31247 naomichi    3u  IPv4 0xc863156b959fd4bd      0t0  TCP localhost:59842->localhost:60022 (ESTABLISHED)

```

対象プロセスをKillして解決した。

### Docker実行時にUnable to write the module manifest file: open xxx: read-only file systemエラー

マウントしたディレクトリはデフォルトで読み取り専用になる。Lima設定ファイルの `mounts` に書き込み可能なパスを追加して再起動すれば良い。

```yml:~/.lima/default/lima.yaml
mounts:
  - location: "/foo/bar"
    writable: true
```
### Docker実行時にerror getting credentials - err: exec: "docker-credential-desktop": executable file not found in $PATH, out: `` エラー

`~/.docker/config.json` を削除。

```zsh
$ rm ~/.docker/config.json
```

### ネットワーク通信が不安定になる

コンテナの内部からインターネットへの通信 (APIの呼び出しなど) を繰り返してると、通信が不安定になることがあった。LimaはデフォルトでホストのDNSを参照するが、明示的にDNSを指定することで安定した。

```yml:~/.lima/default/lima.yaml
useHostResolver: false

dns:
  - 1.1.1.1
  - 1.0.0.1
```

### ソケット通信を行うアプリケーションが起動しない

Rails起動時に `Errno::EPERM: Operation not permitted - connect(2) for /app/tmp/sockets/puma.sock` といったエラーが発生した。どうやら現時点 (2021年11月現在) ボリュームマウントで共有したディレクトリ内でsockファイルは配置できない模様。

[Does volume mounting support sockets, for example mounting docker.sock (docker runtime) for use with Portainer](https://github.com/lima-vm/lima/discussions/422)

`puma.sock` ファイルをマウント領域の外に配置することでRailsを起動することができた。

```rb:config/puma.rb
# /appはマウントディレクトリに指定しており、起動しようとするとエラーが出る
# bind "unix:///app/tmp/sockets/puma.sock"
bind "unix:///var/run/puma.sock"
```

### ホスト側のファイル変更がコンテナに反映されるのに時間がかる

バインドマウントでホスト側のディレクトリをコンテナと共有する際、ホスト側で変更した変更がコンテナへ反映されるまでに1-2秒かかることがある。Limaはファイル同期の仕組みにreverse sshfsが使われており、これが問題とされてる。今後9pかsambaへの移行が検討されており、現状は未解決。


[Filesystem Sharing](https://github.com/lima-vm/lima/issues/20)

[^1]: delegated、docker-sync、Named Volume、gRPC FUSE...色々試したけどさほど変わらず。
[^2]: 前述の記事ではRailsでのテスト実行で1.51倍もの差が出ている。
[^3]: Docker for Macは2021年3月のリリースでバックエンドがAppleのVirtualizationフレームワークからQemuベースに変更された。