创建lanuch template

在创建启动模板和计算节点组之前,我们需要了解 PCS 如何使用 Amazon Machine Images (AMIs)。

PCS 如何使用 AMI

PCS 使用我们提供的 AMI。与EKS不同,PCS 不维护自己的 AMI 。我们负责构建和维护计算节点使用的 AMI。

每个与 PCS 兼容的 AMI 必须安装两样东西:

  1. PCS agent — 处理节点向 PCS 服务的注册。使用 AWS 提供的安装程序从 aws-pcs-repo S3 存储桶安装。
  2. Slurm — 作业调度器。使用 AWS 提供的 Slurm 安装程序单独安装,与 PCS agent 分开。Slurm 版本必须与我们创建集群时指定的版本相匹配。

除了这两个要求之外,我们还可以安装所需的任何软件 — 驱动程序、库、监控代理、身份验证客户端。

示例 AMI 与自定义 AMI

AWS 提供示例 AMI 供快速测试:

aws ec2 describe-images \
  --owners amazon \
  --filters "Name=name,Values=aws-pcs-sample_ami-*-x86_64-slurm-*" \
  --query "Images | sort_by(@, &CreationDate) | [-1].{Name:Name,ImageId:ImageId}" \
  --output table \
  --region us-east-2

示例 AMI 仅供演示使用,不建议用于生产工作负载。它们可能不包含我们特定实例类型所需的驱动程序或配置。

image-20260803135340151

对于生产环境(以及本次研讨会),我们应该使用包含工作负载所需的特定驱动程序和软件的自定义 AMI。

AMI 中包含的内容

cloudformation创建的 AMI 是使用 EC2 Image Builder 从 Amazon Linux 2023 基础镜像构建的。它包括:

组件 用途
PCS agent 向 PCS 注册节点
Slurm 作业调度(与集群版本匹配)
EFA 驱动程序 低延迟节点间网络(EFA 安装程序还捆绑了 OpenMPI 5 和 environment-modules 系统)
Lustre 客户端 挂载 FSx for Lustre 文件系统
EFS utils 挂载 EFS 文件系统
CloudWatch agent 实例级指标和日志收集
SSSD + LDAP 客户端 针对 LDAP 服务器进行用户身份验证(预先配置)
Development Tools gcc、g++、make 用于编译 MPI 程序
Utilities htop、vim、jq

SSSD 配置已内置到 AMI 中 — 计算节点在启动时自动针对 LDAP 服务器进行身份验证,无需在启动模板中进行额外配置。

AMI 是如何构建的

预部署的 AMI 是使用 EC2 Image Builder 通过一个自定义组件构建的,该组件按顺序运行以下步骤:

  1. 更新操作系统
  2. 从 AWS 提供的安装程序安装 PCS agent
  3. 从 AWS 提供的安装程序安装 Slurm
  4. 安装 EFA 驱动程序(EFA 安装程序还引入了 OpenMPI 5 和 environment-modules,因此 module load openmpi5 可在每个节点上运行)
  5. 安装 Lustre 客户端、EFS utils、CloudWatch agent
  6. 安装 Development Tools 组和实用工具
  7. 安装并配置 SSSD/LDAP 客户端

这与https://github.com/aws-samples/sample-parallel-computing-service Terraform 仓库中的模式相同,该仓库提供了一个可用的参考实现。

创建启动模板

我们将创建一个 EC2 启动模板,用于告知 PCS 如何配置计算节点实例。启动模板控制 EFA 网络、文件系统挂载和placement group配置。

PCS 如何使用启动模板

PCS 使用 EC2 启动模板来配置它为计算节点组启动的实例。启动模板定义了:

  • 网络接口 — 用于 HPC 实例的 EFA 配置
  • placement group — 使实例在物理上保持接近,以实现低延迟网络
  • user data — 在启动时运行的 MIME 多部分脚本,用于挂载文件系统和配置服务

