Amazon EKS でのリアルタイム推論のベストプラクティスクラスターセットアップガイド - Amazon EKS

このページの改善にご協力ください

このユーザーガイドに貢献するには、すべてのページの右側のペインにある「GitHub でこのページを編集する」リンクを選択してください。

Amazon EKS でのリアルタイム推論のベストプラクティスクラスターセットアップガイド

序章

このガイドでは、リアルタイムオンライン推論ワークロード用に最適化された Amazon Elastic Kubernetes Service (EKS) クラスターをセットアップするための実践的な演習を提供します。全体にわたって、AWS エキスパートが厳選したベストプラクティスが組み込まれています。モデル、アクセラレーター、スケーリングに関する AWS のベストプラクティスに沿った、厳選されたドライバー、インスタンスタイプ、設定のセットである、独自の EKS クイックスタートアーキテクチャを使用します。このアプローチにより、クラスター設定を選択するタスクを回避できるため、事前設定済みの動作するクラスターをすばやく起動して実行できます。その過程で、サンプルワークロードをデプロイしてセットアップを検証し、アーキテクチャの主要な概念 (CPU にバインドされたタスクを GPU 負荷の高いコンピューティングから切り離すなど) を説明しながら、一般的な質問 (AL2023 ではなく Bottlerocket AMI を選択する理由など) に回答し、クラスターの機能を拡張するための次のステップの概要を説明します。

このガイドは、機械学習 (ML) および人工知能 (AI) エンジニア、プラットフォーム管理者、オペレーター、および AWS と EKS エコシステムを初めて使用するデータ/AI スペシャリスト向けに特化して設計されており、Kubernetes に精通していることを前提としていますが、EKS の使用経験はなくてもかまいません。これは、リアルタイムオンライン推論ワークロードを起動して実行するために必要な手順とプロセスを理解するのに役立つように設計されています。このガイドでは、GPU リソースのプロビジョニング、モデルアーティファクトのストレージの統合、AWS サービスへのセキュアアクセスの有効化、推論エンドポイントの公開など、単一ノード推論クラスターの作成に不可欠な要素について説明します。全体では、不正検出、リアルタイムチャットボット、顧客フィードバックシステムの感情分析など、ユーザー向けアプリケーションに適した低レイテンシーで回復力のある設計を重点的に取り上げています。

このガイドでは、G5 EC2 インスタンスを使用した規範的な基本の開始点を設定することのみに焦点を当てます。AWS Inferentia 固有のクラスター設定やエンドツーエンドのワークフローが必要な場合は、機械学習のための Amazon EKS で AWS Inferentia インスタンスを使用する または Amazon EKS で AI/ML の使用を開始するためのリソース のワークショップを参照してください。

[開始する前に]

始める前に、次のタスクを実行していることを確認してください。

アーキテクチャ

リアルタイムオンライン推論とは、トレーニング済みの機械学習モデルを使用して、最小限のレイテンシーで受信データストリーム対する予測または出力を生成するプロセスを指します。たとえば、ユーザー入力に応じて、リアルタイムの不正検出、画像の分類、ナレッジグラフの生成が可能になります。リアルタイムオンライン推論システムのアーキテクチャは、CPU にバインドされたウェブトラフィック処理を GPU 負荷の高い AI 計算から切り離すことで、ユーザー向けアプリケーションで低レイテンシーの機械学習予測を実現します。このプロセスは通常、大規模なアプリケーションエコシステム内に存在し、バックエンド、フロントエンド、ベクトル、モデルサービスが含まれることが多く、独立したスケーリング、並列開発、障害に対する回復力を実現する特殊なコンポーネントに焦点を当てています。専用 GPU ハードウェアで推論タスクを分離し、API や WebSocket などのインターフェイスを活用することで、高い同時実行性、トランスフォーマーなどのモデルの高速処理、フロントエンドを介したユーザーインタラクションが可能になります。ベクトルデータベースと検索拡張生成 (RAG) パイプラインは、リアルタイム推論システムで大きな役割を果たすことがよくありますが、これらのコンポーネントはこのガイドでは説明していません。少なくとも、一般的なアーキテクチャにはたいてい次のものが含まれます。

  • フロントエンドサービス: ユーザー向けインターフェイスとして機能し、クライアント側のロジックの処理、動的コンテンツのレンダリングを行い、リアルタイムのインタラクションを促進にします。バックエンドサービスと通信して推論リクエストを開始し、結果を表示します。多くの場合、バックエンドサービスへのリクエストを開始します。バックエンドサービスでは、ストリーミング更新に WebSocket を使用し、構造化データ交換に API を使用します。このサービスは、静的アセット用に AWS CloudFront などのコンテンツ配信ネットワーク (CDN) でホストしたり、ウェブサーバーから直接提供したりできるため、通常、専用のロードバランサーは必要ありません。動的コンテンツに必要なスケーリングは、Auto Scaling グループを介して処理されます。

  • バックエンドサービス: アプリケーションのオーケストレーターとして機能し、ユーザー認証、データ検証、サービス調整などのビジネスロジックを管理します (RESTful エンドポイントの API 経由や、永続的な接続の場合は WebSocket 経由など)。推論サービスと通信し、マルチコア CPU と RAM を個別にスケールして、GPU に依存することなく大量のウェブトラフィックを処理します。また、多くの場合、受信リクエストを複数のインスタンスに分散するためにロードバランサー (AWS Application Load Balancer や Network Load Balancer など) が必要になります。同時実行性の高いシナリオでは特に必要です。イングレスコントローラーは、セキュリティとトラフィックシェーピングを強化するために、外部アクセスとルーティングルールをさらに管理できます。

  • 推論サービス: AI 計算のコアとして機能し、カスタムモデルまたはオープンソースモデルを使用してベクトル埋め込み、ナレッジ抽出、モデル推論 (バッチリクエストでは API、リアルタイムストリーミングでは WebSocket を介して公開されるものなど) を実行するのに十分な VRAM (DistilBERT などのモデルでは 8~12 GB など) を持つ GPU で実行されます。この分離によって依存関係の競合を防ぎ、ダウンタイムなしでのモデルの更新と、複数の同時リクエストの負荷分散による水平スケーリングが可能になります。モデルサービスを効果的に公開するために、通常は、ロードバランサーの背後に配置して、レプリケートされたインスタンス間で GPU にバインドされたワークロードを分散します。一方、イングレスリソースまたはイングレスコントローラー (AWS の ALB Ingress Controller など) は外部ルーティング、SSL ターミネーション、パスベースの転送を処理し、個々の GPU に負荷をかけずに安全かつ効率的にアクセスできるようにします。

ソリューションの概要

