宣言的・冪等な Nested Hyper-V ラボ構築基盤。
既存の Hyper-V サーバー 1 台さえあれば、bootstrap.ps1 ひとつで制御 VM (Ansible 内蔵) を
自己構築し、YAML 宣言から Nested ホスト (L1) とその中の VM 群 (L2: Windows / Linux /
Active Directory ドメイン / クラスタ) を、どこでも同じ形で再現します。
設計の全体像は
plan.md、宣言設定の文法はschema.md、 作業メモ・ハマりどころはCLAUDE.mdを参照。
- 前提は「Hyper-V サーバーがあること」だけ。 制御環境・ツールはすべて自己ブートストラップ (制御 VM、Ansible、qemu-img、cloud-init シード、golden イメージは DISM 標準ツールで生成。 Packer/ADK/oscdimg などの外部依存を持たない)。
- どの環境でも決定論的に同じ環境になる。 版固定 (
assets/images.yml) + SHA256 検証 + ベンダリング + 単一の宣言ファイル。再実行は冪等収束 (2 回流して no-change が受け入れ条件)。 - 誰でも使い回せる。 単一エントリ
bootstrap.ps1、ハードコードされたパス/名前なし、 データはリポジトリ配下に自己完結 (data/)、本ドキュメント同梱。
L0 物理 Hyper-V ホスト ── あなたが用意する唯一の前提
│
├─ 制御 VM (Ubuntu + Ansible) CtrlNAT 10.20.0.10 ← bootstrap が自己構築
│
└─ L1: Nested Hyper-V ホスト VM CtrlNAT 10.20.0.20
│ (静的メモリ / ExposeVirtualizationExtensions / MACスプーフィング)
│ ラボストア L: ← golden / L2 VM / cloud-init シードを集約 (大容量・差分の置き場)
│
└─ LabNAT 10.10.0.0/24 (L1内NAT自己完結 — L2 は L1 の中で閉じる)
├─ L2: Windows Server 2025 (golden の差分ディスクから一瞬で作成)
├─ L2: Ubuntu 24.04 (cloud image の差分 + cloud-init NoCloud シード)
├─ L2: dc01 (AD フォレスト) ← 新規フォレストに昇格
└─ L2: mem01 … ← ドメイン参加
「Ansible 一本」ではなく、3 つの道具を使い分ける。分担の境界は 「対象に IP + WinRM がもう在るか」: それが整うまで(と L0 操作)は PowerShell、整った後の“内側の定常構成”だけ Ansible が宣言的に仕上げる。
| 層・操作 | 実行主体 | 接続方式 |
|---|---|---|
| L0 操作 (NAT / L1作成 / 制御VM / golden配送 / ラボストア / 削除) | ホスト PowerShell (Hyper-V cmdlet) | ローカル |
| “ネットワーク前”ブートストラップ (L1/L2 の 静的IP・WinRM有効化・改名・日本語キーボード・RDP / AD 昇格・参加) | ホスト PowerShell Direct | (二段) PowerShell Direct (VMBus, L0→L1→L2) |
IP+WinRM 後の L1/L2 内部 (Hyper-V役割+LabNAT=setup_l1 / L2作成=create_l2 / クラスタ+S2D=create_cluster) |
Ansible | 制御VM → WinRM → L1/L2 |
なぜ混在するか: Ansible は IP+WinRM が前提なので、それが無い“作りたて/壊れた”段階や L0 の操作には 使えない。そこを PowerShell Direct (VMBus) が埋める(物理ネットワーク非依存で、再起動をまたぐ再接続も 確実 — 原則①)。AD 昇格は DC への二段ホップを伴うため PowerShell Direct。L2 は LabNAT 内に隔離され 制御VMから直接届かないため、いずれも L1 を踏み台にする (Windows=二段 PS Direct / Linux=L1 から SSH)。
構築後、どの VM にどう入るか(SSH / WinRM / Hyper-V マネージャー / PowerShell Direct の 使い分け、接続マトリクス、トポロジ図)は
docs/access-guide.mdを参照。bootstrap.ps1完了時にも実環境の値で接続サマリ (Write-ConnectionInfo) が表示される。
- 実行は PowerShell 7 (
pwsh) を推奨。 本リポジトリの.ps1は UTF-8 (BOM なし) + 日本語コメント/文字列を 含む。Windows PowerShell 5.1 は BOM なしスクリプトを ANSI コードページ (日本語環境=cp932) として読むため、 日本語が化けて構文エラーで落ちることがある (例:teardown.ps1)。pwsh 7 は.ps1を既定で UTF-8 として 読むので、そのまま正しく動く。ホストに pwsh が無ければwinget install Microsoft.PowerShell等で導入する。 (※ 全.ps1を UTF-8 with BOM で保存し直せば 5.1 でも動くが、本プロジェクトは pwsh 7 前提で統一する。 詳細はKB/0017。) - Windows Server / Windows 11 等で Hyper-V 役割が有効であること (これだけ)。
- Python (pyyaml + jsonschema) … 設定の検証/解決に使用。
- イメージ(Windows Server 2025 評価版 ISO / Ubuntu cloud image)は すべて自動ダウンロード。
Windows ISO はフォーム登録なしの固定直リンクから取得するため、利用者の手作業は不要。
別言語/別版にしたい場合のみ
assets/images.ymlのiso_urlを差し替える。
PowerShell 7 (
pwsh) で実行すること (上記「前提」参照)。下記は pwsh セッション内、またはpwsh -File .\bootstrap.ps1 ...の形で。SSH 越しに叩く場合もpwsh -NoProfile -File ...を使う。
# まず DryRun で構築プランと必要イメージだけ確認 (VM は作らない)
.\bootstrap.ps1 -L1 l1\standard-host.yml -L2 l2\minimal-windows.yml -DryRun
# 本番実行 (制御VM自己構築 → L1 → L2 → (あれば)AD まで一括・冪等)
.\bootstrap.ps1 -L1 l1\standard-host.yml -L2 l2\ad-forest.ymlbootstrap.ps1 の流れ:
- プリフライト (Hyper-V / Python / 設定ファイル)
- 検証 + 解決 (
tools/resolve.py→build/resolved.json) - イメージ整備 (Ubuntu 自動取得 / Windows golden を DISM で生成)
- L0→L1 プロビジョニング → ホスト WinRM → 制御 VM 構築 → ラボストア増設 → golden 配送
- L1 内 Hyper-V+LabNAT (Ansible) → L2 作成 (Ansible) → AD 構築 (PowerShell Direct)
再実行すれば全工程が冪等に収束します (no-change が受け入れ条件)。 完了時にフェーズ別の構築時間と合計を表示します。
宣言ファイルの cpu / memory_gb(L1 は l1.cpu / l1.memory_gb)を書き換えて bootstrap.ps1 を再実行すれば、
既存 VM もその値へ冪等に収束します。静的メモリ・CPU 数の変更は Hyper-V 仕様で VM オフ必須のため、
ドリフトのある VM だけ一旦停止→適用→再起動されます(他は no-change)。詳細は KB/0018。
# 例: l2/ad-forest.yml の memory_gb を 4 -> 8 に編集してから
.\bootstrap.ps1 -L1 l1\standard-host.yml -L2 l2\ad-forest.ymlL2 宣言に接続先 azure_arc を 1 か所書き、登録したい VM に arc: true を付けるだけで、
Connected Machine agent の導入から azcmagent connect までを冪等に行う。
インバウンド開放も VPN も踏み台も要らない(通信はアウトバウンド 443 のみ)。
# 1) オンボード用サービスプリンシパルを作る (初回だけ。build/arc-cred.json が出力される)
.\scripts\New-ArcOnboarding.ps1 -SubscriptionId <sub> -ResourceGroup rg-nestlab-arc -Location japaneast
# 2) 宣言に沿って構築 -> Arc 登録まで一括
.\bootstrap.ps1 -L1 l1\standard-host.yml -L2 l2\arc-demo.yml# l2/arc-demo.yml (抜粋) — シークレットは書かない
azure_arc:
resource_group: rg-nestlab-arc
location: japaneast
defaults:
arc: true同梱の l2/arc-demo.yml は Windows Server 2025 と Ubuntu 24.04 を 1 台ずつ作り、両方を Arc に
登録する。登録後は Azure 側から Run Command でゲスト内のコマンドを実行できる
(実行には Azure Connected Machine Resource Administrator 以上が必要。オンボード用 SP は
最小権限のため含まない)。詳しい文法は schema.md の「Azure Arc への登録」。
L2 VM の宣言に features を書くと、その Windows 機能を Ansible (制御VM→WinRM, L1ルータ経由) で冪等に導入します。
L2 のゲスト内構成は AD を除き Ansible が本線(クラスタ構築 create_cluster.yml と同じ経路)。
# l2/*.yml の defaults / group / vm に書ける
groups:
- name: members
name_prefix: mem
count: 1
ip_from: 10.10.0.21
features: [Web-Server, Web-Mgmt-Console, Web-Windows-Auth] # ← IIS 一式bootstrap.ps1 実行時、AD 参加の後に configure_l2.yml(role l2_config → ansible.windows.win_feature)が走る。
L0、L1、全Windows L2には、configure_windows_baseline.yml が最新stable PowerShellを
winget install --id Microsoft.PowerShell --source winget で冪等導入する。個別VMの
features / applications 宣言は不要で、今後追加するWindows VMも自動的に対象になる。
同じ場所へ applications: [claude_code, microsoft_word] を宣言すると、AD参加後に対象VMだけへClaude CodeとMicrosoft 365 Apps版Wordを冪等導入できる。Wordのライセンス認証とClaude Codeのアカウント認証は、対象ユーザーがRDPログオン後に行う。
features は冪等(既に入っていれば no-change)。Web-Server 以外の任意の Windows 機能名を並べられる。
ドメイン参加メンバーへの接続は Kerberos(FQDN + ドメイン管理者 UPN)で行う。制御 VM は
非ドメインの Linux なので、メンバーへ IP+NTLM では拒否される(0x8009030e)。configure_l2.yml
の前に Ensure-ControlKerberos.ps1 が制御 VM に krb5.conf/hosts/pywinrm[kerberos] を用意し、
動的インベントリが該当 L2 を FQDN+Kerberos へ切り替える(クラスタノードは従来どおり / 詳細 KB/0019)。
# L1 + 中の L2 すべて + 制御 VM を削除 (確認あり)。L2 は L1 のディスクごと消える
.\teardown.ps1
# 確認なしで削除 / CtrlNAT スイッチや build 成果物まで消す完全クリーン
.\teardown.ps1 -Force
.\teardown.ps1 -IncludeSwitch -IncludeBuild -Force削除後に bootstrap.ps1 を実行すれば、まっさらから再構築できます。
完了すると、建った環境への接続先・資格情報・接続例が一覧表示されます
(Write-ConnectionInfo)。各 VM への入り方の詳細・図解は
docs/access-guide.md を参照。
L1 (ホスト土台) と L2 (中身) を別ファイルにし、L1 は使い回します。スキーマ詳細は
schema.md を参照。パターン C (ハイブリッド: 既定 + count/ip_from の糖衣 +
overrides エスケープハッチ)。
| ファイル | 内容 | 状態 |
|---|---|---|
l1/standard-host.yml |
標準 Nested ホスト (8vCPU/32GB, LabNAT 10.10.0.0/24) | ✅ |
l2/minimal-windows.yml |
Windows Server 2025 を 1 台 | ✅ 実機検証 |
l2/minimal-linux.yml |
Ubuntu 24.04 を 1 台 (cloud-init) | ✅ 実機検証 |
l2/ad-forest.yml |
AD フォレスト dc01 + メンバ mem01 | ✅ 実機検証 |
l2/multi-lang.yml |
ゲスト言語選択のデモ (en / ja の Win + ja の Linux) | ✅ resolve/DryRun |
l2/fileserver-s2d.yml |
AD + 2ノード ファイルサーバクラスタ + S2D | 🚧 ロール足場 |
最小の例 (l2/minimal-windows.yml):
defaults: { cpu: 2, memory_gb: 4, os: windows_server_2025 }
groups:
- { name: win, name_prefix: win, count: 1, ip_from: 10.10.0.51 }L2 ごとに language: を指定するだけで、その言語の ISO を自動 DL し、その言語の golden を
作って起動する (defaults / group / vm / overrides で継承・上書き可)。
groups:
- { name: win-ja, count: 1, ip_from: 10.10.0.62, os: windows_server_2025, language: ja-jp }
- { name: lin-ja, count: 1, ip_from: 10.10.0.63, os: ubuntu_2404, language: ja-jp }- Windows:
languageの ISO を取得し言語別 golden (win2025-golden-<lang>.vhdx) を生成。 - Linux: 単一 cloud image を使い cloud-init で
locale(例ja_JP.UTF-8)を設定。 - 利用可能言語は
assets/images.ymlのwindows_languages.catalog(en-us / ja-jp / de-de / fr-fr / es-es / it-it / ko-kr / zh-cn / pt-br / ru-ru。LCID追加で拡張可)。 - L1(ホスト/1段目)は安定性のため常に en-us 固定(多言語化による不具合・複雑化を避けるため)。
将来 GUI を付ける際は、この
language値をチェックボックス/ドロップダウンで選ぶ形にできる。
| パス | 役割 |
|---|---|
bootstrap.ps1 |
単一エントリポイント |
tools/resolve.py |
L1+L2 宣言の検証 (JSON Schema + 意味検証) と確定モデルへの展開 |
schema/*.schema.json |
L1/L2 の JSON Schema |
scripts/Build-WindowsGoldenDism.ps1 |
ISO から golden VHDX を DISM 生成 (oscdimg 不要) |
scripts/Get-UbuntuImage.ps1 |
Ubuntu cloud image を版固定取得 → VHDX 変換 |
scripts/Invoke-HostProvision.ps1 |
L0 上に L1 VM を冪等作成 |
scripts/Add-L1LabStore.ps1 |
L1 に大容量ラボストア(L:)を増設・初期化 |
scripts/Copy-GoldenToL1.ps1 |
golden/ベースを L1 へ Copy-VMFile 配送 |
scripts/Publish-L2Seeds.ps1 |
Linux L2 の cloud-init シードを生成・配送 |
scripts/Initialize-AdForest.ps1 |
L2 上に AD フォレスト構築 + ドメイン参加 |
control-node/Ensure-ControlNode.ps1 |
Ansible 内蔵 制御 VM を構築 |
control-node/Invoke-Ansible.ps1 |
制御 VM へ同期し playbook 実行 |
ansible/ |
動的インベントリ + ロール (nested_host / l1_network / l2_vm / ad / cluster_s2d / azure_local) |
python -m pytest tests/ -q # resolver / スキーマのユニットテスト
python tools/resolve.py --l1 l1/standard-host.yml --l2 l2/ad-forest.yml --validate-only- L2 OS ディスクは差分(ディファレンシング)ディスクで golden/cloud image から作成。 一瞬で済み容量も最小 (Windows L2 は初期 ~300MB)。
- golden/L2 は L1 の OS ディスクではなくラボストア(L:) に置く (L1 OS は宣言値へ拡張するが、用途を分離するため)。
ansible.windows.win_powershellの引数は文字列で渡るため、数値は必ず[int]等で型付け ("4"*1GBが文字列反復になり OutOfMemoryException になる)。- 統合コンポーネント名はロケール依存のため ID で特定 (日本語ホスト対応)。
- group_vars は動的インベントリ隣接 (
ansible/inventory/group_vars/) に置く。
- ✅ スキーマ / resolver / 検証 / DryRun プラン (pytest)
- ✅ L0→L1 冪等プロビジョニング (nested / 静的メモリ / MAC spoof)
- ✅ イメージ整備 (Windows=ISO直リンク自動DL→DISM golden / Ubuntu=固定URL自動DL+変換)
- ✅ 制御ノード自動構築 + 本線疎通 (制御VM Ansible → WinRM → Hyper-V)
- ✅ L2: Windows Server 2025 (差分ディスク・冪等・実機検証)
- ✅ L2: Ubuntu 24.04 (cloud-init で hostname/静的IP/SSH・実機検証)
- ✅ L2: AD フォレスト + ドメイン参加 (L1踏み台 PowerShell Direct・実機検証)
- ✅ bootstrap.ps1 一発再現 (L1→L2→AD を一括・冪等)
- 🚧 クラスタ + S2D (ロール足場) / Azure Local (別管理ロールで OSS ラップ) / GUI
進捗の詳細は plan.md を参照。