Amazon EKS 即時推論的最佳實務叢集設定指南 - Amazon EKS

協助改進此頁面

若要為本使用者指南貢獻內容,請點選每個頁面右側面板中的在 GitHub 上編輯此頁面連結。

Amazon EKS 即時推論的最佳實務叢集設定指南

簡介

本指南提供實作演練,即設定針對即時線上推論工作負載最佳化的 Amazon Elastic Kubernetes Service (EKS) 叢集,並且還涵蓋了由 AWS 專家精心策劃的最佳實務。它使用了固定的 EKS 快速啟動架構,這是一組精選驅動程式、執行個體類型和組態,符合模型、加速器和擴展的 AWS 最佳實務。此方法可協助您略過選取叢集設定的任務,進而讓您能快速啟動和執行功能正常且預先設定的叢集。在此過程中,我們將部署範例工作負載,以驗證您的設定、解釋重要架構概念 (例如從 GPU 密集型運算解耦 CPU 受限的任務)、解決常見問題 (例如,為什麼選擇 Bottlerocket AMI 而不是 AL2023?),並概述後續步驟以擴展叢集的功能。

本指南專為機器學習 (ML) 和人工智慧 (AI) 工程師、平台管理員、操作人員和初次使用 AWS 和 EKS 生態系統的資料/AI 專家而設計,並且假設他們皆熟悉 Kubernetes,但先前沒有 EKS 經驗。它旨在協助您了解啟動和執行即時線上推論工作負載所需的步驟和程序。本指南說明建立單一節點推論叢集的基本知識,包括佈建 GPU 資源、整合模型成品的儲存、啟用安全的 AWS 服務存取以及公開推論端點。本指南通篇強調了面向使用者的應用程式 (例如詐騙偵測、即時聊天機器人以及客戶意見回饋系統中的情緒分析) 的低延遲和彈性設計。

在本指南中,我們會重點介紹使用 G5 EC2 執行個體設定基礎、規範性的起點。如果您要尋找 AWS Inferentia 特定的叢集組態或端到端工作流程,請參閱 開始使用 Amazon EKS 上的 AI/ML 的資源 中的 搭配 Amazon EKS 使用 AWS Inferentia 執行個體,以進行機器學習 或我們的工作坊。

開始之前

開始之前,請確定您已執行下列任務:

架構

即時線上推論是指使用經過訓練的機器學習模型,以最低延遲在傳入資料串流上產生預測或輸出的程序。例如,它會啟用即時詐騙偵測、影像分類或產生知識圖形,以回應使用者輸入。即時線上推論系統的架構可從 GPU 密集型 AI 運算解耦 CPU 受限的 Web 流量處理,進而在面向使用者的應用程式中提供低延遲機器學習預測。此程序通常會存在於更大的應用程式生態系統中,往往包括後端、前端、向量和模型服務,並且重點關注特殊元件,以實現獨立擴展、平行開發以及靈活應對故障。在專用 GPU 硬體上隔離推論任務及利用 API 和 WebSocket 等介面,可確保高度並行、快速處理變壓器等模型,以及透過前端開展使用者互動。請注意,雖然向量資料庫和檢索增強生成 (RAG) 管道通常在即時推論系統中扮演重要角色,但本指南並未涵蓋這些元件。典型的架構通常至少包括:

  • 前端服務:作為面向使用者的介面,其負責處理用戶端邏輯、轉譯動態內容以及促進即時互動,同時可與後端服務進行通訊以啟動推論請求和顯示結果,通常還會向後端服務發出請求,而該服務會使用 WebSocket 進行串流更新或使用 API 進行結構化資料交換。此服務通常不需要專用負載平衡器,因為它可以託管在 AWS CloudFront 內容交付網路 (CDN) 上,以用於靜態資產,或直接從 Web 伺服器提供,如果動態內容需要,還可透過 Auto Scaling 群組處理擴展。

  • 後端服務:作為應用程式的協調器,可管理商業邏輯,例如使用者身分驗證、資料驗證和服務協調 (例如,透過適用於 RESTful 端點的 API 或確保持久連接的 WebSocket)。它會與推論服務進行通訊、在多核心 CPU 和 RAM 上獨立擴展 (無需依賴 GPU 亦可處理高 Web 流量),而且通常需要負載平衡器 (例如 AWS Application Load Balancer 或 Network Load Balancer) 才能在多個執行個體之間分發傳入請求,尤其是在高度並行的情況下。傳入控制器可以進一步管理外部存取和路由規則,進而提高安全性和流量成形。

  • 推論服務:作為 AI 運算的核心,在具有足夠 VRAM (例如,8-12 GB,適用於 DistilBERT 等模型) 的 GPU 上執行,以使用自訂或開放原始碼模型來執行向量嵌入、知識擷取和模型推論 (例如,透過 API 進行批次請求或透過 WebSocket 實現即時串流)。此隔離可防止相依性衝突、允許模型更新而不發生停機,並針對多個並行請求啟用具有負載平衡的水平擴展。為了有效地公開模型服務,它通常會位於負載平衡器後方,以在複製的執行個體之間分發 GPU 繫結的工作負載,而傳入資源或控制器 (例如 AWS 中的 ALB 傳入控制器) 會處理外部路由、SSL 終止和路徑型轉送,進而確保安全且高效的存取,並且不會讓個別 GPU 不堪負荷。