リアルタイムオンライン推論システムには、予測不可能な大量のトラフィックバーストを処理しながら超低レイテンシーを実現できる、高性能で回復力のあるアーキテクチャが必要です。このソリューションの概要では、エンドユーザーに最小限の遅延でライブデータの即時予測を提供する機械学習モデルをクラスターでホストして管理できるように、作成する Amazon EKS クラスターで次の AWS コンポーネントがどのように連携するのか説明します。

  • Amazon G5 EC2 インスタンス – GPU 負荷の高い推論タスクでは、g5.xlarge および g5.2xlarge G5 EC2 インスタンスタイプを使用します。このインスタンスタイプは、24GB のメモリ (FP16 で 80 億個のパラメータなど) と単一の NVIDIA A10G GPU を備えています。NVIDIA Ampere アーキテクチャに基づくこれらの GPU は、NVIDIA A10G Tensor Core GPU と第 2 世代 AMD EPYC プロセッサを搭載し、4 ~ 8 個の vCPU、最大 10 Gbps のネットワーク帯域幅、250 ~ 450 GB のローカル NVMe SSD ストレージをサポートしているため、複雑なモデルに対して高速なデータ移動と計算能力を実現しており、低レイテンシーで高スループットの推論タスクに最適です。EC2 インスタンスタイプの選択はアプリケーション固有であり、モデル (画像、動画、テキストモデルなど) やレイテンシーとスループットの要件によって異なります。たとえば、画像モデルや動画モデルを使用している場合は、P5 EC2 インスタンスを使用すると最適なリアルタイムレイテンシーを実現できます。G5 EC2 インスタンスは、すぐに起動して実行できることから開始点に適しており、パフォーマンスベンチマークテストを通じてワークロードに適しているか評価できるため、G5 EC2 インスタンスから始めることをお勧めします。より高度なケースについては、G6 EC2 インスタンスを検討してください。

  • Amazon EC2 M7g インスタンス – データ前処理、API リクエスト処理、Karpenter コントローラーのホスティング、アドオン、その他のシステムコンポーネントなどの CPU 負荷の高いタスクでは、m5.xlarge M7g EC2 インスタンスタイプを使用します。M7g インスタンスは、4 基の vCPU、16 GB のメモリ、最大 12.5 Gbps のネットワーク帯域幅を備え、AWS Graviton3 プロセッサを搭載している、ARM ベースのインスタンスです。EC2 インスタンスタイプの選択はアプリケーション固有であり、ワークロードのコンピューティング、メモリ、スケーラビリティの要件によって異なります。コンピューティング最適化ワークロードでは、C7g EC2 インスタンスを検討することをお勧めします。これも Graviton3 プロセッサを使用しますが、特定のユースケースでは M7g インスタンスよりも高いコンピューティングパフォーマンスを発揮するように最適化されています。また、新しい C8g EC2 インスタンス (利用可能な場合) は、C7g インスタンスよりも最大 30% 優れたコンピューティングパフォーマンスを提供します。コスト効率と幅広いワークロード (アプリケーションサーバー、マイクロサービス、ゲームサーバー、中規模データストアなど) との互換性のために M7g EC2 インスタンスから始めて、パフォーマンスベンチマークテストを通じてワークロードに適しているか評価することをお勧めします。

  • Amazon S3 Mountpoint CSI ドライバー – 複数のポッドが GPU を共有する単一 GPU インスタンスのワークロード (GPU リソースを利用するために同じノードで複数のポッドがスケジュールされているなど) では、Mountpoint S3 CSI ドライバーを使用してメモリ使用量を最適化します。これは、コスト重視で複雑さの少ないセットアップでの大規模モデル推論などのタスクに不可欠です。Amazon S3 バケットを Kubernetes クラスターで使用できる POSIX のようなファイルシステムとして公開します。これにより、推論ポッドはモデルアーティファクト (モデルの重みなど) を最初にダウンロードする必要なくメモリに直接読み取り、標準ファイル操作を使用してデータセットを入力できます。さらに、S3 は実質的に無制限のストレージ容量を持ち、データ集約型の推論ワークロードを高速化します。ストレージ CSI ドライバーの選択はアプリケーション固有であり、ワークロードのスループットとレイテンシーの要件によって異なります。FSx for OpenZFS CSI ドライバーは、ランダム I/O またはノード間で完全 POSIX 準拠の共有永続ボリュームに対してサブミリ秒のレイテンシーを提供しますが、スケーラビリティ、大規模なデータセットのコストの削減、読み取り負荷の高い推論パターン (ストリーミングモデル入力など) に対応する S3 マネージドオブジェクトストレージの組み込みという特長がある Mountpoint S3 CSI ドライバーから始めて、パフォーマンスベンチマークテストを通じてワークロードに適しているか評価することをお勧めします。

  • EKS Pod Identity Agent – AWS サービスへのアクセスを有効にするために、単一のサービスプリンシパルを使用して Amazon EKS クラスター内のポッドレベルの IAM ロールの関連付けを簡単に行える EKS Pod Identity Agent を使用します。EKS Pod Identity は、クラスターごとに個別の OIDC プロバイダーを利用するのではなく、単一のサービスプリンシパル (pods.eks.amazonaws.com) を利用することで、従来のサービスアカウントの IAM ロール (IRSA) アプローチに代わる効率的な方法を提供します。これにより、アクセス許可の割り当てが簡単になります。さらに、ロールを複数のクラスターで再利用でき、IAM ロールセッションタグターゲット IAM ロールなどの高度な機能をサポートしています。

  • EKS Node Monitoring Agent – 推論サービスの継続的な可用性と信頼性を確保するために、自動修復機能がある EKS ノードモニタリングエージェントを使用します。自動修復は、異常なノードを自動的に検出して置き換え、ダウンタイムを最小限に抑える機能です。拡張ヘルスチェック (KernelReady、NetworkingReady など) を使用して、ハードウェア、カーネル、ネットワーク、ストレージの問題がないかノードを継続的にモニタリングします。GPU ノードの場合、アクセラレーター固有の障害を検出し、異常なノードを遮断して適切な修復を開始して、GPU の一時的な問題が解決されるまで 10 分待機してから、永続的な障害の場合は 30 分後にノードを交換します。

  • Bottlerocket AMI – EKS クラスターのセキュリティ強化基盤を提供するために、Bottlerocket AMI を使用します。Bottlerocket AMI には、コンテナの実行に必要な必須コンポーネントのみ含まれていて起動時間が最小限であるため、高速スケーリングが可能です。ノード AMI の選択はアプリケーション固有であり、ワークロードのカスタマイズ、セキュリティ、スケーラビリティの要件によって異なります。AL2023 AMI はホストレベルのインストールとカスタマイズで極めて高い柔軟性を発揮しますが (追加のノード設定なしで PV/PVC に専用キャッシュディレクトリを指定するなど)、フットプリントが小さく、コンテナ化されたワークロード (マイクロサービス、推論サーバー、スケーラブル API など) に対する最適化が組み込まれている Bottlerocket AMI から始めて、パフォーマンスベンチマークテストを通じてワークロードに適しているか評価することをお勧めします。

  • AWS Load Balancer Controller (LBC) – リアルタイム推論エンドポイントを公開するために、AWS Load Balancer Controller を使用します。これは、Kubernetes Ingress リソースと Service リソースに応じて、HTTP/HTTPS トラフィックの場合は Application Load Balancer (ALB)、TCP/UDP トラフィックの場合は Network Load Balancer (NLB) を自動的にプロビジョニングして管理し、推論モデルと外部クライアントの統合を実現します。さらに、複数のポッドまたはノードに推論リクエストを分散するパスベースのルーティングなどの機能をサポートし、トラフィックスパイク発生時のスケーラビリティを確保し、接続の多重化やヘルスチェックなどの AWS ネイティブ最適化によりレイテンシーを最小限に抑えます。

1. EKS クラスターを作成する

このステップでは、AWS CloudFormation を利用した eksctl ClusterConfig テンプレートを使用して、CPU ノードとマネージド型ノードグループを持つクラスターを作成します。CPU ノードのみを持つクラスターを初期化すると、Karpenter のみを使用して CPU 集約型ノードと GPU ノードを管理できるようになり、後のステップで作成する Karpenter NodePool を使用した最適なリソース割り当てが可能になります。リアルタイム推論ワークロードをサポートするために、EKS Bottlerocket AMI、EKS Node Monitoring Agent、EKS Pod Identity Agent、Mountpoint S3 CSI ドライバー、AWS Load Balancer Controller (LBC)、kube-proxyvpc-cni、および coredns ドライバーが含まれるクラスターをプロビジョニングします。m7g.xlarge インスタンスは、Karpenter コントローラー、アドオン、その他のシステムコンポーネントのホスティングなど、CPU システムタスクに使用されます。

デフォルトでは、eksctl は CIDR ブロックが 192.168.0.0/16 のクラスター専用の VPC を作成します。VPC には 3 つのパブリックサブネットと 3 つのプライベートサブネットがあり、それぞれが 3 つの異なるアベイラビリティーゾーン (または us-east-1 リージョン内の 2 つの AZ) に分散されます。これは Kubernetes ワークロードをデプロイするのに最適な方法です。また、このテンプレートはインターネットゲートウェイをデプロイし、ルートテーブルのデフォルトルートを介してパブリックサブネットへのインターネットアクセスを提供します。また、パブリックサブネットのいずれかに 1 つの NAT ゲートウェイをデプロイします。プライベートサブネットのルートテーブルのデフォルトルートは、NAT ゲートウェイを介してアウトバウンドトラフィックをルーティングし、インターネットアクセスを実現します。この設定の詳細については、「プライベートサブネットにノードをデプロイする」を参照してください。

認証情報を確認する

AWS CLI 認証情報が有効であり、AWS サービスで認証できるかどうかを確認します。

aws sts get-caller-identity

成功した場合、CLI は AWS ID (UserId、Account、および Arn) の詳細を返します。

インスタンスの可用性を確認する

G5 インスタンスタイプを使用できないリージョンがあります。最寄りのリージョンを確認します。例:

aws ec2 describe-instance-types --instance-types g5.xlarge g5.2xlarge --region us-east-1

成功した場合は、指定したリージョンで G5 インスタンスタイプを使用できます。

Bottlerocket AMI を使用できないリージョンがあります。最寄りのリージョンの Bottlerocket AMI ID を取得して確認します。例:

aws ssm get-parameter --name /aws/service/bottlerocket/aws-k8s-1.33/arm64/latest/image_id \ --region us-east-1 --query "Parameter.Value" --output text

成功した場合は、Bottlerocket AMI は指定したリージョンで使用できます。

環境を準備する

まず、新しいターミナルウィンドウで次の環境変数を設定します。: サンプルプレースホルダーは、クラスター名、目的のリージョン、Karpenter リリースバージョンKubernetes バージョンなど、固有の値に置き換えてください。

