電通総研 テックブログ

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

Amazon EKSのWindowsノードを用いてUEアプリケーションを配信する 前編

こんにちは、金融ソリューション事業部の孫です。
前回の記事で、Amazon EKS(AWSが提供するKubernetesサービス、以下EKSと略)用のWindowsノードAMI(Amazon Machine Images、以下AMIと略)の構築方法について紹介しました。
今回は、このAMIを使用してWindowsノードを作成し、UnrealEngine(以下はUE)アプリケーションを配信する方法を説明します。

今回のUEアプリケーションサーバーはWindowsコンテナで実行され、PixelStreaming技術を使用してWebブラウザからアクセスできます。
従来のアプリケーションのインストールとは異なり、ユーザーはアプリケーションをローカルにインストールする必要がなく、Webブラウザを通じていつでもどこでもアクセスできます。

はじめに

Kubernetesの運用において、GPUリソースを利用する際にはいくつかの大きな課題があります。
これはホストマシンのハードウェアに特別な要求があるだけでなく、関連する設定も非常に複雑です。
Linuxコンテナに対しては、Nvidia公式がNVIDIA device plugin for Kubernetesを提供しており、開発者がホストマシン上のGPUリソースへ簡単にアクセスできます。
一方、Windowsコンテナに対しては、Nvidiaからはまだプラグインのサポートが出ていません。
幸いにも、TensorWorks社がKubernetes Device Plugins for DirectXオープンソース化している為、これを利用することでWindowsコンテナにおいてもホストマシン上のGPUリソースへのアクセスが可能になります。

また、今回のデモで使用するUEアプリケーションは、UnrealEnginePixelStreaming sampleを利用します。

今回のブログ内容は多岐にわたるため、ブログを前編後編の2つに分けて紹介します。

前編では、Dockerイメージの構築に焦点を当てます:

  • Signalling Serverサービスイメージの構築
  • TURN/STUN Serverサービスイメージの構築
  • デモ用UEアプリケーションイメージの構築
  • ローカルテスト

後編では、EKSの構築とKubernetes yamlファイルの作成について説明します:

  • Kubernetes Device Plugins for DirectXのインストールとテスト
  • EKS環境の構築
  • yamlファイルの作成
  • プロジェクトのデプロイとゲームの展示

この記事を読む前に、読者が以下の知識を持っていることを前提とします:

  • AWSの基本操作
  • EKSの使用経験
  • コンテナの開発経験
  • UnrealEngineの基本操作

実施手順

  1. PixelStreamingアーキテクチャの紹介
  2. Signalling ServerのDockerイメージの構築
  3. TURN/STUN ServerのDockerイメージの構築
  4. デモ用UEアプリケーションのDockerイメージの構築
  5. ローカルテストおよびECRへのアップロード

開発環境

  • OS:Windows 11 Pro 22H2 x64
  • RAM:32GB
  • CPU:Intel Core i5-13600K
  • GPUGeForce RTX 3080
  • UnrealEngineのバージョン:5.3
  • Docker Desktopのバージョン:4.30.0
    ※注意:Docker DesktopはWindowsコンテナモードに切り替えてください。

前提準備

サービスの構築を始める前に、以下の環境準備が完了していることを前提とします:

  • AWSアカウントの登録
  • ローカルのAWS CLI環境の設定が完了
  • Docker Desktopがインストールされている
    ※注意:環境設定の詳細ステップについては、関連公式ドキュメントを参照してください。本文では詳しく述べません。

1. PixelStreamingアーキテクチャの紹介

Pixel Streamingについては、金融ソリューション事業部の山下さんのブログにご紹介したとおり、ビデオストリームをユーザーのWebブラウザに直接送信する技術です。

PixelStreamingのアーキテクチャは、主に三つの部分から構成されます:

  1. UE+PixelStreaming Plugin:ゲームのメディアストリーム出力を提供します。
  2. Signalling and Web Server:クライアントとのWebRTC接続を確立するサービスとユーザーがアクセスするWebページを提供します。
  3. STUN/TURN Server:NATトラバーサルサービスを提供します。


※詳細な技術情報については、UnrealEngineドキュメントを参照してください。ここでは詳しく述べません。

これから、これら三つの部分のDockerイメージをそれぞれ構築します。

2. Signalling ServerのDockerイメージの構築

