【Windows10Home】DockerによるPython開発環境の構築

Windows 10 Homeで特定バージョンのPython保持しなければならなくなったので、その際やったことを記事にまとめます。
オーソドックスにWSL2でDockerを動かし、コンテナ上にPython環境を構築してみようと思います。

### 対象読者
開発初心者の方。特にPython。
そもそもWSLって？　Dockerって名前は聞いたことあるけど、実際どんなものなの？　という方にも（なるべく）理解してもらえる（といいなぁ）よう、頑張ります。

### 対象マシン
Windows 10 Home
x64システムの場合、バージョン 1903 以降、ビルド 18362 以上が必要です。

# そもそも
なぜ特定バージョンのPythonを保持しなければならなくなったのかといえば、そのバージョンでしか（まともに）動かない機械学習Modelを作ってしまったからです。
だからPythonのアップデートはしたくないんだけど、別のModelでは別のバージョンを使いたい……なんてことは、Pythonの開発をやっていると稀によくあります。
pyenvなどでも1つのマシンに異なるバージョンを保持することができますが、Dockerのコンテナにプロジェクトの開発環境をまるっと保持してしまえばよりシンプルに、かつ移植性も高い環境を構築できるため、こちらを採用しました。
~~（ぶっちゃけpyenvよくわかってない）~~

### でもその前に
ただ、初心者は特にですが、必要性がなければこんなことをする必要もないです。
機械学習といえばGPUだ！　Ubuntuだ！　となりいきなり壮大な環境構築を頑張ろうとして躓いて嫌になって……。てなるのも悲しいので。
それでもPythonの開発ではライブラリの導入を含めたトライ＆エラーが頻発します。
そのたびにインストール＆アンインストールを繰り返しているとローカルの開発環境がぐっちゃぐちゃに汚れ、このライブラリどこにパス通ってるんだ……？　なんてことにもしばしば。
そうなったら新規一転、開発環境を仮想化してしまうのも手です。

あとは自然言語処理でMeCab使いたいけど、Windowsだと文字コードでバグりまくる……という場合も、簡単にLinux系の環境を用意できるのも利点ですね。
開発初心者の方はこんな風に、必要に迫られた段階でステップアップとして取り組むと良いと思います。


### 用語
何となく仮想環境を構築したい理由はわかったよ。じゃあWSLって？　Dockerって？　という方に。

* **Dockerとは**
仮想化技術の一つです。
非常に手軽にWEBサーバーやAPサーバー、DBサーバーをデプロイできることからWEB開発の現場では広く使われている（らしい）技術です。おれWEB畑じゃないからね。
Hyper-VやVirtualBoxと何が違うの？　と思われる方がいるかもしれませんが、これらは物理マシンのネットワークスイッチなどをエミュレートできるのに対し、DockerではベースとなるOSと完全に分離せず、OSのカーネル上にコンテナを配置する形をとります。
（前者をホスト型仮想化、後者をコンテナ型仮想化といいます）
例えばHyper-Vではホストとは異なるOSを積んだり、個別にネットワークアダプタを割り当てることが可能ですが、それらの点でいうとDockerは劣ります。
なのになぜDockerを使うのかと言われれば、やはりその軽量さ、手軽さが大きな理由でしょうか。
DockerではOS上にほかのプロセスと切り離された環境にコンテナを作るので、仮想環境の動作をホスト型仮想化よりも少ないリソースで実現できるのです。

* **WSL(2)とは**
Windows Subsystem for Linuxの略で、そのまんま、Windows上でLinuxを動かすための技術です。
じゃあやっぱHyper-Vでいいじゃん！　って感じですが、Windows 10 HomeじゃHyper-V使えないし、さらにいうとHyper-Vより少ないリソースでLinuxを動かすことができて、んでもって動作も軽量軽快。
個人的に一番違いを感じるのは起動ですね。
また今回はDocker Desktop for Winodwsという製品を使いたいのですが、本来こちらはHyper-Vの仮想化とネットワークを利用します。
前述の通りHomeではHyper-Vが使えないのですが、WSL2にUbuntuをインストールすることで、Docker Desktop for Windowsのバックエンドとして機能させることができるのです。

要はWSL2でUbuntuを動かして、そこにDockerをインストールするってことですね。


### 環境のイメージ
以上を踏まえ、今回はこんなイメージの環境を構築してみたいと思います。
やっと図が出てきた。

