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

※本稿は以下の一連の記事のうちの一つです。
[【Windows】Docker Desktop for WindowsとVagrantの便利な環境を共存させる #1/3【Hyper-V】](https://qiita.com/tmiki/items/bdc7ff43b3d8d636ec8f)
[【Windows】Docker Desktop for WindowsとVagrantの便利な環境を共存させる #2/3【Hyper-V】](https://qiita.com/tmiki/items/d5c63a2ca0573e01b321)
[【Windows】Docker Desktop for WindowsとVagrantの便利な環境を共存させる #3/3【Hyper-V】](https://qiita.com/tmiki/items/1d5edb4e482f0f38bfd2)　←**いまココ**



## Hyper-V通常VM用のネットワーク設定を自動化する
ここまでの作業で、Hyper-Vの仮想ネットワークの設定・WinNATの設定・VagrantでVMインスタンスを立ち上げる、ということができました。
今回設定した仮想ネットワークにはDHCP機能がないため、VMインスタンスのIPアドレスは自動では設定されません。
これを自動化するためには、大きく以下の2通りの方法があります。

- VagrantによるVMインスタンス構築時、IPv4設定を併せて行う
- ホストOS側でDHCPサーバを稼働させ、「Hyper-V Internal with WinNAT」ネットワークに所属するゲストOSがDHCPでIPv4設定できるようにする


### VagrantによるVMインスタンス構築時、IPv4設定を併せて行う
まずはこちらの方が簡単です。
Vagrantの「Shell Provisioner」の機能を使います。

Shell Scripts - Provisioning - Vagrant by HashiCorp
https://www.vagrantup.com/docs/provisioning/shell.html

Vagrantfileに下記のように「config.vm.provision」から始まる2行を追加します。Optionとして「inline」を指定し、実行コマンドを直接書きます。
Optionとして「path」を指定し、シェルスクリプトファイルを指定することが可能ですが、今回は使えません。「path」を指定する場合、実行前にファイルをVM内にSSH経由でコピーするという動作になるようで、ネットワークの設定ができていない状態ではファイルのコピーに失敗するからです。

```:Vagrantfile(抜粋)
  # 下記2行を追加
  config.vm.provision "shell",
    inline: "ip address add 192.168.254.13/23 dev eth0; route add default gw 192.168.254.1"
```

先ほど作ったVagrantfileに追加すると、下記のようになるでしょう。

```:Vagrantfile
Vagrant.configure("2") do |config|
  config.vm.box = "Yojimbo108/AmazonLinux2"

  config.vm.provider "hyperv" do |v|
    v.memory = 1024
    v.cpus = 1
  end

  config.vm.define :"stdsv3" do |c1|
    c1.vm.hostname = "stdsv3"
  end

  # 下記2行を追加
  config.vm.provision "shell",
    inline: "ip address add 192.168.254.13/23 dev eth0; route add default gw 192.168.254.1"

end
```


いったん、vagrant destroyしたのち、再度vagrant upを実行します。初回は失敗し、先ほどと同じ状況になります。
詳細な理由は未調査ですが、「config.vm.provision "shell"」による設定が行われる前にSMBによるフォルダ共有をしようとしたため、エラーになっているように見えます。
またその後、VM内でnetworkサービスがリスタートされています。eth0はDHCPで設定されるようになっているので、networkサービスの実行に失敗します。

```:vagrant&nbsp;up(1回目)
$ vagrant up
Bringing machine 'stdsv3' up with 'hyperv' provider...
==> stdsv3: Verifying Hyper-V is enabled...
==> stdsv3: Verifying Hyper-V is accessible...
==> stdsv3: Importing a Hyper-V instance
    stdsv3: Creating and registering the VM...
    stdsv3: Successfully imported VM
    stdsv3: Please choose a switch to attach to your Hyper-V instance.
    stdsv3: If none of these are appropriate, please open the Hyper-V manager
    stdsv3: to create a new virtual switch.
    stdsv3:
    stdsv3: 1) DockerNAT
    stdsv3: 2) Hyper-V Internal with WinNAT
    stdsv3:
    stdsv3: What switch would you like to use? 2
    stdsv3: Configuring the VM...
==> stdsv3: Starting the machine...
==> stdsv3: Waiting for the machine to report its IP address...
    stdsv3: Timeout: 120 seconds
    stdsv3: IP: fe80::215:5dff:fe14:2512
==> stdsv3: Waiting for machine to boot. This may take a few minutes...
    stdsv3: SSH address: fe80::215:5dff:fe14:2512:22
    stdsv3: SSH username: vagrant
    stdsv3: SSH auth method: private key
    stdsv3:
    stdsv3: Vagrant insecure key detected. Vagrant will automatically replace
    stdsv3: this with a newly generated keypair for better security.
    stdsv3:
    stdsv3: Inserting generated public key within guest...
    stdsv3: Removing insecure key from the guest if it's present...
    stdsv3: Key inserted! Disconnecting and reconnecting using new SSH key...
==> stdsv3: Machine booted and ready!
==> stdsv3: Preparing SMB shared folders...
    stdsv3: You will be asked for the username and password to use for the SMB
    stdsv3: folders shortly. Please use the proper username/password of your
    stdsv3: account.
    stdsv3:
    stdsv3: Username: ****
    stdsv3: Password (will be hidden):
Error! Your console doesn't support hiding input. We'll ask for
input again below, but we WILL NOT be able to hide input. If this
is a problem for you, ctrl-C to exit and fix your stdin.
     stdsv3: Password (will be hidden): ********

Vagrant requires administrator access to create SMB shares and
may request access to complete setup of configured shares.
==> stdsv3: Setting hostname...
The following SSH command responded with a non-zero exit status.
Vagrant assumes that this means the command failed!

service network restart

Stdout from the command:

Restarting network (via systemctl):  [FAILED]


Stderr from the command:

Job for network.service failed because the control process exited with error code. See "systemctl status network.service" and "journalctl -xe" for details.
```

次にvagrant provisionを実行します。Shell Provisionerが実行され、指定したIPアドレスが設定されます。

```:vagrant&nbsp;provision(2回目)
$ vagrant provision
==> stdsv3: Running provisioner: shell...
    stdsv3: Running: inline script
```

vagrant sshを実行してログインしてみます。ifconfigコマンドを実行すると、指定したIPアドレスが付与されていることを確認できます。
netstatコマンドを実行すると、IPv4アドレスでSSH接続していることを確認できます。

```:vagrant&nbsp;ssh
$ vagrant ssh
Last login: Fri Mar  1 15:21:31 2019

       __|  __|_  )
       _|  (     /   Amazon Linux 2 AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-2/
[vagrant@stdsv3 ~]$ ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.254.13  netmask 255.255.254.0  broadcast 0.0.0.0
        inet6 fe80::215:5dff:fe14:2512  prefixlen 64  scopeid 0x20<link>
        ether 00:15:5d:14:25:12  txqueuelen 1000  (Ethernet)
        RX packets 7649  bytes 1955138 (1.8 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 682  bytes 114005 (111.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
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (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

[vagrant@stdsv3 ~]$ netstat -an|grep ESTABLISHED
tcp        0      0 192.168.254.13:22       192.168.254.1:64372     ESTABLISHE

```

この状態では、OS再起動時などにIPアドレスが消えてしまいます。
/etc/sysconfig/network-scripts/ifcfg-eth0に必要な設定を書き込みます。その他、必要な設定を実施します。
まとめると以下のような変更が必要になります。

- /etc/sysconfig/network-scripts/ifcfg-eth0に、IPv4設定を書き込む
- /etc/resolv.confに、自分のISPから提供されているDNSサーバを設定する
- /etc/ssh/sshd_configを変更し、SSH経由でユーザ名・パスワードでログインできるように設定する

Vagrantfileを以下のように変更します。実行コマンドの行数が多くなるので、Inline Scriptsの機能を利用します。

Shell Scripts - Provisioning - Vagrant by HashiCorp -> Inline Scripts
https://www.vagrantup.com/docs/provisioning/shell.html#inline-scripts

```:Vagrantfile
$provisioner_script = <<-SCRIPT

  ip address add 192.168.254.13/23 dev eth0;
  route add default gw 192.168.254.1;

  echo '# This file has been overwritten by Vagrant Shell Provisioner' > /etc/sysconfig/network-scripts/ifcfg-eth0;
  echo BOOTPROTO=none >> /etc/sysconfig/network-scripts/ifcfg-eth0;
  echo DEVICE=eth0 >> /etc/sysconfig/network-scripts/ifcfg-eth0;
  echo NM_CONTROLLED=no >> /etc/sysconfig/network-scripts/ifcfg-eth0;
  echo ONBOOT=yes >> /etc/sysconfig/network-scripts/ifcfg-eth0;
  echo IPADDR=192.168.254.13 >> /etc/sysconfig/network-scripts/ifcfg-eth0;
  echo NETMASK=255.255.254.0 >> /etc/sysconfig/network-scripts/ifcfg-eth0;
  echo GATEWAY=192.168.254.1 >> /etc/sysconfig/network-scripts/ifcfg-eth0;
  echo DNS1=xxx.xxx.xxx.xxx >> /etc/sysconfig/network-scripts/ifcfg-eth0;
  echo DNS2=xxx.xxx.xxx.yyy >> /etc/sysconfig/network-scripts/ifcfg-eth0;

  sed -i 's/^nameserver .*$/nameserver xxx.xxx.xxx.xxx\nnameserver xxx.xxx.xxx.yyy/' /etc/resolv.conf

  sed -i 's/^PasswordAuthentication .*$/PasswordAuthentication yes/' /etc/ssh/sshd_config;
  systemctl reload sshd;

SCRIPT

Vagrant.configure("2") do |config|
  config.vm.box = "Yojimbo108/AmazonLinux2"

  config.vm.provider "hyperv" do |v|
    v.memory = 1024
    v.cpus = 1
  end

  config.vm.define :"stdsv3" do |c1|
    c1.vm.hostname = "stdsv3"
  end

  config.vm.provision "shell", inline: $provisioner_script

end
```

もう一度、vagrant privisionを実行します。その後VMインスタンスにログインし、意図したとおりの設定になっていることを確認します。


### ホストOS側でDHCPサーバを稼働させ、「Hyper-V Internal with WinNAT」ネットワークに所属するゲストOSがDHCPでIPv4設定できるようにする

大きく、以下の作業を実施する必要があります。

- Open DHCP Serverをインストールし、Serviceとして実行させる
- Windows Firewallの設定をする

詳細は下記記事にまとめました。
[【Windows】非Server EditionでDHCPサーバを構築する【DHCP】 - Qiita](https://qiita.com/tmiki/items/d5965ee767378cf7f13f)

# おわりに
Hyper-Vのネットワーク機能は一部不安定なところがあること、VagrantはHyper-Vの仮想ネットワークを操作する機能がないことから、
Hyper-Vを作業用のVMのハイパーバイザとして利用するにはまだ少し難があります。
現状では、VirtualBoxの利便性にはまだまだかなわないという感触です。

しかしながら、Docker Desktop for Windowsは今後、Hyper-Vに注力していくように見えますので、Hyper-Vの重要性が高まっていくように予測しています。

でもこんな話もありますし、これが実現できるんだったら無理してHyper-Vでいろいろ頑張らなくても良いような気がしてきました。
[VirtualBox 6.0 から Hyper-V と共存できるはず - Qiita](https://qiita.com/takotakot/items/94d3218cf8de5b2c1da8)