PCS 启动模板中的用户数据必须采用 MIME 多部分归档格式。这是因为 PCS 在节点注册期间会将我们的用户数据与其自身的内部用户数据合并。

检索堆栈输出

首先,确定哪个可用区拥有 hpc7a 实例,并使用匹配的子网:

export INFRA_STACK="pcs-workshop-infra"
export AWS_DEFAULT_REGION="us-east-2"

export HPC_SUBNET=$(aws cloudformation describe-stacks \
  --stack-name $INFRA_STACK \
  --query "Stacks[0].Outputs[?OutputKey=='HpcSubnetId'].OutputValue" \
  --output text)

export PRIVATE_SG=$(aws cloudformation describe-stacks \
  --stack-name $INFRA_STACK \
  --query "Stacks[0].Outputs[?OutputKey=='PrivateSecurityGroupId'].OutputValue" \
  --output text)

export EFA_SG=$(aws cloudformation describe-stacks \
  --stack-name $INFRA_STACK \
  --query "Stacks[0].Outputs[?OutputKey=='EfaSecurityGroupId'].OutputValue" \
  --output text)

export EFS_ID=$(aws cloudformation describe-stacks \
  --stack-name $INFRA_STACK \
  --query "Stacks[0].Outputs[?OutputKey=='EfsFileSystemId'].OutputValue" \
  --output text)

export FSX_ID=$(aws cloudformation describe-stacks \
  --stack-name $INFRA_STACK \
  --query "Stacks[0].Outputs[?OutputKey=='FsxLustreFileSystemId'].OutputValue" \
  --output text)

export FSX_MOUNT=$(aws cloudformation describe-stacks \
  --stack-name $INFRA_STACK \
  --query "Stacks[0].Outputs[?OutputKey=='FsxLustreMountName'].OutputValue" \
  --output text)

export FSX_DNS="${FSX_ID}.fsx.${AWS_DEFAULT_REGION}.amazonaws.com"

echo "HPC Subnet: $HPC_SUBNET"
echo "Private SG: $PRIVATE_SG"
echo "EFA SG: $EFA_SG"
echo "EFS ID: $EFS_ID"
echo "FSx DNS: $FSX_DNS"
echo "FSx Mount: $FSX_MOUNT"

cloudformation的 HpcSubnetId 输出是位于 hpc7a 实例可用的可用区中的私有子网。CloudFormation 模板使用 Lambda function 自动检测此项。

创建placement group

placement group是一项 EC2 功能,可影响实例在数据中心中的物理位置分布。对于使用 EFA 的 HPC 工作负载,我们需要一个 cluster placement group — 它将所有实例放置在物理上彼此靠近的硬件上,从而最小化节点之间的网络延迟。

如果没有placement group,我们的实例可能会分布在数据中心中相距很远的机架上,为每条 MPI 消息增加数微秒的延迟。对于紧耦合的并行工作负载,这一点很重要。

aws ec2 create-placement-group \
  --group-name hpc-cluster-pg \
  --strategy cluster \
  --region $AWS_DEFAULT_REGION

cluster 策略将实例尽可能紧密地打包在一起。我们在启动模板中引用此placement group,以便 PCS 启动的每个计算节点都位于同一个物理集群中。

创建user data文件

EC2 user data是一个脚本(或一组脚本),在实例首次启动时自动运行。PCS 要求用户数据采用 MIME 多部分归档格式 — 这是因为 PCS 会注入其自身的用户数据用于节点注册,而 MIME 格式允许将多个脚本组合在一起。

我们的 HPC 计算节点的user data执行两项操作:

  1. 将 EFS 挂载到 /home — 使所有用户在每个节点上都拥有共享的主目录
  2. 配置并将 FSx Lustre 挂载到 /fsx — 为 hpc7a 实例配置特殊的 EFA 网络