解決方案概觀

即時線上推論系統需要高效能的彈性架構,可提供極低的延遲,同時處理無法預測的大量流量暴增。此解決方案概觀解釋說明下列 AWS 元件如何在我們將建立的 Amazon EKS 叢集中協同運作,以確保我們的叢集能夠託管和管理機器學習模型,進而為最終使用者提供對即時資料的即時預測,同時將延遲降至最低。

  • Amazon G5 EC2 執行個體:對於 GPU 密集型推論任務,我們會使用 g5.xlarge 和 g5.2xlarge G5 EC2 執行個體類型,這些類型配備單 (1) 個 NVIDIA A10G GPU 和 24GB 記憶體 (例如 FP16 具有 80 億個參數)。這些 GPU 以 NVIDIA Ampere 架構為基礎,採用 NVIDIA A10G Tensor 核心 GPU 和第二代 AMD EPYC 處理器,可支援 4-8 個 vCPU、高達 10 Gbps 的網路頻寬,以及 250-450 GB 的本機 NVMe SSD 儲存,從而確保為複雜的模型提供快速資料移動和運算能力,使其能夠更加適合低延遲、高輸送量的推論任務。選擇 EC2 執行個體類型是應用程式特定的,具體取決於您的模型 (例如影像、影片、文字模型) 以及您的延遲和輸送量需求。例如,如果使用影像及/或影片模型,您可能希望使用 P5 EC2 執行個體,以獲得最佳的即時延遲。我們建議您從 G5 EC2 執行個體開始,因為它為快速啟動和執行提供了良好的起點,然後透過效能基準測試評估其是否適合您的工作負載。若為更進階的案例,則請考慮 G6 EC2 執行個體

  • Amazon EC2 M7g 執行個體:對於 CPU 密集型任務 (例如資料預先處理、API 請求處理、託管 Karpenter 控制器、附加元件和其他系統元件),我們會使用 m5.xlarge M7g EC2 執行個體類型。M7g 執行個體是 ARM 型執行個體,具備 4 個 vCPU、16 GB 記憶體、高達 12.5 Gbps 的網路頻寬,並採用 AWS Graviton3 處理器。選擇 EC2 執行個體類型是應用程式特定的,具體取決於工作負載的運算、記憶體和可擴展性需求。若為運算最佳化工作負載,則您可以考慮使用 C7g EC2 執行個體,這些執行個體也使用 Graviton3 處理器,但會針對特定使用案例的運算效能進行最佳化處理,從而獲得比 M7g 執行個體更高的運算效能。或者,較新的 C8g EC2 執行個體 (如有) 可提供比 C7g 執行個體高出 30% 的運算效能。我們建議您從 M7g EC2 執行個體開始,以確保其成本效益和與各種工作負載 (例如,應用程式伺服器、微服務、遊戲伺服器、中型資料存放區) 的相容性,然後透過效能基準測試評估其是否適合您的工作負載。

  • Amazon S3 掛載點 CSI 驅動程式:若為在單一 GPU 執行個體上的工作負載,其中多個 Pod 會共用 GPU (例如,在同一節點上排程多個 Pod 以利用其 GPU 資源),我們會使用掛載點 S3 CSI 驅動程式來最佳化記憶體用量,且這對於成本敏感、低複雜度設定中的大型模型推論等任務來說至關重要。它將 Amazon S3 儲存貯體公開為 Kubernetes 叢集可用的類似 POSIX 的檔案系統,允許對 Pod 進行推論,從而將模型成品 (例如模型權重) 直接讀取到記憶體 (無需先行下載),以及輸入資料集 (使用標準檔案操作)。此外,S3 具有幾乎無限的儲存容量,並可加速資料密集型推論工作負載。選擇儲存 CSI 驅動程式是應用程式特定的,具體取決於工作負載的輸送量和延遲需求。雖然 FSx for OpenZFS CSI 驅動程式可跨節點為隨機 I/O 或完全 POSIX 相容共用持久性磁碟區提供低於一毫秒的延遲,但我們建議您從掛載點 S3 CSI 驅動程式開始,因為其具有可擴展性、大型資料集的成本較低,並且內建了與 S3 受管物件儲存的整合,可用於需要大量讀取的推論模式 (例如,串流模型輸入),然後透過效能基準測試評估其是否適合您的工作負載。

  • EKS Pod 身分識別代理程式:若要啟用對 AWS 服務的存取,我們會使用 EKS Pod 身分識別代理程式,而該代理程式使用了單一服務主體並促進 Amazon EKS 叢集內 Pod 層級的 IAM 角色關聯。EKS Pod 身分識別透過運用單一服務主體 (pods.eks.amazonaws.com) 來提供傳統的服務帳戶的 IAM 角色 (IRSA) 方法的簡化替代方案,而不是依賴每個叢集的個別 OIDC 提供商,從而可以更輕鬆地指派許可。此外,利用該代理程式,角色可以在多個叢集之間重複使用並支援進階功能,例如 IAM 角色工作階段標籤目標 IAM 角色

  • EKS 節點監控代理程式:為確保推論服務的持續可用性和可靠性,我們會搭配使用 EKS 節點監控代理程式和自動修復,從而可自動偵測並取代運作狀態不佳的節點,並將停機時間降至最低。它使用增強型運作狀態檢查 (例如 KernelReady、NetworkingReady) 持續監控節點是否存在硬體、核心、聯網和儲存問題。對於 GPU 節點,它會偵測加速器特定的故障,透過包圍隔離運作狀態不佳的節點來啟動正常的修補措施、等待 10 分鐘以解決暫時性的 GPU 問題,以及在故障持續維持 30 分鐘後取代節點。

  • Bottlerocket AMI:為了向 EKS 叢集提供安全性強化的基礎,我們會使用 Bottlerocket AMI,其中僅包含執行容器所需的基本元件,並提供最短的開機時間以實現快速擴展。選擇節點 AMI 是應用程式特定的,具體取決於工作負載的自訂、安全性和可擴展性需求。雖然 AL2023 AMI 可為主機層級安裝和自訂提供更大的彈性 (例如,在 PV/PVC 中指定專用快取目錄,而無需任何其他節點組態),但我們建議您從 Bottlerocket AMI 開始,以降低其佔用空間,並針對容器化工作負載 (例如微服務、推論伺服器、可擴展 API) 執行內建最佳化,然後透過效能基準測試來評估其是否適合您的工作負載。

  • AWS Load Balancer 控制器 (LBC):若要公開即時推論端點,我們會使用 AWS Load Balancer 控制器,而該控制器其會基於 Kubernetes 傳入和服務資源,自動佈建和管理用於 HTTP/HTTPS 流量的 Application Load Balancer (ALB) 以及用於 TCP/UDP 流量的 Network Load Balancer (NLB),進而實現推論模型與外部用戶端的整合。此外,它還支援路徑類型路由等功能,可跨多個 Pod 或節點分發推論請求,確保流量高峰期間的可擴展性,並透過連接多工和運作狀態檢查等 AWS 原生最佳化,將延遲降至最低。