![環境のイメージ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1073313/3ae76166-00c3-f207-38bb-568da7b5cc64.png)

ホストのWindowsでWSL2を起動し、そこでUbuntuを動かします。UbuntuやDockerの制御はPowerShell等からでもできるのですが、今回はWindows Terminalからコントロールします。
そのUbuntu上にDocker（Docker Desktop for Windows）を導入。
Docker上にコンテナを配置し、それらを開発環境として利用します。
今回はAnacondaのDockerイメージをDockerHubから引っ張ってきます。DockerHubってのはDockerのコンテナを公開できる、GitHubみたいなもんだね。
んでAnacondaのコンテナでJupyter Labを動かし、Windowsのブラウザから操作したりプロジェクトをVS Codeでいじくったりできるようになろうってのが今回の目標です。

### ちょっと注意
ここまで言っておいて水差すつもりはないんですけど、**製品版のWindowsではWSL2をバックエンドとしたDockerコンテナではGPUを使えません。**
（2021年3月19日現在）
Insider Previewならできるのですが、そちらは自己責任となりますのでご注意を。
自分はGPUを使うプロジェクトに関してはローカルでやりくりしています。


# WSL2のインストール
やぁ、前置きがウルトラ長くなってしまった。
まずはWSL2のインストールからはじめましょ。
公式ドキュメント：https://docs.microsoft.com/ja-jp/windows/wsl/install-win10

コマンドラインからのインストールは現在（2021/3/19）プレビュー版でしか利用できないようです。

### PowerShellの起動
Windowsの検索機能で「PoweShell」と入力します。表示されたアイコンを右クリックし、「管理者として実行」を選んでください。
![02_PowerShellの起動.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1073313/0abe859d-6f38-fdf9-fe47-2b2b3ce8f4ef.png)

### Linux用Windowsサブシステム、仮想マシンの機能の有効化
起動したPowerShellから下記のコマンドを入力し、必要な機能を有効化します。
Linux用Windowsサブシステム

```
dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
```
仮想マシンの機能

```
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
```

どちらもすぐに処理は完了すると思います。

### Linuxカーネル更新プログラムパッケージのダウンロード
以下のリンクからLinuxカーネル更新プログラムパッケージをダウンロードします。
https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi

ダウンロードしたパッケージをダブルクリックし、インストールします。
手順に従って進み、以下の画面となればOKです。
![スクリーンショット 2021-03-22 102422.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1073313/2f8327b7-d26f-222b-7c34-be7e7ebaad34.png)

### WSL2を既定のバージョンとして設定
再度管理者としてPowerShellを開いて、次のコマンドを実行します。

```
wsl --set-default-version 2
```
新たにLinux系OSをインストールする際に使用するWSLの規定バージョンを2に指定しています。

これでひとまずWSL2の準備は完了。


# Ubuntuのインストール
続いてはWSL2に乗っけるOS（Linuxディストリビューション）をインストールします。
WSL2ではDebian GNU/Linuxなどのディストリビューションも利用可能とのことですが、今回は機械学習の開発環境を構築することが目的なのでよく使われるUbuntuを使うことにします。
バージョンは16.04 LTS / 18.04 LTS / 20.04 LTSから選びます。
自分は20.04 LTSをインストールしますが、それ以外のバージョンでも手順は同じ（はず）です。

### Microsoft StoreからUbuntuをダウンロード
スタートメニューから「Microsoft Store」を開き、検索窓に「Ubuntsu」と入力します。
（MSのアカウントでサインインしていなくてもDLできました）
![004.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1073313/a8436fa4-44ef-2e60-69e0-4d766cdf512c.png)

画面右上の「インストール」ボタンをクリックします。
![05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1073313/6c3df7fc-0132-d3c2-2615-e7fc39db716a.png)

### Ubuntuの起動
インストールが完了すると「インストール」のボタンが「起動」に変わっているので、クリックしてUbuntuを起動します。
![06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1073313/97f09c33-e775-beb7-986d-5924f55b8c2a.png)

Ubuntuを起動すると初期化が行われ、続いてユーザー名とパスワードの入力が求められます。
ご自身のものを入力してください。

あとちなみにスタートメニューに「Ubuntu 20.04 LTS」が追加されていると思います。
![08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1073313/be1ad194-f418-66df-8b30-f6a88e8b9e1d.png)


### 起動状況の確認
念のためPowerShellからUbuntuの起動状況を確認しましょう。
（別に管理者権限で開かなくてもいいです）
以下のコマンドを実行してください。

