小規模VDI環境を構築する Part4 DBとIISを使ってVMを自動構築

ASP.NET

今回はDBとIISを使ってVMを自動構築していきます。

流れとしては、IISで作成したAPIが叩かれるとPowershellモジュールが動き、DBからまだアサインされていないVM用のデータ(IPアドレス等)を取得、それを基にVMを作成・設定し、VMの設定をDBに反映します。これによって、APIを叩くだけでVMの作成から設定までを自動化します。

VM作成の流れは以下の通りです。

DBのテーブルにデータ追加(DBサーバー)

VMの構築と自動設定は前のパートで行いましたが、IPアドレス等はpowershellで手打ちしないとまだ構築できません。(まだ半自動ですね)

そのため、DBにあらかじめIPアドレスとVM名などを関連付けたテーブルを作成しておき、それを参照することで手打ちしなくてもVMの自動設定が完了するようにします

DB構築時に作成したテーブルにデータを追加していきます。
以下のように作成しました。

--IPはお好みで入れてください
INSERT INTO virtual_machines
([vm_name],[status],[ipaddr])
VALUES
('VDI1', 'Not created','192.168.0.10'),
('VDI2', 'Not created','192.168.0.11'),
('VDI3', 'Not created','192.168.0.12'),
('VDI4', 'Not created','192.168.0.13'),
('VDI5', 'Not created','192.168.0.14')

これで以下のようにデータが入りました。これでVM名やIPアドレスの自動登録が出来るようになりました。

NULLになっている残りのデータはどうするのかというと、VM作成時に追加します。
例えばCPU4コア、メモリ4GB、ドライブ50GBを「プラン1」とします。
プラン1用のVM作成APIを作成するので、それが実行されたときにcreated_at、hostname、CPU、メモリ、ストレージ、OSなどが構築時にDBにも追加されます。
もっと高スペックのVMには8コアメモリ8GBのプラン2を作成できるAPIを作成しておきます。
このようにプランごとにAPIを作成しておき、VM作成時にプランの内容がDBにデータ追加されるようにします。
これででDBでVMのスペックや状態などを素早く確認することができます。起動が遅いHyper-Vマネージャーでいちいち確認する必要もありません。
以下はVM作成後のDBの例

DBの接続設定

APIサーバーがDBの情報を取ってこれるようにDBサーバーで1433ポートを開けておきます。

そして、DBサーバーでSQLServer構成マネージャーを開き、SQL Serverネットワークの構成→プロトコル→TCP/IPを有効にします。(ここで悩みました。。)

参考にさせていただいた記事
【Error:40】SQL Serverへの接続ができないときの対策まとめ。 – おーみんブログ

これでAPIサーバーからDBサーバーにクエリを送信できるようになりました。

IISでAPIの作成(APIサーバー)

IISでAPIを作成してIIS構築時に作ったAPI用サイトに設置していきます。

APIに関しては以前記事を書いているのでこちらをご覧ください。
続きを合わせて3つの記事があるので記事を基にしてAPI作成とIISに配置が完了したものとします。
後述のVM構築API作成でこの記事の内容を基にするところがあります。

Visual Studioのインストール

API作成の記事をすっ飛ばした方、もしくはAPIサーバーにVisual Studioをインストールしていない方はAPIをIISに設置するのにVisual Studioを使用するのでインストールしてください。無料版で大丈夫です!
Visual Studio Tools のダウンロード – Windows、Mac、Linux 用の無料インストール

ASP.NETが使えればいいのでAzure開発等はなくても大丈夫です。

インストールが長いですが完了すれば再起動します。

そのあとにちょっとした設定があってパーソナライズされるのですが、これが固まっているのかどうかわからないくらい遅いので僕はタスク終了させて再起動させました。。。

VM構築スクリプトをサーバーに配置(ホストサーバー)

APIサーバーからVM名をIPアドレスを受け取ってそれを基にVMを作成してネットワーク設定・AD参加などを行うスクリプトです。
APIからの実行のみに対応しています。前のパートで作成した自動構築スクリプトをAPI用に改良しましたので長いですがどうぞ、、
(前のパートのスクリプトをまとめてAPI実行に対応したものです)

