貧弱なWindows PCでもDockerを動かしたい！（WSL2）

この記事は [Docker Advent Calendar 2020](https://qiita.com/advent-calendar/2020/docker) および [ISL Advent Calendar 2020](https://adventar.org/calendars/5538) 13日目の記事です。

**説明はいいから早く使い方を教えろ！という方は[手順](#手順)を見てください。**

どうも、最近は[M1 MacでもWindowsがスムーズに動作](https://www.gizmodo.jp/2020/12/m1-mac-windows10.html)することを知り期待が膨らむ筆者です。
釣りっぽいタイトルですが、**手順自体はハイスペックPCでも全く同じです**。

この記事ではDocker Desktop for WindowsをWSL2で実行するまでの手順と、筆者が遭遇した問題に対する解決策を紹介します。
基本的には既存の記事と変わりませんが説明通りに進めるだけではネットワークが繋がらなかったり情報が古かったりしたので現時点の方法として残しておきます。
なおPCが貧弱かどうかは主観的な評価であり、用途や環境によっても異なります[^1]。

LGTMしていただけるととても喜びます。

## 序論
コンテナ型仮想化といえばDocker[^2]といわれるほど有名なDockerですが、仮想化と聞いただけで相当なスペックが必要だと思っていませんか？Dockerは、ホスト型仮想化ソフトウェア[^3]からLinuxを立ち上げて動作させるよりストレージ消費量やメモリ消費量が少なく、高速で起動します。コマンド１つでサーバを作成・削除できたり、Docker Composeと組み合わせると更に複数のコンテナをコマンド１つで管理できたりします。
なお、いわゆる**「つよつよPC」の場合でも[システムイメージをHDDに移動する](#ディスクイメージをcドライブ以外に移動させる)ことはSSDの寿命を延ばすために有効**です。

[^1]: 現在発売されているエントリークラスのノートPCを含め、殆どのPCでWSL2およびDockerが動作します。(Windowsが32bit版の場合は再インストールが必要となりますが、そもそもDockerを使おうとする人は64bit版だと思っています)
[^2]: 個人開発の場合。企業において本番環境で用いられているコンテナオーケストレーションツールは圧倒的にKubernetesの方が多いです。
[^3]: 仮想化の種類については リンク:[仮想化ソフトの種類](https://qiita.com/Pentas/items/5f558deeddcfef814b3e) に分かりやすくまとまっています。

## 用語解説
### WSL2とは
正式名称は**Windows Subsystem for Linux 2**で、Windows 10 バージョン 2004のリリースと共に利用できるようになりました。
その名前からWSL1の後継かと思われがちですが、原理や特徴は大きく異なっています。
詳細は公式ドキュメントの「[WSL 1 と WSL 2 の比較](https://docs.microsoft.com/ja-jp/windows/wsl/compare-versions)」をチェック！
.NET Framework や Internet Explorer 11 などは最初から有効になっているので意識していないかもしれませんが、WSL2もこれらとと同様に**「Windowsの機能」の一つ**として提供されています。

#### WSLとは
>Windows Subsystem for Linux とは、Linuxのバイナリ実行ファイルをWindows 10およびWindows Server上でネイティブ実行するための互換レイヤーである。

（[ウィキペディア](https://ja.wikipedia.org/wiki/Windows_Subsystem_for_Linux) より引用）
つまり**本物のLinuxをWindowsで動かせる**技術です。
ベータ版ではWindowsの開発者モードを有効にしないと利用できませんでしたが、2017年10月に公開されたWindows 10 バージョン 1709で利用可能になった正式版ではこの制限が撤廃されました。
なお、単にWSLといった場合にはWSL1とWSL2を含む全体について指すと認識しています。**WSL＝WSL1ではないので注意**。

### Dockerとは
>Docker とは開発者やシステム管理者が、コンテナでアプリケーションを **構築（build）、実行（run）、共有（share）**するためのプラットフォームです。
>コンテナは新しくありませんが、コンテナを使えばアプリケーションのデプロイがより簡単になります。

（公式ドキュメント内 [Docker の概念](https://docs.docker.jp/get-started/index.html#docker) より）

>Dockerの大きな特徴は、どんなアプリケーションでも、場所を問わずに構築・移動・実行できるプラットフォームを提供することです。

（[コンテナにもエンジンが必要! ―その代表格と言える「Docker」とは](https://thinkit.co.jp/article/17349) より引用）

コンテナ技術自体は以前から存在していましたが、Dockerの登場により敷居が下がり、個人開発者であってもその恩恵を簡単に受けられるようになりました。
そして、2020年5月28日にWindows 10 May 2020 UpdateがリリースされたことでHomeエディションのユーザでも利用できるようになりました。

なお、個人開発で使われている仮想環境にはVagrantもあり、[Vagrantとは？](https://eng-entrance.com/linux-vagrant-install#Vagrant)によれば、VirtualBoxやVMwareの「コマンドラインのフロントエンド」として動作し、比較的簡単なコマンドを叩くだけで仮想マシンを管理できるツールのようです。
（ VagrantとDockerそれぞれの特徴は[こちらの記事](https://qiita.com/hirohero/items/1eaa22b7f87eb66b2f2d)が分かりやすいと思います ）

## システム要件（Hyper-V）
確認方法: [Docker Desktop のシステム要件をどうやって確認したらいいの？](https://futureys.tokyo/how-can-i-check-system-requirements-for-docker-desktop/)

- 64bit版のWindows 10
    - Intel CPUの場合 ： Windows 10 バージョン 1903, ビルド 18362.1049 以上
    - AMD CPUの場合： Windows 10 バージョン 2004, ビルド 19041 以上
- SLAT (Second Level Address Translation) 対応[^4]のCPU
- 4GB以上のRAM
- BIOSでハードウェア仮想化支援機能が有効になっていること  
  機能自体はほぼすべてのPCに搭載されていると思いますので、有効になっていない場合はUEFI(BIOS)の設定画面から有効にする必要があります。

（ [Dockerドキュメント](https://docs.docker.jp/docker-for-windows/install.html#win-system-requirements) および [WSLのインストールガイド](https://docs.microsoft.com/ja-jp/windows/wsl/install-win10#requirements) より引用）
Docker Desktop for Windowsに追加で必要な要件はありません。
なお、ハードウェア仮想化支援機能を有効にできない場合は、WSLは利用できませんが VirtualBox 6.1 未満であれば仮想環境を利用することができます。（その場合ゲストOSは32bit版のみ）

[^4]: SLATとは仮想マシンごとのメモリアドレス空間を、ホストCPUの実メモリアドレス空間に再マップする機能であり、Intel EPT (Extended Page Table) や AMD NPT (Nested Page Table) / RVI (Rapid Virtual Indexing) などという名称で提供されています。<br>[このパソコンで「Hyper-V」は動くのか？](https://forest.watch.impress.co.jp/docs/serial/win10tips/1149277.html) に確認方法が掲載されています。

## 手順
では、実際にWSLとDockerをインストールしてみましょう。

- Dockerの導入にUbuntuのインストールを前提としている記事もありますが、別途**ディストリビューションをインストールする必要はありません**！
- Windows 10 ビルド20262以降の場合は`wsl --install`のみでWSL2をインストールできますが、**もれなくUbuntu又はDebianがついてきます！**（記事執筆時点ではプレビュービルド）

1. Windowsの機能から「**Linux 用 Windows サブシステム**」と「**仮想マシン プラットフォーム**」を有効にする  
前者はWSLそのもの、後者はWSL2に必要な機能です。  
![「機能」と検索する画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171520/44f28fce-a917-76a4-5978-d22e4149c523.png)  
↑Windowsボタンを押して「機能」と入力するとすぐに出せます。  
![Windowsの機能](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171520/3d6813bd-687b-5e32-0552-0e54d550d2b8.png)  
他の仮想化ソフトウェアを使う方は「Windows ハイパーバイザー プラットフォーム」も有効にすると再起動無しで併用できるようになります。  
インストール後は再起動を促されるので「今すぐ再起動」を選択しWindowsを再起動します。
1. Linuxカーネルをインストール  
⚠ WSLを有効にした後、再起動していないとインストールできません  
[公式インストールガイド](https://docs.microsoft.com/ja-jp/windows/wsl/install-win10#step-4---download-the-linux-kernel-update-package) からMSIパッケージをダウンロードしインストールする  
（ARM CPUの場合は「注意」内のリンクからダウンロードしてください）  
![カーネルインストール時の画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171520/76280c83-4073-58e6-5bb5-4a5d445a7361.png)
1. Dockerをインストールし再起動  
    1. [Docker Desktop for Windows](https://hub.docker.com/editions/community/docker-ce-desktop-windows) 右下の「Get Docker」からダウンロードします。  
    ![Dockerダウンロードページ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171520/043a659f-7ce5-df4d-ba59-b6d2c1fb9598.png)
    1. インストーラを実行します。　⚠ 起動させてから画面が出るまでに時間がかかるので注意！  
    インストーラの画面が他のウィンドウの背後に表示される場合があります。  
    デスクトップにショートカットを作成したくない場合は「Add shortcut to desktop」のチェックを外します。  
    ![Dockerインストーラ起動時.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171520/bf9487d5-f11c-f3cd-2345-52e4f28dddf7.png)
    1. インストールを実行します。  
    ![Dockerインストール中.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171520/5b99e3f1-68a7-4418-1d54-2c9b0d4fb9de.png "気長に待ちましょう☕")
1. インストールが完了したら「Close and restart」を選択してWindowsを再起動します。  
再起動が終わるとDockerが起動できるようになっているはずです！お疲れ様でした！
![Docker-Tutorial.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171520/b9d089cf-396a-0fe7-55bb-7425d66e3c4c.png)
初回起動時に表示されるチュートリアルではクリックだけでサーバを作ることができます！

### Linux ディストリビューションをインストールすればさらに便利に
このままでもDockerは使えますが、文字コードや改行コードが違ったり、bashが無かったりします。
やっぱりDockerはUNIX系で動かすのが便利。これは元号が変わっても変わりません。



## うまくいかない時は
### エラーメッセージが表示される
- 「'wsl' は、内部コマンドまたは外部コマンド、操作可能なプログラムまたはバッチ ファイルとして認識されていません。」  
WSLを有効にしてください。（手順1）
- 「Linux 用 Windows サブシステムには、ディストリビューションがインストールされていません。」  
Dockerをインストールしてください。（手順3）
- 「This update only applies to machines with the Windows Subsystem for Linux」  
![WSLが無効の状態でカーネルをインストールしようとしたときの画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171520/6585b246-1e43-99c3-8262-8b56edf5f22c.png)  
WSLを有効にしてからカーネルをインストールしてください。（手順1）
- 「WSL 2 installation is incomplete.」  
![WSLより前にDockerをインストールした場合の画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171520/beed0cd8-3aea-2766-620e-f33262693ca0.png)  
Restart を押さずウィンドウを開いたままにするか Cancel を押してから、WSL2のカーネルをインストールしてください。  
Cancel を押した場合はカーネルをインストールした後に手動でDockerを起動する必要があります。

### 他の仮想化ソフトウェアが動作しなくなった

「Windows ハイパーバイザー プラットフォーム」を有効にすることで、WSL2(Hyper-V)とVirtualBoxやVMware Player、Androidエミュレータなどを**設定変更や再起動無しに**共存させることができます。
なお、この方法で共存させる場合、使用するソフトによって条件があります。

- VirtualBoxの場合 バージョン6.1.16 以降でなければ動作しない  
（ [WSL2やHyper-Vと共存できるVirtualBox 6.1.16が公開されました](http://kzlog.picoaccel.com/virtualbox-coexist-with-wsl2-hyper-v/) より）  
バージョンが低いと `Call to WHvSetupPartition failed: ERROR_SUCCESS (Last=0xc000000d/87) (VERR_NEM_VM_CREATE_FAILED).` のようなエラーが表示されます。
- VMware Workstation Player 15の場合
`VMware PlayerとDevice/Credential Guardには互換性がありません。`のようなエラーが表示されます。

    >- ☐Intel VT-x/EPT または AMD-V/RVI を仮想化(V)
    >- ☐CPU パフォーマンス カウンタを仮想化(U)
    >- ☒IOMMU（IO メモリ管理ユニット）を仮想化(I)
    >
    >のように、上2つのチェックを外すことで、動いてます。

（ [WSL2とHyper-Vの関係](https://qiita.com/matarillo/items/ca1eecf8f9a3cd76f9ce#windows-ハイパーバイザー-プラットフォーム) より）

#### 一時的にHyper-Vを無効化することによる解決策
[Hyper-V をオン/オフする方法](https://qiita.com/masoo/items/b73dadb0e99f9be528fe)
bcdeditを用いることで一時的にHyper-Vを無効化できますが、切り替えの度に再起動が必要なのでお勧めしません。

### インターネットに繋がらない
この問題は、他の仮想化ソフトウェアを使っている場合などで作られた、「インターネットアクセスなし」のネットワークアダプタ（以下アダプタ）を使ってWSLが接続しようとすることで起こります。
なので、インターネットに接続できるアダプタを使って通信するようにしてやればいいのです。
具体的な解決方法は複数あると思いますが、私はいくつかのアダプタを無効にすることで対処しました。
#### インターネットに接続できないアダプタに接続するアダプタを無効にする
WSLはアダプタごとに新たなアダプタを作り、それを用いて外部と通信します。

1. スタートボタンをクリックして「ネット」と入力し、「ネットワークの状態」画面を開く
2. 「アダプターのオプションを変更する」を選択  
![「ネットワークの状態」画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171520/5e025643-342c-34e7-731f-c29ee5cfd4e7.png)  
3. **「vEthernet」から始まるアダプタの中で**、インターネットに接続できないアダプタに繋がっているアダプタを無効にする。（複数あればすべて）  
  ![アダプタを無効にする画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171520/9a14040e-550e-e2ab-0fef-6ba29bca3abb.png)  
どのvEthernetアダプタがどのアダプタに繋がっているかは名前で判断してください。

#### インターネットに接続**できる**アダプタを優先的に使う設定にする（未検証）
こちらは検証できていないのですが、各アダプタはプロトコルごとに優先度（メトリック[^5]）を設定する項目があり、それを設定することでも改善できると思われます。

1. スタートボタンをクリックして「ネット」と入力し、「ネットワークの状態」を選択
2. 「アダプターのオプションを変更する」を選択
3. **「vEthernet」から始まるアダプタの中で**、優先させたい（インターネットに接続できる）アダプタのプロパティを表示  
![アダプタのプロパティを表示する画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171520/cfba7247-cce9-513a-7cb1-d487d80e618b.png)  
4. IPv4を選択し、プロパティを表示  
![vEthernetのプロパティ画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171520/ebe8f333-a3c3-4022-de88-7e48b578e634.png)  
5. 詳細設定を開き、メトリックを変更する  
![IPv4のプロパティ画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171520/0b7dc409-d7e1-af81-3550-5daafd587875.png)  
![メトリック設定画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171520/13fb9d86-0f1c-9086-24c6-f82cd52eeed7.png)  
メトリックはコストを表す値なので**小さいほど優先度が高くなります**。  
変更前のメトリックはメモしておかなくても、一度対応するアダプタ（「vEthernet」が付かないアダプタ）を無効→有効することで復活します。

### メモリが足りない
この問題については[WSL2によるホストのメモリ枯渇を防ぐための暫定対処](https://qiita.com/yoichiwo7/items/e3e13b6fe2f32c4c6120)からの引用となりますが、WSL2のメモリサイズを固定することで対処できるそうです。

>`%USERPROFILE%\.wslconfig`のコンフィグに以下の設定を行います。
>
>- `%USERPROFILE%`は`C:\Users\taro`のようなホームディレクトリを指します。
>- コンフィグが存在しない場合は作成してください。
>- 設定した後はWSL2を再起動するか、OSを再起動してください。
>
>```properties
>[wsl2]
>memory=6GB
>swap=0
>```
>
>- `memory`にはWSL2が最大確保するメモリサイズを指定します。PCの搭載メモリとWSL2の使用用途に応じてメモリサイズの値は変更してください。ちなみに**未指定時のデフォルト値は`PC搭載メモリの80%`**です。
>- (個人的に推奨) `swap`を0に設定してスワップを無効にします。
>    - スワップを無効にするとWSL2側で実際にメモリ不足になった途端に変な挙動を起こすかもしれません。しかしその代わりSSD等で大量のスワップIn/Outの発生を防ぐため、大量書き込みによるSSDの寿命を縮めることは回避できます。トレードオフを意識して設定するかどうかを考えましょう。

また、WSL2を再起動することでもキャッシュ等をクリアすることができるようです。

>以下のURLの情報を参考にWSL2を一度再起動すれば、WSL2の仮想マシンがファイルキャッシュ等がクリアされた状態で立ち上がります。
>
>[WSL を再起動する方法](https://kagasu.hatenablog.com/entry/2020/01/02/155532)
>
>ちなみにWSLの再起動などの操作についてはWSL1もWSL2も基本的に同じです。

### ディスクの空き容量が足りない
#### ディスクイメージをCドライブ以外に移動させる
空き容量がまだあっても特にSSDの方にはおすすめです。
Dockerのアップデート時でも元の場所に戻す必要はありません！
[WSL2 Dockerのイメージ・コンテナの格納先を変更したい](https://qiita.com/neko_the_shadow/items/ae87b2480345152bc3cb)

⚠ 他のディストリビューションにも適用できる方法ですが、アプリのアンインストールやリセットをする時に**Docker以外の場合は元の場所に戻す必要があります**。

#### ディスクイメージを縮小する
$\{筆者の環境ではあまり小さくならなかったです…}$
[WSL の仮想ハードディスクのサイズを縮小する \`diskpart\`](https://zfhrp6.com/posts/2020-08-15_shrink-wsl-vhd.html)

[^5]: `メトリックとは、特定のネットワークインターフェイスの IP ルートに割り当てられる値で、そのルートの使用に関連するコストを識別します。`（[Microsoft Docs](https://docs.microsoft.com/ja-jp/troubleshoot/windows-server/networking/automatic-metric-for-ipv4-routes)より）

## 参考
- Dockerについて  
[Dockerコンテナ時代の第二章～Kubernetesの成熟とエコシステム発展の時代](https://www.publickey1.jp/blog/20/dockerkubernetes.html)  
（[Dockerコンテナ時代の第一章の終わり、そして第二章の展望など](https://www.publickey1.jp/blog/17/post_265.html) の続き）
- 手順について  
[WSL2 + Ubuntu 20.04 + Docker 開発環境構築](https://qiita.com/amenoyoya/items/ca9210593395dbfc8531)