WSL2 + Docker Desktop + VSCode（Remote - Containers） でGoの開発環境構築

## 概要

バージョン`2004`で正式導入されたWSL2ですが、先日バージョン`1909`と`1903`へのバックポートが発表されました！
https://devblogs.microsoft.com/commandline/wsl-2-support-is-coming-to-windows-10-versions-1903-and-1909/
筆者の開発PCは`2004`へのアップデートが許可されていないので、非常に助かります。
そこで今回は、`Docker Desktop for Windows`のWSL2インテグレーション機能と、VSCodeの`Remote - Containers`拡張機能を使って、`Dockerfile`でコンテナ内に`Go`の「快適な」開発環境を作ってみるところまでやってみようと思います。
※既に以下のような素晴らしい記事があり書くのを迷いましたが、自身の理解を深めるのと備忘録も兼ねて書きました。
https://tech-lab.sios.jp/archives/21675

## 筆者の環境
| エディション | バージョン | OSビルド |
|:-:|:-:|:-:|
| Windows 10 Pro | 1909 | 18363.1049 |

## システム要件

| エディション | バージョン | OSビルド |
|:-:|:-:|:-:|
| Windows 10（全エディション） | 2004 | - |
| Windows 10 x64（全エディション） | 1909 | 18363.1049 以上 |
| Windows 10 x64（全エディション） | 1903 | 18362.1049 以上 |

## 手順

1. WSLをインストールし、WSL2に更新する

1. Docker Desktop for Windows をインストールする

1. VSCoceの拡張機能 Remote Containers を使って、Goの開発環境を構築する

## 1. WSLをインストールし、WSL2に更新する

以下URLの公式手順通りにやれば問題ありませんが、一応この記事でも手順を書いておきます。
https://docs.microsoft.com/ja-jp/windows/wsl/install-win10

### 1‐1. CPU仮想化が有効になっているか確認

タスクマネージャを開き、CPUの仮想化が有効となっていることを確認してください。
もし有効になっていない場合は、BIOS（UEFI）で仮想化を有効にしてください。（メーカーによって設定方法が異なるので説明は割愛します）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321472/6fe1ca41-89e7-16cc-3baf-e134a7dda580.png)


### 1‐2. Windows Subsystem for Linuxをインストールする

#### GUI（マウス操作）でやる場合

`コントロールパネル` > `プログラムと機能` > `Windowsの機能の有効化または無効化`の順に選択して以下画面を開き、`Linux用Windowsサブシステム`にチェックを入れます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321472/754aec7e-8efa-3c50-18a5-dace4a38b04e.png)

#### CUI（コマンド操作）でやる場合

管理者権限でPowerShellを開き、以下コマンドを実行してください。

```powershell:powershell
dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
```

成功すると以下のようなメッセージが表示されます。

```powershell:powershell
展開イメージのサービスと管理ツール
バージョン: 10.0.18362.900

イメージのバージョン: 10.0.18363.1049

機能を有効にしています
[==========================100.0%==========================]
操作は正常に完了しました。
```

### 1‐3. 仮想化マシンプラットフォームのオプションコンポーネントを有効にする

#### GUI（マウス操作）でやる場合

先程と同様に、`コントロールパネル` > `プログラムと機能` > `Windowsの機能の有効化または無効化`の順に選択して以下画面を開き、`仮想マシンプラットフォーム`にチェックを入れます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321472/0480e2c1-60f9-c4af-8f50-35ba030f221b.png)

#### CUI（コマンド操作）でやる場合

管理者権限でPowerShellを開き、以下コマンドを実行してください。

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

成功すると以下のようなメッセージが表示されます。

```powershell:powershell
展開イメージのサービスと管理ツール
バージョン: 10.0.18362.900

イメージのバージョン: 10.0.18363.1049

機能を有効にしています
[==========================100.0%==========================]
操作は正常に完了しました。
```

### 1‐4. PCを再起動する

