はじめに

以前、Hyper-VへのAlmaLinuxのインストールをKickstartで自動化する記事を書いた。

その記事の最後で、

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

と書いたので、今回は実際に試してみる。

Ubuntu Serverでは、SubiquityというインストーラーからAutoinstallを利用することで、ユーザー作成やディスク設定、SSHサーバのインストール等を自動化できる。

今回は、Hyper-VのVM作成からUbuntu Serverのインストールまでを自動化してみる。

実施環境

  • ホストOS:Windows 11
  • ゲストOS:Ubuntu Server 26.04 LTS
  • PowerShellバージョン:5.1

前提

この記事では、UbuntuのISOを加工するためにWSLを使用する。

WSLをインストールしていない場合は、事前にインストールしておく。

今回はUbuntu ServerのISOファイルをダウンロードし、以下に配置したものとする。

%USERPROFILE%\Downloads\ubuntu-26.04-live-server-amd64.iso

ISOは公式からダウンロードできる。

Autoinstallについて

Ubuntu Serverでは、Autoinstallを使ってOSインストールを自動化できる。

Autoinstallの設定はYAML形式で記述する。

設定をインストーラーへ渡す方法はいくつかあり、cloud-inituser-dataとして渡す方法のほか、インストールメディアのルートにautoinstall.yamlを配置する方法もある。

今回は後者の方法を使う。

ISO内を以下のような構成にする。

/
├─ autoinstall.yaml
├─ boot/
│  └─ grub/
│     └─ grub.cfg
├─ casper/
└─ ...

Autoinstallの設定ファイル作成

PowerShellからAutoinstall関連ファイルを置くディレクトリを作成する。

# VMの名前
$vmName = "Ubuntu26-04-sandbox"

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

# Autoinstall関連ファイルの保存先
$autoinstallDir = "$env:USERPROFILE\Hyper-V\autoinstall\Ubuntu26-04"

# autoinstall.yaml
$autoinstallYamlFile = "$autoinstallDir\autoinstall.yaml"