1. 建立 EKS 叢集

在此步驟中,我們會使用採用 AWS CloudFormation 技術的 eksctl ClusterConfig 範本,建立具有 CPU 節點和受管節點群組的叢集。使用僅限 CPU 的節點初始化叢集,讓我們能夠專門使用 Karpenter 來管理 CPU 密集型和 GPU 節點,進而使用我們在後續步驟中建立的 Karpenter NodePools 來最佳化資源配置。為了支援即時推論工作負載,我們會使用 EKS Bottlerocket AMI、EKS 節點監控代理程式、EKS Pod 身分識別代理程式、掛載點 S3 CSI 驅動程式、AWS Load Balancer 控制器 (LBC) 和 kube-proxyvpc-cnicoredns 驅動程式佈建叢集。m7g.xlarge 執行個體將可用於 CPU 系統任務,包括託管 Karpenter 控制器、附加元件和其他系統元件。

根據預設,eksctl 會為叢集建立專用 VPC,其中 CIDR 區塊為 192.168.0.0/16。VPC 包含三個公有子網路和三個私有子網路,每個子網路分散在三個不同的可用區域 (或 us-east-1 區域中的可用區域),這是部署 Kubernetes 工作負載的理想方法。範本也會部署網際網路閘道,透過其路由表中的預設路由和其中一個公有子網路中的單個 NAT 閘道,提供對公有子網路的網際網路存取;而私有子網路路由表中的預設路由則透過 NAT 閘道引導傳出流量,以進行網際網路存取。若要進一步了解此設定,請參閱將節點部署到私有子網路

