我的 4 节点 PVE + Ceph 集群运维笔记:从调优到日常维护的命令全集

random-pic-api

前言

家里现在跑着一套 4 节点 PVE + Ceph 集群,硬件都是一些二手 NUC、小主机和软路由刷 PVE,主要承载 Homelab 的各种服务:NAS 备份、Drive、监控、Jellyfin、还有一堆实验性的 LXC。

集群跑起来之后,日常 80% 时间是不用管的,但剩下 20% 时间全都是各种奇奇怪怪的维护:OSD 飘了、日志塞满、某个 LXC 挂载不上 cephfs、偶发脑裂……每次遇到都要翻一遍搜索引擎,干脆把这些命令全部攒成一份自己的”运维速查手册”,平时直接往里面复制粘贴。

这篇文章就是这份手册本身。内容并不是按”教程”的顺序组织的,而是按我最常翻到的频率来排,越前面的越常用。如果你也是 PVE + Ceph Homelab 的玩家,应该都能在里面找到几段能直接抄走的命令。

⚠️ 这些命令我自己的环境全都跑过,但不保证对你也 100% 安全 —— 尤其是 Ceph 和 corosync 相关的操作,动之前请务必确认集群状态是 HEALTH_OK,并且重要数据有备份


集群日常

一键换源

PVE 的官方源在国内速度感人,企业源还要订阅,第一件事就是换国内镜像。我一直用 LinuxMirrors 的一键脚本,支持 Debian 底层、PVE 源、Ceph 源三件套一次搞定:

