•20 min read

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

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

NVMe-over-TCP (NVMe/TCP)は、標準的なイーサネット上で堅牢かつ高性能なブロックストレージ転送を提供し、NVMeコマンドセットの効率性を活用します。このガイドでは、そのアーキテクチャ、デプロイに関する考慮事項、および高IOPS、低レイテンシーのストレージを実現するためのKubernetesへの統合について詳しく説明します。カーネル空間とユーザー空間(SPDK)の実装におけるパフォーマンスへの影響を分析し、要求の厳しいパフォーマンス目標を達成する方法を示します。

Audio Briefing
0:00 / 0:00

NVMe/TCPアーキテクチャの概要

NVMe/TCPは、NVMeコマンドとデータをTCP/IPパケット内にカプセル化します。これにより、標準的なネットワークインフラストラクチャで高性能ストレージトラフィックを伝送でき、特殊なファイバーチャネルやInfiniBandハードウェアは不要になります。

主要コンポーネント

  1. NVMeホスト: NVMe/TCPストレージを利用するイニシエーターで、通常はLinuxサーバーまたはKubernetesノードです。
  2. NVMeターゲット: TCP経由でNVMeネームスペースをエクスポートするストレージサーバーです。これは、専用のストレージアプライアンス、NVMeドライブを備えたLVM/ZFSを実行する汎用サーバー、またはSPDKベースのソリューションのいずれかです。
  3. 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コアとメモリが必要であり、設定がより複雑になる可能性があります。

Advertisement

アーキテクチャ比較: NVMe/TCP vs. 代替案

機能iSCSINVMe/TCP (カーネル)NVMe/TCP (SPDK)NVMe/RoCE (RDMA)
プロトコルSCSI over TCP/IPNVMe over TCP/IPNVMe over User-space TCP/IPNVMe 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未満のレイテンシーを達成するには、いくつかの要因が重要です。

  1. 高速NVMe SSD: ターゲットの基盤となるストレージが十分な性能を持っている必要があります。
  2. 高帯域幅ネットワーク: 25GbEまたは100GbEが推奨されます。
  3. CPUリソース: SPDK用の専用コア、またはカーネルベースのI/O用の十分なコア。
  4. 最適な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インスタンスを実行し、基盤となるターゲットとネットワークが負荷を維持できることを確認する必要があります。

Advertisement

本番環境での落とし穴とトラブルシューティング

  1. カーネルモジュールがロードされていない:

    • 症状: nvme connectが「No such device」で失敗するか、lsmod | grep nvme-tcpに何も表示されない。
    • 修正: sudo modprobe nvme-tcpとsudo modprobe nvmet-tcp (ターゲット上)。これらを/etc/modules-load.d/nvme.confに追加して、再起動後も永続化されるようにします。
  2. ネットワーク接続の問題:

    • 症状: nvme connectがタイムアウトするか、接続確立に失敗する。
    • 修正: IPアドレス、サブネットマスク、ゲートウェイを確認します。ターゲットとイニシエーターの両方でファイアウォール (firewalld, ufw, iptables) を確認します。ポート4420が開いていることを確認します。診断にはping, traceroute, netcatを使用します。
  3. NQNの不一致:

    • 症状: nvme connectが「Invalid NQN」などで失敗する。
    • 修正: nvme connectで使用されているNQNが、ターゲットで設定されたもの (nvme target create -n ...) と正確に一致していることを再確認します。NQNは大文字と小文字を区別します。
  4. マルチパス構成エラー:

    • 症状: 1つのパスのみがアクティブであるか、multipath -llが単一のマルチパスデバイスではなく個別のデバイスを表示する。
    • 修正: multipathdが実行されていることを確認します。/etc/multipath.confが正しく構成されていること、特にuser_friendly_namesとfind_multipathsを確認します。すべてのターゲットIPが同じNQNで接続されていることを確認します。変更後、multipathdを再起動します。
  5. パフォーマンスのボトルネック:

    • 症状: 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のチューニングを検討します。
  6. CSIドライバーの問題:

    • 症状: PVCが保留状態のままで、ポッドがボリュームをマウントできない。
    • 修正: CSIドライバーのコントローラーとノードプラグインのログ (kubectl logs -n <csi-namespace> <pod-name>) を確認します。CSIDriverとStorageClassの定義を確認します。CSIドライバーがワーカーノードでnvme-cliコマンドを実行するために必要な権限 (例: 特権コンテナまたはバイナリ用のhostPathマウントを介して) があることを確認します。