檢查您的憑證

檢查您的 AWS CLI 憑證是否有效,並且可以使用 AWS 服務進行身分驗證:

aws sts get-caller-identity

如果成功,CLI 將傳回有關您的 AWS 身分 (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 需要特定的 IAM 角色和政策 (例如 Karpenter 控制器 IAM 角色、執行個體設定檔和政策),才能將 EC2 執行個體 作為 Kubernetes 工作節點進行管理。它會使用這些角色來執行動作,例如啟動和終止 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 需要佈建和管理 AWS 負載平衡器的許可,例如為傳入資源建立 ALB 或為 LoadBalancer 類型的服務建立 NLB。我們將在叢集建立期間指定此許可政策。在叢集建立期間,我們將使用 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)"

安裝掛載點 S3 CSI 驅動程式時,其 DaemonSet Pod 會設定為使用可用於執行的服務帳戶。掛載點 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 身分識別、節點監控代理程式、CoreDNS、Kubeproxy、VPC CNI 外掛程式建立 Kubernetes Service 帳戶。到目前為止,掛載點 S3 CSI 驅動程式不適用於 EKS Pod 身分識別,因此我們建立了服務帳戶的 IAM 角色 (IRSA) 和 OIDC 端點。此外,我們為 AWS Load Balancer 控制器 (LBC) 建立了一個服務帳戶。為存取 Bottlerocket 節點,eksctl 會自動連接適用於 Bottlerocket 的 AmazonSSMManagedInstanceCore,以允許透過 SSM 的安全 Shell 工作階段。

在設定環境變數的相同終端中,執行下列命令區塊,以建立叢集:

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. 驗證叢集節點和 Pod 運作狀態

讓我們來執行幾個運作狀態檢查,以確保叢集準備就緒。之前的命令完成時,使用下列命令,以檢視執行個體類型並您的 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 身分識別關聯,以及如何將角色與叢集中命名空間的服務帳戶進行對應:

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

在 CPU 工作節點上安裝 Karpenter 控制器 (cpu-worker),以最佳化成本並節省 GPU 資源。我們會將其安裝在 "kube-system" 命名空間中,並指定我們在叢集建立期間定義的 "karpenter" 服務帳戶。另外,此命令會設定 CPU 節點的叢集名稱和 Spot 執行個體中斷佇列。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 NodePools

在此步驟中,我們會設定互斥的 CPU 和 GPU Karpenter NodePools。NodePool 規格中的 limits 欄位限制了每個 NodePool 在所有已佈建的節點中可以使用的最大資源總數 (例如 CPU、記憶體、GPU),如果超過這些限制,則會阻止額外的節點佈建。雖然 NodePools 支援廣泛的執行個體類別 (例如 cg),但指定特定的執行個體類型容量類型和資源限制可以協助您更輕鬆地預估隨需工作負載的成本。在這些 NodePools 中,我們使用了 G5 執行個體系列中的多種不同的執行個體類型。這樣一來,Karpenter 可根據 Pod 資源請求自動選取最適當的執行個體類型,最佳化資源使用率,同時遵循 NodePool 的總限制。若要進一步了解,請參閱建立 NodePools

設定 GPU NodePool

在此 NodePool 中,我們會設定資源限制,以管理具有 GPU 功能的節點的佈建。這些限制旨在限制集區中所有節點的資源總數,即最多允許 10 個執行個體同時執行。每個執行個體可以是 g5.xlarge (4 個 vCPU、16 GiB 記憶體、1 個 GPU),也可以是 g5.2xlarge (8 個 vCPU、32 GiB 記憶體、1 個 GPU),只要 vCPU 總數不超過 80 個、記憶體總量不超過 320 GiB,且 GPU 總數不超過 10 個。例如,集區可以佈建 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

尋找 status.conditions (例如 ValidationSucceeded: TrueNodeClassReady: TrueReady: True),以確認 NodePool 運作狀態良好。

設定 CPU NodePool

