はじめに

以前、PowerShellからHyper-VのVM作成を行う記事を書いた。 ただ、例で使ったAlmaLinuxの場合、VMを作成した後にGUIからポチポチとインストールを行う必要がある。検証用のVMを何度も作る場合、面倒だ。

今回は、Red Hat系で使われる自動インストール機能であるKickstartを使って、 AlmaLinuxのインストールを自動化したので、その備忘録を残す。

実施環境

  • ホストOS: Windows 11
  • ゲストOS: AlmaLinux 10.1
  • PowerShellバージョン: 5.1

前提

この記事の手順は管理者権限で起動したPowerShellで実行する。

Kickstartの設定ファイルを作成する

Kickstartでインストールするためには、ks.cfgファイルを作成する必要がある。

PowerShellから以下コマンドで、ks.cfgファイルをHyper-VのVM用フォルダに作成する。

# VMの名前(好きな名前でOK)
$vmName = "AlmaLinux10-1-sandbox"
# 作成したVMに関連するファイル(チェックポイント等)の保存先(好きなパスでOK)
$vmPath = "$env:USERPROFILE\Hyper-V\$vmName"
# 今回はKickstart関連ファイルの保存先(好きなパスでOK)
$ksDir = "$vmPath\kickstart"
# `ks.cfg`ファイルのファイル名と保存先
$ksFile = "$ksDir\ks.cfg"

# Kickstart関連ファイルの保存先を作成する
New-Item -ItemType Directory -Force -Path $ksDir | Out-Null

# `ks.cfg`ファイルを作成する
$ksContent = @'
# GUIではなく、テキストモードでインストールする
text

# インストール完了後にシャットダウンする
# PowerShell側でVM停止を検知し、ISOとKickstart用VHDXを外すため
shutdown

# インストール元としてCD/DVDメディアを使う
cdrom

# 言語、キーボード、タイムゾーン
# キーボードを日本語配列にしたい場合は、keyboard --xlayouts='jp'にする
lang en_US.UTF-8
keyboard --xlayouts='us'
timezone Asia/Tokyo --utc

# DHCPでネットワークを有効化し、ホスト名を「almalinux.local」にする
network --bootproto=dhcp --device=link --activate --hostname=almalinux.local

# rootのパスワードを指定する
rootpw --plaintext example_root_password

# root以外のユーザを作成し、wheelグループに入れる
user --name=example_user --password=example_user_password --plaintext --groups=wheel

# SSHを許可し、FirewallとSELinuxは有効にする
firewall --enabled --service=ssh
selinux --enforcing

# ブートローダー設定
bootloader --location=mbr

# インストール対象は1つ目のディスクだけにする
ignoredisk --only-use=sda

# 既存パーティションがあれば削除し、自動パーティション作成する
zerombr
clearpart --all --initlabel --drives=sda
autopart --type=lvm

# SSHサーバを入れておく
%packages
@^minimal-environment
openssh-server
%end

# インストール後にSSHを自動起動し、日本語ロケールを設定する
# dnfでインストールしておきたいものがあれば、ここに書く
%post
systemctl enable sshd
dnf install -y glibc-langpack-ja
echo 'LANG=ja_JP.UTF-8' > /etc/locale.conf
%end
'@

# BOMなしUTF-8で保存する
$utf8NoBom = New-Object System.Text.UTF8Encoding $false
[System.IO.File]::WriteAllText($ksFile, $ksContent, $utf8NoBom)

Kickstart用VHDXを作成する

Red Hat系では、OEMDRVというラベルのボリューム直下にks.cfgがあると、自動でKickstartファイルとして読み込む。そのため、今回はOEMDRVラベルのVHDXを作成し、Windowsにマウントした後、ks.cfgを配置するようにする。

以下コマンドを実行する。冒頭にも書いたとおり、管理者権限で実行すること。

