電通総研 テックブログ

電通総研が運営する技術ブログ

UEアプリ用のカスタムWindows Node AMIを作成する 後編

こんにちは、金融ソリューション事業部の孫です。
前編では、Unreal Editorを含むWindowsコンテナイメージの構築を完了しました。
本記事では、前編で構築したコンテナイメージを利用するAmazon EKS(以下EKS)のWindows Node AMIを作成します。

EKSのドキュメントをよると、AWSはユーザー向けに最適化されたEKS optimized WindowsAMIを提供していることがわかりました。

このEKS optimized WindowsAMIにはEKSでの動作可能なミドルウェアがインストールされていますが、前編で構築したコンテナイメージをサポートするには不足している部分がまだ多く存在します。

そのため、この記事ではUnreal Editor用のカスタムWindows AMIを構築します。

はじめに

本記事では、以下を前提知識として扱います。

今回では、HashiCorp社が提供するPackerツールを使用してAMIを作成します。
また、AWSが提供するEC2 Image Builderを使用してAMIを作成することも可能ですが、以下の理由でPackerを選定しました。

  • イメージ作成プロセスを完全に制御したい
  • 今後、既存のCI/CDプロセスに統合する予定がある

Packerについて

Packerは、HashiCorpによって開発されたオープンソースのツールで、異なるプラットフォーム向けにサーバーイメージを自動で作成するためのものです。
開発者やシステム管理者がテンプレートファイルを通じてイメージを定義し、AWS、Azure、Google Cloud Platformなど複数のクラウドサービス向けのイメージを作成できます。

Packerの特徴と機能:

  • イメージの自動作成: PackerはOSのイメージを自動作成し、手動でのイメージ作成や設定の時間と複雑さを軽減します。
  • マルチプラットフォームに対応:Packerは複数のクラウドプラットフォームと仮想化技術をサポートしており、異なる環境間での一貫性を促進します。
  • カスタマイズと拡張性: Packerの設定はシンプルなテンプレートファイルを使用して行われ、ユーザーは具体的なニーズに応じてイメージをカスタマイズできます。さらに、Packerは多くのビルダー、事前に設定されたテンプレート、プラグインをサポートしており、機能を拡張できます。
  • DevOpsツールチェーンとの統合: Packerは、VagrantやTerraformなどの他のDevOpsツールと容易に統合でき、CI/CDのプロセスをサポートします。

使用手順

  • テンプレートの作成:ユーザーはまず、イメージの作成方法を定義したテンプレートファイルを作成する必要があります。
  • ビルドの実行:Packerのコマンドラインツールを使用してビルドを実行します。

具体的な作成手順は、次のセクションで詳しく説明します。

実施手順

  1. Packerツールのインストール
  2. Packerのテンプレートファイルの作成
  3. Windowsイメージのビルドとテストの実行

使用する環境およびソフトウェアのバージョンは以下のとおりです:

  • OS:Windows 11 Pro 22H2 x64
  • RAM: 32GB
  • CPU: i5-13600K
  • Packer: v1.10.0
  • eksctl: 0.176.0
  • AWS CLI: version 2

1. Packerツールのインストール

PackerのインストールはTerraformと同様に非常に簡単です。
exeファイルをダウンロードし、環境変数に追加するだけでインストールが完了します。

# PackerのダウンロードURL
https://developer.hashicorp.com/packer/install

# 環境変数の設定
`Control Panel` ⇒ `System` ⇒ `System Settings` ⇒ `Environment Variables`を順に開き、
`System variables`のボックス内で`PATH`変数をダブルクリックして編集画面を開きます。
変数値の最後に実行ファイルの所在Pathを追加します。例:C:\path

また、Chocolateyを使用してWindowsソフトウェアを管理しているユーザーは、以下のコマンドを実行するだけでインストールが完了します。

$ choco install packer

※他のシステムのユーザーは、Packerのインストールガイドを参考にインストールしてください。

インストール完了後、Packerが正しくインストールされているか確認します:

# 以下のコマンドを実行し、正しくバージョン情報が表示されればインストール成功
$ packer version
Packer v1.10.0

