Amazon Web Services ブログ

UDP トラフィックを Kubernetes にルーティングする方法

この記事は How to route UDP traffic into Kubernetes (記事公開日: 2022 年 2 月 22 日) を翻訳したものです。

Amazon Elastic Kubernetes Service (Amazon EKS) は、リリースして以来、お客さまのアプリケーションを信頼性高くかつ大規模に実行することを支援してきました。UDP、もしくは User Datagram Protocol は、例えばリアルタイムストリーミング、オンラインゲーム、IoT といったワークロードにとって理想的な、低レイテンシーのプロトコルです。AWS の Network Load Balancer (NLB) は、非常に低レイテンシーで高スループットを維持しつつ、秒間数千万リクエストを捌くよう設計されています。UDP、Kubernetes、Network Load Balancer の組み合わせによって、低レイテンシーの要件に応えつつアジリティを改善する能力をお客様に提供します。

UDP ベースの LoadBalancer タイプの Kubernetes Service の設定は、TCP ベースの LoadBalancer と似ています。一方で、例えば Network Load Balancer のヘルスチェックの設定と同様、いくつか留意すべき点があります。この記事では、UDP ベースのゲームサーバーをデプロイする方法と、UDP ベースではないヘルスチェックを可能にするパターンを紹介します。

背景

Kubernetes では、デプロイ可能なコンピューティングの最小単位は Pod です。各 Pod は、1 つの IP アドレスを共有する、1 つまたは複数のコンテナで構成されています。しかしこれは、アプリケーションにとっては、接続先の Pod を発見し識別することを難しくします。例えば、フロントエンドアプリケーションがバックエンドのワークロードへの接続を試みる時が、これに該当します。

Kubernetes Service は、Pod のセットへのネットワーク接続を可能にします。Kubernetes Service は、Pod のセットを、抽象的な Service 名や IP アドレスに接続します。この抽象化によって他のアプリケーションは、単に Service 名を参照することで、Pod のセット上で実行しているアプリケーションへ到達できるようになります。このことは、他のアプリケーションが、個別の Pod に設定された IP アドレスをもはや知る必要がないことを意味します。外部のアプリケーションおよびエンドユーザーは、Kubernetes Service についても、あたかもクラスターの外部に公開されているかのようにアクセスできます。

Kubernetes Service は、Pod へのインターフェースを外部に公開する責務を持ちます。これにより、クラスター内、または外部プロセスと Service との間のネットワークアクセスを可能にします。後者については、例えば ClusterIPNodePortLoadBalancerExternalName といった異なる Kubernetes Service タイプを通じて実現できます。Kubernetes Service は、TCP (デフォルト設定) 、UDP、SCTP の各プロトコルをサポートします。

AWS における Kubernetes Service の利用で、最も人気のある方法の 1 つが、LoadBalancer タイプによるものです。AWS クラウドでは、AWS Load Balancer Controller を使用して、クラスター内で実行している Service 向けにインターネットから送信される TCP および UDP トラフィックをルーティングする Network Load Balancer を設定できます。例えばインスタンスや IP といったターゲットの種類は、アノテーションを使用して設定できます。

UDP ベースのアプリケーションを EKS へデプロイする際の重要な考慮事項

UDP ベースのアプリケーションの Kubernetes へのデプロイは、TCP ベースのアプリケーションと全く同じです。

以下は、TCP ベースの Service の設定例です。

apiVersion: v1
kind: Service
metadata:
  name: sample-tcp-svc
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-type: "external"
    service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: "ip"   
    service.beta.kubernetes.io/aws-load-balancer-healthcheck-healthy-threshold: 3
    service.beta.kubernetes.io/aws-load-balancer-healthcheck-unhealthy-threshold: 3
    service.beta.kubernetes.io/aws-load-balancer-healthcheck-timeout: 10
    service.beta.kubernetes.io/aws-load-balancer-healthcheck-interval: 10      
    service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
