電通総研 テックブログ

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

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

こんにちは、金融ソリューション事業部の孫です。
記事の前編では、Kubernetes上に必要なDockerイメージを作成しました。
今回は、EKSの構築に取り組み、KubernetesホスティングのUEアプリケーションを配信します。

はじめに

前編で述べたように、KubernetesGPUを必要とするサービスの実行が容易なことではなく、特にWindowsノードではさらに難しいです。
この記事では、Kubernetes Device Plugins for DirectXプラグインを使用してGPUサポート問題を解決します。
執筆時点で、WindowsノードでGPUを必要とするサービスを実行する記事はほとんど見つかりませんでした。
この記事が同様の解決策を求めている読者に役立つことを願っています。

このブログを理解するために必要な前提知識は、前編の記事を参照してください。

実施手順

  1. EKS環境の構築
  2. Kubernetes Device Plugins for DirectXのインストールとテスト
  3. AWS ALB Ingress Controllerのインストール
  4. UEアプリケーションリリース用のyamlファイルの作成
  5. プロジェクトのデプロイとデモンストレーション

開発環境

  • eksctl: 0.176.0
  • kubectl: v1.27.3
  • AWS CLI: version 2
  • helm: v3.14.4
  • Helm chart version: 1.7.2
  • wddm-device-plugin: 0.0.1
  • AWS ALB Ingress Controller version: v2.7.2

インフラ構造図

以下のEKSサービスを構築します:

1. EKS環境の構築

ClusterとNodeGroupの作成

eksctlコマンドを使用して、EKSクラスターとNodeGroupを迅速に構築します。
このクラスターには、WindowsNodeとLinuxNodeの2つのNodeGroupが含まれています。
※Tips:EKSサービスの一部プラグイン、例えばネットワークプラグインVPC-CNI)はLinuxノードでのみ動作するため、Windowsノードのみのクラスターを作成することはできません。
前回の記事で、NVIDIA GPUドライバを含むWindowsNodeに必要なAMIが既に作成されています。

以下のyamlファイルで設定します:

# 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-0ffxxxxxxx
    amiFamily: WindowsServer2022FullContainer
    volumeSize: 120
    minSize: 1
    maxSize: 1
  - name: linux-ng
    amiFamily: AmazonLinux2
    minSize: 1
    maxSize: 1

以下のコマンドでクラスターを作成します:

$ eksctl create cluster -f test_windows_node_eksctl.yaml

作成後、CloudFormationで作成されたリソースを確認できます。

ECRアクセス権の追加

CloudFormationを開き、作成されたクラスターのStackで NodeInstanceRole を見つけます。


クラスターがECRからイメージを引き出せるように、そのRoleページでAmazonEC2ContainerRegistryReadOnlyポリシーをアタッチします。


これにより、ClusterとNodeGroupの作成が完了しました。

2. Kubernetes Device Plugins for DirectXのインストールとテスト

Kubernetes Device Plugins for DirectXは、GPUサービスを実行するための重要なプラグインです。
次に、このプラグインをインストールします。
インストールされたことを確認するために、まずWindowsNodeの状態情報を確認します。


割り当て可能なリソースにGPU関連のリソースはないことがわかります。

次に、プラグインのインストールを行います。
以下はインストールに使用するYamlファイルの内容です。
リソースタイプは DaemonSet で、これにより追加する各WindowsNodeに自動的にこのプラグインがインストールされます。
また、WDDM_DEVICE_PLUGIN_MULTITENANCYパラメータを通じて、各WindowsNodeはGPUリソースの数を設定できます。