```
wsl --list --verbose
```

これはWSLにインストールされているLinuxディストリビューションの一覧と起動状況、使用するWSLのバージョンを表示するコマンドです。

![09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1073313/d063d5df-403d-6843-28df-60e174eac33b.png)
Ubuntu-20.04がVERSION「2」としてインストールされていますね。
大丈夫そうです。


### Windows Terminalのインストール（おまけ）
これはオプションなので別にやらなくてもいいんですけど、自分はUbuntuの操作はWindows Terminalから行っています（結構使いやすい）。
Windows TerminalもMicrosoft Storeからインストールできますできます。
（検索窓に「Windows Terminal」と入力してください）
![10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1073313/ed50cd5b-120e-896a-0f2f-b2e1c2608ec6.png)

用途としてはUbuntuの制御がメインなので、起動時にUbuntuのシェルが立ち上がるよう規定値を変更します。
タブの「▽」をクリックし、「設定」を選択してください。
![11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1073313/c360f839-ab6b-948d-d637-11317feee632.png)

「Setting.json」というjsonファイルが開かれます。
上のほうに「defaultProfile」という項目があります。
ここに起動時に立ち上げたいシェルのguidを入力することで規定値を変更することができます。
![12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1073313/9f693b1d-3e47-93e4-1b76-4690e35b216d.png)

少しスクロールすると「profiles」→「list」というところに各シェルの情報が格納されているのがわかります。
Ubuntuがインストールされていれば「"name": "Ubuntu-20.04"」の項目があるはずなので、そのguidをコピーし、先ほどのdefaultProfileの値に貼り付けます。
![13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1073313/198f5e53-04a3-cb35-3611-8f317ac5359a.png)

これでWindows Terminalを起動するとデフォルトでUbuntuのシェルが立ち上がるようになりました。
![14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1073313/6b38f098-e03f-169e-6303-87a4c8b67ac2.png)


# Docker Desktop for Windowsのインストール
バックエンド環境が準備できたところで、Dockerをインストールしましょう。
公式ドキュメント：https://docs.docker.jp/docker-for-windows/install.html

### インストーラーのダウンロード
インストーラーはDocker Hubからダウンロードできます。
https://hub.docker.com/editions/community/docker-ce-desktop-windows/

「Get Docker」をクリックするとダウンロードが始まります。
特にSign upしなくてもDLできます。

### インストール
ダウンロードしたインストーラーをダブルクリックして起動します。
「Install required Windows Components for WSL 2」にチェックし、「OK」をクリックします。
![16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1073313/778edac8-2f6c-9735-ef0b-0221d274ee39.png)

完了です。
![17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1073313/6bdef04e-727b-da60-cf82-5a5e9e5f30d5.png)

試しにDockerを起動してみましょう。
もちろん空っぽです。
このあとコンテナ入れるので、チュートリアルはスキップして良いです。
![18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1073313/041871e2-70a9-6012-d714-e6ab20f22ab1.png)

# Dockerディスク領域の変更（オプション）

Docker Desktopをインストールすると問答無用でシステムドライブに仮想環境が作られます。
↑の手順でもマウントするパスの指定とかなかったしね。
コンテナを立てていくとCドライブの容量を圧迫してしまうので、データドライブに領域を移します。

なおWSLのインストール一覧は下記のコマンドで確認することができます。

```
wsl --list
```

Windows Terminalでの実行結果です。このとき接続先のシェルはPowerShellにしてください。
（先の手順でデフォルトがUbuntuになっているので）
領域を移したいのはこの「docker-desktop-data」ってやつです。
![19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1073313/9070fe84-e54a-685f-995b-28bfd2798ebd.png)

ちなみに上記のイメージの実態は `%LocalAppData%\Docker\wsl\distro\ext4.vhdx` というものです。
エクスプローラーで `%LocalAppData%\Docker\wsl\distro` と入力すると、 `ext4.vhdx` というファイルがあります。
（多分デフォルトだと `C:\Users\（ユーザー名）\AppData\Local\Docker\wsl\distro` とかじゃないかな）
あ、ちなみに `vhdx` は　`VirtualHardDisk` って意味ね。

### Docker / WSLの停止
まずはDocker Desktopを停止します。
タスクバーの「△」を選択し、Dockerのクジラみたいなアイコンを右クリックすると操作一覧が表示されるので「Quit Docker Desktop」を選択します。
![20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1073313/4db142ee-91ae-973d-3269-863f4184d0c1.png)