ヒント

一部の変数 (${AWS_REGION}${K8S_VERSION} など) はブロックの早い段階で定義し、一貫性を保って繰り返しを避けるために、後のコマンドで参照します。これらの値が適切にエクスポートされ、その後の定義で使用できるように、コマンドを順番に実行してください。

export TEMPOUT="$(mktemp)" export K8S_VERSION=1.33 export KARPENTER_VERSION="1.5.0" export AWS_REGION="us-east-1" export EKS_CLUSTER_NAME="eks-rt-inference-${AWS_REGION}" export S3_BUCKET_NAME="eks-rt-inference-models-${AWS_REGION}-$(date +%s)" export NVIDIA_BOTTLEROCKET_AMI="$(aws ssm get-parameter --name /aws/service/bottlerocket/aws-k8s-${K8S_VERSION}-nvidia/x86_64/latest/image_id --query Parameter.Value --output text)" export STANDARD_BOTTLEROCKET_AMI="$(aws ssm get-parameter --name /aws/service/bottlerocket/aws-k8s-${K8S_VERSION}/arm64/latest/image_id --query Parameter.Value --output text)" export AWS_ACCOUNT_ID="$(aws sts get-caller-identity --query Account --output text)" export ALIAS_VERSION="$(aws ssm get-parameter --name "/aws/service/eks/optimized-ami/${K8S_VERSION}/amazon-linux-2023/x86_64/standard/recommended/image_id" --query Parameter.Value | xargs aws ec2 describe-images --query 'Images[0].Name' --image-ids | sed -r 's/^.*(v[[:digit:]]+).*$/\1/')"

必要なロールとポリシーを作成する

Karpenter では、EC2 インスタンスを Kubernetes ワーカーノードとして管理するために、特定の IAM ロールとポリシー (Karpenter コントローラー IAM ロール、インスタンスプロファイル、ポリシーなど) が必要です。これらのロールを使用して、EC2 インスタンスの起動と終了、リソースのタグ付け、他の AWS サービスの操作などのアクションを実行します。Karpenter の cloudformation.yaml を使用して Karpenter ロールとポリシーを作成します。

curl -fsSL https://raw.githubusercontent.com/aws/karpenter-provider-aws/v${KARPENTER_VERSION}/website/content/en/preview/getting-started/getting-started-with-karpenter/cloudformation.yaml > "${TEMPOUT}" \ && aws cloudformation deploy \ --stack-name "Karpenter-${EKS_CLUSTER_NAME}" \ --template-file "${TEMPOUT}" \ --capabilities CAPABILITY_NAMED_IAM \ --parameter-overrides "ClusterName=${EKS_CLUSTER_NAME}"

AWS LBC には、イングレスリソースの場合は ALB、LoadBalancer タイプのサービスの場合は NLB を作成するなど、AWS ロードバランサーをプロビジョニングと管理を行うためのアクセス許可が必要です。このアクセス許可ポリシーは、クラスターの作成時に指定します。クラスターの作成時に、ClusterConfig で eksctl を使用してサービスアカウントを作成します。LBC IAM ポリシーを作成します。

aws iam create-policy \ --policy-name AWSLoadBalancerControllerIAMPolicy \ --policy-document "$(curl -fsSL https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.13.0/docs/install/iam_policy.json)"

Mountpoint S3 CSI ドライバーをインストールすると、その DaemonSet ポッドは実行の際にサービスアカウントを使用するように設定されます。Mountpoint for Mountpoint S3 CSI ドライバーには、このガイドの後半で作成する Amazon S3 バケットを操作するためのアクセス許可が必要です。このアクセス許可ポリシーは、クラスターの作成時に指定します。クラスターの作成時に、ClusterConfig で eksctl を使用してサービスアカウントを作成します。S3 IAM ポリシーを作成します。

aws iam create-policy \ --policy-name S3CSIDriverPolicy \ --policy-document "{\"Version\": \"2012-10-17\", \"Statement\": [{\"Effect\": \"Allow\", \"Action\": [\"s3:GetObject\", \"s3:PutObject\", \"s3:AbortMultipartUpload\", \"s3:DeleteObject\", \"s3:ListBucket\"], \"Resource\": [\"arn:aws:s3:::${S3_BUCKET_NAME}\", \"arn:aws:s3:::${S3_BUCKET_NAME}/*\"]}]}"

: この名前のロールが既に存在する場合は、ロールに別の名前を付けます。このステップで作成するロールは、クラスターと S3 バケットに固有です。

クラスターを作成する

このテンプレートでは、eksctl は EKS Pod Identity、Node Monitoring Agent、CoreDNS、Kubeproxy、VPC CNI プラグインの Kubernetes サービスアカウントを自動的に作成します。現在、Mountpoint S3 CSI ドライバーは EKS Pod Identity では使用できないため、サービスアカウントの IAM ロール (IRSA) と OIDC エンドポイントを作成します。さらに、AWS Load Balancer Controller (LBC) のサービスアカウントを作成します。Bottlerocket ノードにアクセスするために、eksctl は Bottlerocket 用の AmazonSSMManagedInstanceCore を自動的にアタッチして、SSM を介した安全なシェルセッションを許可します。

環境変数を設定するのと同じターミナルで、次のコマンドブロックを実行してクラスターを作成します。

eksctl create cluster -f - <<EOF --- apiVersion: eksctl.io/v1alpha5 kind: ClusterConfig metadata: name: ${EKS_CLUSTER_NAME} region: ${AWS_REGION} version: "${K8S_VERSION}" tags: karpenter.sh/discovery: ${EKS_CLUSTER_NAME} # Add more tags if needed for billing iam: # Creates an OIDC endpoint and IRSA service account for the Mountpoint S3 CSI Driver # Uses the S3 CSI Driver policy for permissions withOIDC: true podIdentityAssociations: # Creates the pod identity association and service account # Uses the Karpenter controller IAM policy for permissions - namespace: "kube-system" serviceAccountName: karpenter roleName: ${EKS_CLUSTER_NAME}-karpenter permissionPolicyARNs: - arn:aws:iam::${AWS_ACCOUNT_ID}:policy/KarpenterControllerPolicy-${EKS_CLUSTER_NAME} # Creates the pod identity association and service account # Uses the AWS LBC policy for permissions - namespace: kube-system serviceAccountName: aws-load-balancer-controller createServiceAccount: true roleName: AmazonEKSLoadBalancerControllerRole permissionPolicyARNs: - arn:aws:iam::${AWS_ACCOUNT_ID}:policy/AWSLoadBalancerControllerIAMPolicy iamIdentityMappings: - arn: "arn:aws:iam::${AWS_ACCOUNT_ID}:role/KarpenterNodeRole-${EKS_CLUSTER_NAME}" username: system:node:{{EC2PrivateDNSName}} groups: - system:bootstrappers - system:nodes managedNodeGroups: # Creates 2 CPU nodes for lightweight system tasks - name: ${EKS_CLUSTER_NAME}-m7-cpu instanceType: m7g.xlarge amiFamily: Bottlerocket desiredCapacity: 2 minSize: 1 maxSize: 10 labels: role: cpu-worker # Enable automatic Pod Identity associations for VPC CNI Driver, coreDNS, kube-proxy addonsConfig: autoApplyPodIdentityAssociations: true addons: # Installs the S3 CSI Driver addon and creates IAM role # Uses the S3 CSI Driver policy for IRSA permissions - name: aws-mountpoint-s3-csi-driver attachPolicyARNs: - "arn:aws:iam::${AWS_ACCOUNT_ID}:policy/S3CSIDriverPolicy" - name: eks-pod-identity-agent - name: eks-node-monitoring-agent - name: coredns - name: kube-proxy - name: vpc-cni EOF

このプロセスが完了するまでに数分かかります。ステータスをモニタリングする場合は、AWS CloudFormation コンソールを参照してください。

2. クラスターノードとポッドの状態を確認する

いくつかのヘルスチェックを実行して、クラスターの準備が整っていることを確認します。前のコマンドが完了したら、次のコマンドを使用して、インスタンスタイプを表示し、CPU システムノードが Ready 状態になったことを確認します。

kubectl get nodes -L node.kubernetes.io/instance-type

正常な出力は次の例のようになります。

NAME                             STATUS   ROLES    AGE     VERSION               INSTANCE-TYPE
ip-192-168-35-103.ec2.internal   Ready    <none>   12m     v1.33.0-eks-802817d   m7g.xlarge
ip-192-168-7-15.ec2.internal     Ready    <none>   12m     v1.33.0-eks-802817d   m7g.xlarge

次のコマンドを使用して、すべての Pod Identity の関連付けと、クラスターの名前空間内のサービスアカウントにロールをマッピングする方法を検証します。

