MacでDocker Desktopを使う際にパフォーマンスを簡単に上げる方法やDocker Desktopを使わない方法を探してみた

### 環境
- macOS Monterey
- Intel Mac
### 要件
- [Homebrew](https://brew.sh/index_ja)インストール済み（後半のDocker Desktopを使わない方法を試す場合）
- Pythonのプログラムが実行可能（ベンチマークを動かしたい場合のみ）

## はじめに
MacでDockerを使う時にまず考えられる方法として、Docker Desktopがありますよね。
https://www.docker.com/products/docker-desktop/  

インストールして起動するだけでいいので、簡単に環境構築できます。
（Docker Desktopのインストールについては他のところを見てください。）

「じゃあDocker Desktop使えばいいじゃん」となりますが、Docker Desktopには以下の問題があります。
- 商用利用が有料になった
  2021年に有料化され、月5ドル払う必要が出てきました。ただし、個人利用もしくはスモールビジネス（従業員数250人未満かつ年間売上高1000万ドル未満）であれば無料です。
- そもそも遅い
  「Docker Desktop 遅い」で調べると色々と悩んでる人の記事が出てきます。

前半はDocker Desktopを使って早くする方法、後半はDocker Desktopを使わずにLimaというものを用います。特にDocker Desktopを使わない方法は商用利用だろうが無料で試せますし、M1 Macで試した際も問題なく動きました。

## Docker Desktopが遅いことの確認
特にファイルIOが遅く、これが原因で色々な動作が遅いと感じます。本記事で雑に下記のベンチマークを使用しました。やっていることは5KBのファイルを作ってそれを消すということを繰り返しているだけです。

ソースコードやDockerfile等はGitHubで公開しています。
https://github.com/nube266/docker_benchmark

```python
# check_speed_test.py
import numpy # pip install numpyで入れてください
import os
import tqdm # pip install tqdmで入れてください
import time

start_time = time.time()

print("\n")
for i in tqdm.tqdm(range(2000)):
    with open('./test.bin', "wb") as file:
        file.write(numpy.random.bytes(5000))
    os.remove('./test.bin')

print("time: {0}\n".format(time.time() - start_time))
```

筆者のホスト環境でやるとこのようになりました。
```bash
$ python scripts/check_speed_test.py 
100%|█████████████████████████████████████████████████████████| 2000/2000 [00:03<00:00, 537.07it/s]
time: 3.8640241622924805
```

これを何も考えずにDocker Desktopで作成したコンテナ上で使用するとこうなりました。
```bash
$ python check_speed_test.py 
100%|█████████████████████████████████████████████████████████| 2000/2000 [00:10<00:00, 185.14it/s]
time: 10.850170373916626
```

4秒で済んでいた処理が11秒程度かかるようになりました。処理が２、３倍遅くなるのは正直許容したくないので、なんとかする方法を考えてみました。

## Docker Desktopの設定でなんとかしてみる
色々と方法はあると思いますが、いくつか実際に試してみました。

### VirtioFSを設定する
Experimental Featuresとして、VirtioFSというものが追加されました。これはホストとコンテナ間のファイル共有の無駄を削減するオプションです。設定の仕方は下記の画像を参考にしてください。

[Preferences...] ->　[Experimental Features]タブ -> [Enable VirtioFS accelerated directory sharing]にチェック  
![スクリーンショット 2022-06-27 0.04.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/350995/d56b5b33-17ec-0329-16e3-4b9e54d2f6b7.png)
#### 注意事項
- 実験的機能なため、場合によっては不具合が起きます
  （MySQL使用時に不具合が起きると聞いた）
- macOSのバージョンが古いとチェック出来ません
  （macOS 12.3~）

これでどのくらい早くなるのか試してみたら以下のようになりました。
```bash
$ python check_speed_test.py 
100%|█████████████████████████████████████████████████████████| 2000/2000 [00:10<00:00, 199.32it/s]
time: 10.139082908630371
```
ちょっと早くなりました。今回はベンチマークが悪かった可能性もあるため、一考の余地があります。

### メモリサイズを変えてみる
Dockerに割り当てるメモリサイズは設定で変更可能です。まずは自分の設定を確認してみましょう。確認する場所は下記の通りです。
[Preferences...] ->　[Resources]タブ -> [Memory]
![スクリーンショット 2022-06-27 0.18.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/350995/2331d482-d3ce-9acc-6cb9-c234bbcb0253.png)

ここが少ない場合は増やしてみましょう。筆者の実験環境はメモリ16GBなので、メモリサイズ8GBにしています。これを例えば1GBにしてベンチマークを試すと下記のようになります。

```bash
$ python check_speed_test.py 

100%|█████████████████████████████████████████████████████████| 2000/2000 [00:11<00:00, 176.62it/s]
time: 11.422436237335205
```

遅くなっていますね。自分の用途とPCのスペックに合わせて調整してみると早くなるかもしれないです。

### Use gRPC FUSE for file sharingのチェックを外す
ファイルシステムとして現在はデフォルトでgRPC FUSEが使われています。これを従来使われていたosxfsに戻すという方法です。

[Preferences...] ->　[General]タブ -> [Use gRPC FUSE for file sharing]のチェックを外す
![スクリーンショット 2022-06-28 1.19.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/350995/596085dd-e297-3f2f-7c7d-083fe263782c.png)

「新しい方が良いんじゃないの？」と思うかもしれませんが、実際にはファイルIOが早くなる場合があります。また、gRPC FUSEはMySQLを使う時に不具合が生じることもありますのでその場合もOFFにしましょう。

実際に試してみると以下のようになりました。
```bash
$ python check_speed_test.py 

100%|█████████████████████████████████████████████████████████████| 2000/2000 [00:08<00:00, 243.90it/s]
time: 8.238460302352905
```

今回のベンチマークは、モロに影響が出るタイプのものだったらしく、とても早くなりました。

### マウント時のオプションでdelegatedを付ける
ファイルをマウントする時にdelegatedやcachedのオプションを付けるという方法があります。これはホストとコンテナ間での一貫性の担保をせず、同期まで遅延を許容するというオプションです。[こちら](https://docs.docker.jp/docker-for-mac/osxfs-caching.html)から引用すると
- consistent：完全な一貫性（常にホストとコンテナが完全に同じ表示）
- cached：ホストの表示が信頼できる（ホスト上の更新がコンテナ上に反映するまで、遅延が発生するのを許容）
- delegated：コンテナの表示が信頼できる（コンテナ上の更新がホスト上に反映するまで、遅延が発生するのを許容）

デフォルトはconsistent扱いです。しかし、完全に同期している必要があるかというあまりないと思います。特にコンテナ内でファイルIOは大体完結していることが多いため、delegatedは付けても問題ないと思います。

docker-compose.ymlのマウントの記述を以下のようにすれば可能です。
```bash
# マウント時にdelegatedを付ける例
volumes:
      # - ./scripts:/home
      # ↓
      - ./scripts:/home:delegated
```

ベンチマークの結果はこのようになりました。なお、gRPC FUSEではなくosxfsを使用しています。
```bash
python check_speed_test.py 

100%|█████████████████████████████████████████████████████████████| 2000/2000 [00:07<00:00, 261.01it/s]
time: 7.735698461532593
```

少し早くなりました。もし、ファイルのマウントする時に、ファイルの一貫性がいらない場合や開発、デバック時にはdelegatedオプションを付けるのが有効かもしれません。

## Docker Desktopを使わない方法について
DockerはそもそもLinux向けで作られているらしいです。また、M1 Macだとさらに遅く感じる状況があります。そこでVirtualBoxを使用してLinuxの仮想環境を作り、その上でDockerを立ち上げるという方法があります。（ちなみにDocker　Desktopは裏で勝手にやってくれています）

しかし、VirtualBoxは問題点として、
- M1 Macに非対応（今後対応するかもしれないです）
- なんだかんだ重い
というのが挙げられます。

### LimaでDockerを使用する
そこで今回は[Lima](https://github.com/lima-vm/lima)を使用してみました。Limaについては[こちらのREADME](https://github.com/lima-vm/lima/blob/master/README.ja.md)を見るのが分かりやすいです。

個人的に良いと思ったところは、
- 環境構築が楽（VirtualBox入れるよりも）
- M1 Mac対応（ARM対応）
- 無料（Docker Desktopがいらないので）

ただし、注意点としてDocker Desktopを使わない場合はアンインストールもしくはオフにしましょう。また、Docker DesktopはGUIの機能だけが有料ということではないため、「GUIを使わなければ無料なんでしょ？」と思わないようにしましょう。つまり、商用利用にあたりライセンス料を不必要に払いたくない場合はアンインストールしましょう。

#### Limaのインストール
まずはlimaをbrew install
```bash
brew install lima
# もし下記のものがなければ合わせてインストール
brew install docker docker-compose wget
# バージョンの確認（インストール出来ているかの確認）
lima --version
```

#### LimaのDocker用環境のyamlファイルをダウンロード＆編集
下記のようにwgetで取ってきます。
```bash
cd workdir # どこでもいい
mkdir lima
cd lima
wget https://github.com/lima-vm/lima/raw/master/examples/docker.yaml
```

次にdocker.yamlを自分用に設定しましょう。まずはdocker.yamlに以下のように追記しましょう。（コメント文は不要）
```bash
# resource
cpus: 2
memory: "8GiB"
disk: "20GiB"
# sshによるエラーの回避
ssh:
  loadDotSSHPubKeys: false
  localPort: 60006
```
resourceに関してはPCのスペックによって変更しましょう。Docker Desktopの時の設定を覚えている場合はそれに従うのが良いです。

次にファイル書込みの権限を変えます。本来はホームディレクトリは読み込み権限しかないのですが、それだと不便なので書込み権限をLimaの環境に与えます。今回はホームディレクト全てにアクセス出来るようにしますが、あまり推奨はされないので、使うディレクトリだけを指定する方が良いです。
```bash
# docker.yaml
mounts:
    - location: "~"
      writable: true # この行を追加
```

#### Limaによる仮想環境の起動
下記のコマンドでLimaによるDocker環境を起動します。
```bash
cd lima
limactl start ./docker.yaml
```

#### limactl（Limaのコマンド）の説明
limaのコマンドで使うものを簡単に紹介します。
```bash
limactl start docker   # 環境の起動
limactl stop -f docker # 環境の停止
limactl rm docker      # 環境の削除
limactl ls             # 現在存在する環境の一覧表示
```
もし、docker.yamlを書き換えた場合は、それだけだと反映されないのでlimactl stopとlimactl rmで一旦削除しましょう。

#### Docker daemonを自動で起動するように設定
Dockerをシェル起動時の段階で使えるように、~/.zshrc（もしくは~/.bashrc）に以下の２行を追加します。
```bash
export DOCKER_HOST=unix:///${HOME}/.lima/docker/sock/docker.sock
limactl start docker
# もし、Docker Desktopに戻したい場合は以下のように書き換える
# export DOCKER_HOST=unix:///var/run/docker.sock
```

#### Dockerが使用可能かの確認
~/.zshrcを書き換えたら、ターミナルを開き直すかsource ~/.zshrcで変更を反映させます。その後、下記のコマンドで確認をしましょう。
```bash
docker run hello-world
```

### Limaのベンチマークテスト
LimaでDocker Desktop本題のベンチマークテストを行いました。
```bash
python check_speed_test.py

100%|███████████████████████████████████████████████████████████| 2000/2000 [00:17<00:00, 111.45it/s]
time: 17.959896564483643
```
思ったより遅くなったなぁというのが所感でした（Docker Desktopは11秒）。ただ、Limaは無料なことと、M1 Macでも使えるという点は評価できるとは思います。

## 最後に
今回は
- Docker Desktopを使う上でどうやったらファイルIOが速くなるか
- Docker Desktopを使わずに楽に環境構築する方法はないか

という二つのことを調べてみました。まだまだ色々な方法はあると思いますので、自分でも探してみましょう。また、Docker Desktopは色々とアップデートが繰り返されているので、改善される事を期待しつつ、新しいオプション等が出るかをチェックをしましょう。~~（筆者がチェックするとは言っていません）~~