ネットワークエンジニアのITブログ

長らくネットワークで仕事をしてきましたが、ここ数年クラウドとサーバー系に触れる機会が増えて、クラウドのネットワークというのが自分の性分にはあっているようです。最近のお気に入りはNSXALBとGoogle Cloud。

【VCF 9.0構築】ついに完成!VCFインストーラーによるBring-upとSDDC環境の全貌(第6回)

前回は、Nested ESXiのOSインストールと、SSL証明書の再生成を含む初期設定を完了させました。これで、VCF 9.0をデプロイするための「物理・仮想インフラ」の準備がすべて整いました。

今回はいよいよ、VCF構築のハイライトである「Bring-up(自動展開)」を実行します。通常はVCFインストーラーのGUIから設定を進めていくため、その手順を追いながら、デプロイ後のJSONファイルの活用術についても解説します。

構成図

論理構成

今回構築するVCF 9.0環境の論理アーキテクチャは以下の通りとなります。

物理構成

VCFを支える物理ホストの構成は以下の通りです。

VCFインストーラーによる構築手順

VCFインストーラーは、VCFの管理ドメイン(Management Domain)を自動展開するためだけに存在する、期間限定の仮想アプライアンスです。
Bring-upが完了した後はSDDC Managerにその役割を譲るため、最終的には削除してしまって構いません。
それでは、VCFインストーラーの構築から始めていきます。

VCFインストーラーの構築

ソフトウェアのダウンロード

VCFの構築には、Broadcomのサポートポータルから必要なソフトウェア(OVAファイルとパラメータシート)をダウンロードしておく必要があります。

Broadcom Support Portalからの入手

Broadcom Support Portal(Home - Support Portal - Broadcom support portal)にログインし、「My Downloads」から「VMware Cloud Foundation」→「VMware Cloud Foundation 9」から最新版の「9.0.2.0」をクリックします。

「VMware Cloud Foundation Installer」の右端にある「View Group」をクリックします。

「SDDC Manager Appliance」の右端にダウンロードボタンをクリックし、ファイル(VCF-SDDC-Manager-Appliance-9.0.2.0.25151285.ova)をダウンロードします。

VCFインストーラーの構築

VCFインストーラーの新規仮想マシン作成

esx03にログインし、「新規仮想マシンの作成/登録」をクリックし、「OVFファイルまたはOVAファイルから登録マシンをデプロイ」を選択して「次へ」を選択します。

仮想マシンの名前は「Cloud Builder」を省略して、cbとします。
また、先ほどダウンロードしたOVAファイルをドラッグアンドドロップします。

ストレージの選択では、ローカルストレージの「ds-sata-03」を選択して「次へ」を選択します。

使用許諾誓約書に同意して「次へ」を選択します。

ネットワークのマッピングは192.168.10.0/24のVLAN10である「PG-infra-Mgmt」、シンプロビジョニングを選択します。

その他の設定は以下を設定します。

項目 設定値
Host Name cb.vcf.local
NTP Servers 192.168.10.11
Network 1 IP Address 192.168.10.12
Network 1 Subnet Mask 255.255.255.0
Network Default Gateway 192.168.10.1
DNS Domain Name vcf.local
Domain Search Path vcf.local
Domain Name Servers 192.168.10.11

設定の確認で、内容を確認し「完了」をクリックします。
なお、注意書きがあるように、デプロイが完了するまでは、ブラウザを更新しないで待ちましょう。

デプロイが完了したかは、別タブで「https://cb.vcf.local」にアクセス、ログイン画面が表示されるまで待ちます。
これで、VCFインストーラーの構築は完了となります。

VCFインストーラーの事前設定

VCFインストーラーへのログイン

ログイン画面が表示されたら、以下のユーザー名でログインします。
ユーザー名:admin@local
パスワード:構築時のlocal userで設定したパスワード

ログインすると以下のような画面が表示されます。
画面左側に「バイナリのダウンロード」、画面右側に「デプロイ」が表示されています。
まず最初に「バイナリのダウンロード」の設定を行っていきます。

support.broadcom.comでトークンを生成

バイナリのダウンロードでは、support.broadcom.comに接続してバイナリのダウンロードを行っていきますが、それにはダウンロード用のトークンを事前に生成しておく必要があります。
以下のURLからsupport.broadcom.comの「VCP Certification Production Licenses」に接続し、画面右上の「Generate Token」をクリックします。
Home - Support Portal - Broadcom support portal - Support Portal

画面左上の「Generate Token」をクリックします。

Descriptionを入力する画面が表示されるので、「vcf.local Token」と入力して「Submit」をクリックします。

Generate Download Token画面に戻るとトークンが生成されているので、控えておきます。

デポの設定

VCFインストーラーのTop画面に戻り、画面中央左にある「デポの設定とバイナリ管理」をクリックします。

オンラインデポとオフラインデポが表示されますが、今回はトークンを利用してオンラインでデポに接続するので、「オンライン デポへの接続」の「構成」をクリックします。

トークンの入力画面が表示されるので、先ほど控えておいたトークンを入力して「認証」をクリックします。

認証が成功すると、デポ設定の画面で「デポ接続がアクティブ」と表示され、バイナリ管理に各バージョンの一覧と、バージョンごとの各コンポーネントが表示されます。

画面下のほうで、製品とバージョンのリストが表示されるのでそれぞれ以下を選択します。
製品:VMware Cloud Foundation
バージョン:9.0.2.0

選択すると、対象バージョン製品が表示されるので、ダウンロードしたい製品にチェックを入れて、「ダウンロード」ボタンをクリックします。
ここでは、「VMware Cloud Foundation Operations」をダウンロードする手順を記載しています。


正しくダウンロードできたら、残りの製品もダウンロードします。
なお、VCF9の環境を最小限のリソースで構築するために、今回「VMware Cloud Foundation Automation」の導入は見送ります。

デプロイウィザードによる準備

事前準備が完了したので、いよいよここからデプロイウィザードによるデプロイを行っていきます。
メニューに沿って順番に設定していきますので、まずは、「デプロイウィザード」をクリックします。


1. 既存のコンポーネント

ここでは、既存のコンポーネントはないので、チェックせずに「次へ」をクリックします。

2. Management Network(管理ネットワーク設定)

デプロイに関する以下の情報を入力します。

項目 設定値
バージョン 9.0.2.0
VCFインスタンス名 homelab-vcf
管理ドメイン名 mgmt-domain
デプロイモデル シンプル(単一ノード)
DNSドメイン名 vcf.local
DNSサーバ 192.168.10.11
NTPサーバ 192.168.10.11
パスワードの作成 無効


3. VCF Operations

VCF Operationsに関する以下の情報を入力します。

項目 設定値
Operations Applianceのサイズ 極小規模
OperationsのプライマリFQDN vrops.vcf.local
フリート管理アプライアンスのアプライアンスのFQDN fleet.vcf.local
VCF Operationsと同じパスワードを使用 有効
Operations CollectorアプライアンスのアプライアンスのFQDN collector.vcf.local
VCF Operationsと同じパスワードを使用 有効


4. VCF Automation

VCF Automationに関する以下の情報を入力します。

項目 設定値
後でVCF Automationインスタンスを接続します 有効


5. vCenter Server

vCenter Serverに関する以下の情報を入力します。

項目 設定値
アプライアンスのFQDN vc.vcf.local
アプライアンスのサイズ 極小
アプライアンスのストレージサイズ デフォルト
データセンタ名 mgmt-domain-dc01
クラスタ名 mgmt-domain-cl01
SSOドメイン名 vcf.local
6. NSX Manager

NSX Managerに関する以下の情報を入力します。

項目 設定値
アプライアンスのサイズ 中規模
仮想IPアドレス(VIP)のFQDN nsx.vcf.local
アプライアンスのFQDN nsx-manager01.vcf.local


7. ストレージ

ストレージに関する以下の情報を入力します。

項目 設定値
ストレージタイプの選択 vSAN
vSANアーキテクチャ vSAN OSA
vSANデータストア名 mgmt-domain-cl01-ds-vsan01
許容する障害の数 1
vSANの重複排除と圧縮の有効化 無効


8. ホスト

ホストの追加では、esx-n01.vcf.local~esx-n04.vcf.localまでのFQDNを登録し、フィンガープリントの確認を行います。

9. ネットワーク

ネットワークに関する以下の情報を入力します。

項目 設定値
ESX管理ネットワーク VLAN ID 10
MTU 1500
CIDR表記 192.168.10.0/24
ゲートウェイ 192.168.10.1
ESX管理ネットワークから同じ入力を使用 有効
vMotionネットワーク VLAN ID 11
MTU 9000
CIDR表記 192.168.11.0/24
ゲートウェイ 192.168.11.1
IPアドレス範囲 192.168.11.21~192.168.11.24
vSANネットワーク VLAN ID 12
MTU 9000
CIDR表記 192.168.12.0/24
ゲートウェイ 192.168.12.1
IPアドレス範囲 192.168.12.21~192.168.12.24


10. Distributed Switch

選択したプロファイルでカスタムスイッチ構成を選択し、一番下にある「ネットワークトラフィック:NSX」のトランスポートゾーンを以下の内容で設定します。

項目 設定値
トランスポートゾーン名 VCF-Created-Overlay-Zone
VLAN ID 13
IPアドレス割り当て(TEP) IPアドレスプール
プール名 nsx-tep-pool
CIDR 192.168.13.0/24
IPアドレス範囲の開始 192.168.13.21
終了 192.168.13.30
ゲートウェイ 192.168.13.1


11. SDDC Manager

SDDC Managerに関する以下の情報を入力します。

項目 設定値
アプライアンスのFQDN sddc.vcf.local


12. 確認

この画面で、今までの設定内容を確認することができます。
また、画面右上に「JSON仕様のダウンロード」をクリックすると、今後同様の設定をする際に、このJSONファイルを編集して読み込むことで、設定を一から設定する必要がなくなります。

13. 検証とデプロイ

ここでは、検証の実行をすることで、事前に問題がないかをチェックしてくれます。
ここでは、「vSAN Disks Availability(AllFlash)」の項目の警告が出ている状態になっていればOKです。

警告やエラーが出た場合は、指示に従って環境を修正することで、検証の再実行で解決することもあります。
私がここで発生したエラーをいくつか紹介しますので、もし問題が発生した場合の参考としてください。

  • DNSの逆引きの設定が漏れていた
  • NestedホストのFQDNを変更した際に証明書が古いままで整合性チェックがNGとなっていた
  • 時刻同期が失敗していた、または一部設定が漏れていた
  • パスワードポリシーが15文字以上を満たしていなかった
  • vSAN、vMotionなどのVLANのゲートウェイの設定が漏れていた

vSAN以外の警告がないことを確認したら、画面右上の「すべての警告を確認」をクリックすると、画面右下の「デプロイ」が有効化されるので、クリックします。

デプロイがスタートし、ここから2~3時間ぐらい完了までかかるので気長に待ちましょう。

途中、83%まで進んで、最後の「Operationsアプライアンスのデプロイと構成」のところまで行くと、進捗とステータスが最初に戻りますが、失敗したと思わずにそのまま待ってください。
数分待つとまた進んで完了します。
これは何度実施しても同じ事象が発生していましたが、デプロイ自体は問題なく進みます。

デプロイの完了確認と各種コンポーネントへのログイン

デプロイが完了すると、完了画面が表示されるとともに「JSON仕様のダウンロードボタン」と「パスワードの確認」ボタンが表示されるので、それぞれダウンロードと確認をしておきましょう。
また、VCF Operationsのログイン画面のリンクが表示されるので、クリックします。
また、VCFインストーラーのトップ画面でもデプロイが正常に完了したことを確認できます。

Operationsのログインでは、ログイン方法を「ローカルアカウント」、ユーザー名を「admin」でログインします。

また、vCenter、NSX Manager、SDDC Managerにもログインできることを確認できました。


まずは、VCF管理ドメインの構築は完了となります。
今回はいったんここまでとなります。

終わりに

リソースをなるべく抑えるように各コンポーネントを構築しましたが、今のクラスタの使用状況を確認したところ、安定していることもあって、CPUには余裕があり、メモリとストレージが半分ぐらいの使用状況でした。
この後、NSXでオーバーレイネットワークを作成していくにあたり、NSX Edge2台を追加で構築する必要がありますが、今のリソースの使用状況であれば行けそうです。
当初、Nested環境用の物理ホスト1台で実施して構築が完了するもリソースが不足してしまい、vCenterにログインできなくなってしまったことを考えると、最低でも物理ホストは2台必要というのがわかりました。
今後、Avi Load Balancerなどの検証をしていくとなると、管理ドメインも結構ギリギリになってきそうです。