eksctl get podidentityassociation --cluster ${EKS_CLUSTER_NAME} --region ${AWS_REGION}

出力には、Karpenter (「karpenter」) と AWS LBC (「aws-load-balancer-controller」) の IAM ロールが表示されます。

DaemonSets が使用可能であることを確認します。

kubectl get daemonsets -n kube-system

正常な出力は次の例のようになります。

NAME                           DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR          AGE
aws-node                       3       3       3     3          3         <none>                 12m
dcgm-server                    0       0       0     0          0         kubernetes.io/os=linux 12m
eks-node-monitoring-agent      3       3       3     3          3         kubernetes.io/os=linux 12m
eks-pod-identity-agent         3       3       3     3          3         <none>                 12m
kube-proxy                     3       3       3     3          3         <none>                 12m
s3-csi-node                    2       2       2     2          2         kubernetes.io/os=linux 12m

クラスターにすべてのアドオンがインストールされていることを確認します。

eksctl get addons --cluster ${EKS_CLUSTER_NAME} --region ${AWS_REGION}

正常な出力は次の例のようになります。

NAME                           VERSION              STATUS    ISSUES    IAMROLE                                           UPDATE AVAILABLE    CONFIGURATION VALUES    POD IDENTITY ASSOCIATION ROLES
aws-mountpoint-s3-csi-driver   v1.15.0-eksbuild.1   ACTIVE    0    arn:aws:iam::143095308808:role/eksctl-eks-rt-inference-us-east-1-addon-aws-m-Role1-RAUjk4sJnc0L
coredns                        v1.12.1-eksbuild.2   ACTIVE    0
eks-node-monitoring-agent      v1.3.0-eksbuild.2    ACTIVE    0
eks-pod-identity-agent         v1.3.7-eksbuild.2    ACTIVE    0
kube-proxy                     v1.33.0-eksbuild.2   ACTIVE    0
metrics-server                 v0.7.2-eksbuild.3    ACTIVE    0
vpc-cni                        v1.19.5-eksbuild.1   ACTIVE    0

3. Karpenter をインストールする

Karpenter コントローラーを CPU ワーカーノード (cpu-worker) にインストールしてコストを最適化し、GPU リソースを節約します。これを「kube-system」名前空間にインストールし、クラスターの作成時に定義した「karpenter」サービスアカウントを指定します。さらに、このコマンドは、クラスター名と CPU ノードのスポットインスタンス中断キューを設定します。Karpenter は IRSA を使用してこの IAM ロールを引き受けます。

# Logout of helm registry before pulling from public ECR helm registry logout public.ecr.aws # Install Karpenter helm upgrade --install karpenter oci://public.ecr.aws/karpenter/karpenter --version "${KARPENTER_VERSION}" --namespace "kube-system" --create-namespace \ --set "settings.clusterName=${EKS_CLUSTER_NAME}" \ --set "settings.interruptionQueue=${EKS_CLUSTER_NAME}" \ --set controller.resources.requests.cpu=1 \ --set controller.resources.requests.memory=1Gi \ --set controller.resources.limits.cpu=1 \ --set controller.resources.limits.memory=1Gi \ --set serviceAccount.annotations."eks\.amazonaws\.com/role-arn"="arn:aws:iam::${AWS_ACCOUNT_ID}:role/${EKS_CLUSTER_NAME}-karpenter" \ --wait

正常な出力は次の例のようになります。

Release "karpenter" does not exist. Installing it now.
Pulled: public.ecr.aws/karpenter/karpenter:1.5.0
Digest: sha256:9a155c7831fbff070669e58500f68d7ccdcf3f7c808dcb4c21d3885aa20c0a1c
NAME: karpenter
LAST DEPLOYED: Thu Jun 19 09:57:06 2025
NAMESPACE: kube-system
STATUS: deployed
REVISION: 1
TEST SUITE: None

Karpenter が実行されていることを確認します。

kubectl get pods -n kube-system -l app.kubernetes.io/name=karpenter

正常な出力は次の例のようになります。

NAME                       READY   STATUS    RESTARTS   AGE
karpenter-555895dc-865bc   1/1     Running   0          5m58s
karpenter-555895dc-j7tk9   1/1     Running   0          5m58s

4. Karpenter NodePool をセットアップする

このステップでは、相互に排他的な CPU と GPU の Karpenter NodePool を設定します。NodePool 仕様の limits フィールドは、各 NodePool がプロビジョニングされたすべてのノードで消費できる最大合計リソース (CPU、メモリ、GPU など) を制限するものであり、これらの制限を超えた場合に追加でノードプロビジョニングが行われないようにします。NodePool は幅広いインスタンスカテゴリ (cg など) をサポートしていますが、特定のインスタンスタイプキャパシティタイプ、リソース制限を指定すると、オンデマンドワークロードのコストをより簡単に見積もることができます。これらの NodePool では、G5 インスタンスファミリー内でさまざまなインスタンスタイプのセットを使用します。これにより、Karpenter はポッドリソースリクエストに基づいて最適なインスタンスタイプを自動的に選択し、NodePool の合計制限を尊重しながらリソース使用率を最適化できます。詳細については、「NodePool を作成する」を参照してください。

GPU NodePool をセットアップする

この NodePool では、GPU 機能を備えたノードのプロビジョニングを管理するためのリソース制限を設定します。これらの制限は、プール内のすべてのノードの合計リソースを制限し、合計で最大 10 個のインスタンスを許可するように設計されています。各インスタンスは、vCPU の合計数が 80 を超えず、メモリ合計量が 320 GiB を超えず、かつ GPU の合計数が 10 を超えない限り、g5.xlarge (4 基の vCPU、16 GiB の メモリ、1 基の GPU) または g5.2xlarge (8 基の vCPU、32 GiB のメモリ、1 基の GPU) のいずれかになります。たとえば、プールは 10 個の g5.2xlarge インスタンス (80 基の vCPU、320 GiB、10 基の GPU)、または 10 個の g5.xlarge インスタンス (40 基の vCPU、160 GiB、10 基の GPU)、または 5 個の g5.xlarge と 5 個の g5.2xlarge (60 基の vCPU、240 GiB、10 基の GPU) などの組み合わせをプロビジョニングでき、リソースの制約を尊重しながらワークロードの需要に基づいて柔軟性を確保できます。

さらに、Bottlerocket AMI の Nvidia バリアントの ID を指定します。最後に、30 分後に空のノードを削除するように中断ポリシーを設定し (consolidateAfter: 30m)、ノードの最大存続期間を 30 日 (expireAfter: 720h) に設定することによって、コストを最適化し、GPU 負荷の高いタスクに対してノードヘルスを維持します。詳細については、「中断の影響を受けやすいワークロードの Karpenter 統合を無効にする」と「ttlSecondsAfterFinished を使用して Kubernetes ジョブを自動クリーンアップする」を参照してください。

cat <<EOF | envsubst | kubectl apply -f - apiVersion: karpenter.sh/v1 kind: NodePool metadata: name: gpu-a10g-inference-g5 spec: template: metadata: labels: role: gpu-worker gpu-type: nvidia-a10g spec: requirements: - key: node.kubernetes.io/instance-type operator: In values: ["g5.xlarge", "g5.2xlarge"] - key: "karpenter.sh/capacity-type" operator: In values: ["on-demand"] taints: - key: nvidia.com/gpu value: "true" effect: NoSchedule nodeClassRef: name: gpu-a10g-inference-ec2 group: karpenter.k8s.aws kind: EC2NodeClass expireAfter: 720h limits: cpu: "80" memory: "320Gi" nvidia.com/gpu: "10" disruption: consolidationPolicy: WhenEmpty consolidateAfter: 30m --- apiVersion: karpenter.k8s.aws/v1 kind: EC2NodeClass metadata: name: gpu-a10g-inference-ec2 spec: amiFamily: Bottlerocket amiSelectorTerms: - id: ${NVIDIA_BOTTLEROCKET_AMI} role: "KarpenterNodeRole-${EKS_CLUSTER_NAME}" subnetSelectorTerms: - tags: karpenter.sh/discovery: "${EKS_CLUSTER_NAME}" securityGroupSelectorTerms: - tags: karpenter.sh/discovery: "${EKS_CLUSTER_NAME}" tags: nvidia.com/gpu: "true" EOF

正常な出力は次の例のようになります。

nodepool.karpenter.sh/gpu-a10g-inference-g5 created
ec2nodeclass.karpenter.k8s.aws/gpu-a10g-inference-ec2 created

NodePool が作成され、正常であることを確認します。

kubectl get nodepool gpu-a10g-inference-g5 -o yaml

ValidationSucceeded: TrueNodeClassReady: TrueReady: True などの status.conditions を探して、NodePool が正常であることを確認します。

CPU NodePool をセットアップする

