上一篇 下一篇 分享链接 返回 返回顶部

如何在香港服务器运行 Debian 时利用 NVMe-oF 构建分布式高速存储

发布人:Minchunlin 发布时间:2025-09-04 16:55 阅读量:752


那天在香港葵涌机房,夜里 2:10,我抱着一袋螺丝和两根 100G DAC 线爬到第 7 柜的最上层,手机里同事的消息还在跳:“业务高峰,写入抖 99p 到 12ms,DB 缓存顶不住了。”——我们必须把 NVMe-oF 的分布式盘阵在这晚落地,不然第二天的广告投放必崩。

这篇文章,就是我那晚边干边记的完整流程与踩坑记录。希望你看完,哪怕第一次做,也能照着把它搭起来;老手也能从调优表和“坑位清单”里拿到点新东西。

目标:在香港机房的多台 Debian 服务器上,用 NVMe-oF(NVMe over Fabrics) 搭一套高吞吐、低延迟、可多路径容灾的分布式存储池,主要服务于低延迟数据库(MySQL/ClickHouse)和日志分析(Fluentd/Vector)。

为什么 NVMe-oF(选 TCP 为主,RDMA 为可选)

机房是托管/租用混合环境,跨机柜 RoCE 的网络前提(PFC/ECN/无损网)不一定稳妥;NVMe-TCP 走 L3,改造成本低、可快速上线。

同机柜内、专用网可控,再考虑 RDMA 版本(RoCEv2),作为性能上限或后续演进。

架构总览

角色

  • Target(存储节点):提供 NVMe 盘并通过 NVMe-oF 导出子系统(subsystem)。
  • Initiator(计算/业务节点):通过 NVMe-oF 连接远端命名空间(namespace),在本地聚合成卷给 DB/服务使用。

网络

  • 业务网(10/25/40G,MTU 1500)
  • 存储专网(100G,MTU 9000),单独 VLAN,走 L3;NVMe-TCP 端口默认 4420。

多路径

  • Initiator 侧开启 NVMe Multipath(ANA),两个 Target 提供同一命名空间的两条访问路径,链路/节点故障自动切换。

拓扑(文本图)

[Initiator-01/02/03] --(VLAN 200/MTU 9000, 100G)--> [ToR Storage Switch] --> [Target-A]
                                                       |                                   \
                                                       +------------------------------------[Target-B]

硬件与软件清单(实配示例)

角色 型号/配置 关键参数
Target-A/B 2U 单路 EPYC 7543P 32C/64T,256GB RAM,U.2 NVMe × 8(7.68TB 级别,PCIe 4.0),双口 100GbE(Mellanox CX5 或 Intel E810)
Initiator-01/02/03 1U 双口 100GbE 24C/48T,128GB RAM,系统盘 SATA/NVMe 各 1
交换机 100GbE ToR 支持 VLAN、Jumbo Frame(MTU 9216),基本 QoS
操作系统 Debian 12 (Bookworm) 内核 6.1 LTS(自带 nvme-cli、nvmet 模块稳定)

注:NVMe 盘我用的是 U.2 企业级(如 PM9A3、P5510 同级别),保证写放大控制、掉电保护和可观的 DWPD。

命名与地址规划

主机名 角色 存储 VLAN(VLAN 200) 业务 VLAN
target-a Target 10.200.0.11/24 10.10.0.11/24
target-b Target 10.200.0.12/24 10.10.0.12/24
init-01 Initiator 10.200.0.101/24 10.10.0.101/24
init-02 Initiator 10.200.0.102/24 10.10.0.102/24
init-03 Initiator 10.200.0.103/24 10.10.0.103/24

NQN 规范

  • 目标端 subsystem:nqn.2025-08.example:target.nvme.pool0
  • 各 initiator hostnqn:使用系统默认 /etc/nvme/hostnqn(保持唯一),并在 target 的 allowed_hosts 中白名单。

系统准备与内核/网络调优(Debian 12)

1) 基础包

apt update && apt -y install nvme-cli fio smartmontools ethtool jq \
  sysfsutils numactl irqbalance bc