また、SDDC ManagerやNSX Managerにログインすると、基本はVCF Operationsを使用することを促すようなメッセージが表示されていました。
いままでOperationsをあまり触ることがなかったので、この辺もいろいろ確認していきたいと思います。

【Tips】再構築に備えたJSONの保存と「sslThumbprint」の罠

構築完了後(またはValidation成功後)、設定内容を定義したJSONファイルをダウンロードできます。
これを保存しておけば、次回以降の再構築が劇的に楽になりますが、一点だけ非常に重要なポイントがあります。

sslThumbprint情報を削除すること

JSONファイルを再利用する場合、各ホストのセクションにある「sslThumbprint」の項目を削除してください。

ESXiを再構築するたびに、この情報(Thumbprint)は更新されます。JSONに古い情報が残っていると、Validationで不整合が発生し、デプロイが進まなくなります。
「設定は合っているはずなのに検証が通らない」という現象の多くは、この情報の不一致が原因です。再構築を想定している方は、ぜひこの手順を覚えておいてください。

【VCF 9.0構築】Nested ESXiの初期設定と証明書再生成(第5回)

前回は、PowerCLIを用いて物理ホスト上に4台のNested ESXi(仮想ESXi)の「器」を作成し、ESXiのインストールISOを自動マウントするところまで完了しました。

今回は、この器に対して実際にESXi OSをインストールし、VCFの要件を満たすための初期設定を行っていきます。
ここで最も注意すべきは、ホスト名やドメイン名を変更した際に発生する「SSL証明書の不整合」です。
これを放置すると、後続のCloud Builderによるデプロイ(Bring-up)が確実に失敗します。

ここでは、手動でのOSインストール後、PowerCLIを用いてホスト名の設定から証明書の再生成までを一気に自動化する手法を解説します。

構成図

論理構成

今回構築するVCF 9.0環境の論理アーキテクチャは以下の通りとなります。

物理構成

VCFを支える物理ホストの構成は以下の通りです。

Nested ESXiへのOSインストール

前回のスクリプト(02_Deploy-VCF-NestedVMs.ps1)で、4台の仮想マシンにはすでにESXi 8.0のISOファイルがマウントされています。
まずは、vSphere ClientからNested ESXi(ここではesx-n01)をパワーオンし、通常の物理サーバーと同じようにESXiのインストールを進めます。
途中のインストール先ディスクの選択において、前回16GB(Thin)で作成したSATA側のブート用仮想ディスクを選択します。
間違っても50GB(NVMe)や250GB(SATA)のvSAN用ディスクを選ばないように注意してください。

あとは、画面の指示通り進んでインストールを行い再起動します。
再起動が終わったら、DCUI(ダイレクトコンソール)から管理IPアドレス(esx-n01:192.168.10.21)のみを手動で設定しておきます。
これ以降は、PowerCLIによる自動設定を行っていくため、ホスト名やDNS、その他の設定はここで行う必要はありません。

PowerCLIスクリプトによる自動設定項目と重要ポイント

ここからは、第3回で物理ESXiホストの設定を実施したのと同じく、PowerCLIによる設定となります。
すでに、実行環境はある前提で記載していきます。
もし、実行時にエラーなどが発生する場合は、第3回の記事をご覧ください。

このスクリプトを実行すると、対話形式で対象のIPアドレスとホスト名を聞かれます。
入力後、VCFの要件を満たすために以下の4つの設定が順次自動適用されます。

1. ホスト名・DNS・ドメインの構成

インストール直後のデフォルト状態(localhost)から、指定されたホスト名(例:esx-n01)およびドメイン(vcf.local)へ変更し、名前解決の要となるDNSサーバー(dc01)のIPアドレスを登録します。

2. 必須サービスの「ホスト連動」起動(NTP・SSH)

SSHとNTPのサービス(TSM-SSH、ntpd)を単に起動するだけでなく、スタートアップポリシーを「On(ホストと連動して開始および停止)」に強制変更しています。VCF環境においてホスト再起動後にNTPが停止したままになるのは致命傷となるため、この設定で確実な時刻同期を行います。

3. ジャンボフレーム(MTU 9000)の適用

第3回で物理スイッチや物理ESXi側に設定したMTU 9000を、このNested ESXi側の仮想スイッチ(vSwitch0)と管理インターフェース(vmk0)にも適用します。これにより、物理からNestedの内部まで、NSXのオーバーレイ通信を透過させる足回りが完成します。

4. SSH経由でのSSL証明書再生成

ESXiはインストール直後に「localhost.localdomain」で自己署名証明書を自動生成しますが、その後GUI等からホスト名を変更しても、証明書のSubject(CN)は古いまま更新されません。 この不整合状態のままVCFのデプロイを走らせると、SDDC Managerがホストを認証できず、エラーで停止します。
スクリプトでは、ホスト名が変更されたことを検知すると、Windows標準のSSHコマンドを用いて対象のESXiに直接ログインし、/sbin/generate-certificates と /etc/init.d/hostd restart を実行することで、正しいホスト名の証明書を自動で再生成させています。

スクリプト:03_Config-VCF-NestedESXi.ps1

# ==============================================================================
# VCF 9.0 Nested ESXi Configuration Script (Phase 3 - 冪等性&SSH強化版)
# ==============================================================================

# --- 1. User Input ---
$nested_ip = Read-Host "Enter the Nested ESXi IP (e.g., 192.168.10.21)"
$nested_name = Read-Host "Enter the short Hostname (e.g., esx-n01)"
$esxi_user = "root"
$esxi_pass = "VMware1!"
$dns_ntp_ip= "192.168.10.11"
$domain    = "vcf.local"

$confirm = Read-Host "`nStart configuration for [$nested_name / $nested_ip]? (Y/N)"
if ($confirm -notmatch "^[Yy]$") { exit }

Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Confirm:$false | Out-Null
Write-Host "`nConnecting to [$nested_ip]..." -ForegroundColor Cyan
Connect-VIServer -Server $nested_ip -User $esxi_user -Password $esxi_pass | Out-Null
$vmhost = Get-VMHost

# ==============================================================================
# 2. Hostname, DNS and Domain (Skip if already correct)
# ==============================================================================
$net = Get-VMHostNetwork -VMHost $vmhost
$current_host = $net.HostName
$current_domain = $net.DomainName

$needs_cert_regen = $false

if ($current_host -ne $nested_name -or $current_domain -ne $domain) {
    Write-Host "Configuring Hostname, DNS, and Domain ($domain)..."
    Set-VMHostNetwork -VMHost $vmhost -HostName $nested_name -DomainName $domain -SearchDomain $domain -DnsAddress $dns_ntp_ip -Confirm:$false | Out-Null
    $needs_cert_regen = $true
} else {
    Write-Host "Hostname ($nested_name) and Domain ($domain) are already correct. Skipping update." -ForegroundColor Yellow
}

# ==============================================================================
# 3. Services (SSH, NTP) - 強制有効化&ホスト連動設定
# ==============================================================================
Write-Host "Configuring Services (ESXi Shell, SSH, NTP)..."
$currentNtp = Get-VMHostNtpServer -VMHost $vmhost
if ($currentNtp -notcontains $dns_ntp_ip) {
    Add-VMHostNtpServer -VMHost $vmhost -NtpServer $dns_ntp_ip -Confirm:$false | Out-Null
}

# TSM(ESXi Shell), TSM-SSH(SSH), ntpd を「ホスト連動(On)」にして起動
$servicesToEnable = @("TSM", "TSM-SSH", "ntpd")
foreach ($srvName in $servicesToEnable) {
    $service = Get-VMHostService -VMHost $vmhost | Where-Object { $_.Key -eq $srvName }
    
    # Policy "On" = ホストと連動して開始および停止
    if ($service.Policy -ne "On") {
        Set-VMHostService -HostService $service -Policy "On" -Confirm:$false | Out-Null
    }
    # 停止している場合は起動
    if (-not $service.Running) {
        Start-VMHostService -HostService $service -Confirm:$false | Out-Null
    }
}

# ==============================================================================
# 4. MTU 9000
# ==============================================================================
Write-Host "Setting MTU to 9000 for vSwitch0 and vmk0..."
Get-VirtualSwitch -VMHost $vmhost -Name "vSwitch0" | Set-VirtualSwitch -Mtu 9000 -Confirm:$false | Out-Null
Get-VMHostNetworkAdapter -VMHost $vmhost -Name "vmk0" | Set-VMHostNetworkAdapter -Mtu 9000 -Confirm:$false | Out-Null

# ==============================================================================
# 5. Configuration Report
# ==============================================================================
Write-Host "`n=======================================================" -ForegroundColor Green
Write-Host " Configuration Report : $nested_name ($nested_ip)" -ForegroundColor Green
Write-Host "=======================================================" -ForegroundColor Green

# Network
$net_updated = Get-VMHostNetwork -VMHost $vmhost
Write-Host "[Hostname & Domain]"
Write-Host "  Hostname       : $($net_updated.HostName)"
Write-Host "  Domain         : $($net_updated.DomainName)"
Write-Host "  FQDN           : $($net_updated.HostName).$($net_updated.DomainName)"
Write-Host "  Search Domains : $($net_updated.SearchDomain -join ', ')"
Write-Host "  DNS Servers    : $($net_updated.DnsAddress -join ', ') `n"

# Services
Write-Host "[Services Status (Policy 'On' = Start/Stop with Host)]"
$chkServices = Get-VMHostService -VMHost $vmhost | Where-Object { $_.Key -match "TSM|ntpd" }
foreach ($s in $chkServices) {
    Write-Host "  $($s.Key.PadRight(10)) : Policy = $($s.Policy), Running = $($s.Running)"
}
Write-Host "  NTP Server : $(Get-VMHostNtpServer -VMHost $vmhost)`n"

# MTU
Write-Host "[Network (MTU)]"
$chkVSwitch = Get-VirtualSwitch -VMHost $vmhost -Name "vSwitch0"
$chkVmk0 = Get-VMHostNetworkAdapter -VMHost $vmhost -Name "vmk0"
Write-Host "  vSwitch0 MTU : $($chkVSwitch.Mtu)"
Write-Host "  vmk0 MTU     : $($chkVmk0.Mtu)"

Write-Host "=======================================================" -ForegroundColor Green

# --- 6. Disconnect PowerCLI ---
Disconnect-VIServer -Server $nested_ip -Confirm:$false | Out-Null
Write-Host "`nPowerCLI Disconnected safely." -ForegroundColor Cyan

# ==============================================================================
# 7. Auto Regenerate Certificates via SSH (Conditional)
# ==============================================================================
if ($needs_cert_regen) {
    Write-Host "`n[Final Step] Regenerating SSL Certificates and restarting hostd via SSH..." -ForegroundColor Yellow
    Write-Host "※ You will be prompted to enter the ESXi root password: $esxi_pass" -ForegroundColor Yellow
    ssh -o StrictHostKeyChecking=no "$esxi_user@$nested_ip" "/sbin/generate-certificates && /etc/init.d/hostd restart"
    Write-Host "`nCertificate regeneration complete!" -ForegroundColor Green
} else {
    Write-Host "`n[Final Step] Certificate regeneration skipped (Hostname/Domain were already correct)." -ForegroundColor Green
}

Write-Host "`nAll operations for [$nested_name] are COMPLETE!" -ForegroundColor Green

スクリプトの実行

それでは、このスクリプトを実行していきます。
このスクリプトは、Nestedホストを1台ずつ設定するようになっているため、ここでは、esx-n01(192.168.10.21)の設定を自動的に実施していきます。

PS C:\Users\hippi\Desktop\VCF9自動構築> .\03_Config-VCF-NestedESXi.ps1
Enter the Nested ESXi IP (e.g., 192.168.10.21): 192.168.10.21
Enter the short Hostname (e.g., esx-n01): esx-n01

Start configuration for [esx-n01 / 192.168.10.21]? (Y/N): Y

Connecting to [192.168.10.21]...
Configuring Hostname, DNS, and Domain (vcf.local)...
Configuring Services (SSH, NTP)...
Setting MTU to 9000 for vSwitch0 and vmk0...

=======================================================
 Configuration Report : esx-n01 (192.168.10.21)
=======================================================
[Hostname & Domain]
  Hostname       : esx-n01
  Domain         : vcf.local
  FQDN           : esx-n01.vcf.local
  Search Domains : vcf.local
  DNS Servers    : 192.168.10.11 

[Services Status]
  TSM        : Policy = on, Running = True
  TSM-SSH    : Policy = on, Running = True
  ntpd       : Policy = on, Running = True
  NTP Server : 192.168.10.11

