帮助改进此页面
要帮助改进本用户指南,请选择位于每个页面右侧窗格中的在 GitHub 上编辑此页面链接。
Amazon EKS 上实时推理的最佳实践集群设置指南
简介
本指南提供一个实作演练,用于设置针对实时在线推理工作负载进行优化的 Amazon Elastic Kubernetes Service(EKS)集群,其中包含了由 AWS 专家整理的最佳实践。它使用带有预设偏好的 EKS 快速启动架构:一组精心策划的驱动程序、实例类型和配置,与模型、加速器和扩展的 AWS 最佳实践保持一致。这种方法可以帮助您绕过选择集群设置的任务,让您能够快速启动并运行功能齐全的预配置集群。在此过程中,我们将部署示例工作负载来验证您的设置,解释关键架构概念(例如将 CPU 密集型任务与 GPU 密集型计算分离),解决常见问题(例如,为什么选择 Bottlerocket AMI 而不是 AL2023?),并概述扩展集群功能的后续步骤。
本指南专为不熟悉 AWS 和 EKS 生态系统的机器学习(ML)和人工智能(AI)工程师、平台管理员、操作员以及数据/人工智能专家而设计,其假设您熟悉 Kubernetes,但之前没有 EKS 使用经验。本指南旨在帮助您了解启动和运行实时在线推理工作负载所需的步骤和流程。本指南向您展示创建单节点推理集群的基本要素,包括预置 GPU 资源、为模型构件集成存储、启用安全 AWS 服务访问以及公开推理端点。本指南自始至终强调面向用户的应用程序(例如欺诈检测、实时聊天机器人和客户反馈系统中的情感分析)的低延迟、弹性设计。
在本指南中,我们专门介绍如何使用 G5 EC2 实例设置基础的规范性起点。如果您正在寻找 AWS Inferentia 特定的集群配置或端到端工作流程,请参阅将 AWS Inferentia 实例与 Amazon EKS 用于机器学习或在 Amazon EKS 上开始使用人工智能/机器学习的资源中我们的讲习会。
开始前的准备工作
开始之前,请确保您已执行以下任务:
架构
实时在线推理是指使用经过训练的机器学习模型,以最小的延迟对传入的数据流生成预测或输出的过程。例如,它支持实时欺诈检测、图像分类或根据用户输入生成知识图谱。实时在线推理系统的架构通过将 CPU 密集型 Web 流量处理与 GPU 密集型人工智能计算分离,在面向用户的应用程序中提供低延迟的机器学习预测。此过程通常存在于更大的应用程序生态系统中,通常包括后端、前端、向量和模型服务,侧重于专门的组件,以实现独立扩展、并行开发和故障恢复能力。在专用 GPU 硬件上隔离推理任务并利用 API 和 WebSocket 等接口可确保高并发性、快速处理转换器等模型以及通过前端的用户交互。请注意,尽管向量数据库和检索增强生成(RAG)管道通常在实时推理系统中发挥重要作用,但本指南中并未涵盖这些组件。典型的架构通常至少包括以下部分:
-
前端服务:作为面向用户的界面,处理客户端逻辑,呈现动态内容并促进实时交互。该服务与后端服务通信以启动推理请求并显示结果,通常向使用 WebSocket 进行流式更新或使用 API 进行结构化数据交换的后端服务发起请求。该服务通常不需要专用的负载均衡器,因为它可以托管在 AWS CloudFront 等内容分发网络(CDN)上(用于静态资产),也可以直接从 Web 服务器提供服务,如果需要动态内容,则可通过自动扩缩组进行扩展。
-
后端服务:充当应用程序的编排工具,管理业务逻辑,如用户身份验证、数据验证和服务协调(例如,通过 RESTful 端点的 API 或用于持久性连接的 WebSocket)。该服务与推理服务通信,在多核 CPU 和 RAM 上独立扩展,无需依赖 GPU 即可处理高 Web 流量,并且通常需要负载均衡器(例如 AWS 应用程序负载均衡器或网络负载均衡器)将传入的请求分发到多个实例,尤其是在高并发场景中。入口控制器可以进一步管理外部访问和路由规则,以增强安全性和流量整形。
-
推理服务:作为人工智能计算的核心,在具有充足 VRAM(例如需要 8-12 GB 的 DistilBERT 等模型)的 GPU 上运行,以使用自定义或开源模型执行向量嵌入、知识提取和模型推理(例如,通过 API 发布批处理请求或通过 WebSocket 发布实时流)。这种隔离可以防止依赖项冲突,允许在不停机的情况下更新模型,并支持水平扩缩,实现多个并发请求的负载均衡。为了有效地公开模型服务,该服务通常位于负载均衡器之后,以在复制的实例之间分配 GPU 密集型工作负载,而入口资源或控制器(例如 AWS 中的 ALB Ingress Controller)处理外部路由、SSL 终止和基于路径的转发,以确保安全高效的访问,不会让单个 GPU 不堪重负。
解决方案概述
实时在线推理系统需要高性能、弹性的架构,能够在处理不可预测的大流量突发时提供超低延迟。本解决方案概述说明以下 AWS 组件如何在我们将创建的 Amazon EKS 集群中协同工作,以确保我们的集群能够托管和管理机器学习模型,以最小的延迟为最终用户提供对实时数据的即时预测。
-
Amazon G5 EC2 实例
:对于 GPU 密集型推理任务,我们使用 g5.xlarge 和 g5.2xlarge G5 EC2 实例类型,它们具有一个 (1) 个 NVIDIA A10G GPU,内存为 24 GB(例如 FP16 上有 80 亿个参数)。这些 GPU 基于 NVIDIA Ampere 架构,由 NVIDIA A10G Tensor Core GPU 和第二代 AMD EPYC 处理器提供支持,支持 4-8 个 vCPU、高达 10 Gbps 的网络带宽和 250-450 GB 的本地 NVMe SSD 存储,可确保复杂模型的快速数据移动和计算能力,非常适合低延迟、高吞吐量的推理任务。选择 EC2 实例类型因应用程序而异,具体取决于您的模型(例如图像、视频、文本模型)以及您的延迟和吞吐量要求。例如,如果使用图像和/或视频模型,则可能需要使用 P5 EC2 实例 才能获得最佳实时延迟。我们建议从 G5 EC2 实例 开始(因为它为快速启动和运行提供了一个很好的起点),然后通过性能基准测试来评估其是否适合您的工作负载。对于更高级的案例,可以考虑 G6 EC2 实例 。 -
Amazon EC2 M7g 实例
:对于数据预处理、API 请求处理、托管 Karpenter 控制器、附加组件和其他系统组件等 CPU 密集型任务,我们使用 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 Mountpoint CSI 驱动程序:对于多个容器组(pod)共享一个 GPU 的单 GPU 实例上的工作负载(例如,在同一个节点上安排多个容器组(pod)以利用其 GPU 资源),我们使用 Mountpoint S3 CSI 驱动程序来优化内存使用情况,这对于成本敏感、低复杂度设置中的大型模型推断等任务至关重要。它将 Amazon S3 存储桶作为类似 POSIX 的文件系统向 Kubernetes 集群公开,允许推理容器组(pod)无需先下载即可将模型构件(例如模型权重)直接读入内存,并使用标准文件操作输入数据集。此外,S3 具有几乎无限的存储容量,可加速数据密集型推理工作负载。选择存储 CSI 驱动程序因应用程序而异,具体取决于工作负载的吞吐量和延迟要求。尽管 FSx for OpenZFS CSI 启动程序可为随机 I/O 或完全符合 POSIX 标准的跨节点共享持久性卷提供亚毫秒级延迟,但我们建议从 Mountpoint S3 CSI 驱动程序开始,因为它具有可扩展性、对于大型数据集较低的成本以及与 S3 托管对象存储的内置集成,可实现读取密集型推理模式(例如流式模型输入),然后通过性能基准测试来评估其是否适合您的工作负载。
-
EKS 容器组身份代理:为了允许访问 AWS 服务,我们使用了 EKS 容器组身份代理,后者使用单一服务主体,促进了 Amazon EKS 集群内容器组(pod)级的 IAM 角色关联。EKS 容器组身份为传统的服务账户 IAM 角色(IRSA)方法提供了一种简化的替代方案,其使用单一服务主体(pods.eks.amazonaws.com),而不是依赖每个集群单独的 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 负载均衡器控制器(LBC):为公开实时推理端点,我们使用了 AWS 负载均衡器控制器,它会根据 Kubernetes Ingress 和服务资源自动预置和管理针对 HTTP/HTTPS 流量的应用程序负载均衡器(ALB)和针对 TCP/UDP 流量的网络负载均衡器(NLB),从而实现推理模型与外部客户端的集成。此外,它还支持基于路径的路由等功能,可在多个容器组(pod)或节点之间分发推理请求,确保流量高峰期间的可扩展性,并通过连接多路复用和运行状况检查等 AWS 原生优化来最大限度地减少延迟。
1. 创建您的 EKS 集群
在此步骤中,我们将使用由 AWS CloudFormation 提供支持的 eksctl ClusterConfig
默认情况下,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 身份的详细信息(用户 ID、账户和 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 发布版本
提示
某些变量(例如 ${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 角色、实例配置文件和策略)来管理作为 Kubernetes Worker 节点的 EC2 实例。它使用这些角色来执行如启动和终止 EC2 实例、标记资源以及与其他 AWS 服务交互之类的操作。使用 Karpenter 的 cloudformation.yaml
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 负载均衡器,例如为 Ingress 资源创建 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)"
安装 Mountpoint S3 CSI 驱动程序后,其 DaemonSet 容器组(pod)将配置为使用服务账户执行。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 容器组身份、节点监控代理、CoreDNS、Kubeproxy、VPC CNI 插件创建 Kubernetes 服务账户。到目前为止,Mountpoint S3 CSI 驱动程序尚未支持 EKS 容器组身份,因此我们创建了一个服务账户 IAM 角色(IRSA)和一个 OIDC 端点。此外,我们还为 AWS 负载均衡器控制器(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
使用以下命令验证所有容器组身份关联及其如何将角色映射到集群命名空间中的服务账户:
eksctl get podidentityassociation --cluster ${EKS_CLUSTER_NAME} --region ${AWS_REGION}
输出应显示 Karpenter 的 IAM 角色(“karpenter”)和 AWS LBC(“aws-load-balancer-controller”)。
验证 DaemonSet 是否可用:
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 Worker 节点(cpu-worker
)上安装 Karpenter 控制器,以优化成本并节省 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 节点池
在此步骤中,我们将配置互斥的 CPU 和 GPU Karpenter 节点池limits
字段限制了每个节点池在所有已预置节点上可以消耗的最大总资源(例如 CPU、内存、GPU),从而防止在超过这些限制时额外预置节点。虽然节点池支持广泛的实例类别(例如,c
、g
),但指定具体的实例类型
设置 GPU 节点池
在此节点池 中,我们将设置资源限制以管理具有 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。最后,我们将中断策略consolidateAfter: 30m
),并将节点的最大生命周期设置为 30 天(expireAfter: 720h
),以优化成本并维护 GPU 密集型任务的节点运行状况。要了解更多信息,请参阅 Disable Karpenter Consolidation for interruption sensitive workloads 和 Use ttlSecondsAfterFinished to Auto Clean-Up Kubernetes Jobs。
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
验证节点池是否已创建且正常运行:
kubectl get nodepool gpu-a10g-inference-g5 -o yaml
查找类似 ValidationSucceeded: True
、NodeClassReady: True
和 Ready: True
的 status.conditions
,以确认节点池正常运行。
设置 CPU 节点池
在此节点池中,我们将限制设置为支持约 50 个实例,这与中等 CPU 工作负载(例如 100-200 个容器组(pod))和典型 AWS vCPU 配额(例如 128-1152)保持一致。计算限制时假设节点池应扩展到 50 个 m7.xlarge 实例:CPU(每个实例 4 个 vCPU × 50 个实例 = 200 个 vCPU)和内存(每个实例 16 GiB × 50 个实例 = 800 GiB)。这些限制旨在限制池中所有节点的总资源,允许最多 50 个 m7g.xlarge 实例(每个实例有 4 个 vCPU 和 16 GiB 内存),前提是 vCPU 总数不超过 200 个且总内存不超过 800 GiB。
此外,我们还指定了 Bottlerocket AMI 的标准变体 ID。最后,我们将中断策略consolidateAfter: 60m
),并将节点的最大生命周期设置为 30 天(expireAfter: 720h
),以优化成本并维护 GPU 密集型任务的节点运行状况。要了解更多信息,请参阅 Disable Karpenter Consolidation for interruption sensitive workloads 和 Use ttlSecondsAfterFinished to Auto Clean-Up Kubernetes Jobs。
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
验证节点池是否已创建且正常运行:
kubectl get nodepool cpu-inference-m7gxlarge -o yaml
查找类似 ValidationSucceeded: True
、NodeClassReady: True
和 Ready: True
的 status.conditions
,以确认节点池正常运行。
5. 部署 GPU 容器组(pod)以公开 GPU
您需要 Nvidia 设备插件才能让 Kubernetes 向 Kubernetes 集群公开 GPU 设备。通常,您需要将插件部署为 DaemonSet;但是,Bottlerocket AMI 会将该插件作为 AMI 的一部分进行预安装。这意味着在使用 Bottlerocket AMI 时,无需部署 Nvidia 设备插件 DaemonSet。要了解更多信息,请参阅 Kubernetes Device Plugin to expose GPUs。
部署示例容器组(pod)
Karpenter 动态运行:当工作负载(容器组(pod))请求 GPU 资源时,它会预置 GPU 节点。要验证容器组(pod)是否能够请求和使用 GPU,请部署一个在其限制内请求 nvidia.com/gpu
资源的容器组(pod)(例如 nvidia.com/gpu: 1
)。要了解有关这些标签的更多信息,请参阅 Schedule workloads with GPU requirements using Well-Known labels。
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)状态是否为“Pending”、“ContainerCreating”、“Running”,然后变为“Completed”:
kubectl get pod gpu-nvidia-smi -w
验证容器组(pod)的节点是否属于 GPU 节点池:
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 的 NGC 目录并在本地计算机上安装 Docker
-
注册一个免费的 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 服务账户的权限:
kubectl get serviceaccount s3-csi-driver-sa -n kube-system -o yaml
在集群创建过程中,我们将 S3CSIDriverPolicy 附加到 IAM 角色并注释了服务账户(“s3-csi-driver-sa”)。Mountpoint 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 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 通用运行时
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 视觉模型设置一个 FastAPI Web 应用程序,用于 GPU 加速的图像分类。该应用程序在运行时从 Amazon S3 下载模型权重,从 NVIDIA 的存储库中获取模型架构进行缓存,并通过 HTTP 下载 ImageNet 类标签。该应用程序包括图像预处理转换并公开两个端点:一个用于状态检查的根 GET 端点和一个接受图像 URL 的 POST /predict
端点。
我们使用带有 PyTorch 的 FastAPI 来提供模型,在运行时以容器化设置从 Amazon S3 加载权重,以便快速进行原型设计和 Kubernetes 部署。有关其他方法,例如优化的批处理或高吞吐量引擎,请参阅 Serving ML Models。
创建应用程序
为应用程序文件创建一个目录(例如 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
我们通过以下方式减小容器映像大小:使用仅运行时的 PyTorch 基础映像、只安装必要的软件包并清理缓存、预缓存模型代码,以及避免在容器映像中“烘焙”权重,从而实现更快的拉取和更新。要了解更多信息,请参阅 Reducing Container Image Sizes。
在与 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
在本地运行容器,将 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 负载均衡器控制器(LBC)在 Amazon EKS 外部公开实时推理模型服务。这包括设置 LBC、使用 Mountpoint S3 CSI 驱动程序将 Amazon S3 的模型权重挂载为持久性卷、部署 GPU 加速的应用程序容器组(pod)、创建用于预置应用程序负载均衡器(ALB)的服务和入口以及测试端点。
首先,验证 AWS LBC 的容器组身份关联,确认服务账户已正确关联到所需的 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 负载均衡器控制器仅支持一个安全组,其标签键为 karpenter.sh/discovery: "${EKS_CLUSTER_NAME}"
,用于选择 Karpenter 的安全组。使用 eksctl 创建集群时,默认集群安全组(带有 "kubernetes.io/cluster/<cluster-name>: owned"
标签)不会使用 karpenter.sh/discovery
标签自动标记。此标签对于 Karpenter 发现此安全组并将其附加到预置的节点至关重要。连接此安全组可确保与 AWS 负载均衡器控制器(LBC)兼容,使其能够自动管理通过 Ingress 公开的服务(例如这些步骤中的模型服务)的入站流量规则。
导出集群的 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 负载均衡器控制器(LBC)
AWS LBC 对于管理 Amazon EKS 上人工智能/机器学习工作负载的入口流量至关重要,可确保对推理端点或数据处理管道的访问。通过与 AWS 应用程序负载均衡器(ALB)和网络负载均衡器(NLB)集成,LBC 可以动态地将流量路由到容器化应用程序,例如运行大型语言模型、计算机视觉模型或实时推理服务的应用程序。由于我们在创建集群期间已经创建了服务账户和容器组身份关联,因此将 serviceAccount.name
设置为匹配集群配置(aws-load-balancer-controller
)中定义的内容。
添加 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!
将模型挂载到持久性卷中
在此步骤中,您将使用由适用于 Amazon S3 的 Mountpoint CSI 驱动程序支持的 PersistentVolume(PV)从 Amazon S3 存储桶挂载模型权重。这使 Kubernetes 容器组(pod)可以将 S3 对象作为本地文件访问,从而无需到临时容器组(pod)存储或初始化容器的资源密集型下载,非常适合大型、多 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"
的环境变量),因此默认情况下,我们的应用程序(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 需要几分钟才能完成预置。验证推理容器组(pod)是否处于“Running”状态:
kubectl get pods -l app=gpunet-inference-app
预期输出应如下所示:
NAME READY STATUS RESTARTS AGE gpunet-inference-app-5d4b6c7f8-abcde 1/1 Running 0 2m
使用 Ingress 和负载均衡器公开服务
创建 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 预置面向互联网的应用程序负载均衡器(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
等待几分钟,以便应用程序负载均衡器(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 实例
清理
为避免将来产生费用,您需要手动删除关联的 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 控制台