😺

Azure Elastic SANがGAされたのでクラスタ環境を作ってみた

2024/03/27に公開

はじめに

2024/2にAzure Elastic SANがGAとなりました。2022/10にプレビュー公開されてから、1年半ほどでのGAになります。

AzureでElasticと名前が付くのは珍しい気がします。
また、SAN(日本語だと記憶域ネットワーク)と名前が付いていますが、
ネットワークサービスではなく、ストレージサービスです。
端的に言うとiSCSIで接続するストレージを提供するストレージサービスです。

つまり、オンプレ時代にFCやiSCSIで接続していたストレージをクラウドで再現した、という感じです。
FCはさすがにプロトコルが違ってくるので難しい気はしますが、iSCSIであればIPベースですので
出来ないことはない、という感じでしょうか。
ありそうで今までなかったのも不思議な感じがします。

さて、そうなってくると、オンプレではたくさんお世話になった人もいるであろう、
共有ストレージを使ってクラスタ(WSFC)を組む、というのを早速試してみたいと思います。

Azure Elastic SANのデプロイ

まずは、Elastics SANを作成します。

サイズの最小は、Base領域のみの1TBです。
今回は追加領域も1TB追加しています。

ゾーン冗長が使えるリージョンもありますが、東日本リージョンはまだ対応していません。
LRSを使う場合は、AZを指定します。

Azure Elastic SANの作成
sanName="esan" # リソース名
rgName="rg-esan" # リソースグループ
region="japaneast" # リージョン
zone="1" # 可用性ゾーン

# Azure Elastic SANの作成
az elastic-san create -n $sanName -g $rgName -l $region \
                      --base-size-tib 1 \
                      --extended-capacity-size-tib 1 \
                      --sku "{name:Premium_LRS,tier:Premium}" \
                      --availability-zones $zone

VolumeGroupとVolumeの作成

Azure Elastic SANのリソースが作成出来たら、
続けてボリュームグループとボリュームを作成します。

ボリュームグループ、ボリュームの作成
vgName="VolumeGroup1" # ボリュームグループ名
volumeName="Volume1" # ボリューム名

# ボリュームグループの作成
az elastic-san volume-group create --elastic-san-name $sanName \
                                  -g $rgName \
                                   -n $vgName

# ボリュームの作成
az elastic-san volume create --elastic-san-name $sanName \
                             -g $rgName \
                             -v $sanVgName \
                             -n $volumeName \
                             --size-gib 500

ネットワークの構成

ここまでで、ストレージとしては作成できていますが、VMとの接続部分のネットワークの構成を実施します。何もしないとパブリックIPベースでの接続になってしまいます。

仮想ネットワークにデプロイしたサービスとの接続には、他のAzureサービスと同様、サービスエンドポイントと、プライベートエンドポイントの両方を使うことができます。

サービスエンドポイントの場合は、「Microsoft.Storage」のサービスエンドポイントを有効化することで、VM等とElastic SANの間の通信がサービスエンドポイント経由になります。

今回は、プライベートエンドポイント経由で作成します。

プライベートエンドポイントの作成
vnetName="vnet-esan"
vmSubnetName="vm"
peSubnetName="endpoint"
endpointName="pe-esan"
plsConnectionName="pls-esan"

# Vnet作成
az network vnet create -g $rgName -n $vnetName --address-prefixes 10.0.0.0/16 

# subent 作成
az network vnet subnet create -g $rgName -n $vmSubnetName --vnet-name $vnetName --address-prefixes 10.0.0.0/24
az network vnet subnet create -g $rgName -n $peSubnetName --vnet-name $vnetName --address-prefixes 10.0.1.0/24

# Elastic SANのID取得
id=$(az elastic-san show -g $rgName \
    --elastic-san-name $sanName \
    --query 'id' \
    --output tsv)