2. Packerのテンプレートファイルの作成

構築するWindows AMIに含める必要があるソフトウェアは以下のとおりです。
今回は、Windowsノード上でUnreal Editorを含むコンテナイメージを実行する為、GPU関連のソフトウェアを含めています。

  1. NVIDIA GPU drivers
  2. Vulkan runtime library
  3. DirectX shader compiler

※本記事では取り扱いませんが、EKSを活用してコンテナを運用する手順についてはunrealcontainersを参照してください。

また、Unreal Editorコンテナの起動速度を向上させるために、Windows AMI内に事前にキャッシュしておく必要もあります。

次に、テンプレートファイルの作成に取り掛かります。

Packerテンプレートファイル

Packerテンプレートのフォーマットと文法はTerraformと同様であり、Terraformの使用経験があるユーザーはすぐに使いこなせるでしょう。
Terraformの使用経験がないユーザーでも心配ありません。テンプレートの定義内容は非常に直感的で、何をしようとしているのかが理解しやすいです。

では、テンプレートファイル(eks-worker-node-ami.pkr.hcl)の作成を開始しましょう。

  • まず、必要なプラグインおよびそのバージョン情報を定義します。
#ターゲットのクラウドサービスはawsであるため
packer {
    required_plugins {
        amazon = {
            version = ">= 1.0.9"
            source = "github.com/hashicorp/amazon"
        }
    }
}
  • AMI作成のEC2を定義する

この設定は、PackerがEC2インスタンスを構築し、そこからAMIを作成するために使用するパラメータと設定を指定します。
利用されるPackerのSource名はamazon-ebsです。
主なパラメータには、AMI名、ソースAMI、インスタンスタイプ、リージョンなどがあります。

ここでは、AWSが提供する EKS optimized WindowsAMI をベースイメージとして使用し、その上に必要なシステム環境を構築します。
EKS optimized WindowsAMI には、EKSに接続するために必要なOS環境が既に構築されているため、この基盤を使用することでプロセスを簡素化できます。

source "amazon-ebs" "eks-worker-node" {
    ami_name      = "eks-windows-worker-node"  # AMI name
    instance_type = "g4dn.2xlarge"             # instancetype
    region        = "ap-northeast-1"           # region
    vpc_id        = "vpc-xxxxxxxxxxxxxxxxx"    # VPC id
    subnet_id     = "subnet-xxxxxxxxxxxxxx"    # Subnet id

    # base AMI
    source_ami_filter {
        filters = {
            name                = "Windows_Server-2022-English-Full-EKS_Optimized-1.28-*"
            root-device-type    = "ebs"
            virtualization-type = "hvm"
        }
        
        most_recent = true
        owners      = ["amazon"]
    }
    
    # Expand the boot disk to 120GB
    launch_block_device_mappings {
        device_name = "/dev/sda1"
        volume_size = 120
        volume_type = "gp3"
        delete_on_termination = true
    }
    
    # Allow S3 access for the VM
    temporary_iam_instance_profile_policy_document {
        Version = "2012-10-17"
        Statement {
            Action   = ["s3:Get*", "s3:List*", "s3:Describe*","s3-object-lambda:Get*","s3-object-lambda:List*"]
            Effect   = "Allow"
            Resource = ["*"]
        }
    }
    
    # Use our startup script to enable SSH access
    user_data_file = "${path.root}/scripts/startup.ps1"
    
    # Use SSH for running commands in the VM
    communicator = "ssh"
    ssh_username = "Administrator"
    ssh_timeout  = "30m"
    
    # Don't automatically stop the instance, since sysprep will perform the shutdown
    disable_stop_instance = true
}
  • ビルドプロセスを作成する

ここで、AMI名、参照するソース名(上記で構築したEC2インスタンス)を設定し、最後にPowershellスクリプトを実行して必要な環境を構築します。
ビルドが完了したAMIは、AWSコンソールで確認できます。