[Network (MTU)]
  vSwitch0 MTU : 9000
  vmk0 MTU     : 9000
=======================================================

Configuration for [esx-n01] is COMPLETE!

PS C:\Users\hippi\Desktop\VCF9自動構築> 

環境の確認と残り3台の実施

スクリプトで設定した部分は設定内容を出力するようにしているので、正しく設定が反映されたか確認できます。
同様にGUIでも設定できていることを確認してください。
問題なければ、残りのNestedホストesx-n02、esx-n03、esx-n04も同様に設定しましょう。
以下は、esx-n01のホスト名、NTPサーバー、DNSサーバーなどの設定が正しく設定されていることをGUIで確認しました。

今後の展開

これで、VCFの要件(ネットワーク疎通、NTP同期、証明書の正当性)を満たしたNested ESXiクラスタが完成しました。

次回は、VCF構築の司令塔である「Cloud Builder」アプライアンスをデプロイし、運命のパラメータシート(JSONファイル)作成に挑みます。

【VCF 9.0構築】VCF要件を突破する!Nested ESXiの展開とチューニング(第4回)

前回は、PowerCLIを用いて物理ESXiホスト(esx01〜03)を一括で初期セットアップし、ヒューマンエラーのない強固な土台を構築しました。

今回はいよいよ、VCF 9.0の「管理ドメイン(Management Domain)」を構成するための直接の舞台となる、4台のNested ESXi(仮想ESXi)を展開します。
物理サーバー上にESXiをインストールするのとは異なり、Nested環境でVCFの厳しいハードウェアチェック(特にvSANの要件)をパスするためには、いくつかのチューニングと「コードの修正」が必要になります。

本稿では、限られた物理リソースを最大限に活用し、VCFデプロイを成功させるためのNested ESXi構築術を解説します。

構成図

論理構成

今回構築するVCF 9.0環境の論理アーキテクチャは以下の通りとなります。

物理構成

VCFを支える物理ホストの構成は以下の通りです。

物理リソースを「論理」で拡張する

VCF 9.0の最小構成には、4台のESXiホストが必要です。しかし、私たちの手元にあるVCF基盤用の物理ホストは「esx01」と「esx02」の2台のみです。

そこで、この2台の物理ホストの上に、それぞれ2台ずつ(計4台)の仮想マシンとしてESXiを稼働させる「Nested(入れ子)仮想化」を採用します。これにより、物理的な台数制限を超えて、VCFのフル機能を検証できる環境を生み出します。

Nested ESXi仮想マシンのスペック構成

今回デプロイする4台のNested ESXi(esx-n01 〜 esx-n04)には、管理ドメインの重厚なコンポーネント群を支えるため、以下のスペックを割り当てます。構成の全体像を表にまとめました。

リソース 割り当てサイズ / 設定 配置先・備考
CPU 8 vCPU (8コア / 1ソケット) VHV(ハードウェア仮想化)を必ず有効化
メモリ 64 GB 「予約なし」とし、物理SATA側へスワップを退避
ネットワーク vNIC x 2 (VMXNET3) PG-VCF-Trunk (トランクポート) に接続
ハードディスク 1 16 GB (Thin) SATA SSD (ESXi OSブート領域)
ハードディスク 2 50 GB (Thin) NVMe SSD (vSAN キャッシュ層 / SSD偽装必須)
ハードディスク 3 250 GB (Thin) SATA SSD (vSAN キャパシティ層)

表の通り、単なるリソース割り当てだけでなく、VCFのチェックを通過させるための「特別な設定(VHV有効化、スワップ退避、SSD偽装)」が随所に散りばめられています。これらについて、一つずつ解説していきます。

CPUトポロジの変更(8コア / 1ソケット)

本稿で紹介するPowerCLIスクリプトでは自動的に最適化されるため、通常は意識する必要はありませんが、もしGUIから手動でNested仮想マシンを作成する場合は「CPUトポロジ」の設定に注意してください。
vCPUに「8」と入力すると、vSphere Clientのデフォルトでは「2コア/ソケット × 4ソケット」といった構成に割り振られることがあります。

実は、このままの状態でVCFのデプロイ(Bring-up)を開始すると、十中八九エラーで失敗します。 主な理由は以下の2点です。

ライセンス計算の不整合

vSphere 8のライセンス体系では「1ソケットあたり最低16コア」としてカウントされるロジックがあります。4ソケット構成だと、システムが「最低64コア分のライセンスが必要」と誤認し、キャパシティ不足でデプロイをブロックしてしまいます。

vNUMAの不整合

物理ホストが1ソケットの場合、仮想マシン側が4ソケットだとアーキテクチャの矛盾が生じます。VCFの厳格なフェイルセーフ機能がこれを検知し、パフォーマンス低下を防ぐために処理を停止させます。

手動設定を行う際は、必ず「8コア / 1ソケット」になっているかを確認するようにしてください。

VHV(ハードウェア仮想化)の公開

まず、CPU設定において非常に重要なのが「ハードウェア アシストによる仮想化をゲスト OS に公開(VHV)」を有効にすることです。これが無効だと、Nested ESXiの上でさらに仮想マシン(vCenterやNSX等)を動かすことができません。

メモリ全予約の回避とSATAへのスワップ退避

第1回でも触れましたが、ここがホームラボ設計の肝となります。
通常、Nested ESXiでは「すべてのゲストメモリを予約」することが推奨されますが、128GBの物理メモリ上で64GBのVMを2台「全予約」すると、物理ホストがリソース枯渇でハングアップしてしまいます。

そのため、今回はあえて「メモリ予約は0」とし、オーバーコミットを許容します。その代わり、起動時に作成される巨大なスワップファイル(.vswp)が高速なNVMe(vSAN用)を圧迫しないよう、VMの構成ファイルとスワップの配置先を「SATA SSD」側のデータストアに指定します。
「低速なSATAにスワップを逃がし、高速なNVMeをvSANのI/Oに専念させる」という、リソースの使い分けによる最適化です。

vSANを騙す!ストレージの「SSD偽装」とUUID

VCFのデプロイ(Bring-up)において、最も厳しいチェックが入るのがvSANです。Nested環境でハイブリッドvSANを構成するためには、仮想ディスクに対して以下の2つの修正が必要になります。

1. virtualSSD フラグ(SSD偽装)

vSANのキャッシュ層として使用する仮想ディスクは、ESXiから「フラッシュ(SSD)」として認識されている必要があります。通常、VMDKはHDDとして認識されますが、詳細パラメータに scsiX:Y.virtualSSD = 1 を追記することで、強制的にSSDとして認識させることが可能です。
XとYの部分のパラメータは環境に応じて設定してください。
私の場合は、「scsi0:1.virtualSSD」となっていました。

2. disk.EnableUUID

vSANがディスクを正確に識別し、ディスクグループを構成するために必須の設定です。これが「TRUE」になっていないと、デプロイの途中でディスクが見つからず、エラーで終了してしまいます。

PowerCLIによる一括デプロイ

これらの複雑な設定(CPUトポロジ、複数枚のNIC、SSD偽装フラグなど)を4台分手動で行うのは、ミスの可能性が高くなります。
そこで、今回もPowerCLIスクリプトを活用します。以下のスクリプトは、対話形式で物理ホストを選択し、VCF 9.0に最適化されたNested VMを一気に生成します。

スクリプト:02_Deploy-VCF-NestedVMs.ps1

# ==============================================================================
# VCF 9.0 Nested ESXi 仮想マシン 一括作成スクリプト (ISO自動マウント・完全版)
# ==============================================================================

# --- 1. 対話型ホスト選択メニュー ---
$host_choice = ""
while ($host_choice -notmatch "^[12]$") {
    Write-Host "`n=======================================================" -ForegroundColor Cyan
    Write-Host " Nested ESXi (esx-n0X) デプロイメント・メニュー" -ForegroundColor Cyan
    Write-Host "=======================================================" -ForegroundColor Cyan
    Write-Host " [1] esx01 (192.168.10.101) へ esx-n01, esx-n02 を作成"
    Write-Host " [2] esx02 (192.168.10.102) へ esx-n03, esx-n04 を作成"
    Write-Host "-------------------------------------------------------" -ForegroundColor Cyan
    
    $host_choice = Read-Host "デプロイ先の物理ホストを選択してください (1 または 2)"
}

# 選択結果に基づく変数の自動設定
if ($host_choice -eq "1") {
    $phy_host_ip = "192.168.10.101"
    $vm_list     = @("esx-n01", "esx-n02")
    $ds_nvme     = "ds-nvme-01"
    $ds_sata     = "ds-sata-01"
} else {
    $phy_host_ip = "192.168.10.102"
    $vm_list     = @("esx-n03", "esx-n04")
    $ds_nvme     = "ds-nvme-02"
    $ds_sata     = "ds-sata-02"
}

# --- 2. 共通環境変数 ---
$phy_user    = "root"
$phy_pass    = "VMware1!"
$portgroup   = "PG-VCF-Trunk"

$num_cpu     = 8
$memory_gb   = 64
$disk_boot   = 16
$disk_cache  = 50
$disk_cap    = 250

# ★ マウントするISOのファイル名
$iso_filename = "VMware-VMvisor-Installer-8.0U3-24022510.x86_64.iso"

# --- 3. 物理ホストへの接続 ---
Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Confirm:$false | Out-Null
Write-Host "`n物理ホスト [$phy_host_ip] に接続しています..." -ForegroundColor Cyan
Connect-VIServer -Server $phy_host_ip -User $phy_user -Password $phy_pass | Out-Null
$vmhost = Get-VMHost

# --- 4. VMの一括作成処理 (スキップ機能付き) ---
foreach ($vm_name in $vm_list) {
    Write-Host "`n[$vm_name] の確認を開始します..." -ForegroundColor Cyan

    # 【安全装置】すでに同名のVMが存在するかチェック
    $existing_vm = Get-VM -Name $vm_name -ErrorAction SilentlyContinue
    if ($existing_vm) {
        Write-Host "  -> [スキップ] 仮想マシン '$vm_name' はすでに存在するため、作成処理をスキップします。" -ForegroundColor Yellow
        continue
    }

    # 1. VM作成 (Bootディスクを Thin Provisioning に変更)
    Write-Host "  -> VM本体とBootディスク(Thin 16GB)を作成中..."
    $vm = New-VM -Name $vm_name -VMHost $vmhost -Datastore $ds_sata -NumCpu $num_cpu -MemoryGB $memory_gb -DiskGB $disk_boot -DiskStorageFormat Thin -NetworkName $portgroup -GuestId "vmkernel8Guest" -Confirm:$false

    # 2. CPUトポロジの修正 (8コア / 1ソケット)
    Write-Host "  -> CPUトポロジ (8コア/1ソケット) を構成中..."
    Set-VM -VM $vm -NumCpu 8 -CoresPerSocket 8 -Confirm:$false | Out-Null

    # 3. NIC 2 の追加
    Write-Host "  -> NIC 2 を追加・接続中..."
    New-NetworkAdapter -VM $vm -NetworkName $portgroup -Type Vmxnet3 -StartConnected:$true -Confirm:$false | Out-Null

    # 4. vSAN用ディスクの追加
    Write-Host "  -> vSAN Cache (50GB) と Capacity (250GB) を追加中..."
    New-HardDisk -VM $vm -CapacityGB $disk_cache -Datastore $ds_nvme -StorageFormat Thin -Confirm:$false | Out-Null
    New-HardDisk -VM $vm -CapacityGB $disk_cap -Datastore $ds_sata -StorageFormat Thin -Confirm:$false | Out-Null

    # ★ 5. CD/DVDドライブの追加とISOのマウント
    Write-Host "  -> CD/DVDドライブを追加し、ESXiインストーラ(ISO)をマウント中..."
    $iso_path = "[$ds_sata] ISO/$iso_filename"
    New-CDDrive -VM $vm -IsoPath $iso_path -StartConnected:$true -Confirm:$false | Out-Null

    # 6. 詳細設定 (VHV, SSDフラグ, UUID有効化)
    Write-Host "  -> ハードウェア仮想化、SSDフラグ(Cacheのみ)、UUID(最重要) を設定中..."
    $spec = New-Object VMware.Vim.VirtualMachineConfigSpec
    $spec.NestedHVEnabled = $true

    # ★ キャッシュ用(scsi0:1) のみを SSD に偽装する
    $extra1 = New-Object VMware.Vim.OptionValue
    $extra1.Key = "scsi0:1.virtualSSD"
    $extra1.Value = "1"
    
    # UUIDの有効化(vSAN必須設定)
    $extra3 = New-Object VMware.Vim.OptionValue
    $extra3.Key = "disk.EnableUUID"
    $extra3.Value = "TRUE"
    
    $spec.ExtraConfig += $extra1
    $spec.ExtraConfig += $extra3

    $vm.ExtensionData.ReconfigVM_Task($spec) | Out-Null

    Write-Host "[$vm_name] の作成が完了しました!" -ForegroundColor Green
}