Unreal Engineの公式にはSignallingサーバーのDockerイメージが提供されていますが、これはLinuxベースのDockerイメージであり、Windowsサーバー上では動作しません。
そのため、公式のDockerfileを参考にして、Windowsシステムで動作するDockerfileを構築する必要があります。

作成したDockerfileの主な処理手順は以下のとおりです:

  1. Node.jsをインストール
  2. Signalling Serverのコードをコンテナにコピー
  3. Signalling Serverアプリケーションをインストール
  4. エントリーポイント(entrypoint)プログラムを設定

ここで注意すべき点:

  • 基本イメージの選択について
    • Dockerイメージのサイズを可能な限り小さくするために、mcr.microsoft.com/windows/servercore:ltsc2022 をBase Imageとして選択しました
  • Signalling Serverのコードについて
    • Epic公式のリポジトリからダウンロード可能
    • フロントエンドについては、事前に SignallingWebServer/platform_scripts/cmd/setup.bat を実行して構築する必要があります
    • 完了すると Public フォルダが生成されます
    • イメージサイズを小さくするために、不要なフォルダ(Docs、platform_scripts)を削除します
    • 最終的なファイル構成は以下のとおりです
SignallingWebServer
│  .dockerignore
│  cirrus.js
│  config.json
│  Dockerfile
│  package-lock.json
│  package.json
│  Readme.md
│  turnserver.conf
├─modules
├─Public
└─tps

上記の手順に基づいて作成されたDockerfileは以下のとおりです:

# escape=`
FROM mcr.microsoft.com/windows/servercore:ltsc2022 as installer
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop';$ProgressPreference='silentlyContinue';"]

# Install Node.js 
RUN Invoke-WebRequest -OutFile nodejs.zip -UseBasicParsing "https://nodejs.org/dist/v19.9.0/node-v19.9.0-win-x64.zip"; Expand-Archive nodejs.zip -DestinationPath C:\; Rename-Item "C:\\node-v19.9.0-win-x64" c:\nodejs

FROM mcr.microsoft.com/windows/servercore:ltsc2022 as signalling-server
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop';$ProgressPreference='silentlyContinue';"]

# Set Node Environment variable
WORKDIR C:\nodejs
COPY --from=installer C:\nodejs\ .
RUN SETX PATH C:\nodejs
RUN npm config set registry https://registry.npmjs.org/

# copy SignallingWerServer source
COPY SignallingWebServer C:\SignallingWebServer

# install dependencies
COPY SignallingWebServer\package*.json C:\tmp\
RUN cd C:\tmp\; npm ci
# RUN New-Item -ItemType Directory -Path C:/app
RUN Copy-Item -Path C:\tmp\node_modules -Destination C:\SignallingWebServer -Recurse
RUN Remove-Item -Path C:\tmp\node_modules -Recurse

WORKDIR C:\SignallingWebServer

RUN npm prune --production

RUN ls -l .

# Expose TCP ports 80 for player WebSocket connections and web server HTTPaccess
EXPOSE 80

# Expose TCP port 8888 for streamer WebSocket connections
EXPOSE 8888
EXPOSE 8888/udp

# Expose TCP port 19302 for connections to Google's stun server
EXPOSE 19302

# entrypoint
ENTRYPOINT [ "node", "cirrus.js" ]

次に、イメージのビルドを実行します:

## Build
$ cd D:\SignallingWebServer
$ docker build -t signalling-demo .

## 確認
$ docker images signalling-demo
REPOSITORY        TAG       IMAGE ID       CREATED      SIZE
signalling-demo   latest    xxxxxxxxxxx   2 days ago   4.68GB

3. TURN/STUN ServerのDockerイメージの構築

Epic公式と同様に、coturnを使用してTURN/STUNサービスを提供します。
Dockerfileの処理手順は以下のとおりです:

  • 非常にシンプルで、coturnを直接インストールするだけです。
FROM alpine:3.17 as turn-server

# add dependencies
RUN apk update && \
  apk add -u --no-cache bind-tools coturn

# set entrypoint script
COPY entrypoint.sh /
RUN chmod +x /entrypoint.sh

CMD ["/entrypoint.sh"]

エントリーファイル(entrypoint.sh)の内容は次の通りです:

  • エントリーファイルには、coturnサービスを開始するために必要なパラメータが定義されています。
