在 Google Kubernetes Engine 上进行性能测试

如需测试 Google Kubernetes Engine (GKE) 工作负载在多个 GKE 客户端上的读取和写入 性能,请使用 IOR 基准测试工具。以下说明介绍了如何自动执行客户端设置,以及如何通过 Kubernetes Pod 之间基于免密 SSH 的 mpirun 使用 IOR 来测试汇总 I/O。

前提条件

  • 已预配 Managed Lustre 实例。

  • 已配置本地 Docker 环境,并已通过身份验证以推送到 Google Artifact Registry 或 Container Registry(请参阅 身份验证方法)。

  • 确保网络的 mtu 值已 设置为 8896

创建 GKE 集群

如需测试性能,您需要启用 Managed Lustre CSI 驱动程序的 GKE 集群。对于高性能存储工作负载,请使用计算优化机器系列(例如 c2c3)和 TIER_1 网络配置 GKE 节点池。

运行以下命令以创建针对性能测试进行了优化的 Standard GKE 集群:

gcloud container clusters create CLUSTER_NAME \
    --zone=ZONE \
    --machine-type=MACHINE_TYPE \
    --addons=LustreCsiDriver \
    --network-performance-configs=total-egress-bandwidth-tier=TIER_1 \
    --network=NETWORK \
    --num-nodes=NUM_NODES
  • ZONENETWORK 替换为您的具体部署 值。集群必须与 Managed Lustre 实例位于同一 VPC 网络中。

  • 选择 MACHINE_TYPE。如需了解如何选择机器类型以获得最佳吞吐量,请参阅 性能注意事项

  • 如果您的机器类型不支持 TIER_1 网络,请从命令中删除 --network-performance-configs 行。

  • 指定 NUM_NODES。如需使文件系统达到饱和状态,集群的汇总网络容量应比文件系统的预配吞吐量高出约 20%。

    对于启用了 Tier 1 网络的机器,单个节点可以推送 25 Gbps 到 200 Gbps(约 3,000 到 25,000 MBps),具体取决于虚拟机系列和 CPU 数量。对于标准实例,出站流量通常限制为每个 vCPU 约 2 Gbps。

    例如,如果 Managed Lustre 实例容量的理论吞吐量为 100,000 MBps,则需要 120,000 MBps (100,000 * 1.2) 的汇总客户端出站流量才能使其达到饱和状态:

    • 对于标准实例: 如果每个节点的公布出站流量为 2,000 MBps,则应至少预配 60 个节点 (120,000 / 2,000)。
    • 对于 Tier 1 网络: 如果每个节点的公布出站流量为 10,000 MBps(约 80 Gbps),则应至少预配 12 个节点 (120,000 / 10,000)。

创建 IOR Docker 映像