# --- 5. 切断 ---
Disconnect-VIServer -Server $phy_host_ip -Confirm:$false | Out-Null
Write-Host "`n[$phy_host_ip] での Nested ESXi VM 作成プロセスが完了しました。" -ForegroundColor Cyan

スクリプトの実行

それでは、このスクリプトを実行していきます。
今回は、esx01(192.168.10.101)に、esx-n01、esx-n02の2台を作成します。

PS C:\work> .\02_Deploy-VCF-NestedVMs.ps1

=======================================================
 Nested ESXi (esx-n0X) デプロイメント・メニュー
=======================================================
 [1] esx01 (192.168.10.101) へ esx-n01, esx-n02 を作成
 [2] esx02 (192.168.10.102) へ esx-n03, esx-n04 を作成
-------------------------------------------------------
デプロイ先の物理ホストを選択してください (1 または 2): 1

物理ホスト [192.168.10.101] に接続しています...

[esx-n01] の確認を開始します...
  -> VM本体とBootディスク(Thin 16GB)を作成中...
  -> CPUトポロジ (8コア/1ソケット) を構成中...
  -> NIC 2 を追加・接続中...
  -> vSAN Cache (50GB) と Capacity (250GB) を追加中...
  -> CD/DVDドライブを追加し、ESXiインストーラ(ISO)をマウント中...
  -> ハードウェア仮想化、SSDフラグ(Cacheのみ)、UUID(最重要) を設定中...
[esx-n01] の作成が完了しました!

[esx-n02] の確認を開始します...
  -> VM本体とBootディスク(Thin 16GB)を作成中...
  -> CPUトポロジ (8コア/1ソケット) を構成中...
  -> NIC 2 を追加・接続中...
  -> vSAN Cache (50GB) と Capacity (250GB) を追加中...
  -> CD/DVDドライブを追加し、ESXiインストーラ(ISO)をマウント中...
  -> ハードウェア仮想化、SSDフラグ(Cacheのみ)、UUID(最重要) を設定中...
[esx-n02] の作成が完了しました!

[192.168.10.101] での Nested ESXi VM 作成プロセスが完了しました。
PS C:\work>

これにより、2台のNestedホスト2台の作成が完了しました。
実際、GUIからも2台の仮想マシンが作成できたことを確認できます。

問題なければ、再度、スクリプトを実行して、esx-n03、esx-n04も作成します。

今後の展開

Nested ESXiの「器」が整いました。あとはISOからESXiをインストールし、第3回で紹介した設定スクリプト(03_Config-VCF-NestedESXi.ps1)で中身を整えれば、準備完了です。

次回はいよいよ、VCF構築の司令塔である「Cloud Builder」アプライアンスをデプロイし、運命のパラメータシート(JSONファイル)作成に挑みます。VCFデプロイの成否を分ける、最も神経を使う工程のスタートです。

【VCF 9.0構築】ヒューマンエラーを排除!物理ESXiホストの初期設定をPowerCLIで自動化する(第3回)

前回は、esx03上にVyOSルーターとAD/DNS/NTPサーバーを構築し、VCFを迎えるためのネットワークと基盤管理サービスの準備を行いました。

しかし、ここで一つ重要な工程の解説が漏れていました。そもそも、これら管理系コンポーネントや、今後展開するNested ESXiを稼働させるための「土台となる物理ESXiホスト(esx01〜03)自体の初期セットアップ」をどのように行ったかについてです。

VCF環境の構築において、最も気を付けないといけないのは「手作業によるヒューマンエラー」です。VLAN IDの打ち間違い、NTPの指定漏れ、データストアの命名規則のブレなど、些細なミスが後のSDDC ManagerによるBring-up(自動展開)プロセスで致命的なエラーを引き起こします。

そこで本稿では、手動によるGUI操作を極力排除し、PowerCLIを用いて物理ESXiホストのネットワーク、ストレージ、時刻設定などを正確かつ一括で構成する手法を解説します。

構成図

論理構成

今回構築するVCF 9.0環境の論理アーキテクチャは以下の通りとなります。

物理構成

VCFを支える物理ホストの構成は以下の通りです。

GUI手動設定の罠と自動化のメリット

ESXiをインストールした後、通常はWebブラウザからHost Clientにログインし、ポチポチと設定を行います。
ホストが1台であれば問題ありませんが、今回はesx01、esx02、esx03と3台の物理ホストが存在します。さらに、それぞれのホストで「管理用ポートグループ」「VCFのVLANトランク用ポートグループ」「NVMeとSATAのデータストア名変更」などを個別に設定していくと、必ずどこかで設定ミスや差異が生じます。

PowerCLIを使用してスクリプト化することで、以下のメリットが得られます。

  • 冪等性(べきとうせい)と確実性の担保:何度実行しても同じ、正確な設定が保証される。
  • 構築時間の圧倒的な短縮:数十分かかる設定作業が数秒で完了する。
  • 設定値のドキュメント化:スクリプト自体が「どういう設定をしたか」の設計書(コード)になる。

物理ESXiの事前準備

PowerCLIからスクリプトを流し込むため、物理サーバーにESXi 8.0 Update 3をインストールした後、DCUI(ダイレクトコンソール)から以下の最低限の設定のみを手動で行っておきます。

  • 管理IPアドレスの静的設定(例:192.168.10.101など)
  • SSHおよびESXi Shellの有効化(トラブルシューティングおよびスクリプト実行用)
  • ホスト名の設定(esx01.vcf.local など)

ここから先は、作業用PCのPowerCLIから実行します。

PowerCLI実行前の準備

PowerCLIのインストール

Windows環境でPowerCLIが未インストールの場合は、事前に管理者権限のPowerShellで以下を実行してください。

Install-Module -Name VMware.PowerCLI -Scope CurrentUser

実行ポリシーの変更

スクリプトの実行がブロックされる場合は、ポリシーを一時的に変更します。

Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

PowerCLIによる初期構成スクリプト実行

以下は、物理ホスト(esx03を例とする)に対して、必要なネットワーク、NTP、ストレージ設定を一括で流し込むPowerCLIスクリプトです。
環境に合わせて変数(IPアドレスやVLAN ID)を変更するだけで、他のホスト(esx02, esx03)にも即座に流用可能です。

スクリプトの解説とポイント

このスクリプトでは、単なるIPアドレスの設定だけでなく、VCF環境特有のシビアな要件をクリアするためのチューニングを同時に仕込んでいます。特に重要なのが以下の3点です。

1. NTPの徹底的な同期と自動起動

VCF展開時、ホスト間でNTPが同期していないと、証明書の生成エラーなどでBring-upが致命的な失敗を招きます。スクリプト内で、前回構築したAD/NTPサーバー(dc01)を確実に向かせ、NTPデーモン(ntpd)がホストと連動して自動起動するようにポリシーを強制しています。

2. VLANトランクとセキュリティポリシーの解放

今後デプロイするNested ESXi間でオーバーレイ通信(NSX TEP等)を通すため、物理側のポートグループ(PG-VCF-Trunk)には**VLAN ID 4095(全VLAN許可)を設定しています。
加えて、Nested ESXi特有のMACアドレス通信を物理スイッチがブロックしないよう、Set-SecurityPolicy を用いて「無差別モード(Promiscuous mode)」「MACアドレス変更」「偽装転送」の3つをすべて許可(True)**に変更しています。

3. VCF向けの詳細設定(Advanced Settings)チューニング

スクリプトの終盤で構成している詳細設定は、Nested環境を安定稼働させるための隠し味です。MACフラッピング対策(Net.FollowHardwareMac)の無効化や、メモリの透過的ページ共有(TPS)に関する設定をVCFの推奨値に合わせることで、パフォーマンスの劣化や不要な警告を防いでいます。

スクリプト:01_Config-VCF-ESXi.ps1

ライセンスの情報は、実行前に変更して下さい。
また、ホスト名やIPアドレスも環境にあわせて変更してください。

# ==============================================================================
# VCF 9.0 ホームラボ用 物理ESXi 初期設定スクリプト (対話型プロンプト・完全版)
# ==============================================================================

# --- 1. 対話型ホストID選択メニュー ---
$host_id = ""
while ($host_id -notmatch "^\d{2}$") {
    Write-Host "`n=======================================================" -ForegroundColor Cyan
    Write-Host " VCF 9.0 ESXi 初期設定スクリプト" -ForegroundColor Cyan
    Write-Host "=======================================================" -ForegroundColor Cyan
    Write-Host " [01] esx01 (IP: 192.168.10.101)"
    Write-Host " [02] esx02 (IP: 192.168.10.102)"
    Write-Host " [03] esx03 (IP: 192.168.10.103)"
    Write-Host " [手動] 上記以外の任意の2桁の数字を入力"
    Write-Host "-------------------------------------------------------" -ForegroundColor Cyan
    
    $input_id = Read-Host "設定対象のホストIDを入力してください (例: 01)"
    
    if ($input_id -match "^\d{2}$") {
        $host_id = $input_id
    } else {
        Write-Host "`n[エラー] 2桁の数字 (01, 02, 03...) で入力してください!" -ForegroundColor Red
    }
}

# 選択されたIDに基づいて変数を自動生成
$esxi_host   = "192.168.10.1$host_id" 
$esxi_user   = "root"
$esxi_pass   = "VMware1!"             # 初期パスワード
$dns_ntp_ip  = "192.168.10.11"        # dc01 (DNS/NTP)
$domain      = "vcf.local"

# ライセンスキー設定
$license_key = "XXXXX-XXXXX-XXXXX-XXXXX-XXXXX" # ご自身のライセンスキーに変更してください

# 最終確認
$confirm = Read-Host "`n対象ホスト [$esxi_host] の設定を開始します。よろしいですか? (Y/N)"
if ($confirm -notmatch "^[Yy]$") {
    Write-Host "処理を中断しました。" -ForegroundColor Yellow
    exit
}

# 証明書エラーを無視する設定
Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Confirm:$false | Out-Null

# --- 2. ESXiホストへの接続 ---
Write-Host "`n[$esxi_host] に接続しています..." -ForegroundColor Cyan
Connect-VIServer -Server $esxi_host -User $esxi_user -Password $esxi_pass | Out-Null
$vmhost = Get-VMHost

# --- 3. DNS と ドメインの設定 (サーチドメインの上書き含む) ---
Write-Host "DNS、ドメイン、および検索ドメイン ($domain) を設定しています..." -ForegroundColor Cyan
Get-VMHostNetwork -VMHost $vmhost | Set-VMHostNetwork -DnsAddress $dns_ntp_ip -DomainName $domain -SearchDomain $domain -Confirm:$false | Out-Null

# --- 4. ライセンスキーの割り当て ---
Write-Host "ライセンスキーを割り当てています..." -ForegroundColor Cyan
Set-VMHost -VMHost $vmhost -LicenseKey $license_key -Confirm:$false | Out-Null

# --- 5. サービス (TSM / TSM-SSH / NTP) の有効化と自動起動設定 ---
Write-Host "各種サービス (ESXi Shell, SSH, NTP) を設定しています..." -ForegroundColor Cyan
$currentNtp = Get-VMHostNtpServer -VMHost $vmhost
if ($currentNtp -notcontains $dns_ntp_ip) {
    Add-VMHostNtpServer -VMHost $vmhost -NtpServer $dns_ntp_ip -Confirm:$false | Out-Null
}

$servicesToEnable = @("TSM", "TSM-SSH", "ntpd")
foreach ($srvName in $servicesToEnable) {
    $service = Get-VMHostService -VMHost $vmhost | Where-Object { $_.Key -eq $srvName }
    Set-VMHostService -HostService $service -Policy "On" -Confirm:$false | Out-Null
    if (-not $service.Running) {
        Start-VMHostService -HostService $service -Confirm:$false | Out-Null
    }
}

# --- 6. MTU 9000 (Jumbo Frame) の設定 ---
Write-Host "vSwitch0 と vmk0 の MTU を 9000 に変更しています..." -ForegroundColor Cyan
Get-VirtualSwitch -VMHost $vmhost -Name "vSwitch0" | Set-VirtualSwitch -Mtu 9000 -Confirm:$false | Out-Null
Get-VMHostNetworkAdapter -VMHost $vmhost -Name "vmk0" | Set-VMHostNetworkAdapter -Mtu 9000 -Confirm:$false | Out-Null