PCを再起動してWSLのインストールとWSL2の更新を完了させます。

### 1‐5. WSL2を既定のバージョンとして設定する

新しいLinuxディストリビューションをインストールする際の既定のバージョンをWSL2に変更します。
PowerShellを開き、以下コマンドを実行してください。

```powershell:powershell
wsl --set-default-version 2
```

以下のメッセージが表示された場合はLinuxカーネルを更新する必要があります。

```powershell:powershell
WSL 2 を実行するには、カーネル コンポーネントの更新が必要です。詳細については https://aka.ms/wsl2kernel を参照してください
WSL 2 との主な違いについては、https://aka.ms/wsl2 を参照してください
```

メッセージに表示されている以下URLにアクセスし、Linuxカーネル更新プログラムパッケージをダウンロードします。
https://aka.ms/wsl2kernel

ダウンロードしたインストーラーを起動し、Nextをクリックすればインストール完了です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321472/d05daad0-790e-d1f9-1ac2-c0263e531b59.png)

インストールが完了したら、再び以下コマンドを実行してみてください。今度は成功するはずです。

```powershell:powershell
wsl --set-default-version 2
```

### 1‐6. Linuxディストリビューションのインストール

`Microsoft Store`を開き、Linuxディストリビューションをインストールします。
`wsl`で検索すればいくつか候補が出てきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321472/71076c74-9315-d338-9144-b288f840e7df.png)

何でもいいですが、今回は`Ubuntu`をインストールします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321472/47b1335e-4613-8182-a80c-14d9dd5bd87e.png)

インストールが完了したら、`Ubuntu`を起動します。
しばらく待つとユーザー名とパスワードの入力を促されるので、任意のものを入力します。
※今後`Ubuntu`内で何かするときはパスワード入力を求められるので、パスワードは忘れないでください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321472/b289ca88-1290-3462-c7e8-1c0f54de24be.png)

これで、Windows上でLinux（Ubuntu）を起動できるようになりました！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321472/71393b51-5010-1930-cdc6-df1d854645b1.png)


念のためPowerShellを開き、以下コマンドを実行してWSLのバージョンが2になっていることを確認しましょう。

```powershell:powershell
wsl -l -v
  NAME      STATE           VERSION
* Ubuntu    Running         2
```

## 2. Docker Desktop for Windows をインストールする

次に、`Docker Desktop for Windows`をインストールし、`Docker`を使えるようにします。

ここで注意点ですが、以下のリリースノートに記載してある通り、バージョンが`1909` `1903`の場合は`Docker Desktop Community 2.3.5.0` 以上をインストールする必要があります。
https://docs.docker.com/docker-for-windows/edge-release-notes/

執筆時点（2020年9月4日）では、Stable版（安定板）の最新バージョンは`Docker Desktop Community 2.3.0.4`です。
筆者の環境はバージョン`1909`なので、今回はEdge版をインストールします。
バージョン`2004`の方はStable版でも問題ないと思います。

以下URLにアクセスし、`Docker Desktop for Windows`のEdge版のインストーラーをダウンロードします。
https://hub.docker.com/editions/community/docker-ce-desktop-windows/

インストーラーを起動すると、最初にConfiguration画面が立ち上がります。
今回は`Docker`のバックエンドを`WSL2`にしたいので、`Install required Windows components for WSL2`にチェックが入っていることを確認します。
従来のHyper-V型は使わないので、`Enabled Hyper-V Windows Features`はオフにしておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321472/868076e1-36cf-ba40-42a8-3126ed25afd3.png)

OKボタンをクリックするとインストールが始まります。以下画面が表示されたら成功です。
`Close and log out`ボタンをクリックするとログアウトされるので、再度Windowsにログインします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321472/c490fe1e-9cf6-1bd1-2e5c-0681ff7f9fc7.png)

