AWS在EKS上推出托管JupyterLab和Code Editor环境,支持VS Code通过SSH-over-SSM连接,简化ML团队开发体验。
要在 Amazon Elastic Kubernetes Service(Amazon EKS)上为 AI 工作流提供动力,数据科学家需要 JupyterLab 和 Code Editor 等交互式 IDE。然而,运行这些 IDE 通常意味着离开托管其流水线的集群,转而使用独立的 JupyterHub 部署或本地笔记本电脑。这种切换会使他们无法访问其流水线所依赖的 GPU 节点、共享存储和 AWS Identity and Access Management(IAM)角色。Amazon SageMaker AI Spaces 附加组件弥补了这一差距。它在您已经运营的集群上运行托管的 JupyterLab 和 Code Editor 环境。传统上,平台团队需要 3–5 天来搭建一个支持 GPU 访问、存储和认证的独立 JupyterHub 环境。有了这个附加组件,数据科学家只需约 5 分钟就能启动一个完全配置好的 Space。
在本文中,您将在 Amazon EKS 集群上安装 SageMaker AI Spaces 附加组件。您将设置支持性附加组件和 IAM 角色,部署 AWS Load Balancer Controller,申请 TLS 证书,并创建 AWS Key Management System(AWS KMS)加密密钥。然后创建您的第一个 Space,并通过浏览器中的预签名 URL 以及通过 SSH-over-SSM 从 VS Code 访问它。最后,您将了解如何将团队迁移到使用 Amazon Cognito 的 OpenID Connect(OIDC)登录。
该解决方案在单个 EKS 集群上运行,分为三层:
网络和访问层。Amazon Route 53 将通配符域名解析到带有 AWS Certificate Manager(ACM)TLS 证书的面向互联网的 Application Load Balancer(ALB)。对于 VS Code,AWS Systems Manager 直接隧道连接到 Space pod。
集群路由层。AWS Load Balancer Controller 配置 ALB。Traefik 按主机名路由。认证中间件使用 AWS Key Management Service(AWS KMS)进行 JSON Web Token(JWT)加密来验证令牌。
计算和存储层。Space pod 在私有子网工作节点上运行。Amazon Elastic Block Store(Amazon EBS)CSI 驱动提供持久卷,Amazon Elastic File System(Amazon EFS)或 Amazon FSx 处理共享或高吞吐量存储。EKS Pod Identity 为 pod 授予作用域 IAM 角色。
将交互式工作负载和训练工作负载整合到同一集群中,可使 GPU 节点在作业之间保持忙碌。与专用笔记本集群相比,这可将 GPU 利用率提高高达 30%。同时还避免了始终在线 GPU 环境的高成本,该成本每月可达数千美元。
图 1:解决方案架构
要跟随操作,您需要一个 AWS 账户,AWS Command Line Interface(AWS CLI)2.x 或更高版本已针对目标 AWS 区域配置完毕,还需要 kubectl 1.30 或更高版本以及 Helm v3。您还需要为自有域名提供一个 Route 53 公共托管区域,在本文中将其引用为 <YOUR_DOMAIN>,以及创建角色、策略、EKS 附加组件、访问条目、Pod Identity 关联、ACM 证书和 KMS 密钥的 IAM 权限。Spaces 附加组件必须是 0.1.4 或更高版本,因为早期版本仅支持 Amazon SageMaker HyperPod。
图 2:带有验证记录的 Route 53 托管区域
一次性设置这些变量。本文其余部分将重复使用它们。
export CLUSTER_NAME=<CLUSTER_NAME>
export REGION=<REGION>
export ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
本文中的每个 IAM 角色都通过 EKS Pod Identity 由 Kubernetes 服务账号来担任,因此它们共享一个信任策略。一次保存并重复使用:
cat > pod-identity-trust.json <<'EOF'
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": "pods.eks.amazonaws.com" },
"Action": ["sts:AssumeRole", "sts:TagSession"]
}]
}
EOF
注意:本指南创建的资源会产生 AWS 费用:面向互联网的 ALB、EBS 卷和 EKS 集群。SSM 高级实例层级每个 Space pod 额外收取约 $0.00695/小时。完成时请按照"清理"部分进行操作。
创建 EKS 集群
集群创建本身遵循标准 EKS 入门指南。此处的关键在于满足四个 Spaces 特定要求。保持 EKS Auto Mode 禁用,因为附加组件需要基于经典 EC2 的节点,运行 Kubernetes 1.30 或更高版本。使用包含公有和私有子网的虚拟私有云(VPC),跨越至少两个可用区,由 NAT 网关为私有子网服务,并将集群端点访问设置为"Public and private"。创建期间,添加 EKS Pod Identity Agent、Amazon EBS CSI Driver、Cert manager 和 External DNS 附加组件,但暂不添加 Amazon SageMaker Spaces 和 AWS Load Balancer Controller。您稍后再安装这些。最后,在私有子网上创建一个托管节点组,使用 Amazon Linux 2023,m5.xlarge 或更大规格,2 个节点。如果您已有一个符合要求的集群,请跳到下一部分。
有一个步骤经常被忽略。为 VPC 中的每个子网添加标签,以便 AWS Load Balancer Controller 可以发现它们,并在安装 Spaces 附加组件之前添加标签。否则,控制器可能会将 ALB 放置在私有子网上,导致 Spaces 无法访问。
export VPC_ID=$(aws eks describe-cluster \
--name $CLUSTER_NAME --region $REGION \
--query 'cluster.resourcesVpcConfig.vpcId' --output text)
ALL_SUBNETS=$(aws ec2 describe-subnets --region $REGION \
--filters "Name=vpc-id,Values=${VPC_ID}" \
--query 'Subnets[*].SubnetId' --output text)
aws ec2 create-tags --region $REGION --resources ${ALL_SUBNETS} \
--tags Key=kubernetes.io/cluster/$CLUSTER_NAME,Value=shared
PUBLIC_SUBNETS=$(aws ec2 describe-subnets --region $REGION \
--filters "Name=vpc-id,Values=${VPC_ID}" "Name=map-public-ip-on-launch,Values=true" \
--query 'Subnets[*].SubnetId' --output text)
aws ec2 create-tags --region $REGION --resources ${PUBLIC_SUBNETS} \
--tags Key=kubernetes.io/role/elb,Value=1
PRIVATE_SUBNETS=$(aws ec2 describe-subnets --region $REGION \
--filters "Name=vpc-id,Values=${VPC_ID}" "Name=map-public-ip-on-launch,Values=false" \
--query 'Subnets[*].SubnetId' --output text)
aws ec2 create-tags --region $REGION --resources ${PRIVATE_SUBNETS} \
--tags Key=kubernetes.io/role/internal-elb,Value=1
设置基础环境
集群运行后,将 kubectl 指向它,确认附加组件 pod 处于健康状态,并授予 External DNS 管理 DNS 记录所需的 Route 53 权限。
配置 kubectl:
aws eks update-kubeconfig --name $CLUSTER_NAME --region $REGION
kubectl get nodes
两个工作节点均报告 Ready:
NAME STATUS ROLES AGE VERSION
ip-10-0-1-42.ec2.internal Ready <none> 38m v1.34.6-eks-bbe087e
ip-10-0-2-96.ec2.internal Ready <none> 38m v1.34.6-eks-bbe087e
使用 kubectl get pods -A 确认系统 pod 在所有附加组件命名空间中处于健康状态。在继续之前,kube-system、cert-manager 和 external-dns 中的每个 pod 都应该处于 Running 状态。
External DNS 需要 Route 53 权限来管理记录。创建角色,附加最小权限策略,并通过 Pod Identity 进行绑定:
aws iam create-role --role-name ExternalDNSRole \
--assume-role-policy-document file://pod-identity-trust.json
aws iam put-role-policy --role-name ExternalDNSRole \
--policy-name ExternalDNSRoute53Policy \
--policy-document '{
"Version":"2012-10-17",
"Statement":[
{"Effect":"Allow","Action":["route53:ChangeResourceRecordSets"],
"Resource":"arn:aws:route53:::hostedzone/*"},
{"Effect":"Allow","Action":["route53:ListHostedZones","route53:ListResourceRecordSets","route53:ListTagsForResource"],
"Resource":"*"}
]}'
aws eks create-pod-identity-association \
--cluster-name $CLUSTER_NAME --region $REGION \
--namespace external-dns --service-account external-dns \
--role-arn arn:aws:iam::${ACCOUNT_ID}:role/ExternalDNSRole
kubectl rollout restart deployment -n external-dns external-dns
安全提示:为每个 Pod Identity 角色设置最小权限操作和资源。优先使用明确的资源 ARN 而非通配符,并确认只有目标服务账户可以扮演该角色。
安装 AWS Load Balancer Controller
AWS Load Balancer Controller 提供支撑 Spaces UI 的 ALB。使用 Helm 进行安装。
定义控制器的 IAM 策略、角色和 Pod Identity 关联:
curl -sS -o /tmp/lbc-iam-policy.json \
https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/main/docs/install/iam_policy.json
aws iam create-policy --policy-name AWSLoadBalancerControllerIAMPolicy \
--policy-document file:///tmp/lbc-iam-policy.json
aws iam create-role --role-name AWSLoadBalancerControllerRole \
--assume-role-policy-document file://pod-identity-trust.json
aws iam attach-role-policy --role-name AWSLoadBalancerControllerRole \
--policy-arn arn:aws:iam::${ACCOUNT_ID}:policy/AWSLoadBalancerControllerIAMPolicy
aws eks create-pod-identity-association \
--cluster-name $CLUSTER_NAME --region $REGION \
--namespace kube-system --service-account aws-load-balancer-controller \
--role-arn arn:aws:iam::${ACCOUNT_ID}:role/AWSLoadBalancerControllerRole
使用 Helm chart 安装。显式传入 vpcId 和 region。在 chart v3.2+ 版本中,如果控制器通过 EC2 元数据自动检测 VPC(EKS 会阻止此行为),控制器将启动失败。
helm repo add eks https://aws.github.io/eks-charts
helm repo update eks
helm install aws-load-balancer-controller eks/aws-load-balancer-controller \
-n kube-system \
--set clusterName=$CLUSTER_NAME \
--set serviceAccount.create=true \
--set serviceAccount.name=aws-load-balancer-controller \
--set region=$REGION \
--set vpcId=$VPC_ID
kubectl rollout status deployment -n kube-system aws-load-balancer-controller --timeout=180s
两个控制器副本均已启动:
NAME READY UP-TO-DATE AVAILABLE AGE
aws-load-balancer-controller 2/2 2 2 174m
创建证书、密钥和 SSM 配置
Spaces 附加组件需要 TLS 证书、KMS 密钥(用于 JWT 加密)以及用于远程访问的 SSM 服务设置。
请求覆盖您域名及通配符的 ACM 证书,使用 DNS 验证方式,然后读取 ACM 期望的 CNAME 记录:
CERT_ARN=$(aws acm request-certificate \
--domain-name "<YOUR_DOMAIN>" \
--subject-alternative-names "*.<YOUR_DOMAIN>" \
--validation-method DNS \
--region $REGION \
--query CertificateArn --output text)
# Read the CNAME records ACM expects, then add them to your Route 53
# hosted zone. The console's 'Create records in Route 53' button
# does this for you.
aws acm describe-certificate --certificate-arn "$CERT_ARN" \
--region $REGION \
--query 'Certificate.DomainValidationOptions[].ResourceRecord'
等待证书状态变为 Issued,然后复制 ARN。
Figure 3: Certificate issued for the domain
安全提示:DNS 验证用于确认域名所有权并触发 ACM 自动续期。请将验证 CNAME 保留在 Route 53 中。删除它们会导致续期失败。

