1. 离线部署的整体思路与方案选型
做存储交付的同行应该都有体会:大部分生产环境的网络边界卡得死死的,外网不通,公共软件源更是不存在。我最近就接手了这么个活儿,客户现场要求在内网离线环境里部署一套 Ceph Reef 版本集群,最终交付目标是提供 RBD 块设备给虚拟化平台做后端存储。标题拆开看就三件事:离线、Reef、RBD。但实际落地时,这三件事叠加在一起,工作量远超预期。
先说版本。Ceph Reef 是 18.x 系列,是继 Quincy 之后的一个稳定版本,社区主推用 cephadm 做容器化部署,不再像老版本那样用 ceph-deploy 或者手动装一堆 rpm 包。这意味着集群的所有组件——MON、MGR、OSD、RGW——都以容器方式跑在节点上。容器化部署带来一个直接问题:目标环境没有外网,容器镜像从哪来?系统依赖包从哪来?所以离线部署的本质,就是把“断网环境下拉齐所有部署物料”这件事搞定。
在方案选型上,我对比过两条路。第一条是传统包部署,直接在节点上用系统包管理器安装 ceph-common、ceph-mon、ceph-osd 这些软件包,不跑容器,配置文件和 systemd 服务手动管理。这条路的好处是脱离容器,镜像问题不存在,离线难度低一些。但劣势也很明显:版本混装容易出问题、升级麻烦、Reef 版本官方对非 cephadm 部署方式的测试投入明显减少。第二条就是官方主推的 cephadm + podman,所有组件容器化,用命令编排整个集群。选了这条路的团队,离线部署的核心矛盾就集中在两点:容器镜像包准备、系统依赖包准备。
我的最终选择是 cephadm 容器化方案,符合官方长期演进方向,后续扩缩容、升级都能吃到官方工具链的红利。为了让大家对后面每一步有底,我先说下整体规划思路:先在一台能联网的机器上把所有安装物料打包,包括系统依赖、cephadm 安装脚本、容器镜像;再把物料运到内网,搭一个本地软件源;然后在内网节点上逐台初始化系统环境,导入镜像,用 cephadm bootstrap 拉起第一个 MON;接着添加其余节点和 OSD;最后创建 RBD 存储池、创建块设备、映射挂载验证。下面按这个流程一步步来。
2. 离线部署包的准备与本地源搭建
2.1 联网机器上的物料收集
离线部署的第一步,是找一台“摆渡机”——能访问外网、磁盘空间够大、和最终内网环境同架构(都是 x86_64 最稳妥)。在这台机器上,把目标端可能用到的所有物料提前备齐。
系统依赖层面,我的习惯是直接用发行版自带的工具做全量依赖收集。以 Debian 系发行版为例,核心思路是下载 cephadm 需要运行的基础工具链,比如 podman、python3、lvm2、nfs-common 等。Ceph 官方文档里对宿主机有明确要求,我通常会先装一个最小化系统,然后在一台干净机器上用apt-get download配合apt-cache depends把相关包递归拉下来。需要注意的是,依赖收集一定要在干净环境里做,否则会把本机无关的软件包混进去,导致离线源臃肿不说,还有可能引入版本冲突。
容器镜像层面,要拉取的镜像是 Ceph 官方发布的 Reef 版本镜像,通常叫ceph/ceph:v18.2.x。这里有个坑:cephadm 默认会从容器仓库拉取镜像,离线环境必须先手动把镜像拉下来并保存为 tar 文件。用 podman 的话就是podman pull之后podman save导出。建议多存几个标签版本,比如 v18.2.0、v18.2.4,以免目标环境指定的版本和仓库里的对不上。
2.2 本地软件源的搭建与镜像导入
物料运到内网节点后,不能直接全部解压散落一地,得先建立一个可复用的本地源。Debian 系上我用的是apt-ftparchive生成 Packages 索引,再把整个目录放到一台内网节点的 HTTP 服务或者 NFS 共享上。操作上分为三步:先把收集到的 deb 包放入一个目录;然后在该目录下运行apt-ftparchive packages . > Packages生成索引;最后将目录挂载到 HTTP 或直接通过 file 协议挂进各节点的 sources.list 里。这个过程看起来简单,但有个常见的错误是忘记修改 sources.list 里的Signed-By或者关掉签名校验,导致 apt 拒绝使用本地源。
容器镜像的导入相对直接。每个节点上执行podman load -i ceph-image.tar,加载完毕后用podman images确认镜像在本地列表里。注意 cephadm 引导时会尝试按标签查找镜像,所以镜像的完整名称和标签必须和后续 bootstrap 指定的完全一致,建议在准备阶段就把镜像 tag 固定写进项目文档里,避免现场临时改。
2.3 物料清单与版本核对
离线部署最怕“到现场才发现缺包”。我会专门维护一张物料清单表格,每次项目交付都按这张表核对,实测能避免九成以上的返工:
| 物料项 | 说明 | 核查方式 |
|---|---|---|
| 系统依赖包 | podman、lvm2、python3 等 | apt 本地源索引生成无报错 |
| cephadm 脚本 | 离线执行引导的关键文件 | 执行./cephadm --help验证 |
| Ceph 容器镜像 tar | Reef 版本 x86_64 镜像 | podman load成功且 tag 正确 |
| NTP 离线源 | 内网时间同步依赖 | 各节点可访问时间服务器 |
| DNS/主机名映射 | 集群节点互访基础 | 各节点/etc/hosts一致 |
这里特别提醒:很多团队只关注容器镜像,忽略了时间同步。Ceph 的 MON 对时钟偏移极其敏感,偏移超过一定阈值会直接导致部分节点上的服务被判定为不健康,集群状态报错。离线环境基本没有外网 NTP 服务,必须在内网自建时间源,否则后期 RBD 在多个节点间读写时会出现各种诡异的客户端超时问题。
3. 系统初始化与 cephadm 部署流程
3.1 节点系统初始化
物料就绪后,开始进入正式部署。这个环节我习惯先把所有节点的基础环境统一掉,不让小事在后期拖后腿。每个节点都要做这几件事:设置静态 IP 和主机名,确保/etc/hosts里写全所有节点的 IP 和主机名映射;关闭防火墙或者放通 Ceph 需要的端口段——MON 默认 3300 和 6789,OSD 和 MGR 使用的端口段也一并规划好;配置本地软件源并apt update;安装 podman 以及 cephadm 运行需要的 python3、lvm2 等基础包;最后统一配置时间同步客户端,指向内网时间服务器。
节点初始化最容易翻车的点在于磁盘规划。Ceph OSD 节点的数据盘必须是裸设备,不能包含分区表,更不能提前做了文件系统。我遇到过好几回,同事图省事把数据盘提前用mkfs格式化了,结果在ceph orch apply osd的时候设备被跳过,排查了半天才发现是文件系统残留导致 cephadm 认为设备不可用。正确做法是数据盘保持出厂状态,用lsblk确认没有任何分区和文件系统标记。
3.2 导入镜像与 bootstrap 引导集群
先把 cephadm 脚本传到第一个节点,加上执行权限,确认版本无误。然后执行引导命令。这里的关键参数是--image,必须显式指定本地镜像,否则 cephadm 会尝试去公共仓库拉取,这一步在离线环境会直接卡死。我当时用的命令大致是:
./cephadm bootstrap --mon-ip 172.16.0.10 \ --image ceph/ceph:v18.2.4 \ --allow-fqdn-hostname \ --ssh-user root \ --skip-monitoring-stack--mon-ip指定第一个 MON 监听的 IP;--allow-fqdn-hostname是因为部分现场主机名带域名后缀,不加这个参数 cephadm 可能拒绝启动;--skip-monitoring-stack先跳过监控组件,离线环境下监控镜像没准备好,硬拉反而会中断 bootstrap。bootstrap 过程会自己生成并配置 SSH 密钥,输出一串访问 Dashboard 的地址和账号,这些信息先抄下来存好。
bootstrap 结束以后,先别急着加 OSD,用ceph -s看一眼集群状态。正常情况下会有一个 MON 和一个 MGR,状态如果是 HEALTH_WARN 不要慌,离线环境第一次拉起出现告警很常见,关键看有没有 ERROR 级别的红色报错。如果状态正常,再继续往集群里加节点。
3.3 添加节点与 OSD 编排
添加节点这一步,本质是把新节点的 SSH 访问权限交给 cephadm,让它能够远程操纵新节点。操作方法官方文档写得很清楚:在已经有集群密钥的节点上,把公钥复制到新节点,再用ceph orch host add命令注册主机。我习惯把注册和打标签分开做,先 add 主机,再通过ceph orch host label add给节点打上标签,便于后面按标签批量管理 OSD。
OSD 的编排是所有环节里最需要耐心的。先跑ceph orch device ls确认每台节点上的裸设备都被正常识别。能识别到之后,执行:
ceph orch apply osd --all-available-devices这个命令会把所有可用裸盘都变成 OSD。如果只想指定某块盘,可以用--device参数细粒度控制。这里有一个经验:不要一上来就--all-available-devices,建议先在单台节点上试用,确认服务能正常拉起,再铺开到全集群。因为如果某个节点的磁盘有问题,全量应用时你很难快速定位是哪块盘导致的故障 OSD。
等 OSD 全部起来后,再次执行ceph -s,正常情况下 PG 状态会在几分钟内从 creating 变成 active+clean。这个状态是我判断集群能否进入下一步的唯一标准,不达到干净状态,我不会创建任何存储池。
4. RBD 块设备从创建到挂载全流程
4.1 创建存储池并启用 RBD 应用
集群健康之后,RBD 的使用链路就正式开始了。首先要创建一个独立的存储池,存储池之于 RBD 相当于“命名空间”,不同业务的数据可以隔离到不同池里,互相不干扰。创建池子时要规划好 PG 数量。PG 数量不是越大越好,太大会消耗大量内存和 CPU 做 PG 状态维护,太小则分布不均匀。我这边有个简单估算方式:每个 OSD 分摊 100 到 200 个 PG,再乘 OSD 数量,最后取一个接近 2 的幂。比如 12 个 OSD,每个按 100 算,就是 1200,向上取 2 的幂是 2048,那就把 PG 数量定为 2048。当然生产环境还要考虑未来的扩容余量,宁可偏大一点,也不要在扩容时反复调 PG。
创建命令如下:
ceph osd pool create rbdpool 2048 2048 ceph osd pool application enable rbdpool rbd第二行命令是给存储池打上“RBD 应用”的标签,不打这个标签,后面用rbd命令操作时会有告警提示,虽然不阻止使用,但属于隐患,现场排查时容易被误导。
4.2 创建块设备镜像与特性处理
存储池就绪后,用rbd命令创建块设备。Reef 版本的 RBD 默认开启了一堆高级特性,比如 object-map、fast-diff、deep-flatten、exclusive-lock。这些特性在最新的内核客户端上没问题,但如果你的使用场景是经典内核映射,或者客户端是老版本内核,建议在创建时显式关闭部分特性,否则rbd map会直接报错:
rbd create --size 100G rbdpool/vm-disk-01 \ --image-feature layering,exclusive-lock把特性裁剪到最小可用集合,是保证跨环境兼容性的关键。我踩过最深的坑,就是在某现场一次性默认特性全开,之后用内核模块映射时直接报“不支持 requested feature”,不得不重建镜像。所以现在我的默认习惯是只保留layering和exclusive-lock,前者支持克隆,后者保证多端并发写的一致性,够用了。如果你的场景完全不需要克隆,甚至可以只用layering。
4.3 内核映射、格式化与挂载
镜像创建完成后,在需要访问块设备的客户端节点上执行映射:
rbd map rbdpool/vm-disk-01如果客户端没有装rbd-common工具,需要先把对应的软件包装好。映射成功后可以用lsblk看到一个新块设备,通常是/dev/rbd0,对应底层就是存储池里的那个镜像。注意,RBD 默认不开启客户端缓存,所以映射出来的设备更像是“网络磁盘”,写数据直接走网络到 OSD。这也意味着它特别适合虚拟化平台直接作为虚拟机的后端存储盘。
接下来就是块设备的标准操作:
mkfs.ext4 /dev/rbd0 mkdir -p /srv/vm-data mount /dev/rbd0 /srv/vm-data格式化和挂载本质上是把块设备变成一个可用文件系统,这里没有特殊技巧,唯一要提醒的是确认客户端节点的内核版本支持 RBD 模块,大部分主流发行版默认都编译进去了,但精简内核环境要提前检查/lib/modules/$(uname -r)里有没有rbd.ko。
4.4 开机自动挂载配置
投产环境不能指望每次重启后手动映射和挂载。RBD 工具包自带一个rbdmap机制,用来处理开机自动映射。我通常在/etc/ceph/rbdmap里按行写入需要自动映射的镜像和挂载参数,例如:
rbdpool/vm-disk-01 id=admin,keyring=/etc/ceph/ceph.client.admin.keyring然后启用rbdmap服务并设置开机自启。需要注意,rbdmap处理的是“映射”动作,不处理“挂载文件系统”动作。映射完成后的挂载,要么写在/etc/fstab里并加上_netdev选项,要么做成独立的 systemd 服务。我个人更推荐 systemd 方式,因为/etc/fstab在网络设备映射尚未完成时挂载,容易触发启动顺序问题,表现为开机后设备文件还没出现就尝试挂载,只能报错。
5. 集群验证与常见问题排错
5.1 集群健康状态检查
RBD 挂载成功只是开始,真正能交付的标准是集群长期健康。我有一套固定的验证序列:先看ceph -s的总体状态;再看ceph osd tree确认 OSD 分布和 up/in 状态;接着看ceph osd pool ls detail确认存储池 PG 数量和属性;最后在客户端节点上实际跑一轮dd读写测试,用真实 I/O 确认块设备链路通不通。
这里有个容易被忽视的点:验证读写的时候,建议分别测试块设备的底层读写和文件系统层的读写。底层用rbd bench或者直接dd到/dev/rbd0,文件系统层就是正常的文件读写。两个层次都通过,才能说明 RBD 服务和内核模块间的链路没问题。我见过不少案例,ceph -s显示集群全绿,但客户端dd直接报 I/O error,最后定位是客户端节点网络 MTU 不匹配,导致大数据块传输失败。
5.2 常见故障与排查思路
离线部署 Ceph 的排错,和在线环境相比差异很大,因为很多在线环境可以临时拉包、临时查文档,离线环境所有工具都得提前备好。下面几个问题是现场出现频率最高的。
第一个是时钟偏移。离线的节点如果时间源没配好,过一阵子 MON 会报clock skew,严重时直接导致 MON 之间投票异常,表现为某个 MON 频繁与主节点断连。处理方式是在所有节点安装并启动 NTP 客户端,指向内网时间服务器,同时手工校准一次时间差,等ceph -s恢复后再观察一段时间。
第二个是 cephadm 引导时报镜像拉取失败。这个问题的根源基本是 bootstrap 命令没带--image参数,或者带的是 tag 但本地镜像没有该 tag。排查方法很直接:podman images列出本地镜像全名和 tag,和引导命令里的--image做逐字符比对。注意latest标签和具体版本号标签指向的镜像 ID 即使完全相同,cephadm 仍然会依据字符匹配来决定是否本地命中。
第三个是 OSD 创建后一直处于down状态。这种问题大概率出在磁盘本身或者文件系统残留。先用ceph orch device ls看设备是否被识别,再用lsblk -f检查磁盘上有没有残留文件系统,有的话必须先清理干净再重新应用 OSD。如果磁盘没问题,还要检查节点上是否有另一个 OSD 占用了相同盘符,多路径环境下尤其容易撞车。
第四个是客户端rbd map报不支持的特性。这个问题的原因我在前面已经说过:默认全特性在旧内核上兼容性差。解决方案是创建镜像时显式指定特性集合,或者对已有镜像使用rbd feature disable逐项关闭多余特性,然后再重新映射。
5.3 现场排错工具体验
离线环境下,排错工具也要提前备好。我每次进现场都会准备一个工具包,包括tcpdump、iperf3、sysstat系列、以及现成的 Ceph 客户端工具二进制。别看这些不起眼,关键时刻能救命。比如 RBD 读写慢,客户会各种甩锅到存储,我用iperf3在客户端和 OSD 节点间跑一轮网络带宽,三分钟就能把网络是否背锅排除掉,比反复调存储参数高效得多。
另外,cephadm 的日志查看方式也值得单独说。容器化部署后,不能用老办法直接看/var/log/ceph下的文件,更多时候要用:
ceph -W cephadm journalctl -u ceph-<服务名>@<节点名>.service我先在引导节点上把ceph -W cephadm挂着观察集群事件,再按需用 journalctl 钻进单个服务看细粒度日志。这种“先宏观后微观”的排查顺序,能最大限度减少无效操作。
写在最后的一点个人体会
离线部署这件事,技术难度其实没有想象中高,真正考验人的是“物料管理和现场预案”。我做过好几个断网环境的存储项目,每一次顺利交付都靠提前把问题想清楚:缺什么依赖、哪个版本、磁盘怎么规划、时间怎么同步。这些细枝末节,在线部署时可能忍忍就过去了,离线环境一旦漏掉一个,现场就得花成倍的时间去解决。
最后分享一个小技巧:每次部署完,我会把执行过的命令和踩过的坑整理成一页速查卡,直接随交付文档交给客户。这样即使后续换人维护,遇到问题也能快速定位方向。RBD 这块后续还可以扩展的内容很多,比如动态扩容、快照备份、克隆复用,都是生产环境高频需求。如果这套离线部署流程在你那边能跑通,往这些方向延伸就会顺很多。