# --- 7. ポートグループの追加とセキュリティポリシーの緩和 ---
Write-Host "ポートグループを追加し、Nested ESXi用のセキュリティポリシーを適用しています..." -ForegroundColor Cyan
$vSwitch = Get-VirtualSwitch -VMHost $vmhost -Name "vSwitch0"

if (-not ($vSwitch | Get-VirtualPortGroup -Name "PG-VCF-Trunk" -ErrorAction SilentlyContinue)) {
    New-VirtualPortGroup -VirtualSwitch $vSwitch -Name "PG-VCF-Trunk" -VLanId 4095 -Confirm:$false | Out-Null
}
if (-not ($vSwitch | Get-VirtualPortGroup -Name "PG-Infra-Mgmt" -ErrorAction SilentlyContinue)) {
    New-VirtualPortGroup -VirtualSwitch $vSwitch -Name "PG-Infra-Mgmt" -VLanId 10 -Confirm:$false | Out-Null
}

$vSwitch | Get-SecurityPolicy | Set-SecurityPolicy -AllowPromiscuous $true -MacChanges $true -ForgedTransmits $true -Confirm:$false | Out-Null

# --- 8. 詳細設定 (Advanced Settings) のチューニング ---
Write-Host "詳細設定 (MACフラッピング対策 / TPS) を構成しています..." -ForegroundColor Cyan
Get-AdvancedSetting -Entity $vmhost -Name "Net.FollowHardwareMac" | Set-AdvancedSetting -Value 0 -Confirm:$false | Out-Null
Get-AdvancedSetting -Entity $vmhost -Name "Mem.ShareForceSalting" | Set-AdvancedSetting -Value 0 -Confirm:$false | Out-Null
Get-AdvancedSetting -Entity $vmhost -Name "Mem.ShareScanGHz" | Set-AdvancedSetting -Value 4 -Confirm:$false | Out-Null


# ==============================================================================
# 9. 設定情報 確認レポート出力
# ==============================================================================
Write-Host "`n=======================================================" -ForegroundColor Green
Write-Host " 設定情報確認レポート : $esxi_host" -ForegroundColor Green
Write-Host "=======================================================" -ForegroundColor Green

# ライセンス情報
Write-Host "[ライセンス]"
$licMgr = Get-View $vmhost.ExtensionData.ConfigManager.LicenseManager
$currentKey = $licMgr.Licenses.LicenseKey
Write-Host "  License Key : $($currentKey -join ', ') `n"

# ネットワーク情報
$net = Get-VMHostNetwork -VMHost $vmhost
Write-Host "[DNS / ドメイン]"
Write-Host "  DNS Servers    : $($net.DnsAddress -join ', ')"
Write-Host "  Domain         : $($net.DomainName)"
Write-Host "  Search Domains : $($net.SearchDomain -join ', ') `n"

# サービス情報
Write-Host "[サービス自動起動状態]"
$chkServices = Get-VMHostService -VMHost $vmhost | Where-Object { $_.Key -match "TSM|ntpd" }
foreach ($s in $chkServices) {
    Write-Host "  $($s.Key.PadRight(10)) : Policy = $($s.Policy), Running = $($s.Running)"
}
Write-Host "  NTP Server : $(Get-VMHostNtpServer -VMHost $vmhost)`n"

# vSwitch / ポートグループ / MTU
Write-Host "[ネットワーク (MTU & ポートグループ)]"
$chkVSwitch = Get-VirtualSwitch -VMHost $vmhost -Name "vSwitch0"
$chkVmk0 = Get-VMHostNetworkAdapter -VMHost $vmhost -Name "vmk0"
Write-Host "  vSwitch0 MTU : $($chkVSwitch.Mtu)"
Write-Host "  vmk0 MTU     : $($chkVmk0.Mtu)"

$chkPGs = Get-VirtualPortGroup -VirtualSwitch $chkVSwitch
foreach ($pg in $chkPGs) {
    Write-Host "  PG Name : $($pg.Name.PadRight(15)) | VLAN : $($pg.VLanId)"
}
Write-Host ""

# セキュリティポリシー
Write-Host "[セキュリティポリシー (vSwitch0)]"
$chkSec = $chkVSwitch | Get-SecurityPolicy
Write-Host "  無差別モード (Promiscuous)   : $($chkSec.AllowPromiscuous)"
Write-Host "  MACアドレス変更 (MacChanges) : $($chkSec.MacChanges)"
Write-Host "  偽装転送 (ForgedTransmits)   : $($chkSec.ForgedTransmits)`n"

# 詳細設定
Write-Host "[詳細設定 (Advanced Settings)]"
$chkMac = Get-AdvancedSetting -Entity $vmhost -Name "Net.FollowHardwareMac"
$chkTPS1 = Get-AdvancedSetting -Entity $vmhost -Name "Mem.ShareForceSalting"
$chkTPS2 = Get-AdvancedSetting -Entity $vmhost -Name "Mem.ShareScanGHz"
Write-Host "  Net.FollowHardwareMac : $($chkMac.Value) (期待値: 0)"
Write-Host "  Mem.ShareForceSalting : $($chkTPS1.Value) (期待値: 0)"
Write-Host "  Mem.ShareScanGHz      : $($chkTPS2.Value) (期待値: 4)"

Write-Host "=======================================================" -ForegroundColor Green

# --- 10. 切断 ---
Disconnect-VIServer -Server $esxi_host -Confirm:$false | Out-Null
Write-Host "[$esxi_host] から切断しました。作業完了です。" -ForegroundColor Cyan

スクリプトの実行

それでは、このスクリプトを実行していきます。
今回は、vyosなどを稼働させるesx03(192.168.10.103)の設定を自動的に実施します。
このスクリプトは、esx01、esx02の設定も実施可能で、1台ずつ実行するように作成しています。

PS C:\work\VCF9構築完全版\00_PowerCLIスクリプト> .\01_Config-VCF-ESXi.ps1

=======================================================
 VCF 9.0 ESXi 初期設定スクリプト
=======================================================
 [01] esx01 (IP: 192.168.10.101)
 [02] esx02 (IP: 192.168.10.102)
 [03] esx03 (IP: 192.168.10.103)
 [手動] 上記以外の任意の2桁の数字を入力
-------------------------------------------------------
設定対象のホストIDを入力してください (例: 01): 03

対象ホスト [192.168.10.103] の設定を開始します。よろしいですか? (Y/N): Y

[192.168.10.103] に接続しています...
DNS、ドメイン、および検索ドメイン (vcf.local) を設定しています...
ライセンスキーを割り当てています...
各種サービス (ESXi Shell, SSH, NTP) を設定しています...
vSwitch0 と vmk0 の MTU を 9000 に変更しています...
ポートグループを追加し、Nested ESXi用のセキュリティポリシーを適用しています...
詳細設定 (MACフラッピング対策 / TPS) を構成しています...

=======================================================
 設定情報確認レポート : 192.168.10.103
=======================================================
[ライセンス]
  License Key : XXXXX-XXXXX-XXXXX-XXXXX-XXXXX

[DNS / ドメイン]
  DNS Servers    : 192.168.10.11
  Domain         : vcf.local
  Search Domains : vcf.local 

[サービス自動起動状態]
  TSM        : Policy = on, Running = True
  TSM-SSH    : Policy = on, Running = True
  ntpd       : Policy = on, Running = True
  NTP Server : 192.168.10.11

[ネットワーク (MTU & ポートグループ)]
  vSwitch0 MTU : 9000
  vmk0 MTU     : 9000
  PG Name : Management Network | VLAN : 10
  PG Name : PG-Infra-Mgmt   | VLAN : 10
  PG Name : PG-VCF-Trunk    | VLAN : 4095
  PG Name : VM Network      | VLAN : 0

[セキュリティポリシー (vSwitch0)]
  無差別モード (Promiscuous)   : True
  MACアドレス変更 (MacChanges) : True
  偽装転送 (ForgedTransmits)   : True

[詳細設定 (Advanced Settings)]
  Net.FollowHardwareMac : 0 (期待値: 0)
  Mem.ShareForceSalting : 0 (期待値: 0)
  Mem.ShareScanGHz      : 4 (期待値: 4)
=======================================================
[192.168.10.103] から切断しました。作業完了です。

PS C:\work\VCF9構築完全版\00_PowerCLIスクリプト> 

環境の確認と残り2台の実施

スクリプトで設定した部分は設定内容を出力するようにしているので、正しく設定が反映されたか確認できます。
同様にGUIでも設定できていることを確認してください。
以下は、vSwitch0のトポロジーですが、ポートグループやセキュリティ設定が正しく反映されていることが確認できました。

問題なければ、Nestedホストを稼働させるesx01、esx02も同様に設定しましょう。

今後の展開

このスクリプトをベースに、IPアドレスやホスト名などの変数を書き換えてesx01、esx02、esx03に流し込むことで、ヒューマンエラーが一切ない、強固で同一仕様の物理アンダーレイ基盤が完成しました。

これでようやく、VCFを迎え入れるためのすべての「土台作り」が完了です!

次回は、今回組み上げたesx01/02の物理基盤の上に、いよいよVCFの直接の土台となる「Nested ESXi」を4台デプロイし、vSAN要件を突破するためのシビアな構成(virtualSSD等)を行っていきます。

【VCF 9.0構築】SDDC Manager展開前の必須要件:VyOSルーターとAD/DNS/NTPの構築(第2回)

前回は、VCF 9.0ホームラボの全体アーキテクチャと、リソース枯渇を防ぐための物理機器の役割分離について解説しました。
今回は、VCFの土台となる「アンダーレイネットワーク」と「基盤管理サービス」の構築を行います。

VCF(VMware Cloud Foundation)のデプロイは、SDDC Managerによる厳格な事前チェック(Validation)から始まります。このチェックにおいて、「ネットワークの疎通(MTU含む)」「DNSの正引き・逆引き」「NTPの時刻同期」の3つは、1つでも不備があると容赦なくデプロイが失敗する非常にシビアなポイントです。

ここでは、VCF基盤ホスト(esx01, esx02)の負荷を下げるため、外部管理基盤用ホスト(esx03)上にVyOSおよびWindows Server(AD/DNS/NTP)を構築し、VCFを迎えるための完璧な事前準備を整える手順を解説します。

論理構成

今回構築するVCF 9.0環境の論理アーキテクチャは以下の通りとなります。

外部管理基盤用ホスト(esx03)の役割

まず前提として、今回構築するコンポーネントはすべて、VCF環境の外側に位置する「esx03」上に仮想マシンとしてデプロイします。
VCFのBring-up(自動展開)プロセス中、インフラのコアとなるDNSやルーターが再起動したり、リソース不足で応答遅延を起こしたりすると致命的なエラーに繋がるため、管理系コンポーネントを物理的に切り離しておくことは非常に重要です。

VyOSによるアンダーレイネットワークの構築

VCF環境では、管理(Management)、vMotion、vSAN、NSX TEPなど、複数のネットワークセグメント(VLAN)が交差します。これらをルーティングし、外部ネットワークと繋ぐためのゲートウェイとして、軽量でCLI操作に優れた「VyOS」を採用します。

VyOSのネットワークインターフェース設計

VyOSの仮想マシンには、複数のネットワークインターフェース(vNIC)を割り当てるか、1つのトランクポートを割り当ててVyOS内部でVIF(VLANインターフェース)を切る構成にします。今回は管理をシンプルにするため、後者のVLANトランク構成を採用しています。


主なインターフェースとVLANの割り当ては以下の通りです。

eth0:アップリンク(既存の家庭内LAN・インターネットへ接続)
eth1.10:管理ネットワーク(VLAN 10 / vCenterやSDDC Managerが配置されるセグメント)
eth1.11:vMotionネットワーク(VLAN 11)
eth1.12:NSX TEPネットワーク(VLAN 12)
eth1.13:vSANネットワーク(VLAN 13)

interfaces {
    ethernet eth0 {
        address 192.168.10.1/24
        hw-id 00:0c:29:c8:e4:48
        offload {
            gro
            gso
            sg
            tso
        }
    }
    ethernet eth1 {
        hw-id 00:0c:29:c8:e4:52
        mtu 9000
        offload {
            gro
            gso
            sg
            tso
        }
        vif 11 {
            address 192.168.11.1/24
            description vMotion
            mtu 9000
        }
        vif 12 {
            address 192.168.12.1/24
            description vSAN
            mtu 9000
        }
        vif 13 {
            address 192.168.13.1/24
            description Host-TEP
            mtu 9000
        }
        vif 14 {
            address 192.168.14.1/24
            description Edge-TEP
            mtu 9000
        }
        vif 20 {
            address 192.168.20.1/24
            description Transit-to-C2960
            mtu 1500
        }
    }
    loopback lo {
    }
}