続いてPowerShellで下記のコマンドを実行します。

```
wsl --shutdown
```

念のため起動状況を確認します。

```
wsl --list --verbose
```

STATEがすべて「Stopped」になっていればOKです。
![21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1073313/80123aa9-e789-979a-b9dc-ffeb81fb6289.png)

### データのエクスポート
`docker-desktop-data` の移動先のフォルダを作っておきます。
データドライブに `D:\01_Docker` というフォルダを予めつくっておきました。フォルダ名は気にしないでください。

下記のコマンドで、上記フォルダの中に `docker-desktop-data.tar` というファイルをエクスポートします。

```
wsl --export docker-desktop-data  D:\01_Docker\docker-desktop-data.tar
```
コンテナは空っぽの状態なんで、すぐに終わります。

### docker-desktop-dataの登録の解除
WSLにおけるdocker-desktop-dataの登録を解除します。

```
wsl --unregister docker-desktop-data
```

### .tarファイルのインポート
上記でデータドライブにエクスポートしておいた.tarファイルをインポートします。
`D:\01_Docker` フォルダの直下に `data` フォルダを作成します。
下記のコマンドを実行し、 `D:\01_Docker\docker-desktop-data.tar` ファイルを `D:\01_Docker\data` フォルダにインポートします。

```
> wsl --import docker-desktop-data D:\01_Docker\data D:\01_Docker\docker-desktop-data.tar
```

`D:\01_Docker\data` フォルダに `ext4.vhdx` が作成されています。
![22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1073313/626b9c99-84c6-5037-22f2-924cd3f8f8f5.png)

### WSL / Dockerの起動
WSLとDockerを起動しましょう。
WSLはPowerShellで `wsl` と入力すれば起動します。
DockerはアイコンをクリックすればOK。


# Dockerコンテナの作成
ちょっと話が脇道にズレてしまいましたが、いよいよ最終目的である仮想開発環境を構築しましょう！
冒頭のイメージ図でもふれたとおり、今回はDocker HubからAnacondaのコンテナをダウンロードします。

### 作業フォルダの準備～Dockerfileの作成
Dockerではコンテナを作るときに `Dockerfile` というファイルを作成し、そこにどんな構成のコンテナとするか～といった情報をコードとして記述します。
コンテナを立ち上げる時にDockerがこのファイルを参考にセットアップしてくれるんですね。

まずはそのコンテナにおける作業フォルダを作成します。
本格的な運用をする場合には1プロジェクトにつき1コンテナとすることが多いので、コンテナ名＝プロジェクト名するのが分かりやすいでしょう。
今回は `D:\Project\202103_TestProject` というフォルダを作成しました。
ここでは半角英数にしておいたほうが賢明です。
特に **スペースは全角半角問わずやめておいたほうが良いでしょう。**

作成したフォルダの直下で右クリック→新規作成→テキストドキュメントを選択します。
![01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1073313/dd3aeb45-51ac-f8c6-07b2-c0c7bf9d8610.png)

作成したファイル名を「Dockerfile」に変更します。
このとき、 **「.txt」の拡張子を削除します。**
Dockerfileに拡張子はないのです。
![03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1073313/5a1f96a3-d7b4-f480-3e7b-6940ef96f541.png)

警告が出ても無視してください。
![02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1073313/de9412cc-9ad9-a172-a83f-817495188736.png)

### Dockerfileの記述
作成したDockerfileに「どんなコンテナを作るのか」という情報を記述していきます。
DOckerfileを選択し右クリック→プログラムから開くを選び、好きなテキストエディタから編集してください。

今回は次のような命令を記述していきます。

```
FROM continuumio/anaconda3:2019.03

RUN pip install --upgrade pip && pip install Keras && pip install tensorflow

WORKDIR /workdir

EXPOSR 8888

ENTRYPOINT ["jupyter-lab", "--ip=0.0.0.0", "--port=8888", "--no-browzer", "--allow-root", "--NotebookApp.token=''"]

CMD ["--notebook-dir=/workdir"]

```

一つ一つ見ていきましょう。

#### `FROM continuumio/anaconda3:2019.03`
ここでDocker Hubから「2019.03時点のAnaconda3を引っ張って来いよ」と明示しています。