# Kickstart用VHDXの保存先
$ksVhdxPath = "$vmPath\kickstart-oemdrv.vhdx"
# Kickstart用VHDXを作成する
New-VHD -Path $ksVhdxPath -SizeBytes 64MB -Dynamic | Out-Null
# マウントするパス
$mountPath = "$vmPath\kickstart-mount"
# マウント先フォルダを作成する
New-Item -ItemType Directory -Force -Path $mountPath | Out-Null
# フォルダにマウントしたかどうかのフラグ用変数
$accessPathAdded = $false

try {
  # VHDXをWindowsにマウントする
  $vhd = Mount-VHD -Path $ksVhdxPath -PassThru
  # マウントしたVHDXに対応するディスク情報を取得する
  $disk = $vhd | Get-Disk

  # 初期化してFAT32でフォーマットする
  # ラベルはKickstart自動検出用にOEMDRVにする
  Initialize-Disk -Number $disk.Number -PartitionStyle MBR

  # VHDXを使ってパーティションを作成する
  $partition = New-Partition `
    -DiskNumber $disk.Number `
    -UseMaximumSize

  # 自動でドライブ文字を割り当てないようにする
  Set-Partition `
    -DiskNumber $disk.Number `
    -PartitionNumber $partition.PartitionNumber `
    -NoDefaultDriveLetter $true

  # 作成したパーティション情報を再取得する
  $partition = Get-Partition `
    -DiskNumber $disk.Number `
    -PartitionNumber $partition.PartitionNumber
  # 念のため、ドライブ文字が割り当てられていた場合は削除する
  if ($partition.DriveLetter) {
    Remove-PartitionAccessPath `
      -DiskNumber $disk.Number `
      -PartitionNumber $partition.PartitionNumber `
      -AccessPath "$($partition.DriveLetter):\"
  }

  # パーティションをFAT32でフォーマットし、ラベルをOEMDRVにする
  Format-Volume `
    -Partition $partition `
    -FileSystem FAT32 `
    -NewFileSystemLabel OEMDRV `
    -Confirm:$false | Out-Null

  # ドライブ文字ではなく、フォルダにマウントする
  Add-PartitionAccessPath `
    -DiskNumber $disk.Number `
    -PartitionNumber $partition.PartitionNumber `
    -AccessPath $mountPath
  $accessPathAdded = $true

  # KickstartファイルをVHDXのルート直下にks.cfgとしてコピーする
  Copy-Item `
    -LiteralPath $ksFile `
    -Destination "$mountPath\ks.cfg" `
    -Force
}
finally {
  # フォルダにマウントしている場合、マウントを解除する
  if ($accessPathAdded) {
    Remove-PartitionAccessPath `
      -DiskNumber $disk.Number `
      -PartitionNumber $partition.PartitionNumber `
      -AccessPath $mountPath `
      -ErrorAction SilentlyContinue
  }
  # 処理中にエラーが発生しても、必ずVHDXをアンマウントする
  Dismount-VHD -Path $ksVhdxPath -ErrorAction SilentlyContinue
}

Kickstartを使ってVMを作成する

前回の記事を参考にVMを作成する。ただし、今回はks.cfgがあるVHDXを接続して作成する。

ダウンロードしたAlmaLinuxのISOファイルは、前回と同様、%USERPROFILE%\Downloads\に置くことにする。

まず、前回と同様に以下コマンドでVMを作成する。

# AlmaLinux VMの.vhdxファイルの保存先
$vhdPath = "$vmPath\$vmName.vhdx"
# ダウンロードしたAlmaLinux ISOの保存先
$isoPath = "$env:USERPROFILE\Downloads\AlmaLinux-10.1-x86_64-minimal.iso"
# 接続する仮想スイッチ名
$switchName = "Default Switch"
# 動的メモリを使用するかどうか
$dynamicMemoryEnabled = $true
# VM起動時に割り当てるメモリサイズ
$memoryStartupBytes = 4GB
# 最小メモリ
$minimumBytes = 2GB
# 最大メモリ
$maximumBytes = 8GB
# VMの仮想ストレージの最大サイズ
$vhdSizeBytes = 40GB
# VMに割り当てる仮想CPUコア数
$processorCount = 2
# 仮想マシンの世代
$generation = 2