MTU 9000(ジャンボフレーム)の徹底

NSXのオーバーレイ通信(TEP)やvSANの同期トラフィックを通すため、関連するインターフェースのMTUは必ず「9000」に設定します。
以下は、VyOSにおける設定コマンドの抜粋です。

インターフェースとVLANの設定(例:管理とTEP)
set interfaces ethernet eth1 vif 10 address '192.168.10.1/24'
set interfaces ethernet eth1 vif 10 description 'VCF-Management'

set interfaces ethernet eth1 vif 12 address '192.168.12.1/24'
set interfaces ethernet eth1 vif 12 description 'VCF-TEP'
set interfaces ethernet eth1 vif 12 mtu '9000'

スタティックルーティングの設定(デフォルトゲートウェイを上位ルーターへ)
set protocols static route 0.0.0.0/0 next-hop 192.168.0.1
commit
save

設定完了後、VyOSから各セグメントに対してping(必要に応じてサイズを指定したping)を実行し、疎通とMTU 9000が正しく通ることを確実にチェックしておきます。

AD/DNS/NTPサーバー(dc01)の構築

続いて、VCF環境の「名前解決」と「時刻同期」の要となるWindows Server(dc01)を構築します。
VCFのBring-upにおいて、最もつまずきやすいのがこのDNSとNTPの設定です。

DNS設定:正引き・逆引きの完全一致が絶対条件

VCF(SDDC Manager)は、デプロイ対象の全コンポーネント(ESXi、vCenter、NSX Managerなど)に対して、DNSの正引き(Aレコード)と逆引き(PTRレコード)が完全に一致するかを厳密にチェックします。1つでも登録漏れや不一致があると、事前検証(Validation)の段階で弾かれます。

AD/DNSのインストールとフォレスト作成(vcf.local)が完了している前提で、以下の「vcf.local」というドメインを作成し、以下のレコードを登録しました。

1. 逆引き参照ゾーンの作成

VCFは逆引き(IPからホスト名を解決)ができないと即座にエラーになります。まずは 192.168.10.x の逆引きゾーンを作成します。
dc01 のデスクトップ画面左下のスタートボタンを右クリックし、**「Windows PowerShell (管理者)」**を起動して、以下のコマンドを順番に実行してください。

Add-DnsServerPrimaryZone -NetworkId "192.168.10.0/24" -ReplicationScope "Forest"
2. レコードの一括登録(Aレコード & PTRレコード)

今回作成したIPマトリクスを一気に登録します。
以下のブロックをすべてコピーして、PowerShellに貼り付けて実行してください。
(CreatePtr オプションにより、逆引きレコードも同時に自動作成されます)

$domain = "vcf.local"
$records = @(
    @{ Name="vyos"; IP="192.168.10.1" },
    @{ Name="sks3200"; IP="192.168.10.2" },
    @{ Name="dc01"; IP="192.168.10.11" },
    @{ Name="cb"; IP="192.168.10.12" },
    @{ Name="esx-n01"; IP="192.168.10.21" },
    @{ Name="esx-n02"; IP="192.168.10.22" },
    @{ Name="esx-n03"; IP="192.168.10.23" },
    @{ Name="esx-n04"; IP="192.168.10.24" },
    @{ Name="vc"; IP="192.168.10.31" },
    @{ Name="sddc"; IP="192.168.10.32" },
    @{ Name="nsx"; IP="192.168.10.33" },
    @{ Name="vrops"; IP="192.168.10.34" },
    @{ Name="fleet"; IP="192.168.10.35" },
    @{ Name="collector"; IP="192.168.10.36" },
    @{ Name="nsx-manager01"; IP="192.168.10.39" },
    @{ Name="esx01"; IP="192.168.10.101" },
    @{ Name="esx02"; IP="192.168.10.102" },
    @{ Name="esx03"; IP="192.168.10.103" }
)
foreach ($record in $records) {
    Add-DnsServerResourceRecordA -Name $record.Name -ZoneName $domain -IPv4Address $record.IP -CreatePtr
    Write-Host "$($record.Name).$domain ($($record.IP)) を登録しました。" -ForegroundColor Green
}
3. 登録の確認テスト

登録が完璧か、試しに nslookup で確認してみましょう。
以下のコマンドを実行して、正しく名前とIPが返ってくれば大成功です。

PS C:\>
PS C:\> nslookup vc.vcf.local 192.168.10.11
サーバー:  dc01.vcf.local
Address:  192.168.10.11

名前:    vc.vcf.local
Address:  192.168.10.31

PS C:\>
PS C:\> nslookup 192.168.10.31 192.168.10.11
サーバー:  dc01.vcf.local
Address:  192.168.10.11

名前:    vc.vcf.local
Address:  192.168.10.31

PS C:\>

DNSマネージャー画面で正しく登録されていることも確認できました。

  • Windows ServerのDNSマネージャー画面(正引き参照ゾーン)


  • Windows ServerのDNSマネージャー画面(逆引き参照ゾーン)


NTP(時刻同期)の構成

仮想化基盤において時刻のズレは、認証エラーやログの不整合、最悪の場合はクラスタの通信断を引き起こします。VCFではNTPサーバーの指定が必須となります。
今回は、構築したADドメインコントローラー(dc01)をNTPサーバーとして稼働させ、VCFの全コンポーネント(ESXi、vCenter、NSX等)がこのdc01を参照するように統一します。

引き続きPowerShell(管理者)で実行します。
※ここでは日本の標準的なNTPであるNICT (ntp.nict.jp) と同期させます。

# Windows Time サービスを停止
net stop w32time

# 外部NTPサーバー(NICT)を指定して設定
w32tm /config /syncfromflags:manual /manualpeerlist:"ntp.nict.jp,0x9" /reliable:yes /update

# サービスを再起動
net start w32time

# 即座に同期を実行
w32tm /resync /rediscover

# 同期状態の確認(Source が ntp.nict.jp になっていればOK)
PS C:\> w32tm /query /status
閏インジケーター: 0 (警告なし)
階層: 4 (二次参照 - (S)NTP で同期)
精度: -23 (ティックごとに 119.209ns)
ルート遅延: 0.0121445s
ルート分散: 7.9069369s
参照 ID: 0x142B5EC7 (ソース IP:  20.43.94.199)
最終正常同期時刻: 2026/05/06 1:06:37
ソース: time.windows.com,0x9
ポーリング間隔: 10 (1024s)

PS C:\>

踏み台PCからの確認(Validation事前チェック)

VyOSとDNS/NTPの構築が完了したら、作業用PC(踏み台)から最終チェックを行います。
作業用PCのネットワーク設定で、DNSサーバーを今回構築した「dc01」に向けた上で、コマンドプロンプトから以下の確認を実施します。

ルーティングの確認:管理ネットワーク(192.168.10.x)宛にpingが通るか。
名前解決の確認:nslookup esx-n01.vcf.local(正引き)および nslookup 192.168.10.21(逆引き)の両方で正しい結果が返ってくるか。
これらが完璧に通れば、VCFを迎えるためのアンダーレイ環境は完成です。

PS C:\>
PS C:\> ping 192.168.10.1

192.168.10.1 に ping を送信しています 32 バイトのデータ:
192.168.10.1 からの応答: バイト数 =32 時間 =2ms TTL=63
192.168.10.1 からの応答: バイト数 =32 時間 =3ms TTL=63
192.168.10.1 からの応答: バイト数 =32 時間 =3ms TTL=63
192.168.10.1 からの応答: バイト数 =32 時間 =3ms TTL=63

192.168.10.1 の ping 統計:
    パケット数: 送信 = 4、受信 = 4、損失 = 0 (0% の損失)、
ラウンド トリップの概算時間 (ミリ秒):
    最小 = 2ms、最大 = 3ms、平均 = 2ms
PS C:\>
PS C:\> nslookup esx-n01.vcf.local 192.168.10.11
サーバー:  dc01.vcf.local
Address:  192.168.10.11

名前:    esx-n01.vcf.local
Address:  192.168.10.21

PS C:\>
PS C:\> nslookup 192.168.10.21 192.168.10.11
サーバー:  dc01.vcf.local
Address:  192.168.10.11

名前:    esx-n01.vcf.local
Address:  192.168.10.21

PS C:\>

今後の展開

ルーティング、名前解決、時刻同期というインフラの「三大要件」がesx03上に整いました。
これでようやく、主役であるVCF基盤(esx01, esx02)のセットアップに取り掛かることができます。

次回は、基盤となる物理ESXiホストのネットワーク設定(分散仮想スイッチの構成)と、いよいよVCF展開の司令塔となる「Cloud Builder」アプライアンスのデプロイ、およびJSONパラメータの作成について解説します。

【VCF 9.0構築】自宅でプライベートクラウド構築。最小ホームラボアーキテクチャとリソース要件(第1回)

自宅のホームラボをVMware Cloud Foundation(VCF)9.0へ刷新していこうと思います。
Broadcomによるポートフォリオの統合により、インフラのデファクトスタンダードは単体のvSphereからVCFを中心としたプライベートクラウド基盤へと完全にシフトしました。vExpert向けに提供される検証用ライセンスもVCFの知識と資格取得が前提となるなど、状況は大きく変わっています。
これに伴い、これまで長年運用してきた「ESXiとvCenterを個別に構築する」旧来の環境を破棄し、新たにVCF環境を構築することとしました。

ここでは、ハードウェアリソースが限られるホームラボ環境において、Nested(入れ子)技術を利用し、最小リソースでVCF 9.0をデプロイするための設計と要件をまとめます。

VCF 9.0 のデプロイモデルとアーキテクチャ

VCFの最大の特徴は、インフラの展開(Bring-up)とライフサイクル管理(LCM)を自動化する「SDDC Manager」の存在にあります。従来はエンジニアが手作業で構築していたコンポーネント群を、SDDC ManagerがBroadcomのベストプラクティス(VVD)に則って自動構成します。

このVCFの厳格なアーキテクチャを理解する上で、Broadcomの公式ドキュメント(Design Guide)は必読です。本環境の設計にあたり、以下の公式ドキュメントの「Design Blueprints」および「Architecture Models」の概念を全面的に引用・リファレンスとしています。

参考・引用元:VMware Cloud Foundation Design (Broadcom TechDocs)

公式ドキュメントにおいて、単一サイトにおけるVCFの展開アーキテクチャは、大きく分けて以下の2つのモデルが定義されています。

標準アーキテクチャ(Standard Architecture)

公式のブループリント「VCF Fleet in a Single Site」に該当する、エンタープライズにおける本来のベストプラクティス構成です。

SDDC Manager、vCenter、NSX Managerといった管理コンポーネントが稼働する「Management Domain(管理ドメイン)」と、実際のユーザー業務VMが稼働する「VI Workload Domain(ワークロードドメイン)」を、物理ホストレベルで完全に分離します。ドメインごとにvCenterが独立して稼働(スケールアウト)する形となります。
高い可用性とセキュリティを担保できる反面、それぞれのドメインで複数台の物理ホストを要求するため、個人ラボでこの構成を初手から組むのは極めて困難です。

統合アーキテクチャ(Consolidated Architecture)

公式のブループリント「VCF Fleet in a Single Site with Minimal Footprint」に該当する、VCFの最小構成モデルです。

専用のワークロードドメインを別途展開せず、「Management Domain」のリソースプール(クラスタ)内に、ユーザーの業務VMやNSX Edgeなどを同居させます。管理と業務の分離は、物理ホストではなく「リソースプール」という論理的な枠組みで行われます。

物理ホストを新たに分割・追加する必要がないため、限られたハードウェアリソースでVCFの挙動やワークロードの通信検証を行うホームラボにおいては、この構成が最適解となります。今回はこの「統合アーキテクチャ」を採用し、デプロイを進めていきます。

最小ホームラボの全体構成(Topology)

論理構成

上記を踏まえ、今回構築するVCF 9.0環境の論理アーキテクチャは以下の通りとなります。

物理構成

VCFを支える物理ホストの構成は以下の通りです。

物理ホストの役割分離(3台構成)

VCF展開時のリソース枯渇によるBring-upの失敗を防ぐため、物理ホストの役割を基盤用と管理用に分割しました。
実は、VCF基盤用ホスト1台構成で構築は完了したのですが、リソース不足によりvCenterが動作せず、2台構成にしています。

  • esx01 / esx02(VCF基盤用):VCFの土台です。この2台の上で、Management Domainを構成するNested ESXi(esx-n01〜n04)を計4台稼働させます。
  • esx03(外部管理基盤用):VCFコンポーネントの外側から基盤を支えるAD/DNS/NTP(dc01)、ルーター(vyos)、およびデプロイメントの司令塔となるVCFインストーラーを稼働させます。
