日々の備忘録

技術について日々学んだことを書きます。

Vagrant + Hyper-V 静的IP設定 + SMB設定

せっかくWindows 10 Proを使っているのでHyper-Vvagrantで操作してみたいと思い、試行錯誤しました。

本来であれば、以下の項目を全て満たしたかったのですが、どうしても3だけは解決できませんでした。

  1. 静的IPの設定
  2. ホスト、ゲスト間フォルダ共有
  3. vagrant up時のインタラクティブ操作の排除

一応使用する目線で許容範囲にはなったので記事にしたいと思います。

環境

また、Hyper-V で利用可能な仮想ネットワークが既にあることを前提としています。(Hyper-V自体は何年か前に利用可能にしておいたのでこの辺りの流れは覚えておらず...)

静的IPアドレスの指定

まず、Hyper-VIPアドレスを固定するには以下の方法があります。

ちなみに、vagrant 公式ドキュメントに記載の通り、Hyper-V providerを選択するとVagrantfileに記載するネットワーク設定のほとんどが無視されるので、自分でどうにかする必要があります。

DHCPサーバを立てたところで、VMMACアドレスVagrantで指定できないので必然的に後者を選択することになります。

まずHyper-V NATにはホストに対して一つのNATネットワークしか構築できない制約があり、実態に応じて広い範囲で構築する必要があります。

VM構築の前にNATネットワークを構築する必要があるので、triggerを使います。Hyper-Vを使用する場合は管理者権限で起動したPowershellが大前提となるので、これのために権限周りで何か気にする必要はないと思います。

# Vagrantfile
# ... 中略
config.trigger.before :up do |trigger|
  trigger.run = {
    path: "create-nat-hyperv-switch.ps1"
  }
end
# create-nat-hyperv-switch.ps1
$switch_name = "VagrantNATSwitch"
$ip_addr = "192.168.100.1"
$ip_prefix = "24"
$ip_addr_prefix = "192.168.100.0/24"
$network_name = "VagrantNATNetwork"

if ($switch_name -notin (Get-VMSwitch | Select-Object -ExpandProperty Name)) {
    New-VMSwitch -Name $switch_name -SwitchType Internal
} else {
    Set-VMSwitch -Name $switch_name -SwitchType Internal
}

$vmnet_adapter = Get-VMNetworkAdapter -ManagementOS -SwitchName $switch_name

$network_adapter = Get-NetAdapter | ? { $_.DeviceID -eq $vmnet_adapter.DeviceID }

if ($network_adapter | Get-NetIPAddress -IPAddress $ip_addr -ErrorAction SilentlyContinue) {
    $network_adapter | Disable-NetAdapterBinding -ComponentID ms_server -ErrorAction SilentlyContinue
    $network_adapter | Enable-NetAdapterBinding -ComponentID ms_server -ErrorAction SilentlyContinue
} else {
    $network_adapter | Remove-NetIPAddress -Confirm:$false -ErrorAction SilentlyContinue
    $network_adapter | Set-NetIPInterface -Dhcp Disabled -ErrorAction SilentlyContinue
    $network_adapter | New-NetIPAddress -AddressFamily IPv4 -IPAddress $ip_addr -PrefixLength $ip_prefix -ErrorAction Stop | Out-Null
}

$interface_alias = $network_adapter.Name
Set-NetConnectionProfile -InterfaceAlias $interface_alias -NetworkCategory Private

if ($ip_addr_prefix -notin (Get-NetNAT | Select-Object -ExpandProperty InternalIPInterfaceAddressPrefix)) {
    New-NetNAT -Name $network_name -InternalIPInterfaceAddressPrefix $ip_addr_prefix
} else {
    Write-Host ("{0} for static IP configuration already exists. skipping" -F $ip_addr_prefix)
}

以上で vagrant up 時に先にこちらのNAT構築、仮想スイッチ作成が完了します。

私の環境の問題かもしれませんが、Set-NetConnectionProfile を毎回実行しないとホストマシンが再起動するごとにネットワークプロファイルがPublic扱いに戻されてしまい、SMBなどがファイアウォールで許可されない状況になります。

識別できないネットワーク扱いになっているのを解消すればいいようですが、ローカルセキュリティポリシーの設定を緩くするか、powershell - Windows 8.1 Hyper-V network adapter is set to public and won't save as private - Server Fault によれば、レジストリを弄ることでPublic扱いに変更されることを回避可能なようです。試していませんが、ローカルセキュリティポリシーを緩くするのはあまり気が進みませんね。

Vagrantを介している場合は毎回Set-NetConnectionProfileを上記スクリプトで実行するので問題にはなりませんが、Hyper-V自体をきちんと使おうとすると真面目にやらなければならないんだろうと思います。

ウィルス対策ソフトがインストールされている場合、Set-NetConnectionProfileでもSMBが通らないことがあるので、その場合はウィルス対策ソフトのファイアウォール設定を確認してください。

あとはこれをVMに如何に設定するかなのですが、ssh接続が必要な処理の前に仮想スイッチに切り替えてしまうと処理が進まなくなるため、切り替えタイミングが重要です。