构建安装了 OpenMPI 和 IOR 的容器映像。编译支持异步 I/O (AIO) 的 IOR,以获得更好的性能。

  1. 在本地创建一个名为 Dockerfile 的文件:

    FROM ubuntu:22.04
    
    # Prevent interactive prompts during installation
    ENV DEBIAN_FRONTEND=noninteractive
    
    # Install dependencies, SSH, and required Autotools packages
    RUN apt-get update && apt-get install -y \
      openssh-server \
      openmpi-bin \
      libopenmpi-dev \
      wget \
      git \
      make \
      gcc \
      g++ \
      automake \
      autoconf \
      libtool \
      pkg-config \
      libaio-dev \
      sudo \
      && rm -rf /var/lib/apt/lists/*
    
    # Build IOR from source (version 4.0.0) with Asynchronous I/O (AIO) support
    RUN git clone -b 4.0.0 https://github.com/hpc/ior /tmp/ior \
      && cd /tmp/ior \
      && ./bootstrap \
      && ./configure --disable-dependency-tracking --with-aio \
      && make -j"$(nproc)" \
      && make install \
      && rm -rf /tmp/ior
    
    # Configure SSH for OpenMPI passwordless communication
    RUN mkdir /var/run/sshd
    RUN echo 'root:root' | chpasswd
    RUN sed -i 's/^#PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config
    RUN sed -i 's/^#PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config
    
    # SSH login fix so user isn't kicked out after container initialization
    RUN sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd
    
    EXPOSE 22
    CMD ["/usr/sbin/sshd", "-D"]
    
  2. 构建此映像并将其推送到您首选的容器注册表。本文档中的说明使用 Artifact Registry。

    export IMAGE_TAG="gcr.io/PROJECT_ID/lustre-ior-benchmark:latest"
    docker build -t $IMAGE_TAG .
    docker push $IMAGE_TAG
    

为 MPI 生成免密 SSH 密钥

OpenMPI 需要使用免密 SSH 进行节点间通信。创建 SSH 密钥并将其存储在 Kubernetes Secret 中。

  1. 生成 RSA 密钥:

    ssh-keygen -t rsa -b 4096 -C "mpi-user" -N '' -f "./id_rsa"
    
  2. 创建 Kubernetes Secret:

    kubectl create secret generic mpi-ssh-secret \
      --from-file=id_rsa=./id_rsa \
      --from-file=id_rsa.pub=./id_rsa.pub \
      --from-file=authorized_keys=./id_rsa.pub
    

创建永久性卷和声明

使用 静态预配将 GKE pod 连接到 Managed Lustre 实例。

  1. 创建一个名为 lustre-pv.yaml 的文件。替换以下内容:

    • CAPACITY,替换为实例的存储容量(以 GiB 为单位)。
    • EXTENDED_LUSTRE_ID,替换为 Managed Lustre 标识符,格式为 PROJECT_ID/ZONE/INSTANCE_NAME。 例如,project-123/us-west1-a/my-lustre-instance
    • LUSTRE_IP,替换为实例的装载 IP 地址。
    • FS_NAME,替换为实例的文件系统名称。

    您可以使用 gcloud lustre instances describe 命令检索这些值。

    apiVersion: v1
    kind: PersistentVolume
    metadata:
      name: my-lustre-pv
    spec:
      storageClassName: ""
      claimRef:
        name: my-lustre-pvc
        namespace: default
      accessModes:
        - ReadWriteMany
      capacity:
        storage: CAPACITYGi   # retain `Gi` suffix
      persistentVolumeReclaimPolicy: Retain
      volumeMode: Filesystem
      csi:
        driver: lustre.csi.storage.gke.io
        volumeHandle: EXTENDED_LUSTRE_ID   # project-name/zone/instance-name
        volumeAttributes:
          ip: LUSTRE_IP
          filesystem: FS_NAME
    ---
    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: my-lustre-pvc
    spec:
      storageClassName: ""
      volumeName: my-lustre-pv
      accessModes:
        - ReadWriteMany
      resources:
        requests:
          storage: CAPACITYGi
    
  2. 应用清单:

    kubectl apply -f lustre-pv.yaml
    

部署 MPI 工作器

如需在多个节点上扩缩 IOR 任务,请使用 基准测试映像部署 StatefulSet

  1. 创建一个名为 mpi-workers.yaml 的文件。指定 PROJECT_ID, 并将 NUM_NODES 设置为集群中的节点数。

    apiVersion: v1
    kind: Service
    metadata:
      name: mpi-workers
      labels:
        app: mpi-worker
    spec:
      clusterIP: None
      selector:
        app: mpi-worker
      ports:
      - port: 22
        name: ssh
    ---
    apiVersion: apps/v1
    kind: StatefulSet
    metadata:
      name: mpi-worker
    spec:
      serviceName: "mpi-workers"
      replicas: NUM_NODES
      selector:
        matchLabels:
          app: mpi-worker
      template:
        metadata:
          labels:
            app: mpi-worker
        spec:
          tolerations:
            - operator: "Exists"
          containers:
            - name: mpi-worker
              image: gcr.io/PROJECT_ID/lustre-ior-benchmark:latest
              command: ["/bin/sh", "-c"]
              args:
                - >-
                  mkdir -p /var/run/sshd &&
                  ssh-keygen -A &&
                  mkdir -p /root/.ssh &&
                  echo "Host *" > /root/.ssh/config &&
                  echo "    StrictHostKeyChecking no" >> /root/.ssh/config &&
                  echo "    UserKnownHostsFile=/dev/null" >> /root/.ssh/config &&
                  cp /mnt/mpi-ssh-keys/id_rsa /root/.ssh/id_rsa &&
                  cp /mnt/mpi-ssh-keys/id_rsa.pub /root/.ssh/id_rsa.pub &&
                  cp /mnt/mpi-ssh-keys/authorized_keys /root/.ssh/authorized_keys &&
                  chmod 700 /root/.ssh &&
                  chmod 600 /root/.ssh/* &&
                  exec /usr/sbin/sshd -D
              ports:
                - containerPort: 22
              volumeMounts:
                - name: lustre-mount
                  mountPath: /lustre
                - name: ssh-key-secret
                  mountPath: /mnt/mpi-ssh-keys
                  readOnly: true
          volumes:
            - name: lustre-mount
              persistentVolumeClaim:
                claimName: my-lustre-pvc
            - name: ssh-key-secret
              secret:
                secretName: mpi-ssh-secret
    
  2. 应用清单:

    kubectl apply -f mpi-workers.yaml
    

运行 IOR 基准测试

从第一个 pod (mpi-worker-0) 启动基准测试,将其视为头节点。

  1. 生成包含工作器的内部 IP 地址的主机文件,并将其复制到头节点:

    kubectl get pods -l app=mpi-worker -o jsonpath='{range .items[*]}{.status.podIP}{"\n"}{end}' > hosts.txt
    kubectl cp hosts.txt mpi-worker-0:/root/hostfile
    
  2. 在头 pod 内打开 bash 会话:

    kubectl exec -it mpi-worker-0 -- /bin/bash
    
  3. 在 pod 内,创建一个测试目录:

    mkdir -p /lustre/test
    
  4. 定义测试变量:

    export NUM_NODES="NUM_NODES"
    export PROCESSES_PER_NODE="PROCESSES_PER_NODE"
    export NUM_PROCESSES=$(( NUM_NODES * PROCESSES_PER_NODE ))
    

    其中:

    • NUM_NODES:参与 测试的工作器 pod 总数。

    • PROCESSES_PER_NODE:要在每个 容器上运行的 MPI 排名数。建议您先将其设置为与客户端机器上的物理核心数(或 vCPU 数的一半)相匹配。 对于高性能机器类型,将其设置为介于 816 之间通常会产生最佳网络吞吐量。

  5. 运行基准测试命令:

    写入吞吐量

    此命令会持续写入 60 秒,以测试峰值稳态吞吐量。每个任务的文件大小设置为任意大的上限 50 TiB,以使任务持续写入,直到 60 秒计时器到期。

    mpirun \
      --allow-run-as-root \
      --mca plm_rsh_no_tree_spawn 1 \
      --mca opal_set_max_sys_limits 1 \
      --mca plm_rsh_num_concurrent ${NUM_NODES} \
      --mca plm_rsh_args "-o StrictHostKeyChecking=no" \
      --npernode ${PROCESSES_PER_NODE} \
      --np ${NUM_PROCESSES} \
      --hostfile ~/hostfile \
      /usr/local/bin/ior \
      -a AIO \
      --posix.odirect \
      -F -g -v -w -k \
      -t 4m -b 50t \
      -D 60 \
      -O stoneWallingWearOut=1 \
      -O stoneWallingStatusFile=/lustre/test/ior-easy.stonewall \
      -o /lustre/test/ior_file

    读取吞吐量

    此阶段会读取在 60 秒写入吞吐量测试期间成功写入的数据量。 虽然每个任务的文件大小 (-b) 设置为 50 TiB 以匹配写入阶段的几何图形,但 -O stoneWallingWearOut=1 标志会指示 IOR 在达到 stonewall 状态文件中记录的确切数据边界后立即停止读取。

    mpirun \
      --allow-run-as-root \
      --mca plm_rsh_no_tree_spawn 1 \
      --mca opal_set_max_sys_limits 1 \
      --mca plm_rsh_num_concurrent ${NUM_NODES} \
      --mca plm_rsh_args "-o StrictHostKeyChecking=no" \
      --npernode ${PROCESSES_PER_NODE} \
      --np ${NUM_PROCESSES} \
      --hostfile ~/hostfile \
      /usr/local/bin/ior \
      -a AIO \
      --posix.odirect \
      -F -g -v -r -k \
      -t 4m -b 50t \
      -D 60 \
      -O stoneWallingWearOut=1 \
      -O stoneWallingStatusFile=/lustre/test/ior-easy.stonewall \
      -o /lustre/test/ior_file

    写入 IOPS

    此测试使用较小的 4 KiB 传输大小和 8 GiB 每个任务的文件大小来衡量文件系统可以处理的最大每秒输入/输出操作数 (IOPS)。

    mpirun \
      --allow-run-as-root \
      --mca plm_rsh_no_tree_spawn 1 \
      --mca opal_set_max_sys_limits 1 \
      --mca plm_rsh_num_concurrent ${NUM_NODES} \
      --mca plm_rsh_args "-o StrictHostKeyChecking=no" \
      --np ${NUM_PROCESSES} \
      --oversubscribe \
      --map-by node \
      --bind-to socket \
      --hostfile ~/hostfile \
      /usr/local/bin/ior \
      -e \
      -t 4k \
      -b 8g \
      -s 1 \
      -a AIO \
      --posix.odirect \
      --aio.max-pending=256 \
      -w \
      -F \
      -z \
      -Q 1 \
      -G 1745405099 \
      -D 45 \
      -O stoneWallingWearOut=1 \
      -o /lustre/test/ior-random

    读取 IOPS

    为防止从稀疏文件读取,此测试使用两个命令:一个写入命令,用于使用 4 MiB 传输大小和 8 GiB 每个任务的文件大小创建一个实心文件;另一个命令,用于执行实际的 4 KiB 随机读取 IOPS 测试。

    1. 创建要读取的文件:

      mpirun \
        --allow-run-as-root \
        --mca plm_rsh_no_tree_spawn 1 \
        --mca opal_set_max_sys_limits 1 \
        --mca plm_rsh_num_concurrent ${NUM_NODES} \
        --mca plm_rsh_args "-o StrictHostKeyChecking=no" \
        --npernode ${PROCESSES_PER_NODE} \
        --np ${NUM_PROCESSES} \
        --oversubscribe \
        --map-by node \
        --bind-to socket \
        --hostfile ~/hostfile \
        /usr/local/bin/ior \
        -a AIO \
        --posix.odirect \
        -w \
        -F \
        -k \
        -t 4m \
        -b 8g \
        -s 1 \
        -Q 1 \
        -G 1745405099 \
        -o /lustre/test/ior_rand_read
    2. 运行 IOR 读取测试:

      mpirun \
        --allow-run-as-root \
        --mca plm_rsh_no_tree_spawn 1 \
        --mca opal_set_max_sys_limits 1 \
        --mca plm_rsh_num_concurrent ${NUM_NODES} \
        --mca plm_rsh_args "-o StrictHostKeyChecking=no" \
        --oversubscribe \
        --map-by node \
        --bind-to socket \
        --npernode ${PROCESSES_PER_NODE} \
        --np ${NUM_PROCESSES} \
        --hostfile ~/hostfile \
        /usr/local/bin/ior \
        -a AIO \
        --posix.odirect \
        --aio.max-pending 256 \
        -r \
        -F \
        -z \
        -t 4k \
        -b 8g \
        -s 1 \
        -Q 1 \
        -G 1745405099 \
        -D 45 \
        -O stoneWallingWearOut=1 \
        -o /lustre/test/ior_rand_read

    mpirun 标志包括:

    • --mca plm_rsh_no_tree_spawn 1:停用基于树的守护程序生成,以提高跨节点的启动可靠性。
    • --mca opal_set_max_sys_limits 1:自动尝试将系统 限制(例如最大打开文件数)设置为允许的最大值。
    • --mca plm_rsh_num_concurrent:设置在启动工作器守护进程时将使用的并发 SSH 连接数上限mpirun
    • --mca plm_rsh_args ...:绕过严格的主机密钥检查,以防止 交互式 SSH 提示使 MPI 进程启动挂起。
    • --prefix ...:为 Rocky Linux 和 RHEL 显式定义 OpenMPI 安装路径,以便工作器节点可以找到所需的守护进程 (orted)。
    • --allow-run-as-root:允许 mpirun 以根用户身份执行。
    • --oversubscribe:允许 MPI 在节点上安排的进程数超过可用物理核心数。
    • --map-by node:以轮循方式在可用节点之间均匀分配 MPI 进程 。
    • --bind-to socket:将 MPI 进程绑定到物理 CPU 插槽,以 优化内存访问和缓存性能。
    • --npernode:每个节点的进程数。
    • --np:要启动的 MPI 进程总数。
    • --hostfile:指定包含要运行的主机列表的文件。

    ior 标志包括:

    • -a AIO --posix.odirect:使用异步 I/O (AIO) 引擎与 POSIX 直接 I/O 相结合。这会绕过客户端 RAM 页面缓存,并 强制并发非阻塞写入直接写入存储服务器, 确保基准测试衡量的是真实的网络存储性能,而不是内存缓冲区。
    • --aio.max-pending=256:确定每个进程中正在进行的并发 异步 I/O 操作数上限。
    • -C:重新排序任务以获得最佳读取性能。
    • -F:每个进程一个文件模式。
    • -g:使用屏障分隔测试的写入和读取阶段。
    • -v:输出详细日志记录。
    • -w / -r:指示 IOR 运行写入 (-w) 或读取 (-r) 性能测试。
    • -k:防止 IOR 在写入后删除测试文件,使其 可用于读取测试。
    • -e:在写入阶段后执行 fsync,以保证数据提交到存储驱动器。
    • -z:指示 IOR 执行随机访问 I/O,而不是顺序访问。
    • -s 1:将段数设置为 1。
    • -Q 1:设置每个节点的任务偏移量,对齐任务,以便基准测试 在所有节点上正确协调。
    • -G 1745405099:对随机种子时间戳进行硬编码,以便读取阶段 生成与写入阶段使用的完全相同的随机文件偏移量。
    • -t:设置每个 I/O 操作的传输大小(例如,4m 表示 吞吐量,4k 表示 IOPS)。
    • -b:设置每个进程的目标块大小(例如,50t 表示吞吐量,8g 表示 IOPS),以确保测试在定时运行期间不会耗尽有效载荷数据。
    • -D:将测试运行时长限制为特定的秒数 (例如,6045)。这种“stonewalling”会均匀终止基准测试,以捕获真实的稳态测量结果。
    • -O stoneWallingWearOut=1:强制所有并发线程在整个时长内持续生成写入负载,防止速度较快的线程提前完成并降低整体网络压力。
    • -O stoneWallingStatusFile=<path>:在写入阶段结束时写入状态验证文件。后续读取阶段使用此文件仅读取成功提交的块,防止在读取期间出现空指针错误。
    • -o:Managed Lustre 文件系统上测试文件的路径。

查看结果

基准测试完成后,它会直接在终端中显示所有客户端虚拟机或 pod 的汇总性能指标。在输出底部查找结果 表,以找到最大吞吐量或 IOPS。

关键指标

  • aggregate filesize:在测试期间所有参与的客户端写入或读取的数据总量。

  • bw(MiB/s) / Max Write / Max Read :顺序测试最重要的指标。这显示了 Managed Lustre 文件系统实现的汇总带宽。

  • IOPS:随机 I/O 测试最重要的指标。这显示了每秒输入/输出操作数上限。

将结果导出到文件

如果您想以编程方式解析结果、将其馈送到数据库中或将其保存以供日后分析,您可以指示 IOR 以 JSON 和 CSV 格式导出摘要数据,而不是仅将其打印到屏幕上。

为此,请将 -O 标志附加到 ior 命令字符串的末尾:

-O summaryFormat=JSON \
-O summaryFile=/lustre/test/perf-results/summary.json \
-O saveRankPerformanceDetailsCSV=/lustre/test/perf-results/details.csv

在运行基准测试之前,输出目录必须存在于文件系统上。

预期性能与实际性能

您可以根据文件系统的预配容量和性能层级计算其数学最大吞吐量。由于存储容量以吉比字节 (GiB) 为单位预配,而层级以特比字节 (TiB) 为单位评级,因此您必须先转换容量:

(Capacity GiB / 1024) * Tier MBps = 理论最大 MBps

例如,216,000 GiB 实例在 500 MBps/TiB 层级中,数学上可提供 105,469 MBps 的吞吐量 ((216000 / 1024) * 500)。

观察到的最大吞吐量始终受文件系统的预配吞吐量容量或客户端机器的汇总网络出站流量限制(以较低者为准)的限制。

基准测试数值可能无法达到理论速度的常见原因包括:

  • TCP/IP 开销: 标准网络封装和数据包标头大约消耗原始带宽的 5-10%。您的数学最大值包含此开销,但 IOR 基准测试仅衡量写入磁盘的原始有效载荷。

  • 客户端网络限制: 客户端机器具有严格的出站流量带宽上限。如果您使用的客户端或节点数量较少,或者机器类型未启用 Tier 1 网络,则客户端会在 Managed Lustre 文件系统达到其限制之前限制基准测试。

  • MPI 上下文切换: 如果 PROCESSES_PER_NODE 设置为高于客户端机器上的物理核心数,则 CPU 争用和上下文切换开销会人为降低基准测试的 I/O 性能。

  • 缺少直接 I/O: 如果省略 --posix.odirect 标志,数据将通过客户端的 RAM 页面缓存。这会引入内存瓶颈和 CPU 开销,从而掩盖真实的网络存储性能。

清理

为避免因本页中使用的资源导致您的 Google Cloud 账号产生费用,请按照以下步骤操作:

  1. 从 Managed Lustre 卷中删除测试文件:

    kubectl exec mpi-worker-0 -- rm -rf /lustre/test
    
  2. 删除 GKE 集群:

    gcloud container clusters delete CLUSTER_NAME --zone=ZONE
    

    删除集群还会删除 GKE pod、Kubernetes Secret 和永久性卷声明。

  3. 如果您推送了基准测试 Docker 映像,但不再需要该映像,请从代码库中删除该映像:

    gcloud container images delete gcr.io/PROJECT_ID/lustre-ior-benchmark:latest --force-delete-tags
    
  4. 如果您专门为此测试创建了 Managed Lustre 实例,但不再需要该实例,请将其删除:

    gcloud lustre instances delete INSTANCE_ID --location=LOCATION
    

排查常见的基准测试瓶颈

如果基准测试结果远低于预期的存储性能层级,请参阅排查常见瓶颈