ホスト名 モデル CPU メモリ データストア 役割
esx01 HP ProDesk 600 G6 SFF 8 CPUs (i7-10700 2.90GHz) 128 GB NVMe SSD (500 GB) vSANキャッシュ層(NVMeでI/O高速化)
SATA SSD (1TB) vSANキャパシティ層 兼 物理ESXi用/スワップ退避先
esx02 HP ProDesk 600 G6 SFF 8 CPUs (i7-10700 2.90GHz) 128 GB NVMe SSD (500 GB) vSANキャッシュ層(NVMeでI/O高速化)
SATA SSD (2TB) vSANキャパシティ層 兼 物理ESXi用/スワップ退避先
esx03 HP ProDesk 600 G5 SFF 8 CPUs (i7-9700 3.00GHz) 64 GB SATA SSD (1TB) 外部管理基盤用

ネットワーク機器(10G化の足回り)

VCF環境(特にvSANの同期やNSXのオーバーレイ通信)において、従来の1Gbpsネットワークでは帯域もMTUも圧倒的に不足します。そこで今回は、コストと発熱を抑えつつ、VCFの要件であるジャンボフレーム(MTU 9000)を捌ける物理ネットワーク機器を導入しました。

10G SFP+対応NIC(Mellanox ConnectX-4 Lx)

esx01とesx02に搭載している25GbE対応のNICです。ESXi標準のInboxドライバで即認識され、vSANの高速化(RDMA)やNSXのオフロード処理にも対応しているため、VCFホームラボにおいて非常に優秀なパフォーマンスを発揮します。UTPではなくSFP+モデルを選ぶことで、スリムPC特有の熱暴走リスクも回避しています。
このNICは2ポートタイプですが、1ポートタイプでも問題ありません。
私は、この後2ポート接続による冗長化を行いたかったので、2ポート製品を選択しています。
Amazon | Vogzone 25GbE NICカード Mellanox MCX4121A-ACAT、PCIe 3.0 x8 25GbイーサネットNIC、Mellanox ConnectX-4 Lxチップセット付き、デュアルSFP28ネットワークカード RDMA対応 | Vogzone | ネットワークカード 通販

10G DACケーブル

ホストとスイッチ間を接続するためのDAC(Direct Attach Copper)ケーブルです。SFP+モジュールと光ファイバーを別々に購入するよりも圧倒的に安価で、ホームラボのような短距離接続における10G化の最適解です。
Amazon.co.jp: 10Gtek 10G SFP+ ケーブル, SFP+ DAC Twinaxケーブル, Cisco SFP-H10GB-CU2M、 Meraki、Ubiquiti UniFi、Mikrotik、Intel、Fortinet、 Netgear、Arista、D-Link、Supermicro、TP-Linkなど互換, 2m, グリーン : パソコン・周辺機器

10G/1G ブリッジスイッチ(SKS3200-4E2X)

10Gネットワークと既存の1Gネットワーク(外部接続用)を橋渡しするための要となるスイッチです。非常に安価ながら、VCFのアンダーレイ通信に必須となる「VLANタグのハンドリング」と「ジャンボフレーム(MTU 9000)の透過」をサポートしています。
Amazon.co.jp: XikeStor 6ポート WEB管理スイッチ (4×2.5G + 2×10G SFP+)搭載 全新 2.5gbps スイッチングハブアルミニウム合金製ケース 放熱性能 ファンレスで静音 小型 家庭向け 企業向け | SKS3200-4E2X : パソコン・周辺機器

アンダーレイネットワーク接続

VCFが要求するアンダーレイネットワークのルーティングは、VyOSをGWとして構成します。
初期構築段階では検証の複雑さを排除するため、BGPは使用せずスタティックルーティングで確実な足回りを固めるスモールスタートとします。

VCFデプロイに向けたシビアなリソース要件

SDDC Managerによるデプロイを完走させるためには、Nested ESXiに対する厳密なリソース割り当てとストレージの使い分けが必須となります。

Nested ESXiのメモリ割り当てと「全予約」の罠

VCFのManagement Domainは、初期構成の管理コンポーネント群だけで莫大なメモリを消費します。
一般的に、Nested ESXiを稼働させる際はパフォーマンス低下を防ぐために「すべてのゲストメモリを予約」することが推奨されます。しかし、今回の環境(物理メモリ128GB)で、64GBのNested ESXiを2台「全予約」してしまうと、物理ESXi自身のカーネルやvSANの処理に必要なメモリが枯渇し、システムがダウン(またはパワーオン不可)してしまいます。

そのため今回はあえて「メモリの全予約は行わない(オーバーコミットを許容する)」という、ホームラボならではのリソース運用を行っています。

全予約を行わない場合、仮想マシンの起動時にメモリと同容量の巨大なスワップファイル(.vswp)が作成されますが、ここで前述した「SATA SSD」が活きてきます。この巨大なスワップファイルを低速でも容量に余裕のあるSATA側へ逃がすことで、VCFの要であるNVMe(vSAN)の容量とI/Oを守り抜く設計としています。

ホスト名 vCPU メモリ データストア ネットワーク
esx-n01 〜 n04 8 vCPU 64 GB(※予約なし) mgmt-domain-cl01-ds-vsan01(※4台で1つのvSANを形成) VDS (複数VLANトランク)

vSAN要件を満たすNVMeとSATAの分離

VCFの必須要件であるハイブリッドvSANをNested環境で構成するため、物理ディスクの役割を以下のように完全に分離します。

  • NVMe(500GB):vSANのキャッシュ層として全容量を割り当てます。
  • SATA(1TB / 2TB):vSANのキャパシティ層、物理ESXi自身のOS領域、および万が一のシステムスワップ退避先として使用します。

OSの動作など速度が不要な領域をSATAへ逃がし、高速なNVMeを100% vSANのオーバーヘッド処理に専有させることで、デプロイ時のストレージ窒息を回避します。
キャパシティ層もすべてNVMeで構成(オールフラッシュvSAN)したかったのですが、500GBの容量ですべてを賄おうとした結果、デプロイ途中で容量不足エラーに陥ってしまったため、今回のハイブリッド構成に着地しました。

VCF 管理コンポーネントのリソース消費量(参考)

参考までに、統合アーキテクチャ上で稼働する管理コンポーネント(仮想マシン)のリソース一覧です。これらが起動するだけで、クラスタ全体で「22 vCPU / 90 GBメモリ」が常時消費されます。

仮想マシン名 役割 vCPU メモリ
vc vCenter Server 2 vCPU 14 GB
sddc SDDC Manager 4 vCPU 16 GB
nsx-manager01 NSX Manager 6 vCPU 24 GB
vrops Aria Operations 2 vCPU 8 GB
fleet Aria関連コンポーネント 4 vCPU 12 GB
collector Aria関連コンポーネント 4 vCPU 16 GB

今後の展開

本環境における第一目標は、Management Domainの展開後、統合ドメイン上にNSX Edgeをデプロイし、オーバーレイネットワーク上の仮想マシンを外部ネットワークと通信させることです。
次回は、このVCFデプロイを成功させるための具体的な事前準備として、VyOSによるルーティング設定と、AD/DNS/NTP環境の構築手順を解説します。

【第2弾】Google Forms × GAS × GitLabでFW通信許可申請を完全自動化してみた

今回は、前回の「Google Cloudプロジェクト作成の自動化」に続き、開発チームから頻繁に依頼が来る「ファイアウォールインバウンド通信許可申請」の仕組みを完全自動化したお話です。

これまでは手作業で申請内容をレビューし、Terraformのコードを書いてGitLabへPushしていましたが、運用担当者の負荷を削減するため、GAS(Google Apps Script)を利用した自動化パイプラインを構築しました。

結果として、「フォームに入力するだけで、GitLabにTerraformのコードが自動生成され、MR(マージリクエスト)が飛んでくる」というインフラ基盤が完成しました。外部API(Google Cloudの権限など)に依存せず、スプレッドシートをデータベースとして活用する、絶対に壊れない堅牢な構成になっています。

今回はそのアーキテクチャの全体像と、構築の過程でハマったエラーとその解決策を備忘録としてまとめていきます。

自動化の全体アーキテクチャ

まずは今回の仕組みの構成図です。申請者がフォームに入力してからFWルールがデプロイされるまでの流れは以下のようになっています。

申請者

Google Formsから「対象プロジェクト」「ネットワーク」「送信元IP」「TCP/UDPポート番号」などを入力します。

Google Sheets / GAS

フォームの回答をトリガーにGASが起動します。過去の申請履歴から連番(例: fw-gctestpj01-01)を自動計算し、Terraform(HCL)のコードを動的に生成してGitLabのAPIを叩きます。

GitLab

リポジトリ上の「managed_firewall.tf」を更新するMRが作成されます。

インフラ管理者

MRの内容をレビューしてマージ(Approve)します。

Terraform Cloud

CI/CDパイプラインが走り、GCP上にFWルールが自動生成されます。

構築のステップ(大まかな作業の流れ)

実際にこの自動化パイプラインを構築する手順を、順番に解説していきます。
大まかな作業の流れは以下の4ステップです。

  • Googleフォームとスプレッドシートの作成(申請の入り口とデータベース作り)
  • GitLabトークンとプロジェクトIDの取得(GASから操作するための鍵)
  • GAS(Apps Script)の作成と設定(HCLを自動生成するロジック)
  • 自動実行のトリガー設定(フォーム送信をフックにするスイッチ)

Step 1: Googleフォームとスプレッドシートの作成

まずは申請者が入力するGoogleフォームを作成します。今回は以下の項目を設定しました。

  • プロジェクト名 (短文回答)
  • ネットワーク (短文回答)
  • 優先度 (短文回答)
  • ターゲット (ラジオボタン: ネットワーク上のすべてのインスタンス / 指定されたターゲットタグ)
  • ターゲットタグ (短文回答)
  • 送信元IP範囲 (短文回答 / 例: 172.16.1.0/24)
  • 【TCP】送信先ポート番号 (短文回答 / 例: 22, 80, 443)
  • 【UDP】送信先ポート番号 (短文回答 / 例: 514)


フォームができたら「回答」タブから「Google スプレッドシートにリンク」をクリックし、データを受け止めるスプレッドシートを作成します。
ここで極めて重要なポイントとして、作成したスプレッドシートの1行目(ヘッダー行)の一番右に、GASがシステム的に利用する以下の2つの列を手動で追加しておきます。

  • 生成ルール名
  • 処理ステータス

Step 2: GitLabのトークンとプロジェクトIDの取得

GASがGitLabのAPIを叩いてMRを作成できるように準備を行います。

1. パーソナルアクセストークンの発行
GitLabのユーザーアイコンから「Access Tokens」を開き、スコープ「api」「write_repository」にチェックを入れてトークンを発行します。

2. プロジェクトIDの確認
対象のGitLabリポジトリのトップページに行き、プロジェクト名の下に記載されている「プロジェクトID(数字)」をコピーしておきます。

Step 3: GAS(Apps Script)の作成

スプレッドシートの画面に戻り、上部メニューの「拡張機能」>「Apps Script」をクリックしてエディタを開きます。
ここに、スプレッドシートのデータを読み取ってTerraform(HCL)の文字列に変換するコードを記述します。

コードを貼り付けたら、上部の CONFIG 部分にシート名、GITLAB_CONFIG 部分に取得したトークンとプロジェクトIDを設定して保存します。

実装したGASのコード

スプレッドシートの裏側で動いているGASの主要なロジックです。TCPとUDPの複数ポート(カンマ区切り)にも対応し、既存の「完了」ステータスのルールをすべて結合してGitLabへPushする宣言的アプローチになっています。

/**
 * ==============================================================================
 * ツール名: FW Rule Auto-Generator (Ultra Simple Version)
 * 概要: フォームの入力値をそのままHCLに変換し、GitLabへMRを作成する(TCP/UDP両対応版)
 * ==============================================================================
 */

const CONFIG = {
  SHEET_NAME: 'FWルール許可申請_インバウンド', // ★実際のタブ名に合わせて変更
  COLUMNS: {
    PROJECT_ID: 'プロジェクト名',
    NETWORK: 'ネットワーク',
    PRIORITY: '優先度',
    TARGET_TYPE: 'ターゲット',
    TARGET_TAGS: 'ターゲットタグ',
    SOURCE_IPS: '送信元IP範囲',
    PORT_TCP: '【TCP】', 
    PORT_UDP: '【UDP】',
    RULE_NAME: '生成ルール名',
    STATUS: '処理ステータス'
  }
};