build {
    name    = "eks-worker-windows-node"
    sources = ["source.amazon-ebs.eks-worker-node"]
    
    # Run our setup script
    provisioner "powershell" {
        script = "${path.root}/scripts/setup_base_eks_optimized_ami.ps1"
    }
    
    # Perform cleanup and shut down the VM
    provisioner "powershell" {
        script = "${path.root}/scripts/cleanup.ps1"
        valid_exit_codes = [0, 2300218]
    }
}

ここまでで、AMI作成のテンプレートファイルの作成が完了しました。
次に、環境を構築するためのPowerShellスクリプトを作成します。

このスクリプトの目的は、EC2のSSHを構成し、後続のEC2へのログインおよび構築スクリプトの実行に使用することです。
EC2のmetaServiceにはIMDSv1とIMDSv2の2つのバージョンがあります。
ご自身のEC2設定に応じて、対応するバージョンのコードを選択してください。

# startup.ps1 
<powershell>

# Install the OpenSSH server and set the sshd service to start automatically at system startup
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
Set-Service -Name sshd -StartupType 'Automatic'

# Create the OpenSSH configuration directory if it doesn't already exist
$sshDir = 'C:\ProgramData\ssh'
if ((Test-Path -Path $sshDir) -eq $false) {
    New-Item -Path $sshDir -ItemType Directory -Force | Out-Null
}

# Retrieve the SHH public key from the EC2 metadata service
$authorisedKeys = "$sshDir\administrators_authorized_keys"

# IMDSv2
#$response = Invoke-WebRequest -Uri "http://169.254.169.254/latest/api/token" -Method PUT -Headers @{"X-aws-ec2-metadata-token-ttl-seconds"="21600"}
#$token = $response.Content
#$metadata = Invoke-WebRequest -Uri "http://169.254.169.254/latest/meta-data/public-keys/0/openssh-key" -Headers @{"X-aws-ec2-metadata-token"=$token}
#$metadata.Content | Out-File -FilePath "$authorisedKeys"

# Retrieve the SHH public key from the EC2 metadata service
# IMDSv1
curl.exe 'http://169.254.169.254/latest/meta-data/public-keys/0/openssh-key' -o "$authorisedKeys"

# Set the required ACLs for the authorised keys file
icacls.exe "$authorisedKeys" /inheritance:r /grant "Administrators:F" /grant "SYSTEM:F"

# Install the Windows feature for containers, which will require a reboot
Install-WindowsFeature -Name Containers -IncludeAllSubFeature

# Restart the VM
Restart-Computer

</powershell>

まず、使用するバージョン情報およびインストールに使用する一時フォルダーを設定します。

# setup.ps1 
# Constants
$Containered_Ver = "1.7.11"
$eks_optimized_ami_windows_Ver = "1.28.0"

$ContainerdPath = "$env:ProgramFiles\containerd"
$TempRoot = "C:\TempEKSArtifactDir"
$TempPath = "$TempRoot\EKS-Artifacts"

# Create each of our directories
Write-Host "Create each of our directories"
foreach ($dir in @($TempRoot, $TempPath)) {
    New-Item -Path $dir -ItemType Directory -Force | Out-Null
}

バージョン選定に関して注意すべき2点は次の通りです:

次に、必要なソフトウェアを順にインストールします。

# Install the NVIDIA GPU drivers
Write-Host "Install the NVIDIA GPU drivers"
$driverBucket = 'ec2-windows-nvidia-drivers'
$driver = Get-S3Object -BucketName $driverBucket -KeyPrefix 'latest' -Region 'us-east-1' | Where-Object {$_.Key.Contains('server2022')}
Copy-S3Object -BucketName $driverBucket -Key $driver.Key -LocalFile "$TempRoot\driver.exe" -Region 'us-east-1'
Start-Process -FilePath "$TempRoot\driver.exe" -ArgumentList @('-s', '-noreboot') -NoNewWindow -Wait
  • Vulkan runtime library
