
凌晨两点半,香港葵涌机房的空模型推理服务的 P99 启动时延又飙到了180 秒,业务同学在 Slack 里接龙吐槽“早上灰屏 3 分钟”。我站在一排 2U 服务器前,盯着那几块 NVMe 指示灯一闪一闪,心里想:这事儿必须今天夜里搞定。
这篇文章是我当晚和接下来一周的完整实操笔记:在 CentOS 7 环境下,使用 Ceph 搭建一套面向“模型权重高并发读取”的分布式存储,并把模型加载 P99 从 180s 压到 24s。我会把现场的硬件参数、网络拓扑、版本选型、部署步骤、踩坑和优化手法,全部摊开讲,不管你是新手还是老手,都能照着落地。
1. 现场背景与目标
1.1 读写画像与目标值
读多写少:模型权重(几十 GB 到上百 GB),部署/升级时写入,业务启动时并发读取。
大量并发小顺序片段读取 + 若干大顺序读取(PyTorch/Megatron/Transformers 的 mmap 读模式 + 进程并发预取)。
目标:
- 模型实例冷启动 P99 ≤ 30s(单副本权重体量 30–80 GB)。
- 业务峰值并发 50–120 个实例同时读,不打抖。
- 存储成本可控,数据可靠性 >= 3 副本等效。
1.2 机房与硬件(香港、单机房两机架)
| 角色 | 台数 | CPU/RAM | 系统盘 | 数据盘 | 网卡 | 备注 |
|---|---|---|---|---|---|---|
| MON+MGR(mon01–03) | 3 | 2×E5-26xx/128GB | SATA SSD 480G | 无 | 2×10GbE | 管理/监视 |
| MDS(mds01–02) | 2 | 2×E5-26xx/128GB | SATA SSD 480G | 无 | 2×10GbE | CephFS 元数据 |
| OSD(osd01–06) | 6 | 2×E5-26xx/256GB | SATA SSD 480G | 8×12TB HDD + 2×1.92TB NVMe | 2×25GbE | HDD 做 data,NVMe 做 DB/WAL |
| RGW(rgw01–02,可选) | 2 | 同上 | SSD | 无 | 2×10GbE | 备选 S3 接入 |
| 推理节点(srv01–08) | 8 | 2×E5-26xx/384GB | SSD | 2×1.92TB NVMe(本地缓存) | 2×25GbE | 业务侧读取 |
网络:公有网络(client)与集群网络(cluster)分离
公网(client):172.16.10.0/24;集群(cluster):172.16.20.0/24;跨交换机 L2,机架间 RTT 0.15–0.25ms,MTU 9000。
2. 版本与策略选型(CentOS 7 友好)
- 操作系统:CentOS 7.9(内核 3.10,稳定为先)
- Ceph 版本:Nautilus(14.2.x) —— CentOS 7 最稳妥的生产选择
- Octopus 及后续版本更偏向 CentOS 8+/容器化,但在本场景与现网限制下我们锁定 Nautilus。
- 部署方式:ceph-ansible(自动化 + 可回滚)
- Ansible 2.8.x(与 ceph-ansible 4.x 匹配)
- 后端引擎:BlueStore(默认)
- HDD 做 data,NVMe 绑定 DB/WAL(随机读/元数据更快)
- 接口选择:CephFS 提供统一文件视图(模型权重天然是文件/目录),后台:
- metadata pool(3 副本)
- data pool(EC 4+2 或 8+3,视容量与带宽权衡)
3. 系统层准备(每台 Ceph 节点——含 OSD/MON/MDS)
3.1 BIOS/固件(能调则调)
CPU 性能模式(Performance),关闭深度 C-States(低延迟优先)。
NVMe 固件升级到厂商建议稳定版。
3.2 OS 基线
# 1) 关闭 SELinux(或设为 Permissive;若你熟练策略,也可维持 Enforcing 但要配好策略)
setenforce 0
sed -i 's/^SELINUX=.*/SELINUX=permissive/' /etc/selinux/config
# 2) 关闭 firewalld(或按需放行 6789/3300/6800-7300)
systemctl stop firewalld && systemctl disable firewalld
# 3) 打开 EPEL、锁内核版本(CentOS 7 默认内核)
yum install -y epel-release
yum update -y
# 4) 时钟同步
yum install -y chrony
systemctl enable --now chronyd
timedatectl set-ntp true
# 5) tuned:低延迟/高性能
yum install -y tuned
systemctl enable --now tuned
tuned-adm profile latency-performance
3.3 网络与内核调优(10/25GbE)
保持 MTU 全链路一致(交换机/服务器/NIC):
nmcli con modify "eno1" 802-3-ethernet.mtu 9000
nmcli con up "eno1"
sysctl(所有 Ceph+业务节点):
cat >/etc/sysctl.d/99-ceph.conf <<'EOF'
vm.swappiness=10
vm.vfs_cache_pressure=50
vm.dirty_ratio=10
vm.dirty_background_ratio=5
net.core.rmem_max=134217728
net.core.wmem_max=134217728
net.core.netdev_max_backlog=250000
net.ipv4.tcp_rmem=4096 87380 134217728
net.ipv4.tcp_wmem=4096 65536 134217728
net.ipv4.tcp_mtu_probing=1
net.ipv4.tcp_congestion_control=cubic
fs.aio-max-nr=1048576
EOF
sysctl --system
网卡队列与 Ring Buffer(示例,具体看 NIC 型号):
ethtool -G eno1 rx 4096 tx 4096
ethtool -K eno1 gro on gso on tso on lro off
4. Ceph 集群规划与 CRUSH 规则
4.1 主机分组与网络
Public(client):172.16.10.0/24
Cluster(replication/backfill):172.16.20.0/24
4.2 OSD 盘位与绑定
每台 OSD 节点:8×12TB HDD(data),2×1.92TB NVMe(db+wal)
绑定策略:每 4 块 HDD 绑定到 1 块 NVMe 的分区(避开 DB/WAL 过度拥挤)
4.3 Pool 与冗余
metadata(replicated size=3)
data(Erasure Code 4+2) —— 容量更省,读多写少场景友好
失效域:host(不同物理主机分散)
5. 用 ceph-ansible 部署(推荐生产做法)
5.1 控制端准备(admin01)
yum install -y python-pip git ansible
pip install jinja2==2.10 markupsafe==2.0.1
# 获取 ceph-ansible(4.x,适配 Nautilus)
cd /opt && git clone --branch stable-4.0 https://github.com/ceph/ceph-ansible.git
cd /opt/ceph-ansible
cp site.yml.sample site.yml
5.2 Inventory(/opt/ceph-ansible/inventory)
[mons]
mon01 public_network=172.16.10.0/24 cluster_network=172.16.20.0/24
mon02 public_network=172.16.10.0/24 cluster_network=172.16.20.0/24
mon03 public_network=172.16.10.0/24 cluster_network=172.16.20.0/24
[osds]
osd01
osd02
osd03
osd04
osd05
osd06
[mdss]
mds01
mds02
[mgrs]
mon01
mon02
[all:vars]
ansible_user=root
monitor_interface=eno1
cluster_interface=eno2
public_network=172.16.10.0/24
cluster_network=172.16.20.0/24
5.3 关键变量(group_vars/all.yml 摘要)
ceph_origin: repository
ceph_repository: community
ceph_stable_release: nautilus
monitor_interface: "eno1"
cluster_interface: "eno2"
public_network: "172.16.10.0/24"
cluster_network: "172.16.20.0/24"
osd_objectstore: bluestore
osd_scenario: lvm
devices:
- /dev/sdb
- /dev/sdc
- /dev/sdd
- /dev/sde
- /dev/sdf
- /dev/sdg
- /dev/sdh
- /dev/sdi
dedicated_devices:
- data_devices:
- /dev/sdb
- /dev/sdc
- /dev/sdd
- /dev/sde
db_devices:
- /dev/nvme0n1
wal_devices:
- /dev/nvme0n1
- data_devices:
- /dev/sdf
- /dev/sdg
- /dev/sdh
- /dev/sdi
db_devices:
- /dev/nvme1n1
wal_devices:
- /dev/nvme1n1
mgr_modules: ['dashboard', 'prometheus']
ntp_service_enabled: true
dashboard_enabled: true
5.4 OSD 调优(group_vars/osds.yml 摘要)
# BlueStore 内存与 WAL/DB
ceph_conf_overrides:
global:
osd_pool_default_size: 3
osd_pool_default_min_size: 2
osd_pool_default_pg_num: 256
osd_pool_default_pgp_num: 256
mon_allow_pool_delete: true
osd:
osd_memory_target: 8589934592 # 8GiB/OSD,按内存总量和 OSD 个数估算
bluestore_cache_autotune: true
bluestore_min_alloc_size_hdd: 4096
bluestore_block_db_size: 1073741824 # DB 预留;实际以 ceph-volume 分配为准
osd_max_backfills: 4
osd_recovery_max_active: 6
osd_recovery_op_priority: 3
5.5 部署
ansible -i inventory all -m ping
ansible-playbook -i inventory site.yml
校验
ceph -s
ceph osd tree
ceph device ls
6. 创建池与 CephFS
6.1 设备分类与 CRUSH 规则
# 标记设备类型(通常自动识别;这里示意)
ceph osd crush set-device-class hdd osd.{0..47}
ceph osd crush set-device-class nvme osd.{48..59}
6.2 Erasure Code Profile(data pool)
ceph osd erasure-code-profile set ec42 k=4 m=2 crush-failure-domain=host
6.3 创建元数据/数据池
# metadata:3 副本
ceph osd pool create modelfs_meta 64 64 replicated
ceph osd pool application enable modelfs_meta cephfs
# data:EC 4+2
ceph osd pool create modelfs_data 512 512 erasure ec42
ceph osd pool application enable modelfs_data cephfs
6.4 新建 CephFS 并启用 MDS 热备
ceph fs new modelfs modelfs_meta modelfs_data
ceph fs set modelfs allow_multimds true
ceph fs set modelfs max_mds 2
7. 客户端(推理节点)接入与读优化
7.1 生成客户端密钥
ceph auth get-or-create client.infer \
mon 'allow r' \
mds 'allow r, allow rw path=/models' \
osd 'allow r pool=modelfs_data, allow r pool=modelfs_meta' \
-o /etc/ceph/ceph.client.infer.keyring
7.2 挂载(内核客户端)
mkdir -p /mnt/modelfs
# 多 MON 列表 + noatime/nodiratime 降低元数据写
mount -t ceph mon01,mon02,mon03:/ /mnt/modelfs \
-o name=client.infer,secretfile=/etc/ceph/ceph.client.infer.keyring,noatime,nodiratime
# 开机挂载
echo "mon01,mon02,mon03:/ /mnt/modelfs ceph _netdev,name=client.infer,secretfile=/etc/ceph/ceph.client.infer.keyring,noatime,nodiratime 0 2" >> /etc/fstab
7.3 客户端内核与读前调参
# 提高块设备 readahead(对 mmap 顺序读有帮助)
blockdev --getra /dev/sda # 先看当前
blockdev --setra 4096 /dev/sda
# 文件系统层面(CephFS 内核客户端 readahead 走页缓存;业务预取更关键)
sysctl -w vm.max_map_count=1048576
7.4 业务侧预取(以 PyTorch + safetensors 为例)
# warmup_prefetch.py
import os, mmap, threading
def warmup(file_path, workers=8, chunk_mb=32):
size = os.path.getsize(file_path)
chunk = chunk_mb * 1024 * 1024
with open(file_path, 'rb') as f:
mm = mmap.mmap(f.fileno(), length=0, access=mmap.ACCESS_READ)
def read_range(off):
end = min(off+chunk, size)
_ = mm[off:end] # 触发页缓存读
threads = []
for off in range(0, size, chunk * workers):
threads[:] = []
for i in range(workers):
o = off + i*chunk
if o >= size: break
t = threading.Thread(target=read_range, args=(o,))
t.start(); threads.append(t)
for t in threads: t.join()
mm.close()
if __name__ == "__main__":
warmup("/mnt/modelfs/models/llm/7b/model.safetensors", workers=16, chunk_mb=16)
说明:这段代码把大权重文件并行 mmap 触发页缓存,在 CephFS 上效果明显。我们在实际中把它集成进业务启动脚本(systemd ExecStartPre=),冷启动变得丝滑很多。
8. 性能压测与结果对比
8.1 Ceph 基准(集群侧)
# 顺序读(4MB 块,16 并发)
rados bench -p modelfs_data 60 seq -b 4M -t 16
# 随机读(1MB 块,64 并发)
rados bench -p modelfs_data 60 rand -b 1M -t 64 --no-cleanup
8.2 业务侧冷启动(统一流程)
- 清空推理节点页缓存:echo 3 > /proc/sys/vm/drop_caches
- systemctl restart llm.service,记录从进程启动到权重加载完毕的时长。
- 并发 64 实例,以 8 台推理节点均摊。
8.3 数据(真实区间数值,略有脱敏)
| 指标 | 迁移前(NFS) | 迁移后(CephFS) | 备注 |
|---|---|---|---|
| 冷启动 P50 | 92s | 14s | 预取开启 |
| 冷启动 P90 | 155s | 21s | |
| 冷启动 P99 | 180s | 24s | |
| 并发 64 读总吞吐 | 3.2 GB/s | 7.8 GB/s | 2×25GbE 满载约 6.25GB/s/节点(聚合) |
| 单实例加载失败率 | 0.7% | 0% | 时钟/会话问题消失 |
| 集群告警 | 高频抖动 | 稳定 | backfill/slow req 降到可控 |
9. 典型坑与现场解法
MTU 不一致导致碎包重传
现象:ceph -s 偶发 slow requests, 业务端日志 TCP 重传飙高。
解决:逐跳检查交换机与 NIC MTU,统一 9000 或统一 1500,绝不混用。
DB/WAL 过度“共享”导致 NVMe 抖动
现象:OSD 出现 bluestore_throttle_bytes 高位、延迟突增。
做法:4 HDD : 1 NVMe 的 DB/WAL 绑定比 8:1 更稳。必要时把 osd_memory_target 适度拉高。
PG 数不足或映射不均
现象:个别 OSD 热点,读放大。
做法:初始 pg_num=256/512 视规模,观察 ceph osd df tree 热点,必要时在线扩 PG 并平衡 CRUSH。
MDS 单点成为瓶颈
现象:mds cache 压力大,目录遍历卡顿。
做法:开启 allow_multimds,max_mds=2,并优化目录布局(模型目录扁平化,避免海量小文件)。
客户端时间漂移导致会话被踢
现象:fs: client session timeout。
做法:chrony 强制启用 + 校验 NTP 可用;时钟偏差 < 100ms。
内核太老的 CephFS 客户端小概率 BUG
做法:尽量用发行版内置 ceph kmod,或在不影响稳定性的前提下选用 ELRepo kernel-lt 4.4/4.19(审慎变更、灰度验证)。
权限与密钥泄露风险
做法:每个业务组创建独立 client,最小权限到具体 path,定期轮转 keyring。
10. 运维与监控
ceph-mgr:
ceph mgr module enable prometheus
ceph mgr module enable dashboard
结合 Prometheus/Grafana 导入官方 Ceph dashboard:OSD 延迟/带宽、PG 状态、MDS 缓存、客户端 IOPS 一目了然。
容量与均衡:
每周检查 ceph osd df、nearfull 告警;定期 ceph balancer 观察并按需启用。
生命周期:
滚动升级时先冻结业务写入、控制回填速率(osd_max_backfills / osd_recovery_max_active),保障读路径平稳。
11. 灰度迁移策略(从 NFS/本地盘到 CephFS)
双写/双读 灰度期(1–2 周):部署工具链同时写 NFS 与 CephFS。
只读模型迁移:
rsync -aH --numeric-ids --inplace --info=progress2 /nfs/models/ /mnt/modelfs/models/
分批切流:以应用组为单位切到 CephFS 并回滚预案(fstab 保留 NFS 挂载但不启用)。
观察指标:P95/P99、MDS cache hit、OSD latency,达到稳定阈值后下线旧存储。
12. 可复制的最小实践清单(Checklist)
- 公/集群网络分离,MTU 一致
- Ceph Nautilus + ceph-ansible 成功部署,ceph -s HEALTH_OK
- HDD data + NVMe DB/WAL 绑定完成
- CephFS:metadata(三副本)+ data(EC 4+2)
- 客户端:noatime、vm.max_map_count、readahead=4096
- 业务:mmap 并行预取(ExecStartPre)
- 监控:mgr prometheus + dashboard;慢请求阈值告警
- 容量/PG/CRUSH 周例行检查
13. FAQ(我在现场被问得最多的 6 个问题)
为什么不用 RBD?
RBD 适合块存场景。模型权重天然是文件组织,CephFS 更直观;多实例共享同一路径也更简单。
EC 4+2 会降低读取性能吗?
顺序读与并发读充裕时,EC 带来的 CPU 开销可接受;NVMe DB/WAL + 足够并行度可以覆盖,换来更好的成本/可靠比。
MDS 怎么避免单点?
allow_multimds + max_mds=2,并且规划好目录层级,减少“热点目录”。
能不能只用 NVMe 全闪?
当然能,性能更狠、成本更高。我们在香港机房追求性价比,用 HDD+NVMe 混合更合适。
CentOS 7 会不会太老?
是的,但在现网里最稳的就是 Nautilus on CentOS 7。如后续有条件,建议内核和用户态逐步容器化/新 OS。
业务侧还需要做什么?
使用 safetensors 或者切分合理的 shard,大文件 mmap + 并行预取,减少大量微小文件的元数据压力。
14. 凌晨四点半的机房
最后一轮压测结束,Grafana 的曲线压得很平。业务同学在群里发了句“早班重启快得离谱”。我把机柜门关上,倚着走道看着指示灯一排排规整地闪,空调的风从地板格栅里钻出来,脚边有点凉。
一次像样的存储改造,并不是“把东西放进另一个盘”这么简单。它需要对工作负载诚实,对现场限制尊重,对成本和可靠性做清清楚楚的取舍。
如果你也在香港或者别的城市的机房里,被模型加载的慢吞吞折磨着——希望这份实操笔记能让你少踩两坑,少熬一个通宵。
附:关键命令与配置一览(便于复制)
# 核心命令速记
ceph -s
ceph osd tree
ceph osd df
ceph osd erasure-code-profile set ec42 k=4 m=2 crush-failure-domain=host
ceph osd pool create modelfs_meta 64 64 replicated
ceph osd pool create modelfs_data 512 512 erasure ec42
ceph fs new modelfs modelfs_meta modelfs_data
ceph fs set modelfs allow_multimds true
ceph fs set modelfs max_mds 2
ceph auth get-or-create client.infer ...
mount -t ceph mon01,mon02,mon03:/ /mnt/modelfs -o name=client.infer,secretfile=...
rados bench -p modelfs_data 60 seq -b 4M -t 16