创建 KMS 加密密钥。认证中间件会为每个 JWT 调用 kms:GenerateDataKey,因此密钥必须为对称的 ENCRYPT_DECRYPT 类型(这是 CLI 的默认类型):
KMS_KEY_ARN=$(aws kms create-key --region $REGION \
--description "SageMaker Spaces JWT encryption" \
--query 'KeyMetadata.Arn' --output text)
aws kms create-alias --region $REGION \
--alias-name alias/sagemaker-spaces-jwt \
--target-key-id "$KMS_KEY_ARN"
开启 SSM 高级实例层。Session Manager 隧道连接到混合托管实例,VS Code remote 使用的正是这种实例,需要该层级(每个 Space pod 约 $0.00695/小时):
aws ssm update-service-setting --region $REGION \
--setting-id arn:aws:ssm:$REGION:${ACCOUNT_ID}:servicesetting/ssm/managed-instance/activation-tier \
--setting-value advanced
安装 Spaces 附加组件
为 Spaces 控制器和认证中间件创建 IAM 角色,然后安装附加组件。
首先创建每个 Space pod 在 SSM fleet 中使用的 SSM 托管实例角色:
aws iam create-role --role-name SageMakerSpacesSSMManagedNodeRole \
--assume-role-policy-document '{
"Version":"2012-10-17",
"Statement":[{"Effect":"Allow","Principal":{"Service":"ssm.amazonaws.com"},"Action":"sts:AssumeRole"}]
}'
aws iam attach-role-policy --role-name SageMakerSpacesSSMManagedNodeRole \
--policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
接下来,创建 Spaces controller role。它需要 SSM、PassRole 和 KMS 权限。将以下策略保存为 spaces-controller-policy.json,并将 <REGION>、<ACCOUNT_ID> 和 <KMS_KEY_ARN> 替换为你的实际值:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "SSMAccountLevel",
"Effect": "Allow",
"Action": [
"ssm:CreateActivation",
"ssm:DeleteActivation",
"ssm:DescribeActivations",
"ssm:DescribeInstanceInformation",
"ssm:DeregisterManagedInstance",
"ssm:ListTagsForResource",
"ssm:AddTagsToResource",
"ssm:ListDocuments",
"ssm:DescribeSessions"
],
"Resource": "*"
},
{
"Sid": "SSMDocumentMgmt",
"Effect": "Allow",
"Action": [
"ssm:CreateDocument",
"ssm:DescribeDocument",
"ssm:GetDocument",
"ssm:UpdateDocument",
"ssm:UpdateDocumentDefaultVersion",
"ssm:DeleteDocument"
],
"Resource": "arn:aws:ssm:<REGION>:<ACCOUNT_ID>:document/SageMaker-Space*"
},
{
"Sid": "SSMSessionMgmt",
"Effect": "Allow",
"Action": [
"ssm:StartSession",
"ssm:TerminateSession",
"ssm:ResumeSession",
"ssm:GetConnectionStatus"
],
"Resource": [
"arn:aws:ssm:<REGION>:<ACCOUNT_ID>:document/SageMaker-Space*",
"arn:aws:ssm:<REGION>:<ACCOUNT_ID>:managed-instance/*",
"arn:aws:ssm:<REGION>::document/AWS-StartSSHSession"
]
},
{
"Sid": "PassSSMManagedNodeRole",
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": "arn:aws:iam::<ACCOUNT_ID>:role/SageMakerSpacesSSMManagedNodeRole",
"Condition": {
"StringEquals": {
"iam:PassedToService": "ssm.amazonaws.com"
}
}
},
{
"Sid": "KMSForJWT",
"Effect": "Allow",
"Action": [
"kms:GenerateDataKey",
"kms:Decrypt",
"kms:Encrypt",
"kms:DescribeKey"
],
"Resource": "<KMS_KEY_ARN>"
}
]
}
创建 role 并附加策略:
aws iam create-role --role-name SageMakerSpacesControllerRole \
--assume-role-policy-document file://pod-identity-trust.json
aws iam put-role-policy --role-name SageMakerSpacesControllerRole \
--policy-name SageMakerSpacesControllerPolicy \
--policy-document file://spaces-controller-policy.json
通过 Pod Identity 将 controller 和 auth middleware 服务账号绑定到此 role:
for SA in jupyter-k8s-controller-manager jupyter-k8s-authmiddleware; do
aws eks create-pod-identity-association \
--cluster-name $CLUSTER_NAME --region $REGION \
--namespace jupyter-k8s-system --service-account $SA \
--role-arn arn:aws:iam::${ACCOUNT_ID}:role/SageMakerSpacesControllerRole
done
安全注意事项:为了更严格的责任分离,建议拆分为两个 role:一个拥有 controller 的 SSM 操作权限,一个拥有 auth middleware 的 KMS 加密和解密权限。
使用你的域名、证书 ARN、密钥 ARN 和托管节点 role 名称定义 addon-config.yaml:
jupyter-k8s:
# 根据官方 AWS 文档,这里的 'enable'(不是 'enabled')是正确的:
# https://docs.aws.amazon.com/sagemaker/latest/dg/operator-install.html
# jupyter-k8s 和 jupyter-k8s-aws-hyperpod 子 chart 使用不同的 schema,
# 因此下面的 clusterWebUI 正确使用了 'enabled'。不是笔误。
workspacePodWatching:
enable: true
jupyter-k8s-aws-hyperpod:
clusterWebUI:
enabled: true
domain: "<YOUR_DOMAIN>"
awsCertificateArn: "<ACM_CERT_ARN>"
traefik:
shouldInstall: true
auth:
kmsKeyId: "<KMS_KEY_ARN>"
remoteAccess:
enabled: true
ssmManagedNodeRole: SageMakerSpacesSSMManagedNodeRole
安装 add-on:
aws eks create-addon \
--cluster-name $CLUSTER_NAME --region $REGION \
--addon-name amazon-sagemaker-spaces \
--configuration-values file://addon-config.yaml \
--resolve-conflicts OVERWRITE
轮询直到 add-on 达到 ACTIVE 状态(大约需要三分钟):
aws eks describe-addon \
--cluster-name $CLUSTER_NAME --region $REGION \
--addon-name amazon-sagemaker-spaces \
--query 'addon.{status:status,version:addonVersion,issues:health.issues}'
add-on 报告 ACTIVE 状态且 issues 列表为空:
{
"status": "ACTIVE",
"version": "v0.1.4-eksbuild.1",
"issues": []
}