Linux 基础体系 · 第 27/85 篇。示例面向现代主流 Linux 发行版;发行版差异、权限和生产风险会明确说明。

Linux 生产运行手册:容量、变更、监控、应急和复盘

生产运维的目标不是让系统“永远不出错”,而是在资源逐渐消耗、软件持续变更、故障不可避免的条件下,使系统具备可观测、可控制、可恢复和可学习的能力。

本文把生产运行拆成五个相互连接的环节:

  1. 容量:系统还剩多少资源,什么时候会耗尽;
  2. 变更:系统为什么发生变化,变化能否验证和撤销;
  3. 监控:如何从数据中判断系统状态,而不是凭感觉操作;
  4. 应急:故障发生后,如何先恢复服务,再保留证据;
  5. 复盘:如何把一次事故转化为系统、流程和工具的改进。

这些环节不是并列关系。容量不足可能诱发故障,变更可能改变容量增长速度,监控负责发现和定位,应急负责降低影响,复盘则应修改下一次运行的条件。


一、先建立生产运行的基本模型

1.1 “生产状态”是什么

对 Linux 主机或服务而言,生产状态不是某个进程“还在运行”,而是多个条件同时满足:

S=PROCS = P \land R \land O \land C

其中:

  • PP可提供服务,进程、端口和依赖链可用;
  • RR资源可用,CPU、内存、磁盘、inode、文件描述符、网络等没有进入危险状态;
  • OO可观测,能够取得足够的指标、日志和事件;
  • CC可控,可以安全地变更、降级、重启、回滚或恢复。

只要其中一个条件不满足,系统就可能处于“表面正常、实际不可运营”的状态。例如:

  • Web 进程仍然存在,但磁盘写满,无法写访问日志和临时文件;
  • CPU 使用率不高,但大量线程阻塞在 I/O,请求仍然超时;
  • 服务恢复了,但没有保留故障期间的日志,之后无法判断根因;
  • 配置已修改,重启后生效,但没有备份旧版本,回滚只能凭记忆手工恢复。

因此,运行手册必须同时描述状态、证据、操作和恢复路径

1.2 操作前的最小安全边界

任何生产操作至少要回答五个问题:

  1. 目标是什么:恢复请求、释放空间、切换配置,还是采集证据?
  2. 影响范围是什么:单个进程、单台主机、一个可用区,还是整个集群?
  3. 当前状态如何确认:使用什么命令或指标证明操作前状态?
  4. 操作失败如何停止:是否有明确的中止条件?
  5. 如何验证和恢复:成功条件是什么,失败后如何回滚?

对具有写权限的命令尤其如此。rmsystemctl restart、包升级、磁盘挂载、内核参数修改都不是“普通命令”,而是状态转换操作。


二、容量管理:不仅是“磁盘还剩多少”

2.1 容量的定义:可用资源与增长速度

容量是资源在约束条件下继续承载工作负载的能力。生产环境中至少要分别管理:

  • 文件系统数据块;
  • inode;
  • 内存与 swap;
  • CPU 时间;
  • 文件描述符;
  • 进程数;
  • 网络带宽、连接数和端口;
  • 日志、备份、临时文件等特定数据集。

“磁盘还有 20%”并不等于“容量安全”。更准确的判断要考虑增长速度和恢复所需空间。

设当前可用空间为 A0A_0,每天增长量为 gg,预测周期为 TT,增长不确定性为 uu,安全余量为 HH,则需要满足:

A0g×T×(1+u)+HA_0 \geq g \times T \times (1+u) + H

例如:

  • 文件系统当前可用 A0=180 GiBA_0=180\text{ GiB}
  • 每天平均增长 g=8 GiBg=8\text{ GiB}
  • 未来预测 T=14T=14 天;
  • 需要覆盖 u=25%u=25\% 的波动;
  • 预留 H=40 GiBH=40\text{ GiB} 用于临时文件、日志压缩和恢复操作。

则最低需要:

8×14×1.25+40=180 GiB8 \times 14 \times 1.25 + 40 = 180\text{ GiB}

这说明当前容量恰好处在边界上,并不安全。这个推导的关键是:容量告警不能只看当前剩余百分比,还要看消耗速度、波动和故障处理所需空间

2.2 文件系统数据块与 inode 是两种不同的耗尽

文件系统通常同时管理:

  • 数据块:存放文件内容;
  • inode:存放文件类型、权限、属主、大小、时间以及数据块索引等元数据。

大量大文件会耗尽数据块;大量小文件可能先耗尽 inode。后者的典型表现是:

No space left on device

即使 df -h 看起来还有空间,创建新文件也可能失败。

检查两者:

df -hT
df -ih

可能得到:

Filesystem     Type  Size  Used Avail Use% Mounted on
/dev/sda2      ext4   100G   91G  4.0G  96% /

Filesystem      Inodes IUsed IFree IUse% Mounted on
/dev/sda2       6553600 6550000 3600  100% /

第二个结果表示 inode 接近耗尽。此时删除几个大文件可能没有帮助,必须定位并减少小文件数量,例如缓存碎片、失败任务产生的临时文件、异常日志切分文件等。

df 统计的是文件系统视角的已分配块;du 遍历目录项统计可见文件大小,两者不一致是正常现象。常见原因包括:

  1. 文件已删除,但进程仍持有打开文件描述符;
  2. 挂载点下原有文件被挂载后的目录遮蔽;
  3. 稀疏文件的逻辑大小与实际占用不同;
  4. 文件系统元数据、预留块和统计时刻不同。

定位目录占用时:

sudo du -xhd1 /var 2>/dev/null | sort -h

这里:

  • -x 限制在当前文件系统,避免把其他挂载点算进去;
  • -h 使用人类可读单位;
  • -d1 只统计一层目录;
  • sort -h 按容量排序。

df 明显大于 du,检查已删除但仍打开的文件:

sudo lsof +L1

典型输出:

COMMAND PID USER FD TYPE DEVICE SIZE/OFF NLINK NODE NAME
app    2187 app  7w REG  8,2  21474836480    0 1234 /var/log/app.log (deleted)

这里 NLINK=0 说明目录项已经删除,但进程仍持有文件描述符。空间只有在该描述符关闭后才会归还。安全处理顺序通常是:

  1. 确认进程和文件确实属于目标服务;
  2. 检查服务是否支持重新打开日志;
  3. 优先使用服务自身的日志重载机制或 logrotatepostrotate
  4. 若只能重启,先确认高可用、连接排空和回滚路径;
  5. 重启后再次执行 dflsof +L1 验证。

不应直接对生产日志执行 rm,因为删除目录项不一定释放空间,也可能破坏服务正在使用的日志文件。

2.3 稀疏文件与“看起来很大”的文件

创建稀疏文件时,逻辑大小可以远大于实际占用:

truncate -s 100G /tmp/sparse.img
ls -lh /tmp/sparse.img
du -h /tmp/sparse.img

可能看到:

-rw-r--r-- 1 user user 100G ... /tmp/sparse.img
0       /tmp/sparse.img

ls 显示逻辑大小,du 显示实际分配块。备份、复制、压缩工具对稀疏文件的处理方式不同,不能只依据逻辑大小估算容量。

2.4 预留空间、挂载点和权限

ext4 等文件系统通常可能存在面向特权用户的预留块。预留块有助于在普通用户耗尽空间后,仍让管理员登录、写入日志或执行恢复操作,但具体默认值与发行版、文件系统创建参数有关,不能假定所有系统一致。

查看文件系统参数:

sudo tune2fs -l /dev/sda2 | grep -E 'Block count|Reserved block count|Block size'

修改预留比例属于高风险操作,应先确认文件系统类型、设备和业务需要。错误指定设备可能破坏其他文件系统。

另一个常见误区是目录权限。du /var/lib/app 可能因为权限不足而漏报;df 则通常仍能显示文件系统总量。生产排查应记录命令是否以足够权限执行,并注意不要把敏感目录内容输出到公共工单。