#!/bin/sh
export INTERNAL_IP="${INTERNAL_IP:-$(ip a | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*' | grep -v '127.0.0.1' | head -n 1)}"
export EXTERNAL_IP="${EXTERNAL_IP:-$(dig +short myip.opendns.com @resolver1.opendns.com)}"

echo "---------"
echo "INTERNAL_IP : $INTERNAL_IP"
echo "EXTERNAL_IP : $EXTERNAL_IP"
echo "TURN_REALM  : $TURN_REALM"
echo "---------"

turnadmin -a \
  -u ${TURN_USER:-turn} \
  -p ${TURN_PASS:-change} \
  -r ${TURN_REALM:-example.com}

# Start coturn server with options
# https://github.com/coturn/coturn/blob/master/README.turnserver
# https://github.com/coturn/coturn/wiki/turnserver
turnserver -n --no-cli \
  --verbose \
  --listening-port=${TURN_PORT:-3478} \
  --relay-ip="${INTERNAL_IP}" \
  --listening-ip="${INTERNAL_IP}" \
  --external-ip="${EXTERNAL_IP?missing external ip}/${INTERNAL_IP}" \
  --server-name=${TURN_REALM:-example.com} \
  --fingerprint \
  --lt-cred-mech \
  --realm=${TURN_REALM:-example.com} \
  --user="${TURN_USER:-pixel}:${TURN_PASS:-changeme}" \
  --rest-api-separator=":" \
  --channel-lifetime=${TURN_CHANNEL_LIFETIME:-"-1"} \
  --min-port=${TURN_MIN_PORT:-49152} \
  --max-port=${TURN_MAX_PORT:-65535} ${EXTRA_ARGS} \
  --no-tlsv1 \
  --no-tlsv1_1 \
  --no-tlsv1_2 \
  --secure-stun \
  --tls-listening-port=${TLS_PORT:-5349} \
  --cert=${CERT_FILEPATH:-/etc/turn/tls.crt} \
  --pkey=${PKEY_FILEPATH:-/etc/turn/tls.key}

注意すべき点は、ネットワーク遅延の観点からLinuxサーバーがWindowsサーバーよりも優れているため、Linuxシステムを使用してTURN/STUNサービスを提供することです。
コンパイル時にはDocker Desktopの実行モードを一時的にLinuxコンテナに切り替える必要があります。
切り替え後、イメージのビルドを行います。

## Build
$ cd D:\TURN

$ docker build -t turn-demo  .

## 確認
$ docker images turn-demo
REPOSITORY        TAG       IMAGE ID       CREATED      SIZE
turn-demo       latest     xxxxxxxxxxx   2 days ago     31MB

4. デモ用UEアプリケーションのDockerイメージの構築

Unreal EngineのDockerイメージに関しては、Epic公式が提供するRuntimeイメージruntime-windows-ltsc2022があります。
しかし、Unreal EngineのアプリケーションをEpicが提供したWindows Runtimeコンテナ上で実行すると、以下のエラーが発生します。

LogWindows: Error: appError called: Assertion failed: (((HRESULT)(Hr)) >= 0) [File:D:\build\++UE5\Sync\Engine\Plugins\Runtime\WindowsMoviePlayer\Source\WindowsMoviePlayer\Private\WindowsMoviePlayer.cpp] [Line: 45]


LogWindows: Error: Failed to create dialog. The operation completed successfully. Error: 0x0 (0)
LogWindows: Error: === Critical error: ===
LogWindows: Error:
LogWindows: Error: Assertion failed: (((HRESULT)(Hr)) >= 0) [File:D:\build\++UE5\Sync\Engine\Plugins\Runtime\WindowsMoviePlayer\Source\WindowsMoviePlayer\Private\WindowsMoviePlayer.cpp] [Line: 45]
LogWindows: Error:
LogWindows: Error:

どうやら、イメージの構築中にいくつかのミドルウェアが不足しているようです。
一方、 mcr.microsoft.com/windows/server:ltsc2022 ベースイメージを使用すると問題は発生しません。
そのため、公式が使用する windows/servercore イメージの代わりに windows/server ベースイメージを使用して再コンパイルします。

Pixel Streamingプロジェクトの準備