对于 hpc7a 实例,Lustre 可以使用 EFA 而不是 TCP 进行数据传输。这需要配置 Lustre 网络层(lnet)以通过 EFA 接口路由流量,从而提供比标准 TCP 显著更好的 I/O 性能。

创建用户数据文件:

cat > hpc-userdata.txt << USERDATA
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="==//=="

--==//==
Content-Type: text/x-shellscript; charset="us-ascii"
MIME-Version: 1.0

#!/bin/bash

# Mount EFS at /home
echo "${EFS_ID}.efs.${AWS_DEFAULT_REGION}.amazonaws.com:/ /home nfs4 nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,_netdev 0 0" >> /etc/fstab
mount /home

# Configure Lustre over EFA for hpc7a
modprobe lnet
modprobe kefalnd ipif_name=enp34s0
modprobe ksocklnd

lnetctl lnet configure
lnetctl net del --net tcp
lnetctl net add --net tcp --if enp34s0
lnetctl net add --net efa --if rdmap36s0 --peer-credits 32
lnetctl net add --net efa --if rdmap42s0 --peer-credits 32
lnetctl set discovery 1
lnetctl udsp add --src efa --priority 0
modprobe lustre

# Mount FSx Lustre at /fsx
echo "${FSX_DNS}@tcp:/${FSX_MOUNT} /fsx lustre defaults,_netdev,flock,user_xattr,noatime 0 0" >> /etc/fstab
mkdir -p /fsx
chmod a+rwx /fsx
mount /fsx
chmod 777 /fsx

# Configure Enroot for container support
# Cache on Lustre (shared across nodes), everything else on EBS (needs overlayfs xattr support)
cat > /etc/enroot/enroot.conf << 'ENROOTCFG'
ENROOT_RUNTIME_PATH /var/tmp/enroot/runtime
ENROOT_CONFIG_PATH  /var/tmp/enroot/config
ENROOT_CACHE_PATH   /fsx/enroot/cache
ENROOT_DATA_PATH    /var/tmp/enroot/data
ENROOT_TEMP_PATH    /var/tmp/enroot/tmp
ENROOTCFG
mkdir -p /var/tmp/enroot/runtime /var/tmp/enroot/config /var/tmp/enroot/data /var/tmp/enroot/tmp /fsx/enroot/cache
chmod 1777 /var/tmp/enroot /var/tmp/enroot/runtime /var/tmp/enroot/config /var/tmp/enroot/data /var/tmp/enroot/tmp
chmod 1777 /fsx/enroot/cache

# Remove NVIDIA container hook (CPU-only workshop)
rm -f /etc/enroot/hooks.d/98-nvidia.sh

--==//==
USERDATA

lnetctl 命令将 Lustre 网络配置为使用 EFA 而不是 TCP。在 hpc7a.96xlarge 实例上,enp34s0 是主网络接口,而 rdmap36s0/rdmap42s0 是两个 EFA 接口。lnetctl udsp add --src efa --priority 0 告知 Lustre 优先使用 EFA 而不是 TCP 进行数据传输,从而提供显著更好的 I/O 性能。

EFS 挂载使用 NFSv4.1 并采用较大的读/写大小(1 MB)以实现最佳吞吐量。_netdev 选项确保挂载等待网络可用。

SSSD/LDAP 身份验证已在 AMI 中配置 — 我们无需向用户数据添加 LDAP 配置。计算节点将在启动时自动针对 LDAP 服务器对用户进行身份验证。

创建 HPC 计算启动模板

EC2 启动模板是一个蓝图,定义了实例在启动时应如何配置。PCS 使用启动模板为它创建的每个计算节点设置网络、安全和启动时配置。

我们无需在启动模板中指定实例类型或 IAM 配置文件 — PCS 在我们创建计算节点组时会设置这些内容。

启动模板处理其他所有内容:网络接口、放置、安全组和用户数据。

对于 hpc7a.96xlarge,关键的网络细节是它具有 2 张网卡,每张都能运行 EFA。我们在启动模板中为每张网卡配置一个 EFA 接口。