よくある質問

  1. NVMe/TCPがiSCSIよりも優れている主な利点は何ですか? NVMe/TCPは、最新のSSD向けに設計された、非常に効率的で並列なNVMeコマンドセットをTCP/IP上で直接活用します。iSCSIは古いSCSIコマンドセットを使用しており、より多くのオーバーヘッドを発生させ、NVMeデバイスの並列処理には最適化されていません。これにより、NVMe/TCPは大幅に高いIOPSと低いレイテンシーを実現します。

  2. カーネルNVMe/TCPよりもSPDKを検討すべきなのはどのような場合ですか? SPDKは、レイテンシーの1マイクロ秒、IOPSの1つ1つが重要となる、極限のパフォーマンス要件に最適です。カーネルのTCP/IPスタックとブロックレイヤーをバイパスし、ユーザー空間ポーリングを使用することで、コンテキストスイッチと割り込みオーバーヘッドを排除します。これは、複雑さの増加、専用CPUコアの割り当て、および潜在的に高いメモリ消費を伴います。ほとんどの高性能アプリケーションでは、適切なチューニングを施したカーネルNVMe/TCPで十分です。

  3. NVMe/TCPはマルチテナントKubernetes環境に適していますか? はい、慎重なリソース管理を行えば適しています。各NVMe/TCP接続はワーカーノードでリソースを消費します。適切に設計されたCSIドライバーはこれらの接続を管理できます。マルチテナンシーの場合、分離とクォータを適用するために、異なるテナントに別々のNVMeサブシステムまたはネームスペースを使用することを検討してください。ネットワークセグメンテーション (VLAN、別々のNIC) は、セキュリティとパフォーマンスの分離をさらに強化できます。

  4. NVMe/TCPはネットワーク障害をどのように処理しますか? NVMe/TCPは、他のTCPベースのプロトコルと同様に、TCPの信頼性メカニズムに依存しています。高可用性のためには、マルチパスが不可欠です。複数のネットワークパス (例: 複数のNIC、複数のターゲットIP) を構成することで、イニシエーター上のmultipathdは、いずれかのパスが失敗した場合に自動的に代替パスにフェイルオーバーし、ストレージへの継続的なアクセスを保証します。NVMe-oF仕様には、コントローラーレベルのフェイルオーバーメカニズムも含まれています。

  5. NVMe/TCPのセキュリティに関する考慮事項は何ですか? NVMe/TCPは、デフォルトでは強力な認証や暗号化を含んでいません。本番環境でのデプロイには、以下の点が重要です。

    • ネットワーク分離: NVMe/TCPトラフィックを専用の隔離されたネットワークセグメント (VLAN) に配置します。
    • IPホワイトリスト: ターゲットファイアウォールを設定し、許可されたイニシエーターIPからの接続のみを受け入れるようにします。
    • TLS/DTLS: NVMe-oF仕様は、暗号化と認証のためにTLS/DTLSをサポートしていますが、実装は様々です。転送中のデータ暗号化が必要な場合は、NVMe/TCPターゲットとイニシエーターがTLSをサポートし、設定されていることを確認してください。
    • ホストレベル認証: 一部の実装では、SASLなどのホストレベル認証メカニズムをサポートしている場合があります。
Share this article:

Stay Updated

Get the latest posts delivered straight to your inbox.

Free Developer Utilities

Free In-Browser Developer Tools

Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.

Explore Tools
Advertisement
2026年におけるクラウドネイティブセキュリティの進化
security

2026年におけるクラウドネイティブセキュリティの進化

2026年のクラウドネイティブセキュリティは、TetragonによるeBPF runtime defense、自動化されたSLSAコンプライアンス、ゼロトラストのサービスメッシュmTLS、SPIFFE/SPIREワークロードID、in-toto attestationによるサプライチェーンの完全性で進化します。

Read more