# _DirectX-Device-DaemonSet.yamlの内容
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: device-plugin-wddm
spec:
  selector:
    matchLabels:
      app: device-plugin-wddm
  template:
    metadata:
      labels:
        app: device-plugin-wddm
    spec:
      nodeSelector:
        kubernetes.io/os: 'windows'
        kubernetes.io/arch: 'amd64'
        node.kubernetes.io/windows-build: '10.0.20348'      
      securityContext:
        windowsOptions:
          hostProcess: true
          runAsUserName: "NT AUTHORITY\\SYSTEM"
      hostNetwork: true
      containers:
      - name: device-plugin-wddm
        image: "index.docker.io/tensorworks/wddm-device-plugin:0.0.1"
        imagePullPolicy: Always
        # Configure the WDDM device plugin to allow 4 containers to mount each display device simultaneously
        env:
        - name: WDDM_DEVICE_PLUGIN_MULTITENANCY
          value: "4"

以下のコマンドでプラグインをインストール:

$ kubectl apply -f ./DirectX-Device-DaemonSet.yaml

インストール結果の確認:

  • Nodeの状態情報の確認

    • インストール前と比較して directx.microsoft.com/display: リソースが4つあります

  • GPUが正しく取得できているかの確認
    GPUリソースが正常に取得できたかを確認するため、_device-discovery-wddm.yml.yaml という確認用のyamlファイルを実行します。

# _device-discovery-wddm.yml.yamlの内容
apiVersion: batch/v1
kind: Job
metadata:
  name: example-device-discovery-wddm
spec:
  template:
    spec:
      containers:
      - name: example-device-discovery-wddm
        image: "index.docker.io/tensorworks/example-device-discovery:0.0.1"
        resources:
          limits:
            directx.microsoft.com/display: 1
      nodeSelector:
        "kubernetes.io/os": windows
      restartPolicy: Never
  backoffLimit: 0

以下のコマンドでYamlファイルを実行:

$ kubectl apply -f ./_device-discovery-wddm.yml.yaml


これにより、ホストマシンにインストールされているNvidiaビデオカード情報を取得できるかを確認します。

  • Nvidia GPUの状態確認
    Nvidia GPUの状態を確認するために、_nvidia-smi-wddm.yml というyamlファイルを使用します。
# _nvidia-smi-wddm.ymlの内容
apiVersion: batch/v1
kind: Job
metadata:
  name: example-nvidia-smi
spec:
  template:
    spec:
      containers:
      - name: example-nvidia-smi
        image: "mcr.microsoft.com/windows/servercore:ltsc2022"
        command: ["nvidia-smi.exe"]
        resources:
          limits:
            directx.microsoft.com/display: 1
      nodeSelector:
        "kubernetes.io/os": windows
      restartPolicy: Never
  backoffLimit: 0

以下のコマンドでyamlファイルを実行:

$ kubectl apply -f ./_nvidia-smi-wddm.yml

これにより、Nvidia GPUの状態を確認し、結果を得られます。

3. AWS ALB Ingress Controllerのインストール

PixelStreamingサービスが外部からアクセス可能になるように、AWS ALB Ingress Controllerプラグインをインストールします。
このプラグインを使って、アプリケーションロードバランサー(以下はALB)を作成します。

ALB Ingress Controller用のIAMの構築

# OIDC Create
$ eksctl utils associate-iam-oidc-provider --region ap-northeast-1 --cluster test-windows-node-cluster --approve

# AWS Load Balancer ControllerにIAMポリシーを作成し、AWS APIへの呼び出しを代行させる。
$ curl -o iam_policy_latest.json https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/main/docs/install/iam_policy.json

# Create Policy
$ aws iam create-policy \
>     --policy-name AWSLoadBalancerControllerIAMPolicy \
>     --policy-document file://iam_policy_latest.json


$ eksctl create iamserviceaccount \
> --cluster=test-windows-node-cluster \
> --namespace=kube-system \
> --name=aws-load-balancer-controller \
> --attach-policy-arn=arn:aws:iam::236960927670:policy/AWSLoadBalancerControllerIAMPolicy \
> --override-existing-serviceaccounts \
> --approve

