【Windows】Docker Desktop for WindowsとVagrantの便利な環境を共存させる #1/3【Hyper-V】

# はじめに
最近、Windows 10にアップグレードしたことに伴い、ローカルの仮想マシン用のハイパーバイザを、もともと利用していたOracle VirtualBoxからHyper-Vに乗り換えました。
VirtualBoxとHyper-Vを切り替えて使うという選択肢もゼロではありませんでしたが、切り替えにはOS再起動を伴うので少々面倒です。
Windows 10でも引き続きVirtualBoxを使うという選択肢もありましたが、Docker Desktop for Windowsも使ってみたかったのでHyper-Vに乗り換えました。

# やりたかったこと
Vagrant+VirtualBoxの環境を長年使っておりますが、VagrantはVirtualBoxをProviderとした場合、かなり柔軟にいろいろ設定が出来ます。
Hyper-Vに移行した場合でも、VMの操作については同様の使用感を実現したい、というのが大きな目的です。
要件を整理すると下記のようになりました。

- Docker Desktop for Windowsを使う
    - 各Docker container内からInternetへ接続できるようにする
    - PublishしたPortは、ホストOSからの接続・ネットワーク外部のホストからの接続ができるようにする
    - Docker Containerが稼働するOS（MobyLinuxVM）のネットワークは192.168.252.0/23にする
- Hyper-Vの仮想マシン（VM）を普通に使う
    - 各VMからInternetへ接続できるようにする（WinNATを利用）
    - 各VM間で通信できるようにする
    - VagrantでVMを作成・破棄できるようにする
    - ホストOSから任意のターミナルソフト（自分の場合はTeraTerm）を使って、VMにSSH接続する
    - Hyper-VのVMが稼働するネットワークは192.168.254.0/23にする
    - Vagrantfile内の設定をもとにVMに固定のプライベートIPアドレスを付与する（**できない**）
- 上記の環境を共存させる
- 両環境のネットワークは可能であれば分離する（した）


# TL;DR
- Docker Desktop for Windowsについて
    - Docker Desktop for Windowsのセットアップは、特に難しいことを追加でやる必要はない
    - Docker Desktop for Windowsで起動したコンテナからInternetへの接続、「-p」オプションで公開したポートへの他のPCからの接続も特に難しいことは無い
    - Docker Desktop for WindowsのNATの仕組みは詳細不明（※詳細未調査。ただし、WinNATとの併存はできる模様）
- Hyper-Vの仮想ネットワークについて
    - Hyper-Vの仮想マシンからInternetへ接続できるようにするには、幾つか手作業での設定（PowerShellベースの操作）が必要
    - Hyper-VのVM用ネットワークはWinNATを適用する
    - Hyper-Vの仮想ネットワークは、VirtualBoxやVMWareと比べて機能が少ない（NAT/NAPT/DHCPサーバ機能は無い）ので、いろいろと自前で設定する必要がある
    - VagrantはHyper-Vの仮想ネットワークを操作する機能が無い（※少なくとも2019/3/18現在の最新版である2.2.4では）
    - 「Default Switch」という機能が実装され、NAT/NAPT/DHCPサーバ機能が実装されているが、うちの環境では不安定で使えなかった（コントロールパネルのNetwork Connectionsに表示されたり消えたりを繰り返す）


