GMO DevelopersDay
GMO DevelopersDay
2024.11.29Fri - 11.30Sat YouTube Live
イベント開催まであと12

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

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

Hyper-V上でコンテナを作成、操作する方法を紹介します。
内容の詳細は下記に記載いたしますのでご確認ください。

前提

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

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

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

$RootPath = "D:\InvokeVContainer"

Function Get-InvokeVContanerRoot(){
     Return $RootPath
}

Function Import-ContainerImage($FilePath, $ImageName) { 
    $File = Get-ChildItem $FilePath
    If ($File.Extension -eq ".vhdx") {
        If ($File -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}" -eq $True){
            Copy-Item $File -Destination ("$RootPath\Images\" + $File.Name)
        }Else{
            If($ImageName){$ImageName = $File.BaseName}
            Copy-Item $File -Destination ("$RootPath\Images\" + $ImageName + "_" + [Guid]::NewGuid() + ".vhdx") 
        }
    }Else{
        Expand-Archive -Path $FilePath -DestinationPath $RootPath\Images -Force
    } 
}

Function Get-ContainerImage { 
    Get-ChildItem $RootPath\Images *.vhdx | Where-Object {$_.Name -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}"} | Select `
    @{Label="Name"; Expression={($_.BaseName.Substring(0, $_.BaseName.Length - ($_.BaseName.Split("_")[$_.BaseName.Split("_").Length - 1].Length) - 1))}}, `
    @{Label="Path"; Expression={($_.FullName)}}, 
    @{Label="Size(MB)"; Expression={($_.Length /1024/1024)}}, `
    @{Label="Created"; Expression={($_.LastWriteTime)}}, `
    @{Label="ParentPath"; Expression={(Get-VHD $_.FullName).ParentPath}} | Where-Object {$_.Name -ne ""}
}

Function Export-ContainerImage([String]$ImageName, [String]$ExportPath, [Switch]$Tree){
    [Reflection.Assembly]::LoadWithPartialName( "System.IO.Compression.FileSystem" ) | Out-Null
    $Archive = [System.IO.Compression.ZipFile]::Open($ExportPath, "Update")
    $CompressionLevel = [System.IO.Compression.CompressionLevel]::Optimal
    $Image = Get-ChildItem (Get-ContainerImage | Where {$_.Name -eq "$ImageName"}).Path
    [System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile($Archive, $Image.FullName, $Image.Name, $CompressionLevel) | Out-Null  

    If($Tree){ 
        $ParentFile = (Get-ContainerImage | Where {$_.Name -eq "$ImageName"}).ParentPath  
        While ($ParentFile -ne ""){
            If($ParentFile -ne ""){
                [System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile($Archive, $ParentFile, (Get-ChildItem $ParentFile).Name, $CompressionLevel) | Out-Null
                $ParentFile = (Get-ContainerImage | Where {$_.Path -eq "$ParentFile"}).ParentPath
            }
        }        
    }
    $Archive.Dispose()
}

Function New-ContainerImage([String]$ContainerName, [String]$ImageName) {    
    $ImageName = $ImageName + "_" + [Guid]::NewGuid() 
    $VM = Get-VM $ContainerName
    $Disk = Get-VMHardDiskDrive $VM   
    If($VM.State -eq "Off"){
        Get-VMHardDiskDrive $VM | Copy-Item -Destination "$RootPath\Images\$ImageName.vhdx"
     }Else{
        Checkpoint-VM $VM -SnapshotName "$ContainerName"
        Copy-Item (Get-VHD (Get-VMHardDiskDrive $VM).Path).ParentPath -Destination "$RootPath\Images\$ImageName.vhdx"
        Remove-VMSnapshot $VM –Name "$ContainerName" 
    }   
}

Function Merge-ContainerImage([String]$ImageName, [String]$NewImageName, [Switch]$Del) { 
    $ImagePath = (Get-ContainerImage | Where {$_.Name -eq "$ImageName"}).Path  
    $ParentPath = (Get-VHD "$ImagePath").ParentPath
    $NewImageID = $NewImageName + "_" + [Guid]::NewGuid() 
    Copy-Item "$ParentPath" -Destination "$RootPath\Images\$NewImageID.vhdx" 
    Copy-Item "$ImagePath" -Destination "$RootPath\Images\$NewImageID.avhdx"
    Set-VHD -Path "$RootPath\Images\$NewImageID.avhdx" –ParentPath "$RootPath\Images\$NewImageID.vhdx"
    Merge-VHD –Path "$RootPath\Images\$NewImageID.avhdx" –DestinationPath "$RootPath\Images\$NewImageID.vhdx"  
    If($Del){ Remove-Item $ImagePath }
}

Function Remove-ContainerImage[String]$ImageName, [Switch]$Tree) { 
    $ImagePath = (Get-ContainerImage | Where {$_.Name -eq "$ImageName"}).Path
    If($ImagePath) {
        $ChildItems = Get-ChildItem -Path "$RootPath" -Include "*.vhdx" -Recurse | Get-VHD | Where {$_.ParentPath -eq "$ImagePath"}
        If($ChildItems.Count -eq 0){
            Remove-Item $ImagePath -Recurse
        }Else{   
            If($Tree){
                ForEach ($ChildItem in $ChildItems) {
                    If($ChildItem.Path.ToString().ToUpper().StartsWith("$RootPath\Images".ToString().ToUpper())){
                        $ChildImageName = (Get-ChildItem ($ChildItem.Path)).Name
                        $ImageName = ($ChildImageName.Substring(0, $ChildImageName.Length - ($ChildImageName.Split("_")[$ChildImageName.Split("_").Length - 1].Length) - 1)) 
                        Remove-ContainerImage $ImageName -Tree
                    }Else{
                        $ContainerName =  (Get-ChildItem ($ChildItem.Path)).BaseName
                        Remove-Container($ContainerName)
                    }
                }            
                Remove-Item $ImagePath -Recurse
            }Else{
                Write-Host """$ImageName"" is used other container images or containers." -ForegroundColor Red
            }
        }
    }Else{
         Write-Host """$ImageName"" is not container image name." -ForegroundColor Red
    }
}

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
} 

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}}
}

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

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

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
}

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
}

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)
}

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]    
        }        
    }
}

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."
}

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
}

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)    
        }
    }
}

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
        }
    }
}

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

コンテナの操作

コンテナの作成

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コマンド自体もいくつかのコマンドを組み合わせて作成しています。

手順としては、以下です。

  • コンテナの作成
  • コンテナの起動
  • コンテナのブート完了まで待機
  •  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

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


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

著書の紹介欄

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

できるPRO Windows Server 2016 Hyper-V

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

初めてのWindows Azure Pack本が発売

Windows Azure Pack プライベートクラウド構築ガイド

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

ブログの著者欄

樋口 勝一

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

1999年6月GMOインターネットグループ株式会社に入社。Windows Serverをプラットフォームとしたサービス開発から運用・保守まで幅広く担当。講演登壇や出版、ネット記事連載などでマイクロソフト社と強い信頼関係を構築。「マイクロソフトMVPアワード」を15度受賞し、インターネットソリューションのスペシャリストとして活躍。

採用情報

関連記事

KEYWORD

採用情報

SNS FOLLOW

GMOインターネットグループのSNSをフォローして最新情報をチェック