# iamserviceaccountの確認
$ eksctl  get iamserviceaccount --cluster test-windows-node-cluster
NAMESPACE       NAME                            ROLE ARN
kube-system     aws-load-balancer-controller    arn:aws:iam::

$ kubectl get sa aws-load-balancer-controller -n kube-system
NAME                           SECRETS   AGE
aws-load-balancer-controller   0         32s

aws-load-balancer-controller インストール

# 参考:https://github.com/aws/eks-charts/tree/master/stable/aws-load-balancer-controller
helm repo add eks https://aws.github.io/eks-charts

$ helm install aws-load-balancer-controller eks/aws-load-balancer-controller -n kube-system --set clusterName=test-windows-node-cluster --set serviceAccount.create=false --set serviceAccount.name=aws-load-balancer-controller

# インストールされていることを確認。
kubectl -n kube-system get deployment 
NAME                           READY   UP-TO-DATE   AVAILABLE   AGE
aws-load-balancer-controller   0/2     2            0           10s

# Webhook-svcが作成されていることを確認。
$ kubectl -n kube-system get svc 
NAME                                TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                  AGE
aws-load-balancer-webhook-service   ClusterIP   xx.xxx.xx.x     <none>        443/TCP                  102m
kube-dns                            ClusterIP   xx.xxx.xx.x     <none>        53/UDP,53/TCP,9153/TCP   3m9s

4. UEアプリケーションリリース用のyamlファイルの作成

TURNサービスリリース用yaml

以下はTURNサービスへの接続用のアカウント名とパスワードを設定するyamlの内容です。
今回はDemoのため、アカウント名とパスワードは平文で処理されています。
ただし、実際の本番環境では、KubernetesのSecretリソースタイプを使用してこれらの情報を安全に作成するべきです。

# _turn-demo-deployment.yamlの内容
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: turn-demo
  labels:
    app: turn-demo
spec:
  selector:
    matchLabels:
      app: turn-demo
  template:
    metadata:
      labels:
        app: turn-demo
    spec:
      restartPolicy: Always
      terminationGracePeriodSeconds: 30
      hostNetwork: true
      nodeSelector:
        kubernetes.io/os: linux
      containers:
        - name: turn-server
          image: [aws accound id].dkr.ecr.ap-northeast-1.amazonaws.com/turn-windows-demo:latest      
          imagePullPolicy: Always
          ports:
            - name: turn-udp
              containerPort: 3478
              hostPort: 3478
              protocol: UDP
            - name: turn-tcp
              containerPort: 3478
              hostPort: 3478
              protocol: TCP
          env:
            - name: INTERNAL_IP
              valueFrom:
                fieldRef:
                  fieldPath: status.podIP
            # Firewall rules on the node pool instances must be created for these port ranges
            - name: TURN_PORT
              value: "3478"
            - name: TLS_PORT
              value: "443"
            - name: TURN_MIN_PORT
              value: "49152"
            - name: TURN_MAX_PORT
              value: "65535"
            - name: TURN_REALM
              value: turnserver
            - name: TURN_USER
              value: [turn username]
            - name: TURN_PASS
              value: [turn password]

PixelStreamingサービス用yamlファイル

ネットワーク遅延を低減するため、SignallingサーバーとUnrealEngineのデモゲームを同一のPodに配置しています。
以下のパラメータ設定に注意してください:

  • --peerConnectionOptions中のSTUN/TURNサービスのIPアドレスは、LinuxNodeのPublicIPであるべきです。
    • ネットワーク遅延を減少させるために、STUN/TURNサーバーはLinuxノードにスケジュールされます。
    • 今回はPublic IPを手動で記入することを行っていますが、本番環境では手作業を減少させるためにPublic IPアドレスを確定するスクリプトを追加すべきです。
  • --peerConnectionOptions中のSTUN/TURNサービスのアカウント名とパスワードは、_turn-demo-deployment.yamlで設定された値と一致する必要があります。
  • GPUリソースの割り当てについては、 directx.microsoft.com/display 値を指定することで設定します。