spec:
  type: LoadBalancer
  selector:
    app: sample-tcp-app
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80

加えて、AWS Load Balancer Controller による UDP ベースの Service のための Network Load Balancer の設定プロセスは、TCP ベースの Service と同じです。しかしながら、留意すべき重要な違いがあります。

ヘルスチェック

UDP では、ヘルスチェックを実施できません。しかしながら、(TCP、HTTP、HTTPS といった) 他のプロトコルと、ターゲットによって指定されたポートとを組み合わせて実施できます。もしアプリケーションが UDP 通信のみをサポートしている場合、TCP ポートを外部に公開するためにサイドカーコンテナを使用できます。サイドカーのおかげで、TCP ベースのヘルスチェックをサポートするためにアプリケーションの挙動を変えることなく、アプリケーションコンテナに集中できます。例えば、この記事で参照するサンプルのゲームサーバーは、NGINX をサイドカーとして動かし、ポート 80 をリッスンします。

TCP のヘルスチェックは、UDP ベースのアプリケーションコンテナとは統合されず、サイドカーで処理されます。一方でサイドカーは、UDP ベースのアプリケーションコンテナの失敗を認識しません。そのため、ゲームサーバーのヘルスチェックの精度を高めるため、Kubernetes の Liveness Probe (訳注: コンテナ内で実行しているアプリケーションが再起動を必要としているかどうかをチェックするメカニズム) を設定することをお勧めします。この記事の Liveness Probe のために使用するスクリプトでは、UDP ベースのゲームサーバーのステータスをモニタリングし、UDP ヘルスチェックが失敗した時に、サイドカーの NGINX サーバーを停止します。ターゲットグループで UnhealthyThresholdCount 連続失敗数のしきい値に達した場合、リクエストは他の正常動作している Pod へリダイレクトされます。Liveness Probe の実行間隔は、Network Load Balancer のヘルスチェックの実行間隔よりも短くする必要があります。Network Load Balancer の UnhealthyThresholdCount 連続失敗数 (デフォルトでは 3 に設定) は、迅速なフェイルオーバーのために変更できます。

以下は、TCP ヘルスチェックを実施する、UDP ベースの Service の設定例です。

apiVersion: v1
kind: Service
metadata:
  name: sample-udp-svc
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-type: "external"
    service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: "ip"
    service.beta.kubernetes.io/aws-load-balancer-healthcheck-port: "80"
    service.beta.kubernetes.io/aws-load-balancer-healthcheck-protocol: TCP 
    service.beta.kubernetes.io/aws-load-balancer-healthcheck-healthy-threshold: 3
    service.beta.kubernetes.io/aws-load-balancer-healthcheck-unhealthy-threshold: 3
    service.beta.kubernetes.io/aws-load-balancer-healthcheck-timeout: 10
    service.beta.kubernetes.io/aws-load-balancer-healthcheck-interval: 10   
    service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
spec:
  selector:
    app: sample-udp-app
  ports:
    - protocol: UDP
      port: 8081
      targetPort: 8081
  type: LoadBalancer

ここで、Kubernetes 上で実行している UDP ベースのゲームサーバーのアーキテクチャコンポーネントを確認してみましょう。加えて、マルチプレイヤーのゲームサーバーの UDP トラフィックを EKS クラスターにルーティングする方法を紹介します。

アーキテクチャ

サンプルのコネクションレスな UDP ベースのゲームサーバーをデプロイするために使用するアーキテクチャは、下記のコンポーネントで構成されます。

  • Amazon EKS クラスター、および Arm ベースの AWS Graviton2 プロセッサを搭載した Amazon EC2 C6g インスタンスで構成されたノードグループ
  • Kubernetes クラスター用の AWS Elastic Load Balancing を管理する AWS Load Balancer Controller
  • LoadBalancer タイプの Kubernetes Service を使用して EKS にデプロイされた、UDP ベースのゲームサーバー
  • ポート 80 を外部に公開するゲームサーバーと併存する、サイドカーとしてデプロイされた NGINX コンテナ。
  • ゲームサーバーは、Liveness Probe として使用するために、udp-health-probe を含みます。
  • AWS Load Balancer Controller を介してプロビジョニングされた、単一のターゲットグループと関連付けられた UDP リスナーを持つ Network Load Balancer。このターゲットグループは、IP アドレスでターゲットを登録し、TCP プロトコル (ポート 80) を使用してターゲットのヘルスチェックを実施するよう設定されています。

