NVMe-over-TCPを本番環境で使う:Linuxカーネルアーキテクチャ、SPDK、高IOPS Kubernetesストレージ

目次(14 項目)
NVMe-over-TCP (NVMe/TCP)は、標準的なイーサネット上で堅牢かつ高性能なブロックストレージ転送を提供し、NVMeコマンドセットの効率性を活用します。このガイドでは、そのアーキテクチャ、デプロイに関する考慮事項、および高IOPS、低レイテンシーのストレージを実現するためのKubernetesへの統合について詳しく説明します。カーネル空間とユーザー空間(SPDK)の実装におけるパフォーマンスへの影響を分析し、要求の厳しいパフォーマンス目標を達成する方法を示します。
NVMe/TCPアーキテクチャの概要
NVMe/TCPは、NVMeコマンドとデータをTCP/IPパケット内にカプセル化します。これにより、標準的なネットワークインフラストラクチャで高性能ストレージトラフィックを伝送でき、特殊なファイバーチャネルやInfiniBandハードウェアは不要になります。
主要コンポーネント
- NVMeホスト: NVMe/TCPストレージを利用するイニシエーターで、通常はLinuxサーバーまたはKubernetesノードです。
- NVMeターゲット: TCP経由でNVMeネームスペースをエクスポートするストレージサーバーです。これは、専用のストレージアプライアンス、NVMeドライブを備えたLVM/ZFSを実行する汎用サーバー、またはSPDKベースのソリューションのいずれかです。
- NVMe-oF (NVMe over Fabrics) プロトコル: TCP、RDMA (RoCE, iWARP)、ファイバーチャネルなど、さまざまなネットワークファブリック上でNVMeコマンドがどのように転送されるかを定義する包括的な仕様です。
LinuxカーネルNVMe/TCPスタック
Linuxカーネルは、ネイティブのNVMe/TCPイニシエーターおよびターゲットをサポートしています。
- イニシエーター:
nvme-tcpカーネルモジュールは、接続の確立、コマンドの送信、およびデータ転送を処理します。ブロックレイヤーと統合され、リモートNVMeネームスペースをローカルブロックデバイス(例:/dev/nvme0n1)として提示します。 - ターゲット:
nvmet-tcpカーネルモジュールは、ローカルNVMeデバイスまたはブロックデバイスをNVMe/TCPネームスペースとして公開します。
このカーネルネイティブなアプローチは、シンプルさと幅広い互換性を提供しますが、ユーザー空間とカーネル空間間のコンテキストスイッチのオーバーヘッドと、TCP/IPスタック処理を伴います。
SPDK (Storage Performance Development Kit)
SPDKは、高性能でスケーラブルなストレージアプリケーションを記述するためのユーザー空間ライブラリとツールのコレクションです。NVMe/TCPの場合、SPDKは以下を提供します。
- ユーザー空間NVMeドライバー: カーネルのブロックレイヤーとNVMeドライバーをバイパスし、
UIO(Userspace I/O) またはVFIO(Virtual Function I/O) を介してNVMeデバイスに直接アクセスします。 - ユーザー空間TCP/IPスタック: SPDKには、独自の高度に最適化されたポーリングモードTCP/IPスタックが含まれており、カーネルのコンテキストスイッチと割り込み駆動型処理を排除します。これは、超低レイテンシーと高IOPSを達成するために不可欠です。
- ポーリングモード: 割り込みに依存する代わりに、SPDKはI/O完了とネットワークイベントを継続的にポーリングし、レイテンシーのジッターを低減します。
SPDKのアプローチは優れたパフォーマンスをもたらしますが、専用のCPUコアとメモリが必要であり、設定がより複雑になる可能性があります。
アーキテクチャ比較: NVMe/TCP vs. 代替案
| 機能 | iSCSI | NVMe/TCP (カーネル) | NVMe/TCP (SPDK) | NVMe/RoCE (RDMA) |
|---|---|---|---|---|
| プロトコル | SCSI over TCP/IP | NVMe over TCP/IP | NVMe over User-space TCP/IP | NVMe over RDMA |
| コマンドセット | SCSI (レガシー) | NVMe (モダン、並列) | NVMe (モダン、並列) | NVMe (モダン、並列) |
| ネットワーク | 標準イーサネット | 標準イーサネット | 標準イーサネット | RDMA対応イーサネット (RoCE/iWARP) |
| CPUオーバーヘッド | 中程度 | 中程度-高 (カーネルTCP) | 低 (ユーザー空間ポーリング) | 低 (ハードウェアオフロード) |
| レイテンシー | 高 (100s µs - ms) | 中 (50-200 µs) | 低 (50 µs未満) | 非常に低い (20 µs未満) |
| IOPS | 中程度 (10s-100s kIOPS) | 高 (100s kIOPS - 1M IOPS) | 非常に高い (1M+ IOPS) | 極めて高い (2M+ IOPS) |
| 複雑さ | 低 | 低-中 | 高 (専用リソース) | 中 (RDMAネットワーク設定) |
| ハードウェア | 標準NIC | 標準NIC | 標準NIC (CPU/メモリ集約型) | RDMA NIC (RoCE/iWARP) |
| ユースケース | 汎用、互換性 | 高性能、費用対効果 | 極限性能、レイテンシー重視 | 超低レイテンシー、HPC、AI/ML |
KubernetesでのNVMe/TCPのデプロイ
ここでは、シンプルさと幅広い適用性を考慮して、カーネルベースのNVMe/TCPターゲットに焦点を当て、高性能を実現する方法を示します。SPDKの場合も原則は同様ですが、ターゲットの設定はより複雑になります。
前提条件
- Kubernetesクラスター (v1.20+)
- すべてのノードにLinuxカーネル 5.0+ (堅牢なNVMe/TCPサポートのため)
- ターゲットノードとイニシエーターノードに
nvme-cliがインストールされていること - イニシエーターノードに
multipath-toolsがインストールされていること
1. NVMe/TCPターゲットのセットアップ (例: 専用ストレージサーバー)
NVMe SSD (/dev/nvme0n1) を備えたストレージサーバーを想定します。LVM論理ボリュームを作成し、それを公開します。
# On the NVMe/TCP Target Server
# 1. Ensure nvmet-tcp module is loaded
sudo modprobe nvmet-tcp
# 2. Create a Volume Group and Logical Volume (example: 100GB)
# Replace /dev/nvme0n1 with your actual NVMe device
sudo pvcreate /dev/nvme0n1
sudo vgcreate nvme_vg /dev/nvme0n1
sudo lvcreate -L 100G -n nvme_lv nvme_vg
# 3. Configure NVMe/TCP Target
# Create a subsystem (nqn.2023-10.com.locionic:k8s-storage)
# This NQN (NVMe Qualified Name) uniquely identifies the storage subsystem.
sudo nvme target create -t tcp -n nqn.2023-10.com.locionic:k8s-storage
# 4. Add a controller (listener) for the target
# Replace 192.168.1.100 with your target server's IP address
sudo nvme target add-listener -t tcp -n nqn.2023-10.com.locionic:k8s-storage -a 192.168.1.100 -s 4420
# 5. Create a namespace and attach the logical volume
# The namespace ID (nsid) must be unique within the subsystem.
sudo nvme target add-namespace -t tcp -n nqn.2023-10.com.locionic:k8s-storage -d /dev/nvme_vg/nvme_lv -s 1
# 6. Enable the subsystem
sudo nvme target enable -t tcp -n nqn.2023-10.com.locionic:k8s-storage
# Verify target configuration
sudo nvme target show
2. Kubernetesイニシエーターのセットアップ (ワーカーノード)
NVMe/TCPストレージを使用する各Kubernetesワーカーノードには、イニシエーターモジュールとmultipath-toolsが必要です。
# On each Kubernetes Worker Node
# 1. Ensure nvme-tcp module is loaded
sudo modprobe nvme-tcp
# 2. Install multipath-tools
sudo apt update && sudo apt install -y multipath-tools # Debian/Ubuntu
# OR
sudo yum install -y device-mapper-multipath # CentOS/RHEL
# 3. Configure multipath (optional but highly recommended for HA)
# Edit /etc/multipath.conf
sudo tee /etc/multipath.conf <<EOF
defaults {
user_friendly_names yes
find_multipaths yes
# Increase queue depth for better performance with NVMe
queue_without_daemon no
max_sectors_kb 512
}
blacklist {
devnode "^(ram|loop|fd|md|dm-|sr|scd|st)[0-9]*"
devnode "^hd[a-z]"
devnode "^sd[a-z]"
}
EOF
# 4. Restart multipathd
sudo systemctl enable multipathd
sudo systemctl restart multipathd
# 5. Connect to the NVMe/TCP target (manual test)
# Replace 192.168.1.100 with your target server's IP
# Replace nqn.2023-10.com.locionic:k8s-storage with your target NQN
sudo nvme connect -t tcp -n nqn.2023-10.com.locionic:k8s-storage -a 192.168.1.100 -s 4420
# 6. Verify connection and device
# You should see a new /dev/nvmeXnY device
lsblk
sudo nvme list
3. Kubernetes CSIドライバーのデプロイ
動的なプロビジョニングとシームレスな統合には、Container Storage Interface (CSI) ドライバーが不可欠です。汎用NVMe/TCP CSIドライバーが存在する可能性もありますが、多くの場合、ベンダー固有のドライバーを使用するか、汎用ブロックストレージCSIドライバーをNVMe/TCP接続を管理するように適応させます。ここでは、接続管理のためにnvme-cliを活用する概念的なCSIドライバーの概要を説明します。
本番環境レベルのCSIドライバーは以下を処理します。
- ターゲット上のNVMeネームスペースの動的プロビジョニング。
- ワーカーノードでのNVMe/TCPデバイスの接続/切断。
- マルチパス構成。
- ボリュームの公開と非公開。
例: NVMe/TCPの概念的なCSIドライバー
これは簡略化された表現です。実際のCSIドライバーは、gRPCサービス、コントローラー、ノードプラグインを含む、より複雑なものになります。
# csi-nvmetcp-driver.yaml
apiVersion: storage.k8s.io/v1
kind: CSIDriver
metadata:
name: nvmetcp.locionic.com
spec:
attachRequired: false # NVMe/TCP devices are directly attached
podInfoOnMount: false
volumeLifecycleModes:
- Persistent
- Ephemeral
fsGroupPolicy: File
# storageclass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nvmetcp-fast
provisioner: nvmetcp.locionic.com # Must match the CSIDriver name
parameters:
targetIp: "192.168.1.100" # IP of your NVMe/TCP target
targetNqn: "nqn.2023-10.com.locionic:k8s-storage"
targetPort: "4420"
# Other parameters for dynamic provisioning (e.g., size, LVM details)
reclaimPolicy: Delete
volumeBindingMode: Immediate
allowVolumeExpansion: true
# pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-nvmetcp-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: nvmetcp-fast
resources:
requests:
storage: 10Gi
# pod-with-pvc.yaml
apiVersion: v1
kind: Pod
metadata:
name: fio-test-pod
spec:
containers:
- name: fio
image: ghcr.io/locionic/fio:latest # A custom FIO image with nvme-cli
command: ["/bin/bash", "-c", "sleep infinity"]
volumeMounts:
- name: nvmetcp-volume
mountPath: /data
volumes:
- name: nvmetcp-volume
persistentVolumeClaim:
claimName: my-nvmetcp-pvc
4. マルチパスI/O構成
高可用性と帯域幅の増加のために、NVMe/TCPターゲットへの複数のパスを構成します。これには、ターゲットが複数のIPアドレスまたはネットワークインターフェースを介して同じネームスペースを公開する必要があります。
ターゲット側 (2つのIPアドレスの例):
# On NVMe/TCP Target Server
# Assuming 192.168.1.100 and 192.168.1.101 are target IPs
sudo nvme target add-listener -t tcp -n nqn.2023-10.com.locionic:k8s-storage -a 192.168.1.100 -s 4420
sudo nvme target add-listener -t tcp -n nqn.2023-10.com.locionic:k8s-storage -a 192.168.1.101 -s 4420
イニシエーター側 (Kubernetesワーカーノード):
nvme connectコマンドは、ターゲットNQNとポートが同じでIPが異なる場合、複数のパスを自動的に検出します。その後、multipathdがこれらのパスを管理します。
# On Kubernetes Worker Node
# Connect to both target IPs
sudo nvme connect -t tcp -n nqn.2023-10.com.locionic:k8s-storage -a 192.168.1.100 -s 4420
sudo nvme connect -t tcp -n nqn.2023-10.com.locionic:k8s-storage -a 192.168.1.101 -s 4420
# Verify multipath device
sudo multipath -ll
# Expected output will show a single device with multiple paths
# e.g., mpatha (360014050000000000000000000000000) dm-0 NVME,Linux
# size=100G features='0' hwhandler='0' wp=rw
# |- 0:0:0:0 nvme0n1 8:0 active ready running
# `- 1:0:0:0 nvme1n1 8:16 active ready running
CSIドライバーはこれを抽象化し、指定されたすべてのターゲットIPに接続します。
パフォーマンスベンチマーク (50万+ランダム書き込みIOPS)
50万+のランダム書き込みIOPSと100µs未満のレイテンシーを達成するには、いくつかの要因が重要です。
- 高速NVMe SSD: ターゲットの基盤となるストレージが十分な性能を持っている必要があります。
- 高帯域幅ネットワーク: 25GbEまたは100GbEが推奨されます。
- CPUリソース: SPDK用の専用コア、またはカーネルベースのI/O用の十分なコア。
- 最適なFIOパラメーター:
iodepth: デバイスを飽和させるための高いキュー深度。numjobs: 複数の並列ジョブ。direct=1: OSキャッシュをバイパス。ioengine=libaioまたはioengine=io_uring(モダンカーネルの場合)。randwrite: ランダム書き込み。bs=4k: 標準ブロックサイズ。
FIOテスト例 (fio-test-pod内):
# On the fio-test-pod
# Ensure /data is mounted from the NVMe/TCP PVC
cd /data
# Create a test file
dd if=/dev/zero of=testfile bs=1M count=1024 status=progress
# FIO command for 4K random writes
fio --name=randwrite_test \
--ioengine=libaio \
--iodepth=128 \
--rw=randwrite \
--bs=4k \
--direct=1 \
--numjobs=4 \
--size=10G \
--runtime=60 \
--filename=testfile \
--group_reporting \
--output-format=json
予想される出力 (50万+ IOPS、100µs未満のレイテンシーの抜粋):
{
"jobs": [
{
"jobname": "randwrite_test",
"write": {
"io_bytes": 10737418240,
"bw": 178957,
"iops": 44739,
"lat_ns": {
"min": 20000,
"max": 150000,
"mean": 75000,
"stddev": 15000
}
}
},
{
"jobname": "randwrite_test",
"write": {
"io_bytes": 10737418240,
"bw": 178957,
"iops": 44739,
"lat_ns": {
"min": 20000,
"max": 150000,
"mean": 75000,
"stddev": 15000
}
}
},
{
"jobname": "randwrite_test",
"write": {
"io_bytes": 10737418240,
"bw": 178957,
"iops": 44739,
"lat_ns": {
"min": 20000,
"max": 150000,
"mean": 75000,
"stddev": 15000
}
}
},
{
"jobname": "randwrite_test",
"write": {
"io_bytes": 10737418240,
"bw": 178957,
"iops": 44739,
"lat_ns": {
"min": 20000,
"max": 150000,
"mean": 75000,
"stddev": 15000
}
}
}
],
"global_data": {
"write": {
"io_bytes": 42949672960,
"bw": 715828,
"iops": 178957,
"lat_ns": {
"min": 20000,
"max": 150000,
"mean": 75000,
"stddev": 15000
}
}
}
}
注: 上記のFIO出力例では、4つのジョブで約179k IOPSを示しています。50万+を達成するには、numjobs、iodepthをスケールアップし、場合によっては異なるポッド/ノード間で複数のFIOインスタンスを実行し、基盤となるターゲットとネットワークが負荷を維持できることを確認する必要があります。
本番環境での落とし穴とトラブルシューティング
-
カーネルモジュールがロードされていない:
- 症状:
nvme connectが「No such device」で失敗するか、lsmod | grep nvme-tcpに何も表示されない。 - 修正:
sudo modprobe nvme-tcpとsudo modprobe nvmet-tcp(ターゲット上)。これらを/etc/modules-load.d/nvme.confに追加して、再起動後も永続化されるようにします。
- 症状:
-
ネットワーク接続の問題:
- 症状:
nvme connectがタイムアウトするか、接続確立に失敗する。 - 修正: IPアドレス、サブネットマスク、ゲートウェイを確認します。ターゲットとイニシエーターの両方でファイアウォール (
firewalld,ufw,iptables) を確認します。ポート4420が開いていることを確認します。診断にはping,traceroute,netcatを使用します。
- 症状:
-
NQNの不一致:
- 症状:
nvme connectが「Invalid NQN」などで失敗する。 - 修正:
nvme connectで使用されているNQNが、ターゲットで設定されたもの (nvme target create -n ...) と正確に一致していることを再確認します。NQNは大文字と小文字を区別します。
- 症状:
-
マルチパス構成エラー:
- 症状: 1つのパスのみがアクティブであるか、
multipath -llが単一のマルチパスデバイスではなく個別のデバイスを表示する。 - 修正:
multipathdが実行されていることを確認します。/etc/multipath.confが正しく構成されていること、特にuser_friendly_namesとfind_multipathsを確認します。すべてのターゲットIPが同じNQNで接続されていることを確認します。変更後、multipathdを再起動します。
- 症状: 1つのパスのみがアクティブであるか、
-
パフォーマンスのボトルネック:
- 症状: IOPSまたはレイテンシーの目標が達成されない。
- 修正:
- ターゲット: CPU使用率 (
top,htop)、ディスクI/O (iostat -x 1)、ネットワークI/O (sar -n DEV 1) を確認します。基盤となるNVMe SSDは飽和していますか? - ネットワーク: ネットワークリンク使用率 (
iftop,nload) を確認します。パケットロスはありますか?スイッチポートは正しく設定されていますか (例: フロー制御、ジャンボフレーム)? - イニシエーター: CPU使用率を確認します。FIOの
iodepthとnumjobsが十分に高いことを確認します。新しいカーネルの場合はio_uringを検討します。SPDKの場合、CPUコアの分離とヒュージページが設定されていることを確認します。 - カーネルTCPスタックチューニング: カーネルNVMe/TCPの場合、
net.core.somaxconn,net.ipv4.tcp_tw_reuse,net.ipv4.tcp_max_syn_backlog,net.ipv4.tcp_fin_timeoutのチューニングを検討します。
- ターゲット: CPU使用率 (
-
CSIドライバーの問題:
- 症状: PVCが保留状態のままで、ポッドがボリュームをマウントできない。
- 修正: CSIドライバーのコントローラーとノードプラグインのログ (
kubectl logs -n <csi-namespace> <pod-name>) を確認します。CSIDriverとStorageClassの定義を確認します。CSIドライバーがワーカーノードでnvme-cliコマンドを実行するために必要な権限 (例: 特権コンテナまたはバイナリ用のhostPathマウントを介して) があることを確認します。
よくある質問
-
NVMe/TCPがiSCSIよりも優れている主な利点は何ですか? NVMe/TCPは、最新のSSD向けに設計された、非常に効率的で並列なNVMeコマンドセットをTCP/IP上で直接活用します。iSCSIは古いSCSIコマンドセットを使用しており、より多くのオーバーヘッドを発生させ、NVMeデバイスの並列処理には最適化されていません。これにより、NVMe/TCPは大幅に高いIOPSと低いレイテンシーを実現します。
-
カーネルNVMe/TCPよりもSPDKを検討すべきなのはどのような場合ですか? SPDKは、レイテンシーの1マイクロ秒、IOPSの1つ1つが重要となる、極限のパフォーマンス要件に最適です。カーネルのTCP/IPスタックとブロックレイヤーをバイパスし、ユーザー空間ポーリングを使用することで、コンテキストスイッチと割り込みオーバーヘッドを排除します。これは、複雑さの増加、専用CPUコアの割り当て、および潜在的に高いメモリ消費を伴います。ほとんどの高性能アプリケーションでは、適切なチューニングを施したカーネルNVMe/TCPで十分です。
-
NVMe/TCPはマルチテナントKubernetes環境に適していますか? はい、慎重なリソース管理を行えば適しています。各NVMe/TCP接続はワーカーノードでリソースを消費します。適切に設計されたCSIドライバーはこれらの接続を管理できます。マルチテナンシーの場合、分離とクォータを適用するために、異なるテナントに別々のNVMeサブシステムまたはネームスペースを使用することを検討してください。ネットワークセグメンテーション (VLAN、別々のNIC) は、セキュリティとパフォーマンスの分離をさらに強化できます。
-
NVMe/TCPはネットワーク障害をどのように処理しますか? NVMe/TCPは、他のTCPベースのプロトコルと同様に、TCPの信頼性メカニズムに依存しています。高可用性のためには、マルチパスが不可欠です。複数のネットワークパス (例: 複数のNIC、複数のターゲットIP) を構成することで、イニシエーター上の
multipathdは、いずれかのパスが失敗した場合に自動的に代替パスにフェイルオーバーし、ストレージへの継続的なアクセスを保証します。NVMe-oF仕様には、コントローラーレベルのフェイルオーバーメカニズムも含まれています。 -
NVMe/TCPのセキュリティに関する考慮事項は何ですか? NVMe/TCPは、デフォルトでは強力な認証や暗号化を含んでいません。本番環境でのデプロイには、以下の点が重要です。
- ネットワーク分離: NVMe/TCPトラフィックを専用の隔離されたネットワークセグメント (VLAN) に配置します。
- IPホワイトリスト: ターゲットファイアウォールを設定し、許可されたイニシエーターIPからの接続のみを受け入れるようにします。
- TLS/DTLS: NVMe-oF仕様は、暗号化と認証のためにTLS/DTLSをサポートしていますが、実装は様々です。転送中のデータ暗号化が必要な場合は、NVMe/TCPターゲットとイニシエーターがTLSをサポートし、設定されていることを確認してください。
- ホストレベル認証: 一部の実装では、SASLなどのホストレベル認証メカニズムをサポートしている場合があります。
Free In-Browser Developer Tools
Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.
Related Articles

KubernetesにおけるLinuxcgroupsv2:PSI、メモリ制限、OOMシールド
KubernetesにおけるLinuxcgroupsv2のPSI(PressureStallInformation)、メモリ制限、OOMシールドについて、本番環境レベルのアーキテクチャとコード例を交えて解説する包括的なガイド。
Read more
LLM推論における連続動的バッチ処理:Orca、vLLM、TGIのレイテンシベンチマーク
LLM推論における連続動的バッチ処理について、Orca、vLLM、TGIのレイテンシベンチマークを、本番環境レベルのアーキテクチャとコード例で網羅的に解説する総合ガイドです。
Read more
2026年におけるクラウドネイティブセキュリティの進化
2026年のクラウドネイティブセキュリティは、TetragonによるeBPF runtime defense、自動化されたSLSAコンプライアンス、ゼロトラストのサービスメッシュmTLS、SPIFFE/SPIREワークロードID、in-toto attestationによるサプライチェーンの完全性で進化します。
Read more