この NodePool では、中程度の CPU ワークロード (100 ~ 200 ポッドなど) と一般的な AWS vCPU クォータ (128 ~ 1152 など) に合わせて、約 50 個のインスタンスをサポートするように制限を設定します。この制限は、NodePool が 50 個の m7.xlarge インスタンスにスケールアップする必要があると仮定して計算され、その場合、CPU はインスタンスあたり 4 基の vCPU × 50 個のインスタンス = 200 基の vCPU、メモリはインスタンスあたり 16 GiB × 50 個のインスタンス = 800 GiB になります。これらの制限は、プール内のすべてのノードの合計リソースを制限するように設計されており、vCPU の合計数が 200 を超えず、かつメモリ合計量が 800 GiB を超えない限り、最大 50 個の m7g.xlarge インスタンス (それぞれ 4 基の vCPU と 16 GiB のメモリを持つ) を使用できます。

さらに、Bottlerocket AMI の標準バリアントの ID を指定します。最後に、60 分後に空のノードを削除するように中断ポリシーを設定し (consolidateAfter: 60m)、ノードの最大存続期間を 30 日 (expireAfter: 720h) に設定することによって、コストを最適化し、GPU 負荷の高いタスクに対してノードヘルスを維持します。詳細については、「中断の影響を受けやすいワークロードの Karpenter 統合を無効にする」と「ttlSecondsAfterFinished を使用して Kubernetes ジョブを自動クリーンアップする」を参照してください。

cat <<EOF | envsubst | kubectl apply -f - apiVersion: karpenter.sh/v1 kind: NodePool metadata: name: cpu-inference-m7gxlarge spec: template: metadata: labels: role: cpu-worker spec: requirements: - key: node.kubernetes.io/instance-type operator: In values: ["m7g.xlarge"] - key: karpenter.sh/capacity-type operator: In values: ["on-demand"] taints: - key: role value: cpu-intensive effect: NoSchedule nodeClassRef: name: cpu-inference-m7gxlarge-ec2 group: karpenter.k8s.aws kind: EC2NodeClass expireAfter: 720h limits: cpu: "200" memory: "800Gi" disruption: consolidationPolicy: WhenEmpty consolidateAfter: 60m --- apiVersion: karpenter.k8s.aws/v1 kind: EC2NodeClass metadata: name: cpu-inference-m7gxlarge-ec2 spec: amiFamily: Bottlerocket amiSelectorTerms: - id: ${STANDARD_BOTTLEROCKET_AMI} role: "KarpenterNodeRole-${EKS_CLUSTER_NAME}" subnetSelectorTerms: - tags: karpenter.sh/discovery: "${EKS_CLUSTER_NAME}" securityGroupSelectorTerms: - tags: karpenter.sh/discovery: "${EKS_CLUSTER_NAME}" EOF

正常な出力は次の例のようになります。

nodepool.karpenter.sh/cpu-inference-m7gxlarge created
ec2nodeclass.karpenter.k8s.aws/cpu-inference-m7gxlarge-ec2 created

NodePool が作成され、正常であることを確認します。

kubectl get nodepool cpu-inference-m7gxlarge -o yaml

ValidationSucceeded: TrueNodeClassReady: TrueReady: True などの status.conditions を探して、NodePool が正常であることを確認します。

5. GPU ポッドをデプロイして GPU を公開する

Kubernetes が GPU デバイスを Kubernetes クラスターに公開できるようにするには、Nvidia デバイスプラグインが必要です。通常はプラグインを DaemonSet としてデプロイする必要がありますが、Bottlerocket AMI は AMI の一部としてプラグインをプリインストールします。つまり、Bottlerocket AMI を使用する場合、Nvidia デバイスプラグインの DaemonSet をデプロイする必要はありません。詳細については、「Kubernetes デバイスプラグインで GPU を公開する」を参照してください。

サンプルポッドをデプロイする

Karpenter は動的に動作し、ワークロード (ポッド) が GPU リソースをリクエストすると、GPU ノードをプロビジョニングします。ポッドが GPU をリクエストして使用できることを確認するには、制限内で nvidia.com/gpu リソースをリクエストするポッドをデプロイします (nvidia.com/gpu: 1 など)。これらのラベルの詳細については、「よく知られているラベルを使用して GPU 要件のあるワークロードをスケジュールする」を参照してください。

cat <<EOF | envsubst | kubectl apply -f - apiVersion: v1 kind: Pod metadata: name: gpu-nvidia-smi spec: restartPolicy: OnFailure tolerations: - key: "nvidia.com/gpu" operator: "Exists" effect: "NoSchedule" nodeSelector: role: gpu-worker # Matches GPU NodePool's label containers: - name: cuda-container image: nvidia/cuda:12.9.1-base-ubuntu20.04 command: ["nvidia-smi"] resources: limits: nvidia.com/gpu: 1 requests: nvidia.com/gpu: 1 EOF

正常な出力は次の例のようになります。

pod/gpu-ndivia-smi created

ポッドのステータスが「Pending」、「ContainerCreating」、「Running」、「Completed」であるかどうかを確認します。

kubectl get pod gpu-nvidia-smi -w

ポッドのノードが GPU NodePool に属していることを確認します。

kubectl get node $(kubectl get pod gpu-nvidia-smi -o jsonpath='{.spec.nodeName}') -o custom-columns="Name:.metadata.name,Nodepool:.metadata.labels.karpenter\.sh/nodepool"

正常な出力は次の例のようになります。

Name                             Nodepool
ip-192-168-83-245.ec2.internal   gpu-a10g-inference-g5

ポッドのログを確認します。

kubectl logs gpu-nvidia-smi

正常な出力は次の例のようになります。

Thu Jul 17 04:31:33 2025 +---------------------------------------------------------------------------------------+ | NVIDIA-SMI 570.148.08 Driver Version: 570.148.08 CUDA Version: 12.9 | |-----------------------------------------+----------------------+----------------------+ | GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC | | Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. | | | | MIG M. | |=========================================+======================+======================| | 0 NVIDIA A10G On | 00000000:00:1E.0 Off | 0 | | 0% 30C P8 9W / 300W | 0MiB / 23028MiB | 0% Default | | | | N/A | +---------------------------------------------------------------------------------------+ +---------------------------------------------------------------------------------------+ | Processes: | | GPU GI CI PID Type Process name GPU Memory | | ID ID Usage | |=======================================================================================| | No running processes found | +---------------------------------------------------------------------------------------+

6. (オプション) デプロイ用のモデルアーティファクトを準備してアップロードする

このステップでは、まずモデルの重みを Amazon S3 バケットにアップロードしてから、リアルタイム画像分類用のモデルサービスをデプロイします。デモ用に、NVIDIA GPU と TensorRT を使用して画像に対する低レイテンシーの推論をサポートする NVIDIA の GPUNet に含まれるオープンソース GPUNet-0 ビジョンモデルを使用します。このモデルは ImageNet で事前にトレーニングされており、写真または動画ストリーム内のオブジェクトをその場で分類でき、1,190 万個のパラメータを持つ小さなモデルと見なされます。

環境をセットアップする

GPUNet-0 モデルの重みをダウンロードするために、このステップでは、ローカルマシンにインストールされている NVIDIA の NGC カタログと Docker にアクセスする必要があります。以下の手順に従って無料アカウントをセットアップし、NGC CLI を設定します。

  • 無料の NGC アカウントにサインアップし、NGC ダッシュボードから API キーを生成します (ユーザーアイコン > [セットアップ] > [API キーの生成] > [パーソナルキーの生成] > [NGC カタログ])。

  • NGC CLI (Linux/macOS/Windows) をダウンロードしてインストールし、ngc config set を使用して CLI を設定します。プロンプトが表示されたら API キーを入力します。組織を nvidia に設定し、Enter キーを押して他はデフォルトを受け入れます。成功すると、Successfully saved NGC configuration to /Users/your-username/.ngc/config のように表示されます。

サービスアカウントのアクセス許可を確認する

始める前に、Kubernetes サービスアカウントのアクセス許可を確認します。

kubectl get serviceaccount s3-csi-driver-sa -n kube-system -o yaml

クラスターの作成時に、S3CSIDriverPolicy を IAM ロールにアタッチし、サービスアカウント (「s3-csi-driver-sa」) に注釈を付けました。Mountpoint S3 CSI ドライバーポッドは、S3 を操作するときに IAM ロールのアクセス許可を継承します。正常な出力は次の例のようになります。