ログイン後、`Docker Desktop for Windows`が自動的に起動します。
しばらく待って起動に成功すると以下画面が表示されます。
`Start`ボタンをクリックするとチュートリアルが始まりますが、今回はSKIPします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321472/febea45f-1f90-2893-0e71-5d8130bfb453.png)

画面上部の歯車アイコンをクリックして、Generalの設定で`Use the WSL2 backend engine`がチェックされていることを確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321472/5ee2360b-20bf-316b-35b8-a409beeeefa2.png)

`docker`コマンドが使用できるかどうか確認します。
`PowerShell`を起動して`docker version`コマンドを実行し、バージョンが表示されるかどうか確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321472/7dc755e4-eed3-59b5-2455-71da1d8b8a9c.png)

同じく`Ubuntu`を起動して`docker version`コマンドを実行し、バージョンが表示されるかどうか確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321472/eb9b12a4-9201-298f-fa30-e417ccc20695.png)
これで、Windowsで`Docker`が使用できるようになりました！

## 3. VSCoceの拡張機能 Remote-Containers を使って、Goの開発環境を構築する

最後に、`Docker`コンテナ内で`Go`の開発を「快適に」行うための準備をしていきます。

`Docker`コンテナで開発する際のメリットとしては以下のようなものがあります。

* 開発に必要な手順が記載されている`Dockerfile`があれば、コンテナを起動するだけですぐに開発できる環境が整います。
* チーム内に`Dockerfile`を配布すれば、チーム全員が同じ環境で開発することができます。
* ランタイムやツールのインストールなどをする必要がないので、ホスト環境を汚しません。

`Remote-Containers`を使えば、上記のメリットに加えて、`VSCode`で開発作業が行えるようになるので、コードの編集はもちろん、インテリセンス、構文チェック、定義ジャンプ、デバッグなどの豊富な支援機能も使えるようになり、コンテナ内での開発が非常にスムーズになります。

それでは早速やっていきましょう。

### 3-1. VSCoceの拡張機能 Remote Developmentのインストール

まず、`VSCode`の拡張機能に、`Remote Development`をインストールします。
こちらは`Remote-WSL`と`Remote-Containers`と`Remote-SSH`のセットになっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321472/491a4155-afa8-0d21-7c88-a3ae98cc440c.png)

### 3-2. VSCodeをWSL(Ubunt)から起動する

次に、`Ubuntu`を起動します。
`mkdir`で`go-sample`フォルダを作成し、`cd`で作成したフォルダに移動してから、`code .`コマンドで`VSCode`を起動します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321472/578ce547-8032-56e2-280b-9d28c16703c9.png)

すると、`VSCode`が`WSL（Ubuntu）`の`go-sample`フォルダを開いた状態になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321472/648d59a8-9296-11a3-df76-a1cad11a0f5b.png)

### 3-3. Dockerfileの作成

次に、`go-sample`直下に`.devcontainer`というフォルダを作成します。
`.devcontainer`フォルダ内に、`Dockerfile`という名前のファイルを作成します。
内容は以下の通りにします。

> goのバージョンは1.13.15にしてますが、1.14系やlatestでも問題ないです。
> go getでインストールしているのはGo開発で必要なツール群です。
> ※コンテナをリビルドする度にインストールするのが面倒だったので、Dockerfile内に書きました。もっと良いやり方があったら教えてください！

```dockerfile:Dockerfile
FROM golang:1.13.15

RUN go get -v -u \
    github.com/mdempsky/gocode \
    github.com/uudashr/gopkgs/v2/cmd/gopkgs \
    github.com/ramya-rao-a/go-outline \
    github.com/acroca/go-symbols \
    golang.org/x/tools/cmd/guru  \
    golang.org/x/tools/cmd/gorename \
    github.com/cweill/gotests/... \
    github.com/fatih/gomodifytags \
    github.com/josharian/impl \
    github.com/davidrjenni/reftools/cmd/fillstruct \
    github.com/haya14busa/goplay/cmd/goplay \
    github.com/godoctor/godoctor \
    github.com/go-delve/delve/cmd/dlv \
    github.com/stamblerre/gocode \
    github.com/rogpeppe/godef \
    golang.org/x/tools/cmd/goimports \
    golang.org/x/lint/golint \
    golang.org/x/tools/gopls
```