Function Set-VMIPConfig
([String]$VMName,
[String]$NIC,
[String]$IPAddress = @(),
[String]$Subnet = @(),
[String]$Gateway = @(),
[String]$DNS = @()) {
[CmdletBinding()]
$ManagementService = Get-WmiObject -Namespace root\virtualization\v2 -Class Msvm_VirtualSystemManagementService
$ComputerSystem = Get-WmiObject -Namespace root\virtualization\v2 -Class Msvm_ComputerSystem -Filter "ElementName = '$VMName'"
$SettingData = $ComputerSystem.GetRelated("Msvm_VirtualSystemSettingData")
$NetworkAdapters = $SettingData.GetRelated('Msvm_SyntheticEthernetPortSettingData') | Where-Object { $_.ElementName -eq "$NIC" }
$TargetNetworkAdapter = (Get-VMNetworkAdapter $VMName)[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
}
function Join-AD {
[CmdletBinding()]
param(
[String]$VMName
)
#パスワードはunattend.xmlで設定したものです。
$Password = ConvertTo-SecureString -AsPlainText 'VMパスワード' -Force
$Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList 'Administrator', $Password
$Command = @'
$Domain = "hypervdi.win"
#ドメイン管理者のアカウント
$DomainAdmin = "Administrator@hypervdi.win"
#ドメイン管理者のパスワード
$Password = ConvertTo-SecureString "パスワード" -AsPlainText -Force
$Credential = New-Object System.Management.Automation.PSCredential($DomainAdmin, $Password)
Add-Computer -DomainName $Domain -Credential $Credential
Restart-Computer -Force
'@
$ScriptBlock = [ScriptBlock]::Create($Command)
Invoke-Command -VMName $VMName -Credential $Credential -ScriptBlock $ScriptBlock
}
Function DeployPlan1VM {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]$VMName,
[Parameter(Mandatory = $true)]$IPaddress
)
$RAM = 4GB
$CPUCore = 4
#大きなネットワークになる場合はここもDBから取ってくるようにします
$Subnet = "255.255.255.0"
$Gateway = "192.168.10.1"
$DNS = "8.8.8.8"
#前のパートで作成した差分ディスクのパスを入れます
$VHD = New-VHD -Path  "D:\Hyper-V\$VMName\$VMName.vhdx" -Differencing -ParentPath "D:\Hyper-V\差分ディスクのパス" -ErrorAction Stop
New-VM -Name $VMName -Generation 2 -MemoryStartupByte $RAM -VHDPath $VHD.Path -Path "D:\Hyper-V\$VMName" -SwitchName VirtualSwitch
Set-VM -Name $VMName -ProcessorCount $CPUCore -ErrorAction Stop
#VLAN設定が必要な場合
#Set-VMNetworkAdapterVlan -VMName $VMName -Access -VlanId **
Set-VMIPConfig -VMName $VMName -NIC "ネットワーク アダプター" -IPAddress $IPaddress -Subnet $Subnet -Gateway $Gateway -DNS $DNS
Start-VM -Name $VMName
Start-Sleep 60
#パスワードはunattend.xmlで設定したものです。
$Password = ConvertTo-SecureString -AsPlainText 'VMパスワード' -Force
$Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList 'Administrator', $Password
#New-PSSessionでVMに接続
$pss = New-PSSession -VMName $VMName -Credential $Credential
while ($true) {
Start-Sleep 10
#接続できていない場合は再度試みる
$SessionState = (Get-PSSession -VMName $VMName).State
if ($SessionState -eq "Opened") {
break
}
else {
Get-PSSession | Remove-PSSession
$pss = New-PSSession -VMName $VMName -Credential $Credential
}
}
#RDP接続許可
$Command = @'
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server" -Name "fDenyTSConnections" -Value "0"
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp" -Name UserAuthentication -Value "0"
'@
$ScriptBlock = [ScriptBlock]::Create($Command)
Invoke-Command -Session $pss -ScriptBlock $ScriptBlock
Remove-PSSession $pss
#AD参加
Join-AD -VMName $VMName
}
}

