TechReport

技術者情報

ソリューション

2017年12月13日(水)公開

第213回

Hyper-Vで作るコンテナ 新しい仮想マシンの使い方-Vol.5

オリジナルのPowerShellコマンドの作成(2)

前回に引き続きオリジナルモジュールを作成していきます。

記事INDEX

前提

モジュール名は「InvokeVContainer」とします。
前回配置した「C:\Users\<ユーザー名>\Documents\WindowsPowerShell\Modules\ InvokeVContainer.psm1」のモジュールファイルのコマンドを追加していきます。

最終的な完成版は、GitHub(https://github.com/InvokeV/InvokeV-Container)に掲載しています。

公開しているモジュールInvokeVContainer.psm1のコードは以下となります。

今回は主にコンテナの操作について紹介します。

コンテナの操作

〇コンテナの作成
InvokeVコンテナの相対は、Hyper-V上の仮想マシンです。従ってコンテナの作成は仮想マシンの作成と同じ方法で行います。ポイントは、親となるコンテナイメージの仮想ハードディスクの差分ファイルを作成して、コンテナのハードディスクとして利用するという点です。

New-Container

Function New-Container([String]$ContainerName, [String]$ImageName, [Long]$Memory=1024MB, [Int]$Processer=1, [String]$SwitchName) {
$ImagePath = (Get-ContainerImage | Where {$_.Name -eq "$ImageName"}).Path
$VHD = New-VHD -Path "$RootPath\Containers\$ContainerName\$ContainerName.vhdx" -Differencing -ParentPath "$ImagePath"
$VM = New-VM -Name "$ContainerName" -Generation 2 -MemoryStartupByte $Memory -VHDPath $VHD.Path -Path "$RootPath\Containers"
Set-VM $VM -DynamicMemory -MemoryMaximumBytes $Memory -ProcessorCount $Processer
If($SwitchName -ne ""){
Get-VMNetworkAdapter $VM | Connect-VMNetworkAdapter –SwitchName $SwitchName
}
Set-VMFirmware $VM -EnableSecureBoot Off
Set-VMProcessor $VM -ExposeVirtualizationExtensions $True
}

・引数として、コンテナ名、親のコンテナイメージ名、メモリ、プロセッサ数、仮想スイッチ名を指定します。メモリは1024MB、プロセッサ数は1つを既定として設定しています(引数で変更可)。
・コンテナイメージ名からGet-ContainerImageコマンドでコンテナイメージのvhdxファイルパスを取得します。
・New-VHDコマンドでコンテナ用の差分仮想ハードディスクファイルを作成します。親ファイルはコンテナイメージのvhdx-ファイルとなります。
・New-VMコマンドで仮想マシンとしてコンテナを作成します。
・仮想スイッチ名が指定されている場合は、接続します。
・LinuxなどWindows以外のコンテナを想定して、セキュアブートをOFFにしておきます。
・Nested Hyper-Vとしてのコンテナ利用を想定して、Nestedを有効にしておきます。

〇コンテナの詳細取得
コンテナの詳細として、仮想マシンとしての名前と状態の情報に加え、仮想ハードディスクファイルのパスと差分仮想ハードディスクファイルの親ファイルのパスを表示しています。

Get-Container

Function Get-Container {
Get-VM | Where-Object {Test-Path ("$RootPath\Containers\" + $_.Name)} | Select `
@{Label="Name"; Expression={$_.Name}}, State, @{Label="Path"; Expression={((Get-VMHardDiskDrive $_.Name | Where-Object {$_.ControllerLocation -eq 0}).Path)}}, `
@{Label="ParentPath"; Expression={(Get-VHD(Get-VMHardDiskDrive $_.Name | Where-Object {$_.ControllerLocation -eq 0}).Path).ParentPath}}
}

・Get-VMコマンドで名前と状態を取得します。
・Get-VMHardDiskDriveコマンドで仮想ハードディスクファイルのパスを取得します。
・Get-VHDコマンドで差分仮想ハードディスクファイルの親ファイルのパスを取得します。


〇コンテナの起動

Start-Container

Function Start-Container([String]$ContainerName) {
Start-VM $ContainerName
}

・Start-VMコマンドを利用してコンテナを起動します。


〇コンテナの停止

Stop-Container

Function Stop-Container([String]$ContainerName) {
Stop-VM $ContainerName -Force
}

・Stop-VMコマンドを利用してコンテナを停止(シャットダウン)します。


〇コンテナの削除
コンテナの削除はコンテナイメージの削除とは異なり、仮想マシンとして作成されているコンテナを削除するとともに、仮想ハードディスクファイルや仮想マシンの構成ファイルなども削除する必要があります。

Remove-Container

Function Remove-Container([String]$ContainerName) {
$VM = Get-VM "$ContainerName"
If($VM.State -eq "Running"){
Stop-VM $VM -TurnOff
}
Remove-VM $VM -Force
Remove-Item "$RootPath\Containers\$ContainerName" -Recurse
}

・Get-VMコマンドで仮想マシンを取得します。
・もし仮想マシンが実行中の場合は、停止します。
・Remove-VMコマンドで仮想マシンを削除します。
・Hyper-V上からは削除されましたが、構成ファイルや仮想ハードディスクファイルが残っているので、フォルダごと削除します。


〇コンテナの作成~起動~IP設定
コンテナイメージ自体にIPの設定がされていない場合、コンテナは作成された時点では同様にIPの設定が行われておらず、ネットワークに接続できません。Hyper-V上の仮想マシンであるコンテナは、起動状態であれば外部からリモートパワーシェルやWMIを利用してIPの設定を行うことが可能です(現時点でUbunt16~uは除くCentOS7~は可)。コンテナ作成~起動~IP設定をひとまとめにしたコマンドがRun-Containerとなります。Run-Containerコマンド自体もいくつかのコマンドを組み合わせて作成しています。

手順としては、
 1. コンテナの作成
 2. コンテナの起動
 3. コンテナのブート完了まで待機
 4. IPアドレスの設定
となります。

1つずつ解説していきましょう。

Run-Container

Function Run-Container([String]$ContainerName, [String]$ImageName, [Long]$Memory=1024MB, [Int]$Processer=1, [String]$SwitchName, [String]$IPAddress, [String]$Subnet, [String]$Gateway, [String]$DNS = @()){
New-Container -ContainerName "$ContainerName" -ImageName "$ImageName" -Memory $Memory -Processer $Processer -SwitchName "$SwitchName"
Start-Container "$ContainerName"
Wait-ContainerBoot "$ContainerName"
Set-ContainerIPConfig -ContainerName "$ContainerName" -IPAddress $IPAddress -Subnet $Subnet -Gateway $Gateway -DNS $DNS
}

・New-Containerコマンドに必要な引数に加えて、IPアドレス情報も引数として設定します。
・New-Containerコマンド(既出)でコンテナを作成します。
・Start-Containerコマンド(既出)でコンテナを起動します。
・Wait-ContainerBootコマンドでコンテナの起動~ブートが完了するまで待機します。IP設定はブートが完全に終了するまでできません。
・Set-ContainerIPConfigコマンドでIP設定を行います。


Wait-ContainerBoot

Function Wait-ContainerBoot([String]$ContainerName){
$Flg = $False
$TimeCount = 0
Do
{
If((Get-ContainerIPAddress $ContainerName) -ne ""){
$Flg = $True
}Else{
$TimeCount = $TimeCount + 1
If($TimeCount -eq 180){
$Flg = $True
}
}
Start-Sleep -s 1
}
While ($Flg -eq $False)
}

・ブート完了を監視するコンテナ名を引数で指定します。
・Do~Whileのループを抜けるためのフラグと、タイムアウトのカウンタを定義します。
・Get-ContainerIPAddressコマンド(後出)で、コンテナのIPアドレスが取得できるまでループします。
・ループしている間に1秒ごとにタイムアウトカウンタを追加して、180秒でタイムアウトします。
・フラグがTrueとなったらループを抜けます(=コンテナのIPが取得できた or タイムアウトした)。IP設定前であっても169.254.X.XというIPアドレスが自動的に割り当てられるので、このアドレスが取得できたらブート完了と判断しています。


Get-ContainerIPAddress


Function Get-ContainerIPAddress([String]$ContainerName){
$ManagementService = Get-WmiObject -Namespace root\virtualization\v2 -Class Msvm_VirtualSystemManagementService
$ComputerSystem = Get-WmiObject -Namespace root\virtualization\v2 -Class Msvm_ComputerSystem -Filter "ElementName = '$ContainerName'"
$SettingData = $ComputerSystem.GetRelated("Msvm_VirtualSystemSettingData") | Where-Object { $_.ElementName -eq "$ContainerName" }
$NetworkAdapters = $SettingData.GetRelated('Msvm_SyntheticEthernetPortSettingData')
$TargetNetworkAdapter = (Get-VMNetworkAdapter $ContainerName)[0]
$NetworkSettings = @()
ForEach ($NetworkAdapter in $NetworkAdapters) {
If ($NetworkAdapter.Address -eq $TargetNetworkAdapter.MacAddress) {
$NetworkSettings = $NetworkSettings + $NetworkAdapter.GetRelated("Msvm_GuestNetworkAdapterConfiguration")
}
}
If($NetworkSettings.Length -eq 0){
Return ""
}Else{
If( $NetworkSettings[0].IPAddresses.Length -eq 0){
Return ""
}Else{
Return $NetworkSettings[0].IPAddresses[0]
}
}
}

・コンテナ名を引数とします。
・Get-WmiObjectコマンドで、WMIのクラスを利用して起動中のコンテナのIP情報を取得します。WMIの利用方法は、ここでは細かい説明は省略します。
・Get-VMNetworkAdapterコマンドで取得したコンテナの、ネットワークアダプターのMacAddressと一致したWMIのネットワーク情報があった場合に、そのアダプタに設定されたIPアドレスを取得します。


Set-ContainerIPConfig

Function Set-ContainerIPConfig([String]$ContainerName, [String]$IPAddress = @(), [String]$Subnet = @(), [String]$Gateway = @(), [String]$DNS = @()){
$ManagementService = Get-WmiObject -Namespace root\virtualization\v2 -Class Msvm_VirtualSystemManagementService
$ComputerSystem = Get-WmiObject -Namespace root\virtualization\v2 -Class Msvm_ComputerSystem -Filter "ElementName = '$ContainerName'"
$SettingData = $ComputerSystem.GetRelated("Msvm_VirtualSystemSettingData") | Where-Object { $_.ElementName -eq "$ContainerName" }
$NetworkAdapters = $SettingData.GetRelated('Msvm_SyntheticEthernetPortSettingData')
$TargetNetworkAdapter = (Get-VMNetworkAdapter $ContainerName)[0]
$NetworkSettings = @()
ForEach ($NetworkAdapter in $NetworkAdapters) {
If ($NetworkAdapter.Address -eq $TargetNetworkAdapter.MacAddress) {
$NetworkSettings = $NetworkSettings + $NetworkAdapter.GetRelated("Msvm_GuestNetworkAdapterConfiguration")
}
}
$NetworkSettings[0].DHCPEnabled = $False
$NetworkSettings[0].IPAddresses = $IPAddress
$NetworkSettings[0].Subnets = $Subnet
$NetworkSettings[0].DefaultGateways = $Gateway
$NetworkSettings[0].DNSServers = $DNS
$NetworkSettings[0].ProtocolIFType = 4096
$ManagementService.SetGuestNetworkAdapterConfiguration($ComputerSystem.Path, $NetworkSettings.GetText(1)) | Out-Null
#Write-Host "IP was configured."
}

・コンテナ名、IPアドレス、サブネットマスク、ゲートウェイアドレス、DNSアドレスを引数として設定します。
・WMIを利用して起動中のコンテナのIP設定を行います。
・Get-VMNetworkAdapterコマンドで取得したコンテナの、ネットワークアダプターのMacAddressと一致したWMIのネットワーク情報があった場合に、ネットワークの各設定を行います。
・ネットワークの各設定値は複数設定することが可能なので、配列に格納して割り当てる必要があります。

その他

〇ツリー表示
Get-ContainerImageやGet-Containerコマンドでは、一覧は参照できますが、その親子関係の把握が難しくなっています。そこで、簡易的ではありますが、ツリー構造をPowerShellで表示しようというおまけ的なコマンドを追加しました。

Get-TreeView

Function Get-TreeView() {
$Global:Tree = "`r`n"
$RootFiles = Get-ChildItem $RootPath\Images *.vhdx | Get-VHD | Where {$_.ParentPath -eq ""}
ForEach($File in $RootFiles){
Set-TreeView $File.Path 0
}
Write-Host $Global:Tree
}

・ルートフォルダ内でvhdxファイルを検索して、差分ハードディスクファイルとして親ファイルが無いもの=大元のイメージを抽出します。
・それぞれのイメージに対してSet-TreeVieコマンドで紐づいている子ファイルを抽出しています。

Set-TreeView

Function Set-TreeView([String]$ParentFile, [Int]$Level) {
$Files = Get-ChildItem -Path $RootPath -Include "*.vhdx" -Recurse | Get-VHD | Where {$_.ParentPath -eq $ParentFile}
$BaseName = (Get-ChildItem $ParentFile).BaseName
If($BaseName -match "[A-Fa-f0-9]{8}\-[A-Fa-f0-9]{4}\-[A-Fa-f0-9]{4}\-[A-Fa-f0-9]{4}\-[A-Fa-f0-9]{12}"){
$Name = ($BaseName.Substring(0, $BaseName.Length - ($BaseName.Split("_")[$BaseName.Split("_").Length - 1].Length) - 1))
}Else{
$Name = $BaseName
}
If((Get-ChildItem $ParentFile).FullName.ToUpper().StartsWith(("$RootPath\Images").ToUpper())){
$Name = "[" + $Name + "]"
}Else{
$Name = " " + $Name
}
$Space = ""
For ( $i = 0; $i -lt (($Level - 1) * 12); $i++ ){
$Space += " "
}
If($Level -ne 0){
$Space += " |"
For ( $i = 0; $i -lt (10 - $Nam.Length) ; $i++ ){
$Space += "-"
}
}
$Global:Tree += $Space + $Name + "`r`n`r`n"
If($Files.Count -eq 0){
}Else{
ForEach($File in $Files){
Set-TreeView $File.Path ($Level + 1)
}
}
}

・差分ハードディスクファイルとしての親ファイルパスと、階層レベルを引数として設定します。
・InvokeVのルートフォルダ以下で親ファイルを参照している子ファイルを、Get-ChildItemコマンドとGet-VHDコマンドを使って抽出します。
・BaseNameとして拡張子を除いたファイル名を抽出します。
・ファイル名にGUIDが含まれている場合は、名前部分だけを抽出します。
・GUIDが含まれていない場合は、そのままファイル名となります。
・InvokeVのイメージフォルダに対象ファイルがある場合は、イメージファイルと判断して名前に[ ]を追加します。
・コンテナファイルの場合は、名前の前にスペースを追加します。
・引数で設定されたレベルの分だけ階層の深さをあらわすために、スペースを追加して表示を整えます。
・引数で渡された子ファイルに対して、さらにSet-TreeViewコマンドを実行し、描画を参照ファイルがなくなるまで続けます。再度Set-TreeViewコマンドを呼び出す場合は、レベルを-1して引数とします。

Get-TreeViewの実行結果

PS C:\> Get-TreeView

[centos_7]

|---------- centos7_01

[nanoiis]

|---------- nanoiis_01

|----------[nanoiis_img02]

|---------- nanoiis_02

|---------- nanoiis_03

|----------[nanoiis_img03]

[ubuntu_16.04.1]

|---------- ubuntu_01

[win2016]

|---------- win2016_01

|----------[win2016_hyper-v]

[win2016_core]

|---------- win2016_core_01

〇親ファイルとの再接続
InvokeVコンテナは差分仮想ハードディスクを利用して実現しています。インポートやエクスポート、コンテナイメージやコンテナの削除を繰り返していくうちに、もし差分仮想ハードディスクの親子関係が壊れてしまった場合、vhdxファイルの親ファイルの属性を再設定することで、修復できる場合があります。万が一のためのコマンドとして作成しましたが、1つずつSet-VHDコマンドを実行することでも修復は可能です。ただし、親ファイルに何か大きな変更があった場合などは、再設定しても子ファイルが修復されない場合もあります。


Correct-ContainerImage

Function Correct-ContainerImage() {
$Images = Get-ChildItem -Path "$RootPath\Images" -Include "*.vhdx" -Recurse
ForEach($Image in $Images){
$ParentPath = (Get-VHD $Image).ParentPath
If ($ParentPath){
Set-VHD -Path $Image.FullName –ParentPath $ParentPath -IgnoreIdMismatch
Write-Host $Image.Name ">" (Get-ChildItem $ParentPath).Name
}
}
}

・イメージフォルダ以下のvhdxファイルを取得します。
・各イメージファイルの差分仮想ハードディスクファイルとして、属性で親ファイルがある場合、Set-VHDコマンドのIgnoreIdMismatchオプションスイッチを追加して強制的に親子関係を再設定します。
・再設定されたファイルを表示します。

以上が、InvokeVContainer.psm1モジュールコマンド一覧となります。GitHub(https://github.com/InvokeV/InvokeV-Container)にはこれらのコマンドの使い方サンプルSample.ps1が付属していますので参考にしていただけたらと思います。

InvokeV Container Manager

GitHub(https://github.com/InvokeV/InvokeV-Container)には、この他にも、「InvokeV Container Manger」(InvokeVContainerManger.exe)というツールがアップされています。これは、PowerShellベースのInvokeVコンテナをGUIツールで利用できるようにしたものです。コンテナの主要な操作はすべてこれまで解説してきたモジュールInvokeVContainer.psm1のコマンドを利用しています。基本的な操作はマウスの右クリックからとなっており、簡単に操作できるようになっています。実行した操作は全てコマンドウィンドウにPowerShellのコマンドがそのまま表示されるので、InvokeVコンテナのコマンドのサンプルとしても利用できます。

01 InvokeV Container Manger
01 InvokeV Container Manger

以上、5回に渡って紹介しました「InvokeVコンテナ」、いかがだったでしょうか。紹介した内容を参考に、モジュールをカスタマイズして独自のコマンドを作成したり、これらのコマンドを組み合わせてInvokeVコンテナを自動的に展開、操作することができるようになります。


InvokeVコンテナは、「より速く」「より小さく」「より使いやすく」をHyper-Vで実現する、新しい仮想マシンの使い方を提案します。

樋口 勝一

GMOインターネット株式会社

1999年6月GMOインターネットに入社。Windowsを用いたホスティング事業の立ち上げの際、サービス開発からその後の保守・運用まで1人で担当。2010年には「お名前.comWindowsデスクトップ」をリリースし、マイクロソフト社と強い信頼関係を構築。2007年から連続で「マイクロソフトMVPアワード」を受賞し、Windowsのスペシャリストとして活躍。

執筆者一覧

Hyper-Vで本格的なサーバー仮想環境を構築。仮想環境を設定・操作できる!

できるPRO Windows Server 2016 Hyper-V
GMOインターネット株式会社 樋口 勝一 著

◇Hyper-Vのさまざまな機能がわかる
◇インストールからの操作手順を解説
◇チェックポイントやレプリカも活用できる
Windows Server 2016 Hyper-Vは、仮想化ソフトウェア基盤を提供する機能であり、クラウドの実現に不可欠のものです。
本書では、仮想化の基礎知識から、Hyper-Vでの仮想マシンや仮想スイッチの設定・操作、プライベートクラウドの構築、Azureとの連携などを解説します。

詳細はこちら → Amazonインプレスブックス

初めてのWindows Azure Pack本が発売

Windows Azure Pack プライベートクラウド構築ガイド
GMOインターネット株式会社 樋口 勝一 著

本書は、Windows Azure PackとHyper-Vを利用し、企業内IaaS(仮想マシン提供サービス)を構成するための、IT管理者に向けた手引書です。試用したサーバーは、最小限度の物理サーバーと仮想マシンで構成しています。Windows Azure Packに必要なコンポーネントのダウンロード、実際にプライベートクラウド構築する過程を、手順を追って解説しています。これからプライベートクラウドの構築を検討するうえで、作業負担の軽減に役立つ一冊です。

詳細はこちら