2.5 内存容量:使用率不是压力

Linux 的内存包括匿名页、文件页、内核内存、slab、页表、共享内存等。文件缓存并不等于“被应用永久占用”,因此不能简单把 free 的 used 当作可用内存。

free -h

关注:

  • available:内核根据回收文件页等能力估计的可用内存;
  • swap 使用量及其增长趋势;
  • major page fault、直接回收、内存压缩;
  • 是否出现 OOM killer。

查看 OOM 证据:

sudo journalctl -k -g 'oom|Out of memory|Killed process' --since '1 hour ago'

free 的字段含义受 procps 版本影响,不能只凭一列作结论。要结合:

vmstat 1 5
cat /proc/meminfo
ps -eo pid,comm,%mem,rss,vsz --sort=-rss | head

RSS 是进程当前驻留物理内存的近似值,不等同于进程独占内存;共享库和共享页可能被多个进程共同计入。容器环境还要结合 cgroup 限制,因为主机尚有内存并不代表容器没有达到自己的 memory.max。

2.6 容量告警应同时包含阈值和趋势

只设置“磁盘使用率超过 80% 告警”会漏掉快速增长的磁盘。更有用的告警条件包括:

剩余空间<H\text{剩余空间} < H

或:

增长速度>rmax\text{增长速度} > r_{\max}

或:

预测耗尽时间=当前可用空间近期开销速度<Tnotice\text{预测耗尽时间} = \frac{\text{当前可用空间}}{\text{近期开销速度}} < T_{\text{notice}}

其中 TnoticeT_{\text{notice}} 必须大于人工处理、审批、扩容和验证所需时间。阈值不是越低越好:太低会来不及处理,太高则产生大量无效告警。


三、变更管理:把“修改系统”变成可验证的状态转换

3.1 变更不是命令,而是状态机

变更是使系统从旧状态 S0S_0 转移到新状态 S1S_1 的受控过程。一个完整变更包含:

S0准备Sready执行S1验证SacceptedS_0 \xrightarrow{\text{准备}} S_{\text{ready}} \xrightarrow{\text{执行}} S_1 \xrightarrow{\text{验证}} S_{\text{accepted}}

如果验证失败,则进入:

S1回滚S0S_1 \xrightarrow{\text{回滚}} S_0'

这里的 S0S_0' 不一定与原来的 S0S_0 完全相同。例如数据库已经写入新数据、日志已经产生,因此回滚软件版本不等于回滚全部业务状态。

变更前必须区分三类对象:

  1. 代码和软件包:可以通过版本号、包管理器或镜像回滚;
  2. 配置:可通过版本控制和备份恢复;
  3. 数据和外部副作用:常常不能简单逆转,需要兼容迁移、补偿操作或快照恢复。

3.2 先记录基线,再修改配置

以 systemd 服务为例,变更前可记录:

systemctl status myapp --no-pager
systemctl show myapp \
  -p ActiveState -p SubState -p MainPID -p ExecMainStartTimestamp \
  -p Restart -p MemoryCurrent
ss -lntp
journalctl -u myapp --since '30 min ago' --no-pager

这些命令分别确认:

  • 服务是否处于 active/running
  • 主进程 PID 和启动时间;
  • systemd 的重启策略;
  • 当前内存统计(若该属性适用);
  • 监听端口和对应进程;
  • 最近日志是否已有错误。

