Windows10 ProでHyper-V(Ubuntu) + VS Code Remote DevelopmentでRails開発環境を作る

## 前提

Windows10 Pro 上でRails開発環境作るにはいくつか選択肢がありますが、本記事では以下の方法について説明します。

- Hyper-VでUbuntu 18.04 を動かす
- Rails開発環境はUbuntuの中に構築
- 開発はWindows上でVSCodeを起動し、Remote Development Extension で Ubuntuにssh接続して行う

## Windows: Hyper-Vの有効化とUbuntuのインストール

Hyper-Vでは仮想マシンを「クイック作成」で作ることもできますが、デフォルトだとディスクサイズが小さい（10GB）であるため、開発環境として使うのであれば通常の作成方法で行くほうが良いでしょう。以下の記事通りに作業を進めればインストールできます。メモリとディスクの作成は各自必要なサイズを確保してください。

[Windows 10 Pro Hyper\-V に Ubuntu 18\.04 LTS をインストール \- Qiita](https://qiita.com/mfunaki/items/9e2901936b04f00f9cd7)

Ubuntuのインストーラについては、日本国内のミラーサイトから落とすと時間短縮になると思います。たとえば理研のミラーサイトから18.04を落とすなら以下のURLになります。

http://ftp.riken.jp/Linux/ubuntu-releases/bionic/

他のミラーサイト一覧は以下にあります。

https://launchpad.net/ubuntu/+cdmirrors?_ga=2.16459574.1287987484.1561604204-225884078.1561604204

Ubuntuの日本語Remixを利用する方法もありますが、今回構築する開発環境ではWindows上でVSCを利用するため、日本語化するにしてもターミナルのメッセージが日本語化される程度で十分でしょうから、標準のインストーラで問題ないと思います。

インストールが完了したら、VMは終了したままにしておきましょう。

## Windows: 仮想ネットワークの追加作成

自分が調べた限りでは、どうやらHyper-Vではネットワーク側でDHCPを切るということができないようです。そのため、仮想スイッチに設定されているアドレスの範囲で勝手に固定IPにするしかないようです。

が、Windows10 1809では最初から準備されている Default Switchのネットワークアドレスが***勝手に変わる***という何とも致命的なバグがあるようで、新規にNATネットワークを作るという方法で対処する必要があります。

[Hyper\-V Default switch IP address range change\. Ver 1809 Build 17763\.1 \- Microsoft Tech Community \- 261431](https://techcommunity.microsoft.com/t5/Windows-Insider-Program/Hyper-V-Default-switch-IP-address-range-change-Ver-1809-Build/td-p/261431)

NATネットワークのセットアップ方法は以下に掲載されています。Hyper-Vマネージャで内部仮想スイッチを作るだけではNATネットワークにならないようなので注意しましょう。なお、以下のヘルプには日本語ページもありますが、機械翻訳されていてところどころ表現が怪しいです。可能であれば英語版のほうを参照するのが良いでしょう。

- [Set up a NAT network \| Microsoft Docs](https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/user-guide/setup-nat-network)
- [NAT ネットワークのセットアップ \| Microsoft Docs](https://docs.microsoft.com/ja-jp/virtualization/hyper-v-on-windows/user-guide/setup-nat-network)

上記ページに書いてある手順をコマンドにまとめると以下のようになります。ここでは192.168.99.1をネットワークインタフェースに割り当て、192.168.99.0/24 のネットワークをNATの内側のネットワークとしています。

```powershell
New-VMSwitch -SwitchName "NewNAT" -SwitchType Internal
Get-NetAdapter # NewNATのifIndexを記録し、次の行のIFINDEXを置き換える
New-NetIPAddress -IPAddress 192.168.99.1 -PrefixLength 24 -InterfaceIndex IFINDEX
New-NetNat -Name MyNATnetwork -InternalIPInterfaceAddressPrefix 192.168.99.0/24
```

## Windows: ネットワークインタフェースに新規作成したネットワークを接続する

ここで先ほど作成した仮想ネットワークにVMのネットワークインタフェースを接続します。このとき、VMはシャットダウン状態である必要があります。
![qiita_hyper-v.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5974/8b19e72f-8cab-340d-5367-5f07cf7b5417.png)


## Ubuntu: 固定IP設定

まずは固定IPの設定をしましょう。Ubuntuを起動し、GUI接続してターミナル上で作業を行いましょう。

Hyper-V上のUbuntuでは `eth0` としてネットワークインタフェースが見えていますので、`eth0` に対して固定IPを設定します。

Ubuntu 18.04ではnetplanを利用してネットワークインタフェースの設定を管理しています。デフォルトではnetplan経由でNetwork Managerに移譲していますが、ここではデフォルトの設定を削除してnetplanで直接固定IPを設定します。具体的な手順は以下の通りです。

```bash
cd /etc/netplan
rm 01-network-manager-all.yaml
vim fixed.yaml # エディタはお好きなものを
```

以下はnetplan用のyamlファイル記述例です。ネットワークアドレスが192.168.99.0/24で、NATネットワークのゲートウェイアドレスが192.168.99.1、VMのアドレスが192.168.99.2の場合の設定です。

```yaml
# /etc/netplan/fixed.yaml
network:
  version: 2
  renderer: networkd
  ethernets:
    eth0:
      dhcp4: no
      dhcp6: no
      addresses: [192.168.99.2/24]
      gateway4: 192.168.99.1
      nameservers:
        addresses: [192.168.99.1]
```

ファイルの編集が終わったら、以下のコマンドで設定を反映させ、正しく設定できているかどうかを確認し、実際に通信がうまくいくかも確認しましょう。

```bash
sudo netplan apply
ip a s eth0          # 192.168.99.2が設定されていればよい。
ip r                 # gatewayが192.168.99.1になっていることをチェック 
```

## Ubuntu: openssh-serverのインストール

固定IPの設定が終わったら、OpenSSHサーバーのインストールを行います。

```bash
sudo apt update 
sudo apt install -y openssh-server
```

今回は開発マシン上のVMということで設定はデフォルトのままにしてこの後の作業を進めますが、カスタマイズしたい方は適宜以降のステップに変更内容を反映してください。

## Windows: VSCode Remote Development Extension のインストール

ここでVSCodeを起動し、Remote Development Extensionをインストールします。

![qiita_vscode_remote_extension.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5974/e05adc8d-49d8-358b-8081-dc4b738a7495.png)

## Windows: ssh configの設定

VMのアドレスを固定できたら、次に ssh configファイルの設定を行います。Remote Development Extensionをインストールしていると、ファイルを編集するためのコマンドが用意されていますので、そちらをつかいましょう。

![qiita_vscode_ssh_config.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5974/27255064-81dc-bc4c-4863-864a16ba9f9c.png)

コマンドを選択すると編集するファイルの候補がでてきます。ここではWindows上のホームフォルダ直下にある `.ssh/config` を選択して進めます。

必要な設定は以下のようになります。

```
Host vm
  HostName 192.168.99.2
  User YOUR_USERNAME
  IdentityFile "~/.ssh/ubuntu"
```

`YOUR_USERNAME` はUbuntu上で作成したユーザの名前を入れてください。`IdentityFile`はVM接続時に利用するssh鍵の指定で、次のセクションで説明します。


## Windows: SSH鍵の生成と公開鍵のコピー

Remote Development Extensionではsshを利用して外部ホストに接続します。このときに使うssh鍵は安全性と利便性のバランスを取り、VM接続専用の鍵をパスフレーズなしで作成しておくのが良いでしょう。

### PowerShellで鍵作成する場合


```shell
cd .ssh
ssh-keygen.exe -t ecdsa -f ubuntu
```

`-t` オプションの引数はお好みに合わせてください。ここでは`ecdsa`を指定しています。このコマンドを実行すると、`.ssh` 以下に `ubuntu`, `ubuntu.pub` という２つのファイルが生成されます。

ここで生成された `ubuntu.pub` をVM上の `~/.ssh/authorized_keys` に記載する必要があるのですが、残念ながらWindows10 Proには `ssh-copy-id` コマンドが入っていません。GUI経由で手動で張り付けるか、一時的に `~/.ssh/config` で `IdentityFile` の行をコメントアウトしてパスワード接続した後、VSCode上のターミナルから張り付けてもいいでしょう。

### Ubuntu for WSLで鍵作成＆公開鍵コピーする場合

WSL for Ubuntuがインストール済みであれば、`ssh-copy-id` を利用することでssh公開鍵のコピーも行えます。

```bash
cd /mnt/c/Users/$USER/.ssh/
ssh-keygen -t ecdsa -f vm
ssh-copy-id -i ubuntu vm 
```

## Windows: VSCodeからVMに接続する

ここまでの設定でWindows上のVSCodeからVM上のUbuntuにssh接続が可能になります。ssh接続する前に、設定画面で `Show Login Terminal` のオプションを有効にしておきましょう。これを有効にしておかないとVMへの初回接続時に `.ssh/known_hosts`へのホスト鍵登録のダイアログに対する操作が行えず、接続できなくなります。

![qiita_vscode_ssh_terminal_option.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5974/14108e95-770f-cfc5-5a4c-2f1b97d43dd0.png)

設定後、コマンドパレット経由で `Connect to Host`を選択します。

![qiita_vscode_ssh_connect_host.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5974/6ff7d606-0e88-b4a5-d642-197e914fd130.png)

コマンド選択後、`~/.ssh/config`に記載されたホストの一覧がでてきます。先ほど設定した `vm` を選択しましょう。

![qiita_vscode_ssh_host_selection.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5974/58c94530-3108-7dbb-c8a3-2f208dd95d6a.png)

これで無事にVMにssh接続ができれば作業完了です。

## Tips

### tmuxを使おう

ssh接続した状態でVSCodeのウィンドウを閉じると、VSCode上で開いていたUbuntu上のターミナルはすべて切断されます。特に初期はエクステンションのインストール後に必要なreloadでウィンドウを閉じることが多いので、まずtmuxをインストール＆設定してターミナル上の作業はtmux経由で行うようにしましょう。

なお、tmux上のテキストをコピーする際、Windows上のようにマウスで選択してCtrl + C してもクリップボードにコピーができません。Shiftキーを押しながらマウスでセレクトすることでコピーが可能になります（選択済みテキストの反転色が異なるので視覚的に判別が可能）。

### VMにインストールされるエクステンションはホスト単位

Remote Developmentを利用すると、UI関係以外のエクステンションがリモートホスト（この例ではVM）にインストールされます。このとき、エクステンションはフォルダやワークスペース毎ではなくホスト単位でインストールされるようです。ワークスペース毎にエクステンションの有効・無効を切り替えたい場合は、ローカルでの開発と同様ワークスペースの設定で行う必要があります。

## Q&A

### Hyper-Vを選んだ理由は？

WSL1はファイルシステムのパフォーマンスが悪いというのと、Dockerが利用できないという問題があります。

現在Insider Previewで提供されているWSL2はHyper-Vで動作し、ファイルシステム絡みのパフォーマンスも改善されているということですが、WSL自体が起動されないとLinuxが動作しないという設計になっています。systemdなども使えないことから、それであれば普通に仮想マシンを利用するほうが開発環境としては自由度が高くて良いと判断しました。また、WSL2用のlinux kernelに適用された最適化はがLinux本体にも反映されるようであれば、WSL2同様の恩恵をHyper-Vの仮想マシンでも受けることができるようになるでしょう（実際にいつそうなるかどうかは不明）。

VSCode Remote Developmentを前提とした場合、sshで接続できるリモートホスト（現時点ではLinux x86のみ）であれば良いため、接続先のパフォーマンスが高くなるであろうものを選びました。Hyper-V移行まではVirtualBoxを使っていましたが、Hyper-VのほうがI/Oパフォーマンスやバッテリー消費などの点で上という印象を持っています。