# プライベートエンドポイントの作成
az network private-endpoint create -g $rgName -n $endpointName \
                                   --connection-name $plsConnectionName \
                                   --private-connection-resource-id $id \
                                   --vnet-name $vnetName \
                                   --subnet $peSubnetName \
                                   --location $region \
                                   --group-id $vgName 


公式ドキュメントの手順ではここまでしかありませんが、利用するVMでプライベートエンドポイントにアクセスするには、プライベートエンドポイントの名前解決ができる必要があるため、プライベートDNSゾーンを追加していきます。

プライベートDNSゾーン追加
dnsZoneName="privatelink.blob.core.windows.net"
privateDnsZoneGroup="default"

# プライベートDNSゾーンの作成
az network private-dns zone create -g $rgName -n $dnsZoneName

# プライベートDNSゾーンとVnetのリンク
az network private-dns link vnet create -g $rgName -n "link" \
                                        -e "no" \
                                        -v $vnetName \
                                        -z $dnsZoneName

# プライベートDNSゾーンとプライベートエンドポイントの統合
az network private-endpoint dns-zone-group create -g $rgName -n $privateDnsZoneGroup \
                                                  --endpoint-name $endpointName \
                                                  --zone-name $dnsZoneName \
                                                  --private-dns-zone $dnsZoneName

VM作成

作成しておいたvmサブネットにWindowsServer2019を2台作成します。
必要となるWindowsの機能もそれぞれ追加します。

VM作成
vm1Name="vm1"
vm2Name="vm2"
userName="admin"
password="(パスワード)"
size="Standard_D2s_v5"

vnetName="vnet-esan"
vmSubnetName="vm"

# vm1作成 
az vm create -g $rgName -n $vm1Name \
             --iamge Win2019Datacenter \
             -public-ip-address "" \
             --admin-username $userName \
             --admin-password $password \
             --vnet-name $vnetName \
             --subnet $vmSubnetName \
             --nsg-rule "NONE"
             --size $size

# vm2作成 
az vm create -g $rgName -n $vm2Name \
             --iamge Win2019Datacenter \
             -public-ip-address "" \
             --admin-username $userName \
             --admin-password $password \
             --vnet-name $vnetName \
             --subnet $vmSubnetName \
             --size $size

# 機能の追加(vm1)
az vm run-command invoke -g $rgName -n $vm1Name \
                         --command-id RunPowerShellScript \
                         --scripts "Install-WindowsFeature -name FS-FileServer,FS-iSCSITarget-Server,Multipath-IO,Failover-Clustering -IncludeManagementTools"

# 機能の追加(vm2)
az vm run-command invoke -g $rgName -n $vm2Name \
                         --command-id RunPowerShellScript \
                         --scripts "Install-WindowsFeature -name FS-FileServer,FS-iSCSITarget-Server,Multipath-IO,Failover-Clustering -IncludeManagementTools"

MPIOとiSCSIの設定

VMを作成したら、OS内の設定を実施していきます。
まず、MPIOの設定でiSCSIサポートを有効化しておきます。

MPIOやiSCSIの設定値は、公式ドキュメントにベストプラクティスが記載されているので、それを参考にします。
https://learn.microsoft.com/ja-jp/azure/storage/elastic-san/elastic-san-best-practices

MPIOの設定
# Enable multipath support for iSCSI devices
Enable-MSDSMAutomaticClaim -BusType iSCSI

# Set the default load balancing policy based on your requirements. In this example, we set it to round robin which should be optimal for most workloads.
mpclaim -L -M 2

# Set disk time out to 30 seconds
Set-MPIOSetting -NewDiskTimeout 30

iSCSIの設定はレジストリの編集になります。
KEYの途中の「0005」の部分は環境によって変わる可能性があります。
公式ドキュメントは0004になっていますが、私の環境では0005でした。

設定する前にregeditで対象のキーを確認して数字を設定してください。
DriverDescが「Microsoft iSCSI Initiator」になっているものが対象です。

alt text