始めてみよう

前提条件

  • 管理者権限を持つ AWS アカウント。この記事では、管理者権限を持つ AWS アカウントを既に持っている前提で話を進めます。
  • コマンドラインツール。Mac および Linux ユーザーは、最新の AWS CLIkubectleksctlgit をワークステーションにインストールする必要があります。
  • ゲームサーバーをインストールし起動するために、GitHub の containerized-game-servers リポジトリをワークステーションへクローンします。

アプリケーションのイメージを登録する

このステップでは、ゲームサーバーおよびサイドカーの NGINX コンテナのイメージをビルドし、Amazon Elastic Container Registry (Amazon ECR) に登録します。上記の前提条件でクローンした containerized-game-servers リポジトリのローカルで下記コマンドを実行し、 udp-nlb-sample ディレクトリへ移動します。

cd containerized-game-servers/udp-nlb-sample

環境変数に AWS リージョンと AWS アカウント ID を設定するために、下記コマンドを実行します。

export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --output text --query Account)
export AWS_REGION=us-west-2

ゲームサーバーのイメージをビルドし登録する

次のステップでは、ゲームサーバーの Docker イメージをビルドし、ECR リポジトリへ登録します。このステップでは、Arm ベースのイメージを作成します。

Docker の Buildx CLI プラグインは、複数の CPU アーキテクチャのイメージを作成するプロセスを簡略化します。もし macOS または Windows 上で Docker Desktop のバージョン 2.1.0 以上を実行している場合、Buildx およびクロスプラットフォームのイメージをビルドするのに必要なすべての機能を使用できるようになっています。DEB または RPM パッケージ管理コマンドを使用して Docker Linux パッケージをインストールした場合、Buildx も含まれています。

(訳注: ローカルのワークステーションで Docker を起動し、下記コマンドを実行することで、ゲームサーバーのイメージをビルドし登録できます。また、failed to solve with frontend dockerfile.v0: failed to create LLB definition: unexpected status code [manifests latest]: 403 Forbidden のエラーが出る場合、export DOCKER_BUILDKIT=0 を実行してから、再度下記コマンドを実行してください。)

cd containerized-game-servers/udp-nlb-sample/stk
sh ecr-repos.sh
sh build.sh

サイドカーのイメージをビルドし登録する

NGINX をサイドカーコンテナとして実行しゲームサーバーと併存させ、Network Load Balancer による TCP ターゲットヘルスチェックをサポートします。まず、Arm ベースの NGINX イメージを ECR リポジトリに登録する必要があります。

(訳注: ローカルのワークステーションで Docker を起動し、下記コマンドを実行することで、サイドカーのイメージをビルドし登録できます。)

cd containerized-game-servers/udp-nlb-sample/nginx-static-sidecar/
sh ecr-repos.sh
sh build.sh

EKS クラスターを作成する

eksctl を使用して、クラスターを作成します。この例では、最新バージョンの eksctl を使用していることをご確認ください。加えて下記コマンドは、Arm ベースの AWS Graviton2 プロセッサを搭載したインスタンスで構成されたマネージドノードグループを作成します。

cd containerized-game-servers/udp-nlb-sample
eksctl create cluster -f eks-arm64-cluster-spec.yaml

AWS Load Balancer Controller をデプロイする

AWS Load Balancer Controller は、Kubernetes クラスター内の AWS Elastic Load Balancing を管理する責務を持ちます。AWS Load Balancer Controller をデプロイするには、EKS ユーザーガイドに概説されている手順に従ってください。