イメージを構築する前に、Unreal Engineからデモアプリケーションを作成し、そしてパッケージ化にします。

まず、Epic Games Launcher を開き、Unreal EngineSample に移動します。

次に、画面を下にスクロールして Pixel Streaming Demo プロジェクトを見つけます。

クリックして、Create Project ボタンを押してUnreal Engine 5.3プロジェクトを作成します。

プロジェクトが作成されたら、プロジェクトを起動します。
これはPixel Streamingのサンプルなので、Pixel Streamingに関する設定は事前に完了しています。

そして、プロジェクトをパッケージ化します。ターゲットプラットフォームはWindowsです。

パッケージ化が完了した後のファイル構造は次の通りです:

Dockerfileの作成

次に、Dockerfileの作成を開始します。
Epic公式のDockerfileを参考にします。

基本的な処理手順は次の通りです:

  1. DirectX runtimeのインストール
  2. DirectX shader compilerのインストール
  3. Visual C++ runtimeのインストール
  4. entrypointファイルをコンテナにコピー
  5. パッケージ化されたPixel Streamingプロジェクトをコンテナにコピー

Dockerfileの全内容は以下のとおりです:

# escape=`
FROM  mcr.microsoft.com/windows/server:ltsc2022 

# Retrieve the DirectX runtime files required by the Unreal Engine, since even the full Windows base image does not include them
RUN curl --progress-bar -L "https://download.microsoft.com/download/8/4/A/84A35BF1-DAFE-4AE8-82AF-AD2AE20B6B14/directx_Jun2010_redist.exe" --output %TEMP%\directx_redist.exe && `
    start /wait %TEMP%\directx_redist.exe /Q /T:%TEMP%\DirectX && `
    expand %TEMP%\DirectX\APR2007_xinput_x64.cab -F:xinput1_3.dll C:\Windows\System32\ && `
    expand %TEMP%\DirectX\Feb2010_X3DAudio_x64.cab -F:X3DAudio1_7.dll C:\Windows\System32\ && `
    expand %TEMP%\DirectX\Jun2010_D3DCompiler_43_x64.cab -F:D3DCompiler_43.dll C:\Windows\System32\ && `
    expand %TEMP%\DirectX\Jun2010_XAudio_x64.cab -F:XAudio2_7.dll C:\Windows\System32\ && `
    expand %TEMP%\DirectX\Jun2010_XAudio_x64.cab -F:XAPOFX1_5.dll C:\Windows\System32\