差分イメージからVMを作成し、ネットワーク設定とAD設定までやってくれるので、RDPしてVMを利用することが出来ます。

これをIISで実行出来るようにPowerShellモジュールにします。これはHyper-Vが動いている物理サーバーに設置します。
名前をつけて保存でpsm1ファイルにして、”C:\Program Files\WindowsPowerShell\Modules\hypervdi\hypervdi.psm1″というパスになるように保存します。

これでPowerShellモジュールにすることができました。Powershellを開いてみてDeployPlanと打ってTabを押すとDeployPlan1VMと補完されます。

TrustedHostsの登録

以下のコマンドをホストサーバー上で実行します。

Set-Item WSMan:\localhost\Client\TrustedHosts -Value APIサーバーのIP

TrustedHostsに登録しなくてもAPIは動くかもしれませんが、念のためTrustedHostsに登録しておきます。

VM構築API作成(APIサーバー)

VM構築APIの仕組みは簡単です。まずDBからまだ未作成のVM名とIPアドレスを取得して、物理サーバーにDeployPlan1VMコマンドをInvoke-Commandで送信するだけの関数を作成し、それをAPIから実行させます。

API用スクリプト

以下を自作モジュールとしてpsm1ファイルに保存します。

Function Invoke-SQLQuery{
param (
[Parameter(Mandatory = $true)][String] $SQLQuery
)
$ConnectionString = New-Object -TypeName System.Data.SqlClient.SqlConnectionStringBuilder
#サーバー
$ConnectionString['Data Source']         = "DBserver"
#データベース
$ConnectionString['Initial Catalog']     = "hypervdi"
#Windows認証
$ConnectionString['Integrated Security'] = $true
$resultsDataTable = New-Object System.Data.DataTable
$SqlConnection = New-Object System.Data.SQLClient.SQLConnection($ConnectionString)
$SqlCommand = New-Object System.Data.SQLClient.SQLCommand($SQLQuery, $SqlConnection)
# データベース接続
$SqlConnection.Open()
#クエリ実行
$resultsDataTable.Load($SqlCommand.ExecuteReader())
$SqlConnection.Close()
# 画面表示
return $resultsDataTable
}
Function Invoke-DeployPlan1VM {
#ドメイン管理者のアカウント
$DomainAdmin = "Administrator@hypervdi.win"
#ドメイン管理者のパスワード
$Password = ConvertTo-SecureString "ADパスワード" -AsPlainText -Force
$Credential = New-Object System.Management.Automation.PSCredential($DomainAdmin, $Password)
$SQLQuery = "SELECT TOP (1) * FROM [hypervdi].[dbo].[virtual_machines] where status = 'Not Created'"
$SQLdata = Invoke-SQLQuery -SQLQuery $SQLQuery
$VMName = $SQLdata.vm_name
$IPaddress = $SQLdata.ipaddr
#複数ホストになった場合はDB等で管理して取得する
$HostName = "windows-server1"
$result = Invoke-Command -ComputerName $HostName -Credential $Credential -ScriptBlock {param($VMName,$IPaddress) DeployPlan1VM -VMName $VMName -IPaddress $IPaddress} -ArgumentList $VMName,$IPaddress
if($result){
####VMの情報をDBに送信
$TimeStamp = Get-Date
$SQLQuery = "UPDATE virtual_machines SET created_at = '$TimeStamp', status = 'Run', hostname = '$HostName', vcpu_core = 4, vcpu_resource = 100, ram = 4, storage = 50, os = '2019' where vm_name = '$VMName'"
$SQLdata = Invoke-SQLQuery -SQLQuery $SQLQuery
}
}

これを自作モジュールにしてお好みの場所に置いておきます。僕は以下にしました。

"C:\Program Files\WindowsPowerShell\Modules\hypervdiapi\hypervdiapi.psm1"

APIをVM作成用に改良

仮のAPIは作成していると思いますので、VM作成用のコードに改良していきます。以下のようになりました。