ゲームサーバーをデプロイする

サンプルのゲームサーバーは、LoadBalancer タイプの Kubernetes Service として設定されています。デプロイ時、AWS Load Balancer Controller は、インターネット向けの Network Load Balancer を、ターゲットタイプを「IP」、リスナープロトコルを「UDP」としてプロビジョニングします。このデモでは、Pod ネットワーキングに AWS VPC CNI を使用しています。VPC CNI は、ノードの ENI のセカンダリ IP アドレスを介して、Pod の IP への直接アクセスをサポートします。もし別の CNI を使用している場合、Pod の IP へのダイレクトルーティングをサポートしていることをご確認ください。

このデモのために、NGINX をサイドカーとして使用します。このデモに含まれる udp-health-probe.py スクリプトは、ゲームサーバーの Liveness Probe として設定されています。Liveness Probe は、このスクリプトを定期的な間隔で実行してゲームサーバーが正常動作していることを検証し、ヘルスチェックを実施しているサイドカーの NGINX プロセスに通知することなくゲームサーバーを再起動することを支援します。結果として、ゲームサーバーとサイドカーはプロセス名前空間を共有することをお勧めします。Liveness Probe は、このスクリプトを定期実行してゲームサーバーのヘルスチェックを行い、UDP ポートが使用不可になった場合、SIGKILL シグナルをサイドカーの NGINX コンテナに送信します。Network Load Balancer のターゲットヘルスチェックが失敗した場合、到達する UDP トラフィックは、正常動作している Pod へリダイレクトされます。

環境変数 AWS_REGION および AWS_ACCOUNT_ID が正しく設定されていることをご確認ください。

cd containerized-game-servers/nginx-static-sidecar/
cat stknlb-static-tcphealth-sidecar.yaml | envsubst | kubectl apply -f -

上記コマンド実行後、全ての Pod のステータスが Running になるまでお待ちください。

kubectl get Pods --selector=app=stknlb --watch
出力結果
NAME                     READY   STATUS    RESTARTS   AGE
stknlb-8c59f46d8-ln558   2/2     Running   0          3m33s

ゲームサーバーをテストする

supertuxkart をダウンロードすることで、ローカルのワークステーションのゲームサーバーをテストできます。supertuxkart のオンラインモードを使用して、直前のステップで作成した Network Load Balancer の URL に接続します。

次に、EXTERNAL-IP の URL とポート 8081 を入力して、ゲームロビーに移動します。他のプレイヤーがゲームロビーに入ってくるのを待ち、「Start race」を選択してゲームを開始します。

クリーンアップ

今後の課金を避けるために、この記事で作成した全てのリソースを削除しましょう。下記コマンドは、クラスターに加えてノードグループも削除します。

eksctl delete cluster --name arm-us-west-2

まとめ

この記事では、低レイテンシーのニーズを満たすために、Network Load Balancer の裏側で、コネクションレスな UDP ベースのアプリケーションを拡張する方法を説明しました。サイドカーを使用することで、UDP リスナーを使用する Network Load Balancer と共に TCP ターゲットヘルスチェックを可能にする方法を説明しました。

containerized-game-servers の GitHub リポジトリをご覧いただくことで、Mutating Webhook を使用してデプロイするパターンについて詳しく知ることができます。本稿執筆時点 (2022 年 2 月 22 日現在) では、AWS の Network Load Balancer は、IPv4 ターゲットの UDP のみをサポートしています。私たちは引き続き、Network Load Balancer に UDP プロトコルとデュアルスタックのアドレスタイプのサポートを追加して、UDP トラフィックを IPv6 の Kubernetes クラスターにルーティングする方法を説明する例を公開予定です。AWS Containers Roadmap にアクセスし、フィードバック、新しい機能の提案、ロードマップのレビューをお願いします。

翻訳はプロフェッショナルサービスの伊藤が担当しました。原文はこちらです。