apiVersion: v1 kind: ServiceAccount metadata: annotations: eks.amazonaws.com/role-arn: arn:aws:iam::143095308808:role/eksctl-eks-rt-inference-us-east-1-addon-aws-m-Role1-fpXXjRYdKN8r creationTimestamp: "2025-07-17T03:55:29Z" labels: app.kubernetes.io/component: csi-driver app.kubernetes.io/instance: aws-mountpoint-s3-csi-driver app.kubernetes.io/managed-by: EKS app.kubernetes.io/name: aws-mountpoint-s3-csi-driver name: s3-csi-driver-sa namespace: kube-system resourceVersion: "2278" uid: 50b36272-6716-4c68-bdc3-c4054df1177c

許容範囲を追加する

S3 CSI ドライバーは、すべてのノードで DaemonSet として実行されます。ポッドは、それらのノードで CSI ドライバーを使用して S3 ボリュームをマウントします。テイントがある GPU ノードでスケジュールできるようにするには、DaemonSet に許容範囲を追加します。

kubectl patch daemonset s3-csi-node -n kube-system --type='json' -p='[{"op": "add", "path": "/spec/template/spec/tolerations/-", "value": {"key": "nvidia.com/gpu", "operator": "Exists", "effect": "NoSchedule"}}]'

正常な出力は次の例のようになります。

daemonset.apps/s3-csi-node patched

モデルの重みを S3 にアップロードする

このステップでは、Amazon S3 バケットを作成し、NVIDIA GPU Cloud (NGC) から GPUNet-0 モデルの重みをダウンロードして、バケットにアップロードします。これらの重みには、アプリケーションが推論するために実行時にアクセスします。

Amazon S3 バケットを作成します。

aws s3 mb s3://${S3_BUCKET_NAME} --region ${AWS_REGION}

バケットの S3 バージョニングを有効にし、意図しない削除や上書きによって即時かつ永続的なデータ損失が発生しないようにします。

aws s3api put-bucket-versioning --bucket ${S3_BUCKET_NAME} --versioning-configuration Status=Enabled

バケットにライフサイクルルールを適用して、上書きまたは削除されたオブジェクトのバージョンは最新でなくなった時点から 14 日後に削除し、期限切れの削除マーカーを削除し、不完全なマルチパートアップロードは 7 日後に削除するように設定します。詳細については、「S3 ライフサイクル設定の例」を参照してください。

aws s3api put-bucket-lifecycle-configuration --bucket $S3_BUCKET_NAME --lifecycle-configuration '{"Rules":[{"ID":"LifecycleRule","Status":"Enabled","Filter":{},"Expiration":{"ExpiredObjectDeleteMarker":true},"NoncurrentVersionExpiration":{"NoncurrentDays":14},"AbortIncompleteMultipartUpload":{"DaysAfterInitiation":7}}]}'

NGC から GPUNet-0 モデルの重みをダウンロードします。例えば、macOS では次のようになります。

ngc registry model download-version nvidia/dle/gpunet_0_pyt_ckpt:21.12.0_amp --dest ~/downloads
注記

このダウンロードコマンドは、オペレーティングシステムに合わせて調整が必要になる場合があります。このコマンドを Linux システムで機能させるには、コマンドの一環でディレクトリを作成する必要があります (例: mkdir ~/downloads)。

正常な出力は次の例のようになります。

{ "download_end": "2025-07-18 08:22:39", "download_start": "2025-07-18 08:22:33", "download_time": "6s", "files_downloaded": 1, "local_path": "/Users/your-username/downloads/gpunet_0_pyt_ckpt_v21.12.0_amp", "size_downloaded": "181.85 MB", "status": "Completed", "transfer_id": "gpunet_0_pyt_ckpt[version=21.12.0_amp]" }

後のステップのアプリケーションコードで想定している名前と一致するようにチェックポイントファイルの名前を変更します (モデル状態ディクショナリを含む標準の PyTorch *.pth.tar チェックポイントであるため、抽出は必要ありません)。

mv ~/downloads/gpunet_0_pyt_ckpt_v21.12.0_amp/0.65ms.pth.tar gpunet-0.pth

AWS CLI で AWS 共通ランタイムを有効にして S3 スループットを最適化します。

aws configure set s3.preferred_transfer_client crt

モデルの重みを S3 バケットにアップロードします。

aws s3 cp gpunet-0.pth s3://${S3_BUCKET_NAME}/gpunet-0.pth

正常な出力は次の例のようになります。

upload: ./gpunet-0.pth to s3://eks-rt-inference-models-us-east-1-1752722786/gpunet-0.pth

モデルサービスを作成する

このステップでは、GPUNet-0 ビジョンモデルを使用して GPU アクセラレーション画像分類用の FastAPI ウェブアプリケーションをセットアップします。アプリケーションは、実行時に Amazon S3 からモデルの重みをダウンロードし、キャッシュ用に NVIDIA のリポジトリからモデルアーキテクチャを取得して、HTTP 経由で ImageNet クラスラベルをダウンロードします。アプリケーションには画像の前処理変換が含まれており、ステータスチェック用のルート GET と画像 URL を受け入れる POST /predict エンドポイントという 2 つのエンドポイントが公開されます。

PyTorch で FastAPI を使用してモデルを提供し、コンテナ化されたセットアップで実行時に Amazon S3 から重みをロードすることによって、プロトタイプ作成と Kubernetes デプロイを短時間で行います。最適化されたバッチ処理や高スループットエンジンなどその他の方法については、「機械学習モデルを提供する」を参照してください。

アプリケーションの作成

model-testing などのアプリケーションファイル用のディレクトリを作成し、ディレクトリをそれに変更して、次のコードを app.py という名前の新しいファイルに追加します。

import os import torch import json import requests from fastapi import FastAPI, HTTPException from PIL import Image from io import BytesIO, StringIO import torchvision.transforms as transforms from torch.nn.functional import softmax import warnings from contextlib import redirect_stdout, redirect_stderr import argparse import boto3 app = FastAPI() # Suppress specific warnings from the model code (quantization is optional and unused here) warnings.simplefilter("ignore", UserWarning) device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # Load model code from cache (if present) # Use backed cache directory torch.hub.set_dir('/cache/torch/hub') # Allowlist for secure deserialization (handles potential issues in older checkpoints) torch.serialization.add_safe_globals([argparse.Namespace]) # Load the model architecture only on container startup (changed to pretrained=False) # Precision (FP32 for full accuracy, could be 'fp16' for speed on Ampere+ GPUs) with redirect_stdout(StringIO()), redirect_stderr(StringIO()): gpunet = torch.hub.load('NVIDIA/DeepLearningExamples:torchhub', 'nvidia_gpunet', pretrained=False, model_type='GPUNet-0', model_math='fp32') # Download weights from S3 if not present, then load them model_path = os.getenv('MODEL_PATH', '/cache/torch/hub/checkpoints/gpunet-0.pth') os.makedirs(os.path.dirname(model_path), exist_ok=True) # Ensure checkpoints dir exists if not os.path.exists(model_path): s3 = boto3.client('s3') s3.download_file(os.getenv('S3_BUCKET_NAME'), 'gpunet-0.pth', model_path) checkpoint = torch.load(model_path, map_location=device, weights_only=True) gpunet.load_state_dict(checkpoint['state_dict']) # Move to GPU/CPU gpunet.to(device) gpunet.eval() # Preprocessing preprocess = transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ]) # Load ImageNet labels labels_url = "https://s3.amazonaws.com/deep-learning-models/image-models/imagenet_class_index.json" response = requests.get(labels_url) json_data = json.loads(response.text) labels = [json_data[str(i)][1].replace('_', ' ') for i in range(1000)] # Required, FastAPI root @app.get("/") async def hello(): return {"status": "hello"} # Serve model requests @app.post("/predict") async def predict(image_url: str): try: response = requests.get(image_url) response.raise_for_status() img = Image.open(BytesIO(response.content)).convert("RGB") input_tensor = preprocess(img).unsqueeze(0).to(device) with torch.no_grad(): output = gpunet(input_tensor) probs = softmax(output, dim=1)[0] top5_idx = probs.topk(5).indices.cpu().numpy() top5_probs = probs.topk(5).values.cpu().numpy() results = [{ "label": labels[idx], "probability": float(prob) } for idx, prob in zip(top5_idx, top5_probs)] return {"predictions": results} except Exception as e: raise HTTPException(status_code=400, detail=str(e))

Dockerfile を作成する

次の Dockerfile は、NVIDIA Deep Learning Examples for Tensor Cores GitHub リポジトリの GPUNet モデルを利用して、アプリケーションのコンテナイメージを作成します。

ランタイムのみの PyTorch ベースを使用し、キャッシュクリーンアップを追加した必須パッケージのみをインストールして、モデルコードを事前にキャッシュし、コンテナイメージでの重みの「ベーキング」を回避してプルと更新をより高速に行えるようにすることで、コンテナイメージのサイズを削減します。詳細については、「コンテナイメージサイズを削減する」を参照してください。