### 3-4. devcontainer.jsonの作成

同じく`.devcontainer`フォルダ内に`devcontainer.json`という名前のファイルを作成します。
内容は以下の通りにします。

> settingsで設定、extentionsで拡張機能を指定できるので、お好みでどうぞ。
> 今回はGoの拡張機能をインストールして、設定をちょっと変えています。
> エディタの環境まで共有できるのですごく便利ですね！

```json-doc:devcontainer.json
{
	// VSCodeに表示されるワークスペース名
	"name": "go-sample",

	// Docker buildを実行するディレクトリ。devcontainer.jsonファイルからの相対パスで設定します。
	"context": "..",

	// コンテナの内容を定義するDockerfileのパス。devcontainer.jsonファイルからの相対パスで設定します。
	"dockerFile": "Dockerfile",

	// コンテナ側のVSCodeの設定値（setting.json）を変更したい場合、設定します。
	"settings": {
		"terminal.integrated.shell.linux": "/bin/bash",
		"go.formatTool": "goimports",
		"go.useLanguageServer": true,
	},

	// コンテナ側のVSCodeに拡張機能をインストールする必要がある場合、拡張機能のIDを配列で設定します。
	"extensions": [
		"golang.Go"
	]
}
```

### 3-5. Remote-Containersでコンテナにリモート接続する

次に、左下にある`>< WSL: Ubuntu`をクリックし、`Remote-Containers: Reopen in Container`を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321472/cafbdee4-565b-6141-8ac6-3aee78b1f43f.png)

`VSCode`が再読込され、コンテナ内で起動します。
先程`WSL(Ubuntu)`側で作成したフォルダ／ファイルがマウントされているのがわかります。
※初回はコンテナの作成に時間がかかりますが、次回からはもう少し早く起動すると思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321472/ffd5202f-1d70-9dc9-4e18-a09a2828c338.png)

ターミナルで`Bash`を起動し、`go version`を実行します。
`Go`のバージョン`1.13.15`がインストールされていることを確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321472/93f7d6d4-81e2-9e7f-d53e-e6c8457902dd.png)

左下の歯車アイコンから設定を開き、`@modified`と入力して変更した設定のみを表示します。
`devcontainer.json`の`settings`で記載した内容が、リモート側の`VSCode`設定に反映されていることを確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321472/d9c7546d-1c51-4441-3fa4-2b645f1ae7c9.png)


拡張機能はローカルにインストールするものとリモート側にインストールするものとで管理が分かれます。
`devcontainer.json`の`extensions`で設定した`Go`の拡張機能がリモート側にインストールされていることを確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321472/1d136c99-1b39-b390-3a39-1e02865d751c.png)

`Dockerfile`でインストールするよう設定した`Go`のツール群もインストールされています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321472/3553a047-39f2-194b-72d8-92d598181aaf.png)

`Docker Desktop`でもイメージが作成されていることが確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321472/9efc9c43-2285-ef0c-d2b9-7ebf1fbccc4d.png)

コンテナも作成され、起動中（RUNING）になっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321472/0d181585-22af-8fcb-7881-01faa512b7a3.png)

### 3-6. コンテナ内でのGo開発（hello world）

環境が整ったので、コンテナ内で`Go`の`hello world`を作ってみます。
ワークスペース直下に`main.go`という名前のファイルを作成します。
内容は以下の通りにします。
> コピペでなく自分で入力すると、インテリセンスやスニペットなどの支援機能が効いていることが実感できると思います。

```golang:main.go
package main

import "fmt"

func main() {
	fmt.Println("hello world")
}
```