# install the Vulkan runtime library
Invoke-WebRequest -Uri "https://sdk.lunarg.com/sdk/download/latest/windows/vulkan-runtime-components.zip?u=" -OutFile "$env:TEMP\vulkan-runtime-components.zip"
Expand-Archive -Path "$env:TEMP\vulkan-runtime-components.zip" -DestinationPath "$env:TEMP"
Copy-Item -Path "*\x64\vulkan-1.dll" -Destination C:\Windows\System32\
# Retrieve the DirectX shader compiler files needed for DirectX Raytracing (DXR)
Invoke-WebRequest -Uri "https://github.com/microsoft/DirectXShaderCompiler/releases/download/v1.6.2104/dxc_2021_04-20.zip" -OutFile "$env:TEMP\dxc.zip"
Expand-Archive -Path "$env:TEMP\dxc.zip" -DestinationPath "$env:TEMP"
Copy-Item -Path "$env:TEMP\bin\x64\dxcompiler.dll" C:\Windows\System32\
Copy-Item -Path "$env:TEMP\bin\x64\dxil.dll" C:\Windows\System32\

# Clean up any temp files generated during prerequisite installation
Remove-Item -LiteralPath "$env:TEMP" -Recurse -Force
New-Item -Type directory -Path "$env:TEMP"

そして、EKS optimized WindowsAMI に含まれているContainerdのバージョンを更新します(1.6.18⇒1.7.1)

# TEMPORARY UNTIL EKS ADDS SUPPORT FOR CONTAINERD v1.7.11:
# Download and extract the containerd 1.711 release build
Write-Host "Download and extract the containerd 1.7.11 release build"
$webClient = New-Object System.Net.WebClient
$containerdTarball = "$TempPath\containerd-$Containered_Ver.tar.gz"
$containerdFiles = "$TempPath\containerd-$Containered_Ver"
$webClient.DownloadFile("https://github.com/containerd/containerd/releases/download/v$Containered_Ver/containerd-$Containered_Ver-windows-amd64.tar.gz", $containerdTarball)
New-Item -Path "$containerdFiles" -ItemType Directory -Force | Out-Null
tar.exe -xvzf "$containerdTarball" -C "$containerdFiles"

# Stop containerd service 
Stop-Service -Name "containerd" -Force

# Upgrade container version from 1.6.18 to 1.7.11
Write-Host "Upgrade container version from 1.6.18 to 1.7.11"
Move-Item -Path "$containerdFiles\bin\containerd.exe" -Destination "$ContainerdPath\containerd.exe" -Force
Move-Item -Path "$containerdFiles\bin\containerd-shim-runhcs-v1.exe" -Destination "$ContainerdPath\containerd-shim-runhcs-v1.exe" -Force
Move-Item -Path "$containerdFiles\bin\ctr.exe" -Destination "$ContainerdPath\ctr.exe" -Force

# restart containerd service 
Start-Service -Name containerd

# Clean up the containerd intermediate files
Write-Host "Clean up the containerd intermediate files"
Remove-Item -Path "$containerdFiles" -Recurse -Force
Remove-Item -Path "$containerdTarball" -Force

最後に、EKS optimized WindowsAMI のImage Builderコンポーネントを使用して、前編で作成したコンテナイメージをキャッシュイメージとして追加します。

# Download the EKS artifacts archive
Write-Host "Download the EKS artifacts archive"
$webClient.DownloadFile("https://ec2imagebuilder-managed-resources-us-east-1-prod.s3.amazonaws.com/components/eks-optimized-ami-windows/$eks_optimized_ami_windows_Ver/EKS-Artifacts.zip", "C:\EKS-Artifacts.zip")

# Extract the EKS artifacts archive
Write-Host "Extract the EKS artifacts archive"
Expand-Archive -Path "C:\EKS-Artifacts.zip" -DestinationPath $TempRoot
Remove-Item -Path "C:\EKS-Artifacts.zip" -Force

# Add the unreal-engine-dev-windows-5.1:latest to the list of images to pre-pull
Write-Host "Add the unreal-engine-dev-windows-5.1:latest image"
$baseLayersFile = "$TempPath\eks.baselayers.config"
$baseLayers = Get-Content -Path $baseLayersFile -Raw | ConvertFrom-Json
$baseLayers.ue += "unreal-engine-dev-windows-5.1:latest"
$patchedJson = ConvertTo-Json -Depth 100 -InputObject $baseLayers
Set-Content -Path $baseLayersFile -Value $patchedJson -NoNewline