const GITLAB_CONFIG = {
  API_URL: 'https://gitlab.com/api/v4',
  PROJECT_ID: 'xxxxxxxx', // ★GitLabのプロジェクトID
  TOKEN: 'xxxxxxxxxxxxx', // ★有効なGitLabトークン
  BASE_BRANCH: 'main',
  FILE_PATH: 'managed_firewall.tf'
};

function onFormSubmitSimple() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName(CONFIG.SHEET_NAME);
  if(!sheet) throw new Error(`シート「${CONFIG.SHEET_NAME}」が見つかりません。`);
  
  const dataRange = sheet.getDataRange();
  const values = dataRange.getDisplayValues();
  const headers = values[0];
  
  const getColIdx = (keyword) => {
    let idx = headers.findIndex(h => h.trim() === keyword);
    if (idx === -1) idx = headers.findIndex(h => h.includes(keyword));
    return idx;
  };

  const idx = {
    proj: getColIdx(CONFIG.COLUMNS.PROJECT_ID),
    nw: getColIdx(CONFIG.COLUMNS.NETWORK),
    prio: getColIdx(CONFIG.COLUMNS.PRIORITY),
    tgtType: getColIdx(CONFIG.COLUMNS.TARGET_TYPE),
    tgtTags: getColIdx(CONFIG.COLUMNS.TARGET_TAGS),
    srcIps: getColIdx(CONFIG.COLUMNS.SOURCE_IPS),
    tcp: getColIdx(CONFIG.COLUMNS.PORT_TCP),
    udp: getColIdx(CONFIG.COLUMNS.PORT_UDP),
    rule: getColIdx(CONFIG.COLUMNS.RULE_NAME),
    status: getColIdx(CONFIG.COLUMNS.STATUS)
  };

  if (idx.proj === -1 || idx.rule === -1 || idx.status === -1) return;

  let isNewDataProcessed = false;
  let latestRuleName = "update-fw-rules";

  // --- 1. 新規データの採番とステータス更新 ---
  for (let i = 1; i < values.length; i++) {
    const projectId = values[i][idx.proj];
    const currentStatus = values[i][idx.status];

    if (projectId !== '' && currentStatus === '') {
      isNewDataProcessed = true;
      let count = 0;
      for (let j = 1; j < values.length; j++) {
        if (values[j][idx.proj] === projectId && values[j][idx.rule] !== '') count++;
      }
      
      const serial = String(count + 1).padStart(2, '0');
      const ruleName = `fw-${projectId}-${serial}`;
      latestRuleName = ruleName;

      sheet.getRange(i + 1, idx.rule + 1).setValue(ruleName);
      sheet.getRange(i + 1, idx.status + 1).setValue('完了');
      values[i][idx.rule] = ruleName;
      values[i][idx.status] = '完了';
    }
  }

  if (!isNewDataProcessed) return;

  // --- 2. 全「完了」データからHCLを再構築 ---
  let allTerraformCode = "";

  for (let i = 1; i < values.length; i++) {
    const row = values[i];
    if (row[idx.rule] !== '' && row[idx.status] === '完了') {
      const parsePorts = (input) => {
        if (!input) return [];
        return String(input).split(/[,\n、]+/).map(p => `"${p.trim()}"`).filter(p => p !== '""');
      };

      let targetTags = [];
      if (row[idx.tgtType].includes('指定されたターゲットタグ') && row[idx.tgtTags]) {
        targetTags = row[idx.tgtTags].split(/[,\n、]+/).map(p => `"${p.trim()}"`).filter(p => p !== '""');
      }

      const fwData = {
        ruleName: row[idx.rule],
        projectId: row[idx.proj],
        networkLink: `"${row[idx.nw]}"`,
        priority: row[idx.prio] || "1000",
        tcpPorts: parsePorts(row[idx.tcp]),
        udpPorts: parsePorts(row[idx.udp]),
        sourceIps: row[idx.srcIps].split(/[,\n、]+/).map(p => `"${p.trim()}"`).filter(p => p !== '""'),
        targetTags: targetTags,
        description: "自動申請フォームからの作成"
      };

      allTerraformCode += generateFwResourceHcl(fwData);
    }
  }

  // --- 3. GitLabへMR作成 ---
  createGitLabMrFlow(allTerraformCode, latestRuleName);
}

function generateFwResourceHcl(fw) {
  const resourceName = fw.ruleName.replace(/-/g, '_');
  let hcl = `\n# --- FWルール: ${fw.ruleName} ---\n`;
  hcl += `resource "google_compute_firewall" "managed_fw_${resourceName}" {\n`;
  hcl += `  project       = "${fw.projectId}"\n`;
  hcl += `  name          = "${fw.ruleName}"\n`;
  hcl += `  description   = "${fw.description}"\n`;
  hcl += `  network       = ${fw.networkLink}\n`;
  hcl += `  direction     = "INGRESS"\n`;
  hcl += `  priority      = ${fw.priority}\n\n`;

  if (fw.sourceIps.length > 0) hcl += `  source_ranges = [${fw.sourceIps.join(', ')}]\n`;
  if (fw.targetTags.length > 0) hcl += `  target_tags   = [${fw.targetTags.join(', ')}]\n`;

  if (fw.tcpPorts.length > 0) {
    hcl += `\n  allow {\n`;
    hcl += `    protocol = "tcp"\n`;
    hcl += `    ports    = [${fw.tcpPorts.join(', ')}]\n`;
    hcl += `  }\n`;
  }

  if (fw.udpPorts.length > 0) {
    hcl += `\n  allow {\n`;
    hcl += `    protocol = "udp"\n`;
    hcl += `    ports    = [${fw.udpPorts.join(', ')}]\n`;
    hcl += `  }\n`;
  }

  hcl += `}\n`;
  return hcl;
}

function createGitLabMrFlow(fileContent, ruleName) {
  const timestamp = Utilities.formatDate(new Date(), 'Asia/Tokyo', 'yyyyMMdd-HHmmss');
  const newBranchName = `feature/fw-${ruleName}-${timestamp}`;
  try {
    createBranch(newBranchName, GITLAB_CONFIG.BASE_BRANCH);
    const action = fileExists(GITLAB_CONFIG.FILE_PATH, newBranchName) ? 'update' : 'create';
    createCommit(newBranchName, GITLAB_CONFIG.FILE_PATH, fileContent, action);
  } catch (e) {
    console.error(`GitLab連携エラー: ${e.toString()}`);
  }
}

function callGitLabApi(method, endpoint, payload = null) {
  const url = `${GITLAB_CONFIG.API_URL}/projects/${GITLAB_CONFIG.PROJECT_ID}${endpoint}`;
  const options = { method, headers: { 'PRIVATE-TOKEN': GITLAB_CONFIG.TOKEN, 'Content-Type': 'application/json' }, muteHttpExceptions: true };
  if (payload) options.payload = JSON.stringify(payload);
  const response = UrlFetchApp.fetch(url, options);
  if (response.getResponseCode() >= 400) throw new Error(response.getContentText());
  return JSON.parse(response.getContentText());
}
function createBranch(branch, ref) { return callGitLabApi('post', `/repository/branches?branch=${branch}&ref=${ref}`); }
function fileExists(filePath, branch) { try { callGitLabApi('get', `/repository/files/${encodeURIComponent(filePath)}?ref=${branch}`); return true; } catch (e) { return false; } }
function createCommit(branch, filePath, content, action) { return callGitLabApi('post', '/repository/commits', { branch, commit_message: `Update ${GITLAB_CONFIG.FILE_PATH}`, actions: [{ action, file_path: filePath, content }] }); }
function createMergeRequest(sourceBranch, targetBranch, title) { return callGitLabApi('post', '/merge_requests', { source_branch: sourceBranch, target_branch: targetBranch, title }).web_url; }

Step 4: 完全自動化のためのトリガー設定

最後に、フォームが送信された瞬間にスクリプトが動くようにトリガーを設定します。
GASエディタの左メニューにある時計マーク(トリガー)から以下のように設定します。

  • 実行する関数: onFormSubmitSimple
  • イベントのソース: スプレッドシートから
  • イベントの種類: フォーム送信時


動作確認

それでは、実際に動作を確認していきましょう。

フォーム入力

フォームから必要な情報を入力し、送信をクリックします。TCPとUDPを両方入れたり、カンマ区切りで複数ポートを入れることも可能です。


スプレッドシートの確認

送信後、スプレッドシートを見ると自動的に「fw-gctestpj01-01」のような連番が振られ、ステータスが「完了」になります。

マージリクエスト確認と承認

数秒後、GitLabにマージリクエストが作成されています。TCPとUDPのブロックが綺麗に分かれて生成されていることが確認できます。問題がなければマージします。

リソースの作成

マージするとTerraform Cloud は自動的にPlan と Applyを実行しリソースを作成します。
FWルールを確認すると申請通りFWルールが作成されていることを確認できました。

構築でハマった4つの落とし穴と解決策

理屈はシンプルですが、いざ実装して動かしてみるとIaC特有の仕様の壁にぶつかりました。トラブルシューティングとして残しておきます。

1. GCP APIへの依存による権限エラーとキャッシュ問題

当初、フォームの選択肢を動的にするためGASからGCPのCompute APIを叩き、VPCネットワーク一覧を取得しようとしました。しかし、GASの実行権限や組織ポリシーの壁、さらにAPI結果のキャッシュ遅延によって「正しいはずなのに動かない」という事態に陥りました。

解決策: 外部API依存の完全排除

思い切ってGAS側でのGCP API連携を全て廃止しました。スプレッドシートを「正となるデータベース」として扱い、フォームの入力値をそのままHCL化するだけの処理(純度100%のテキスト変換)に徹することで、絶対に壊れない堅牢な仕組みになりました。

2. GitLab APIの「上書き」仕様によるルールの消失

2つ目のFWルール申請が来た際、GASからGitLabへファイルの更新リクエストを送ったところ、元々あった1つ目のルールのコードが消え、2つ目のルールだけに「上書き」されてしまう事象が発生しました。GitLab APIの `update` アクションはファイルの末尾追記ではなく「ファイル全体の上書き」を行う仕様でした。

解決策: IaCの宣言的アプローチへの転換

GASのロジックを根本から変更しました。新しく来た申請データだけを送るのではなく、「スプレッドシートにある過去の全完了データをループで読み込み、すべてのFWルールのHCLを結合して1つの巨大なコードを作り、それでGitLabを丸ごと上書きする」という宣言的なアプローチにしました。

3. 複数申請時のルール名の連番化と重複

FWルール名を `fw-プロジェクト名-01` のように連番にしたかったのですが、申請が同時に複数来た場合、GASが同じ番号を採番してしまいデプロイ時にリソース名重複エラーが発生するリスクがありました。

解決策: スプレッドシートを利用したロック制御

「処理ステータス」列をロックフラグとして活用しました。GASが起動した際、まずはステータスが空欄の行を見つけます。連番を決定したら、MRを作成する前に即座にシートへルール名と「完了」の文字を書き込みます。この状態管理により、連番のズレを防ぎました。

4. TCPとUDPの複数ポート指定のHCL組み立て

フォームで `80, 443` のようにカンマ区切りで複数ポートが入力された場合、Terraformの allow ブロックを動的に、かつ正確に生成する必要がありました。

解決策: 入力項目を分け、配列処理でブロックを生成

フォームの入力欄をTCPとUDPに分離し、GAS側では文字列を `split` して配列化。配列の要素数が1以上の場合のみ、それぞれ独立した allow ブロックをHCLとして追記するロジックを実装しました。

Tips(Single Source of Truthの考え方)

このシステムでは、「Googleフォーム/スプレッドシートはあくまで起票ツール」とし、「GitLab上のコードをインフラの正本」とする役割分担を徹底しています。

そのため、作成済みのFWルールを修正・削除したい場合は、スプレッドシートを直接いじるのではなく、GitLabのコード(managed_firewall.tf)を直接修正してMRを出すのが正しい運用です。スプレッドシートをマスターにしてしまうと、手動での緊急修正などが先祖返りで消えてしまうリスクがあるため、GitOpsのベストプラクティスに従うのが安全です。

おわりに

数々の仕様の壁にぶつかりましたが、結果としてセキュアで確実なインフラ払い出しのパイプラインを構築することができました。

GASを使うことで、スプレッドシートを簡易的な状態管理データベースとして使いつつ、GitLabとの連携もシームレスに行えるのが非常に強力です。
運用担当者が日々繰り返し実施しているような作業は、ほぼGoogleフォーム、スプレッドシート、GASを利用することで自動化できるので、皆さんいろいろチャレンジしてみてください!