在此 NodePool 中,我們會將限制設定為支援約 50 個執行個體,以符合中等 CPU 工作負載 (例如 100-200 個 Pod) 和典型的 AWS vCPU 配額 (例如 128-1152)。計算這些限制時,會假設 NodePool 應向上擴展到 50 個 m7.xlarge 執行個體:CPU (每個執行個體 4 個 vCPU × 50 個執行個體 = 200 個 vCPU) 和記憶體 (每個執行個體 16 GiB × 50 個執行個體 = 800 GiB)。這些限制旨在限制集區中所有節點的資源總數,允許最多 50 個 m7g.xlarge 執行個體 (每個執行個體都有 4 個 vCPU 和 16 GiB 記憶體),只要總 vCPU 不超過 200 個且記憶體總量不超過 800GiB。

此外,我們會指定 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

尋找 status.conditions (例如 ValidationSucceeded: TrueNodeClassReady: TrueReady: True),以確認 NodePool 運作狀態良好。

5. 部署 GPU Pod 以公開 GPU

您需要 Nvidia 裝置外掛程式,才能讓 Kubernetes 以向 Kubernetes 叢集公開 GPU 裝置。一般而言,您需要將外掛程式部署為 DaemonSet;不過,Bottlerocket AMI 會將外掛程式預先安裝為 AMI 的一部分。這表示使用 Bottlerocket AMI 時,無需部署 Nvidia 裝置外掛程式 DaemonSet。若要進一步了解,請參閱 Kubernetes 裝置外掛程式以公開 GPU

部署範例 Pod