2) BIOS/固件要点(经验)

  • NVMe 驱动器:确认固件为数据中心版,启用电源保护;PCIe 4.0 通道完整。
  • CPU 电源策略:禁用深度 C-State(或在 OS 侧设 performance),减少尾延迟。
  • IOMMU:启用(方便后续隔离/直通可能性)。

3) 网络(存储 VLAN 开启 MTU 9000)

# 假设存储口为 ens5f0
ip link set dev ens5f0 mtu 9000
ip addr add 10.200.0.11/24 dev ens5f0   # target-a 示例
ip link set dev ens5f0 up

TCP 栈与队列调优(通用稳妥值,按需微调):

cat >/etc/sysctl.d/99-nvmetcp.conf <<'EOF'
net.core.rmem_max=134217728
net.core.wmem_max=134217728
net.ipv4.tcp_rmem=4096 87380 134217728
net.ipv4.tcp_wmem=4096 65536 134217728
net.core.default_qdisc=fq
net.ipv4.tcp_congestion_control=bbr
net.core.netdev_max_backlog=250000
EOF
sysctl --system

网卡队列与 ring buffer(示例):

ethtool -G ens5f0 rx 4096 tx 4096
ethtool -L ens5f0 combined 32
# 关/开大包聚合按业务定:存储面通常保留 GRO 打开,LRO 由驱动控制
ethtool -K ens5f0 gso on gro on tso on

Target 端:NVMe-TCP 内核 Target(nvmet)配置

确保模块:

modprobe nvmet
modprobe nvmet-tcp

通过 configfs 建立子系统/命名空间/端口

# 1) 准备 configfs
mount -t configfs none /sys/kernel/config

# 2) 创建 subsystem
SUBSYS=nqn.2025-08.example:target.nvme.pool0
mkdir -p /sys/kernel/config/nvmet/subsystems/$SUBSYS
echo 1 > /sys/kernel/config/nvmet/subsystems/$SUBSYS/attr_allow_any_host  # 先开,后面再锁白名单更安全

# 3) 绑定本地 NVMe 盘作为命名空间(示例:/dev/nvme0n1 和 nvme1n1)
#   注意: nvmet 需要 "device_path" 指向块设备
for i in 1 2; do
  mkdir -p /sys/kernel/config/nvmet/subsystems/$SUBSYS/namespaces/$i
  echo -n "/dev/nvme$((i-1))n1" > /sys/kernel/config/nvmet/subsystems/$SUBSYS/namespaces/$i/device_path
  echo 1 > /sys/kernel/config/nvmet/subsystems/$SUBSYS/namespaces/$i/enable
done

# 4) 创建监听端口(NVMe-TCP)
PORT=1
mkdir -p /sys/kernel/config/nvmet/ports/$PORT
echo 10.200.0.11 > /sys/kernel/config/nvmet/ports/$PORT/addr_traddr
echo tcp        > /sys/kernel/config/nvmet/ports/$PORT/addr_trtype
echo 4420       > /sys/kernel/config/nvmet/ports/$PORT/addr_trsvcid
echo ipv4       > /sys/kernel/config/nvmet/ports/$PORT/addr_adrfam

# 5) 绑定端口到子系统
ln -s /sys/kernel/config/nvmet/subsystems/$SUBSYS /sys/kernel/config/nvmet/ports/$PORT/subsystems/$SUBSYS

白名单 HostNQN(安全建议)

获取 initiator 的 /etc/nvme/hostnqn,然后关闭 allow_any_host 并加入允许列表:

echo 0 > /sys/kernel/config/nvmet/subsystems/$SUBSYS/attr_allow_any_host
echo -n "nqn.2014-08.org.nvmexpress:uuid:xxxx-...." > \
  /sys/kernel/config/nvmet/subsystems/$SUBSYS/allowed_hosts/hostnqn-init01
# 为每个 initiator 都加一条

可选:新内核可评估 NVMe-TCP 的 in-band 认证;生产上我仍建议网段隔离 + NQN 白名单 + ACL为主。

开机自启(systemd)

把上面的步骤封装到脚本 /usr/local/sbin/nvmet-setup.sh,并创建服务:

cat >/usr/local/sbin/nvmet-setup.sh <<'EOS'
#!/usr/bin/env bash
set -euo pipefail
mountpoint -q /sys/kernel/config || mount -t configfs none /sys/kernel/config
modprobe nvmet nvmet-tcp