# VMを作成する
New-VM `
  -Name $vmName `
  -Path $vmPath `
  -NewVHDPath $vhdPath `
  -SwitchName $switchName `
  -NewVHDSizeBytes $vhdSizeBytes `
  -Generation $generation

# CPUの設定を行う
Set-VMProcessor -VMName $vmName -Count $processorCount

# メモリの設定を行う
Set-VMMemory `
  -VMName $vmName `
  -DynamicMemoryEnabled $dynamicMemoryEnabled `
  -StartupBytes $memoryStartupBytes `
  -MinimumBytes $minimumBytes `
  -MaximumBytes $maximumBytes

# チェックポイントを無効にする
Set-VM -Name $vmName -CheckpointType Disabled

# DVDドライブにAlmaLinux ISOを接続
Add-VMDvdDrive -VMName $vmName -Path $isoPath

# Secure Bootを有効にする
Set-VMFirmware -VMName $vmName -EnableSecureBoot On -SecureBootTemplate MicrosoftUEFICertificateAuthority

# AlmaLinux ISOから起動するようにする
$dvdDrive = Get-VMDvdDrive -VMName $vmName | Where-Object { $_.Path -eq $isoPath }
Set-VMFirmware -VMName $vmName -FirstBootDevice $dvdDrive

ここまでは前回とほぼ同じだが、今回はKickstart用のOEMDRV VHDXを接続するようにする。

# Kickstart用のOEMDRV VHDXを接続する
Add-VMHardDiskDrive -VMName $vmName -Path $ksVhdxPath

接続した状態で、VMを起動させインストールを開始させる。インストールが完了し、Kickstartに書いたshutdownによりVMが停止するまで待つ。

インストール完了後、AlmaLinux ISOとKickstart用VHDXを外すようにして、VMを起動させる。

以下のコマンドをまとめて実行すると、インストール完了後にVMを起動し、vmconnect.exeで接続するところまで行える。VM接続画面が表示されれば、インストール後の起動まで完了したことが分かる。

# VMを起動してインストール開始
Start-VM -Name $vmName
# インストール完了後、KickstartのshutdownによりVMが停止するまで待つ
$timeoutMinutes = 60
$deadline = (Get-Date).AddMinutes($timeoutMinutes)
while ((Get-VM -Name $vmName).State -ne "Off") {
  if ((Get-Date) -gt $deadline) {
    throw "インストール完了待ちがタイムアウトしました。VMの画面を確認してください。"
  }
  Start-Sleep -Seconds 10
}
# インストールISOを外す
Get-VMDvdDrive -VMName $vmName |
  Set-VMDvdDrive -Path $null

# Kickstart用VHDXを外す
Get-VMHardDiskDrive -VMName $vmName |
  Where-Object { $_.Path -eq $ksVhdxPath } |
  Remove-VMHardDiskDrive

# 本体VHDXから起動するようにする
$osDisk = Get-VMHardDiskDrive -VMName $vmName |
  Where-Object { $_.Path -eq $vhdPath }

Set-VMFirmware -VMName $vmName -FirstBootDevice $osDisk

# インストール済みAlmaLinuxを起動する
Start-VM -Name $vmName

# 接続する
vmconnect.exe localhost $vmName

これでVMに接続し、ログインできればOK。

VM作成から接続までのスクリプト

前回と同様に、VM作成からOSインストール、接続までの手順をまとめたスクリプトを貼る。実行後はインストール完了まで待つだけなので便利だ。冒頭にも書いたとおり、管理者権限が必要なことに注意。ps1スクリプトとして使うなら、先頭に#Requires -RunAsAdministratorを書いた方がいいかもしれない。

$vmName = "AlmaLinux10-1-sandbox"
$vmPath = "$env:USERPROFILE\Hyper-V\$vmName"
$ksDir = "$vmPath\kickstart"
$ksFile = "$ksDir\ks.cfg"

New-Item -ItemType Directory -Force -Path $ksDir | Out-Null

$ksContent = @'
# GUIではなく、テキストモードでインストールする
text

# インストール完了後にシャットダウンする
# PowerShell側でVM停止を検知し、ISOとKickstart用VHDXを外すため
shutdown

# インストール元としてCD/DVDメディアを使う
cdrom

# 言語、キーボード、タイムゾーン
# キーボードを日本語配列にしたい場合は、keyboard --xlayouts='jp'にする
lang en_US.UTF-8
keyboard --xlayouts='us'
timezone Asia/Tokyo --utc

# DHCPでネットワークを有効化し、ホスト名を「almalinux.local」にする
network --bootproto=dhcp --device=link --activate --hostname=almalinux.local

# rootのパスワードを指定する
rootpw --plaintext example_root_password

# root以外のユーザを作成し、wheelグループに入れる
user --name=example_user --password=example_user_password --plaintext --groups=wheel

# SSHを許可し、FirewallとSELinuxは有効にする
firewall --enabled --service=ssh
selinux --enforcing

# ブートローダー設定
bootloader --location=mbr

# インストール対象は1つ目のディスクだけにする
ignoredisk --only-use=sda

# 既存パーティションがあれば削除し、自動パーティション作成する
zerombr
clearpart --all --initlabel --drives=sda
autopart --type=lvm

# SSHサーバを入れておく
%packages
@^minimal-environment
openssh-server
%end

# インストール後にSSHを自動起動し、日本語ロケールを設定する
%post
systemctl enable sshd
dnf install -y glibc-langpack-ja
echo 'LANG=ja_JP.UTF-8' > /etc/locale.conf
%end
'@
$utf8NoBom = New-Object System.Text.UTF8Encoding $false
[System.IO.File]::WriteAllText($ksFile, $ksContent, $utf8NoBom)

$ksVhdxPath = "$vmPath\kickstart-oemdrv.vhdx"

New-VHD -Path $ksVhdxPath -SizeBytes 64MB -Dynamic | Out-Null

$mountPath = "$vmPath\kickstart-mount"

New-Item -ItemType Directory -Force -Path $mountPath | Out-Null

$accessPathAdded = $false

try {
  $vhd = Mount-VHD -Path $ksVhdxPath -PassThru
  $disk = $vhd | Get-Disk
  Initialize-Disk -Number $disk.Number -PartitionStyle MBR

  $partition = New-Partition `
    -DiskNumber $disk.Number `
    -UseMaximumSize

  Set-Partition `
    -DiskNumber $disk.Number `
    -PartitionNumber $partition.PartitionNumber `
    -NoDefaultDriveLetter $true

  $partition = Get-Partition `
    -DiskNumber $disk.Number `
    -PartitionNumber $partition.PartitionNumber
  if ($partition.DriveLetter) {
    Remove-PartitionAccessPath `
      -DiskNumber $disk.Number `
      -PartitionNumber $partition.PartitionNumber `
      -AccessPath "$($partition.DriveLetter):\"
  }

  Format-Volume `
    -Partition $partition `
    -FileSystem FAT32 `
    -NewFileSystemLabel OEMDRV `
    -Confirm:$false | Out-Null

  Add-PartitionAccessPath `
    -DiskNumber $disk.Number `
    -PartitionNumber $partition.PartitionNumber `
    -AccessPath $mountPath
  $accessPathAdded = $true

  # KickstartファイルをVHDXのルート直下にks.cfgとしてコピーする
  Copy-Item `
    -LiteralPath $ksFile `
    -Destination "$mountPath\ks.cfg" `
    -Force
}
finally {
  # フォルダにマウントしている場合、マウントを解除する
  if ($accessPathAdded) {
    Remove-PartitionAccessPath `
      -DiskNumber $disk.Number `
      -PartitionNumber $partition.PartitionNumber `
      -AccessPath $mountPath `
      -ErrorAction SilentlyContinue
  }
  Dismount-VHD -Path $ksVhdxPath -ErrorAction SilentlyContinue
}

$vhdPath = "$vmPath\$vmName.vhdx"
$isoPath = "$env:USERPROFILE\Downloads\AlmaLinux-10.1-x86_64-minimal.iso"
$switchName = "Default Switch"
$dynamicMemoryEnabled = $true
$memoryStartupBytes = 4GB
$minimumBytes = 2GB
$maximumBytes = 8GB
$vhdSizeBytes = 40GB
$processorCount = 2
$generation = 2

New-VM `
  -Name $vmName `
  -Path $vmPath `
  -NewVHDPath $vhdPath `
  -SwitchName $switchName `
  -NewVHDSizeBytes $vhdSizeBytes `
  -Generation $generation

Set-VMProcessor -VMName $vmName -Count $processorCount

Set-VMMemory `
  -VMName $vmName `
  -DynamicMemoryEnabled $dynamicMemoryEnabled `
  -StartupBytes $memoryStartupBytes `
  -MinimumBytes $minimumBytes `
  -MaximumBytes $maximumBytes

Set-VM -Name $vmName -CheckpointType Disabled

Add-VMDvdDrive -VMName $vmName -Path $isoPath

Set-VMFirmware -VMName $vmName -EnableSecureBoot On -SecureBootTemplate MicrosoftUEFICertificateAuthority

$dvdDrive = Get-VMDvdDrive -VMName $vmName | Where-Object { $_.Path -eq $isoPath }

Set-VMFirmware -VMName $vmName -FirstBootDevice $dvdDrive

Add-VMHardDiskDrive -VMName $vmName -Path $ksVhdxPath

Start-VM -Name $vmName

$timeoutMinutes = 60
$deadline = (Get-Date).AddMinutes($timeoutMinutes)
while ((Get-VM -Name $vmName).State -ne "Off") {
  if ((Get-Date) -gt $deadline) {
    throw "インストール完了待ちがタイムアウトしました。VMの画面を確認してください。"
  }
  Start-Sleep -Seconds 10
}

Get-VMDvdDrive -VMName $vmName |
  Set-VMDvdDrive -Path $null

Get-VMHardDiskDrive -VMName $vmName |
  Where-Object { $_.Path -eq $ksVhdxPath } |
  Remove-VMHardDiskDrive

$osDisk = Get-VMHardDiskDrive -VMName $vmName |
  Where-Object { $_.Path -eq $vhdPath }

Set-VMFirmware -VMName $vmName -FirstBootDevice $osDisk
Start-VM -Name $vmName
vmconnect.exe localhost $vmName

VMの削除

以下コマンドでVMやその関連ファイル、Kickstart関連ファイルを削除できる。

# 作成したVMの名前
$vmName = "AlmaLinux10-1-sandbox"

# VM関連ファイルのパス
$vmPath = "$env:USERPROFILE\Hyper-V\$vmName"

# Kickstart関連ファイル・ディレクトリ
$ksVhdxPath = "$vmPath\kickstart-oemdrv.vhdx"

# VMを取得する
$vm = Get-VM -Name $vmName -ErrorAction SilentlyContinue

if ($null -ne $vm) {
  # VMを停止する
  if ($vm.State -ne 'Off') {
    Stop-VM -Name $vmName -TurnOff -Force

    while ((Get-VM -Name $vmName).State -ne 'Off') {
      Start-Sleep -Seconds 1
    }
  }

  # VMを削除する
  Remove-VM -Name $vmName -Force
}

# Kickstart用VHDXがマウントされたままならアンマウントする
Dismount-VHD -Path $ksVhdxPath -ErrorAction SilentlyContinue

# VM用ディレクトリごと削除する
if (Test-Path -LiteralPath $vmPath) {
  Remove-Item -LiteralPath $vmPath -Recurse -Force
}

おわりに

Red Hat系で/rootにあるanaconda-ks.cfgって何者なんだろうとこれまで思っていたが、今回でKickstart用のファイルだということが分かった。

Ubuntuではautoinstallというもので、Kickstartと同様のことが出来るようだ。いずれ、試してみたいと思う。