New-Item `
  -ItemType Directory `
  -Force `
  -Path $autoinstallDir | Out-Null

Ubuntuのidentity.passwordには平文ではなく、暗号化されたパスワードを指定する必要がある。

今回はWSL上のopensslを使って作成する。

PowerShellから以下を実行し、WSL上に、この後使用するxorrisoopensslをインストールしておく。

wsl bash -lc "sudo apt update && sudo apt install -y xorriso openssl"

前回と同様、ログインユーザーのパスワードをexample_user_passwordとする。

$passwordHash = (
  wsl bash -lc "openssl passwd -6 'example_user_password'"
).Trim()

生成したハッシュを使って、autoinstall.yamlを作成する。

$autoinstallYamlContent = @"
autoinstall:
  version: 1

  # 言語
  locale: en_US.UTF-8

  # キーボード
  keyboard:
    layout: us

  # タイムゾーン
  timezone: Asia/Tokyo

  # Ubuntu Serverのminimal構成を使用する
  source:
    id: ubuntu-server-minimal

  # ユーザー、ホスト名
  identity:
    hostname: ubuntu
    username: example_user
    password: '$passwordHash'

  # SSHサーバをインストールし、パスワード認証を許可する
  ssh:
    install-server: true
    allow-pw: true

  # LVMを使って自動的にパーティションを作成する
  storage:
    layout:
      name: lvm

  # インストール完了後に電源を切る
  shutdown: poweroff
"@

# BOMなしUTF-8で保存する
$utf8NoBom = New-Object System.Text.UTF8Encoding $false

[System.IO.File]::WriteAllText(
  $autoinstallYamlFile,
  $autoinstallYamlContent,
  $utf8NoBom
)

今回は以下の設定になる。

項目 設定
ホスト名 ubuntu
ユーザー名 example_user
パスワード example_user_password
キーボード US
タイムゾーン Asia/Tokyo
ディスク LVMで自動構成
SSH 有効
インストール完了後 電源OFF

cloud-inituser-dataとして渡すわけではないため、#cloud-configは付けていない。

これで、以下のファイルが作成される。

%USERPROFILE%\Hyper-V\autoinstall\Ubuntu26-04
└─ autoinstall.yaml

Autoinstall用ISOを作成する

次に、Ubuntu ServerのISOをAutoinstall用に加工する。

今回はxorrisoを使って元ISOのブート情報を引き継ぎながら、

  • grub.cfgautoinstallを追加する
  • ISO直下へautoinstall.yamlを追加する

という変更を加える。

変更するファイル自体は小さいが、最終的にはUbuntu Server ISOとほぼ同じサイズのISOを書き出すため、ISOの作成にはある程度時間がかかる。

そのため、作成したgrub.cfgautoinstall.yaml、Autoinstall用ISOは削除せず、VMを作り直す場合も使い回す。

%USERPROFILE%\Hyper-V\autoinstall\Ubuntu26-04
├─ grub.cfg
├─ autoinstall.yaml
└─ ubuntu-26.04-autoinstall.iso

設定を変更しない限り、Autoinstall用ISOを再作成する必要はない。

パスを設定する

ISOファイル等のパスを指定する。

# Ubuntu Serverの元ISO
$isoPath = "$env:USERPROFILE\Downloads\ubuntu-26.04-live-server-amd64.iso"

# 作成するAutoinstall用ISO
$autoinstallIsoPath = "$autoinstallDir\ubuntu-26.04-autoinstall.iso"

# ISOから取り出したgrub.cfgの保存先
$grubCfgPath = "$autoinstallDir\grub.cfg"

WSLからWindows上のファイルを参照できるように、Windows形式のパスをWSL形式(/mnt/c/...のような形式)へ変換する関数を作る。

function Convert-ToWslPath([string]$path) {
  $result = wsl.exe --exec wslpath -a -u $path

  return $result.Trim()
}

各ファイルのパスを変換する。

$isoPathWsl = Convert-ToWslPath $isoPath
$autoinstallIsoPathWsl = Convert-ToWslPath $autoinstallIsoPath
$autoinstallYamlFileWsl = Convert-ToWslPath $autoinstallYamlFile
$grubCfgPathWsl = Convert-ToWslPath $grubCfgPath

grub.cfgを取り出す

UbuntuのISOからgrub.cfgを取り出す。

$extractGrubCommand = @"
xorriso \
  -osirrox on \
  -overwrite on \
  -indev '$isoPathWsl' \
  -extract /boot/grub/grub.cfg '$grubCfgPathWsl'
"@

wsl bash -lc $extractGrubCommand

$grubCfgFile = Get-Item -LiteralPath $grubCfgPath

# grub.cfgの読み取り専用属性を解除する。
if ($grubCfgFile.IsReadOnly) {
  $grubCfgFile.IsReadOnly = $false
}

grub.cfgへautoinstallを追加する

取り出したgrub.cfgのUbuntu起動時のカーネルパラメータへ、

autoinstall

を追加する。

$grubContent = [System.IO.File]::ReadAllText($grubCfgPath)

$grubContent = $grubContent -replace `
  '(?m)^(\s*linux\s+/casper/vmlinuz.*?)\s+---\s*$', `
  '$1 autoinstall ---'

[System.IO.File]::WriteAllText(
  $grubCfgPath,
  $grubContent,
  $utf8NoBom
)

変更後のgrub.cfgを確認する。

Get-Content $grubCfgPath

以下のような行になっていればOK。

linux /casper/vmlinuz ... autoinstall ---

ISOを作成する

変更したgrub.cfgautoinstall.yamlを元のUbuntu ISOへ追加し、新しいISOを作る。

$buildIsoCommand = @"
xorriso \
  -indev '$isoPathWsl' \
  -outdev '$autoinstallIsoPathWsl' \
  -overwrite on \
  -map '$grubCfgPathWsl' /boot/grub/grub.cfg \
  -map '$autoinstallYamlFileWsl' /autoinstall.yaml \
  -boot_image any replay \
  -commit
"@

wsl bash -lc $buildIsoCommand

以下のファイルができていればOK。

%USERPROFILE%\Hyper-V\autoinstall\Ubuntu26-04\ubuntu-26.04-autoinstall.iso

当然だが、元のUbuntu公式ISOを変更しているため、作成したISOのSHA256はUbuntu公式の値とは一致しない。

元ISOのチェックサムを確認する場合は、加工前に行う。

作成したISOを確認する

VMを起動する前に、作成したISOへautoinstall.yamlが入っていることを確認する。

$checkAutoinstallCommand = @"
xorriso \
  -indev '$autoinstallIsoPathWsl' \
  -ls /autoinstall.yaml
"@

wsl bash -lc $checkAutoinstallCommand

autoinstall.yamlが表示されればOK。

さらに、最終的なISOのgrub.cfgも確認する。

$verifyGrubCfgPath = "$autoinstallDir\grub.final.cfg"
$verifyGrubCfgPathWsl = Convert-ToWslPath $verifyGrubCfgPath

$verifyGrubCommand = @"
xorriso \
  -osirrox on \
  -overwrite on \
  -indev '$autoinstallIsoPathWsl' \
  -extract /boot/grub/grub.cfg '$verifyGrubCfgPathWsl'
"@

wsl bash -lc $verifyGrubCommand

Get-Content $verifyGrubCfgPath

以下が入っていることを確認する。

linux /casper/vmlinuz ... autoinstall ---

Autoinstallを使ってVMを作成する

作成したISOを使ってHyper-VのVMを作る。

設定は以前のAlmaLinuxの記事とほぼ同じにした。

# Ubuntu VMのVHDX
$vhdPath = "$vmPath\$vmName.vhdx"

# 接続する仮想スイッチ
$switchName = "Default Switch"

# 動的メモリ
$dynamicMemoryEnabled = $true

# 起動時メモリ
$memoryStartupBytes = 4GB

# 最小メモリ
$minimumBytes = 2GB

# 最大メモリ
$maximumBytes = 8GB

# 仮想ディスク
$vhdSizeBytes = 40GB

# CPU
$processorCount = 2

# 第2世代VM
$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

検証用VMなので、今回はチェックポイントを無効にする。

Set-VM `
  -Name $vmName `
  -CheckpointType Disabled

作成したAutoinstall用ISOをDVDドライブへ接続する。

Add-VMDvdDrive `
  -VMName $vmName `
  -Path $autoinstallIsoPath

Ubuntuを第2世代Hyper-V VMで起動するため、Secure Bootを有効にする。

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

ISOから起動するようにする。

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

Set-VMFirmware `
  -VMName $vmName `
  -FirstBootDevice $dvdDrive

これで準備完了。

VMを起動する。

Start-VM -Name $vmName

あとは何も操作する必要はない。

ISO直下のautoinstall.yamlがSubiquityに読み込まれ、

  • ディスク設定
  • Ubuntuのインストール
  • ユーザー作成
  • SSHサーバのインストール

等が自動的に行われる。

autoinstall.yamlで、

shutdown: poweroff

を指定したので、インストール完了後にVMの電源が切れる。

VMが停止したら、インストール用ISOを外す。

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

本体VHDXから起動するよう変更する。

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

Set-VMFirmware `
  -VMName $vmName `
  -FirstBootDevice $osDisk

Ubuntuを起動する。

Start-VM -Name $vmName

Hyper-Vの接続画面も開いておく。

vmconnect.exe localhost $vmName

ログイン画面が表示されたら、

ユーザー名:example_user
パスワード:example_user_password

でログインする。

ログインできれば、自動インストール完了。

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

これまでの処理をまとめると以下になる。

grub.cfgautoinstall.yaml、Autoinstall用ISOは共通ディレクトリへ保存する。

すでにAutoinstall用ISOが存在する場合はISO作成処理をスキップし、既存のISOを使ってVMだけを作成する。

また、ISOを作り直す場合でも、既存のautoinstall.yamlgrub.cfgがあれば使い回す。

$vmName = "Ubuntu26-04-sandbox"
$vmPath = "$env:USERPROFILE\Hyper-V\$vmName"

# Autoinstall関連ファイルはVMとは別の共通ディレクトリへ保存する
$autoinstallDir = "$env:USERPROFILE\Hyper-V\autoinstall\Ubuntu26-04"
$autoinstallYamlFile = "$autoinstallDir\autoinstall.yaml"
$grubCfgPath = "$autoinstallDir\grub.cfg"

$isoPath = "$env:USERPROFILE\Downloads\ubuntu-26.04-live-server-amd64.iso"
$autoinstallIsoPath = "$autoinstallDir\ubuntu-26.04-autoinstall.iso"

New-Item `
  -ItemType Directory `
  -Force `
  -Path $autoinstallDir | Out-Null