# _demo-deployment.yamlの内容
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ue-demo-deployment
  labels:
    app: ue-demo-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: ue-demo-app
  template:
    metadata:
      labels:
        app: ue-demo-app
    spec:
      nodeSelector:
        "kubernetes.io/os": "windows"
      containers:
      - name: signalling-server
        image: [aws accound id].dkr.ecr.ap-northeast-1.amazonaws.com/signalling-windows-demo:latest
        ports:
            - name: http
              containerPort: 80
            - name: stream
              containerPort: 8888
        command: ["cmd", "/C"]
        args:
            - "node"
            - "cirrus.js"
            - '--peerConnectionOptions={"iceServers":[{"urls":["stun:[linuxNode Public IP]:3478","turn:[linuxNode Public IP]:3478?transport=udp"],"username":"[turn username]","credential":"[turn password]"}]}'
      - name: ue-project-demo
        image: [aws accound id].dkr.ecr.ap-northeast-1.amazonaws.com/unreal-engine-windows-demo:latest
        imagePullPolicy: Always
        resources:
          limits:
            directx.microsoft.com/display: 1

ALB作成用のyamlファイル

httpサービス用のALBを作成します。ユーザーがこのALBのDNSアドレスにアクセスすることで、デモUEアプリケーションにアクセスできます。

# _demo-ingress.yamlの内容
# Service
apiVersion: v1
kind: Service
metadata:
  name: demo-web-service
  labels: 
    app: ue-demo-app
spec:
  type: ClusterIP
  selector: 
    app: ue-demo-app
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 80

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: demo-web
  annotations:
    alb.ingress.kubernetes.io/load-balancer-name: demo-alb
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
spec:
  ingressClassName: alb
  rules:
    - http:
        paths:
        - path: /
          pathType: Prefix
          backend:
            service:
              name: demo-web-service
              port:
                number: 80

5. プロジェクトのデプロイとデモンストレーション

以下のコマンドでyamlファイルをデプロイします::

$ kubectl apply -f _turn-demo-deployment.yaml
$ kubectl apply -f _demo-deployment.yaml
$ kubectl apply -f _demo-ingress.yaml

ユーザーとPixelStreamingサーバー間のメディアストリーミング転送は、主にTURNサーバーを通じてリレーされます。
そのため、LinuxNodeのセキュリティグループで以下のインバウンドポートを開放する必要があります。

セキュリティグループ編集

以下のポートを開放します:

TCP 32355-65535 0.0.0.0/0
UDP 32355-65535 0.0.0.0/0
TCP 3478 0.0.0.0/0
UDP 3478 0.0.0.0/0

UEアプリケーションへのアクセス

以下のコマンドでALBのDNSアドレスを確認します:

$ kubectl get ingress


ブラウザで ADDRESS 列に表示されたアドレスを入力すると、UEアプリケーションにアクセスできます!

おわりに

Amazon EKSのWindowsノードでUEアプリケーションの配信に成功しました!
通常、ゲーム開発はWindowsシステムで行われることが多いです、特にUnreal Engineなどの開発エンジンを使用する開発者にとってはそうです。
ゲーム製品とOSの互換性を確保するために、通常はWindows Server環境でゲームをリリースすることが推奨されます。
以前は、KubernetesにはWindowsコンテナ内のGPUアプリケーションのサポートが限られていました。
そのため、ゲーム開発者は従来のEC2インスタンスに依存してゲームサーバーをデプロイおよび運用する必要があり、運用の柔軟性と拡張性を制限していました。

今回のブログを通じて、Kubernetes上でのWindowsコンテナのGPUサポート問題は解決されたことが確認できました。
これにより、将来的にWindowsコンテナがKubernetes領域で広く利用される可能性が示されました!

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

参考文献

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