reload provisionerとの組み合わせでネットワーク設定

# configure-static-ip.sh
STATIC_IP=...
IPV4_PREFIX=...
IPV4_ADDR_GATEWAY=...
IPV4_DNS=...

CONNECTION_UUID=$(nmcli -g uuid con)

nmcli con mod "${CONNECTION_UUID}" ipv4.addresses "${STATIC_IPV4}/${IPV4_PREFIX}"
nmcli con mod "${CONNECTION_UUID}" ipv4.gateway ${IPV4_ADDR_GATEWAY}
nmcli con mod "${CONNECTION_UUID}" ipv4.dns "${IPV4_DNS}"
nmcli con mod "${CONNECTION_UUID}" ipv4.method manual
nmcli con mod "${CONNECTION_UUID}" connection.autoconnect yes
nmcli con reload
# set-hyperv-switch.ps1
$vmname = "..."
$switch_name = ...
Get-VM $vmname | Get-VMNetworkAdapter | Connect-VMNetworkAdapter -SwitchName $switch_name
config.vm.provision "shell", preserve_order: true do |shell|
  shell.path = "./configure-static-ip.sh"
end
config.vm.provision :reload, preserve_order: true

config.trigger.before :reload do |trigger|
    trigger.info = "Setting Hyper-V switch"
    trigger.run = {
        path: "set-hyperv-switch.ps1"
        args: [
            "-vmname #{vm_name}",
            "-switch_name #{switch_name}"
        ]
    }
end

ゲストOS側で静的IPを設定しつつ、reload providerで再起動。triggerでreload直前に仮想スイッチの入れ替えを行っています。

幸い、再起動時(シャットダウン時)にネットワーク疎通ができない場合でもvagrantは強制再起動してくれるのでこれで上手くいきます。

SMBでのフォルダ共有

SMBでのフォルダ共有時、アカウント入力を求められます。

毎回入力するのは面倒ですがVagrantfileにベタ書きするのはよろしくないので、vagrant-envを使って.envを読み込ませるようにします。

SMB_HOST(ホストOSのIP)は必須ではないはずですが、私のマシンでホストOSのIPを特定できていないような挙動を示したことがあるので、念のため設定しています。(問題なければルータでホストマシンのIPを固定しておくといいです。)

# .env
SMB_USERNAME=...
SMB_PASSWORD=...
SMB_HOST=...
# Vagrantfile
config.vagrant.plugins = [
  "vagrant-env",
  ...
]
config.env.enable
config.vagrant.sensitive = [
  ENV['SMB_USERNAME'],
  ENV['SMB_PASSWORD'],
  ENV['SMB_HOST'],
]

config.vm.synced_folder ".", "/vagrant", type: "smb",
  smb_username: ENV['SMB_USERNAME'],
  smb_password: ENV['SMB_PASSWORD'],
  smb_host: ENV['SMB_HOST']

これでSMBによるフォルダ共有は完了です。

vagrant upだけでインタラクティブな操作なしに仮想マシン起動が完結すること

これがどうしても実現できませんでした。

まず、VM未作成の場合に接続可能な仮想スイッチを選択することが必須となります。

Vagrantfile内でrubyの関数が実行可能なので、以下のようにVMの作成状況を確認してネットワーク設定を有効化することができます。(ネットワーク設定はほとんどが無視されるのですが、何故かbridgeはうまいこと読み取ってくれます)

vm_exists = system("pwsh.exe", "-Command", "Get-VM -VMName foo -ErrorAction SilentlyContinue | Out-Null")
if !vm_exists
  config.vm.network "public_network", bridge: "default_switch_name"
end

ただ、これではreload provisionerで仮想スイッチの入れ替えをしたところでスイッチが切り替わらず、静的IP設定だけが残ってしまって疎通不能なマシンができてしまいます。おそらくreload時に改めてVagrantfileを読み直さないので、スイッチ設定が強制で戻されているのかと思います。

次にtyped_triggerも試してみたのですが、Import直後のスイッチの設定はHyper-Vで確認する限りではできているものの、結局入力を求められてしまいました。

# Vagrantfile

ENV['VAGRANT_EXPERIMENTAL'] = "typed_triggers"
...

config.trigger.after :"VagrantPlugins::HyperV::Action::Import", type: :action do |trigger|
  trigger.info = "Setup Default Switch"
  trigger.run = {
    path: "set-default-switch.ps1"
  }
end

以上の通り、VM作成時にインタラクティブな操作が必要になってしまいました。

また、NATを他の用途で使っている場合はそれも考慮する必要があり、少し気を使います。

結論としては、よほどの事情がない限りは素直にVirtualBoxを使ったほうが圧倒的に楽です。

もし真面目にHyper-Vを使いたいのであれば、VagrantHyper-Vネットワーク対応を待つか、こちらからPull requestを送るくらいの気概が必要かもしれません。

参考URL networking - HyperV - Static Ip with Vagrant - Super User