# 最終的なネットワーク構成
試行錯誤の結果、最終的に下記のような構成に落ち着きました。
![DockerDesktopForWindowsWithVagrantWithHyperV_01.PNG](https://qiita-image-store.s3.amazonaws.com/0/137777/9f783b54-2a9f-35a3-daf2-2657b7637584.png)


構成のポイントは下記のとおりです。

- Docker Desktop for Windows用のネットワーク（DockerNAT）
    - 192.168.252.0/23のネットワーク
    - 対応するVirtual Switch名は「DockerNAT」（※インストール時に自動で生成される）
    - ホストOS側のIPアドレスは192.168.252.1/23
    - Docker container用VM（MobyLinuxVM）のIPアドレスは192.168.252.2/23（※今回の観測上はこの値であった。このIPアドレスがどのように決定・設定されるかは不明。）
    - Docker container内からInternetへの接続が可能
- Hyper-V通常VM用のネットワーク（Hyper-V Internal with WinNAT）
    - 192.168.254.0/23のネットワーク
    - 対応するVirtual Switch名は「Hyper-V Internal with WinNAT」（※任意に命名可）
    - ホストOS側のIPアドレスは192.168.254.1/23
    - ゲストOS側（上図のstdsv3）のIPアドレスはネットワークレンジ内であれば任意の値を設定可能。今回は192.168.254.13/23を設定。
    - WinNATの機能を利用し、このネットワーク内と外部ネットワークとの通信はNATするよう設定


実際に使う上で必要な通信パターンは、実質的に以下の4つとなりました。

- ①各Docker containerからInternetへ接続
- ②外部のホストから、Publishされたポートを経由してDocker containerに接続
- ③各VMからPublishされたポートを経由してDocker containerに接続
- ④各VMからInternetへ接続

これを図示したものが下記となります。
![DockerDesktopForWindowsWithVagrantWithHyperV_02.PNG](https://qiita-image-store.s3.amazonaws.com/0/137777/cfe29cc6-ff50-9d61-6b65-acded3dd0d17.png)


# 環境
本稿は以下の環境で検証しました。

- OS: Windows 10 Pro 10.0.17763 Build 17763
- GitBash 2.21.0
- Vagrant 2.2.4
- Docker Desktop for Windows Community Edition (CE):
    -  Version: 2.0.0.3 (31259)
    -  Channel: stable
    -  Build: 8853db3

# 概要
全体の流れは大きく以下のようになります。分量が多いので記事は分割しました。

- [【Windows】Docker Desktop for WindowsとVagrantの便利な環境を共存させる #1/3【Hyper-V】](https://qiita.com/tmiki/items/bdc7ff43b3d8d636ec8f)　←**いまココ**
    - Docker Desktop for Windowsをセットアップする
    - Docker container内からInternetへの疎通を確認する
- [【Windows】Docker Desktop for WindowsとVagrantの便利な環境を共存させる #2/3【Hyper-V】](https://qiita.com/tmiki/items/d5c63a2ca0573e01b321)
    - Hyper-V通常VM用のネットワーク（Hyper-V Internal with WinNAT）を設定する
    - Hyper-V通常VM用のネットワーク（Hyper-V Internal with WinNAT）からの疎通を確認する
- [【Windows】Docker Desktop for WindowsとVagrantの便利な環境を共存させる #3/3【Hyper-V】](https://qiita.com/tmiki/items/1d5edb4e482f0f38bfd2)
    - Hyper-V通常VM用のネットワーク設定を自動化する

# 手順
## Docker Desktop for Windowsをセットアップする
### Docker Desktop for Windowsのインストール
まずは、Docker Desktop for Windowsをインストールします。
インストール手順は下記公式ドキュメントに書いてありますので、これに従います。

Install Docker Desktop for Windows | Docker Documentation
https://docs.docker.com/docker-for-windows/install/

なお、Docker Desktop for Windowsをダウンロードするにあたり、Docker Hubのアカウント（というよりDocker ID）が必要となりますので、事前に登録しておきます。

Docker ID accounts | Docker Documentation
https://docs.docker.com/docker-id/

### Hyper-Vからの見え方を確認する
インストールが完了し、Docker Desktop for Windowsの実行が完了したら、Hyper-V Managerを開きます。
下記のように「Virtual Machines」の一覧に「MobyLinuxVM」というインスタンスが追加されていることが確認できます。
![DockerDesktopForWindows_HyperV_01.png](https://qiita-image-store.s3.amazonaws.com/0/137777/0005675e-8f79-29d3-b54d-4986f798932c.png)

「MobyLinuxVM」を選択し、下のペインの「Networking」タブを押すと、「DockerNAT」というネットワーク（正確にはVirtual Switch）に接続されていることを確認できます。
![DockerDesktopForWindows_HyperV_02.png](https://qiita-image-store.s3.amazonaws.com/0/137777/cc0a3ae7-f509-3534-743a-9f2e44df5fc0.png)

当該Virtual Switchの詳細を見てみましょう。右の「Actions」ペインから「Virtual Switch Manager...」をクリックします。下記のようなウィンドウがポップアップします。
「DockerNAT」というVirtual Switchがあるので、これをクリックします。
「Connection type」が「Internal Network」となっていることが確認できます。
![DockerDesktopForWindows_HyperV_03.png](https://qiita-image-store.s3.amazonaws.com/0/137777/dfb5186d-6931-b0d8-fb86-5d0597b018a8.png)

なお、Hyper-Vで作成できるVirtual Switchには3種類あります。挙動の違いは下記ドキュメントを参照してください。

Create a virtual switch for Hyper-V virtual machines | Microsoft Docs
https://docs.microsoft.com/en-us/windows-server/virtualization/hyper-v/get-started/create-a-virtual-switch-for-hyper-v-virtual-machines

### Docker Desktop for Windowsで利用するネットワークセグメントの変更
次に、タスクトレイのDockerアイコンから、「Settings」を開きます。
デフォルトでは下記の設定になっていると思います。

- Subnet Address: 10.0.75.0
- Subnet Mask: 255.255.255.0

![DockerDesktopForWindows_Settings_Network_01.png](https://qiita-image-store.s3.amazonaws.com/0/137777/e64fb225-2888-735c-686b-6de9421170a7.png)

この値を下記のように変更したのち、Applyボタンを押します。
Networkの設定変更、Docker container用VM（MobyLinuxVM）、Hyper-VのVirtual Switchともに作り直しになるようで、かなり時間がかかるので気長に待ちます。体感では、十数分ぐらいといったところでしょうか。

- Subnet Address: 192.168.252.0
- Subnet Mask: 255.255.254.0

![DockerDesktopForWindows_Settings_Network_02.png](https://qiita-image-store.s3.amazonaws.com/0/137777/71e352f3-674c-341d-953f-d7c25cd45d17.png)

以上で、Docker Desktop for Windowsのセットアップは完了です。

### PowerShellからDocker Desktop for Windows用のネットワーク（DockerNAT）の設定を確認する
PowerShellからネットワーク関連の設定を参照することが可能です。
PowerShellをAdministrator権限で実行し、下記コマンドレットを実行してみます。


#### Get-VMSwitch
Get-VMSwitchで、存在するVirtual Switchの一覧を表示します。

```powershell:Get-VMSwitch
PS C:\WINDOWS\system32> Get-VMSwitch

Name      SwitchType NetAdapterInterfaceDescription
----      ---------- ------------------------------
DockerNAT Internal
```

#### Get-NetAdapter
Get-NetAdapterで、存在するNIC（Network Adapter）の一覧を表示します。NICには「ifIndex」というUniqueなIDが付与されるようです。NICに何か設定変更をしたい場合、このifIndexの値で指定することが多いです。
Docker Desktop for Windowsのインストールにより、「vEthernet (DockerNAT)」というNICが自動で作られています。このNICの「ifIndex」は「26」であることが分かります。

```powershell:Get-NetAdapter
PS C:\WINDOWS\system32> Get-NetAdapter

Name                      InterfaceDescription                    ifIndex Status       MacAddress             LinkSpeed
----                      --------------------                    ------- ------       ----------             ---------
ローカル エリア接続       Realtek PCIe GBE Family Controller            9 Up           80-EE-73-2E-ED-3E         1 Gbps
vEthernet (DockerNAT)     Hyper-V Virtual Ethernet Adapter             26 Up           00-15-5D-14-25-0E        10 Gbps
```

#### Get-NetIPaddress
Get-NetIPaddressで、NICに付与されたIPアドレスを表示します。192.168.252.1が付与されていることが分かります。

```powershell:Get-NetIPaddress
PS C:\WINDOWS\system32> Get-NetIPAddress -InterfaceIndex 26


IPAddress         : 192.168.252.1
InterfaceIndex    : 26
InterfaceAlias    : vEthernet (DockerNAT)
AddressFamily     : IPv4
Type              : Unicast
PrefixLength      : 23
PrefixOrigin      : Manual
SuffixOrigin      : Manual
AddressState      : Preferred
ValidLifetime     : Infinite ([TimeSpan]::MaxValue)
PreferredLifetime : Infinite ([TimeSpan]::MaxValue)
SkipAsSource      : False
PolicyStore       : ActiveStore
```


## Docker container内からInternetへの疎通を確認する
では実際に、Docker containerからInternetへ接続できるかどうかを確認していきます。
CentOS 7のDocker imageをもとにcontainerを実行し、net-utils及びbind-utilsをインストールして疎通を確認していきます。

### CentOS 7のDocker imageをもとにcontainerを実行する
PowerShellを開き、「-ti」オプション付きでdocker runを実行し、プロセスとしてbashを起動させます。初回はDocker imageのダウンロードが行われるので、少し時間がかかるでしょう。
containerの実行が完了するとcontainerのシェルを操作できるようになります。

```powershell
PS C:\WINDOWS\system32> docker run -ti centos bash
[root@7381948a82a5 /]#
```

### net-utils及びbind-utilsをインストールする
containerのシェルからyum installを実行します。

```bash
[root@7381948a82a5 /]# yum install net-tools bind-utils -y
Loaded plugins: fastestmirror, ovl
Determining fastest mirrors
 * base: ftp.jaist.ac.jp
 * extras: ftp.jaist.ac.jp
 * updates: ftp.jaist.ac.jp

# ※途中省略

Running transaction
  Installing : GeoIP-1.5.0-13.el7.x86_64                                                                            1/4
  Installing : 32:bind-libs-9.9.4-73.el7_6.x86_64                                                                   2/4
  Installing : 32:bind-utils-9.9.4-73.el7_6.x86_64                                                                  3/4
  Installing : net-tools-2.0-0.24.20131004git.el7.x86_64                                                            4/4
  Verifying  : 32:bind-utils-9.9.4-73.el7_6.x86_64                                                                  1/4
  Verifying  : GeoIP-1.5.0-13.el7.x86_64                                                                            2/4
  Verifying  : net-tools-2.0-0.24.20131004git.el7.x86_64                                                            3/4
  Verifying  : 32:bind-libs-9.9.4-73.el7_6.x86_64                                                                   4/4

Installed:
  bind-utils.x86_64 32:9.9.4-73.el7_6                    net-tools.x86_64 0:2.0-0.24.20131004git.el7

Dependency Installed:
  GeoIP.x86_64 0:1.5.0-13.el7                             bind-libs.x86_64 32:9.9.4-73.el7_6

Complete!
```

### containerのネットワーク設定を確認する・Internetへの接続を確認する
IPアドレス設定・ルーティングテーブルなどの設定を確認し、Internetへの接続ができるかどうかを確認します。


ifconfigコマンドを実行し、付与されたIPアドレスを確認します。今回は172.17.0.2/16が付与されていることを確認できます。

```bash:ifconfig
[root@1787083910dc /]# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.2  netmask 255.255.0.0  broadcast 172.17.255.255
        ether 02:42:ac:11:00:02  txqueuelen 0  (Ethernet)
        RX packets 8790  bytes 12845100 (12.2 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 3116  bytes 172364 (168.3 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        loop  txqueuelen 1  (Local Loopback)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
```

netstat -rnでルーティングテーブルを見てみます。172.17.0.1がDefault Gatewayになっていることが確認できます。

```bash:netstat&nbsp;-rn
[root@1787083910dc /]# netstat -rn
Kernel IP routing table
Destination     Gateway         Genmask         Flags   MSS Window  irtt Iface
0.0.0.0         172.17.0.1      0.0.0.0         UG        0 0          0 eth0
172.17.0.0      0.0.0.0         255.255.0.0     U         0 0          0 eth0

```

適当なInternet上のホストにpingします。ここでは8.8.8.8を利用します。

```bash:ping
[root@1787083910dc /]# ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=37 time=18.3 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=37 time=14.3 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=37 time=14.1 ms
^C
--- 8.8.8.8 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 14.156/15.612/18.334/1.931 ms
```

キャッシュされたARPテーブルを見てみます。Default Gatewayである172.17.0.1のMACアドレスが保持されていることを確認できます。

```bash:arp&nbsp;-a
[root@1787083910dc /]# arp -a
gateway (172.17.0.1) at 02:42:e0:c7:e4:c3 [ether] on eth0
```

名前解決ができるかどうかdigコマンドを利用して試してみます。問題なく名前解決することができました。
併せてDNSリゾルバとして192.168.65.1が利用されていることが確認できます。

```bash:dig
[root@1787083910dc /]# dig google.com

; <<>> DiG 9.9.4-RedHat-9.9.4-73.el7_6 <<>> google.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 199
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;google.com.                    IN      A

;; ANSWER SECTION:
google.com.             291     IN      A       216.58.197.206

;; Query time: 14 msec
;; SERVER: 192.168.65.1#53(192.168.65.1)
;; WHEN: Sun Mar 17 06:14:05 UTC 2019
;; MSG SIZE  rcvd: 55
```

resolv.confの内容を確認してみます。やはり192.168.65.1が指定されていることが確認できました。

```bash:/etc/resolv.conf
[root@1787083910dc /]# cat /etc/resolv.conf
# This configuration is written to the config.iso
nameserver 192.168.65.1
search ******** # ホストOSの設定が反映される
domain ******** # ホストOSの設定が反映される
```


## Docker container外のホストからcontainerへの疎通ができるかどうか確認する
最後に、適当なDocker conainerを起動させ、ホストOS・他のPCから接続できるかどうかを確認します。
nginxのイメージを使います。

### nginxのDocker imageをもとにcontainerを実行する
PowerShellを開き、nginxのイメージを指定してdocker runを実行します。今回は、公開するPortを8080、container側のPortを80として実行します
実行後、エラー等が表示されなければ恐らく成功です。

```powershell
PS C:\Users\mta> docker run -p 8080:80 nginx
```

### ホストOSからnginxへの疎通を確認する
ホストOSでGitBashを実行し、curl等でTCP/8080にHTTPアクセスして、nginxのWelcomeページが返却されるかどうか確認します。
IPアドレスは、127.0.0.1、物理NICに振られているアドレス（今回の場合は10.1.1.34）それぞれ確認します。

```:127.0.0.1
$ curl http://127.0.0.1:8080/
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
```

```:物理NIC
$ curl http://10.1.1.34:8080/
<!DOCTYPE html>

※途中省略

</html>
```


以上で、Docker containerの疎通確認は終了です。container内からInternetへの接続ができること、ホストOSからcontainerに接続できることを確認しました。
外部のほかのホストからcontainerに接続できるかどうかについては、次の作業で確認します。

ちなみにcontainerから見えるネットワーク設定は、LinuxにDockerをインストールした場合と変わらないようです。


## Hyper-V通常VM用のネットワーク（Hyper-V Internal with WinNAT）を設定する
※続きます

[【Windows】Docker Desktop for WindowsとVagrantの便利な環境を共存させる #2/3【Hyper-V】](https://qiita.com/tmiki/items/d5c63a2ca0573e01b321)