2705 lines
132 KiB
Markdown
2705 lines
132 KiB
Markdown
> 基于 Ceph 官方文档整理 | 文档版本:**Tentacle v20.2.4** | 抓取日期:2026-09-17 | 源文:ceph/ceph@v20.2.4 `doc/rados/troubleshooting/`
|
||
> 官网入口:https://docs.ceph.com/en/tentacle/rados/troubleshooting/
|
||
|
||
# 第 04 册 · RADOS 排障篇
|
||
|
||
---
|
||
|
||
## 本册导读
|
||
|
||
本册覆盖 Ceph RADOS 子系统**官方排障文档的全部 8 个页面**,交付一份"照着做就能排障"的中文手册。
|
||
|
||
### 覆盖页面清单
|
||
|
||
| # | 页面(rst 文件名) | 官方页面标题 | 官网 URL | 本册对应章节 |
|
||
|---|---|---|---|---|
|
||
| 1 | `index.rst` | Troubleshooting | https://docs.ceph.com/en/tentacle/rados/troubleshooting/ | 本册导读 |
|
||
| 2 | `log-and-debug.rst` | Logging and Debugging | https://docs.ceph.com/en/tentacle/rados/troubleshooting/log-and-debug/ | 第二章 |
|
||
| 3 | `troubleshooting-mon.rst` | Troubleshooting Monitors | https://docs.ceph.com/en/tentacle/rados/troubleshooting/troubleshooting-mon/ | 第三章 |
|
||
| 4 | `troubleshooting-osd.rst` | Troubleshooting OSDs | https://docs.ceph.com/en/tentacle/rados/troubleshooting/troubleshooting-osd/ | 第四章 |
|
||
| 5 | `troubleshooting-pg.rst` | Troubleshooting PGs | https://docs.ceph.com/en/tentacle/rados/troubleshooting/troubleshooting-pg/ | 第五章 |
|
||
| 6 | `cpu-profiling.rst` | CPU Profiling | https://docs.ceph.com/en/tentacle/rados/troubleshooting/cpu-profiling/ | 第六章 |
|
||
| 7 | `memory-profiling.rst` | Memory Profiling | https://docs.ceph.com/en/tentacle/rados/troubleshooting/memory-profiling/ | 第七章 |
|
||
| 8 | `community.rst` | The Ceph Community | https://docs.ceph.com/en/tentacle/rados/troubleshooting/community/ | 第八章 |
|
||
|
||
官方 `index.rst` 对排障篇的定位原文:你可能会遇到需要**检查配置(configuration)、查阅文档、调整日志输出、排查 monitor 与 OSD、剖析内存与 CPU 占用,以及在最后手段下向 Ceph 社区求助**的情况。本册即按这条主线组织。
|
||
|
||
### 排障方法论总览:先看什么 → 再查什么 → 如何定位
|
||
|
||
官方在多处反复强调的一条**铁律**:**先 MON,后网络,再 OSD,最后 PG**。`troubleshooting-osd.rst` 开篇即写明——在排查 OSD 之前,先检查 monitor 与网络:先确认 monitor 是否有 quorum(`ceph health` / `ceph -s` 显示 `HEALTH_OK`),如果 monitor 没有 quorum 或状态有错误,**必须先解决 monitor 问题再往下走**;然后再检查网络(主机侧看丢包,交换机侧看 CRC 错误)。
|
||
|
||
```text
|
||
┌───────────────────────────────────────────────────────────────────┐
|
||
│ 第 0 步 现象确认 │
|
||
│ ceph health detail ← 拿第一手告警原文(含 PG/OSD/MON 状态) │
|
||
└───────────────────────────┬───────────────────────────────────────┘
|
||
▼
|
||
┌───────────────────────────────────────────────────────────────────┐
|
||
│ 第 1 步 MON 层(最高优先级) │
|
||
│ · ceph-mon / ceph-mgr 进程是否在跑 │
|
||
│ · 能否连上 MON 节点(tcp/3300、tcp/6789;iptables / nf_conntrack) │
|
||
│ · ceph status 有没有回包 → 有回包说明 quorum 已形成 │
|
||
│ · 逐个 MON 问:ceph tell mon.ID mon_status │
|
||
│ 任一环节不通 → 进入第三章,不要再往下查 OSD/PG │
|
||
└───────────────────────────┬───────────────────────────────────────┘
|
||
▼
|
||
┌───────────────────────────────────────────────────────────────────┐
|
||
│ 第 2 步 网络层 │
|
||
│ netstat -a/-l/-p | grep ceph;netstat -s;dmesg | grep scsi │
|
||
│ 主机侧丢包 / 交换机侧 CRC 错误 / private 网络降级(→ flapping) │
|
||
└───────────────────────────┬───────────────────────────────────────┘
|
||
▼
|
||
┌───────────────────────────────────────────────────────────────────┐
|
||
│ 第 3 步 OSD 层 │
|
||
│ ls /var/log/ceph → 日志;ceph daemon osd.N help → admin socket │
|
||
│ df -h(空间);iostat -x(I/O);ceph osd df / ceph df(容量分布) │
|
||
│ ceph osd tree down(谁 down 了);dmesg / smartctl(硬盘) │
|
||
│ → 第四章:起不来 / down / 满 / 慢请求 / flapping │
|
||
└───────────────────────────┬───────────────────────────────────────┘
|
||
▼
|
||
┌───────────────────────────────────────────────────────────────────┐
|
||
│ 第 4 步 PG 层 │
|
||
│ ceph pg dump_stuck stale / inactive / unclean → 先分大类 │
|
||
│ ceph pg <id> query → 看 recovery_state,定位被谁 block │
|
||
│ rados list-inconsistent-pg <pool> → 不一致 PG │
|
||
│ → 第五章:unfound / incomplete / stale / inconsistent / down │
|
||
└───────────────────────────┬───────────────────────────────────────┘
|
||
▼
|
||
┌───────────────────────────────────────────────────────────────────┐
|
||
│ 第 5 步 深挖:调日志级别 → 复现 → 剖析 → 求助 │
|
||
│ debug_*/ 动态调级(第二章)→ CPU/内存剖析(第六、七章) │
|
||
│ → 备好日志向社区求助(第八章) │
|
||
└───────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
> 💡 补充:官方原文并未给出上面这张"五级流水线"图,它是把 `troubleshooting-osd.rst` 开篇的 "check the monitors and the network first"、`troubleshooting-mon.rst` 的 "Initial Troubleshooting" 四步、以及 `troubleshooting-pg.rst` 的 `dump_stuck` 三分类,按依赖顺序串起来的一套**可执行**流程。每一步的具体命令均出自源文档,没有新增未经官方文档确认的操作。
|
||
|
||
### 三条贯穿全篇的排戒律
|
||
|
||
1. **不要迷信 `ceph df` 的"整体使用率"**——`troubleshooting-osd.rst` 明确:看 `ceph df` 输出时要特别关注**最满的那个 OSD**,而不是 raw 空间使用百分比。只要池子里有一个 OSD 满了,写到这个池的所有 I/O 都可能失败;`ceph df` 报可用空间时是按"池中最满 OSD"算的。
|
||
2. **调日志是有代价的,用完一定要调回去**——`log-and-debug.rst` 警告:冗余日志有时**每小时产生超过 1 GB** 数据,OS 盘写满会导致该节点停止工作;而且 debug 输出会拖慢系统,这种延迟**有时会掩盖住竞态条件(race conditions)**。系统恢复正常后必须把多余的调试设置删掉。
|
||
3. **破坏性命令只在"确认无其他副本"时用**——`ceph osd lost`、`ceph pg <id> mark_unfound_lost delete`、删 PG 目录、`ceph-mon --inject-monmap`,官方文档都给了警告,本册在对应位置原样保留。
|
||
|
||
---
|
||
|
||
## 一、排障总纲:第一手信息与常用定位命令
|
||
|
||
### 1.1 日志位置与获取方式
|
||
|
||
| 类别 | 位置 / 命令 | 说明 |
|
||
|---|---|---|
|
||
| 通用日志目录 | `/var/log/ceph` | Ceph 日志文件默认存放于此;**容器化部署(containerized deployments)通常记在 `/var/log` 下的其他位置** |
|
||
| 列出日志文件 | `ls /var/log/ceph` | 路径被改过或在容器里就不一定在这个目录 |
|
||
| MON 日志 | `/var/log/ceph/ceph-mon.FOO.log*` | `FOO` 为 MON ID |
|
||
| MON 日志真实路径查询 | `ceph-conf --name mon.FOO --show-config-value log_file` | 当默认位置被改过时用它定位 |
|
||
| OSD 日志命名 | `/var/log/ceph/$cluster-$name.log` | `troubleshooting-osd.rst` 举例:长期开高日志级别时,建议把一块盘挂到这个默认日志路径上 |
|
||
| heap profile 输出 | `/var/log/ceph/` | tcmalloc heap profiler 的 dump 落在日志目录,形如 `ceph-osd.0.profile.0001.heap` |
|
||
| admin socket 目录 | `/var/run/ceph` | 用 `ls /var/run/ceph` 列出各守护进程的 socket |
|
||
| MON admin socket | `/var/run/ceph/ceph-mon.ID.asok` | 默认值可被覆盖;容器里常常不在默认位置 |
|
||
| MON admin socket 路径查询 | `ceph-conf --name mon.ID --show-config-value admin_socket` | 或者查 `ceph.conf` 里的替代路径 |
|
||
|
||
获取日志的三种途径(按可用性降序):
|
||
|
||
```bash
|
||
# 1) 直接读文件(最常用)
|
||
ls /var/log/ceph
|
||
|
||
# 2) 通过 admin socket 拿运行时信息(守护进程必须活着)
|
||
ls /var/run/ceph
|
||
ceph daemon {daemon-name} help # 例如 ceph daemon osd.0 help
|
||
ceph daemon {socket-file} help # 也可直接给 /var/run/ceph 下的 socket 文件
|
||
|
||
# 3) 用 ceph-conf 反查被改过的路径
|
||
ceph-conf --name mon.FOO --show-config-value log_file
|
||
ceph-conf --name mon.ID --show-config-value admin_socket
|
||
```
|
||
|
||
admin socket 能干的事情(`troubleshooting-osd.rst` 列举):
|
||
|
||
- Listing Ceph configuration at runtime —— 运行时列出 Ceph 配置
|
||
- Dumping historic operations —— 导出历史操作
|
||
- Dumping the operation priority queue state —— 导出操作优先级队列状态
|
||
- Dumping operations in flight —— 导出正在飞行中的操作
|
||
- Dumping perfcounters —— 导出性能计数器
|
||
|
||
### 1.2 `ceph health detail`:排障的第一条命令
|
||
|
||
几乎所有排障场景,官方文档的第一句话都是"跑 `ceph health detail`"。`ceph health` 只给一行结论,`detail` 才给出**哪个 OSD / 哪个 PG / 哪个 MON** 出问题。
|
||
|
||
```bash
|
||
ceph health
|
||
# HEALTH_WARN 1/3 in osds are down
|
||
|
||
ceph health detail
|
||
# HEALTH_WARN 1/3 in osds are down
|
||
# osd.0 is down since epoch 23, last address 192.168.106.220:6800/11080
|
||
```
|
||
|
||
`health detail` 在本册各场景下的典型输出形态(后面章节会逐一展开):
|
||
|
||
| 场景 | `ceph health detail` 输出样例 |
|
||
|---|---|
|
||
| MON down | `mon.a (rank 0) addr 127.0.0.1:6789/0 is down (out of quorum)` |
|
||
| 时钟偏移 | `mon.c addr 10.10.0.1:6789/0 clock skew 0.08235s > max 0.05s (latency 0.0045s)` |
|
||
| OSD full | `HEALTH_ERR 1 full osd(s); 1 backfillfull osd(s); 1 nearfull osd(s)` + `osd.3 is full at 97%` |
|
||
| OSD nearfull | `HEALTH_WARN 1 nearfull osd(s)` |
|
||
| PG peering 失败 | `HEALTH_ERR 7 pgs degraded; 12 pgs down; 12 pgs peering; ...` + `pg 0.5 is down+peering` |
|
||
| PG unfound | `HEALTH_WARN 1 pgs degraded; 78/3778 unfound (2.065%)` + `pg 2.4 is active+degraded, 78 unfound` |
|
||
| PG stale | `HEALTH_WARN 24 pgs stale; 3/300 in osds are down` + `pg 2.5 is stuck stale+active+remapped, last acting [2,0]` |
|
||
| PG inconsistent | `HEALTH_ERR 1 pgs inconsistent; 2 scrub errors` + `pg 0.6 is active+clean+inconsistent, acting [0,1,2]` |
|
||
|
||
### 1.3 `ceph -w`
|
||
|
||
> ⚠️ **文档未明确**:`ceph -w`(watch,持续观察集群事件流)这一命令**未出现在本册源文(`rados/troubleshooting/` 全部 8 个 rst)中**,官方文档在此处没有给出它的用法、输出格式或退出方式。
|
||
>
|
||
> 💡 补充:之所以在排障方法论里值得一用,是因为本册反复出现的 `slow requests`、`flapping`、`synchronizing` 这类现象都是**随时间变化**的,单次快照容易漏掉;持续观察能抓到"MON 状态在 `synchronizing` ↔ `electing` 之间来回跳"、"OSD 被反复 mark down 又 up"这类只有连续采样才看得见的模式。该命令属于 Ceph 通用管理命令,此处仅作提示,**具体行为请以所用版本的 `ceph --help` / 官方 man page 为准**。
|
||
|
||
### 1.4 常用定位命令清单
|
||
|
||
下表中所有命令均出自本册源文档,按用途归类。
|
||
|
||
| 命令 | 用途 |
|
||
|---|---|
|
||
| **—— 集群整体 ——** | |
|
||
| `ceph health` | 一行式健康结论 |
|
||
| `ceph health detail` | 带对象明细的健康输出(排障第一命令) |
|
||
| `ceph status` / `ceph -s` | 集群状态;**MON 只在 quorum 已形成时才应答 status 请求** |
|
||
| `ceph status -m mymon1` | 用 `-m` 显式指定连接某个 MON(默认 `ceph -s` 是随便挑一个 MON 连) |
|
||
| `ceph report` | 生成一份完整集群报告,**向社区求助时建议附上**(见第八章) |
|
||
| `ceph time-sync-status` | Luminous 及以后:检查时钟偏移 |
|
||
| `ceph df` | 集群整体数据用量与池间分布;**关注最满的 OSD** |
|
||
| `ceph osd df` | 逐个 OSD 展示 fullness(数据占用) |
|
||
| **—— MON ——** | |
|
||
| `ceph tell mon.ID mon_status` | 逐个问单个 MON 的状态(即使没有 quorum 也能问) |
|
||
| `ceph tell mon.<id> mon_status` | 同上 |
|
||
| `ceph --admin-daemon <full_path_to_asok_file> <command>` | 知道 asok 全路径时,绕过 MON 直接打 admin socket |
|
||
| `ceph daemon mon.FOO config show` | 查看该 MON 当前全部配置值 |
|
||
| `ceph daemon mon.FOO config get 'OPTION_NAME'` | 查看该 MON 某个配置项的当前值 |
|
||
| `ceph daemon mon.FOO config set debug_mon 10/10` | 无 quorum 时通过 admin socket 直接改配置 |
|
||
| `ceph tell mon.FOO config set debug_mon 10/10` | 有 quorum 时给**指定** MON 注入配置 |
|
||
| `ceph tell mon.* config set debug_mon 10/10` | 有 quorum 时给**所有** MON 注入配置 |
|
||
| `ceph mon getmap -o /tmp/monmap` | 有 quorum 时:从 quorum 取 monmap |
|
||
| `ceph-mon -i ID-FOO --extract-monmap /tmp/monmap` | 无 quorum 时:从已停止的 MON 直接抽取 monmap |
|
||
| `ceph-mon -i ID --inject-monmap /tmp/monmap` | 把 monmap 注入已停止的 MON(**危险,见第三章**) |
|
||
| `ceph-conf --name mon.ID --show-config-value admin_socket` | 查 MON admin socket 路径 |
|
||
| `ceph-conf --name mon.FOO --show-config-value log_file` | 查 MON 日志文件路径 |
|
||
| **—— OSD ——** | |
|
||
| `ls /var/log/ceph` | 列出 Ceph 日志文件 |
|
||
| `ls /var/run/ceph` | 列出守护进程 admin socket |
|
||
| `ceph daemon {daemon-name} help` | 列出该守护进程支持的 admin socket 命令 |
|
||
| `ceph daemon {daemon-name} config show \| less` | 运行时查看配置(如 `ceph daemon osd.0 config show`) |
|
||
| `ceph tell {daemon-type}.{daemon id or *} config set {name} {value}` | 运行时注入调试配置(如 `ceph tell osd.0 config set debug_osd 0/5`) |
|
||
| `ceph osd tree` | 看 OSD 拓扑(monitoring OSDs 常用手段) |
|
||
| `ceph osd tree down` | 只看处于 `down` 的 OSD |
|
||
| `ceph osd df` | 各 OSD fullness |
|
||
| `ceph osd dump \| grep flags` | 查看 osdmap 上的 flag(如 `flags no-up,no-down`) |
|
||
| `ceph osd dump \| grep pool` | 列出池编号(配合 PG ID `N.xxxxx` 使用) |
|
||
| `ceph osd listpools` | 列出池编号 |
|
||
| `ceph osd set noout` / `ceph osd unset noout` | 全局 noout(**官方不推荐在 post-Luminous 使用**) |
|
||
| `ceph osd add-noout osd.0` / `ceph osd rm-noout osd.0` | 给**单个** OSD 加/去 noout(Luminous+ 更安全的做法) |
|
||
| `ceph osd set-group noout prod-ceph-data1701` | 给整个 CRUSH bucket 加 noout |
|
||
| `ceph osd unset-group noout prod-ceph-data1701` | 给整个 CRUSH bucket 去 noout |
|
||
| `ceph osd set noup` / `ceph osd unset noup` | 阻止 OSD 被 mark up(治 flapping) |
|
||
| `ceph osd set nodown` / `ceph osd unset nodown` | 阻止 OSD 被 mark down(治 flapping) |
|
||
| `ceph osd set-nearfull-ratio <float[0.0-1.0]>` | 调 nearfull 阈值 |
|
||
| `ceph osd set-full-ratio <float[0.0-1.0]>` | 调 full 阈值 |
|
||
| `ceph osd set-backfillfull-ratio <float[0.0-1.0]>` | 调 backfillfull 阈值 |
|
||
| `ceph osd lost 1` | 告知集群 `osd.1` 已 lost,让它尽力恢复(**危险**) |
|
||
| `ceph osd force-create-pg <pgid>` | 强制创建(空)PG |
|
||
| `ceph osd crush rule ls` | 列出 CRUSH rule |
|
||
| `ceph osd crush rule dump erasurepool` | dump 某条 CRUSH rule(看约束) |
|
||
| `ceph osd getcrushmap > crush.map` | 导出 CRUSH map 供离线实验 |
|
||
| `ceph osd erasure-code-profile set myprofile k=5 m=3` | 建更低要求的 EC profile |
|
||
| `ceph osd erasure-code-profile set myprofile crush-failure-domain=osd` | 允许同一 host 上的 OSD 进同一 PG |
|
||
| `ceph daemon osd.<id> dump_historic_ops` | 导出历史 op + 每个 op 经历的事件(**调 slow request 核心**) |
|
||
| `ceph daemon osd.<id> dump_ops_in_flight` | 导出正在飞行中的 op |
|
||
| `ceph config set osd osd_op_num_shards_hdd 1` | 改 HDD OSD shard 数(治 mClock 慢请求,**需重启 OSD**) |
|
||
| `ceph config set osd osd_op_num_threads_per_shard_hdd 5` | 改每 shard 线程数(同上) |
|
||
| **—— PG ——** | |
|
||
| `ceph pg dump_stuck stale` | 列卡在 stale 的 PG |
|
||
| `ceph pg dump_stuck inactive` | 列卡在 inactive 的 PG |
|
||
| `ceph pg dump_stuck unclean` | 列卡在 unclean 的 PG |
|
||
| `ceph pg 0.5 query` | 查 PG 卡住的原因(看 `recovery_state`) |
|
||
| `ceph pg 2.4 list_unfound [starting offset, in json]` | 列出 unfound 对象 |
|
||
| `ceph pg 2.5 mark_unfound_lost revert\|delete` | 把 unfound 对象标记为 lost(**危险**) |
|
||
| `ceph pg repair {placement-group-ID}` | 修复 inconsistent PG(如 `ceph pg repair 1.4`) |
|
||
| `rados list-inconsistent-pg rbd` | 列出 rbd 池里 inconsistent 的 PG |
|
||
| `rados list-inconsistent-obj 0.6 --format=json-pretty` | 列出某 PG 内 inconsistent 的对象明细 |
|
||
| **—— 主机/系统层 ——** | |
|
||
| `df -h` | 文件系统剩余空间 |
|
||
| `iostat -x` | 识别 I/O 相关问题 |
|
||
| `dmesg \| grep scsi` | 内核诊断消息(磁盘/驱动错误) |
|
||
| `dmesg -T` | 人类可读时间戳(避免把旧错误当新错误) |
|
||
| `netstat -a \| grep ceph` | Ceph 相关连接 |
|
||
| `netstat -l \| grep ceph` | Ceph 监听端口 |
|
||
| `sudo netstat -p \| grep ceph` | Ceph 相关进程 |
|
||
| `netstat -s` | 网络统计 |
|
||
| `smartctl` | 硬盘健康(来自 `smartmontools` 包) |
|
||
| `sysctl -w kernel.pid_max=4194303` | 临时提高最大线程数 |
|
||
| **—— 剖析 ——** | |
|
||
| `ceph tell {daemon-type}.{daemon-id} heap start_profiler` | 启动 tcmalloc heap profiler |
|
||
| `ceph tell {daemon-type}.{daemon-id} heap stats` | 打印 heap 统计(**不需要 profiler 在跑**,也不落文件) |
|
||
| `ceph tell {daemon-type}.{daemon-id} heap dump` | dump heap(**必须 profiler 在跑**) |
|
||
| `ceph tell {daemon-type}{daemon-id} heap release` | 释放 tcmalloc 已分配但没用到的内存 |
|
||
| `ceph tell {daemon-type}.{daemon-id} heap stop_profiler` | 停止 heap profiler |
|
||
| `google-pprof --text {path-to-daemon} {log-path/filename}` | 查看 profiler 日志 |
|
||
| `opcontrol --start` / `--stop` / `--reset` | oprofile 启停与重置 |
|
||
| `opreport -gal ./cmon \| less` | 取 top cmon 结果 |
|
||
| `opreport -cal ./cmon \| less` | 取 top cmon 结果(带 call graph) |
|
||
| **—— CRUSH 离线实验 ——** | |
|
||
| `crushtool --decompile crush.map > crush.txt` | 反编译 CRUSH map |
|
||
| `crushtool --compile crush.txt -o better-crush.map` | 重新编译 CRUSH map |
|
||
| `crushtool -i crush.map --test --show-bad-mappings --rule 1 --num-rep 9 --min-x 1 --max-x $((1024 * 1024))` | 离线测映射(**不改集群**) |
|
||
| `crushtool -i better-crush.map --test --show-bad-mappings --show-choose-tries --rule 1 --num-rep 9 --min-x 1 --max-x $((1024 * 1024))` | 同上,附尝试次数直方图 |
|
||
|
||
> 引自:[Troubleshooting](https://docs.ceph.com/en/tentacle/rados/troubleshooting/)、[Troubleshooting Monitors](https://docs.ceph.com/en/tentacle/rados/troubleshooting/troubleshooting-mon/)、[Troubleshooting OSDs](https://docs.ceph.com/en/tentacle/rados/troubleshooting/troubleshooting-osd/)、[Troubleshooting PGs](https://docs.ceph.com/en/tentacle/rados/troubleshooting/troubleshooting-pg/)
|
||
|
||
---
|
||
|
||
## 二、日志与调试(log-and-debug.rst)
|
||
|
||
### 2.1 核心原则
|
||
|
||
`log-and-debug.rst` 开篇就讲了这套机制:**Ceph 各组件的 debug 日志级别可以在运行时(at runtime)调整,服务不用停**。某些情况下你也可能需要把级别写进 `ceph.conf` 或中央配置库(central config store)。
|
||
|
||
三条必须记住的警告:
|
||
|
||
| 警告 | 原文要点 |
|
||
|---|---|
|
||
| 延迟会掩盖 bug | 记住 debug 输出会拖慢系统,**这种延迟有时会掩盖住竞态条件(race conditions)** |
|
||
| 日志能撑爆 OS 盘 | **冗余日志有时每小时产生超过 1 GB 数据**;如果操作系统所在磁盘("OS disk")被写满,**该节点会停止工作** |
|
||
| 用完要还原 | 系统恢复正常后,**删掉不必要的调试设置**,确保集群以最优状态运行;记录 debug 输出是慢操作,且浪费集群资源 |
|
||
|
||
**按需、分组件开启**的做法:debug 日志是资源密集型(resource intensive)的。如果问题出在集群某个特定组件,**只给那个组件开日志**。举例:OSD 跑得好好的、只有 CephFS 的 MDS 有问题,那就只给有问题的那几个具体实例开;然后**按需要逐个 subsystem 逐步开**。
|
||
|
||
### 2.2 日志级别模型:`log level` / `memory level`
|
||
|
||
每个 subsystem 有**两个**可独立设置的级别:
|
||
|
||
- **log level**:输出到日志文件(output logs)的级别
|
||
- **memory level**:留在内存日志(in-memory logs)中的级别
|
||
|
||
取值范围 `1` ~ `20`,`1` 最简略(terse),`20` 最详尽(verbose)。在少数情况下级别可以取**大于 20** 的值,产生的日志**极其详尽(extremely verbose)**。
|
||
|
||
**内存日志何时会被送到输出日志?** 只有以下三种情况之一成立:
|
||
|
||
1. 触发了 fatal signal(致命信号),或
|
||
2. Ceph 代码里的 assertion 被触发,或
|
||
3. **手动触发**了把内存日志送去输出日志(即通过 admin socket 提交命令,参见 Ceph Administration Tool 文档中提交 admin socket 命令的示例)
|
||
|
||
**写法规则**:
|
||
|
||
```ini
|
||
debug {subsystem} = {log-level}/{memory-level}
|
||
#for example
|
||
debug mds balancer = 1/20
|
||
```
|
||
|
||
| 写法 | 效果 |
|
||
|---|---|
|
||
| `debug ms = 5` | 单个值 → log level = `5`,memory level = `5` |
|
||
| `debug ms = 1/5` | 斜杠分隔 → log level = `1`,memory level = `5` |
|
||
|
||
完成排查后,要把各 subsystem 的值**还原为默认值**,或还原到适合日常运行的级别。
|
||
|
||
### 2.3 全量子系统默认级别表
|
||
|
||
下表**逐项**照抄官方 `log-and-debug.rst` 的 subsystem 表(Log Level = 输出日志级别,Memory Level = 内存日志级别)。
|
||
|
||
| Subsystem | Log Level | Memory Level |
|
||
|---|---|---|
|
||
| `default` | 0 | 5 |
|
||
| `lockdep` | 0 | 1 |
|
||
| `context` | 0 | 1 |
|
||
| `crush` | 1 | 1 |
|
||
| `mds` | 1 | 5 |
|
||
| `mds balancer` | 1 | 5 |
|
||
| `mds log` | 1 | 5 |
|
||
| `mds log expire` | 1 | 5 |
|
||
| `mds migrator` | 1 | 5 |
|
||
| `buffer` | 0 | 1 |
|
||
| `timer` | 0 | 1 |
|
||
| `filer` | 0 | 1 |
|
||
| `striper` | 0 | 1 |
|
||
| `objecter` | 0 | 1 |
|
||
| `rados` | 0 | 5 |
|
||
| `rbd` | 0 | 5 |
|
||
| `rbd mirror` | 0 | 5 |
|
||
| `rbd replay` | 0 | 5 |
|
||
| `rbd pwl` | 0 | 5 |
|
||
| `journaler` | 0 | 5 |
|
||
| `objectcacher` | 0 | 5 |
|
||
| `immutable obj cache` | 0 | 5 |
|
||
| `client` | 0 | 5 |
|
||
| `osd` | 1 | 5 |
|
||
| `optracker` | 0 | 5 |
|
||
| `objclass` | 0 | 5 |
|
||
| `filestore` | 1 | 3 |
|
||
| `journal` | 1 | 3 |
|
||
| `ms` | 0 | 5 |
|
||
| `mon` | 1 | 5 |
|
||
| `monc` | 0 | 10 |
|
||
| `paxos` | 1 | 5 |
|
||
| `tp` | 0 | 5 |
|
||
| `auth` | 1 | 5 |
|
||
| `crypto` | 1 | 5 |
|
||
| `finisher` | 1 | 1 |
|
||
| `reserver` | 1 | 1 |
|
||
| `heartbeatmap` | 1 | 5 |
|
||
| `perfcounter` | 1 | 5 |
|
||
| `rgw` | 1 | 5 |
|
||
| `rgw sync` | 1 | 5 |
|
||
| `rgw datacache` | 1 | 5 |
|
||
| `rgw access` | 1 | 5 |
|
||
| `rgw dbstore` | 1 | 5 |
|
||
| `rgw lifecycle` | 1 | 5 |
|
||
| `rgw notification` | 1 | 5 |
|
||
| `rgw bucket logging` | 1 | 5 |
|
||
| `javaclient` | 1 | 5 |
|
||
| `asok` | 1 | 5 |
|
||
| `throttle` | 1 | 1 |
|
||
| `refs` | 0 | 0 |
|
||
| `compressor` | 1 | 5 |
|
||
| `bluestore` | 1 | 5 |
|
||
| `bluefs` | 1 | 5 |
|
||
| `bdev` | 1 | 3 |
|
||
| `kstore` | 1 | 5 |
|
||
| `rocksdb` | 4 | 5 |
|
||
| `fuse` | 1 | 5 |
|
||
| `mgr` | 2 | 5 |
|
||
| `mgrc` | 1 | 5 |
|
||
| `dpdk` | 1 | 5 |
|
||
| `eventtrace` | 1 | 5 |
|
||
| `prioritycache` | 1 | 5 |
|
||
| `test` | 0 | 5 |
|
||
| `cephfs mirror` | 0 | 5 |
|
||
| `cephsqlite` | 0 | 5 |
|
||
| `seastore` | 0 | 5 |
|
||
| `seastore onode` | 0 | 5 |
|
||
| `seastore odata` | 0 | 5 |
|
||
| `seastore ompap` | 0 | 5 |
|
||
| `seastore tm` | 0 | 5 |
|
||
| `seastore t` | 0 | 5 |
|
||
| `seastore cleaner` | 0 | 5 |
|
||
| `seastore epm` | 0 | 5 |
|
||
| `seastore lba` | 0 | 5 |
|
||
| `seastore fixedkv tree` | 0 | 5 |
|
||
| `seastore cache` | 0 | 5 |
|
||
| `seastore journal` | 0 | 5 |
|
||
| `seastore device` | 0 | 5 |
|
||
| `seastore backref` | 0 | 5 |
|
||
| `alienstore` | 0 | 5 |
|
||
| `mclock` | 1 | 5 |
|
||
| `cyanstore` | 0 | 5 |
|
||
| `ceph exporter` | 1 | 5 |
|
||
| `memstore` | 1 | 5 |
|
||
| `trace` | 1 | 5 |
|
||
|
||
> 💡 补充(读法提示):写进 `ceph.conf` 或 `ceph tell ... config set` 时,subsystem 名里的**空格要换成下划线**。官方给的两个例子是:"`debug mds balancer = 1/20`" 这种 ini 写法,以及 "`ceph tell osd.0 config set debug_osd 0/5`" 这种 config 写法——前者空格,后者下划线。
|
||
|
||
### 2.4 运行时动态调整(三种方式)
|
||
|
||
#### 方式一:`ceph tell`(走 MON,最常用)
|
||
|
||
```bash
|
||
ceph tell {daemon-type}.{daemon id or *} config set {name} {value}
|
||
```
|
||
|
||
- `{daemon-type}` 取 `osd`、`mon` 或 `mds`
|
||
- 指定具体 ID → 只作用于那一个守护进程;用 `*` 通配 → 作用于该类型的全部守护进程
|
||
|
||
```bash
|
||
# 只给 osd.0 开 debug
|
||
ceph tell osd.0 config set debug_osd 0/5
|
||
|
||
# 给所有 mon 开 debug(MON 排障常用)
|
||
ceph tell mon.* config set debug_mon 10/10
|
||
|
||
# 给指定 mon 开
|
||
ceph tell mon.FOO config set debug_mon 10/10
|
||
```
|
||
|
||
#### 方式二:`ceph daemon`(连不上 MON 时的备选)
|
||
|
||
> 如果**无法 bind 到 monitor**,`ceph tell` 走不通,就登录到目标守护进程所在主机,用 `ceph daemon` 直接改那个守护进程的配置。
|
||
|
||
```bash
|
||
sudo ceph daemon osd.0 config set debug_osd 0/5
|
||
```
|
||
|
||
> 这正是 `troubleshooting-mon.rst` 里"**无 quorum 时调整 debug 级别**"用的同一种手法:`ceph daemon mon.FOO config set debug_mon 10/10`。
|
||
|
||
#### 方式三:查看当前配置(改之前/之后都该看看)
|
||
|
||
```bash
|
||
# 登录有守护进程运行的主机
|
||
ceph daemon {daemon-name} config show | less
|
||
# 例
|
||
ceph daemon osd.0 config show | less
|
||
```
|
||
|
||
MON 侧等价命令:
|
||
|
||
```bash
|
||
ceph daemon mon.FOO config show
|
||
ceph daemon mon.FOO config get 'OPTION_NAME'
|
||
```
|
||
|
||
#### 什么时候需要重启?
|
||
|
||
**不需要。** `troubleshooting-mon.rst` 明确回答:"Do I need to restart a monitor to adjust debug levels? — No. It is not necessary to restart a Monitor when adjusting its debug levels."
|
||
|
||
还原默认值的做法:把上面的命令里的 `10/10` 换成 `1/10` 再跑一遍(官方原文即此写法)。
|
||
|
||
#### 三种运行时方式对照表
|
||
|
||
| 方式 | 命令形态 | 是否需要 MON 可达 | 作用范围 |
|
||
|---|---|---|---|
|
||
| `ceph tell` | `ceph tell osd.0 config set debug_osd 0/5` | **需要**(命令走 MON) | 单实例 / `*` 全类型 |
|
||
| `ceph daemon` | `sudo ceph daemon osd.0 config set debug_osd 0/5` | 不需要(本地 asok) | 单实例 |
|
||
| 启动前配置 | 写 `ceph.conf` 或中央配置库 | —— | 需重启/重载生效 |
|
||
|
||
### 2.5 开机时(Boot Time)设置
|
||
|
||
要在**启动时**就激活 debug 输出,必须把设置写进 Ceph 配置文件(或在中央配置库里设对应值):
|
||
|
||
- 所有守护进程通用的 subsystem → 写在 `[global]` 段
|
||
- 特定守护进程的 subsystem → 写在对应段(`[mon]`、`[osd]`、`[mds]`)
|
||
|
||
官方给的示例配置:
|
||
|
||
```ini
|
||
[global]
|
||
debug_ms = 1/5
|
||
|
||
[mon]
|
||
debug_mon = 20
|
||
debug_paxos = 1/5
|
||
debug_auth = 2
|
||
|
||
[osd]
|
||
debug_osd = 1/5
|
||
debug_filestore = 1/5
|
||
debug_journal = 1
|
||
debug_monc = 5/20
|
||
|
||
[mds]
|
||
debug_mds = 1
|
||
debug_mds_balancer = 1
|
||
```
|
||
|
||
> 💡 补充:注意上面示例中 `[osd]` 段里的 `debug_monc = 5/20` —— `monc` 是客户端侧与 MON 通信的子系统,默认值是 `0/10`(见 2.3 表),这里被提到了 `5/20`,属于"OSD 与 MON 通信异常"时的典型排查手段。
|
||
|
||
### 2.6 日志轮转加速(Accelerating Log Rotation)
|
||
|
||
**场景**:主机的日志文件系统快满了。
|
||
|
||
**做法**:改 `/etc/logrotate.d/ceph`,在 `weekly` 频率指令**之后**加一条 `size` 指令,提高轮转频率以防文件系统被撑满。为削平日志量尖峰,可以考虑把 `weekly` 改成 `daily`、把 `rotate` 改成 `30`。
|
||
|
||
步骤:
|
||
|
||
1. 记下 `/etc/logrotate.d/ceph` 的默认设置:
|
||
|
||
```text
|
||
rotate 7
|
||
weekly
|
||
compress
|
||
sharedscripts
|
||
```
|
||
|
||
2. 加上 `size` 设置:
|
||
|
||
```text
|
||
rotate 7
|
||
weekly
|
||
size 500M
|
||
compress
|
||
sharedscripts
|
||
```
|
||
|
||
3. 打开当前用户的 crontab 编辑器:
|
||
|
||
```bash
|
||
crontab -e
|
||
```
|
||
|
||
4. 加一条 cron 条目,让 cron 去检查 `/etc/logrotate.d/ceph`:
|
||
|
||
```text
|
||
30 * * * * /usr/sbin/logrotate /etc/logrotate.d/ceph >/dev/null 2>&1
|
||
```
|
||
|
||
这样 `/etc/logrotate.d/ceph` 会被**每 30 分钟**检查一次(并可能轮转)。
|
||
|
||
| 项 | 官方默认值 / 建议值 |
|
||
|---|---|
|
||
| `rotate` | 默认 `7`,建议可改 `30` |
|
||
| 频率 | 默认 `weekly`,建议可改 `daily` |
|
||
| `size` | 官方示例中加的是 `size 500M` |
|
||
| 其他 | `compress`、`sharedscripts` |
|
||
| cron 周期 | 示例中为每 30 分钟 |
|
||
|
||
### 2.7 slow request 相关参数
|
||
|
||
> ⚠️ **文档未明确**:`log-and-debug.rst` 页面本身**没有**列出 slow request 相关参数(该页的 confval 清单里只有通用日志项、MON 集群日志项、`osd_debug_drop_ping_probability` / `osd_debug_drop_ping_duration`、`filestore_debug_omap_check`、MDS 与 RGW 若干项)。slow request 的阈值参数出现在 `troubleshooting-osd.rst`。下面按源文原样列出,**未出现在源文中的参数一律不臆造**。
|
||
|
||
| 名称 | 默认值 | 含义 |
|
||
|---|---|---|
|
||
| `osd_op_complaint_time` | `30` 秒 | 当 `ceph-osd` 响应请求过慢时,集群日志会收到报告"ops 耗时过长"的消息。**这个告警阈值默认为 30 秒,可通过 `osd_op_complaint_time` 配置** |
|
||
| `osd_op_num_shards_hdd` | `5` | mClock HDD 场景下的 OSD shard 数(mClock 排障章节给出的当前配置值)。调慢请求时改为 `1` |
|
||
| `osd_op_num_threads_per_shard_hdd` | `1` | 每 shard 的线程数(同上,当前配置值)。调慢请求时改为 `5` |
|
||
| `osd_debug_drop_ping_probability` | 文档未明确 | `log-and-debug.rst` OSD 段列出的调试项(用于丢弃 ping 的概率),本册源文未给默认值 |
|
||
| `osd_debug_drop_ping_duration` | 文档未明确 | `log-and-debug.rst` OSD 段列出的调试项(丢弃 ping 的持续时间),本册源文未给默认值 |
|
||
|
||
**slow request 的两种日志形态**(`troubleshooting-osd.rst` 原文):
|
||
|
||
Legacy 版本用 `old requests`:
|
||
|
||
```text
|
||
osd.0 192.168.106.220:6800/18813 312 : [WRN] old request osd_op(client.5099.0:790 fatty_26485_object789 [write 0~4096] 2.5e54f643) v4 received at 2012-03-06 15:42:56.054801 currently waiting for sub ops
|
||
```
|
||
|
||
较新版本用 `slow requests`:
|
||
|
||
```text
|
||
{date} {osd.num} [WRN] 1 slow requests, 1 included below; oldest blocked for > 30.005692 secs
|
||
{date} {osd.num} [WRN] slow request 30.005692 seconds old, received at {date-time}: osd_op(client.4240.0:8 benchmark_data_ceph-1_39426_object7 [write 0~4194304] 0.69848840) v4 currently waiting for subops from [610]
|
||
```
|
||
|
||
### 2.8 日志与调试设置项清单(confval)
|
||
|
||
以下均为 `log-and-debug.rst` 列出的设置项。官方原文只列名字,未在本页给出默认值/描述——**本册不对未给出内容做任何编造**。
|
||
|
||
**通用日志项**:
|
||
|
||
| 设置项 | 默认值 | 说明 |
|
||
|---|---|---|
|
||
| `log_file` | 文档未明确 | 日志文件路径 |
|
||
| `log_max_new` | 文档未明确 | 新日志上限 |
|
||
| `log_max_recent` | 文档未明确 | 最近日志条数上限 |
|
||
| `log_to_file` | 文档未明确 | 是否写日志文件 |
|
||
| `log_to_stderr` | 文档未明确 | 是否写 stderr |
|
||
| `err_to_stderr` | 文档未明确 | 错误是否写 stderr |
|
||
| `log_to_syslog` | 文档未明确 | 是否写 syslog |
|
||
| `err_to_syslog` | 文档未明确 | 错误是否写 syslog |
|
||
| `log_flush_on_exit` | 文档未明确 | 退出时是否 flush 日志 |
|
||
| `clog_to_monitors` | 文档未明确 | cluster log 是否发给 monitors |
|
||
| `clog_to_syslog` | 文档未明确 | cluster log 是否写 syslog |
|
||
|
||
**Monitors 集群日志项**:
|
||
|
||
| 设置项 | 默认值 | 说明 |
|
||
|---|---|---|
|
||
| `mon_cluster_log_level` | 文档未明确 | 集群日志级别 |
|
||
| `mon_cluster_log_file` | 文档未明确 | 集群日志文件 |
|
||
| `mon_cluster_log_to_file` | 文档未明确 | 集群日志是否写文件 |
|
||
| `mon_cluster_log_to_syslog` | 文档未明确 | 集群日志是否写 syslog |
|
||
| `mon_cluster_log_to_syslog_facility` | 文档未明确 | syslog facility |
|
||
| `mon_cluster_log_to_stderr` | 文档未明确 | 集群日志是否写 stderr |
|
||
| `mon_cluster_log_to_journald` | 文档未明确 | 集群日志是否写 journald |
|
||
| `mon_cluster_log_to_graylog` | 文档未明确 | 集群日志是否发 graylog |
|
||
| `mon_cluster_log_to_graylog_host` | 文档未明确 | graylog 主机 |
|
||
| `mon_cluster_log_to_graylog_port` | 文档未明确 | graylog 端口 |
|
||
| `mon_log_max` | 文档未明确 | MON 日志上限 |
|
||
| `mon_health_to_clog` | 文档未明确 | 健康状态是否进 cluster log |
|
||
| `mon_health_to_clog_interval` | 文档未明确 | 间隔 |
|
||
|
||
**Filestore**:`filestore_debug_omap_check`(默认值:文档未明确)
|
||
|
||
**MDS**:`mds_debug_scatterstat`、`mds_debug_frag`、`mds_debug_auth_pins`、`mds_debug_subtrees`(默认值:文档未明确)
|
||
|
||
**RADOS Gateway**:`rgw_log_nonexistent_bucket`、`rgw_log_object_name`、`rgw_log_object_name_utc`、`rgw_enable_ops_log`、`rgw_enable_usage_log`、`rgw_usage_log_flush_threshold`、`rgw_usage_log_tick_interval`(默认值:文档未明确)
|
||
|
||
### 2.9 Valgrind
|
||
|
||
调试集群性能时,可能需要追查**内存与线程问题**。`log-and-debug.rst` 说明:
|
||
|
||
- Valgrind 工具套件可用于检测**某个特定守护进程**、**某一类守护进程**、或**整个集群**的问题
|
||
- 因为 Valgrind **计算开销大(computationally expensive)**,只在开发或调试 Ceph 时使用;其他时候用会拖慢系统
|
||
- Valgrind 的消息输出到 `stderr`
|
||
|
||
### 2.10 debug 相关注意事项汇总
|
||
|
||
| 注意点 | 依据 |
|
||
|---|---|
|
||
| 开 debug 前先确认**日志空间充足** | 冗余日志 >1GB/小时,OS 盘满则该节点停止工作 |
|
||
| 调级**不需要重启**守护进程 | MON 篇明确"No" |
|
||
| **只开有问题的那个组件**,逐步按需扩展 | 官方推荐做法 |
|
||
| **问题定位完要还原**默认值 | "restore each subsystem's values to their defaults or to a level suitable for normal operations" |
|
||
| 长期高日志量建议**把一块盘挂到日志路径** | `/var/log/ceph/$cluster-$name.log` |
|
||
| 忘了降日志级别会拖慢 OSD | OSD 篇"Logging Levels"节:OSD 会把宝贵系统资源耗在写冗长日志上 |
|
||
| 高日志级别会影响故障复现 | debug 输出引入的延迟"sometimes hides race conditions" |
|
||
| MON 排障时推荐级别 | `debug_mon = 10`、`debug_ms = 1`(**求助前**建议先设到这个级别再复现) |
|
||
| 求助时不要一次甩几十万行 | 记下复现时刻的时间日期,据此截取日志片段 |
|
||
|
||
> 引自:[Logging and Debugging](https://docs.ceph.com/en/tentacle/rados/troubleshooting/log-and-debug/)
|
||
|
||
---
|
||
|
||
## 三、MON 排障(troubleshooting-mon.rst)
|
||
|
||
### 3.0 先建立一个正确预期
|
||
|
||
官方开篇:即便集群出现 monitor 相关问题,**集群也不一定会挂**。只要**存活的 monitor 数量足够形成 quorum**,集群就还能继续运行。
|
||
|
||
### 3.1 Initial Troubleshooting:四步排除法
|
||
|
||
#### 第 1 步:确认 Monitor 在跑
|
||
|
||
确认 `ceph-mon` 进程在跑。**有可能是升级之后还没重启 mon**——检查这个低级疏漏能省下数小时的排查。
|
||
|
||
同时**务必确认 `ceph-mgr` 守护进程也在跑**。典型配置是**每个 `ceph-mon` 配一个 `ceph-mgr`**。
|
||
|
||
> 注:v1.12.5 之前的 Rook 不会运行超过两个 manager。
|
||
|
||
#### 第 2 步:确认 Monitor 节点可达
|
||
|
||
某些罕见情况下,`iptables` 规则可能挡住了 Monitor 节点或 TCP 端口的访问——这些规则可能是早期压力测试或规则开发遗留的。排查方法:SSH 到每个 Monitor 节点,用 `telnet` 或 `nc` 等工具尝试连接**其他每个 Monitor 节点**的 `tcp/3300` 和 `tcp/6789` 端口。
|
||
|
||
#### 第 3 步:确认 `ceph status` 能跑通并收到回包
|
||
|
||
- 若 `ceph status` **能**收到集群回包 → 集群起来了。Monitor **只在 quorum 已形成时**才应答 `status` 请求。同时确认有一个或多个 `mgr` 守护进程被报为 running——健康的集群里 `ceph status` 会报所有 `mgr` 都在跑。
|
||
- 若 **收不到**回包 → 很可能是 `up` 的 MON 数量不足以形成 quorum。
|
||
- `ceph -s` 不带选项时会**任意挑一个 Monitor** 连。某些情况下用 `-m` 显式指定会有帮助:`ceph status -m mymon1`。
|
||
|
||
#### 第 4 步:都不行?逐个 MON 单独问
|
||
|
||
即使没有形成 quorum,**也可以单独联系每个 Monitor** 并请求它的状态:
|
||
|
||
```bash
|
||
ceph tell mon.ID mon_status
|
||
```
|
||
|
||
对集群里**每一个** Monitor 都跑一遍。另一种方式是 SSH 到每个 MON 节点,查询守护进程的 admin socket(见 3.2)。
|
||
|
||
### 3.2 使用 Monitor 的 admin socket
|
||
|
||
admin socket 是一个 Unix socket 文件,位于 monitor 的 `run` 目录,默认路径 `/var/run/ceph/ceph-mon.ID.asok`。**默认位置可以被覆盖**——集群守护进程跑在容器里时通常就在别处。
|
||
|
||
查找方法:查 `ceph.conf` 里的替代路径,或运行:
|
||
|
||
```bash
|
||
ceph-conf --name mon.ID --show-config-value admin_socket
|
||
```
|
||
|
||
**关键行为**:
|
||
|
||
- admin socket **只在 Monitor 守护进程运行时可用**
|
||
- 每次 Monitor **正常关闭**,admin socket 会被移除
|
||
- 如果 Monitor 没在跑、但 asok 文件还在 → **很可能是非正常关闭**
|
||
- Monitor 没在跑时,无法使用 admin socket,`ceph` 命令很可能返回 `Error 111: Connection Refused`
|
||
|
||
访问方式:
|
||
|
||
```bash
|
||
# 方式一:通过 ceph tell
|
||
ceph tell mon.<id> mon_status
|
||
|
||
# 方式二:知道 asok 全路径时更直接
|
||
ceph --admin-daemon <full_path_to_asok_file> <command>
|
||
```
|
||
|
||
跑 `ceph help` 可以看到 admin socket 支持的所有命令,**尤其注意**这几个:`config get`、`config show`、`mon stat`、`quorum_status`。
|
||
|
||
### 3.3 理解 `mon_status`
|
||
|
||
> 注意:`ceph tell mon.X mon_status` 里的 `X` **不是让你原样输入**,要替换成你自己集群的 MON ID。
|
||
|
||
示例输出:
|
||
|
||
```javascript
|
||
{ "name": "c",
|
||
"rank": 2,
|
||
"state": "peon",
|
||
"election_epoch": 38,
|
||
"quorum": [
|
||
1,
|
||
2],
|
||
"outside_quorum": [],
|
||
"extra_probe_peers": [],
|
||
"sync_provider": [],
|
||
"monmap": { "epoch": 3,
|
||
"fsid": "5c4e9d53-e2e1-478a-8061-f543f8be4cf8",
|
||
"modified": "2013-10-30 04:12:01.945629",
|
||
"created": "2013-10-29 14:14:41.914786",
|
||
"mons": [
|
||
{ "rank": 0,
|
||
"name": "a",
|
||
"addr": "127.0.0.1:6789\/0"},
|
||
{ "rank": 1,
|
||
"name": "b",
|
||
"addr": "127.0.0.1:6790\/0"},
|
||
{ "rank": 2,
|
||
"name": "c",
|
||
"addr": "127.0.0.1:6795\/0"}]}}
|
||
```
|
||
|
||
这份输出说明:monmap 里有三个 monitor(`a`、`b`、`c`),quorum 只由两个组成,`c` 是 `peon`。
|
||
|
||
**关键知识点(官方原文问答)**:
|
||
|
||
| 问题 | 答案 |
|
||
|---|---|
|
||
| 哪个 monitor 出 quorum 了? | `mon.a` |
|
||
| 怎么知道的? | 因为 `mon.a` 的 rank 是 `0`,而**rank 为 0 的 Monitor 按定义就是出 quorum 的**。`quorum` 集合里是 `1` 和 `2`(这是 **rank** 不是名字),不含 `0`;按 monmap,rank `0` 就是 `mon.a` |
|
||
| rank 怎么算出来的? | 每当 monitor 被加入或移除时,rank 会(重新)计算。规则很简单:**`IP:PORT` 组合越大,rank 越低**。例中 `127.0.0.1:6789`(mon.a)数值上小于 `127.0.0.1:6790`(b)和 `127.0.0.1:6795`(c),所以 `mon.a` 的 rank 最高,即 rank `0` |
|
||
|
||
### 3.4 场景:集群有 quorum,但至少一个 Monitor 是 down 的
|
||
|
||
**现象**
|
||
|
||
```text
|
||
$ ceph health detail
|
||
[snip]
|
||
mon.a (rank 0) addr 127.0.0.1:6789/0 is down (out of quorum)
|
||
```
|
||
|
||
**原因**
|
||
|
||
`mon.a` 处于 down / out of quorum 状态。
|
||
|
||
**解决**
|
||
|
||
1. 确认 `mon.a` 在跑。
|
||
2. 确认能从**其他 Monitor 节点**连到 `mon.a` 的节点,同时检查 TCP 端口。检查**所有节点**上的 `iptables` 和 `nf_conntrack`,确认没有 drop/reject 连接。
|
||
|
||
如果初始排查没解决,进一步调查:按 3.2 / 3.3 的方式,通过 admin socket 查这个有问题的 monitor 的 `mon_status`。
|
||
|
||
**状态分流判断**:
|
||
|
||
- 如果 Monitor **真的不在** quorum 里 → 状态会是 `probing`、`electing` 或 `synchronizing` 之一
|
||
- 如果状态是 `leader` 或 `peon` → 说明**这个 Monitor 自认为在 quorum 里,但集群其余部分认为它不在**
|
||
- 处于 `probing`/`electing`/`synchronizing` 的 Monitor,有可能在你排查的过程中已经进 quorum 了 → **再跑一次 `ceph status`** 确认。如果仍然不在,继续往下看。
|
||
|
||
#### 子场景 A:状态 = `probing`
|
||
|
||
**现象**:`ceph health detail` 显示某 MON 状态为 `probing`。
|
||
|
||
**原因**:该 Monitor **还在寻找其他 Monitor**。每个 Monitor 启动后都会在这个状态停留一段时间;连上了 monmap 里指定的其他 Monitor 之后,就退出 `probing`。停留时长取决于集群参数:
|
||
|
||
- 单 monitor 集群(**生产环境绝不要这样**)→ `probing` 几乎瞬间过去
|
||
- 多 monitor 集群 → 一直 `probing` 直到找到足够的 monitor 形成 quorum;**三个 MON 挂了两个,剩下的那个会无限期停在 `probing`**,直到你把其中一个拉起来
|
||
|
||
**解决**:
|
||
|
||
1. 若 quorum 已建立,只要其他 MON 可达,该 MON 应该能很快找到它们。
|
||
2. 若卡在 `probing` 且通信排查已做尽 → **可能是这个 Monitor 在用一个错误的地址去连其他 Monitor**。用 `mon_status` 输出的 monmap,核对其余 Monitor 在 monmap 里的位置是否与网络中实际位置一致。
|
||
- 不一致 → 见 3.5「恢复损坏的 monmap」
|
||
- 一致 → 持续的 `probing` 可能与 monitor 节点间**严重的时钟偏移**有关 → 见 3.6
|
||
3. 时钟也没问题 → **准备好系统日志,向 Ceph 社区求助**(日志准备见 3.9)
|
||
|
||
#### 子场景 B:状态 = `electing`
|
||
|
||
**现象**:`ceph health detail` 显示 MON 状态为 `electing`。
|
||
|
||
**原因**:该 monitor 正处于选举过程中。选举通常很快完成,但有时 monitor 会陷入所谓的 **election storm(选举风暴)**。选举风暴的存在**可能暗示 monitor 节点之间存在时钟偏移**。
|
||
|
||
**解决**:
|
||
|
||
1. 查时钟 → 见 3.6
|
||
2. 时钟是同步的 → 去邮件列表和 bug tracker 搜类似问题。**`electing` 状态不太可能持续**;在 Cuttlefish 之后的版本里,除了时钟偏移,**没有别的明显原因**能解释 `electing` 持续存在。
|
||
3. 若要深挖持续 `electing` 的原因,可以在调查期间把这个有问题的 Monitor 置为 `down`——**前提是存活的 Monitor 数量足够形成 quorum**。
|
||
|
||
#### 子场景 C:状态 = `synchronizing`
|
||
|
||
**现象**:`ceph health detail` 显示 MON 状态为 `synchronizing`。
|
||
|
||
**原因**:该 monitor 正在追赶集群其余部分,以便加入 quorum。同步耗时是**集群 monitor store 大小、集群规模、集群状态**的函数——**更大、更 degraded 的集群**通常会让 Monitor 在 `synchronizing` 停留更久(相对小而新的集群而言)。
|
||
|
||
**解决**:
|
||
|
||
- 一般等待即可
|
||
- **如果状态在 `synchronizing` → `electing` → `synchronizing` 之间来回跳,这说明有问题**:集群状态推进(生成新 map)的速度太快,同步过程跟不上。这个问题在 Cuttlefish 之前更常见,之后的同步过程已被重构增强以避免这种动态。**如果你在较新版本遇到,请到 Ceph bug tracker 报告**(https://tracker.ceph.com),并提供日志佐证。
|
||
|
||
#### 子场景 D:状态 = `leader` 或 `peon`(但集群认为它不在 quorum)
|
||
|
||
**现象**:`ceph health detail` 显示 MON 是 `leader` 或 `peon`。
|
||
|
||
**原因**:正常 `HEALTH_OK` 运行时,集群里**一个 monitor 是 `leader`,其余都是 `peon`**。所以这里的矛盾是"它自己觉得在 quorum,别人觉得不在"。官方判断:**很可能是存在时钟偏移**。
|
||
|
||
**解决**:
|
||
|
||
1. 按 3.6 处理时钟
|
||
2. 处理完仍然显示 `leader`/`peon` → 到 https://tracker.ceph.com 报告,并附日志
|
||
|
||
**状态判定速查表**
|
||
|
||
| `mon_status` 的 state | 含义 | 处置 |
|
||
|---|---|---|
|
||
| `probing` | 还在找其他 MON;启动时短暂正常;三 MON 挂二会永久停留 | 查连通性 → 核 monmap 地址 → 查时钟 |
|
||
| `electing` | 选举中;持续则可能是 election storm | 查时钟 → 搜 ML/bug tracker → 可临时置 down |
|
||
| `synchronizing` | 正在追赶数据以加入 quorum | 等待;若与 `electing` 来回跳则报 bug |
|
||
| `leader` / `peon` | 自认为在 quorum(正常态之一) | 若与集群认知矛盾 → 查时钟 → 报 bug |
|
||
| —— | Monitor 完全没在跑 | 见 3.1 第 1 步(升级后忘重启是常见原因) |
|
||
|
||
### 3.5 恢复损坏/过期的 monmap
|
||
|
||
**现象**:某节点的 `monmap` 过期——该节点 down 了很久,期间集群的 Monitor 发生了变化。
|
||
|
||
一个正常 monmap 长这样:
|
||
|
||
```text
|
||
epoch 3
|
||
fsid 5c4e9d53-e2e1-478a-8061-f543f8be4cf8
|
||
last_changed 2013-10-30 04:12:01.945629
|
||
created 2013-10-29 14:14:41.914786
|
||
0: 127.0.0.1:6789/0 mon.a
|
||
1: 127.0.0.1:6790/0 mon.b
|
||
2: 127.0.0.1:6795/0 mon.c
|
||
```
|
||
|
||
**原因**:节点长时间离线,期间集群 MON 成员变更。
|
||
|
||
**解决**:两种方案二选一。
|
||
|
||
#### 方案 A:废弃这个 Monitor 并重新部署(Scrap and redeploy)
|
||
|
||
**只有在你确定不会丢失该 Monitor 保存的信息时才这么做**。确保你有其他状态良好的 Monitor,这样新 Monitor 才能与存活的 Monitor 同步。
|
||
|
||
> ⚠️ 记住:**如果没有该 Monitor 内容的其他副本,销毁它会造成数据丢失。**
|
||
|
||
#### 方案 B:注入 monmap(Inject a monmap)
|
||
|
||
从集群里存活的 Monitor 取一份最新 monmap,注入到那个 monmap 损坏或缺失的 Monitor。
|
||
|
||
步骤:
|
||
|
||
1. 取 monmap(二选一):
|
||
|
||
```bash
|
||
# a) 有 quorum:从 quorum 取
|
||
ceph mon getmap -o /tmp/monmap
|
||
```
|
||
|
||
```bash
|
||
# b) 没有 quorum:直接从已停止的 Monitor 抽取
|
||
ceph-mon -i ID-FOO --extract-monmap /tmp/monmap
|
||
```
|
||
|
||
(`ID-FOO` 是那个已停止 Monitor 的 ID)
|
||
|
||
2. 停掉要注入的 Monitor:
|
||
|
||
```bash
|
||
service ceph -a stop mon.{mon-id}
|
||
```
|
||
|
||
3. 注入 monmap:
|
||
|
||
```bash
|
||
ceph-mon -i ID --inject-monmap /tmp/monmap
|
||
```
|
||
|
||
4. 启动该 Monitor。
|
||
|
||
> ⚠️ **警告**:向 Monitor 注入 monmap **可能造成严重问题**。注入会**覆盖该 monitor 上现存的最新 monmap**。小心!
|
||
|
||
### 3.6 时钟偏移(Clock Skews)
|
||
|
||
**现象**
|
||
|
||
```text
|
||
mon.c addr 10.10.0.1:6789/0 clock skew 0.08235s > max 0.05s (latency 0.0045s)
|
||
```
|
||
|
||
(上例中 `mon.c` 被标记存在时钟偏移。)
|
||
|
||
**原因**
|
||
|
||
Paxos 一致性算法**要求严格的时间同步**,因此 quorum 内 monitor 之间的时钟偏移会严重影响 monitor 运作,**由此产生的行为可能令人费解**。
|
||
|
||
**解决**
|
||
|
||
在 monitor 节点上跑时钟同步工具,例如 `Chrony` 或传统的 `ntpd`。配置每个 monitor 节点,使 **`iburst` 选项生效**,并且每个 monitor 有**多个 peer**,包括:
|
||
|
||
- 彼此(Each other)
|
||
- 内部 `NTP` servers
|
||
- 多个外部公共 pool servers
|
||
|
||
> 注:`iburst` 选项会发送 **8 个包**的突发(而不是通常的单个包),用于让两个 peer 进入初始同步状态。
|
||
|
||
**进一步建议**:
|
||
|
||
- 建议把集群里**所有节点**都与内部和外部服务器同步,甚至也可以与你的 monitor 同步
|
||
- **在裸金属上跑 `NTP` server**:VM 虚拟化的时钟不适合稳定计时
|
||
- 见 https://www.ntp.org
|
||
- 你的组织可能已有高质量的内部 `NTP` server
|
||
- NTP server 设备厂商举例(官方原文列出):Microsemi(原 Symmetricom)、EndRun、Netburner
|
||
|
||
**时钟偏移 FAQ(官方原文)**
|
||
|
||
| 问题 | 官方回答 |
|
||
|---|---|
|
||
| 最大可容忍时钟偏移是多少? | 默认情况下,monitor 允许时钟漂移**最大 0.05 秒(50 毫秒)** |
|
||
| 能调大吗? | 能,但**强烈建议不要**。该值可通过 **`mon-clock-drift-allowed`** 选项配置,但改它几乎肯定是个坏主意。设这个上限是因为**时钟偏移的 monitor 不可靠**;当前默认值已被证明能在 monitor 遇到严重问题**之前**就告警,改它可能给 monitor 稳定性和整体集群健康带来不可预见的影响 |
|
||
| 怎么知道有没有时钟偏移? | monitor 会通过集群状态 **`HEALTH_WARN`** 告警(输出形如上例)。**Luminous 及以后**还可以跑 **`ceph time-sync-status`** 检查。注意:lead monitor 通常 IP 数值最小,**它永远显示 `0`**——其他 monitor 报告的偏移是**相对 lead monitor** 的,不是相对某个外部参考源 |
|
||
| 有偏移了怎么办? | 同步时钟。用 NTP 客户端可能有帮助。如果**已经在用 NTP 客户端**却仍有偏移问题,判断一下你用的 NTP server 是在远端网络上还是在你自己网络上——**自建 NTP server 通常能缓解**时钟偏移问题 |
|
||
|
||
### 3.7 客户端连不上 / 挂载失败(Client Can't Connect or Mount)
|
||
|
||
**现象**:客户端连不上集群,或挂载失败,伴随超时错误。
|
||
|
||
**原因**:某些操作系统安装工具会往 `iptables` 里加一条 `REJECT` 规则,该规则会拒绝除 `ssh` 之外所有尝试连接主机的客户端。如果 monitor 主机上有这样的 `REJECT` 规则,从**其他节点**连过来的客户端都会失败并超时。
|
||
|
||
典型规则形如:
|
||
|
||
```text
|
||
REJECT all -- anywhere anywhere reject-with icmp-host-prohibited
|
||
```
|
||
|
||
**解决**:可能需要在 Ceph 主机上给 `iptables` 加规则,确保客户端能访问 Ceph monitor(默认端口 **6789**)和 Ceph OSD(默认端口 **6800 到 7568**)相关的 TCP 端口:
|
||
|
||
```bash
|
||
iptables -A INPUT -m multiport -p tcp -s {ip-address}/{netmask} --dports 6789,6800:7568 -j ACCEPT
|
||
```
|
||
|
||
| 组件 | 默认端口 |
|
||
|---|---|
|
||
| Ceph Monitor | `6789` |
|
||
| Ceph OSD | `6800` - `7568` |
|
||
| MON 互联(`troubleshooting-mon.rst` 第 2 步要求连通性测试用) | `tcp/3300` 与 `tcp/6789` |
|
||
|
||
### 3.8 Monitor Store 故障
|
||
|
||
#### 3.8.1 现象:store 损坏的症状
|
||
|
||
Ceph Monitor 把 **Cluster Map** 维护在一个 key-value store 里。如果 key-value store 损坏导致 Monitor 失败,Monitor 日志里可能出现以下错误消息之一:
|
||
|
||
```text
|
||
Corruption: error in middle of record
|
||
```
|
||
|
||
或:
|
||
|
||
```text
|
||
Corruption: 1 missing files; e.g.: /var/lib/ceph/mon/mon.foo/store.db/1234567.ldb
|
||
```
|
||
|
||
> 💡 补充:本册源文**未**明确说明 leveldb 与 rocksdb 的差异、以及各自的损坏判定方法;官方只给了上述两条通用 corruption 错误文本。若需区分 leveldb/rocksdb 特定行为,文档未明确。
|
||
|
||
#### 3.8.2 解决方式一:用健康的 Monitor 恢复
|
||
|
||
**现象**:集群里还有存活的 Monitor。
|
||
|
||
**原因**:只有一个/少数 Monitor 的 store 损坏。
|
||
|
||
**解决**:把损坏的 Monitor **替换(replace)**为一个新 Monitor。新 Monitor 启动后会与健康的 peer 同步;**完全同步之后**就能服务客户端了。
|
||
|
||
#### 3.8.3 解决方式二:用 OSD 恢复(即使所有 monitor 同时挂掉)
|
||
|
||
**现象**:**所有 monitor 同时失败**。
|
||
|
||
**原因**:官方建议部署**至少三个(最好五个)**Monitor,这样全部 MON 失效的可能性很小。但如果数据中心意外断电,而**磁盘设置或文件系统设置配置不当**导致底层文件系统失败,就可能把所有 monitor 一起干掉。此时**可以用 OSD 里的数据来恢复 Monitor**。
|
||
|
||
**解决**:以下脚本(官方原文)用于这种情况:
|
||
|
||
```bash
|
||
ms=/root/mon-store
|
||
mkdir $ms
|
||
|
||
# collect the cluster map from stopped OSDs
|
||
for host in $hosts; do
|
||
rsync -avz $ms/. user@$host:$ms.remote
|
||
rm -rf $ms
|
||
ssh user@$host <<EOF
|
||
for osd in /var/lib/ceph/osd/ceph-*; do
|
||
ceph-objectstore-tool --data-path \$osd --no-mon-config --op update-mon-db --mon-store-path $ms.remote
|
||
done
|
||
EOF
|
||
rsync -avz user@$host:$ms.remote/. $ms
|
||
done
|
||
|
||
# rebuild the monitor store from the collected map, if the cluster does not
|
||
# use cephx authentication, we can skip the following steps to update the
|
||
# keyring with the caps, and there is no need to pass the "--keyring" option.
|
||
# i.e. just use "ceph-monstore-tool $ms rebuild" instead
|
||
ceph-authtool /path/to/admin.keyring -n mon. \
|
||
--cap mon 'allow *'
|
||
ceph-authtool /path/to/admin.keyring -n client.admin \
|
||
--cap mon 'allow *' --cap osd 'allow *' --cap mds 'allow *'
|
||
# add one or more ceph-mgr's key to the keyring. in this case, an encoded key
|
||
# for mgr.x is added, you can find the encoded key in
|
||
# /etc/ceph/${cluster}.${mgr_name}.keyring on the machine where ceph-mgr is
|
||
# deployed
|
||
ceph-authtool /path/to/admin.keyring --add-key 'AQDN8kBe9PLWARAAZwxXMr+n85SBYbSlLcZnMA==' -n mgr.x \
|
||
--cap mon 'allow profile mgr' --cap osd 'allow *' --cap mds 'allow *'
|
||
# If your monitors' ids are not sorted by ip address, please specify them in order.
|
||
# For example. if mon 'a' is 10.0.0.3, mon 'b' is 10.0.0.2, and mon 'c' is 10.0.0.4,
|
||
# please passing "--mon-ids b a c".
|
||
# In addition, if your monitors' ids are not single characters like 'a', 'b', 'c', please
|
||
# specify them in the command line by passing them as arguments of the "--mon-ids"
|
||
# option. if you are not sure, please check your ceph.conf to see if there is any
|
||
# sections named like '[mon.foo]'. don't pass the "--mon-ids" option, if you are
|
||
# using DNS SRV for looking up monitors.
|
||
ceph-monstore-tool $ms rebuild -- --keyring /path/to/admin.keyring --mon-ids alpha beta gamma
|
||
|
||
# make a backup of the corrupted store.db just in case! repeat for
|
||
# all monitors.
|
||
mv /var/lib/ceph/mon/mon.foo/store.db /var/lib/ceph/mon/mon.foo/store.db.corrupted
|
||
|
||
# move rebuild store.db into place. repeat for all monitors.
|
||
mv $ms/store.db /var/lib/ceph/mon/mon.foo/store.db
|
||
chown -R ceph:ceph /var/lib/ceph/mon/mon.foo/store.db
|
||
```
|
||
|
||
**脚本四步(官方归纳)**:
|
||
|
||
1. 从每个 OSD 主机收集 map
|
||
2. 重建 store
|
||
3. 用合适的 capabilities 填充 keyring 文件里的各个实体
|
||
4. 用恢复出来的副本替换 `mon.foo` 上损坏的 store
|
||
|
||
**脚本里的关键注意点(原文注释里的坑)**:
|
||
|
||
| 坑 | 说明 |
|
||
|---|---|
|
||
| 没开 cephx | 可以跳过更新 keyring caps 的步骤,也不需要传 `--keyring`,直接 `ceph-monstore-tool $ms rebuild` 即可 |
|
||
| mgr key 从哪找 | `/etc/ceph/${cluster}.${mgr_name}.keyring`,在部署了 `ceph-mgr` 的那台机器上 |
|
||
| MON ID 顺序 | 如果 monitor 的 id **不是按 IP 地址排序的**,要显式按顺序指定(`--mon-ids b a c`) |
|
||
| 非单字符 MON ID | 也要用 `--mon-ids` 显式传;不确定就查 `ceph.conf` 里有没有 `[mon.foo]` 这样的段 |
|
||
| 用 DNS SRV 查 monitor | **不要**传 `--mon-ids` |
|
||
| 备份 | 先 `mv store.db store.db.corrupted` 留底,每个 monitor 都要做 |
|
||
| 权限 | 放回去后 `chown -R ceph:ceph` |
|
||
|
||
#### 3.8.4 已知局限(Known limitations)
|
||
|
||
上述恢复工具**无法**恢复以下信息:
|
||
|
||
| 丢失项 | 说明 |
|
||
|---|---|
|
||
| **某些新增的 keyring** | 用 `ceph auth add` 添加的所有 OSD keyring 都能从 OSD 副本中恢复,`client.admin` keyring 由 `ceph-monstore-tool` 导入;但 **MDS keyring 和所有其他 keyring 会缺失**,可能需要手动重新添加 |
|
||
| **正在创建中的池** | 如果有 RADOS 池正处于创建过程中,这个状态会丢失。恢复工具假设**所有池都已创建完成**。如果恢复后某个"部分创建的池"里有 PG 卡在 `unknown` 状态,可以用 `ceph osd force-create-pg` 强制创建**空** PG —— **这创建的是空 PG,只有你确定该池为空时才这么做** |
|
||
| **MDS Maps** | MDS maps 会丢失 |
|
||
|
||
### 3.9 Everything Failed! Now What? —— 求助与准备日志
|
||
|
||
#### 求助渠道(MON 篇给出的)
|
||
|
||
- IRC:`#ceph` 和 `#ceph-devel`,在 **OFTC**(server `irc.oftc.net`)
|
||
- 邮件列表:`dev@ceph.io`、`ceph-users@lists.ceph.com`
|
||
- 上游 Ceph Slack:https://ceph-storage.slack.com/
|
||
- 更多联系方式见 https://ceph.io/en/community/connect/ (页面标注为 2023 年 12 月的当前信息)
|
||
|
||
> 务必**先准备好日志**,被要求时能立刻给出。
|
||
|
||
#### 准备日志(Preparing your logs)
|
||
|
||
1. **定位日志**:MON 日志默认在 `/var/log/ceph/ceph-mon.FOO.log*`。若位置被改过:
|
||
|
||
```bash
|
||
ceph-conf --name mon.FOO --show-config-value log_file
|
||
```
|
||
|
||
2. **提高 debug 级别**:日志里的信息量由配置文件的 debug 级别决定。**如果 Ceph 用的是默认 debug 级别,你的日志可能缺少帮助社区定位问题的重要信息**。
|
||
|
||
对有经验的排查者,官方建议只提高**最相关子系统**的级别(这对新手不容易)。**大多数情况下**,设置以下级别就足以记录到足够信息:
|
||
|
||
```ini
|
||
debug_mon = 10
|
||
debug_ms = 1
|
||
```
|
||
|
||
有时这些级别还不够,社区成员会要求你进一步调整这些或其他 debug 级别。**无论如何,收到一些有用信息总比收到空日志强。**
|
||
|
||
3. **截取日志**:只把与 MON 问题相关的日志片段发给社区。因为判断哪些相关可能不容易,社区也接受**完整未删减**的日志。但**不要**发几十万行且没有任何说明的日志。一个常识性做法:**复现问题时记下当前的日期和时间**,然后据此从日志里截取片段。
|
||
|
||
4. **联系社区**:邮件列表 / IRC / Slack,或在 **tracker** 上开新 issue:http://tracker.ceph.com/projects/ceph/issues/new
|
||
|
||
#### FAQ:调 debug 级别要重启 monitor 吗?
|
||
|
||
**不需要。**("No. It is not necessary to restart a Monitor when adjusting its debug levels.")
|
||
|
||
按有无 quorum 分两种手法:
|
||
|
||
```bash
|
||
# 有 quorum:注入到指定的 monitor
|
||
ceph tell mon.FOO config set debug_mon 10/10
|
||
|
||
# 有 quorum:一次性注入到所有 monitor
|
||
ceph tell mon.* config set debug_mon 10/10
|
||
|
||
# 没有 quorum:用该 monitor 的 admin socket 直接改
|
||
ceph daemon mon.FOO config set debug_mon 10/10
|
||
```
|
||
|
||
**还原**:把上面命令里的 `10/10` 换成 `1/10` 再跑一遍。
|
||
|
||
**查看当前值**:
|
||
|
||
```bash
|
||
ceph daemon mon.FOO config show
|
||
ceph daemon mon.FOO config get 'OPTION_NAME'
|
||
```
|
||
|
||
> 引自:[Troubleshooting Monitors](https://docs.ceph.com/en/tentacle/rados/troubleshooting/troubleshooting-mon/)
|
||
|
||
---
|
||
|
||
## 四、OSD 排障(troubleshooting-osd.rst)
|
||
|
||
### 4.0 前提条件
|
||
|
||
排查 OSD **之前**,先:
|
||
|
||
1. **检查 monitor 与网络**。跑 `ceph health` 或 `ceph -s`,显示 `HEALTH_OK` 说明 monitor 有 quorum。若 monitor 没有 quorum 或状态有错误,**先解决 monitor 问题**(见第三章)再继续。
|
||
2. **检查网络**。网络对 OSD 运行和性能影响显著。查**主机侧丢包(dropped packets)**和**交换机侧 CRC 错误**。
|
||
|
||
### 4.1 收集 OSD 数据
|
||
|
||
| 手段 | 命令 | 用途 |
|
||
|---|---|---|
|
||
| 日志 | `ls /var/log/ceph` | 日志不够详细就调级别(见第二章) |
|
||
| admin socket | `ls /var/run/ceph`;`ceph daemon {daemon-name} help`;`ceph daemon {socket-file} help` | 运行时信息 |
|
||
| 拓扑 | `ceph osd tree` | 集群拓扑(monitoring OSDs) |
|
||
| 剩余空间 | `df -h`(`df --help` 看选项) | 文件系统剩余空间 |
|
||
| I/O 统计 | `iostat -x` | 识别 I/O 相关问题 |
|
||
| 内核诊断 | `dmesg \| grep scsi`(配合 `less`/`more`/`grep`/`tail`) | 内核诊断消息 |
|
||
|
||
### 4.2 停止 OSD 但不触发 rebalance
|
||
|
||
**场景**:需要对集群子集做维护,或处理影响某个故障域(例如一个 rack)的问题;停 OSD 时不想让 CRUSH 自动 rebalance。
|
||
|
||
**做法(旧法,官方不推荐)**:
|
||
|
||
```bash
|
||
ceph osd set noout
|
||
```
|
||
|
||
> ⚠️ **官方警告**:这更多是一个"思想实验",用来让读者理解故障域和 CRUSH 行为,**并不是建议在 post-Luminous 世界里真的去跑 `ceph osd set noout`**。当 OSD 回到 `up` 状态,rebalancing 会恢复,`ceph osd set noout` 引入的变更也会被撤销。
|
||
|
||
**做法(Luminous 及以后,更安全)**:只给受影响的 OSD 打 flag:
|
||
|
||
```bash
|
||
ceph osd add-noout osd.0
|
||
ceph osd rm-noout osd.0
|
||
```
|
||
|
||
也可以给**整个 CRUSH bucket** 打 flag。例如要停机给 `prod-ceph-data1701` 加内存:
|
||
|
||
```bash
|
||
ceph osd set-group noout prod-ceph-data1701
|
||
```
|
||
|
||
**完整流程**:
|
||
|
||
```bash
|
||
# 1) 打 flag
|
||
ceph osd set-group noout prod-ceph-data1701
|
||
|
||
# 2) 停 OSD 及该故障域内共置的其他 Ceph 服务
|
||
systemctl stop ceph\*.service ceph\*.target
|
||
|
||
# ... 做维护 ...
|
||
|
||
# 3) 重启 OSD 及其他停掉的守护进程
|
||
# 注:如果维护过程中主机重启过,则不需要手动起,会自动回来
|
||
sudo systemctl start ceph.target
|
||
|
||
# 4) 按需解除 flag
|
||
ceph osd unset noout
|
||
ceph osd unset-group noout prod-ceph-data1701
|
||
```
|
||
|
||
> 注:**OSD 停止时,该 OSD 内的所有 placement group 会被标记为 `degraded`。**
|
||
> 注:多数现代 Linux 发行版用 `systemd`;但某些系统(尤其较老的)可能需要等价的 `service` 或 `start`/`stop` 命令。
|
||
|
||
**noout 相关命令对照**
|
||
|
||
| 命令 | 作用范围 | 官方态度 |
|
||
|---|---|---|
|
||
| `ceph osd set noout` / `unset noout` | 全局 | **不推荐**(post-Luminous) |
|
||
| `ceph osd add-noout osd.N` / `rm-noout osd.N` | 单个 OSD | 推荐(Luminous+) |
|
||
| `ceph osd set-group noout <bucket>` / `unset-group noout <bucket>` | 整个 CRUSH bucket | 推荐 |
|
||
|
||
### 4.3 OSD 起不来(An OSD Won't Start)
|
||
|
||
**现象**:集群已启动,但某个 OSD 起不来。
|
||
|
||
**原因与解决**(官方列出的检查清单,逐项对照):
|
||
|
||
| 检查项 | 现象/原因 | 解决 |
|
||
|---|---|---|
|
||
| **Configuration File** | 新安装后 OSD 起不来,可能是配置文件不合规范(例如写成了 `hostname` 而不是 `host`) | 检查配置文件是否符合标准 |
|
||
| **Check Paths** | 配置里指定的路径与实际数据/元数据路径不一致(例如 journal、WAL、DB 的路径) | 把 OSD 数据与元数据**分开**存放,以判断是配置文件写错还是实际挂载有问题;要把元数据放到单独的块设备上,就对驱动器做分区或 LVM,**每个 OSD 分一个分区** |
|
||
| **Check Max Threadcount** | 某节点上 OSD 特别多,可能撞上了默认最大线程数(**通常是 32,000**);**恢复期间尤其容易遇到** | 把最大线程数提到允许的上限 **4194303**:`sysctl -w kernel.pid_max=4194303`。若有效,必须固化:在 `/etc/sysctl.d` 下的文件或 `/etc/sysctl.conf` 里加 `kernel.pid_max = 4194303` |
|
||
| **Check `nf_conntrack`** | 这个连接跟踪/限流系统给很多生产 Ceph 集群造成问题,且问题往往**缓慢而隐蔽**地浮现:随集群拓扑和客户端负载增长,神秘的间歇性连接失败和性能毛刺越来越多,尤其在一天中的某些时段 | ① 先查 `syslog` 历史里的 "table full" 事件;② 用 `sysctl` 把 `nf_conntrack_max` 调到高得多的值;③ 提高 `nf_conntrack_buckets`,使 **`nf_conntrack_buckets` × 8 = `nf_conntrack_max`**(这可能需要 `sysctl` 之外的命令,例如 `echo 131072 > /sys/module/nf_conntrack/parameters/hashsize`);④ 另一条路是把相关内核模块加入黑名单彻底禁用处理——**这招强力但脆弱**:模块及列出顺序随内核版本而异,且即便黑名单了,`iptables` 和 `docker` 有时仍会激活连接跟踪;官方建议对这些 tunable 采用"**设好就别管(set and forget)**"策略,现代系统上不会消耗可观资源 |
|
||
| **Kernel Version** | 默认地,Ceph 使用的第三方工具可能有 bug 或与某些发行版/内核版本冲突(例如 Google 的 `gperftools` 和 `TCMalloc`) | 确认内核版本与发行版,查阅 **OS recommendations** 与各 Ceph 版本的 release notes |
|
||
| **Segment Fault** | 出现段错误 | 提高日志级别并重启有问题的守护进程;若段错误**复现**,去 bug tracker(https://tracker.ceph.com/projects/ceph/)和 `dev`、`ceph-users` 邮件列表归档(https://ceph.io/resources)搜是否有人遇到过。若确实是新的独有故障,发到 `dev` 邮件列表并提供:**正在运行的 Ceph 具体版本**、`ceph.conf`(**敏感信息用 XXX 打码**)、monitor status 输出、以及日志文件摘录 |
|
||
|
||
固化 `kernel.pid_max`:
|
||
|
||
```ini
|
||
kernel.pid_max = 4194303
|
||
```
|
||
|
||
### 4.4 OSD 挂了(An OSD Failed)
|
||
|
||
**现象**
|
||
|
||
```bash
|
||
ceph health
|
||
# HEALTH_WARN 1/3 in osds are down
|
||
|
||
ceph health detail
|
||
# HEALTH_WARN 1/3 in osds are down
|
||
# osd.0 is down since epoch 23, last address 192.168.106.220:6800/11080
|
||
```
|
||
|
||
或用:
|
||
|
||
```bash
|
||
ceph osd tree down
|
||
```
|
||
|
||
**原因**
|
||
|
||
OSD 失败意味着 `ceph-osd` 进程无响应或已死,相应 OSD 被标记 `down`。存活的 `ceph-osd` 守护进程会向 monitor 报告该 OSD 似乎是 down 的。**只要有 1 个或多个 OSD 同时处于 `in` 和 `down`,就会触发这条健康告警。**
|
||
|
||
**解决(分因施治)**
|
||
|
||
| 线索 | 处置 |
|
||
|---|---|
|
||
| 驱动器故障或其他阻止 `ceph-osd` 运行/重启的故障 | `/var/log/ceph` 下该 OSD 的日志文件里应该有错误消息 |
|
||
| `ceph-osd` 因**心跳失败**或 **`suicide timeout`** 错误停止 | 底层驱动器或文件系统可能无响应。查 `dmesg` 和 `syslog` 输出里的磁盘错误或内核错误。**可能需要加参数**(例如 `dmesg -T` 看人类可读时间戳)以免把旧错误误当新错误 |
|
||
| 一整台主机的 OSD 全 `down` | 检查是否有网络错误或该主机的硬件问题 |
|
||
| 软件错误导致(例如断言失败或其他意外错误) | 到 bug tracker、dev 邮件列表归档、ceph-users 邮件列表归档搜索;若没有明确的修复或已有 bug,向 ceph-devel 邮件列表报告 |
|
||
|
||
> 💡 补充:官方在"正常条件下"给出的最简处置是——**重启 `ceph-osd` 守护进程**,它会重新加入集群并进入 recovery("Under normal conditions, restarting a `ceph-osd` daemon will allow it to rejoin the cluster and recover.")。
|
||
|
||
### 4.5 磁盘满 / No Free Drive Space
|
||
|
||
**现象**
|
||
|
||
```bash
|
||
ceph health
|
||
# HEALTH_WARN 1 nearfull osd(s)
|
||
|
||
ceph health detail
|
||
# HEALTH_ERR 1 full osd(s); 1 backfillfull osd(s); 1 nearfull osd(s)
|
||
# osd.3 is full at 97%
|
||
# osd.4 is backfill full at 91%
|
||
# osd.2 is near full at 87%
|
||
```
|
||
|
||
**原因**
|
||
|
||
OSD 满时,Ceph 通过**阻止新数据写入该 OSD** 来防止数据丢失。在正常运行的集群里,当集群 OSD 和池接近某些"满"比率时,会触发健康检查。
|
||
|
||
**三个关键阈值(默认值,务必记牢)**
|
||
|
||
| 配置项 | 默认值 | 含义 |
|
||
|---|---|---|
|
||
| `mon_osd_full_ratio` | `0.95`(容量 95%) | 超过此点,**客户端被阻止写入数据** |
|
||
| `mon_osd_backfillfull_ratio` | `0.90`(容量 90%) | 超过此点,**backfill 不会启动** |
|
||
| `mon_osd_nearfull_ratio` | `0.85`(容量 85%) | 达到此点,触发 **`OSD_NEARFULL`** 健康检查 |
|
||
|
||
**解决**
|
||
|
||
1. **查看 fullness**:
|
||
|
||
```bash
|
||
ceph osd df # 逐个 OSD 展示数据占用
|
||
ceph df # 集群整体用量与池间分布
|
||
```
|
||
|
||
> ⚠️ 看 `ceph df` 输出时要**特别关注最满的那几个 OSD**,而不是 raw 空间使用百分比。只要**一个离群 OSD 满了**,写到这个 OSD 所属池的所有 I/O 都可能失败。`ceph df` 报告某池可用空间时,是按**该池中最满的那个 OSD** 来套算比率的。
|
||
|
||
2. **拉平分布**(两种途径):
|
||
|
||
- 用 **`reweight-by-utilization`** 命令,逐步把数据从过满的 OSD 挪走、或挪到过空的 OSD
|
||
- 在 Luminous 的较后修订版及以后,用 **`ceph-mgr` 的 `balancer` 模块**自动完成同样的事
|
||
|
||
3. **调整阈值**:
|
||
|
||
```bash
|
||
ceph osd set-nearfull-ratio <float[0.0-1.0]>
|
||
ceph osd set-full-ratio <float[0.0-1.0]>
|
||
ceph osd set-backfillfull-ratio <float[0.0-1.0]>
|
||
```
|
||
|
||
4. **加容量**:建议通过**增加 OSD** 来解决满集群问题——加新 OSD 让集群把数据重新分布到新可用存储上。
|
||
5. **查浪费的空间**:搜索 **`rados bench`** 遗留的孤儿数据(orphans)占用空间。
|
||
6. **Filestore 的极端手段**:如果 legacy Filestore OSD **因为满了而无法启动**,可以通过删除该满 OSD 上**少量 placement group 目录**来回收空间。
|
||
|
||
> ⚠️ **极其重要(官方原文加粗)**:如果你决定删除某个满 OSD 上的 PG 目录,**绝对不要**在另一个满 OSD 上删除**同一个** PG 目录。**否则你会丢数据**。你**必须**在至少一个 OSD 上保留至少一份数据副本。**删除 PG 目录是一种罕见且极端的干预手段,不可轻率进行。**
|
||
|
||
**为什么小集群容易满(官方解释)**:有时满集群问题是因为某个 OSD 失败引起的——可能是因为测试,也可能是因为集群小、非常满或不均衡。当某个 OSD 或节点持有集群数据的过高比例时,部件故障或自然增长就会导致 `nearfull`/`full` 比率被突破。**在小集群上测试 Ceph 对 OSD 故障的韧性时,建议留出充裕的空闲磁盘空间,并考虑临时调低** OSD 的 `full ratio`、`backfillfull ratio` 和 `nearfull ratio`。
|
||
|
||
### 4.6 OSD 慢 / 无响应(OSDs are Slow/Unresponsive)
|
||
|
||
排查这个常见问题时,官方建议**先排除其他可能**,再深入 OSD 性能:确认网络正常、OSD 在跑、OSD 是否在限流 recovery 流量。
|
||
|
||
> 💡 补充(官方 tip):在 pre-Luminous 版本里,`up` 且 `in` 的 OSD 有时会不可用或很慢,是因为正在做 recovery 的 OSD 消耗了系统资源。**较新版本提供了更好的 recovery 处理来避免这种现象。**
|
||
|
||
下面逐项列出官方给出的可能成因与对应处置。
|
||
|
||
#### 4.6.1 网络问题(Networking Issues)
|
||
|
||
**现象**:OSD 延迟、OSD flapping。
|
||
|
||
**原因**:作为分布式存储系统,Ceph 依赖网络完成 OSD peering 与复制、故障恢复、以及周期性心跳。
|
||
|
||
**解决**:确认 Ceph 进程及依赖进程已连接并在监听:
|
||
|
||
```bash
|
||
netstat -a | grep ceph
|
||
netstat -l | grep ceph
|
||
sudo netstat -p | grep ceph
|
||
```
|
||
|
||
查网络统计:
|
||
|
||
```bash
|
||
netstat -s
|
||
```
|
||
|
||
#### 4.6.2 驱动器配置(Drive Configuration)
|
||
|
||
**原因**:一块 SAS 或 SATA 盘**应该只放一个 OSD**,而一块 NVMe 盘可以轻松放两个或更多。但如果其他进程共用这块盘,读写吞吐可能成为瓶颈。共用者包括:journals / metadata、操作系统、Ceph monitor、`syslog` 日志、其他 OSD、以及非 Ceph 进程。
|
||
|
||
**解决要点**:
|
||
|
||
- 因为 Ceph 是**在 journaling 之后**才确认写(acknowledges writes),所以对 legacy FileStore OSD 而言,用**快速 SSD** 是加速响应时间的诱人选项(尤其在 `XFS` 或 `ext4` 上)
|
||
- 相比之下,`Btrfs` 可以同时写和 journal(但**不建议在生产部署中使用 Btrfs**)
|
||
- **给驱动器分区并不会改变其总吞吐或顺序读写上限**。把 journal 放在单独分区可能略微改善吞吐,但**放在单独的物理驱动器上更好**
|
||
|
||
> ⚠️ **官方警告**:**Reef 不支持 FileStore。Reef 之后的版本也不支持 FileStore。** 任何提到 FileStore 的信息**只适用于 Quincy 及更早的 Ceph 版本**。
|
||
|
||
#### 4.6.3 坏扇区 / 碎片化磁盘(Bad Sectors / Fragmented Disk)
|
||
|
||
**解决**:检查驱动器的坏块、碎片及其他会导致性能显著下降的错误。有用的工具:`dmesg`、`syslog` 日志、以及 `smartctl`(在 `smartmontools` 包里)。
|
||
|
||
> 注:`smartmontools` **7.0 及以后**提供 NVMe stat passthrough 和 JSON 输出。
|
||
|
||
#### 4.6.4 Monitor 与 OSD 共置(Co-resident Monitors/OSDs)
|
||
|
||
**原因**:monitor 虽然相对轻量,但**与 OSD 跑在同一主机**上会导致性能问题:Monitor 会发起大量 `fsync()` 调用,这会干扰其他负载。当 monitor **与 OSD 共置在同一块存储驱动器**上时,性能问题的风险尤其尖锐。此外,如果 monitor 跑在较老内核(pre-3.0)或没有 `syncfs(2)` syscall 的内核上,同一主机上跑的多个 OSD 可能提交过多请求,从而互相拖累性能——这有时会导致所谓的"**the bursty writes**"。
|
||
|
||
**解决**:避免 MON 与 OSD 共置,尤其避免共置在同一块盘上。
|
||
|
||
#### 4.6.5 其他进程共置(Co-resident Processes)
|
||
|
||
**原因**:向 Ceph 写数据的进程(例如云方案、虚拟机)如果与 OSD 跑在同一硬件上,会导致显著的 OSD 延迟。
|
||
|
||
**解决**:官方**一般不推荐**让这类进程与 OSD 共置。推荐做法是:**某些主机专门优化给 Ceph 用,其他主机跑其他进程**。把 Ceph 操作与其他应用分离,既可能提升性能,也可能简化排障与维护。
|
||
|
||
> 这种在同一硬件上跑共置进程的做法有时被称为"**convergence(融合)**"。使用 Ceph 时,**只有具备专业知识并经过考虑后才进行融合**。
|
||
|
||
#### 4.6.6 日志级别(Logging Levels)
|
||
|
||
**原因**:高日志级别会导致性能问题。运维人员有时为了追问题调高日志级别,**事后忘记调回去**。这种情况下,OSD 会把宝贵的系统资源消耗在往磁盘写无谓的冗长日志上。
|
||
|
||
**解决**:确实需要长期高日志级别的人,建议**挂一块盘到默认日志路径**(例如 `/var/log/ceph/$cluster-$name.log`)。
|
||
|
||
#### 4.6.7 Recovery 限流(Recovery Throttling)
|
||
|
||
**原因**:取决于你的配置,Ceph 可能**降低** recovery 速率以维持客户端或 OSD 性能,也可能**提高** recovery 速率到影响客户端或 OSD 性能的程度。
|
||
|
||
**解决**:检查客户端或 OSD 是否正在做 recovery。
|
||
|
||
#### 4.6.8 内核版本(Kernel Version)
|
||
|
||
**原因**:较老的内核可能缺少改善 Ceph 性能的更新。
|
||
|
||
**解决**:检查你正在跑的内核版本。
|
||
|
||
#### 4.6.9 SyncFS 内核问题(Kernel Issues with SyncFS)
|
||
|
||
**原因**:老内核可能没有足够新的 `glibc` 来支撑 `syncfs(2)`。
|
||
|
||
**解决**:试着**每台主机只跑一个 OSD**,看性能是否改善。
|
||
|
||
#### 4.6.10 文件系统问题(Filesystem Issues)
|
||
|
||
**官方推荐**:
|
||
|
||
| 场景 | 推荐 | 不推荐 |
|
||
|---|---|---|
|
||
| post-Luminous 版本 | 用 **BlueStore** 后端部署集群 | —— |
|
||
| pre-Luminous 版本,或有特殊理由用上一代 Filestore 后端 | **`XFS`** | **`Btrfs`**、**`ext4`** |
|
||
|
||
- 不推荐 `Btrfs`:它有很多吸引人的特性,但 **bug 可能导致性能问题和虚假的 ENOSPC 错误**
|
||
- 不推荐 `ext4`(用于 Filestore OSD):**`xattr` 限制会破坏对长对象名的支持**,而 RGW 需要长对象名
|
||
|
||
#### 4.6.11 内存不足(Insufficient RAM)
|
||
|
||
**官方建议**:**每个 OSD 守护进程最少 4GB RAM**,并建议**从 6GB 向上取整到 8GB**。
|
||
|
||
**原因**:正常运行时你可能注意到 `ceph-osd` 进程只用了其中一小部分,于是想把多余 RAM 给共置应用用,或者省着配每个节点的内存。**但当 OSD 进入 recovery 时,内存占用会飙升**。如果 recovery 期间可用内存不足,OSD 性能会大幅下降,守护进程甚至可能崩溃或被 Linux **`OOM Killer`** 杀掉。
|
||
|
||
#### 4.6.12 Blocked Requests / Slow Requests
|
||
|
||
**现象**:`ceph-osd` 响应请求过慢时,集群日志会收到报告 ops 耗时过长的消息。**告警阈值默认 30 秒,可通过 `osd_op_complaint_time` 配置**(日志样例见 2.7)。
|
||
|
||
**可能成因(官方清单)**:
|
||
|
||
- 驱动器故障(查 `dmesg` 输出)
|
||
- 内核文件系统 bug(查 `dmesg` 输出)
|
||
- 集群过载(查系统负载、`iostat` 等)
|
||
- `ceph-osd` 守护进程的 bug
|
||
- OSD shard 配置欠佳(**在带 mClock 调度器的 HDD 集群上**)
|
||
|
||
**可能的解决方案(官方清单)**:
|
||
|
||
- 把 VM 从 Ceph 主机上挪走
|
||
- 升级内核
|
||
- 升级 Ceph
|
||
- 重启 OSD
|
||
- 更换故障或即将故障的部件
|
||
- 覆盖 OSD shard 配置(在带 mClock 调度器的 HDD 集群上)——见 4.6.14
|
||
|
||
#### 4.6.13 调试 Slow Requests:`dump_historic_ops` / `dump_ops_in_flight`
|
||
|
||
```bash
|
||
ceph daemon osd.<id> dump_historic_ops
|
||
ceph daemon osd.<id> dump_ops_in_flight
|
||
```
|
||
|
||
你会看到一组操作,以及**每个操作经历过的事件列表**。事件分四层:
|
||
|
||
**来自 Messenger 层**:
|
||
|
||
| 事件 | 含义 |
|
||
|---|---|
|
||
| `header_read` | messenger 首次开始从网络上读取该消息的时间 |
|
||
| `throttled` | messenger 尝试获取内存限流空间(memory throttle space)以把消息读入内存的时间 |
|
||
| `all_read` | messenger 从网络上读完该消息的时间 |
|
||
| `dispatched` | messenger 把消息交给 OSD 的时间 |
|
||
| `initiated` | 与 `header_read` 相同。两者同时存在是**历史遗留的怪现象** |
|
||
|
||
**来自 OSD 处理 ops 的过程**:
|
||
|
||
| 事件 | 含义 |
|
||
|---|---|
|
||
| `queued_for_pg` | 该 op 已被放入其 PG 的处理队列 |
|
||
| `reached_pg` | PG 已开始执行该 op |
|
||
| `waiting for \*` | 该 op 在等待某项其他工作完成后才能继续(例如新的 OSDMap;其目标对象的 scrubbing;某个 PG peering 的完成;均按消息中的说明) |
|
||
| `started` | 该 op 已被接受为 OSD 应当执行的操作,现正在执行 |
|
||
| `waiting for subops from` | 该 op 已被发送给副本 OSD |
|
||
|
||
**来自 `Filestore`**:
|
||
|
||
| 事件 | 含义 |
|
||
|---|---|
|
||
| `commit_queued_for_journal_write` | 该 op 已交给 FileStore |
|
||
| `write_thread_in_journal_buffer` | 该 op 在 journal 的 buffer 中,等待被持久化(作为下一次磁盘写) |
|
||
| `journaled_completion_queued` | 该 op 已落 journal 到磁盘,其回调已排队等待调用 |
|
||
|
||
**数据交给底层存储之后的 OSD 事件**:
|
||
|
||
| 事件 | 含义 |
|
||
|---|---|
|
||
| `op_commit` | 该 op 已被 primary OSD 提交(即写入 journal) |
|
||
| `op_applied` | 该 op 已用 `write()` 写到后端 FS(即已在内存中 apply,但**尚未 flush 到磁盘**),在 primary 上 |
|
||
| `sub_op_applied` | 同 `op_applied`,但针对副本的 "subop" |
|
||
| `sub_op_committed` | 同 `op_commit`,但针对副本的 subop(**仅 EC 池**) |
|
||
| `sub_op_commit_rec/sub_op_apply_rec from <X>` | primary 收到上述事件时打的标记,针对某个具体副本(即 `<X>`) |
|
||
| `commit_sent` | 已向客户端(或对 sub ops 而言是 primary OSD)发回响应 |
|
||
|
||
> 注:官方说这些事件有些看起来冗余,但它们跨越了内部代码的重要边界(比如跨锁把数据传递给新线程)。
|
||
|
||
#### 4.6.14 mClock 调度器下的慢请求 / 慢恢复
|
||
|
||
**适用前提(官方 note)**:这段排障**只适用于**满足以下条件的场景——**HDD 集群**、**运行 mClock 调度器**、且 OSD shard 配置为 **`osd_op_num_shards_hdd` = 5** 和 **`osd_op_num_threads_per_shard_hdd` = 1**。
|
||
|
||
**现象**:在规模较大的 HDD 集群上,启用 mClock 且**出现多个 OSD 节点故障**时,可能报告或观察到:
|
||
|
||
- **slow requests**:并进一步表现为**客户端 I/O 性能下降**
|
||
- **slow background recoveries**:恢复吞吐低于预期
|
||
|
||
**排查步骤**:
|
||
|
||
1. 从 OSD 事件确认 slow requests 是否** predominantly 属于 `queued_for_pg` 类型**
|
||
2. 确认报告出来的 recovery 速率是否**显著低于**按后台 recovery 服务的 QoS 配额所预期的速率
|
||
|
||
**解决**:若上述任一步成立,可应用以下处置。**注意这是破坏性的,涉及 OSD 重启**:
|
||
|
||
```bash
|
||
ceph config set osd osd_op_num_shards_hdd 1
|
||
ceph config set osd osd_op_num_threads_per_shard_hdd 5
|
||
```
|
||
|
||
上述配置**不会立即生效,需要重启环境中的 OSD**。为了把影响降到最低,**OSD 可以采用精心错开(carefully staggered)的方式逐个重启**。
|
||
|
||
> 引自:[Troubleshooting OSDs](https://docs.ceph.com/en/tentacle/rados/troubleshooting/troubleshooting-osd/)
|
||
|
||
### 4.7 Flapping OSDs
|
||
|
||
**现象**:OSD 被**反复且快速**地标记 `up` 然后再 `down`。
|
||
|
||
**原因(官方详解)**
|
||
|
||
OSD 做 peering 和心跳检查时,会**在集群(后端)网络可用时使用它**。
|
||
|
||
上游社区传统上推荐**分离的 public(前端)与 private(集群/后端/复制)网络**,带来两点好处:
|
||
|
||
1. 把(1)心跳流量与复制/恢复流量(private)和(2)客户端流量及 OSD↔MON 流量(public)**隔离开**,避免一股流量把另一股 DoS 掉而引发级联故障
|
||
2. 为 public 和 private 流量都提供额外吞吐
|
||
|
||
**但是**:过去网络技术还在 100Mb/s ~ 1Gb/s 量级时,这种分离往往很关键;**在今天的 10Gb/s、40Gb/s、25/50/100Gb/s 网络下,上述容量顾虑常常被削弱甚至消除**。举例:如果你的 OSD 节点有两个网口,把一个专给 public、另一个专给 private,就意味着**你没有路径冗余**——这会削弱你承受网络维护和网络故障而不显著影响集群或客户端的能力。这种情况下,官方建议**改为把两条链路都只用作 public 网络**:配合 **bonding(LACP)**或**等价路由(例如 FRR)**,你可以同时获得吞吐余量提升、容错、以及**减少 OSD flapping** 的好处。
|
||
|
||
**flapping 的形成机制**:当 private 网络(甚至单条主机链路)故障或降级、而 public 网络继续正常运行时,OSD 可能处理不好:OSD 会**通过 public 网络**互相向 monitor 报告对方 `down`,同时把自己标记 `up`;monitor 又(同样通过 public 网络)发出把受影响 OSD 标记为 `down` 的新集群 map;这些 OSD 又回复 monitor"**I'm not dead yet!**",然后循环往复。**这个场景被称为 `flapping`,很难隔离和修复**。如果没有 private 网络,这种恼人的动态就被避免了:OSD 一般是非 `up` 即 `down`,不会 flapping。
|
||
|
||
**解决:临时冻结状态**
|
||
|
||
如果有东西导致 OSD 'flap',你可以**强制 monitor 停止 flapping**,临时冻结它们的状态:
|
||
|
||
```bash
|
||
ceph osd set noup # prevent OSDs from getting marked up
|
||
ceph osd set nodown # prevent OSDs from getting marked down
|
||
```
|
||
|
||
这些 flag 记录在 osdmap 里:
|
||
|
||
```bash
|
||
ceph osd dump | grep flags
|
||
# flags no-up,no-down
|
||
```
|
||
|
||
清除:
|
||
|
||
```bash
|
||
ceph osd unset noup
|
||
ceph osd unset nodown
|
||
```
|
||
|
||
**四个 flag 的语义**
|
||
|
||
| flag | 作用 |
|
||
|---|---|
|
||
| `noup` | 阻止 OSD 被 mark up |
|
||
| `nodown` | 阻止 OSD 被 mark down |
|
||
| `noin` | 阻止**正在启动**的 OSD 被 mark `in`(即被分配数据) |
|
||
| `noout` | 保护 OSD 不被最终 mark `out`(无论 `mon_osd_down_out_interval` 当前值是多少) |
|
||
|
||
> 注:`noup`、`noout`、`nodown` 是"临时"的——flag 清除后,它们原本阻止的动作很快就能发生。但 **`noin` 会阻止 OSD 在启动时被 mark `in`,并且在 flag 生效期间启动的任何守护进程都会保持那种状态**。
|
||
|
||
> 注:flapping 的成因与影响,可以通过**谨慎调整** `mon_osd_down_out_subtree_limit`、`mon_osd_reporter_subtree_level` 和 `mon_osd_min_down_reporters` 得到一定缓解。**最优设置的推导取决于集群规模、拓扑和所用 Ceph 版本,这些因素之间的相互作用很微妙,超出了本文档的范围。**
|
||
|
||
> 引自:[Troubleshooting OSDs](https://docs.ceph.com/en/tentacle/rados/troubleshooting/troubleshooting-osd/)
|
||
|
||
---
|
||
|
||
## 五、PG 排障(troubleshooting-pg.rst)
|
||
|
||
### 5.1 PG 永远不 clean(Placement Groups Never Get Clean)
|
||
|
||
**现象**:PG 停留在 `active`、`active+remapped` 或 `active+degraded` 状态,**始终达不到 `active+clean`**。
|
||
|
||
**原因**:可能说明 Ceph 集群配置有问题。
|
||
|
||
**解决**:复查 **Pool, PG and CRUSH Config Reference** 里的设置并做适当调整。**一般规则:运行集群时使用多于一个 OSD,并且池 size 大于两个对象副本。**
|
||
|
||
#### 子场景:单节点集群(One Node Cluster)
|
||
|
||
**现象**:单节点上 PG 无法 clean。
|
||
|
||
**原因/官方立场**:**Ceph 不再提供在单节点上运维的文档**。为分布式计算设计的系统按定义就不该跑在单节点上。在包含 Ceph 守护进程的单节点上挂载客户端内核模块,可能由于 **Linux 内核本身**的问题导致死锁(**除非用 VM 作为客户端**)。尽管有这些限制,你仍然可以用单节点配置来试验 Ceph。
|
||
|
||
**解决**:要在单节点上创建集群,必须在创建 monitor 和 OSD **之前**,把 `osd_crush_chooseleaf_type` 从默认的 **`1`**(含义 `host`/`node`)改为 **`0`**(含义 `osd`)。这告诉 Ceph:允许把一个 OSD 的 PG 与同一主机上另一个 OSD 的 PG 放在一起。如果你在搭单节点集群而 `osd_crush_chooseleaf_type` 大于 `0`,Ceph 会尝试把一个 OSD 的 PG 与另一个 OSD 的 PG 放在另一个 node、chassis、rack、row 或 datacenter(取决于设置值)。
|
||
|
||
> tip:**不要**把内核客户端直接挂载在 Ceph 存储集群所在的同一节点上,可能产生内核冲突。但可以**在单节点上的虚拟机里**挂载内核客户端。
|
||
|
||
> 补充(原文):如果你用**单块磁盘**创建 OSD,必须**先手动创建数据目录**。
|
||
|
||
#### 子场景:OSD 数少于副本数(Fewer OSDs than Replicas)
|
||
|
||
**现象**:两个 OSD 处于 `up` 且 `in`,但 PG 不在 `active + clean`。
|
||
|
||
**原因**:可能 `osd_pool_default_size` 设得**大于 `2`**。
|
||
|
||
**解决**(三条路):
|
||
|
||
| 目标 | 做法 |
|
||
|---|---|
|
||
| 想以两个副本在 `active + degraded` 下运行 | 把 `osd_pool_default_min_size` 设为 **`2`**,这样就能在 `active + degraded` 状态下写对象 |
|
||
| 想只有两份存储副本(原始 + 一份副本) | 把 `osd_pool_default_size` 设为 **`2`**,这样集群应能达到 `active + clean` |
|
||
|
||
> 注:**这些改动可以在集群运行时做。** 如果你是在 `ceph.conf` 里改的,可能需要重启集群。
|
||
|
||
#### 子场景:Pool Size = 1
|
||
|
||
**现象/原因**:若 `osd_pool_default_size` 设为 `1`,你就只有**一份**对象副本。OSD 依赖其他 OSD 来告诉它"你该有哪些对象";如果只有一个 OSD 有某对象的副本、没有第二份,就**没有第二个 OSD 来告诉第一个 OSD 它该有这个副本**。
|
||
|
||
**解决**:对映射到该 OSD 的每一个 PG(用 `ceph pg dump` 看),强制让这个 OSD 注意到它需要的 PG:
|
||
|
||
```bash
|
||
ceph osd force-create-pg <pgid>
|
||
```
|
||
|
||
#### 子场景:CRUSH Map 错误
|
||
|
||
**现象**:集群里有 PG 不 clean。
|
||
|
||
**原因**:可能是你的 CRUSH map 有错。
|
||
|
||
**解决**:检查 CRUSH map(第五章末还有 EC 池相关的 CRUSH 排查,见 5.8)。
|
||
|
||
### 5.2 Stuck Placement Groups(卡住的 PG)
|
||
|
||
**背景**:组件故障后 PG 进入 "degraded" 或 "peering" 是**正常的**,通常反映故障恢复流程的正常推进。但**长时间**停留在这些状态之一,可能预示更大的问题。因此 Ceph Monitor 会在 PG "stuck" 于非最优状态时告警。具体检查三类:
|
||
|
||
| 类别 | 含义 |
|
||
|---|---|
|
||
| `inactive` | 该 PG 太久没有 `active`(即它一直无法服务读写请求) |
|
||
| `unclean` | 该 PG 太久没有 `clean`(即它一直无法从之前的故障中完全恢复) |
|
||
| `stale` | 该 PG 的状态**一直没有被某个 `ceph-osd` 更新**。这表明存储该 PG 的所有节点可能都 `down` 了 |
|
||
|
||
**列出来**:
|
||
|
||
```bash
|
||
ceph pg dump_stuck stale
|
||
ceph pg dump_stuck inactive
|
||
ceph pg dump_stuck unclean
|
||
```
|
||
|
||
**三类 stuck 的归因(官方)**:
|
||
|
||
| Stuck 类型 | 通常说明 |
|
||
|---|---|
|
||
| `stale` | 关键的 `ceph-osd` 守护进程没在跑 |
|
||
| `inactive` | peering 问题(见 5.3) |
|
||
| `unclean` | 有东西阻止恢复完成,**可能是 unfound objects**(见 5.4) |
|
||
|
||
### 5.3 PG Down —— Peering 失败(Placement Group Down - Peering Failure)
|
||
|
||
**现象**
|
||
|
||
```bash
|
||
ceph health detail
|
||
# HEALTH_ERR 7 pgs degraded; 12 pgs down; 12 pgs peering; 1 pgs recovering; 6 pgs stuck unclean; 114/3300 degraded (3.455%); 1/3 in osds are down
|
||
# ...
|
||
# pg 0.5 is down+peering
|
||
# pg 1.4 is down+peering
|
||
# ...
|
||
# osd.1 is down since epoch 69, last address 192.168.106.220:6801/8651
|
||
```
|
||
|
||
**原因**:某些情况下 `ceph-osd` 的 peering 过程会出问题,导致 PG 无法变为 active 和可用。
|
||
|
||
**解决**
|
||
|
||
1. 查清 PG 为何被标记 `down`:
|
||
|
||
```bash
|
||
ceph pg 0.5 query
|
||
```
|
||
|
||
```javascript
|
||
{ "state": "down+peering",
|
||
...
|
||
"recovery_state": [
|
||
{ "name": "Started\/Primary\/Peering\/GetInfo",
|
||
"enter_time": "2012-03-06 14:40:16.169679",
|
||
"requested_info_from": []},
|
||
{ "name": "Started\/Primary\/Peering",
|
||
"enter_time": "2012-03-06 14:40:16.169659",
|
||
"probing_osds": [
|
||
0,
|
||
1],
|
||
"blocked": "peering is blocked due to down osds",
|
||
"down_osds_we_would_probe": [
|
||
1],
|
||
"peering_blocked_by": [
|
||
{ "osd": 1,
|
||
"current_lost_at": 0,
|
||
"comment": "starting or marking this osd lost may let us proceed"}]},
|
||
{ "name": "Started",
|
||
"enter_time": "2012-03-06 14:40:16.169513"}
|
||
]
|
||
}
|
||
```
|
||
|
||
`recovery_state` 段告诉我们:peering 因为 `ceph-osd` 守护进程 down 而被阻塞,具体是 **`osd.1`**。
|
||
|
||
2. **处置(两条路)**:
|
||
|
||
- **好路**:把那个 `ceph-osd` 拉起来,recovery 就会继续。
|
||
- **坏路(最后手段)**:如果 `osd.1` 发生了灾难性故障(例如磁盘坏了),可以告知集群该 OSD 已 `lost`,让集群尽力而为。
|
||
|
||
```bash
|
||
ceph osd lost 1
|
||
```
|
||
|
||
Recovery 会继续。
|
||
|
||
> ⚠️ **极其重要(官方原文)**:告知集群某个 OSD 已 lost 是**危险的**,因为**集群无法保证其他数据副本是一致且最新的**。
|
||
|
||
### 5.4 Unfound Objects
|
||
|
||
**现象**
|
||
|
||
```bash
|
||
ceph health detail
|
||
# HEALTH_WARN 1 pgs degraded; 78/3778 unfound (2.065%)
|
||
# pg 2.4 is active+degraded, 78 unfound
|
||
```
|
||
|
||
**原因**:这说明存储集群**知道某些对象(或已有对象的更新副本)存在,但没能找到它们的副本**。
|
||
|
||
官方给的成因示例(数据在 OSD "1" 和 "2" 上的两副本 PG):
|
||
|
||
1. 1 挂了
|
||
2. 2 独自处理了一些写
|
||
3. 1 起来了
|
||
4. 1 和 2 重新 peer,1 上缺失的对象排队等待恢复
|
||
5. **在新对象被复制过去之前,2 挂了**
|
||
|
||
此时:1 知道这些对象存在,但**没有任何存活的 `ceph-osd` 持有这些对象的副本**。这种情况下,**对这些对象的 IO 会阻塞**,集群会期待故障节点尽快回来——**这被认为比对用户返回 IO 错误更好**。
|
||
|
||
> 注:上面这个情形正是**在复制池上设 `size=2`、在纠删码池上设 `m=1` 会有丢数据风险**的原因之一。
|
||
|
||
**解决**
|
||
|
||
**第 1 步:列出 unfound 对象**
|
||
|
||
```bash
|
||
ceph pg 2.4 list_unfound [starting offset, in json]
|
||
```
|
||
|
||
```javascript
|
||
{
|
||
"num_missing": 1,
|
||
"num_unfound": 1,
|
||
"objects": [
|
||
{
|
||
"oid": {
|
||
"oid": "object",
|
||
"key": "",
|
||
"snapid": -2,
|
||
"hash": 2249616407,
|
||
"max": 0,
|
||
"pool": 2,
|
||
"namespace": ""
|
||
},
|
||
"need": "43'251",
|
||
"have": "0'0",
|
||
"flags": "none",
|
||
"clean_regions": "clean_offsets: [], clean_omap: 0, new_object: 1",
|
||
"locations": [
|
||
"0(3)",
|
||
"4(2)"
|
||
]
|
||
}
|
||
],
|
||
"state": "NotRecovering",
|
||
"available_might_have_unfound": true,
|
||
"might_have_unfound": [
|
||
{
|
||
"osd": "2(4)",
|
||
"status": "osd is down"
|
||
}
|
||
],
|
||
"more": false
|
||
}
|
||
```
|
||
|
||
- 如果一次列不完(对象太多),**`more` 字段会是 `true`**,你可以继续查询更多(官方原话: eventually the command line tool will hide this from you, but not yet)
|
||
- 在列表末尾(在 `more: false` 之前),当 `available_might_have_unfound` 为 true 时会给出 `might_have_unfound`。**这等价于 `ceph pg #.# query` 的输出**,因此**不必再直接用 `query`**。它的行为与 `query` 的描述一致,**唯一区别是状态为 `already probed` 的 OSD 会被忽略**
|
||
|
||
**第 2 步:用 `query` 看可能的位置(可选)**
|
||
|
||
```bash
|
||
ceph pg 2.4 query
|
||
```
|
||
|
||
```javascript
|
||
"recovery_state": [
|
||
{ "name": "Started\/Primary\/Active",
|
||
"enter_time": "2012-03-06 15:15:46.713212",
|
||
"might_have_unfound": [
|
||
{ "osd": 1,
|
||
"status": "osd is down"}]},
|
||
```
|
||
|
||
上例中,集群知道 `osd.1` 可能有数据,但它 `down` 了。
|
||
|
||
**`might_have_unfound` 里 OSD 状态的全部可能取值**:
|
||
|
||
- `already probed`
|
||
- `querying`
|
||
- `OSD is down`
|
||
- `not queried (yet)`
|
||
|
||
> 有时集群查询可能位置**只是需要一些时间**。
|
||
|
||
**第 3 步:注意"可能有数据但没被列出"的位置**
|
||
|
||
官方提示:有可能存在其他未被列出的、对象可能存在的位置。例如:某个 OSD 被停掉并移出集群,然后集群完全恢复,随后又通过一系列故障导致集群出现 unfound 对象——此时**集群会忽略那个已被移除的 OSD**。(不过这个场景不太可能。)
|
||
|
||
**第 4 步:万不得已,放弃丢失的对象(回滚 / 删除)**
|
||
|
||
如果所有可能位置都查过了、对象仍然丢失,你可能不得不放弃这些丢失的对象。**只有当发生了异常组合的故障、让集群在写操作本身被恢复之前就得知了这些写操作时,才会出现这种情况。**
|
||
|
||
```bash
|
||
ceph pg 2.5 mark_unfound_lost revert|delete
|
||
```
|
||
|
||
| 选项 | 行为 | 限制 |
|
||
|---|---|---|
|
||
| `delete` | 让集群**彻底忘记**这些对象 | 无 |
|
||
| `revert` | **回滚到该对象的上一个版本**,或者(如果它是个新对象)彻底忘记它 | **对纠删码池不可用**。且要**谨慎使用**——它可能让那些预期该对象存在的应用产生困惑 |
|
||
|
||
### 5.5 Homeless Placement Groups(无家可归的 PG / stale)
|
||
|
||
**现象**
|
||
|
||
```bash
|
||
ceph health
|
||
# HEALTH_WARN 24 pgs stale; 3/300 in osds are down
|
||
```
|
||
|
||
**原因**:有可能持有某个 PG 副本的**所有 OSD 都失败了**。这时,包含这些 PG 的那部分对象存储变为不可用,**monitor 收不到这些 PG 的状态更新**。Monitor 会把**主 OSD 已失败的 PG 标记为 `stale`**。
|
||
|
||
**解决**
|
||
|
||
1. 找出哪些 PG 是 `stale`、以及最后存储它们的 OSD 是哪些:
|
||
|
||
```bash
|
||
ceph health detail
|
||
# HEALTH_WARN 24 pgs stale; 3/300 in osds are down
|
||
# ...
|
||
# pg 2.5 is stuck stale+active+remapped, last acting [2,0]
|
||
# ...
|
||
# osd.10 is down since epoch 23, last address 192.168.106.220:6800/11080
|
||
# osd.11 is down since epoch 13, last address 192.168.106.220:6803/11539
|
||
# osd.12 is down since epoch 24, last address 192.168.106.220:6806/11861
|
||
```
|
||
|
||
2. 上例表示 PG `2.5` 最后是由 **`osd.0` 和 `osd.2`** 管理的 → **重启这些 OSD**,让集群恢复该 PG。
|
||
|
||
### 5.6 只有少数 OSD 收到数据(Only a Few OSDs Receive Data)
|
||
|
||
**现象**:集群里只有少数节点在接收数据。
|
||
|
||
**原因**:检查该池的 PG 数量。因为 PG 到 OSD 的映射涉及"**集群 PG 数 ÷ 集群 OSD 数**"这个除法运算,**少量 PG(即这个运算的余数)有时不会被分布到整个集群**。
|
||
|
||
**解决**:创建一个 **PG 数量是 OSD 数量的倍数**的池。详见 Placement Groups 文档;改默认值见 Pool, PG, and CRUSH Config Reference。
|
||
|
||
### 5.7 无法写数据(Can't Write Data)
|
||
|
||
**现象**:集群是 up 的,但某些 OSD 是 down 的,且你**无法写入数据**。
|
||
|
||
**原因**:确认池中运行的 OSD 数量是否达到**最小值**。如果没有达到池的最小 OSD 数,Ceph **不会允许你往里写数据**,因为无法保证 Ceph 能复制你的数据。
|
||
|
||
**解决**:参见 `osd_pool_default_min_size`(Pool, PG, and CRUSH Config Reference)。
|
||
|
||
### 5.8 PG Inconsistent
|
||
|
||
**现象**:`ceph health detail` 返回 `active + clean + inconsistent`。
|
||
|
||
```bash
|
||
ceph health detail
|
||
# HEALTH_ERR 1 pgs inconsistent; 2 scrub errors
|
||
# pg 0.6 is active+clean+inconsistent, acting [0,1,2]
|
||
# 2 scrub errors
|
||
```
|
||
|
||
**原因**:这可能表示 **scrubbing 过程中出现错误**。
|
||
|
||
**解决**
|
||
|
||
**第 1 步:找出 inconsistent 的 PG**
|
||
|
||
```bash
|
||
ceph health detail
|
||
```
|
||
|
||
或者用更适合程序化处理的方式:
|
||
|
||
```bash
|
||
rados list-inconsistent-pg rbd
|
||
# ["0.6"]
|
||
```
|
||
|
||
**第 2 步:找出 inconsistent 的对象**
|
||
|
||
```bash
|
||
rados list-inconsistent-obj 0.6 --format=json-pretty
|
||
```
|
||
|
||
```javascript
|
||
{
|
||
"epoch": 14,
|
||
"inconsistents": [
|
||
{
|
||
"object": {
|
||
"name": "foo",
|
||
"nspace": "",
|
||
"locator": "",
|
||
"snap": "head",
|
||
"version": 1
|
||
},
|
||
"errors": [
|
||
"data_digest_mismatch",
|
||
"size_mismatch"
|
||
],
|
||
"union_shard_errors": [
|
||
"data_digest_mismatch_info",
|
||
"size_mismatch_info"
|
||
],
|
||
"selected_object_info": "0:602f83fe:::foo:head(16'1 client.4110.0:1 dirty|data_digest|omap_digest s 968 uv 1 dd e978e67f od ffffffff alloc_hint [0 0 0])",
|
||
"shards": [
|
||
{
|
||
"osd": 0,
|
||
"errors": [],
|
||
"size": 968,
|
||
"omap_digest": "0xffffffff",
|
||
"data_digest": "0xe978e67f"
|
||
},
|
||
{
|
||
"osd": 1,
|
||
"errors": [],
|
||
"size": 968,
|
||
"omap_digest": "0xffffffff",
|
||
"data_digest": "0xe978e67f"
|
||
},
|
||
{
|
||
"osd": 2,
|
||
"errors": [
|
||
"data_digest_mismatch_info",
|
||
"size_mismatch_info"
|
||
],
|
||
"size": 0,
|
||
"omap_digest": "0xffffffff",
|
||
"data_digest": "0xffffffff"
|
||
}
|
||
]
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
**第 3 步:读懂输出(官方逐项解释)**
|
||
|
||
上例中:唯一 inconsistent 的对象名为 `foo`,其 head 存在不一致。不一致分两类:
|
||
|
||
1. **`errors`**:这些错误表示 **shard 之间的不一致,但不指明哪个(些)shard 是坏的**。要定位问题,去查 `shards` 数组里的 `errors`(如果有的话)。
|
||
|
||
- `data_digest_mismatch`:从 **`OSD.2`** 读出的副本 digest 与从 `OSD.0`、`OSD.1` 读出的不同
|
||
- `size_mismatch`:从 **`OSD.2`** 读出的副本 size 是 **`0`**,而 `OSD.0` 和 `OSD.1` 报的是 **`968`**
|
||
|
||
2. **`union_shard_errors`**:`shards` 数组中**所有 shard 专属 `errors` 的并集**。这些 `errors` 是设在**有问题的那个 shard** 上的,包括 `read_error` 等类似错误。**以 `oi` 结尾的 `errors` 表示与 `selected_object_info` 做了比较**。检查 `shards` 数组来确定哪个 shard 有哪些错误。
|
||
|
||
- `data_digest_mismatch_info`:`object-info` 里存的 digest **不是** `0xffffffff`,而 `0xffffffff` 是从 `OSD.2` 读出的那个 shard 算出来的
|
||
- `size_mismatch_info`:`object-info` 里存的 size 与从 `OSD.2` 读出的 size 不同(后者是 `0`)
|
||
|
||
> ⚠️ **警告**:如果某个 shard 的 `errors` 属性里列出了 **`read_error`**,那么这个不一致**很可能源于物理存储错误**。这种情况下,去检查该 OSD 使用的存储——**在尝试修盘之前,先看 `dmesg` 和 `smartctl` 的输出**。
|
||
|
||
**第 4 步:修复**
|
||
|
||
```bash
|
||
ceph pg repair {placement-group-ID}
|
||
```
|
||
|
||
例如:
|
||
|
||
```bash
|
||
ceph pg repair 1.4
|
||
```
|
||
|
||
> ⚠️ **官方警告**:这条命令会用 **"authoritative"(权威)副本覆盖"坏"副本**。大多数情况下,Ceph 能依据一些预定义准则从所有可用副本中选出权威副本。**但这并非每次都奏效**:例如可能存储的数据 digest 缺失,这意味着 Ceph 在选权威副本时**会忽略计算出的 digest**。请知晓这一点,**谨慎使用该命令**。
|
||
|
||
> 注:**PG ID 形如 `N.xxxxx`,其中 `N` 是该 PG 所属池的编号。** `ceph osd listpools` 和 `ceph osd dump | grep pool` 都能返回池编号列表。
|
||
|
||
**周期性出现 `active + clean + inconsistent` 且与时钟相关时**:如果由于**时钟偏移**而周期性收到该状态,考虑把你 monitor 主机上的 **NTP** 守护进程配置为 **peers**。参见 The Network Time Protocol 与 Ceph Clock Settings。
|
||
|
||
#### 关于 PG Repair 的更多说明(More Information on PG Repair)
|
||
|
||
**机制**:Ceph 存储并更新集群中对象的校验和(checksums)。对某 PG 做 scrub 时,**lead OSD 会尝试从其副本中选出一份权威副本**——**只有一种可能情况是一致的**。做 **deep scrub** 后,Ceph 会**计算从磁盘读出的每个对象的校验和**,并与之前记录的校验和比较;若不匹配,就视为不一致。在**复制池**中,任何对象副本的校验和与权威副本的校验和不匹配,就意味着存在不一致;发现这些不一致会导致 PG 状态被设为 `inconsistent`。
|
||
|
||
**`pg repair` 能做什么**:
|
||
|
||
- 发现不一致 PG 时,尝试**用权威副本的 digest 覆盖不一致副本的 digest**
|
||
- 在**复制池**中发现不一致副本时,**把该不一致副本标记为 missing**
|
||
- **在复制池中,恢复(recovery)超出 `pg repair` 的范围**
|
||
|
||
**自动修复**:在**纠删码池和 BlueStore 池**中,如果 `osd_scrub_auto_repair`(默认 **`false`**)被设为 **`true`**,且发现的错误**不超过 `osd_scrub_auto_repair_num_errors`(默认 **`5`**)** 个,Ceph 会**自动执行修复**。
|
||
|
||
| 配置项 | 默认值 | 含义 |
|
||
|---|---|---|
|
||
| `osd_scrub_auto_repair` | `false` | 设为 `true` 时,EC 池与 BlueStore 池在错误数不超阈值时自动修复 |
|
||
| `osd_scrub_auto_repair_num_errors` | `5` | 自动修复允许的最大错误数 |
|
||
|
||
**`pg repair` 不能做什么 / 注意事项**:
|
||
|
||
- **`pg repair` 命令不能解决所有问题。发现 PG 包含不一致时,Ceph 不会自动修复 PG。**
|
||
- **RADOS 对象或 omap 的校验和不总是可用**。校验和是**增量计算**的:如果一个复制对象被**非顺序(non-sequentially)**更新,更新涉及的写操作会改变该对象并**使其校验和失效**;而重算校验和时**不会读取整个对象**。
|
||
- `pg repair` 在**没有校验和可用**时也能做修复(如 Filestore 的情况)。使用**复制型 Filestore 池**的用户**可能更偏好手动修复**,而不是 `ceph pg repair`。
|
||
- 这段内容适用于 **Filestore**,**不适用于 BlueStore**——BlueStore 有自己的内部校验和。
|
||
- **matched-record checksum 与 calculated checksum 都无法证明某个具体副本确实是权威的**。如果没有可用的校验和,**`pg repair` 会倾向于 primary 上的数据,但这可能并不是那个未损坏的副本**。由于存在这种不确定性,**发现不一致时需要人工介入**,这种介入有时会用到 **`ceph-objectstore-tool`**。
|
||
|
||
#### PG Repair Walkthrough
|
||
|
||
官方给出的外部链接(原文照录):
|
||
|
||
> https://ceph.io/geen-categorie/ceph-manually-repair-object/ - This page contains a walkthrough of the repair of a PG. It is recommended reading if you want to repair a PG but have never done so.
|
||
|
||
### 5.9 纠删码 PG 不 active+clean(Erasure Coded PGs are not active+clean)
|
||
|
||
**现象**:CRUSH 找不到足够的 OSD 映射到某个 PG 时,该位置会显示 **`2147483647`**,即 **`ITEM_NONE`** 或 **`no OSD found`**。
|
||
|
||
```text
|
||
[2,1,6,0,5,8,2147483647,7,4]
|
||
```
|
||
|
||
下面分三种成因。
|
||
|
||
#### 5.9.1 OSD 不够(Not enough OSDs)
|
||
|
||
**现象**:集群只有 8 个 OSD,而纠删码池需要 9 个 OSD → 显示 "Not enough OSDs"。
|
||
|
||
**解决**:二选一
|
||
|
||
```bash
|
||
# 方案一:另建一个需要更少 OSD 的纠删码池
|
||
ceph osd erasure-code-profile set myprofile k=5 m=3
|
||
ceph osd pool create erasurepool erasure myprofile
|
||
```
|
||
|
||
```text
|
||
方案二:加新 OSD,PG 会自动使用它们
|
||
```
|
||
|
||
#### 5.9.2 CRUSH 约束无法满足(CRUSH constraints cannot be satisfied)
|
||
|
||
**现象**:集群 OSD 数量够,但 CRUSH rule 施加了无法满足的约束。例如:10 个 OSD 分布在 2 台主机上,而 CRUSH rule 要求同一 PG 内不能有两个 OSD 来自同一主机 → 映射可能失败,因为只能找到 2 个 OSD。
|
||
|
||
**排查**:dump 出这条 rule 看约束:
|
||
|
||
```bash
|
||
ceph osd crush rule ls
|
||
# [
|
||
# "replicated_rule",
|
||
# "erasurepool"]
|
||
# $ ceph osd crush rule dump erasurepool
|
||
# { "rule_id": 1,
|
||
# "rule_name": "erasurepool",
|
||
# "type": 3,
|
||
# "steps": [
|
||
# { "op": "take",
|
||
# "item": -1,
|
||
# "item_name": "default"},
|
||
# { "op": "chooseleaf_indep",
|
||
# "num": 0,
|
||
# "type": "host"},
|
||
# { "op": "emit"}]}
|
||
```
|
||
|
||
**解决**:新建一个允许 PG 内 OSD 位于同一 host 的池:
|
||
|
||
```bash
|
||
ceph osd erasure-code-profile set myprofile crush-failure-domain=osd
|
||
ceph osd pool create erasurepool erasure myprofile
|
||
```
|
||
|
||
#### 5.9.3 CRUSH 过早放弃(CRUSH gives up too soon)
|
||
|
||
**现象**:集群 OSD 数刚好够映射 PG(例如总共 9 个 OSD,而纠删码池每 PG 需要 9 个 OSD),CRUSH 可能在找到映射之前就放弃了。
|
||
|
||
**解决**(三条路):
|
||
|
||
1. **降低纠删码池要求**,让每 PG 用更少的 OSD —— 这需要**另建一个池**,因为 **erasure code profile 不能动态修改**
|
||
2. **往集群加更多 OSD** —— 这**不需要修改纠删码池**,PG 会自动变 clean
|
||
3. **用手工 CRUSH rule,让它多试几次**以找到好的映射 —— 可以对已有的 CRUSH rule 通过设置 `set_choose_tries` 为**大于默认值**的值来修改
|
||
|
||
**验证与实施(全部离线进行,不碰集群)**
|
||
|
||
**第 1 步:先验证问题**——从集群导出 crushmap 后用 `crushtool` 测试。**这确保你的实验不会修改 Ceph 集群,只作用于本地文件**:
|
||
|
||
```bash
|
||
ceph osd crush rule dump erasurepool
|
||
# { "rule_id": 1,
|
||
# "rule_name": "erasurepool",
|
||
# "type": 3,
|
||
# "steps": [
|
||
# { "op": "take",
|
||
# "item": -1,
|
||
# "item_name": "default"},
|
||
# { "op": "chooseleaf_indep",
|
||
# "num": 0,
|
||
# "type": "host"},
|
||
# { "op": "emit"}]}
|
||
ceph osd getcrushmap > crush.map
|
||
# got crush map from osdmap epoch 13
|
||
crushtool -i crush.map --test --show-bad-mappings \
|
||
--rule 1 \
|
||
--num-rep 9 \
|
||
--min-x 1 --max-x $((1024 * 1024))
|
||
# bad mapping rule 8 x 43 num_rep 9 result [3,2,7,1,2147483647,8,5,6,0]
|
||
# bad mapping rule 8 x 79 num_rep 9 result [6,0,2,1,4,7,2147483647,5,8]
|
||
# bad mapping rule 8 x 173 num_rep 9 result [0,4,6,8,2,1,3,7,2147483647]
|
||
```
|
||
|
||
参数说明:
|
||
|
||
- `--num-rep`:纠删码 CRUSH rule **需要的 OSD 数量**
|
||
- `--rule`:`ceph osd crush rule dump` 显示的 **`rule_id`** 字段值
|
||
- 该测试会尝试映射 **一百万**个值(本例中由 `[--min-x,--max-x]` 定义的范围),**必须至少显示一个 bad mapping**。
|
||
- **如果输出为空,说明所有映射都成功了**,你可以确信集群的问题不是由 bad mappings 引起的。
|
||
|
||
**第 2 步:反编译 CRUSH map**,以便编辑 CRUSH rule:
|
||
|
||
```bash
|
||
crushtool --decompile crush.map > crush.txt
|
||
```
|
||
|
||
**第 3 步:往 rule 里加一行**:
|
||
|
||
```text
|
||
step set_choose_tries 100
|
||
```
|
||
|
||
加完后 `crush.txt` 的相关部分形如:
|
||
|
||
```text
|
||
rule erasurepool {
|
||
id 1
|
||
type erasure
|
||
step set_chooseleaf_tries 5
|
||
step set_choose_tries 100
|
||
step take default
|
||
step chooseleaf indep 0 type host
|
||
step emit
|
||
}
|
||
```
|
||
|
||
**第 4 步:重新编译并重新测试**:
|
||
|
||
```bash
|
||
crushtool --compile crush.txt -o better-crush.map
|
||
```
|
||
|
||
**第 5 步:所有映射都成功后,用 `--show-choose-tries` 显示"找到全部映射所需尝试次数"的直方图**:
|
||
|
||
```bash
|
||
crushtool -i better-crush.map --test --show-bad-mappings \
|
||
--show-choose-tries \
|
||
--rule 1 \
|
||
--num-rep 9 \
|
||
--min-x 1 --max-x $((1024 * 1024))
|
||
# ...
|
||
# 11: 42
|
||
# 12: 44
|
||
# 13: 54
|
||
# ...
|
||
# 47: 1
|
||
# 48: 0
|
||
# ...
|
||
# 102: 0
|
||
# 103: 1
|
||
# 104: 0
|
||
# ...
|
||
```
|
||
|
||
读法:映射 **42 个 PG 花了 11 次**尝试,映射 **44 个 PG 花了 12 次**尝试,依此类推。**尝试次数的最大值,就是能避免 bad mappings 的 `set_choose_tries` 最小值**(上例中是 **`103`**,因为没有任何 PG 花了超过 103 次才被映射)。
|
||
|
||
> 引自:[Troubleshooting PGs](https://docs.ceph.com/en/tentacle/rados/troubleshooting/troubleshooting-pg/)
|
||
|
||
---
|
||
|
||
## 六、CPU 性能剖析(cpu-profiling.rst)
|
||
|
||
### 6.0 前提
|
||
|
||
如果你是**从源码构建 Ceph**、并且**为了配合 `oprofile` 而编译**的,就可以剖析 Ceph 的 CPU 占用。详见 Installing Oprofile。
|
||
|
||
> oprofile 项目地址:http://oprofile.sourceforge.net/about/
|
||
|
||
### 6.1 初始化 oprofile(Initializing oprofile)
|
||
|
||
`oprofile` **首次使用时必须初始化**。先找到与你正在运行的内核对应的 `vmlinux` 镜像:
|
||
|
||
```bash
|
||
ls /boot
|
||
sudo opcontrol --init
|
||
sudo opcontrol --setup --vmlinux={path-to-image} --separate=library --callgraph=6
|
||
```
|
||
|
||
| 参数 | 说明 |
|
||
|---|---|
|
||
| `--vmlinux={path-to-image}` | 与当前运行内核对应的 `vmlinux` 镜像路径(先用 `ls /boot` 找) |
|
||
| `--separate=library` | 按库分离采样 |
|
||
| `--callgraph=6` | 开启 call graph,深度 6 |
|
||
|
||
### 6.2 启动 oprofile
|
||
|
||
```bash
|
||
opcontrol --start
|
||
```
|
||
|
||
### 6.3 停止 oprofile
|
||
|
||
```bash
|
||
opcontrol --stop
|
||
```
|
||
|
||
### 6.4 获取 oprofile 结果(Retrieving oprofile Results)
|
||
|
||
取 top `cmon` 结果:
|
||
|
||
```bash
|
||
opreport -gal ./cmon | less
|
||
```
|
||
|
||
取 top `cmon` 结果,**带 call graphs**:
|
||
|
||
```bash
|
||
opreport -cal ./cmon | less
|
||
```
|
||
|
||
| 选项 | 含义 |
|
||
|---|---|
|
||
| `-g` | 按 symbol 粒度(含调试信息)显示 |
|
||
| `-a` | 累加(accumulate) |
|
||
| `-c` | 显示 call graph |
|
||
| `-l` | 按 symbol 显示 |
|
||
|
||
> ⚠️ **重要(官方原文)**:**查看完结果后,再次运行 oprofile 之前要先 reset。reset 动作会清掉 session 目录里的数据。**
|
||
|
||
### 6.5 重置 oprofile
|
||
|
||
```bash
|
||
sudo opcontrol --reset
|
||
```
|
||
|
||
> ⚠️ **重要(官方原文)**:**分析完数据后要 reset oprofile。这能确保之前的测试结果不会混进当前测试的结果里。**
|
||
|
||
> 引自:[CPU Profiling](https://docs.ceph.com/en/tentacle/rados/troubleshooting/cpu-profiling/)
|
||
|
||
---
|
||
|
||
## 七、内存剖析(memory-profiling.rst)
|
||
|
||
### 7.0 前提与安装
|
||
|
||
**Ceph Monitor、OSD 和 MDS 都能报告 `TCMalloc` heap profiles。** 想生成这些报告,需要安装 `google-perftools`。
|
||
|
||
> 你的 OS 发行版可能用不同的名字打包(例如 `gperftools`),也可能用不同的包管理器。
|
||
|
||
```bash
|
||
sudo apt-get install google-perftools
|
||
```
|
||
|
||
**输出位置**:profiler 把输出 dump 到你的 **`log file` 目录(`/var/log/ceph`)**。详见 Logging and Debugging。
|
||
|
||
### 7.1 用 Google performance tools 查看 profiler 日志
|
||
|
||
```bash
|
||
google-pprof --text {path-to-daemon} {log-path/filename}
|
||
```
|
||
|
||
**完整示例(官方原文)**:
|
||
|
||
```text
|
||
$ ceph tell osd.0 heap start_profiler
|
||
$ ceph tell osd.0 heap dump
|
||
osd.0 tcmalloc heap stats:------------------------------------------------
|
||
MALLOC: 2632288 ( 2.5 MiB) Bytes in use by application
|
||
MALLOC: + 499712 ( 0.5 MiB) Bytes in page heap freelist
|
||
MALLOC: + 543800 ( 0.5 MiB) Bytes in central cache freelist
|
||
MALLOC: + 327680 ( 0.3 MiB) Bytes in transfer cache freelist
|
||
MALLOC: + 1239400 ( 1.2 MiB) Bytes in thread cache freelists
|
||
MALLOC: + 1142936 ( 1.1 MiB) Bytes in malloc metadata
|
||
MALLOC: ------------
|
||
MALLOC: = 6385816 ( 6.1 MiB) Actual memory used (physical + swap)
|
||
MALLOC: + 0 ( 0.0 MiB) Bytes released to OS (aka unmapped)
|
||
MALLOC: ------------
|
||
MALLOC: = 6385816 ( 6.1 MiB) Virtual address space used
|
||
MALLOC:
|
||
MALLOC: 231 Spans in use
|
||
MALLOC: 56 Thread heaps in use
|
||
MALLOC: 8192 Tcmalloc page size
|
||
------------------------------------------------
|
||
Call ReleaseFreeMemory() to release freelist memory to the OS (via madvise()).
|
||
Bytes released to the OS take up virtual address space but no physical memory.
|
||
$ google-pprof --text \
|
||
/usr/bin/ceph-osd \
|
||
/var/log/ceph/ceph-osd.0.profile.0001.heap
|
||
Total: 3.7 MB
|
||
1.9 51.1% 51.1% 1.9 51.1% ceph::log::Log::create_entry
|
||
1.8 47.3% 98.4% 1.8 47.3% std::string::_Rep::_S_create
|
||
0.0 0.4% 98.9% 0.0 0.6% SimpleMessenger::add_accept_pipe
|
||
0.0 0.4% 99.2% 0.0 0.6% decode_message
|
||
...
|
||
```
|
||
|
||
**heap stats 输出字段解读**
|
||
|
||
| 字段 | 含义 |
|
||
|---|---|
|
||
| `Bytes in use by application` | 应用正在使用的字节数 |
|
||
| `Bytes in page heap freelist` | page heap freelist 中的字节数 |
|
||
| `Bytes in central cache freelist` | central cache freelist 中的字节数 |
|
||
| `Bytes in transfer cache freelist` | transfer cache freelist 中的字节数 |
|
||
| `Bytes in thread cache freelists` | thread cache freelists 中的字节数 |
|
||
| `Bytes in malloc metadata` | malloc 元数据占用的字节数 |
|
||
| `Actual memory used (physical + swap)` | 实际使用的内存(物理 + swap) |
|
||
| `Bytes released to OS (aka unmapped)` | 已通过 `madvise()` 释放给 OS 的字节数(占虚拟地址空间但不占物理内存) |
|
||
| `Virtual address space used` | 使用的虚拟地址空间 |
|
||
| `Spans in use` | 在用 span 数 |
|
||
| `Thread heaps in use` | 在用 thread heap 数 |
|
||
| `Tcmalloc page size` | Tcmalloc 页大小(上例为 `8192`) |
|
||
|
||
### 7.2 比较两个时间点的 heap dump(看"什么在涨")
|
||
|
||
**在同一个守护进程上再做一次 heap dump,会生成另一个文件。** 拿新文件与此前某次 dump 生成的文件做对比,**可以看出这段时间里什么东西长大了**——这是定位内存增长的最实用手法。
|
||
|
||
```text
|
||
$ google-pprof --text --base out/osd.0.profile.0001.heap \
|
||
ceph-osd out/osd.0.profile.0003.heap
|
||
Total: 0.2 MB
|
||
0.1 50.3% 50.3% 0.1 50.3% ceph::log::Log::create_entry
|
||
0.1 46.6% 96.8% 0.1 46.6% std::string::_Rep::_S_create
|
||
0.0 0.9% 97.7% 0.0 26.1% ReplicatedPG::do_op
|
||
0.0 0.8% 98.5% 0.0 0.8% __gnu_cxx::new_allocator::allocate
|
||
```
|
||
|
||
> 详见 Google Heap Profiler:http://goog-perftools.sourceforge.net/doc/heap_profiler.html
|
||
|
||
### 7.3 四个 heap profiler 操作
|
||
|
||
装好 heap profiler 后,启动集群并开始使用 heap profiler。**你可以在运行时启用或禁用 heap profiler,也可以让它持续运行。**
|
||
|
||
> 跑下面的示例命令时:`{daemon-type}` 替换为 `mon`、`osd` 或 `mds`;`{daemon-id}` 替换为 OSD 编号、MON ID 或 MDS ID。
|
||
|
||
#### 7.3.1 启动 Profiler(Starting the Profiler)
|
||
|
||
```bash
|
||
ceph tell {daemon-type}.{daemon-id} heap start_profiler
|
||
```
|
||
|
||
例如:
|
||
|
||
```bash
|
||
ceph tell osd.1 heap start_profiler
|
||
```
|
||
|
||
**开机即启动**:如果环境里有 **`CEPH_HEAP_PROFILER_INIT=true`** 变量,那么**守护进程启动时 profiler 就会被启动**。
|
||
|
||
#### 7.3.2 打印统计(Printing Stats)
|
||
|
||
```bash
|
||
ceph tell {daemon-type}.{daemon-id} heap stats
|
||
```
|
||
|
||
例如:
|
||
|
||
```bash
|
||
ceph tell osd.0 heap stats
|
||
```
|
||
|
||
> 注:**用这条命令报 stats 不需要 profiler 正在运行**,并且**不会**把 heap 分配信息 dump 到文件。
|
||
|
||
#### 7.3.3 Dump Heap Information
|
||
|
||
```bash
|
||
ceph tell {daemon-type}.{daemon-id} heap dump
|
||
```
|
||
|
||
例如:
|
||
|
||
```bash
|
||
ceph tell mds.a heap dump
|
||
```
|
||
|
||
> 注:**dump heap 信息只在 profiler 运行时才有效。**
|
||
|
||
#### 7.3.4 释放内存(Releasing Memory)
|
||
|
||
释放 `tcmalloc` 已分配但**未被 Ceph 守护进程本身使用**的内存:
|
||
|
||
```bash
|
||
ceph tell {daemon-type}{daemon-id} heap release
|
||
```
|
||
|
||
例如:
|
||
|
||
```bash
|
||
ceph tell osd.2 heap release
|
||
```
|
||
|
||
> 💡 补充:注意这条命令在官方原文里的形态是 `{daemon-type}{daemon-id}`(**中间没有点号**),与前几条(`.` 分隔)写法不一致;本册照录原文写法。**实际执行时按所用版本支持的形式为准**(本册源文未对这一差异给出解释)。
|
||
|
||
#### 7.3.5 停止 Profiler
|
||
|
||
```bash
|
||
ceph tell {daemon-type}.{daemon-id} heap stop_profiler
|
||
```
|
||
|
||
例如:
|
||
|
||
```bash
|
||
ceph tell osd.0 heap stop_profiler
|
||
```
|
||
|
||
**heap 命令速查**
|
||
|
||
| 操作 | 命令 | 是否要求 profiler 在跑 |
|
||
|---|---|---|
|
||
| 启动 | `ceph tell osd.1 heap start_profiler` | —— |
|
||
| 打印统计 | `ceph tell osd.0 heap stats` | **否**(且不落文件) |
|
||
| dump | `ceph tell mds.a heap dump` | **是** |
|
||
| 释放内存 | `ceph tell osd.2 heap release` | 文档未明确 |
|
||
| 停止 | `ceph tell osd.0 heap stop_profiler` | —— |
|
||
| 开机自启 | 环境变量 `CEPH_HEAP_PROFILER_INIT=true` | —— |
|
||
|
||
### 7.4 另一种内存剖析法:Valgrind + Massif
|
||
|
||
**适用场景**:Massif heap profiler 工具可以与 Valgrind 一起使用,测量使用了多少 heap 内存。**这个方法很适合排查 RadosGW。**
|
||
|
||
详见 Massif 文档:https://valgrind.org/docs/manual/ms-manual.html
|
||
|
||
**步骤**:
|
||
|
||
1. 从你的发行版包管理器安装 Valgrind,然后启动你要排查的 Ceph 守护进程:
|
||
|
||
```bash
|
||
sudo -u ceph valgrind --max-threads=1024 --tool=massif /usr/bin/radosgw -f --cluster ceph --name NAME --setuser ceph --setgroup ceph
|
||
```
|
||
|
||
2. 该命令运行结束后,会在**当前工作目录**生成一个名为 **`massif.out.<pid>`** 的文件。要跑上面这条命令,**执行它的用户必须对当前目录有写权限**。
|
||
|
||
3. 跑 `ms_print` 从收集到的数据里拿到图和统计:
|
||
|
||
```bash
|
||
ms_print massif.out.12345
|
||
```
|
||
|
||
> **该命令的输出在提交 bug report 时很有帮助。**
|
||
|
||
> 引自:[Memory Profiling](https://docs.ceph.com/en/tentacle/rados/troubleshooting/memory-profiling/)
|
||
|
||
---
|
||
|
||
## 八、社区求助渠道(community.rst)
|
||
|
||
### 8.1 邮件列表
|
||
|
||
| 邮件列表 | 适用 | 订阅 | 退订 |
|
||
|---|---|---|---|
|
||
| **`ceph-users`** | **运维(operational)问题**——官方推荐 | `ceph-users-join@ceph.io` | `ceph-users-leave@ceph.io` |
|
||
| **`ceph-devel`** | 你的问题**疑似 bug**;与**开发版发布包**相关;与**开发测试包**相关;与**你自己的构建**相关 | `dev-join@ceph.io` | `dev-leave@ceph.io` |
|
||
|
||
官方原文:Ceph 社区是极好的信息和帮助来源。**对于 Ceph 的运维问题,我们推荐订阅 ceph-users 邮件列表。**
|
||
|
||
**该发到哪个列表?**
|
||
|
||
| 你的情况 | 建议 |
|
||
|---|---|
|
||
| 一般运维问题 | `ceph-users` |
|
||
| 疑似 bug | `ceph-devel` |
|
||
| 开发版发布包相关 | `ceph-devel` |
|
||
| 开发测试包相关 | `ceph-devel` |
|
||
| 你自己构建的 Ceph | `ceph-devel` |
|
||
|
||
> 💡 补充:`troubleshooting-mon.rst` 里给出的列表地址写作 `dev@ceph.io` 和 `ceph-users@lists.ceph.com`,与 `community.rst` 中的 `dev-join@ceph.io` / `ceph-users-join@ceph.io` 写法不同——**前者是发帖/联系地址,后者是订阅/退订地址**。本册不判断二者是否等同,仅照录原文。
|
||
|
||
### 8.2 IRC / Slack / 其他
|
||
|
||
(来自 `troubleshooting-mon.rst` 的 "Reaching out for help")
|
||
|
||
| 渠道 | 地址 |
|
||
|---|---|
|
||
| IRC | `#ceph` 和 `#ceph-devel`,在 **OFTC**(server `irc.oftc.net`) |
|
||
| 邮件 | `dev@ceph.io`、`ceph-users@lists.ceph.com` |
|
||
| Slack | 上游 Ceph Slack workspace:https://ceph-storage.slack.com/ |
|
||
| 综合联系方式 | https://ceph.io/en/community/connect/ (页面标注为 **2023 年 12 月**的当前信息) |
|
||
|
||
### 8.3 `ceph report`:求助时最该附带的东西
|
||
|
||
> **官方 tip**:如果你提供关于问题的详细信息,社区成员就能帮到你。**附上 `ceph report` 命令的输出**,帮助别人理解你的问题。
|
||
|
||
```bash
|
||
ceph report
|
||
```
|
||
|
||
### 8.4 报告 bug 的方式与应提供的信息
|
||
|
||
#### 开 issue
|
||
|
||
- Tracker 新建 issue:**http://tracker.ceph.com/projects/ceph/issues/new**
|
||
- Ceph bug tracker:https://tracker.ceph.com
|
||
|
||
#### 应提供的信息(按来源汇总)
|
||
|
||
| 来源 | 应提供的信息 |
|
||
|---|---|
|
||
| `community.rst` | `ceph report` 的输出 |
|
||
| `troubleshooting-mon.rst`(准备日志) | ① 定位 MON 日志(`ceph-conf --name mon.FOO --show-config-value log_file`);② **先把 debug 提到 `debug_mon = 10`、`debug_ms = 1` 再复现**;③ 记下复现时的日期时间,据此**截取日志片段**(不要甩几十万行无说明的日志);④ 通过邮件列表 / IRC / Slack 联系,或在 tracker 开新 issue |
|
||
| `troubleshooting-osd.rst`(段错误时) | **正在运行的具体 Ceph 版本**、`ceph.conf`(**敏感信息用 XXX 打码**)、monitor status 输出、日志文件摘录;先去 bug tracker 与 `dev`/`ceph-users` 归档搜索是否有人报过 |
|
||
| `troubleshooting-osd.rst`(软件错误时) | 先搜 bug tracker / dev 邮件列表归档(https://lists.ceph.io/hyperkitty/list/dev@ceph.io/)/ ceph-users 归档(https://lists.ceph.io/hyperkitty/list/ceph-users@ceph.io/);无既有结论则向 ceph-devel 报告 |
|
||
| `memory-profiling.rst` | `ms_print massif.out.<pid>` 的输出——**提交 bug report 时很有帮助** |
|
||
| `troubleshooting-pg.rst` | 官方给出的 PG 手工修复 walkthrough:https://ceph.io/geen-categorie/ceph-manually-repair-object/ |
|
||
|
||
#### 求助信息检查清单(把上面整理成可执行清单)
|
||
|
||
```text
|
||
[ ] 1. ceph report 的完整输出
|
||
[ ] 2. ceph health detail 的输出
|
||
[ ] 3. 正在运行的 Ceph 具体版本
|
||
[ ] 4. ceph.conf(把 secrets 用 XXX 打码)
|
||
[ ] 5. monitor status 输出(ceph tell mon.ID mon_status)
|
||
[ ] 6. 相关日志片段(先把 debug_mon=10 / debug_ms=1 打开再复现;
|
||
[ ] 记下复现时刻,据此截取;不要一次发几十万行无说明日志)
|
||
[ ] 7. 复现步骤 + 复现时间
|
||
[ ] 8. 如涉及内存:ms_print massif.out.<pid> 的输出
|
||
[ ] 9. 已做过哪些排查(避免社区重复建议)
|
||
```
|
||
|
||
> 引自:[The Ceph Community](https://docs.ceph.com/en/tentacle/rados/troubleshooting/community/)、[Troubleshooting Monitors](https://docs.ceph.com/en/tentacle/rados/troubleshooting/troubleshooting-mon/)
|
||
|
||
---
|
||
|
||
## 九、排障速查表
|
||
|
||
### 9.1 命令速查(按场景)
|
||
|
||
| 场景 | 命令 |
|
||
|---|---|
|
||
| **第一动作** | `ceph health detail` |
|
||
| 集群状态 | `ceph status` / `ceph -s`;指定 MON:`ceph status -m mymon1` |
|
||
| 集群整体容量 | `ceph df`(**关注最满的 OSD**) |
|
||
| 单 OSD 容量 | `ceph osd df` |
|
||
| 谁 down 了 | `ceph osd tree down` |
|
||
| OSD 拓扑 | `ceph osd tree` |
|
||
| 时钟偏移 | `ceph time-sync-status`(Luminous+) |
|
||
| **—— MON ——** | |
|
||
| MON 状态 | `ceph tell mon.ID mon_status` |
|
||
| 绕过 MON 直连 asok | `ceph --admin-daemon <full_path_to_asok_file> <command>` |
|
||
| 查 asok 路径 | `ceph-conf --name mon.ID --show-config-value admin_socket` |
|
||
| 查 MON 日志路径 | `ceph-conf --name mon.FOO --show-config-value log_file` |
|
||
| 有 quorum 调 debug | `ceph tell mon.FOO config set debug_mon 10/10` / `ceph tell mon.* config set debug_mon 10/10` |
|
||
| 无 quorum 调 debug | `ceph daemon mon.FOO config set debug_mon 10/10` |
|
||
| 还原 debug | 同上命令,把 `10/10` 换成 `1/10` |
|
||
| 查当前配置 | `ceph daemon mon.FOO config show` / `config get 'OPTION_NAME'` |
|
||
| 取 monmap(有 quorum) | `ceph mon getmap -o /tmp/monmap` |
|
||
| 取 monmap(无 quorum) | `ceph-mon -i ID-FOO --extract-monmap /tmp/monmap` |
|
||
| 注入 monmap(危险) | `ceph-mon -i ID --inject-monmap /tmp/monmap` |
|
||
| 停 MON | `service ceph -a stop mon.{mon-id}` |
|
||
| **—— OSD ——** | |
|
||
| 列日志 | `ls /var/log/ceph` |
|
||
| 列 asok | `ls /var/run/ceph` |
|
||
| asok 帮助 | `ceph daemon {daemon-name} help` / `ceph daemon {socket-file} help` |
|
||
| 运行时配置 | `ceph daemon osd.0 config show \| less` |
|
||
| 运行时注入 | `ceph tell osd.0 config set debug_osd 0/5` / `sudo ceph daemon osd.0 config set debug_osd 0/5` |
|
||
| 文件系统空间 | `df -h` |
|
||
| I/O | `iostat -x` |
|
||
| 内核消息 | `dmesg \| grep scsi`;`dmesg -T` |
|
||
| 网络 | `netstat -a \| grep ceph`;`netstat -l \| grep ceph`;`sudo netstat -p \| grep ceph`;`netstat -s` |
|
||
| 硬盘健康 | `smartctl` |
|
||
| 提高线程上限 | `sysctl -w kernel.pid_max=4194303` |
|
||
| 停 OSD 不 rebalance | `ceph osd add-noout osd.0` / `ceph osd set-group noout <bucket>` |
|
||
| 恢复 | `ceph osd rm-noout osd.0` / `ceph osd unset-group noout <bucket>` |
|
||
| 治 flapping | `ceph osd set noup` + `ceph osd set nodown`;恢复 `unset noup` / `unset nodown` |
|
||
| 看 flag | `ceph osd dump \| grep flags` |
|
||
| 调满阈值 | `ceph osd set-nearfull-ratio` / `set-full-ratio` / `set-backfillfull-ratio` |
|
||
| 标记 OSD lost(危险) | `ceph osd lost 1` |
|
||
| 慢请求诊断 | `ceph daemon osd.<id> dump_historic_ops`;`ceph daemon osd.<id> dump_ops_in_flight` |
|
||
| mClock HDD 调优(需重启) | `ceph config set osd osd_op_num_shards_hdd 1`;`ceph config set osd osd_op_num_threads_per_shard_hdd 5` |
|
||
| 起停 | `systemctl stop ceph\*.service ceph\*.target`;`sudo systemctl start ceph.target` |
|
||
| **—— PG ——** | |
|
||
| 列 stuck | `ceph pg dump_stuck stale` / `inactive` / `unclean` |
|
||
| 查 PG 卡因 | `ceph pg 0.5 query` |
|
||
| 列 unfound | `ceph pg 2.4 list_unfound [starting offset, in json]` |
|
||
| 放弃 unfound(危险) | `ceph pg 2.5 mark_unfound_lost revert\|delete` |
|
||
| 修 inconsistent | `ceph pg repair {placement-group-ID}` |
|
||
| 列 inconsistent PG | `rados list-inconsistent-pg rbd` |
|
||
| 列 inconsistent obj | `rados list-inconsistent-obj 0.6 --format=json-pretty` |
|
||
| 强制建空 PG | `ceph osd force-create-pg <pgid>` |
|
||
| 列池编号 | `ceph osd listpools` / `ceph osd dump \| grep pool` |
|
||
| **—— CRUSH ——** | |
|
||
| 列 rule | `ceph osd crush rule ls` |
|
||
| dump rule | `ceph osd crush rule dump erasurepool` |
|
||
| 导出 map | `ceph osd getcrushmap > crush.map` |
|
||
| 反/编译 | `crushtool --decompile crush.map > crush.txt`;`crushtool --compile crush.txt -o better-crush.map` |
|
||
| 离线测映射 | `crushtool -i crush.map --test --show-bad-mappings --rule 1 --num-rep 9 --min-x 1 --max-x $((1024 * 1024))` |
|
||
| 测尝试次数 | 同上再加 `--show-choose-tries` |
|
||
| EC profile | `ceph osd erasure-code-profile set myprofile k=5 m=3`;`... crush-failure-domain=osd` |
|
||
| **—— 剖析 ——** | |
|
||
| heap 启/停 | `ceph tell osd.N heap start_profiler` / `stop_profiler` |
|
||
| heap stats / dump | `ceph tell osd.N heap stats` / `heap dump` |
|
||
| heap 释放 | `ceph tell osd.N heap release` |
|
||
| 看 heap 文件 | `google-pprof --text {path-to-daemon} {log-path/filename}` |
|
||
| 对比两次 dump | `google-pprof --text --base out/osd.0.profile.0001.heap ceph-osd out/osd.0.profile.0003.heap` |
|
||
| Massif | `sudo -u ceph valgrind --max-threads=1024 --tool=massif /usr/bin/radosgw -f --cluster ceph --name NAME --setuser ceph --setgroup ceph`;`ms_print massif.out.12345` |
|
||
| oprofile | `sudo opcontrol --init`;`--setup --vmlinux=... --separate=library --callgraph=6`;`--start`;`--stop`;`--reset`;`opreport -gal ./cmon`;`opreport -cal ./cmon` |
|
||
| **—— 求助 ——** | |
|
||
| 报告 | `ceph report` |
|
||
| 开 issue | http://tracker.ceph.com/projects/ceph/issues/new |
|
||
|
||
### 9.2 关键参数速查(默认值务必与官方一致)
|
||
|
||
| 参数 | 默认值 | 含义 |
|
||
|---|---|---|
|
||
| `mon_osd_full_ratio` | `0.95` | OSD 满阈值,超过则阻止客户端写入 |
|
||
| `mon_osd_backfillfull_ratio` | `0.90` | 超过则不启动 backfill |
|
||
| `mon_osd_nearfull_ratio` | `0.85` | 触发 `OSD_NEARFULL` |
|
||
| `mon-clock-drift-allowed` | `0.05` 秒(50 毫秒) | 最大可容忍时钟偏移 |
|
||
| `osd_op_complaint_time` | `30` 秒 | slow request 告警阈值 |
|
||
| `osd_scrub_auto_repair` | `false` | EC/BlueStore 池自动修复开关 |
|
||
| `osd_scrub_auto_repair_num_errors` | `5` | 自动修复允许的最大错误数 |
|
||
| `osd_op_num_shards_hdd` | `5`(问题配置)→ 改为 `1` | mClock HDD OSD shard 数 |
|
||
| `osd_op_num_threads_per_shard_hdd` | `1`(问题配置)→ 改为 `5` | mClock HDD 每 shard 线程数 |
|
||
| `osd_crush_chooseleaf_type` | `1`(`host`/`node`);单节点改 `0`(`osd`) | CRUSH chooseleaf 类型 |
|
||
| `osd_pool_default_size` | 文档未明确(>2 可致 PG 不 clean) | 池副本数 |
|
||
| `osd_pool_default_min_size` | 文档未明确(两副本场景建议设 `2`) | 允许写入的最小副本数 |
|
||
| `kernel.pid_max` | 通常 `32,000`;可提到 `4194303` | 最大线程数 |
|
||
| `set_choose_tries` | 大于默认值(示例中设 `100`) | CRUSH 选择尝试次数 |
|
||
| `rotate` / 频率 / `size` | `7` / `weekly` / 示例 `500M` | logrotate 设置 |
|
||
| `debug_mon` / `debug_ms`(求助建议值) | `10` / `1` | MON 排障建议 debug 级别 |
|
||
| `CEPH_HEAP_PROFILER_INIT` | `true` 时开机自启 | heap profiler 环境变量 |
|
||
| 每 OSD 内存建议 | 最低 `4GB`,建议 `6GB`→`8GB` | RAM 建议 |
|
||
| MON 端口 | `6789`(互联还含 `tcp/3300`) | 端口 |
|
||
| OSD 端口 | `6800` - `7568` | 端口 |
|
||
| `ITEM_NONE` | `2147483647` | CRUSH 找不到 OSD 的占位值 |
|
||
|
||
### 9.3 场景 → 第一命令 对照表
|
||
|
||
| 你看到的现象 | 第一命令 | 跳转 |
|
||
|---|---|---|
|
||
| 集群报 `HEALTH_ERR/WARN` 但不知道是谁 | `ceph health detail` | 1.2 |
|
||
| `mon.X is down (out of quorum)` | `ceph tell mon.X mon_status` | 3.4 |
|
||
| `clock skew ... > max 0.05s` | `ceph time-sync-status` | 3.6 |
|
||
| MON 日志出现 `Corruption: error in middle of record` | 换 MON / `ceph-objectstore-tool` 恢复 | 3.8 |
|
||
| `1/3 in osds are down` | `ceph osd tree down` + `ls /var/log/ceph` | 4.4 |
|
||
| `1 full osd(s)` / `nearfull` | `ceph osd df` + `ceph df` | 4.5 |
|
||
| OSD 反复 up/down | `ceph osd set noup` + `ceph osd set nodown` | 4.7 |
|
||
| `N slow requests ... blocked for > 30 secs` | `ceph daemon osd.<id> dump_ops_in_flight` | 4.6.12 |
|
||
| mClock HDD 集群慢 + `queued_for_pg` 居多 | 改 shard 配置并错峰重启 OSD | 4.6.14 |
|
||
| PG 一直是 `active+degraded` 不 clean | 查 `osd_pool_default_size` / `osd_crush_chooseleaf_type` | 5.1 |
|
||
| `pg N.N is down+peering` | `ceph pg N.N query` | 5.3 |
|
||
| `N unfound` | `ceph pg N.N list_unfound` | 5.4 |
|
||
| `N pgs stale` | `ceph health detail`(看 `last acting`) | 5.5 |
|
||
| `pg N.N is active+clean+inconsistent` | `rados list-inconsistent-pg <pool>` → `rados list-inconsistent-obj N.N` | 5.8 |
|
||
| EC PG 映射里出现 `2147483647` | `crushtool -i crush.map --test --show-bad-mappings ...` | 5.9 |
|
||
| 想查内存涨在哪 | `heap start_profiler` → `heap dump` → `google-pprof --text --base ...` | 7.2 |
|
||
| 要向社区求助 | `ceph report` + `debug_mon=10/debug_ms=1` 下的日志片段 | 8.3 |
|
||
|
||
---
|
||
|
||
## 文档未明确项
|
||
|
||
本册严格依据 `doc/rados/troubleshooting/` 下 8 个 rst 源文撰写。以下内容**源文未提供**,在此集中列出,避免读者误以为遗漏:
|
||
|
||
| # | 未明确内容 | 所在位置 | 说明 |
|
||
|---|---|---|---|
|
||
| 1 | `ceph -w` 的用法与输出 | 第一章 1.3 | 该命令**未出现在本册 8 个源文中**;官方未描述其行为 |
|
||
| 2 | `log_file`、`log_max_new`、`log_max_recent`、`log_to_file`、`log_to_stderr`、`err_to_stderr`、`log_to_syslog`、`err_to_syslog`、`log_flush_on_exit`、`clog_to_monitors`、`clog_to_syslog` 的默认值与描述 | 第二章 2.8 | `log-and-debug.rst` 仅以 `confval` 列出名字,未在本页展开 |
|
||
| 3 | `mon_cluster_log_*`、`mon_log_max`、`mon_health_to_clog*` 的默认值与描述 | 第二章 2.8 | 同上 |
|
||
| 4 | `osd_debug_drop_ping_probability`、`osd_debug_drop_ping_duration` 的默认值 | 第二章 2.7 / 2.8 | 同上 |
|
||
| 5 | `filestore_debug_omap_check`、`mds_debug_*`、`rgw_*` 各日志项的默认值 | 第二章 2.8 | 同上 |
|
||
| 6 | slow request 相关参数**(除 `osd_op_complaint_time` 及 mClock 两项外)**的完整清单 | 第二章 2.7 | 源文未给出 slow request 参数族的系统列表 |
|
||
| 7 | **leveldb 与 rocksdb 各自的损坏判定与修复差异** | 第三章 3.8.1 | 官方只给了两条通用 `Corruption:` 错误文本,未区分后端 |
|
||
| 8 | `mon_osd_down_out_interval` 的具体默认值 | 第四章 4.7 | 源文只提到 `noout` 会无视其"当前值",未给默认值 |
|
||
| 9 | `mon_osd_down_out_subtree_limit`、`mon_osd_reporter_subtree_level`、`mon_osd_min_down_reporters` 的最优设置 | 第四章 4.7 | 官方明确说"最优设置的推导取决于集群规模、拓扑和 Ceph 版本……**超出了本文档的范围**" |
|
||
| 10 | `osd_pool_default_size` / `osd_pool_default_min_size` 的默认数值 | 第五章 5.1 | 源文只说"大于 2 会导致 PG 不 clean"、"建议设 2",未给默认值 |
|
||
| 11 | PG 处于 **`incomplete`** 状态的成因与排查方法 | 第五章 | **本册 8 个源文中完全没有 `incomplete` 这一 PG 状态**;官方在 `troubleshooting-pg.rst` 只覆盖了 `active`/`degraded`/`remapped`/`down`/`peering`/`stale`/`unfound`/`inconsistent` |
|
||
| 12 | **`snaptrim`** 相关排障(snaptrim 卡住、snaptrim error 等) | 第五章 | **本册 8 个源文中完全没有 snaptrim 内容** |
|
||
| 13 | `set_choose_tries` 的默认值 | 第五章 5.9.3 | 源文只说"设置为**大于默认值**的值",未给具体默认数 |
|
||
| 14 | `osd_op_num_shards_hdd` / `osd_op_num_threads_per_shard_hdd` 是否为**全局默认值** | 第四章 4.6.14 | 源文只说这段排障适用于"配置为 5 和 1"的场景,未说明这是默认还是被改过的值 |
|
||
| 15 | `ceph tell {daemon-type}{daemon-id} heap release` 中**缺少点号**的写法是否笔误 | 第七章 7.3.4 | 源文确实写成 `{daemon-type}{daemon-id}`,本册照录,未作判断 |
|
||
| 16 | `nf_conntrack_max` / `nf_conntrack_buckets` 的推荐具体数值 | 第四章 4.3 | 源文只给关系式 `buckets × 8 = max` 和一个 `echo 131072 > ...hashsize` 示例,未给推荐值 |
|
||
| 17 | `ceph report` 的完整输出结构与字段含义 | 第八章 8.3 | 源文仅建议"附上 `ceph report` 的输出",未展开 |
|
||
| 18 | MON store 恢复脚本中 `$hosts` 变量如何赋值 | 第三章 3.8.3 | 源文脚本直接使用 `$hosts`,未说明其定义方式 |
|