# Retrieve the DirectX shader compiler files needed for DirectX Raytracing (DXR)
RUN curl --progress-bar -L "https://github.com/microsoft/DirectXShaderCompiler/releases/download/v1.6.2104/dxc_2021_04-20.zip" --output %TEMP%\dxc.zip && `
    powershell -Command "Expand-Archive -Path \"$env:TEMP\dxc.zip\" -DestinationPath $env:TEMP" && `
    xcopy /y %TEMP%\bin\x64\dxcompiler.dll C:\Windows\System32\ && `
    xcopy /y %TEMP%\bin\x64\dxil.dll C:\Windows\System32\

# Install the Visual C++ runtime files using Chocolatey
RUN powershell -NoProfile -ExecutionPolicy Bypass -Command "[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))"
RUN choco install -y vcredist-all

COPY entrypoint.cmd C:\entrypoint.cmd
COPY enable-graphics-apis.ps1 C:\enable-graphics-apis.ps1

##################
# Project Settings
COPY UnrealEngine-Demo C:\UnrealEngine-Demo

# Set our Unreal Engine application as the container's entrypoint, wrapped in the helper script
ENTRYPOINT ["cmd.exe", "/S", "/C", "C:\\entrypoint.cmd", "C:\\UnrealEngine-Demo\\PixelStreaming_53\\Binaries\\Win64\\PixelStreaming_53.exe", "-stdout", "-FullStdOutLogOutput", "-AudioMixer", "-RenderOffscreen", "-unattended", "-PixelStreamingURL=ws://127.0.0.1:8888"]

以下の点に注意してください:

Dockerイメージのビルド

以下のコマンドを実行してイメージをビルドします:

## Build
$ cd D:\UnrealEngine-runtime

$ docker build -t UnrealEngine-demo  .

## 確認
$ docker images unreal-engine-demo
REPOSITORY           TAG       IMAGE ID       CREATED      SIZE
unreal-engine-demo   latest    xxxxxxxxxxx   2 days ago   11.8GB

5. ローカルテストおよびECRへのアップロード

次に、UnrealEngine-demoとsignalling-demoの両方のイメージが正常に動作するかをローカルでテストします。
Docker ではLinuxコンテナとWindowsコンテナを同時に起動できないため、turn-demo イメージのローカルテストは行いません。

signalling-demoイメージの起動

以下のコマンドを実行してコンテナを作成します:

$ docker run --rm -p 8888:8888 -p 8888:8888/udp -p 8080:80 signalling-demo

UnrealEngine-demoイメージの起動

Windows OS上でGPUリソースにアクセスするには、以下の起動パラメータを追加する必要があります:

 --isolation process --device class/5B45201D-F2F2-4F3B-85BB-30FF1F953599

※参考ドキュメント:GPU acceleration in Windows containers

したがって、GPUリソースが必要なコンテナの起動コマンドは以下のとおりです:

$ docker run --rm -ti --isolation process --device class/5B45201D-F2F2-4F3B-85BB-30FF1F953599 --entrypoint "cmd.exe" unreal-engine-demo /S /C C:\\entrypoint.cmd C:\\UnrealEngine-Demo\\PixelStreaming_53\\Binaries\\Win64\\PixelStreaming_53.exe -stdout -FullStdOutLogOutput -AudioMixer -RenderOffscreen -unattended -PixelStreamingURL=ws://[Local Public IP]:8888

Signallingサーバーに正常に接続されると、Signallingサーバーに以下のログが出力されます:

08:19:52.154 Streamer connected: ::ffff:[Local Public IP]
08:19:52.155 ::ffff:[Local Public IP] <- {"type":"identify"}
08:19:52.652 ::ffff:[Local Public IP] -> {"type":"endpointId","id":"DefaultStreamer"}
08:19:52.652 Registered new streamer: DefaultStreamer
08:20:52.646 DefaultStreamer -> {"type":"ping","time":1715761252}

Webブラウザでの確認

ブラウザを開き、127.0.0.1:8080 と入力すると、以下のUEアプリケーション画面が表示されます:

Signallingサーバーのログ出力でも、ユーザー接続のログが表示されます:

これで、イメージが正常に動作することを確認できました。

Amazon Elastic Container Registryへのイメージのアップロード

次に、ローカルのイメージをAmazon Elastic Container Registry(以下はECR)にアップロードします。
これにより、これから構築するEKSで使用できます。

AWS ConsoleAmazon ECRPrivate registryRepositories を開きます。

リポジトリを作成」ボタンをクリックし、以下の3つのイメージリポジトリを順番に作成します:

作成完了後

ローカルのDockerイメージのアップロード

## Amazon ECR login
$ aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin 236960927670.dkr.ecr.ap-northeast-1.amazonaws.com

## Set tags
$ docker tag signalling-demo:latest 236960927670.dkr.ecr.ap-northeast-1.amazonaws.com/signalling-windows-demo:latest

$ docker tag unreal-engine-demo:latest 236960927670.dkr.ecr.ap-northeast-1.amazonaws.com/unreal-engine-windows-demo:latest

$ docker tag turn-demo:latest 236960927670.dkr.ecr.ap-northeast-1.amazonaws.com/turn-windows-demo:latest

## Upload Docker images
$ docker push signalling-windows-demo:latest 236960927670.dkr.ecr.ap-northeast-1.amazonaws.com/signalling-windows-demo:latest

$ docker push unreal-engine-windows-demo:latest 236960927670.dkr.ecr.ap-northeast-1.amazonaws.com/unreal-engine-windows-demo:latest

$ docker push turn-windows-demo:latest 236960927670.dkr.ecr.ap-northeast-1.amazonaws.com/turn-windows-demo:latest

これで、サービスに必要なDokcerイメージの作成タスクが完了しました。

おわりに

今回の前編では、Pixel Streamingアーキテクチャに必要なサービスのDockerイメージを構築しました。
次回の後編では、EKSの構築に焦点を当てて、Kubernetesに基づくUEアプリケーションを配信する手順を紹介します。

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

参考文献

執筆:@chen.sun、レビュー:@iwasaka.emari
Shodoで執筆されました