ベストプラクティスの値はそれぞれ公式ドキュメントに記載の通りですが、デフォルトでその値になっているものもいくつかあります。以下ではデフォルト値と同じだったものはコメントアウトしています。

alt text

iSCSIの設定
set KEY="HKLM\SYSTEM\CurrentControlSet\Control\Class\{4d36e97b-e325-11ce-bfc1-08002be10318}\0005\Parameters"

REM イニシエーターがターゲットへの iSCSI PDU で送信する最大データを 256 KB に設定する
REM reg add %KEY% /v MaxTransferLength /d 262144 /t REG_DWORD /f 
REM イニシエーターがターゲットとネゴシエートする最大 SCSI ペイロードを 256 KB に設定する
REM reg add %KEY% /v MaxBurstLength /d 262144 /t REG_DWORD /f 
REM イニシエーターがターゲットへの iSCSI PDU で送信する最大未承諾データを 256 KB に設定する
reg add %KEY% /v FirstBurstLength /d 262144 /t REG_DWORD /f 
REM イニシエーターがターゲットからの iSCSI PDU で受信できる最大データを 256 KB に設定する
reg add %KEY% /v MaxRecvDataSegmentLength /d 262144 /t REG_DWORD /f 
REM R2T フロー制御を無効にする
REM reg add %KEY% /v InitialR2T /d 0 /t REG_DWORD /f 
REM 即時データを有効にする
REM reg add %KEY% /v ImmediateData /d 1 /t REG_DWORD /f 
REM WMI 要求のタイムアウト値を 30 秒に設定する
REM reg add %KEY% /v WMIRequestTimeout /d 30 /t REG_DWORD /f 
REM リンク ダウン タイムのタイムアウト値を 30 秒に設定します
reg add %KEY% /v LinkDownTime /d 30 /t REG_DWORD /f 

VMとの接続

ようやく、VMとElastic SANの接続です。
GUIで設定してもよいのですが、Azure Elastic SANでボリュームを作成すると、接続用の実行コマンドを合わせて作成してくれます。便利なものがあるので、ぜひ利用しましょう。

注意点としては、Azure Portal上でスクリプトのコピーボタンが表示されるので、
コピーした後、そのままPowerShellのプロンプトに流し込みたいところですが、
そのままではエラーになります。
一度ファイルに保存(.ps1)した上で、スクリプトを実行するとエラー無く実行できます。

自動生成されたスクリプトはこんな感じです。
Elastic SANの固有名の部分だけ、念のため伏せてあります。
Windows用だけ載せていますが、Linux用のスクリプトも自動生成されます。

自動生成されたコード(Windows/PowerShell)
# Check dependency
$title    = 'Confirm'
$choices = @(
	[System.Management.Automation.Host.ChoiceDescription]::new("&Yes to terminate", "Yes to terminate")
	[System.Management.Automation.Host.ChoiceDescription]::new("&No to proceed with rest of the steps", "No to proceed with rest of the steps")
)

## iSCSI initiator
$iscsiWarning = $false
try {
	$checkResult = Get-Service -Name MSiSCSI -ErrorAction Stop
} catch {
	$iscsiWarning = $true
}
if (($checkResult.Status -ne "Running") -or $iscsiWarning) {
	$question = 'iSCSI initiator is not installed or enabled. It is required for successful execution of this connect script. Do you wish to terminate the script to install it?'
	$decision = $Host.UI.PromptForChoice($title, $question, $choices, 0)
	if ($decision -eq 0) {
		exit
	}
}

## Multipath I/O
$multipathWarning = $false
try {
	$checkResult = Get-WindowsFeature -Name 'Multipath-IO' -ErrorAction Stop
} catch {
	$multipathWarning = $true
}
if (($checkResult.InstallState -ne "Installed") -or $multipathWarning) {
	$question = 'Multipath I/O is not installed or enabled. It is recommended for multi-session setup. Do you wish to terminate the script to install it?'
	$decision = $Host.UI.PromptForChoice($title, $question, $choices, 0)
	if ($decision -eq 0) {
		exit
	}
}