1
2
3
bash <(curl -sSL https://linuxmirrors.cn/main.sh)
bash <(curl -sSL https://linuxmirrors.cn/pve.sh)
bash <(curl -sSL https://linuxmirrors.cn/ceph.sh)

跑完之后 apt update 速度直接从龟速变秒开。

重启 PVE 核心服务

某些情况下 Web UI 卡住、节点状态红色,但又不想直接重启整台机器,可以按顺序拉起这四个服务:

1
2
3
4
systemctl start corosync.service \
&& systemctl start pve-cluster.service \
&& systemctl start pvedaemon.service \
&& systemctl start pvestatd.service

顺序很重要:corosyncpve-clusterpvedaemonpvestatd,这是它们的启动依赖链。

查看 OSPF 邻居

我的集群用了 FRR + OSPF 做 mesh 网络(三节点 full mesh、四节点 broadcast):

1
vtysh -c "show ip ospf neighbor"

正常情况下每个节点能看到其他所有节点的邻居关系是 Full/BDRFull/DR。如果长时间停在 2-Way 或者 ExStart,基本是 MTU 不一致或者 link 不通,要去查物理层。


性能与功耗调优

优先使用物理内存

默认的 vm.swappiness=60 对 PVE 来说太积极了,会在内存还很充足的时候就开始 swap,导致虚拟机莫名其妙卡顿。直接改成 0:

1
sysctl vm.swappiness=0 && swapoff -a && swapon -a

想持久化的话写进 /etc/sysctl.conf

1
echo "vm.swappiness = 0" >> /etc/sysctl.conf

CPU 电源模式(performance vs powersave)

我这 4 台机器里,常年跑服务的主节点走 performance,备用节点走 powersave。默认一般是 performance,比较费电。

先装 linux-cpupower

1
apt install linux-cpupower

几个常用命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 查看支持的电源模式
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors

# 查看当前模式
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor

# 实时频率监控
watch -n 1 cpupower monitor

# 查看所有 CPU 信息
cpupower -c all frequency-info

# 一键切换
cpupower -c all frequency-set -g powersave # 节能
cpupower -c all frequency-set -g performance # 性能

cpupower 设置重启后就失效,要持久化的话用下面这条(开机自动写 sysfs):

1
echo "powersave" | tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor

更优雅的做法是用 tuned,但对我的场景来说太重,直接 echo 写 sysfs 就够了。把这一行放进 /etc/rc.local 或者一个 systemd service 就行。

磁盘性能测试(fio)

每次加新盘、换 SSD、怀疑性能不对的时候,我都会用 fio 跑一遍基准:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
apt update && apt install -y fio

# 顺序写入(1MB 块,512MB 数据,30s)
fio --name=seq_write --ioengine=libaio --rw=write --bs=1M --size=512M \
--numjobs=1 --direct=1 --runtime=30 --time_based --group_reporting

# 顺序读取
fio --name=seq_read --ioengine=libaio --rw=read --bs=1M --size=512M \
--numjobs=1 --direct=1 --runtime=30 --time_based --group_reporting

# 随机写入(4K 块,4 个并发 job,iodepth=32)
fio --name=rand_write --ioengine=libaio --rw=randwrite --bs=4K --size=512M \
--numjobs=4 --iodepth=32 --direct=1 --runtime=30 --time_based --group_reporting

# 随机读取
fio --name=rand_read --ioengine=libaio --rw=randread --bs=4K --size=512M \
--numjobs=4 --iodepth=32 --direct=1 --runtime=30 --time_based --group_reporting

# 延迟测试(iodepth=1,单队列反映真实延迟)
fio --name=latency_test --ioengine=libaio --rw=randrw --bs=4K --size=512M \
--numjobs=1 --iodepth=1 --direct=1 --runtime=30 --time_based --group_reporting

重点看:

  • IOPS / BW 是否达到 SSD 标称值的 60% 以上(低于就可能是 SATA 接口降速、OSD 配置问题、或者盘本身挂了);
  • lat99.00th / 99.99th 分位 —— 机械盘超过 30ms 很正常,SSD 超过 10ms 就要怀疑健康状态。

磁盘分区(parted)

新 NVMe 到手第一件事是分区,我习惯直接切成 4 等分留着做不同用途(OSD / 系统盘 / 缓存 / 备份):

1
2
3
4
5
6
7
8
9
parted /dev/nvme0n1
(parted) mklabel gpt # 初始化 GPT —— 会清空数据!
(parted) unit GiB # 切换单位为 GiB
(parted) mkpart primary 0% 25% # 第 1 分区:0%-25%
(parted) mkpart primary 25% 50% # 第 2 分区:25%-50%
(parted) mkpart primary 50% 75% # 第 3 分区:50%-75%
(parted) mkpart primary 75% 100% # 第 4 分区:75%-100%
(parted) print # 验证
(parted) quit

用百分比比硬写 GiB 数字可靠得多,parted 会自己算好 4K 对齐。


Ceph 日常维护

Ceph 是 PVE 集群里最容易出幺蛾子的部分,下面这些命令基本覆盖了我三个月里遇到的所有问题。

重启 Ceph 所有服务

MON / MGR / OSD / MDS 一起拉起来,故障恢复的核武器:

1
2
3
4
systemctl restart ceph-mon.target \
&& systemctl restart ceph-mgr.target \
&& systemctl restart ceph-osd.target \
&& systemctl restart ceph-mds.target

不过正式建议是一个节点一个节点地重启,而不是同时全部 restart,不然集群会短暂进入 HEALTH_WARN

清理 Ceph 日志

Ceph 日志默认非常啰嗦,跑几个月 /var/log/ceph/ 能吃掉好几个 G:

1
2
du -sh /var/log/ceph/         # 先看看多大
rm -rf /var/log/ceph/* # 直接清空

调整日志等级(强烈推荐)

家用场景完全不需要默认那么详细的日志,直接改成最小:

1
2
3
4
5
ceph config set mon debug_mon 0/0
ceph config set osd debug_osd 0/0
ceph config set mgr debug_mgr 0/0

systemctl restart ceph-mon.target ceph-osd.target ceph-mgr.target

这里 0/0 的两个数字含义:

  • 前面的数字:subsystem 级别,影响实时输出(ceph -w);
  • 后面的数字:log_level,影响写入 /var/log/ceph/ 的详细程度。

级别对照:

说明
0只打印严重错误(推荐生产环境
1基本信息(默认)
5详细信息,适合调试
10极限调试,会记录大量信息,非常影响性能

常用组合:

  • 0/0 → 最小日志(推荐)
  • 1/5 → 标准模式
  • 5/10 → 详细调试
  • 10/10 → 极限调试(性能会崩,慎用)

OSD 全局 Flags 速查

这张表我翻得最多,Ceph 维护的时候经常要临时关掉某些自动行为:

名称功能典型场景
nobackfill暂停 PG 的回填(数据迁移到新 OSD)维护时避免迁移占用带宽
norebalance暂停 PG 重新平衡(均衡数据分布)避免数据均衡影响业务性能
norecover暂停 PG 恢复(故障后数据修复)优先保障业务运行,延迟修复
pause完全暂停读写(集群进入只读状态)紧急故障时防止数据不一致
nodeep-scrub禁用深度清理(检查完整性并修复)减少清理对性能的影响
noscrub禁用普通清理(仅检查元数据)同上,但更轻
notieragent暂停缓存分层活动(缓存池与存储池的数据迁移)优化缓存层或调试分层
nodown忽略 OSD 故障报告,不自动标记为 down防止误判导致不必要恢复
noout禁止 OSD 自动标记为 out(超时也不踢)维护时保持 OSD 状态稳定
noin禁止 OSD 启动时自动标记为 in(人工控制)人工控制 OSD 重新加入
noup禁止 OSD 启动强制停止问题 OSD,避免干扰

一般用法:

1
2
3
ceph osd set <flag>       # 设置
ceph osd unset <flag> # 取消
ceph -s # 查看当前有哪些 flag 打开着

单节点维护

要重启某个节点或者换盘,提前打两个 flag 避免 Ceph 以为盘真丢了、开始疯狂迁移数据:

1
2
3
4
5
6
7
8
9
# 维护前
ceph osd set noout
ceph osd set norebalance

# ... 做维护 ...

# 维护后
ceph osd unset noout
ceph osd unset norebalance

手动指定 OSD 的 device class

有时候 Ceph 会把 NVMe 识别成 hdd 或者 ssd,导致 CRUSH rule 走不到正确的池上:

1
2
ceph osd crush rm-device-class osd.<ID>
ceph osd crush set-device-class nvme osd.<ID>

修改 OSD 的通讯 IP

重新规划集群内部网络的时候要用到,可以单独改某个 OSD:

1
2
ceph config set osd.<ID> cluster_addr 10.0.2.2
systemctl restart ceph-osd@<ID>

如果要直接改配置文件,这四个字段都要看一眼:

1
2
3
4
5
[osd.<ID>]
public_addr = 10.0.2.2 # 前端业务地址
cluster_addr = 10.0.2.2 # 集群内部心跳/副本复制地址
public_bind_addr = 10.0.2.2 # 绑定前端地址
cluster_bind_addr = 10.0.2.2 # 绑定心跳地址

安全移除一块 OSD

这是我写进”收藏夹”最频繁用到的流程,每次换盘都得跑一遍。先 reweight 到 0,等数据迁完再真删,否则容易丢副本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 1. 把权重降到 0,让数据逐步迁走(而不是一下子全 out)
ceph osd crush reweight osd.<x> 0

# 2. 等 ceph -s 显示 HEALTH_OK / 无 recovery,再继续

# 3. 把 OSD 踢出集群并停服
ceph osd out <x>
systemctl stop ceph-osd@<x>

# 4. 从 CRUSH / 认证 / OSD map 中彻底删除
ceph osd crush remove osd.<x>
ceph auth del osd.<x>
ceph osd rm <x>

# 5. 清理底层 LVM(物理盘上的 Ceph 痕迹)
ceph-volume lvm zap /dev/sdx --destroy
rm -rf /var/lib/ceph/osd/ceph-<x>

“X daemons have recently crashed” 告警

这个告警巨烦,每次一个 OSD 短暂崩溃恢复之后就会挂着这个 WARN,看起来好像集群还有问题:

1
2
3
4
5
6
7
8
9
10
11
12
# 查看 crash 记录
ceph crash ls
ceph crash ls-new

# 确认已经处理过了,归档掉
ceph crash archive <crash-id>

# 一键归档所有
ceph crash archive-all

# 彻底关掉这个告警(不推荐,除非你真的不想看)
ceph config set mgr mgr/crash/warn_recent_interval 0

LXC 相关

LXC 容器内挂载 cephfs

这个 PVE 官方论坛讨论过几次,最干净的做法是从 host 透传,而不是在 LXC 里再跑一个 ceph client:

1
2
pct set <lxcid> -mp0 /mnt/pve/cephfs,mp=/mnt/cephfs,shared=1
pct restart <lxcid>

其中 shared=1 标记让 PVE 知道这是共享存储,迁移的时候不会傻乎乎地去拷文件。

特权 LXC 的 SSH 权限坑

开了 特权(privileged) 的 LXC 容器里,root 其实是映射到了 host 上的 uid 100000 用户组。结果就是:

  • 在 LXC 里 chown root:root ~/.ssh/authorized_keys 看起来生效;
  • 但 host 侧看到的实际 owner 还是 100000:100000
  • SSH 服务发现 ~/.ssh 不是 root 拥有,直接拒绝 pubkey 登录。

解决办法:在 host 上手动把 LXC 根目录里 .ssh 的 owner 改回 0:0(rootfs 路径一般是 /var/lib/vz/private/<vmid>/root/.ssh):

1
2
3
chown -R 0:0 /var/lib/vz/private/<vmid>/root/.ssh
chmod 700 /var/lib/vz/private/<vmid>/root/.ssh
chmod 600 /var/lib/vz/private/<vmid>/root/.ssh/authorized_keys

这个坑我踩过两次,第二次翻半天博客才想起来 —— 所以专门记一下。

给 LXC 内的 Samba 指定网络名

很多 LXC 里跑 Samba 共享,Finder / 网络邻居里看到的名字却是路由器型号或者乱码,需要改 Avahi:

1
2
3
4
5
6
vim /etc/avahi/avahi-daemon.conf

# [server]
# host-name=xxx # 改成你想显示的名字

systemctl restart avahi-daemon

更系统的做法可以参考我的另一篇 [[476ad31f|受够了手动配 Samba 的一键脚本]]。


集群仲裁与高可用

为什么 4 节点需要额外的投票

Corosync 集群要求 quorum = floor(N/2) + 1。节点数对照:

节点数Quorum最多允许挂几台
321
431
532

4 节点其实和 3 节点一样最多只能挂 1 台 —— 但一旦 4 台分成 2 + 2(脑裂),两边都拿不到 quorum,整个集群全瘫。这比 3 节点还脆弱。

所以 4 节点一定要加一个 外部投票节点(qdevice) 把总投票数凑成奇数。

我用了一台 H28K 作 qdevice

H28K 是一台小型 ARM 软路由(功耗几 W),常年在线,非常适合当 qdevice,IP 是 192.168.31.228

完整步骤参考 PVE 官方文档 - External Vote Support,要点:

  1. 在 qdevice 那台机器上装 corosync-qnetd
  2. 在任意一个 PVE 节点上装 corosync-qdevice
  3. pvecm qdevice setup 192.168.31.228 一键初始化。

完成后 pvecm status 会多出一行 Qdevice,投票权从 4 变成 5,再也不怕 2+2 脑裂了。

添加多个冗余 IP

如果集群网络有多条链路(比如业务网 + 专用心跳网),可以给 corosync 配多个 ring,任何一条挂了都不会导致 quorum 丢失。

具体做法是编辑 /etc/pve/corosync.conf 给每个 nodering1_addr,再把 totem 里的 config_version 加一。参考 这篇腾讯云文档 或者 PVE 官方 Wiki。


主要配置文件速查

/etc/pve 是一个 pmxcfs 内存文件系统,集群同步完全走它,所以在任何节点改里面的文件,其他节点立刻同步。这个设计非常反直觉但极其优雅。

下面是我最常翻的几个路径:

路径用途
/etc/pve/corosync.conf集群通信(成员、ring、quorum 规则)
/etc/pve/ceph.confCeph 集群主配置
/etc/ceph/*其他 Ceph 文件(keyring 等)
/etc/pve/nodes/<node>/每个节点的专属配置子目录
/var/lib/corosync/Quorum / votequorum 状态数据
/etc/pve/storage.cfg存储后端定义(LVM / ZFS / NFS / Ceph RBD 等)
/etc/pve/firewall/集群防火墙规则
/etc/pve/user.cfg用户账户
/etc/pve/acl.cfg访问控制列表
/etc/pve/auth/认证后端(LDAP / TFA)
/etc/pve/qemu-server/VM 配置(100.conf 对应 VMID 100)
/etc/pve/lxc/LXC 容器配置

提醒:不要直接删这些文件,请通过 pvecm / pct / qm 或者 Web UI 改,否则集群状态会乱。


监控与美化

指标服务器(InfluxDB + Grafana)

PVE 自带了往外推 metrics 的能力,配合 InfluxDB + Grafana 一套下来就是 “你在视频里看到过的那种监控大屏”。

我家的 InfluxDB 跑在一台固定节点的 LXC 里(192.168.31.3:8086),在 PVE 数据中心 → 指标服务器 里添加一个 InfluxDB 2.x 类型的服务器:

1
2
3
4
5
6
名称:     proxmox
服务器: 192.168.31.3
端口: 8086
Token: <你自己 InfluxDB 里生成的 API Token>
组织: proxmox
存储桶: proxmox

⚠️ 这个 Token 等于 InfluxDB 的写入凭证,千万别贴到博客或者 GitHub 上。我之前草稿里写了真实 token,差点就原样发出来了……

搭配 Grafana 里现成的 PVE Dashboard 导入进去,CPU / 内存 / 磁盘 / Ceph 一应俱全。

主题美化 —— PVEDiscordDark

PVE 原生深色模式跟没有一样,推荐 PVEDiscordDark,一条命令装好:

1
bash <(curl -s https://raw.githubusercontent.com/Weilbyte/PVEDiscordDark/master/PVEDiscordDark.sh) install

升级 PVE 之后主题会被覆盖,重新跑一次 install 即可。

更多一键脚本

两个仓库值得收藏:


最后聊两句

这套笔记是真的”边踩坑边记”攒起来的,翻到这里你应该已经看得出来:

  • Ceph 是我花时间最多的部分,也是最值得一遍遍回看的部分;
  • LXC 坑不大但很隐蔽,基本都在”权限映射”这一个关键词上;
  • 仲裁 是那种不出事就没存在感、出一次事就怀疑人生的东西,宁可提前花半小时加 qdevice

如果你也在搞 PVE + Ceph Homelab,希望这篇能直接省下你至少一晚的 Google 时间。以后遇到新的奇葩问题我也会接着往里面加,有需要可以收藏一下。


参考