为我们即将创建的启动模板设置 EC2 密钥对。cloudformation会在我们的账户中自动创建 ws-default-keypair,我们也可以使用自己创建的其他keypair,请在运行此代码块之前覆盖 KEY_NAME。该变量不得为空 — 空的 KeyName 会生成一个启动模板,当 PCS 启动实例时会导致验证失败。

export KEY_NAME="${KEY_NAME:-ws-default-keypair}"

创建启动模板:

PG_ID=$(aws ec2 describe-placement-groups \
  --group-names hpc-cluster-pg \
  --query "PlacementGroups[0].GroupId" \
  --output text \
  --region $AWS_DEFAULT_REGION)

aws ec2 create-launch-template \
  --launch-template-name hpc-compute-lt \
  --launch-template-data "{
    \"KeyName\": \"${KEY_NAME}\",
    \"MetadataOptions\": {
      \"HttpEndpoint\": \"enabled\",
      \"HttpPutResponseHopLimit\": 2,
      \"HttpTokens\": \"required\"
    },
    \"Monitoring\": {\"Enabled\": true},
    \"Placement\": {\"GroupId\": \"${PG_ID}\"},
    \"NetworkInterfaces\": [
      {
        \"DeviceIndex\": 0,
        \"NetworkCardIndex\": 0,
        \"InterfaceType\": \"efa\",
        \"Groups\": [\"${PRIVATE_SG}\", \"${EFA_SG}\"]
      },
      {
        \"DeviceIndex\": 1,
        \"NetworkCardIndex\": 1,
        \"InterfaceType\": \"efa\",
        \"Groups\": [\"${PRIVATE_SG}\", \"${EFA_SG}\"]
      }
    ],
    \"UserData\": \"$(base64 -w 0 hpc-userdata.txt 2>/dev/null || base64 -i hpc-userdata.txt | tr -d '\n')\"
  }" \
  --region $AWS_DEFAULT_REGION

以下是该配置的作用:

  • KeyName — 用于连接实例的 SSH 密钥对。Workshop Studio 会自动创建 ws-default-keypair。如果我们自行部署,请在运行上面的代码块之前使用我们账户中现有密钥对的名称覆盖 KEY_NAME
  • Placement GroupId — 通过 ID 引用集群放置组,使实例在物理上彼此靠近。
  • 两个 NetworkInterfaces — hpc7a.96xlarge 具有 2 张网卡。每张都获得一个具有相同安全组的 EFA 接口(用于 VPC 流量的私有 SG,用于节点间通信的 EFA SG)。此处未指定子网 — PCS 从计算节点组的 --subnet-ids 参数设置子网。
  • InterfaceType: efa — 在两个接口上启用 Elastic Fabric Adapter。
  • MetadataOptions — 配置 EC2 实例元数据服务(IMDS),即位于 169.254.169.254 的本地 HTTP 端点,实例上的进程用它来发现自己的身份和 IAM 凭证。
  • UserData — MIME 多部分脚本,经过 base64 编码。

当在启动模板中定义网络接口时,我们必须在每个接口的 Groups 字段中指定安全组 — 我们不能使用顶层的 SecurityGroupIds 参数。

在ec2的launch template中看到这个模板:

image-20260803142235491

保存启动模板 ID 以后面使用:

export HPC_LT_ID=$(aws ec2 describe-launch-templates \
  --launch-template-names hpc-compute-lt \
  --query "LaunchTemplates[0].LaunchTemplateId" \
  --output text \
  --region $AWS_DEFAULT_REGION)
echo "Launch Template ID: $HPC_LT_ID"

我们已经创建了一个启动模板,它为 hpc7a 实例配置 EFA 网络,并挂载 FSx Lustre(通过 EFA)和 EFS 文件系统。接下来,我们将创建一个使用此模板的计算节点组。