Karpenter 可動態運作:當工作負載 (Pod) 請求 GPU 資源時,它會佈建 GPU 節點。若要驗證 Pod 是否能夠請求和使用 GPU,請部署可在其限制內請求 nvidia.com/gpu 資源的 Pod (例如 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

留出一分鐘時間,然後檢查 Pod 是否處於「待定」、「ContainerCreating」、「執行中」以及「已完成」狀態:

kubectl get pod gpu-nvidia-smi -w

驗證 Pod 的節點是否屬於 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

檢查 Pod 的日誌:

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 GPUNet 的開放原始碼 GPUNet-0 的視覺模型部分,而其會支援使用 NVIDIA GPU 和 TensorRT 對影像進行低延遲推論。此模型在 ImageNet 上已經過預先訓練,可讓我們即時分類相片或影片串流中的物件,並被視為具有 1,190 萬個參數的小型模型。

設定您的環境

若要下載 GPUNet-0 模型權重 在此步驟中,您需要存取安裝在您本機電腦上的 NVIDIA NGC 目錄和 Docker。請遵循下列步驟,設定免費帳戶並設定 NGC CLI:

  • 註冊免費的 NGC 帳戶,並從 NGC 儀表板產生 API 金鑰 (使用者圖示 > 設定 > 產生 API 金鑰 > 產生個人金鑰 > NGC 目錄)。

  • 下載並安裝 NGC CLI (Linux/macOS/Windows),並使用下列內容設定 CLI:ngc config set。出現提示時輸入您的 API 金鑰;將 org 設定為 nvidia,然後按 Enter 以接受其他預設。如果成功,您應該會看到類似下列文字的內容:Successfully saved NGC configuration to /Users/your-username/.ngc/config

驗證服務帳戶許可

開始之前,請先檢查 Kubernetes Service 帳戶許可:

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

在叢集建立期間,我們已將 S3CSIDriverPolicy 連接至 IAM 角色,並註釋服務帳戶 ("s3-csi-driver-sa")。掛載點 S3 CSI 驅動程式 Pod 會在與 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 執行。Pod 會在這些節點上使用 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 雲端 (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

在 CLI AWS 中,啟用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 Web 應用程式。應用程式會在執行時期從 Amazon S3 下載模型權重,從 NVIDIA 的儲存庫擷取模型架構以進行快取,並透過 HTTP 下載 ImageNet 類別標籤。應用程式包含映像預先處理轉換並公開兩個端點:用於狀態檢查的根 GET 和接受映像 URL 的 POST /predict 端點。

我們會為模型提供服務,包括搭配使用 FastAPI 和 PyTorch,在執行時期於容器化設定中從 Amazon S3 載入權重,進而實現快速原型設計和 Kubernetes 部署。有關其他方法 (例如最佳化批次處理或高輸送量引擎),請參閱為 ML 模型提供服務

建立應用程式

為您的應用程式檔案建立目錄 (例如 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 會運用 適用於 Tensor 核心的 NVIDIA 深度學習範例 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.pyDockerfile 相同的目錄中,為推論應用程式建置容器映像,並以 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

在本機執行容器,將 AWS 憑證注入為 S3 存取的環境變數。例如:

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 控制器 (LBC) 在 Amazon EKS 外部公開即時推論模型服務。這包括設定 LBC、使用掛載點 S3 CSI 驅動程式將模型權重從 Amazon S3 掛載為持久性磁碟區、部署 GPU 加速的應用程式 Pod、建立服務和傳入以佈建 Application Load Balancer (ALB) 及測試端點。

首先,驗證 AWS LBC 的 Pod 身分識別關聯,確認服務帳戶是否已正確連結至所需的 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 控制器僅支援具有標籤索引鍵 karpenter.sh/discovery: "${EKS_CLUSTER_NAME}" 的單一安全群組,以用於 Karpenter 的安全群組選擇。使用 eksctl 建立叢集時,預設叢集安全群組 (具有 "kubernetes.io/cluster/<cluster-name>: owned" 標籤) 不會自動加上 karpenter.sh/discovery 標籤。此標籤對於 Karpenter 探索此安全群組並將安全群組連接到其佈建的節點至關重要。連接此安全群組可確保與 AWS Load Balancer 控制器 (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 控制器 (LBC)

AWS LBC 對於在 Amazon EKS 上管理通往 AI/ML 工作負載的傳入流量至關重要,進而確保可存取推論端點或資料處理管道。透過與 AWS Application Load Balancer (ALB) 和 Network Load Balancers (NLB) 整合,LBC 可將流量動態路由到容器化應用程式,例如執行大型語言模型、電腦視覺模型或即時推論服務的應用程式。由於我們已在叢集建立期間建立服務帳戶和 Pod 身分識別關聯,因此我們會將 serviceAccount.name 設定為與叢集組態 (aws-load-balancer-controller) 中定義的項目相符。

新增 AWS 擁有的 eks-charts Helm Chart 儲存庫:

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!

將模型掛載至持久性磁碟區中

在此步驟中,您將使用適用於 Amazon S3 的掛載點 CSI 驅動程式支援的 PersistentVolume (PV),從 Amazon S3 儲存貯體掛載模型權重。這可讓 Kubernetes Pod 以本機檔案的形式存取 S3 物件,消除對暫時性 Pod 儲存或 init 容器的資源密集型下載,此方法非常適合大型的多 GB 模型權重。

PV 會掛載整個儲存貯體根目錄 (未在 volumeAttributes 指定路徑),支援多個 Pod 並行唯讀存取,並在容器內公開模型權重 (/models/gpunet-0.pth) 等檔案以進行推論。這可確保不會在應用程式 (app.py) 中觸發備用「下載」,因為檔案是透過掛載存在的。透過從容器映像解耦模型,可啟用共用存取和獨立模型版本更新,而無需重建映像。

建立 PersistentVolume (PV)

建立 PersistentVolume (PV) 資源,以掛載包含模型權重的 S3 儲存貯體,因此無需在執行時期下載檔案即可對多個 Pod 啟用唯讀存取:

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)

建立 PersistentVolumeClaim (PVC),以繫結至 PV,同時請求對掛載的 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 支援的持久性磁碟區,以進行模型存取、套用 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 節點尚不可用,則佈建 GPU 節點 將花費 Karpenter 幾分鐘的時間。確認推論 Pod 處於「執行中」狀態:

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

預期的輸出應類似以下:

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

使用傳入和 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

建立傳入資源,以透過 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) 完成佈建。監控傳入資源狀態,以確認 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

從傳入狀態擷取並匯出 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 執行個體佈建叢集,安裝了掛載點 S3 CSI 驅動程式EKS Pod 身分識別代理程式EKS 節點監控代理程式Bottlerocket AMIAWS Load Balancer 控制器 (LBC)Karpenter,從而可管理 CPU 和 GPU NodePools。您已使用 NVIDIA 裝置外掛程式來啟用 GPU 排程,並使用 PersistentVolume 和 PersistentVolumeClaim 設定 S3 以進行模型存取。您已透過部署範例 GPU Pod、在 Amazon S3 上設定 NVIDIA GPUNet-0 模型的模型存取、啟用 Pod 初始化,以及透過 Application Load Balancer 公開推論服務來驗證設定。若要充分利用叢集,請使用自動修復設定 EKS 節點監控代理程式。請務必執行基準測試,包括 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 儲存貯體。