複数の GKE クライアントから Google Kubernetes Engine(GKE)ワークロードの読み取り / 書き込みパフォーマンスをテストするには、IOR ベンチマーク ツールを使用します。次の手順では、クライアントの設定を自動化し、Kubernetes Pod 間のパスワードなしの SSH で mpirun を使用して IOR を使用し、集約 I/O をテストする方法について説明します。
前提条件
Managed Lustre インスタンスがすでにプロビジョニングされている。
Google Artifact Registry または Container Registry に push するように構成され、認証されたローカル Docker 環境(認証方法をご覧ください)。
ネットワークの
mtu値が8896に設定されていることを確認します。
GKE クラスタを作成する
パフォーマンスをテストするには、Managed Lustre CSI ドライバが有効になっている GKE クラスタが必要です。高パフォーマンス ストレージ ワークロードの場合は、コンピューティング最適化マシン ファミリー(c2 や c3 など)と 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
ZONE と NETWORK は、特定のデプロイの値に置き換えます。クラスタは、Managed Lustre インスタンスと同じ VPC ネットワークに存在する必要があります。
MACHINE_TYPE を選択します。最適なスループットを実現するマシンタイプの選択については、パフォーマンスに関する考慮事項をご覧ください。
マシンタイプが TIER_1 ネットワーキングをサポートしていない場合は、コマンドから
--network-performance-configs行を削除します。NUM_NODES を指定します。ファイル システムを飽和させるには、クラスタの合計ネットワーク容量がファイル システムのプロビジョニングされたスループットを約 20% 超える必要があります。
Tier 1 ネットワーキングが有効になっているマシンでは、VM ファミリーと CPU 数に応じて、単一ノードで 25 Gbps ~ 200 Gbps(約 3,000 ~ 25,000 MBps)をプッシュできます。標準インスタンスの場合、下り(外向き)は通常、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)をプロビジョニングする必要があります。
- 標準インスタンスの場合: 各ノードの公開下り(外向き)が 2,000 MBps の場合は、少なくとも 60 個のノード(
IOR Docker イメージを作成する
OpenMPI と IOR がインストールされたコンテナ イメージをビルドします。パフォーマンスを向上させるため、非同期 I/O(AIO)のサポートを使用して IOR をコンパイルします。
ローカルに
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"]このイメージをビルドして、任意のコンテナ レジストリに push します。このドキュメントの手順では、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 に保存します。
RSA 鍵を生成します。
ssh-keygen -t rsa -b 4096 -C "mpi-user" -N '' -f "./id_rsa"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
Persistent Volume と Claim を作成する
静的プロビジョニングを使用して、GKE Pod を Managed Lustre インスタンスに接続します。
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次のようにマニフェストを適用します。
kubectl apply -f lustre-pv.yaml
MPI ワーカーをデプロイする
複数のノードに IOR タスクをスケーリングするには、ベンチマーク イメージを使用して StatefulSet をデプロイします。
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次のようにマニフェストを適用します。
kubectl apply -f mpi-workers.yaml
IOR ベンチマークを実行する
最初の Pod(mpi-worker-0)からベンチマークを開始し、ヘッドノードとして扱います。
ワーカーの内部 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/hostfilehead Pod 内で bash セッションを開きます。
kubectl exec -it mpi-worker-0 -- /bin/bashPod 内にテスト ディレクトリを作成します。
mkdir -p /lustre/testテスト変数を定義します。
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 数の半分)と一致するように設定することをおすすめします。ハイ パフォーマンス マシンタイプの場合、この値を
8と16の間に設定すると、通常は最高のネットワーク スループットが得られます。
ベンチマーク コマンドを実行します。
書き込みスループット
このコマンドは、ピーク時の定常状態のスループットをテストするために、60 秒間継続的に書き込みを行います。タスクごとのファイルサイズは、60 秒のタイマーが切れるまでタスクが書き込みを続けるように、上限を 50 TiB に設定します。
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 がストーンウォール ステータス ファイルに記録された正確なデータ境界に達するとすぐに読み取りを停止するように指示します。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 のタスクごとのファイルサイズを使用して、ファイル システムが処理できる最大 1 秒あたりの入出力オペレーション(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
スパース ファイルからの読み取りを防ぐため、このテストでは 2 つのコマンドを使用します。1 つは、4 MiB の転送サイズと 8 GiB のタスクごとのファイルサイズを使用してソリッド ファイルを作成する書き込みです。もう 1 つは、実際の 4 KiB ランダム読み取り 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" \ --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
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: ワーカー デーモンを起動するときにmpirunが使用する同時 SSH 接続の最大数を設定します。--mca plm_rsh_args ...: 厳密なホストキー チェックをバイパスして、インタラクティブな SSH プロンプトが MPI プロセスの起動をハングアップしないようにします。--prefix ...: Rocky Linux と RHEL の OpenMPI インストール パスを明示的に定義します。これにより、ワーカーノードは必要なデーモン(orted)を見つけることができます。--allow-run-as-root:mpirunが root ユーザーとして実行できるようにします。--oversubscribe: MPI が使用可能な物理コアよりも多くのプロセスをノードにスケジュールできるようにします。--map-by node: 使用可能なノードに MPI プロセスをラウンドロビン方式で均等に分散します。--bind-to socket: MPI プロセスを物理 CPU ソケットにバインドして、メモリアクセスとキャッシュ パフォーマンスを最適化します。--npernode: ノードあたりのプロセス数。--np: 起動する MPI プロセスの合計数。--hostfile: 実行するホストのリストを含むファイルを指定します。
iorフラグは次のとおりです。-a AIO --posix.odirect: POSIX Direct I/O と組み合わせた非同期 I/O(AIO)エンジンを使用します。これにより、クライアントサイドの RAM ページ キャッシュがバイパスされ、同時実行の非ブロッキング書き込みがストレージ サーバーに直接強制的に行われるため、ベンチマークではメモリ バッファではなく、実際のネットワーク ストレージのパフォーマンスが測定されます。--aio.max-pending=256: プロセスごとに処理中の非同期 I/O オペレーションの最大数を決定します。-C: 読み取りパフォーマンスを最適化するためにタスクの順序を変更します。-F: プロセスごとのファイルモード。-g: バリアを使用して、テストの書き込みフェーズと読み取りフェーズを分離します。-v: 詳細ロギングを出力します。-w/-r: IOR に書き込み(-w)または読み取り(-r)のパフォーマンス テストを実行するよう指示します。-k: IOR が書き込み後にテストファイルを削除しないようにし、読み取りテストで使用できるようにします。-e: 書き込みフェーズの後にfsyncを実行して、データがストレージ ドライブに commit されることを保証します。-z: 順次アクセスではなくランダム アクセス I/O を実行するように IOR に指示します。-s 1: セグメントの数を 1 に設定します。-Q 1: ノードあたりのタスク オフセットを設定し、すべてのノードでベンチマーク座標が正しく調整されるようにタスクを調整します。-G 1745405099: 読み取りフェーズで書き込みフェーズで使用されたものとまったく同じランダム ファイル オフセットが生成されるように、ランダム シード タイムスタンプをハードコードします。-t: 各 I/O オペレーションの転送サイズを設定します(例: スループットの場合は4m、IOPS の場合は4k)。-b: テストの実行中にペイロード データが不足しないように、プロセスあたりのターゲット ブロックサイズ(スループットの場合は50t、IOPS の場合は8gなど)を設定します。-D: テストの実行時間を特定の秒数(60や45など)に制限します。この「ストーンウォール」により、ベンチマークが均等に終了し、真の定常状態の測定値が取得されます。-O stoneWallingWearOut=1: すべての同時実行スレッドに、期間全体にわたって継続的な書き込み負荷を生成させ、高速なスレッドが早期に完了してネットワーク全体の負荷が低下するのを防ぎます。-O stoneWallingStatusFile=<path>: 書き込みフェーズの最後に状態検証ファイルを書き込みます。後続の読み取りフェーズでは、このファイルを使用して正常に commit されたブロックのみを読み取り、読み取り中の null ポインタ エラーを防ぎます。-o: Managed Lustre ファイル システム上のテストファイルのパス。
結果を見る
ベンチマークが完了すると、すべてのクライアント VM または Pod の集計パフォーマンス指標がターミナルに直接表示されます。出力の下部にある [結果] テーブルで、最大スループットまたは IOPS を確認します。
主な指標
aggregate filesize: テスト中にすべての参加クライアントで書き込まれたデータまたは読み取られたデータの合計量。bw(MiB/s)/Max Write/Max Read: 順次テストで最も重要な指標。これは、Managed Lustre ファイル システムで実現された合計帯域幅を示します。IOPS: ランダム I/O テストで最も重要な指標。これは、1 秒あたりの最大入出力オペレーション数を示しています。
結果をファイルにエクスポートする
結果をプログラムで解析したり、データベースにフィードしたり、後で分析するために保存したりする場合は、画面に表示するだけでなく、JSON 形式と CSV 形式で概要データをエクスポートするように IOR に指示できます。
これを行うには、ior コマンド文字列の末尾に -O フラグを追加します。
-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
たとえば、500 Mbps / TiB ティアの 216,000 GiB インスタンスは、数学的に 105,469 Mbps のスループットを提供します((216000 / 1024)* 500)。
観測される最大スループットは、ファイル システムのプロビジョニングされたスループット容量とクライアント マシンのネットワーク下り(外向き)上限の合計のいずれか小さい方によって常に制限されます。
ベンチマークの数値が理論上の速度に達しない一般的な理由には、次のようなものがあります。
TCP/IP オーバーヘッド: 標準のネットワーク カプセル化とパケット ヘッダーは、生の帯域幅の約 5 ~ 10% を消費します。数学的な最大値にはこのオーバーヘッドが含まれますが、IOR ベンチマークではディスクに書き込まれた未加工のペイロードのみが測定されます。
クライアント ネットワークの上限: クライアント マシンには、下り(外向き)帯域幅の厳しい上限が設定されています。少数のクライアントまたはノードを使用する場合、または Tier 1 ネットワーキングが有効になっていないマシンタイプを使用する場合、Managed Lustre ファイル システムが上限に達する前にクライアントがベンチマークをスロットリングします。
MPI コンテキスト切り替え:
PROCESSES_PER_NODEがクライアント マシンの物理コア数よりも大きい値に設定されている場合、CPU 競合とコンテキスト切り替えのオーバーヘッドにより、ベンチマークの I/O パフォーマンスが人為的に低下します。Direct I/O がない:
--posix.odirectフラグが省略されている場合、データはクライアントの RAM ページ キャッシュを通過します。これにより、メモリのボトルネックと CPU オーバーヘッドが発生し、実際のネットワーク ストレージのパフォーマンスが隠蔽されます。
クリーンアップ
このページで使用したリソースについて、 Google Cloud アカウントに課金されないようにするには、次の操作を行います。
マネージド Lustre ボリュームからテストファイルを削除します。
kubectl exec mpi-worker-0 -- rm -rf /lustre/testGKE クラスタを削除します。
gcloud container clusters delete CLUSTER_NAME --zone=ZONEクラスタを削除すると、GKE Pod、Kubernetes Secret、Persistent Volume Claim も削除されます。
ベンチマーク Docker イメージを push して、そのイメージが不要になった場合は、リポジトリからイメージを削除します。
gcloud container images delete gcr.io/PROJECT_ID/lustre-ior-benchmark:latest --force-delete-tagsこのテスト専用に Managed Lustre インスタンスを作成し、不要になった場合は、次の手順で削除します。
gcloud lustre instances delete INSTANCE_ID --location=LOCATION
一般的なベンチマークのボトルネックのトラブルシューティング
ベンチマークの結果が想定されるストレージ パフォーマンス ティアよりも大幅に低い場合は、一般的なボトルネックのトラブルシューティングをご覧ください。