#### `RUN pip install --upgrade pip && pip install Keras && pip install tensorflow`
その後実行するコードです。
イメージとしてはcmdから実行すべきコマンドを記述している感じ。

#### `WORKDIR /workdir`
命令を実行するときのカレントディレクトリを指定します。
上記によりコンテナに`/workdir`が作成され、各種命令はこの中で実行されることになります。

#### `EXPOSE 8888`
ネットワーク上のポートを指定します。

#### `ENTRYPOINT ["jupyter-lab", "--ip=0.0.0.0", "--port=8888", "--no-browzer", "--allow-root", "--NotebookApp.token=''"]`
実行対象に関する設定です。
今回はコンテナ上で起動するJupyter LabをホストのWindowsから利用したいのでそれに関する設定を行っています。
ローカルで完結する環境を想定しているためトークン認証は無効としていますが（"--NotebookApp.token=''"）、例えばクラウドで立ち上げる等であれば設定が必要でしょう。

#### `CMD ["--notebook-dir=/workdir"]`
最後にJupyter labの作業ディレクトリを先ほど作成した/workdirに紐づけます。

以上で命令の記述は完了です！
保存してエディタを閉じます。


### docker-compose.ymlファイルの作成
次にアプリケーションを構成するサービスを「Docker-compose.yml」というファイルに記述します。
今回はDockerの起動にはDocker Composeというツールを使用します。
これは複数コンテナを定義し、実行するDockerアプリケーションを管理するためのツールです。

YAMLというファイルにアプリケーションの各サービスの設定を記述することによって、Docker起動時にYAMLファイルを呼び出すだけで簡単にコンテナの生成、起動を行うことができます。

先ほど作成したDockerfileと同じディレクトリに、`docker-compose.yml`というファイルを作成してください。

そこに以下のように記述します。

```
services:
  dev:
    build:
      context: .
      dockerfile: Dockerfile
    image:project01
    ports:
    - "8080:8888"
    volumes:
    - .:/workdir
```

imageにはご自身のプロジェクト名を入力してください。
また起動対象となるDockerfileもbuild内で指定します。そのためdocker-compose.ymlとDockerfileは同じディレクトリにないといけませんので、注意。
Dockerfileと同じくvolumesには作業ディレクトリとして/workdirを指定しましょう。

ちなみに今回は割愛しますが、docker-compose.ymlファイルを使用しなくとも、シェルから`docker run`というコマンドを実行しても起動できますよ。


### コンテナの起動
これでコンテナを起動する準備ができました。
Powershellを起動し、カレントディレクトリをDockerfile、docker-icompose.ymlが置かれたフォルダに移動します。

```
cd D:\Project\202103_TestProject
```

続いて下記のコマンドを実行します。

```
docker-compose up
```


### Jupyter Labの起動
WindowsからコンテナのJupyter Labに接続しましょう。
コンテナが起動した状態で任意のブラウザを起動し、`localhost:8080`と入力してください。
Jupyter Labが起動すれば成功です！
![スクリーンショット 2021-03-29 173410.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1073313/687a60f6-2e23-a8ac-837d-997f8e557083.png)


Jupyterのナビゲーターを見ればわかる通り、JupyterのカレントディレクトリはそのままDockerfile、docker-compose.ymlが置かれたフォルダとなっています。
このフォルダの中身をコンテナは認識してくれますので、試しにこの直下に「01_Input」「02_Source」「03_Model」「04_Output」というフォルダを作成してみます。
![スクリーンショット 2021-03-29 173513.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1073313/904611ba-8281-ff58-a295-3267bc054fa3.png)


Jupyterでも反映されていますね。
![スクリーンショット 2021-03-29 173540.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1073313/9fa4ac51-6e66-45e2-a087-69fdd5d9a858.png)


今度は逆にJupyter LabからPythonのNotebookを作ってみましょう。
「02_Source」に入り、Python Notebookを作成します。
![スクリーンショット 2021-03-29 173600.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1073313/2ec661fc-08ff-834d-3043-ec955f850399.png)


Windows側のエクスプローラーから「02_Source」の中を見ると、作成した.ipynbファイル（とチェックポイント）があることが分かります。
![スクリーンショット 2021-03-29 173621.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1073313/c9894ca6-619e-043b-f9a2-4e4f2362f911.png)



これで無事WindowsとDockerコンテナの同期が取れていることが確認できました！

お疲れ様です、全作業完了です！
あとはもう、イカしたModelを作りまくって世の中に貢献しちゃってください。