ターミナルでBashを開き、`go run main.go`を実行します。`hello world`と出力されれば成功です！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321472/af0e50f8-6956-ec15-baf2-76148c582682.png)

デバッグもやってみます。
`main.go`のソース上で`fmt.Println("hellow world")`にブレイクポイントを設定し、`F5`キーを押します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321472/951f9384-9b79-375a-7532-f2b0b665b08e.png)

しばらくするとデバッグが実行され、設定したブレイクポイントで止まります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321472/92299066-09c7-3146-9530-cc5ff28bda5a.png)

デバッグツールバーのステップオーバーをクリックするか、`F10`キーを押すと次のステップへ進みます。
`fmt.Plintln`が実行されて、デバッグコンソールに`hello world`を出力されることが確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321472/7d787729-901a-4d46-9f70-921365018a96.png)

これで`Go`のデバッグが出来るようになりました！
※デバッグを終了させる場合は再度`F5`キーを押してください。

### 3-7. コンテナへのリモート接続を終了する

コンテナへのリモート接続を終了させる場合は、左下の`>< Dev Container`から`リモート接続を終了する`を選択するか、右上の`×`ボタンで`VSCode`を閉じてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321472/6f0e0cb9-9f5b-280a-ad2e-20414461f7c1.png)

### 3-8. コンテナへのリモート接続を再開する

再度コンテナへのリモート接続をする場合は、`WSL(Ubunt)`で`go-sample`フォルダを開き、左下の`>< WSL: Ubuntu`から`Remote-Containers: Reopen in Container`を選択してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321472/bcc07e4a-df81-cfc6-a9ed-4dd6512840c3.png)

初回起動では32.373秒かかりましたが、2回目は18.217秒で起動できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321472/a1f6cf2f-81c4-93db-6db2-524e46909b37.png)

### 3-9. （おまけ）Windowsから直接Remote-Containersでコンテナにリモート接続する

今回は`WSL(Ubuntu)`内にソースコードを配置して、`WSL(Ubuntu)`から`VSCode`の`Remote-Containers`でコンテナを起動しましたが、`Windows`上にソースコードを配置して、`Windows`からコンテナを起動することもできます。

やり方は`WSL(Ubuntu)`の時と同じで、`3-3`～`3-5`の手順を`Windows`上で行うだけです。
ソースコードは`Windows`で管理したいよ！っていう方はその方法でも良いかもです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321472/5e150907-2016-14e4-d728-3676df751f23.png)

初回起動では39.594秒かかりました。WSL（32.373秒）より若干遅い？です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321472/229238d8-5c7e-2ba2-7aa4-bbde51bb4e6e.png)

注意点としては、`Windows`からコンテナを起動すると、`Docker Desktop`が以下の警告を表示します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321472/052746eb-1879-bd71-7d00-03054404a94d.png)

> Docker Desktopは、WindowsファイルをWSL2コンテナーに共有したことを検出しました。これはパフォーマンスが低下する可能性があります。

https://docs.docker.com/docker-for-windows/wsl/#best-practices
上記URLの`Docker Desktop WSL2 Backend`のベストプラクティスにもあるように、Linuxファイルシステムからマウントされるとパフォーマンスが大幅に向上するようです。Windowsファイルシステムからコンテナを起動するのは避けるように、ともあるので、気になる方は`WSL`でソースコードを管理したほうがよさそうです。

## まとめ

`WSL2`と`Docker Desktop for Windows`と`VSCode`の相性が良く、`Windows`でも`Docker`開発がスムーズに行えるようになってきました。
`Dockerfile`で開発環境を共有できるのはもちろんですが、`devcontainer.json`で`VSCode`の設定や拡張機能まで共有できるのもいいですね！
※`Go`はまだいいですが、`React`や`Vue`などのフロントエンドの開発だと`ESLint`と`Prettier`の設定などでかなり労力を持っていかれるので、、、
これからは開発で積極的に取り入れていきたいと思います！