SUBSYS="nqn.2025-08.example:target.nvme.pool0"
PORT=1
IP="10.200.0.11"

mkdir -p /sys/kernel/config/nvmet/subsystems/$SUBSYS
echo 0 > /sys/kernel/config/nvmet/subsystems/$SUBSYS/attr_allow_any_host

# 命名空间示例,按实际盘修改
for i in 1 2; do
  NSDIR=/sys/kernel/config/nvmet/subsystems/$SUBSYS/namespaces/$i
  mkdir -p $NSDIR
  echo -n "/dev/nvme$((i-1))n1" > $NSDIR/device_path
  echo 1 > $NSDIR/enable
done

# 允许的 initiator(示例)
echo -n "$(cat /etc/nvme/hostnqn.init01)" > /sys/kernel/config/nvmet/subsystems/$SUBSYS/allowed_hosts/init01
echo -n "$(cat /etc/nvme/hostnqn.init02)" > /sys/kernel/config/nvmet/subsystems/$SUBSYS/allowed_hosts/init02

mkdir -p /sys/kernel/config/nvmet/ports/$PORT
echo $IP   > /sys/kernel/config/nvmet/ports/$PORT/addr_traddr
echo tcp   > /sys/kernel/config/nvmet/ports/$PORT/addr_trtype
echo 4420  > /sys/kernel/config/nvmet/ports/$PORT/addr_trsvcid
echo ipv4  > /sys/kernel/config/nvmet/ports/$PORT/addr_adrfam

ln -snf /sys/kernel/config/nvmet/subsystems/$SUBSYS /sys/kernel/config/nvmet/ports/$PORT/subsystems/$SUBSYS
EOS
chmod +x /usr/local/sbin/nvmet-setup.sh

cat >/etc/systemd/system/nvmet-setup.service <<'EOF'
[Unit]
Description=NVMe-oF Target setup (TCP)
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/nvmet-setup.sh
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable --now nvmet-setup.service

target-b 做同样配置,IP 改为 10.200.0.12,命名空间和 allowed_hosts 保持一致(同一 pool 的镜像/副本语义由上层阵列实现,见下)。

Initiator 端:连接 NVMe-TCP、开启多路径

1) 开内核多路径

# 临时:
echo Y > /sys/module/nvme_core/parameters/multipath
# 永久(GRUB):
sed -i 's/GRUB_CMDLINE_LINUX="/GRUB_CMDLINE_LINUX="nvme_core.multipath=y /' /etc/default/grub
update-grub

2) 自动发现与连接

写 discovery.conf,由 nvme-discovery 服务托管自动连接:

cat >/etc/nvme/discovery.conf <<'EOF'
# target-a
transport=tcp,traddr=10.200.0.11,trsvcid=4420,hostnqn=$(cat /etc/nvme/hostnqn)
# target-b
transport=tcp,traddr=10.200.0.12,trsvcid=4420,hostnqn=$(cat /etc/nvme/hostnqn)
EOF

systemctl enable --now nvme-stas-discoveryd.service || true  # 新版可能叫 nvme-stas*
systemctl enable --now nvme-discovery.service || true        # 旧版 nvme-cli 自带

如果你的发行版用 STAS(Service for NVMe-oF Autoconnect Scripts),优先使用它(nvme-stas 包),自动重连更稳。

也可以手动:

nvme discover -t tcp -a 10.200.0.11 -s 4420
nvme discover -t tcp -a 10.200.0.12 -s 4420
nvme connect-all -t tcp -a 10.200.0.11 -s 4420
nvme connect-all -t tcp -a 10.200.0.12 -s 4420

3) 验证拓扑与 ANA

nvme list
nvme list-subsys
nvme netapp-show-ana || true   # 不同版本命令名略有差异

应能看到同一 NQN 的 两条路径(到 target-a/b),状态为 optimized/ready。

上层卷管理与文件系统

两种常见做法:

1.单命名空间×多路径 → 直接给 DB 用(XFS/ext4)

  • 简单、低延迟。

2.跨 Target 聚合(软件 RAID/LVM/Btrfs/ZFS)

  • 用 mdadm 做 RAID10/0,或 ZFS/Btrfs;适合更大带宽/容量需求。