app.py と同じディレクトリに、Dockerfile を作成します。

FROM pytorch/pytorch:2.4.0-cuda12.4-cudnn9-runtime # Install required system packages required for git cloning RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/* # Install application dependencies RUN pip install --no-cache-dir fastapi uvicorn requests pillow boto3 timm==0.5.4 # Pre-cache the GPUNet code from Torch Hub (without weights) # Clone the repository containing the GPUNet code RUN mkdir -p /cache/torch/hub && \ cd /cache/torch/hub && \ git clone --branch torchhub --depth 1 https://github.com/NVIDIA/DeepLearningExamples NVIDIA_DeepLearningExamples_torchhub COPY app.py /app/app.py WORKDIR /app CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "80"]

アプリケーションをテストする

app.py および Dockerfile と同じディレクトリから、AMD64 アーキテクチャをターゲットにして、推論アプリケーションのコンテナイメージを構築します。

docker build --platform linux/amd64 -t gpunet-inference-app .

AWS 認証情報の環境変数を設定し、オプションで AWS セッショントークンを設定します。例:

export AWS_REGION="us-east-1" export AWS_ACCESS_KEY_ID=ABCEXAMPLESCUJFEIELSMUHHAZ export AWS_SECRET_ACCESS_KEY=123EXAMPLEMZREoQXr8XkiicsOgWDQ5TpUsq0/Z

コンテナをローカルで実行し、S3 アクセスの環境変数として AWS 認証情報を挿入します。例:

docker run --platform linux/amd64 -p 8080:80 \ -e S3_BUCKET_NAME=${S3_BUCKET_NAME} \ -e AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} \ -e AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} \ -e AWS_DEFAULT_REGION=${AWS_REGION} \ gpunet-inference-app

正常な出力は次の例のようになります。

INFO:     Started server process [1]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:80 (Press CTRL+C to quit)

新しいターミナルウィンドウで、クエリパラメータとして公開イメージ URL を含むサンプル POST リクエストを送信して、推論エンドポイントをテストします。

curl -X POST "http://localhost:8080/predict?image_url=http://images.cocodataset.org/test-stuff2017/000000024309.jpg"

正常な出力は、このように上位 5 件の予測が含まれている JSON レスポンスである必要があります (実際のラベルと確率はイメージとモデルの精度によってわずかに異なる場合があります)。

{"predictions":[{"label":"desk","probability":0.28885871171951294},{"label":"laptop","probability":0.24679335951805115},{"label":"notebook","probability":0.08539070934057236},{"label":"library","probability":0.030645888298749924},{"label":"monitor","probability":0.02989606373012066}]}

「Ctrl + C」を使用してアプリケーションを終了します。

コンテナを Amazon ECR にプッシュする

このステップでは、GPUNet-0 モデルサービスのコンテナイメージを Amazon Elastic Container Registry (ECR) にアップロードし、Amazon EKS でデプロイできるようにします。このプロセスでは、イメージを保存する新しい ECR リポジトリを作成し、ECR で認証してから、コンテナイメージにタグ付けしてレジストリにプッシュします。

まず、このガイドの冒頭で説明した、環境変数を設定するディレクトリに戻ります。例:

cd ..

Amazon ECR でリポジトリを作成します。

aws ecr create-repository --repository-name gpunet-inference-app --region ${AWS_REGION}

Amazon ECR にログインします。

aws ecr get-login-password --region ${AWS_REGION} | docker login --username AWS --password-stdin ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com

正常な出力は次の例のようになります。

Login Succeeded

イメージにタグを付けます。

docker tag gpunet-inference-app:latest ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/gpunet-inference-app:latest

Amazon ECR リポジトリにイメージをプッシュします。

docker push ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/gpunet-inference-app:latest

この最後のステップは、完了するまでに数分かかることがあります。

7. (オプション) モデルサービスを公開する

このステップでは、AWS Load Balancer Controller (LBC) を使用して Amazon EKS の外部でリアルタイム推論モデルサービスを公開します。これには、LBC をセットアップし、Mountpoint S3 CSI ドライバーを使用して Amazon S3 からモデルの重みを永続ボリュームとしてマウントし、GPU アクセラレーションアプリケーションポッドをデプロイして、Application Load Balancer (ALB) をプロビジョニングするためのサービスとイングレスを作成し、エンドポイントをテストする手順が含まれます。

まず、AWS LBC に対する Pod Identity の関連付けを確認し、サービスアカウントが必要な IAM ロールに適切にリンクされていることを確認します。

eksctl get podidentityassociation --cluster ${EKS_CLUSTER_NAME} --namespace kube-system --service-account-name aws-load-balancer-controller

正常な出力は次の例のようになります。

ASSOCIATION ARN                                                    NAMESPACE    SERVICE ACCOUNT NAME        IAM ROLE ARN    OWNER ARN
arn:aws:eks:us-east-1:143095308808:podidentityassociation/eks-rt-inference-us-east-1/a-buavluu2wp1jropya    kube-system     aws-load-balancer-controller    arn:aws:iam::143095308808:role/AmazonEKSLoadBalancerControllerRole

クラスターセキュリティグループにタグ付けする

AWS Load Balancer Controller は、Karpenter のセキュリティグループ選択のタグキー karpenter.sh/discovery: "${EKS_CLUSTER_NAME}" を持つ単一のセキュリティグループのみをサポートします。eksctl を使用してクラスターを作成する場合、デフォルトのクラスターセキュリティグループ ("kubernetes.io/cluster/<cluster-name>: owned" タグ付き) には karpenter.sh/discovery タグが自動的にタグ付けされません。このタグは、Karpenter がこのセキュリティグループを検出して、プロビジョニングするノードにアタッチするために不可欠です。このセキュリティグループをアタッチすると、AWS Load Balancer Controller (LBC) との互換性が確保されるため、これらの手順でモデルサービスなど、イングレスを介して公開されるサービスのインバウンドトラフィックルールを自動的に管理できます。

クラスターの VPC ID をエクスポートします。

CLUSTER_VPC_ID="$(aws eks describe-cluster --name ${EKS_CLUSTER_NAME} --query cluster.resourcesVpcConfig.vpcId --output text)"

クラスターのデフォルトのセキュリティグループをエクスポートします。

CLUSTER_SG_ID="$(aws ec2 describe-security-groups --filters Name=vpc-id,Values=$CLUSTER_VPC_ID Name=tag-key,Values=kubernetes.io/cluster/${EKS_CLUSTER_NAME} --query 'SecurityGroups[].[GroupId]' --output text)"

karpenter.sh/discovery タグをデフォルトのクラスターセキュリティグループに追加します。これにより、CPU と GPU の EC2NodeClass セレクタで使用できるようになります。

aws ec2 create-tags --resources ${CLUSTER_SG_ID} --tags Key=karpenter.sh/discovery,Value=${EKS_CLUSTER_NAME}

タグが追加されていることを確認します。

aws ec2 describe-security-groups --group-ids ${CLUSTER_SG_ID} --query "SecurityGroups[].Tags"

結果には、タグとクラスター名を含む次のものが表示されます。例:

{ "Key": "karpenter.sh/discovery", "Value": "eks-rt-inference-us-east-1" }

AWS Load Balancer Controller (LBC) をセットアップする

AWS LBC は、Amazon EKS での AI/ML ワークロードへのイングレストラフィックを管理し、推論エンドポイントまたはデータ処理パイプラインへのアクセスを確保するために不可欠です。AWS Application Load Balancer (ALB) および Network Load Balancer (NLB) と統合することで、LBC は、大規模言語モデル、コンピュータビジョンモデル、リアルタイム推論サービスを実行しているコンテナ化されたアプリケーションなどにトラフィックを動的にルーティングします。クラスターの作成時にサービスアカウントと Pod Identity Association を既に作成しているため、クラスター設定 (aws-load-balancer-controller) で定義されているものと一致するように serviceAccount.name を設定します。

AWS 所有の eks-charts Helm チャートリポジトリを追加します。

helm repo add eks https://aws.github.io/eks-charts

ローカル Helm リポジトリを最新のチャートで更新します。

helm repo update eks

Helm を使用して AWS LBC をデプロイし、EKS クラスター名を指定して、作成済みのサービスアカウントを参照します。

helm install aws-load-balancer-controller eks/aws-load-balancer-controller \ -n kube-system \ --set clusterName=${EKS_CLUSTER_NAME} \ --set serviceAccount.create=false \ --set serviceAccount.name=aws-load-balancer-controller

正常な出力は次の例のようになります。

NAME: aws-load-balancer-controller
LAST DEPLOYED: Wed Jul 9 15:03:31 2025
NAMESPACE: kube-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
AWS Load Balancer controller installed!

永続ボリュームにモデルをマウントする