class VolumeData
{
	[ValidateNotNullOrEmpty()][string]$VolumeName
	[ValidateNotNullOrEmpty()][string]$TargetIQN
	[ValidateNotNullOrEmpty()][string]$TargetHostName
	[ValidateNotNullOrEmpty()][string]$TargetPort
	[AllowNull()][Nullable[System.Int32]]$NumSession

	VolumeData($VolumeName, $TargetIQN, $TargetHostName, $TargetPort, $NumSession) {
		$this.VolumeName = $VolumeName
		$this.TargetIQN = $TargetIQN
		$this.TargetHostName = $TargetHostName
		$this.TargetPort = $TargetPort
		$this.NumSession = if ($NumSession -eq $null) {32} Else {$NumSession}
	}
}

$volumesToConnect= New-Object System.Collections.Generic.List[VolumeData]

$volumesToConnect.Add([VolumeData]::new("volume1", "iqn.2024-03.net.windows.core.blob.ElasticSan.es-************:volume1", "es-************.z32.blob.storage.azure.net", "3260", 32))

$sessions = Get-IscsiSession
if ($sessions -ne $null) {
	$sessions = (Get-IscsiSession).TargetNodeAddress.ToLower() | Select -Unique
}

# Modify the max login retries per session if necessary
$maxLoginRetriesPerSession = 5

foreach($volume in $volumesToConnect) {
	# Check if the volume is already connected
	if ($sessions -ne $null -and $sessions.Contains($volume.TargetIQN.ToLower())) {
		Write-Host $volume.VolumeName [$($volume.TargetIQN)]: Skipped as this volume is already connected -ForegroundColor Magenta
		continue
	}
	# Connect volume
	Write-Host $volume.VolumeName [$($volume.TargetIQN)]: Connecting to this volume -ForegroundColor Cyan
	iscsicli AddTarget $volume.TargetIQN * $volume.TargetHostName $volume.TargetPort * 0 * * * * * * * * * 0
	$LoginOptions = '0x00000002'
	for ($i = 0; $i -lt $volume.NumSession; $i++) {
		iscsicli PersistentLoginTarget $volume.TargetIQN.ToLower() t $volume.TargetHostname.ToLower() $volume.TargetPort Root\ISCSIPRT\0000_0 -1 * $LoginOptions 1 1 * * * * * * * 0
		$loginAttempts = 0
		do {
			iscsicli LoginTarget $volume.TargetIQN t $volume.TargetHostName $volume.TargetPort Root\ISCSIPRT\0000_0 -1 * $LoginOptions 1 1 * * * * * * * 0
			$loginAttempts += 1
		} while (($LASTEXITCODE -ne 0) -and ($loginAttempts -lt $maxLoginRetriesPerSession))
	}
}

WSFC作成

それでは、WSFCを作成します。
クラスタ上にロール(アプリケーション)を展開するには仮想IP等の設定が追加で必要になりますが、
今回はそのあたりはすっ飛ばして、クラスタにVMとディスクを追加するとこまで実施します。

New-Cluster -Name cluster1 -Node vm1,vm2 -AdministrativeAccessPoint DNS

クラスタを作成後、クラスタマネージャの管理画面で確認した様子です。
作成したAzure Elastic SANのボリュームがQuorumディスクとして使用できていることが分かります。
alt text

まとめ

Azure Elastic SANを用いてWindowsサーバのクラスタ構成を作成してみました。
従来、Azure環境ではマネージドディスクを共有ディスクとして用いることでクラスタ環境を作成することはできましたが、よりオンプレに近い構成でクラスタ構成を組むことができるようになったと言えそうです。
単なるクォーラムだであればマネージドディスクで十分な気がしますが、ファイルサーバ等ストレージの性能が必要となるサービスを作成したい場合は使ってみるとよいと思います。

Discussion