# Autoinstall用ISOがすでに存在するか確認する
$autoinstallIsoExists = Test-Path -LiteralPath $autoinstallIsoPath

# 既存ISOがある場合
if ($autoinstallIsoExists) {
  Write-Host "既存のAutoinstall用ISOを使用します: $autoinstallIsoPath"
}

# Autoinstall用ISOがない場合だけ作成する
if (-not $autoinstallIsoExists) {

  # WSLに必要なパッケージを入れる
  wsl bash -lc "sudo apt update && sudo apt install -y xorriso openssl"

  $utf8NoBom = New-Object System.Text.UTF8Encoding $false

  # autoinstall.yamlがない場合だけ作成する
  if (-not (Test-Path -LiteralPath $autoinstallYamlFile)) {

    $passwordHash = (
      wsl bash -lc "openssl passwd -6 'example_user_password'"
    ).Trim()

    $autoinstallYamlContent = @"
autoinstall:
  version: 1
  locale: en_US.UTF-8

  keyboard:
    layout: us

  timezone: Asia/Tokyo

  source:
    id: ubuntu-server-minimal

  identity:
    hostname: ubuntu
    username: example_user
    password: '$passwordHash'

  ssh:
    install-server: true
    allow-pw: true

  storage:
    layout:
      name: lvm

  shutdown: poweroff
"@

    [System.IO.File]::WriteAllText(
      $autoinstallYamlFile,
      $autoinstallYamlContent,
      $utf8NoBom
    )
  }

  # WindowsパスをWSL用に変換する
  function Convert-ToWslPath([string]$path) {
    $result = wsl.exe --exec wslpath -a -u $path

    return $result.Trim()
  }

  $isoPathWsl = Convert-ToWslPath $isoPath
  $autoinstallIsoPathWsl = Convert-ToWslPath $autoinstallIsoPath
  $autoinstallYamlFileWsl = Convert-ToWslPath $autoinstallYamlFile
  $grubCfgPathWsl = Convert-ToWslPath $grubCfgPath

  # grub.cfgがない場合だけ元ISOから取り出す
  if (-not (Test-Path -LiteralPath $grubCfgPath)) {

    $extractGrubCommand = @"
xorriso \
  -osirrox on \
  -overwrite on \
  -indev '$isoPathWsl' \
  -extract /boot/grub/grub.cfg '$grubCfgPathWsl'
"@

    wsl bash -lc $extractGrubCommand
  }

  # grub.cfgの読み取り専用属性を解除する。
  $grubCfgFile = Get-Item -LiteralPath $grubCfgPath

  if ($grubCfgFile.IsReadOnly) {
    $grubCfgFile.IsReadOnly = $false
  }

  # grub.cfgにautoinstallが入っていない場合だけ追加する
  $grubContent = [System.IO.File]::ReadAllText($grubCfgPath)

  if (-not $grubContent.Contains("autoinstall ---")) {

    $grubContent = $grubContent -replace `
      '(?m)^(\s*linux\s+/casper/vmlinuz.*?)\s+---\s*$', `
      '$1 autoinstall ---'

    [System.IO.File]::WriteAllText(
      $grubCfgPath,
      $grubContent,
      $utf8NoBom
    )
  }

  # Autoinstall用ISOを作成する
  $buildIsoCommand = @"
xorriso \
  -indev '$isoPathWsl' \
  -outdev '$autoinstallIsoPathWsl' \
  -overwrite on \
  -map '$grubCfgPathWsl' /boot/grub/grub.cfg \
  -map '$autoinstallYamlFileWsl' /autoinstall.yaml \
  -boot_image any replay \
  -commit
"@

  wsl bash -lc $buildIsoCommand

  Write-Host "Autoinstall用ISOを作成しました: $autoinstallIsoPath"
}