このステップでは、Mountpoint for Amazon S3 CSI ドライバーがサポートする PersistentVolume (PV) を使用して、Amazon S3 バケットからモデルの重みをマウントします。これにより、Kubernetes ポッドはローカルファイルとして S3 オブジェクトにアクセスでき、リソースを大量に消費するエフェメラルポッドストレージまたは init コンテナへのダウンロードが不要になります。そのため、大規模なマルチギガバイトモデルの重みに最適です。

PV はバケットルート全体 (volumeAttributes でパスが指定されていない場合) をマウントし、複数のポッドによる同時読み取り専用アクセスをサポートするとともに、推論のためにコンテナ内のモデルの重み (/models/gpunet-0.pth) などのファイルを公開します。これにより、ファイルがマウントを介して存在するため、アプリケーション (app.py) の「ダウンロード」フォールバックがトリガーされません。モデルをコンテナイメージから切り離すことで、イメージを再構築することなく、共有アクセスとモデルバージョンの個別更新が可能になります。

PersistentVolume (PV) を作成する

PersistentVolume (PV) リソースを作成して、モデルの重みを含む S3 バケットをマウントし、実行時にファイルをダウンロードせずに複数のポッドの読み取り専用アクセスを有効にします。

cat <<EOF | envsubst | kubectl apply -f - apiVersion: v1 kind: PersistentVolume metadata: name: s3-model-pv spec: capacity: storage: 5Gi # Ignored by the driver; can be any value accessModes: - ReadOnlyMany # Read only persistentVolumeReclaimPolicy: Retain storageClassName: "" # Required for static provisioning claimRef: namespace: default # Adjust if you prefer a different namespace name: s3-model-pvc mountOptions: - allow-other # Enables multi-user access (useful for non-root pods) - region ${AWS_REGION} # Optional, include if your bucket is in a different region than the cluster csi: driver: s3.csi.aws.com volumeHandle: gpunet-model-volume # Must be unique across all PVs volumeAttributes: bucketName: ${S3_BUCKET_NAME} EOF

PersistentVolumeClaim (PVC) を作成する

PV にバインドする PersistentVolumeClaim (PVC) を作成し、マウントされた S3 モデルデータへの読み取り専用アクセスをリクエストします。

cat <<EOF | envsubst | kubectl apply -f - apiVersion: v1 kind: PersistentVolumeClaim metadata: name: s3-model-pvc spec: accessModes: - ReadOnlyMany storageClassName: "" # Required for static provisioning resources: requests: storage: 5Gi # Ignored, match PV capacity volumeName: s3-model-pv # Bind to the PV created above EOF

アプリケーションをデプロイする

推論アプリケーションを Kubernetes デプロイとしてデプロイし、モデルアクセス用の S3-backed 永続ボリュームをマウントして、GPU ノードセレクタと許容範囲を適用し、モデルパスの環境変数を設定します。このデプロイではモデルパス ("/models/gpunet-0.pth" の env var) が設定されるため、アプリケーション (app.py 内) はこのパスをデフォルトで使用します。デプロイのボリュームマウントが /models (読み取り専用) の場合、ファイルが PVC 経由で既に存在すると、モデルのダウンロードはトリガーされません。

cat <<EOF | envsubst | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: name: gpunet-inference-app spec: replicas: 1 selector: matchLabels: app: gpunet-inference-app template: metadata: labels: app: gpunet-inference-app spec: tolerations: - key: "nvidia.com/gpu" operator: "Exists" effect: "NoSchedule" nodeSelector: role: gpu-worker containers: - name: inference image: ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/gpunet-inference-app:latest ports: - containerPort: 80 env: - name: MODEL_PATH value: "/models/gpunet-0.pth" resources: limits: nvidia.com/gpu: 1 requests: nvidia.com/gpu: 1 volumeMounts: - name: model-volume mountPath: /models readOnly: true volumes: - name: model-volume persistentVolumeClaim: claimName: s3-model-pvc EOF

GPU ノードをまだ使用できない場合、Karpenter がプロビジョニングするのに数分かかります。推論ポッドが「実行中」状態であることを確認します。

kubectl get pods -l app=gpunet-inference-app

正常な出力は次の例のようになります。

NAME                               READY   STATUS    RESTARTS   AGE
gpunet-inference-app-5d4b6c7f8-abcde        1/1     Running   0          2m

Ingress と Load Balancer でサービスを公開する

ClusterIP サービスを作成し、アプリケーションのポートをターゲットにして、EKS クラスター内部で推論デプロイを公開します。

cat <<EOF | envsubst | kubectl apply -f - apiVersion: v1 kind: Service metadata: name: gpunet-model-service spec: type: ClusterIP ports: - port: 80 targetPort: 80 selector: app: gpunet-inference-app EOF

Ingress リソースを作成して、AWS LBC 経由でインターネット向け Application Load Balancer (ALB) をプロビジョニングし、外部トラフィックを推論サービスにルーティングします。

cat <<EOF | envsubst | kubectl apply -f - apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: gpunet-model-ingress annotations: 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: gpunet-model-service port: number: 80 EOF

Application Load Balancer (ALB) がプロビジョニングを完了するまで数分待ちます。Ingress リソースのステータスをモニタリングして、ALB がプロビジョニングされていることを確認します。

kubectl get ingress gpunet-model-ingress

正常な出力は次のようになります (ADDRESS フィールドが入力されています)。

NAME                   CLASS   HOSTS   ADDRESS                                         PORTS   AGE
gpunet-model-ingress   alb     *       k8s-default-gpunetmo-183de3f819-516310036.us-east-1.elb.amazonaws.com   80      6m58s

後続のテストで使用するため、Ingress のステータスから ALB ホスト名を抽出してエクスポートします。

export ALB_HOSTNAME=$(kubectl get ingress gpunet-model-ingress -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')

モデルサービスをテストする

サンプルイメージ URL (COCO データセットからなど) を使用して POST リクエストを送信し、リアルタイム予測をシミュレートすることによって、公開された推論エンドポイントを検証します。

curl -X POST "http://${ALB_HOSTNAME}/predict?image_url=http://images.cocodataset.org/test-stuff2017/000000024309.jpg"

正常な出力は、このように上位 5 件の予測が含まれている JSON レスポンスである必要があります (実際のラベルと確率はイメージとモデルの精度によってわずかに異なる場合があります)。

{"predictions":[{"label":"desk","probability":0.2888975441455841},{"label":"laptop","probability":0.2464350312948227},{"label":"notebook","probability":0.08554483205080032},{"label":"library","probability":0.030612602829933167},{"label":"monitor","probability":0.029896672815084457}]}

必要に応じて、新しい POST リクエストで他のイメージのテストを続行できます。例:

http://images.cocodataset.org/test-stuff2017/000000024309.jpg
http://images.cocodataset.org/test-stuff2017/000000028117.jpg
http://images.cocodataset.org/test-stuff2017/000000006149.jpg
http://images.cocodataset.org/test-stuff2017/000000004954.jpg

結論

このガイドでは、GPU アクセラレーションリアルタイム推論ワークロード用に最適化された Amazon EKS クラスターをセットアップします。G5 EC2 インスタンスを使用してクラスターをプロビジョニングし、Mountpoint S3 CSI ドライバーEKS Pod Identity AgentEKS Node Monitoring AgentBottlerocket AMIAWS Load Balancer Controller (LBC)Karpenter をインストールして CPU と GPU の NodePool を管理しました。NVIDIA デバイスプラグインを使用して GPU スケジューリングを有効にし、モデルアクセス用に PersistentVolume と PersistentVolumeClaim で S3 を設定しました。サンプル GPU ポッドをデプロイし、Amazon S3 で NVIDIA GPUNet-0 モデルのモデルアクセスをセットアップし、ポッドの初期化を有効にして、Application Load Balancer を介して推論サービスを公開することで、セットアップを検証しました。クラスターを最大限に活用するには、自動修復を有効にして EKS Node Monitoring Agent を設定します。GPU のパフォーマンス、レイテンシー、スループット評価などのベンチマークテストを実施して、応答時間を最適化してください。詳細については、「AI/ML ワークロードのモニタリングツールとオブザーバビリティツールを使用する」を参照してください。

クリーンアップ

今後の料金が発生しないようにするには、関連する CloudFormation スタックを手動で削除して、VPC ネットワークを含め、このガイドで作成したすべてのリソースを削除する必要があります。

eksctl で --wait フラグを使用して CloudFormation スタックを削除します。

eksctl delete cluster --region ${AWS_REGION} --name ${EKS_CLUSTER_NAME} --wait

完了すると、以下のようなレスポンス出力が表示されます。

2025-07-29 13:03:55 [✔]  all cluster resources were deleted

Amazon S3 コンソールを使用して、このガイドで作成した Amazon S3 バケットを削除します。