// Use Custom Powershell Module in C#
using System;
using System.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
namespace hypervdiAPI.Controllers //ここはプロジェクト名.controllerです
{
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
//GET Plan1VMを作成
[HttpGet]
[Route("Invoke-DeployPlan1VM")]
public void DeployPlan1VM()
{
var initialState = InitialSessionState.CreateDefault2();
initialState.ExecutionPolicy = Microsoft.PowerShell.ExecutionPolicy.Unrestricted;
initialState.ImportPSModulesFromPath(@"C:\Program Files\WindowsPowerShell\Modules\hypervdiapi");
Runspace runspace = RunspaceFactory.CreateRunspace(initialState);
runspace.Open();
using var ps = PowerShell.Create(initialState);
ps.Runspace = runspace;
var results = ps.AddCommand("Invoke-DeployPlan1VM").Invoke();
}
}
}

ValueController.csにある自作モジュールを動かすコードをこちらに更新すればOKです。
ただVM作成コマンドを実行するように書き換えただけですね。

試運転してみましょう。swaggerを開いて実行してみます。

Hyper-VのほうでVMの作成が進んでいたら成功です。VM作成が完了すれば200が返ってきます。

もしVM作成が動かなかった場合

ソリューションエクスプローラーのプロジェクト名をクリックするとcsprojファイルが開けますが、その中にある以下の部分を削除してみてもう一度ビルドしてみてください。

 <InvariantGlobalization>true</InvariantGlobalization>

発行してIISに設置する

いよいよ発行していきます。

右のソリューションエクスプローラーのプロジェクト名をクリックすると発行というのがあるのでそれをクリックします。


そして新規プロファイル追加を押して、今回はフォルダを選択します。


任意のパスを選択して完了を押します。何も使われていないフォルダでないとエラーが出ることがあるのでフォルダは新規作成をおすすめします。(以前のAPI作成記事で発行したフォルダはもう使わないのでもしあれば削除しておいたほうが混乱がなくて良いと思います)


発行ボタンを押すとビルドされ、そのフォルダにデータが発行されます。


次にIISでサイトを新規に作ります。こんな感じでつくりました。以前の記事を参考にして作ったサイトがあれば削除しておいてくださいませ。ポートは特に指定がなく、グローバルで公開することがない環境なので80にしてます。


次にアプリケーションプールで先ほど作成したhypervdi-apiのアプリケーションプールを右クリックして既定値の設定を開きます。
そのあと、プロセスモデルのIDのところをドメイン管理者にしてください。これがないとホスト側にリモートコマンドを送信できなかったので、、、
(他に解決法があると思うのですが、、、)

これでサイトが作成されたので、APIのテストをしてみます。
IISの右の操作という欄にWebサイトの参照があるのでそれをクリックします。


そのあとに/api/Values/Invoke-DeployPlan1VMを入れて実行します。

たぶんブラウザがグルグルしたままになります。ホスト側のHyper-Vを見てみると、、、

VMが作成されてました。ブラウザのグルグルが終わったら作成完了です。


DBも更新されてましたので、完璧ですね
(たくさん検証したので11VMも登録しました、、、笑)

IIScontroller以外の場所からAPIを実行するには80番ポートを開けておく必要があります。
ポートを開けてホスト上からPowershellでAPIを叩いてみました。


200が返ってきてVMの作成にも成功していました。

まとめ

今回はDBとIISを利用してVMの自動構築APIを作成しました。
ここまでできれば、あとはフロントを作成してボタンなんかをクリックすればAPIが叩かれてVM作成したり、VMの起動や停止も行えたり、スペックアップ等も全部APIで行うことが出来ます。
これを書いているときにWSUSの登録が漏れていたことに気づきました、、、まあADのグループポリシーでWSUSの設定をしていればあとはVMの中でWindows Updateを実行してあげるだけでWSUSに登録できると思います、、、

とりあえずVDI環境のインフラ部分はほぼ完成しました。これで小規模VDI環境はお手の物ですね!!いろいろカスタムして最強のVDI環境を作ってくださいませ
また追記とかおまけがあれば書きます、、、

タイトルとURLをコピーしました