# Get added new BaseLayers
Write-Host "Perform EKS worker node setup"
Push-Location $TempPath
.\Get-EKSBaseLayers.ps1 -ConfigFile eks.baselayers.config -ContainerRuntime containerd
Pop-Location

# Perform cleanup
Write-Host "Perform cleanup"
Remove-Item -Path "$TempRoot" -Recurse -Force

このスクリプトの目的は、スタートアップスクリプト(startup.ps1)で設定したSSH環境を削除し、sysprep(システム準備)を開始することです。

# cleanup.ps1 
# Perform cleanup
Set-Service -Name sshd -StartupType 'Manual'
Remove-Item -Path 'C:\ProgramData\ssh\administrators_authorized_keys' -Force

# Remove the file for this script, since Packer won't have a chance to perform its own cleanup
Remove-Item -Path $PSCommandPath -Force

# Perform sysprep and shut down the VM
# Need delete edge account for sysprep
& "$Env:ProgramFiles\Amazon\EC2Launch\EC2Launch.exe" sysprep --shutdown=true

3. Windowsイメージのビルドとテスト

テンプレートファイルに対してPackerコマンドを実行し、Windows AMIのビルドを開始します。

  • ビルドコマンド
$ packer build .\eks-worker-node-ami.pkr.hcl

...出力ログ...
==> Wait completed after 52 minutes 42 seconds
==> Builds finished.
  • ビルド完了の確認

AWS Console ⇒ EC2 ⇒ イメージ ⇒ AMIで作成されたAMIを確認できます。

AMIをテストするために、eksctlを使用してEKSを作成します。
以下のyamlファイルでは、2つのnodegroupを含むKubernetesクラスターを定義しています。

# test_windows_node_eksctl.yaml

apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: test-windows-node-cluster
  region: ap-northeast-1
  version: '1.28'

nodeGroups:
  - name: windows-ng
    instanceType: g4dn.2xlarge
    ami: [作成したAMI id, ami-xxxxx]
    amiFamily: WindowsServer2022FullContainer
    volumeSize: 120
    minSize: 1
    maxSize: 1
  - name: linux-ng
    amiFamily: AmazonLinux2
    minSize: 1
    maxSize: 1

EKSの作成を開始します:

$ eksctl create cluster -f test_windows_node_eksctl.yaml

完了確認

eksctlのコマンド実行により、AWSはCloudFormationを呼び出してリソースを作成します。

  • CloudFormationでの確認

  • EKS管理コンソールでの確認

  • Session Managerを使用してWindowsノードにログインし、ソフトウェアのインストールおよびイメージのキャッシュを確認

    • containerdのバージョンが1.7.11であることを確認
    • NVIDIA GPUドライバーを確認
    • VulkanとDirectXを確認
    • unreal-engine-dev-windows-5.1:latestがキャッシュされていることを確認

終わりに

本記事を読むことで、UEアプリ用のカスタムWindows Node AMIの作成方法について理解していただけたと思います。
もちろん、この記事で作成したスクリプトUnreal Editorの動作要件に基づいていますが、ビルドテンプレートファイルは共通のものです。
今後、読者はAWS提供の EKS optimized WindowsAMI がご自身のプロジェクト要件を満たさないと感じた場合、この記事を参考にしてスクリプトを置き換えて独自のWindows AMIを作成できます。

この記事では、Windows ContainerでのGPU利用要件について言及しました。これに興味を持たれる方もいるでしょう。
次回の記事では、今回作成したWindowsノードを使用して、Unreal EngineのPixel Streamingプロジェクトを実現する方法について説明します。

どうぞお楽しみに。

現在、電通総研はweb3領域のグループ横断組織を立ち上げ、Web3およびメタバース領域のR&Dを行っております(カテゴリー「3DCG」の記事はこちら)。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください!
私たちと同じチームで働いてくれる仲間をお待ちしております!
電通総研の採用ページ

参考文献

執筆:@chen.sun、レビュー:寺山 輝 (@terayama.akira)
Shodoで執筆されました