修改 systemd drop-in 配置时,不应直接编辑 /usr/lib/systemd/system/*.service,因为包升级可能覆盖它。通常使用:

sudo systemctl edit myapp

写入例如:

[Service]
Environment="APP_LOG_LEVEL=info"

然后:

sudo systemctl daemon-reload
sudo systemctl restart myapp

daemon-reload 让 systemd 重新读取单元文件;它不会自动重启服务。restart 才会触发进程生命周期变化。执行后必须验证:

systemctl is-active --quiet myapp
echo $?
systemctl status myapp --no-pager
journalctl -u myapp -b -n 100 --no-pager

systemctl is-active 返回 0 只说明 systemd 认为单元处于活动状态,不代表业务请求一定成功,因此还要进行端口、健康检查或真实请求验证。

3.3 配置变更的原子性

直接执行:

sudo sed -i 's/old/new/' /etc/myapp/app.conf

可能留下半写入文件、错误匹配或不可审计的修改。更安全的通用模式是:

sudo cp -a /etc/myapp/app.conf /etc/myapp/app.conf.$(date +%Y%m%d%H%M%S).bak
sudo install -o root -g root -m 0644 /tmp/app.conf.new /etc/myapp/app.conf
sudo myapp --configtest
sudo systemctl reload myapp

关键点:

  • 先备份旧配置;
  • 生成完整的新文件,而不是依赖模糊替换;
  • 使用 install 同时设置属主和权限;
  • 在服务支持的情况下先执行配置校验;
  • 优先 reload,只有确实需要重新初始化时才 restart
  • 通过业务检查验证生效结果。

对于支持原子替换的场景,可先在同一文件系统写临时文件,再使用 mv 替换。mv 在同一文件系统内通常是原子的目录项操作,但这不代表应用会自动重新读取配置,也不保证跨文件配置的一致性。

3.4 包升级的边界

现代发行版应使用发行版包管理器,例如 Debian 系使用 apt,Fedora/RHEL 系常见 dnf。不要用 curl | sh 替代包管理器,因为这会绕过包签名、依赖关系、文件归属和卸载记录。

变更前确认:

cat /etc/os-release
uname -r
dpkg -l 2>/dev/null | grep '^ii  myapp'
rpm -q myapp 2>/dev/null

不同发行版命令不能混用。systemctljournalctl 在使用 systemd 的系统上常见,但并非所有 Linux 环境都使用 systemd;容器镜像中也可能没有 init 系统。

升级内核、glibc、存储驱动或网络组件时,重启和回滚成本更高。必须明确:

  • 当前运行内核与已安装内核是否不同;
  • 新内核是否已进入引导菜单;
  • 远程主机失联时是否有带外控制台;
  • 旧内核是否仍保留;
  • 变更后如何验证网络、挂载、服务和监控代理。

3.5 并发变更与锁

两个运维人员同时修改同一配置,可能产生“最后写入者覆盖前者”的问题。变更系统至少需要:

  • 唯一变更编号;
  • 操作人和执行时间;
  • 目标主机或实例集合;
  • 当前版本校验;
  • 互斥锁或审批状态;
  • 操作日志。

脚本中可用 flock 实现主机级互斥:

(
  flock -n 9 || { echo "已有相同操作在执行" >&2; exit 1; }
  echo "开始执行变更"
  # 变更命令
) 9>/run/lock/myapp-change.lock

这只能防止共享该锁文件的脚本并发,不能替代完整的变更平台锁,也不能防止人工直接修改。


四、监控:从“有数据”到“能判断”

4.1 指标、日志和事件分别回答什么问题

指标是按时间采样的数值,适合趋势和阈值判断,例如 CPU 使用率、请求延迟、磁盘可用空间。

日志是带上下文的事件记录,适合回答“发生了什么”,例如配置加载失败、连接被拒绝、OOM killer 选择了哪个进程。

事件是状态变化或控制动作,例如 systemd 服务进入 failed、主机重启、磁盘挂载失败。

三者不能互相替代:

  • 指标告诉你延迟上升,但通常不能解释具体错误;
  • 日志能解释错误,但不适合直接计算长期趋势;
  • 事件指出服务重启,但不一定说明重启前的资源压力。

Linux 上常见的数据来源包括:

uptime
vmstat 1 5
iostat -xz 1 5       # 需要 sysstat
ss -s
df -hT
journalctl -p warning..alert -b

这些命令的输出受内核、procps、sysstat 和发行版版本影响,采集系统应记录工具版本与主机身份。

4.2 CPU 使用率不能直接等同于性能

CPU 时间通常分为 user、system、idle、iowait、steal 等类别。高 iowait 表示 CPU 处于等待 I/O 的统计状态,但不能简单推导为“磁盘一定坏了”;它还可能与请求模式、队列、文件系统和虚拟化环境有关。

负载平均值表示一段时间内处于可运行或不可中断睡眠状态的任务数量统计,现代 Linux 中不可中断 I/O 等待也可能计入。因此:

  • 负载高、CPU 高:可能是计算饱和;
  • 负载高、CPU 低:可能是 I/O 阻塞;
  • 负载高但运行队列相对 CPU 数并不大:还要考虑不可中断任务和采样窗口。

查看线程级状态:

ps -eLo pid,tid,psr,stat,pcpu,wchan:32,comm --sort=-pcpu | head -30

D 状态通常表示不可中断睡眠,常见于内核等待 I/O;不能用 kill -9 立即解决,因为信号通常要等任务返回可中断状态后才处理。

4.3 磁盘性能要看延迟和队列,不只看吞吐

iostat -xz 1 5

重点观察:

  • await:请求从提交到完成的平均等待时间,包含服务时间;
  • %util:设备忙碌时间比例,具体解释受设备和内核实现影响;
  • avgqu-sz:平均队列长度;
  • 读写吞吐和 IOPS。

SSD、NVMe、RAID、网络块设备对这些字段的解释不同。%util 接近 100% 在某些并行设备上不一定意味着绝对饱和;应结合延迟、队列、业务请求时延和设备类型判断。

若需要更细的证据,可使用 eBPF 工具,但工具名称、权限和内核能力依赖发行版与部署方式。生产上应先确认工具来源、版本、内核兼容性和观测开销,不应在未知来源脚本上直接执行 root 权限命令。

4.4 监控应围绕用户影响设计

对服务而言,至少需要同时观察:

  • 流量:请求数、连接数、任务数;
  • 错误:HTTP 5xx、业务错误、超时、重试;
  • 延迟:平均值之外的 p95、p99;
  • 饱和度:线程池、连接池、队列、CPU、内存、磁盘和文件描述符。

平均延迟可能掩盖长尾。假设 1000 个请求中 990 个耗时 10 ms,10 个耗时 5 s,则平均延迟为:

990×10+10×50001000=59.9 ms\frac{990\times10+10\times5000}{1000}=59.9\text{ ms}

平均值看起来不高,但 1% 请求已经严重超时。生产监控必须明确分位数的统计窗口、样本量和聚合方式。

4.5 日志链路与磁盘治理

使用 journald 的系统可查询:

journalctl -u myapp --since '2025-01-01 10:00:00' --until '2025-01-01 11:00:00'
journalctl -k -b -1
journalctl -p err..alert -b

其中:

  • -u 按 systemd 单元过滤;
  • -k 只看内核消息;
  • -b -1 查看上一次启动;
  • -p err..alert 按优先级过滤。

日志是异步链路的一部分。应用写日志后,可能经过标准错误、journald、rsyslog、文件轮转、采集代理和远端存储。任何一段阻塞、丢弃或配置错误,都可能造成“应用正常但日志缺失”或“日志写满磁盘”。

生产治理需要明确:

  • 日志格式是否包含时间、主机、服务、请求 ID;
  • 本地保留多久;
  • 轮转如何触发;
  • 压缩是否占用额外空间;
  • 远端发送失败时是阻塞、丢弃还是降级;
  • journald 的持久化目录和磁盘配额;
  • 日志中是否包含密码、令牌、个人数据。

日志轮转后,应用是否重新打开文件是关键。若应用不响应 USR1 或 reload,轮转工具可能需要复制后截断,或安排服务重载。错误的轮转策略会同时造成日志丢失和磁盘不释放。


五、应急响应:先控制影响,再进行高质量诊断

5.1 应急的四个阶段

一次生产故障通常可以抽象为:

flowchart LR
    A[发现异常] --> B[确认影响与范围]
    B --> C[控制扩散]
    C --> D[恢复最小可用服务]
    D --> E[保留证据并持续观察]
    E --> F[解除应急状态]
    F --> G[复盘与改进]

关键路径不是“尽快执行一个熟悉命令”,而是先确认阶段:

  1. 确认:用户是否受影响,影响哪些实例、接口和地域;
  2. 控制:停止继续扩散,例如暂停发布、摘除故障实例、限制流量;
  3. 恢复:选择风险最小、可验证的恢复动作;
  4. 证据:保存日志、指标、配置和变更记录;
  5. 观察:确认恢复不是短暂反弹;
  6. 结束:明确谁宣布恢复,避免过早关闭事件。

5.2 故障时的证据优先级

应急操作可能改变现场。例如重启会丢失进程内存状态,清理日志会破坏时间线,强制终止进程会使栈信息消失。因此在不扩大影响的前提下,先采集:

date -Is
hostname
uptime
ps -eo pid,ppid,user,stat,%cpu,%mem,rss,etime,cmd --sort=-%cpu | head -30
free -h
vmstat 1 5
df -hT
df -ih
ss -s
journalctl -b --since '30 min ago' --no-pager > /tmp/journal-$(date +%s).log

如果故障正在快速扩大,恢复优先级高于完整采集;但至少应记录时间、执行命令、返回结果和操作人。不能把“没有证据”误认为“没有根因”。

5.3 磁盘写满:不同根因决定不同操作

情况一:普通大文件占满

确认:

sudo du -xhd1 /var | sort -h

删除前先判断文件是否属于:

  • 可重新生成的缓存;
  • 已完成且已上传的临时文件;
  • 可安全清理的旧日志;
  • 数据库、队列或业务数据。

不要删除未知目录中的文件。清理后验证:

df -hT /var
df -ih /var

情况二:已删除文件仍被打开

sudo lsof +L1

应通过应用日志重载或服务重启关闭描述符。若使用 copytruncate,虽然可避免重启,但在复制和截断之间可能丢失或重复日志,是否接受取决于应用和审计要求。

情况三:inode 耗尽

sudo find /var -xdev -xdev -type f 2>/dev/null \
  | awk -F/ 'NF>0 {count[$2]++} END {for (d in count) print count[d], d}' \
  | sort -n

这个示例只做粗略一级目录统计,不能替代按目录递归分析;find 遍历大量文件本身也可能带来 I/O 压力。清理目标应优先选择大量小型、可重建、已过期的文件。

情况四:日志系统本身进入异常

检查 journald:

journalctl --disk-usage
systemctl status systemd-journald --no-pager

直接删除 journald 正在管理的文件可能造成状态不一致。应使用发行版和 journald 支持的清理方式,并先确认保留期限、审计要求和远端日志完整性。

5.4 内存压力和 OOM

如果发现 OOM:

journalctl -k --since '1 hour ago' -g 'oom|Out of memory|Killed process'
dmesg -T | grep -iE 'oom|out of memory|killed process'

dmesg 是否可读取受 kernel.dmesg_restrict 和权限影响;在 systemd 系统上,内核日志通常也可通过 journalctl -k 获取。

恢复时不要先盲目增加 swap。swap 可以缓解短时内存峰值,但如果工作集持续超过物理内存,系统可能进入高延迟抖动。应区分:

  • 单进程泄漏;
  • 批量任务同时启动;
  • cgroup memory limit 过小;
  • page cache 正常增长;
  • 内核 slab 或连接缓冲异常增长。

可通过降低并发、暂停非关键任务、摘除异常实例、重启泄漏进程或扩容来恢复。每个动作都必须有业务影响评估;例如杀掉占用内存最多的进程,不一定能恢复服务,反而可能删除数据库或消息消费者。

5.5 服务失败和反复重启

systemctl status myapp --no-pager
systemctl show myapp -p ActiveState -p SubState -p Result -p NRestarts
journalctl -u myapp -b --no-pager

重点区分:

  • 进程启动即退出;
  • 配置语法错误;
  • 端口已被占用;
  • 权限或路径错误;
  • 依赖服务未就绪;
  • systemd 的 Restart= 策略导致重启风暴;
  • 服务其实已运行,但健康检查失败。

反复重启会掩盖原始错误并增加依赖系统压力。若服务正在重启风暴,应在确认高可用和流量切换后暂停重启、摘除实例或临时停止自动恢复,再采集日志和进程状态。修改 Restart= 属于变更,不应在没有记录和验证的情况下永久写入配置。

5.6 高负载但 CPU 不高

诊断顺序可以是:

uptime
vmstat 1 5
ps -eLo pid,tid,stat,wchan:32,comm | awk '$3 ~ /^D/ {print}'
iostat -xz 1 5
ss -s

推导逻辑是:

  1. uptime 判断负载是否异常以及持续时间;
  2. vmstat 判断运行队列、内存回收和 I/O 等待;
  3. D 状态线程指向不可中断等待;
  4. iostat 验证块设备队列和延迟;
  5. ss -s 检查连接堆积和套接字资源。

如果是网络存储、DNS、下游数据库或锁竞争,增加 CPU 通常无效。生产应急的价值在于沿证据链缩小故障域,而不是对所有“慢”都执行扩容。


六、恢复与备份:恢复目标决定操作优先级

备份是对数据的独立副本或可恢复表示;快照通常是某一时刻的块设备或文件系统状态记录;二者都不自动等同于可恢复。

恢复设计需要至少定义:

  • RPO(Recovery Point Objective):最多允许丢失多近时间范围的数据;
  • RTO(Recovery Time Objective):从故障发生到恢复服务允许经过的最长时间。

例如 RPO 为 15 分钟,意味着备份或复制链路必须让数据恢复点不早于故障前 15 分钟;RTO 为 30 分钟,意味着恢复流程、凭据、网络、容量和验证必须在 30 分钟内可执行。

文件级备份、块级备份、数据库逻辑备份和快照适用于不同场景:

  • 文件级备份适合配置、文档和部分应用数据;
  • 块级备份适合完整卷恢复,但可能包含不一致的应用状态;
  • 数据库逻辑备份适合跨版本迁移和选择性恢复;
  • 快照恢复速度较快,但通常依赖原存储系统,不能替代异地独立副本。

加密备份还增加密钥生命周期问题:没有密钥,备份等同于不可读数据;密钥与备份放在同一故障域,也可能同时丢失。

恢复演练必须验证全过程,而不是只验证“备份任务成功”:

  1. 取得备份和密钥;
  2. 在隔离环境恢复;
  3. 校验文件、权限、所有者和时间戳;
  4. 启动依赖服务;
  5. 执行数据库一致性或应用级校验;
  6. 运行真实读写流程;
  7. 测量恢复耗时;
  8. 清理测试环境并记录差距。

恢复测试中的“能解压文件”不等于“业务可用”。


七、复盘:从时间线走向可验证的因果链

7.1 事件、原因和根因不是同一概念

假设事故过程是:

  1. 日志量增加;
  2. 文件系统空间耗尽;
  3. 应用无法写临时文件;
  4. 请求错误率上升;
  5. 值班人员重启应用;
  6. 空间暂时释放,服务恢复;
  7. 数小时后再次发生。

“应用重启”是恢复动作,不是根因。
“磁盘满”是直接故障条件。
“日志增长没有容量预测和有效轮转”是控制缺口。
“监控只告警使用率,没有预测耗尽时间”是发现缺口。

复盘应将事实写成可检验的因果链:

日志增长空间消耗写入失败请求失败\text{日志增长} \rightarrow \text{空间消耗} \rightarrow \text{写入失败} \rightarrow \text{请求失败}

每条因果关系都应由证据支撑,例如磁盘指标、日志时间戳、应用错误码和变更记录,而不是使用“可能”“疑似”替代验证。

7.2 时间线必须区分发生时间和发现时间

至少记录:

时间 事实 证据 操作 结果
10:00 日志增长速率升高 磁盘指标 持续增长
10:35 文件系统达到阈值 df、监控 摘除实例 新请求减少
10:42 应用写入失败 应用日志 清理可重建缓存 空间恢复
10:50 健康检查恢复 探针与业务请求 逐步放量 错误率正常

发生时间、日志落盘时间、监控采集时间和人工发现时间可能不同。时钟同步异常也会破坏时间线,因此生产主机需要监控时间偏移,并在复盘中注明时区和时间来源。

7.3 好的改进项必须改变系统条件

“加强监控”“提高责任心”“注意磁盘空间”不可验证。更有效的改进项具有:

  • 明确对象;
  • 明确动作;
  • 明确完成标准;
  • 明确验证方式;
  • 明确责任人与期限;
  • 明确优先级和风险。

例如,将:

增加磁盘监控。

改为:

/var 采集文件系统可用字节和 inode 可用率;当预测 7 天内耗尽或可用空间低于恢复余量时告警;在测试主机注入日志增长,验证告警在规定时间内触发并能定位到挂载点。

改进项还应优先修复控制链中的薄弱环节:

预防发现遏制恢复学习\text{预防} \rightarrow \text{发现} \rightarrow \text{遏制} \rightarrow \text{恢复} \rightarrow \text{学习}

如果无法预防,至少应更早发现;如果发现后无法快速恢复,就应建设降级、切流或备用容量;如果每次事故都重复发生,则说明复盘没有改变系统。


八、权限、审计与生产风险

Linux 的权限模型包含属主、属组、模式位、ACL、能力(capability)、sudo 策略以及服务管理器的隔离配置。命令是否“能执行”不代表它适合生产执行。

检查关键文件:

stat /etc/myapp/app.conf
namei -l /etc/myapp/app.conf
getfacl /etc/myapp/app.conf 2>/dev/null

namei -l 可以逐级显示路径目录权限;文件本身可读不代表路径上的每一级目录都可访问。

生产脚本应避免:

sudo chmod -R 777 /var/lib/myapp
sudo chown -R app:app /

前者破坏最小权限,后者可能导致系统关键文件属主改变。修复权限必须限定路径、明确目标属主和模式,并在操作前记录原状态。

涉及删除、重启、挂载和防火墙的操作,应使用完整路径或明确参数,避免环境变量、当前目录和通配符造成误操作。例如:

rm -rf /var/cache/myapp/*

仍需确认变量展开、挂载状态和目录是否正确。更安全的脚本应启用严格模式并对变量做校验,但严格模式本身也可能使脚本在非关键命令返回非零时提前退出,必须经过测试后使用。


九、结束条件:一个系统什么时候算“恢复”

“进程起来了”不是完整恢复。至少应同时满足:

  1. 服务管理器状态正常;
  2. 监听端口和依赖连接正常;
  3. 健康检查通过;
  4. 真实业务请求成功;
  5. 错误率和延迟回到可接受范围;
  6. CPU、内存、磁盘、inode、连接池等资源没有继续恶化;
  7. 日志和监控链路恢复;
  8. 变更、应急操作和遗留风险已经记录。

恢复后的观察窗口应覆盖故障触发周期。例如故障由每小时任务触发,就不能只观察五分钟;由每日批处理触发,就需要在后续任务完成后确认。

生产运维的最终产物不是一组命令,而是一条可重复的证据链:

容量状态变更记录监控证据应急动作恢复验证复盘改进\text{容量状态} \rightarrow \text{变更记录} \rightarrow \text{监控证据} \rightarrow \text{应急动作} \rightarrow \text{恢复验证} \rightarrow \text{复盘改进}

当每一步都有明确输入、输出、风险和回退路径时,Linux 主机才真正具备可运营性。


系列导航与关联阅读

官方资料

本文依据 Linux 内核、systemd 与主流发行版官方文档重新梳理;正文与实验由 WR BLOG 编写。