# Hyper-V VMを作成する
$vhdPath = "$vmPath\$vmName.vhdx"

$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

# Autoinstall用ISOをDVDドライブへ接続する
Add-VMDvdDrive `
  -VMName $vmName `
  -Path $autoinstallIsoPath

# Secure Boot
Set-VMFirmware `
  -VMName $vmName `
  -EnableSecureBoot On `
  -SecureBootTemplate MicrosoftUEFICertificateAuthority

# ISOから起動する
$dvdDrive = Get-VMDvdDrive -VMName $vmName |
  Where-Object { $_.Path -eq $autoinstallIsoPath }

Set-VMFirmware `
  -VMName $vmName `
  -FirstBootDevice $dvdDrive

# インストール開始
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
}

# インストールISOを外す
Get-VMDvdDrive -VMName $vmName |
  Set-VMDvdDrive -Path $null

# OSディスクから起動するよう変更する
$osDisk = Get-VMHardDiskDrive -VMName $vmName |
  Where-Object { $_.Path -eq $vhdPath }

Set-VMFirmware `
  -VMName $vmName `
  -FirstBootDevice $osDisk

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

# Hyper-V接続画面を開く
vmconnect.exe localhost $vmName

スクリプトを初めて実行したときだけAutoinstall用ISOが作成される。

2回目以降は既存のISOを使用するため、毎回数GBのISOを書き出す必要はない。

autoinstall.yamlの内容を変更した場合は、変更後の内容をISOへ反映するため、ubuntu-26.04-autoinstall.isoを削除してからスクリプトを再実行する。

VMの削除

作成したVMを削除する場合は以下。

# 作成したVMの名前
$vmName = "Ubuntu26-04-sandbox"

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

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

# autoinstall関連ファイル以外のファイルも削除する
if (Test-Path -LiteralPath $vmPath) {
  Remove-Item `
    -LiteralPath $vmPath `
    -Recurse `
    -Force
}

これでVMとVHDX等のVM固有ファイルだけを削除する。

Autoinstall関連ファイルは、

%USERPROFILE%\Hyper-V\autoinstall\Ubuntu26-04

へ別管理しているため、この削除処理では削除しない。

次にVMを作成するときも、同じgrub.cfgautoinstall.yaml、Autoinstall用ISOをそのまま使える。

おわりに

以前の記事ではAlmaLinuxをKickstartで自動インストールしたが、UbuntuでもAutoinstallを使うことで同じようなことができた。

今回の方法では、

  • autoinstall.yamlを作成する
  • GRUBへautoinstallを追加する
  • autoinstall.yamlを組み込んだAutoinstall用ISOを作成する

という手順で自動インストールできるようにした。

当初はcloud-initのNoCloudを使い、user-datameta-dataをISOへ入れる方法も試したが、ISO直下へautoinstall.yamlを配置する方法の方が構成を単純にできた。

また、Autoinstall用ISOの作成には時間がかかるため、grub.cfgautoinstall.yaml、Autoinstall用ISOはVMとは別の場所へ保存し、VMを削除しても残して使い回すようにした。

ちなみに、同様の手順で24.04も自動インストールできた。