我这边给 ClickHouse 做 RAID10(跨 target-a/b 的 4 个 namespace),给 MySQL 做 单盘多路径。

示例:RAID10(mdadm)

# 假设发现的设备为 /dev/nvme0n1, /dev/nvme1n1, /dev/nvme2n1, /dev/nvme3n1
mdadm --create /dev/md/nvme_raid10 --level=10 --raid-devices=4 \
  /dev/nvme0n1 /dev/nvme1n1 /dev/nvme2n1 /dev/nvme3n1

mkfs.xfs -f /dev/md/nvme_raid10
mkdir -p /data/ch
echo '/dev/md/nvme_raid10 /data/ch xfs defaults,noatime,nodiratime 0 0' >> /etc/fstab
mount -a

提示:mdadm 上跨机节点的盘,一定配多路径;断一条链路不应掉阵列。

性能基线与调优(示例数据)

以下数据为我当晚在机房的一组典型跑分,供参考;你的结果会因盘型、CPU、NIC、线缆与交换机而有所差异。

fio 配置(4K 随机读/写;128K 顺序读)
cat >fio-nvmetcp.ini <<'EOF'
[global]
ioengine=libaio
direct=1
time_based=1
runtime=60
group_reporting=1
iodepth=64
numjobs=4
bs=4k

[randread]
filename=/data/ch/fiofile
rw=randread

[randwrite]
filename=/data/ch/fiofile
rw=randwrite

[seqread]
filename=/data/ch/fiofile
rw=read
bs=128k
iodepth=256
EOF
场景 4K 随机读 (IOPS / 平均延迟) 4K 随机写 (IOPS / 平均延迟) 128K 顺序读 (GB/s)
单 Target、单路径、MTU1500 ~520k / 0.40ms ~180k / 1.2ms ~5.8
双 Target、多路径、MTU9000 ~980k / 0.36ms ~320k / 1.0ms ~10.3
双 Target、多路径、MTU9000 + CPU 固频(perf) ~1.05M / 0.34ms ~345k / 0.95ms ~11.0

观察:

  • 大包(MTU 9000)+ 多路径能明显提升顺序带宽与随机读 IOPS;
  • 写性能受后端盘写放大与缓存策略影响更大;
  • 固定性能 governor(cpupower frequency-set -g performance)可减尾延。

可靠性演练(你必须做)

拔一条 DAC/光纤 / 关闭 target-a 的存储口:

  • nvme list-subsys 应看到路径降级,但 namespace 不掉线;I/O 短暂毛刺后恢复。

重启 target-a:

  • nvme-stas/nvme-discovery 应自动重连;dmesg 无大量超时。

模拟磁盘故障:

  • mdadm 阵列降级、报警、恢复流程。

监控与日常巡检

  1. NVMe SMART:nvme smart-log /dev/nvmeXnY(温度、介质错误计数、可用寿命)
  2. I/O:iostat -x 1,nvme top(有些系统包提供)
  3. 节点监控:Node Exporter + Prometheus,采集 nvme_* 指标、网卡队列丢包、TCP 重传
  4. 链路:交换机侧接口错误/丢包、PFC(如用 RDMA)

可选:同机柜 RDMA(RoCEv2)部署要点

只在交换机与链路完全可控时建议上 RDMA(无损以太),否则优先 NVMe-TCP。

  1. Target 端:modprobe nvmet-rdma,addr_trtype=rdma,端口默认 4420。
  2. 交换机:启用 PFC(仅对存储 VLAN 打开),配置 ECN;保证端到端 MTU 一致。
  3. 主机:ethtool -K <iface> rx-fcs on(因 RoCEv2 依赖 FCS),设置 GID 路由。
  4. 性能与尾延可进一步下降(4K 读延迟常见 200~300 µs),但落地复杂度高、维护成本更大。

安全与隔离实践

  • 存储 VLAN 与业务 VLAN 分离;Target 仅在存储 VLAN 监听。
  • NQN 白名单 + 主机侧 hostid/hostnqn 稳定化。
  • 需要跨域/不可信网络时,用 WireGuard/IPsec 叠加加密隧道,再跑 NVMe-TCP。
  • 控制面(SSH、Prometheus)的访问列表与审计。

常见坑与现场解决手记

MTU 不一致:ToR 9216、主机 9000、对端 1500 → 巨帧被分片或直接丢。那晚 iperf3 只有 6~7Gb/s,一查是某台 NIC 的 VLAN 子接口忘了改 MTU。

排查:ip -d link show dev <iface>、交换机接口 show int。

NQN 不匹配:initiator 连接不上,日志报 no matching host。

解决:把 initiator 的 /etc/nvme/hostnqn 精确加入 Target 的 allowed_hosts。

路径只有一条是 optimized:ANA 未正确报告,或 Target 子系统/命名空间不一致。

解决:确保两个 Target 导出的 同一 NQN/namespace id,并正确绑定。

LBA 格式/对齐问题:不同盘的 LBA size/namespace size 不一致导致 mdadm 警告。

解决:选择同型号/容量的 NVMe;或在阵列层对齐分区。

CPU 降频导致尾延高:省电策略把频率压太低。

解决:cpupower frequency-set -g performance;关键进程绑核(taskset/systemd CPUAffinity)。

中断拥挤:100G 下 irqbalance 未生效,单核飙满。

解决:ethtool -L 提升队列、set_irq_affinity 脚本分散到 NUMA 对应核。

混线:一根 DAC 物理损坏;我当场换线,误码与丢包立刻归零。永远备两根好的 DAC/光纤。

自动化样例(Ansible 片段)

- hosts: targets
  become: yes
  tasks:
    - name: Install pkgs
      apt: { name: [nvme-cli, fio, jq], state: present, update_cache: yes }
    - name: Enable nvmet modules
      copy:
        dest: /etc/modules-load.d/nvmet.conf
        content: |
          nvmet
          nvmet-tcp
    - name: Put nvmet setup script
      copy:
        src: files/nvmet-setup.sh
        dest: /usr/local/sbin/nvmet-setup.sh
        mode: '0755'
    - name: Systemd unit
      copy:
        src: files/nvmet-setup.service
        dest: /etc/systemd/system/nvmet-setup.service
    - name: Enable service
      systemd: { name: nvmet-setup.service, enabled: yes, state: started }

容量与成本(示例测算)

项目 数量 单价(参考) 小计
7.68TB U.2 NVMe(企业级) 16 ¥(按你实际采购 ¥…
100GbE NIC(双口) 4 ¥… ¥…
100G DAC 3m 4 ¥… ¥…
2U/1U 服务器 5 ¥… ¥…
合计(不含机柜/电费)     ¥…

容量:可用(RAID10 上层)≈ 30.7TB,单路径降级仍可服务。

凌晨 4:20,我们做完最后一次 failover 演练,监控的 99.9p 抖动从 11ms 掉到 2.7ms。ClickHouse 的合并再也不是“拉扯 CPU 的怪兽”,MySQL 的 binlog 延迟也回到了 200ms 以内。
我把带着汗渍的手套塞进口袋,走到机房外的走廊,香港的天边开始泛灰。那晚之后,这套 NVMe-oF 存储池就像一条安静的高速公路,业务车流越来越大,它却稳稳地扛住了。

如果你也准备在香港的机房里开一条这样的“高速公路”,就照着这篇做吧。先用 NVMe-TCP 打底、多路径保稳,跑顺之后再考虑 RDMA。
最重要的是:把每一个“可能的坑”当成一定会发生的事,在上线之前把它们逐一演练过。这样,等你从机房走出来的时候,天亮了,心也稳了。

附录:快速检查清单(上线前)

  •  存储 VLAN 全链路 MTU 9000、一致
  •  nvme list-subsys 显示双路径、ANA 正常
  •  /etc/nvme/discovery.conf 或 STAS 自动重连生效
  •  nvme smart-log 温度/可用寿命正常
  •  CPU governor performance,网卡队列与中断亲和 OK
  •  断链/重启 target 演练通过
  •  监控告警阈值设置妥当(IO 等待、超时、重传、掉包)

有问题也可以把你的现网参数(网卡/交换机/盘型/工作负载)丢给我,我会按你的现场再给一版更贴身的调优建议。

目录结构
全文