找回密码
立即注册
搜索
发回帖 发新帖

6587

积分

0

好友

798

主题
发表于 8 小时前 | 查看: 5| 回复: 0

一、问题背景

服务器宕机这事,绝大多数时候不是“突然死亡”,而是“前面已经拉了很多次警报,但没人在意”。过去几年我处理过几十起生产环境的“整机不可用”事故,出问题的服务类型覆盖电商交易、支付回调、广告投放、日志采集、消息推送、数据库主库等。一线工程师最怕的不是“机器挂了”,而是“挂了之后我不知道下一步该看哪”。

我自己在 2018 年第一次独立处理一台正式环境的 MySQL 主库宕机时,就吃过这个亏:SSH 连得上去但服务不可用,业务方疯狂催,显示器上 top 跑得飞起,load average 飙到 200+,我盯着屏幕傻眼——CPU?内存?磁盘?网卡?到底是哪一个?那一次复盘后,我给自己定了一条规矩:机器宕机后,先按固定 10 个点的顺序排查,前 5 个点不超过 10 分钟,后 5 个点不超过 30 分钟,全程不跳步。

这套 10 点排查路径后来帮我在多个团队、多个公司多次有效复用。它跟“标准事故响应流程(SIR)”的思路接近,但更贴近“命令级”——每一个点都对应一组具体的命令、一个明确的判断依据、一步可执行的下一步动作。本文的目的是把这套路径完整、可复刻地写出来,让读完文章的初中级运维工程师,即使第一次遇到宕机,也能按部就班往下走。

写之前先把范围说清楚:本文讨论的是“Linux 服务器的整机不可用或关键服务不可用”场景,不限定物理机、虚拟机、还是云主机;排查阶段只到“定位根因 + 给出修复方向”,不深入到具体数据库/中间件的内部调优,因为那要写一整本;10 个排查点之间是“按代价由低到高”的顺序,不是“按可能性从高到低”。

最后给一个明确观点:宕机的根因很少是单点,大多是 1 个表面现象 + 1 个潜伏条件。比如表面是“磁盘写满”,潜伏条件可能是“日志清理 cron 没做或时间配错”;表面是“CPU 100%”,潜伏条件可能是“上游重试风暴 + 连接池没上限”。所以排查时不要在定位到表面后立即收手,要把潜伏条件也找出来,否则会在下个周期重复踩同样的坑。

二、适用场景

本文适合以下读者与场景:

  1. 初中级运维工程师、SRE、DevOps:第一次独立处理生产宕机、想知道“第一步该敲哪条命令”。
  2. 应用开发工程师:oncall 轮到自己时,需要一份可逐条执行的排查清单。
  3. 技术 leader / 值班长:要建立团队层面的“宕机响应手册”,把 10 个点变成团队 SOP。
  4. 云厂商运维、驻场工程师:云主机 vs 物理机的差异本文都会点出来,但落点都在 OS 层。

不适合:

  • 业务方/产品同学读——他们的视角是“业务恢复了没有”,本文是 OS 层视角。
  • DBA 专家读——本文只涉及数据库进程是否在跑、连接是否可达,不会展开 InnoDB/MyISAM 内部。
  • 网络工程师读——本文只在排查点 7、点 8 涉及网络层,不会替代专业的网络设备排查。
  • 安全应急响应——本文不涉及入侵排查、安全取证。

附一个我接团队时常用的“事故严重程度 + 响应窗口”对照表,帮你判断本文方法用在哪一档:

  • P0(整机房/全业务不可用):10 个点全员参与,前 5 个点跑完不超过 5 分钟,整个排查-临时止血闭环不超过 30 分钟。
  • P1(单服务不可用):按本文 10 个点的子集跑,通常 5 个点能定位;但不跳过任何一个点,因为表面现象常常伪装。
  • P2(单实例异常):可以只跑 2-3 个点,但要在事后补一次完整 10 点复盘。
  • P3(亚健康,例如负载偏高、磁盘增长):不在本文主线上,但可以对照“10 个点”反向巡检。

三、核心知识点

3.1 排查顺序学:代价由低到高

所谓“代价由低到高”,是指操作成本、风险、成本、回滚难度。比如:

  • uptime 完全无副作用,先敲。
  • journalctl -xe 读日志,同样无副作用,但读量大,放在第二档。
  • 修改内核参数 sysctl -w ... 副作用大,放到最后档,需要评审。
  • 重启服务、磁盘扩容、回收 inode 都有副作用,必须先确认影响范围再执行。

10 个排查点的顺序就是按这个原则排的——前面 5 个点全是“只读”,不需要改任何东西;后面 5 个点开始涉及“止血”动作,必须带方案和回滚预案。

3.2 现象分类:三档定位

我把常见宕机现象归三类,方便快速分流:

  • 进程类:CPU 高、load 高、某进程僵死/重启、fork 风暴。
  • 资源类:内存 OOM、SWAP 占满、磁盘满、inode 满、fd 耗尽。
  • 依赖类:内核 panic、网卡 down、DNS 失效、上游认证/凭证过期、容器运行时挂、数据库主库切换中。

排查 10 个点正好覆盖这三类的所有“前置信号”。文章后面会按“点”逐条说明。

3.3 关键指标体系

不论是物理机还是云主机,以下指标是必须熟记的,本文之后所有命令都围绕它们:

  • CPU:用户态% / 内核态% / I/O wait% / 软中断%、load average(1/5/15 分钟)、context switch。
  • 内存:可用内存、buffer/cache 占比、commit limit、anon/slab/hugepage 分布。
  • 磁盘:空间使用率、inode 使用率、%util、await、r/s、w/s、队列长度。
  • 网络:带宽、丢包率、重传率、TCP 连接状态分布(ESTABLISHED/TIME_WAIT/CLOSE_WAIT)。
  • 进程:r/b/s 状态进程数、top 进程的 CPU/内存占用、fd 数。
  • 内核:dmesg、kernel panic、OOM、soft lockup、hung_task、RCU stall。

后续排查会反复用到这些维度。

3.4 高风险红线

下面这 7 件事,在排查时必须先看过红线再执行:

  1. 不要先 kill -9 MySQL/Redis/Kafka 主进程——容易丢数据;要先看日志、看优雅退出机制。
  2. 不要直接 rm -rf /var/log/*——要看 logrotate 是否在跑、是否会导致已写入的日志丢失。
  3. 不要先 reboot——物理机 reboot 不一定能进系统;云主机 reboot 可能影响同宿主机其他实例。
  4. 不要在生产盲改 sysctl -p 改完即生效但不可热回滚,要保存原始值。
  5. 不要无证据地“扩容磁盘”——可能 LVM 满、xfs 满只是部分目录。
  6. 不要在没备份前玩数据库——任何 DDL / purge 都要先备份。
  7. 不要“先重启服务再说”——重启可以让表面现象消失,但会丢失故障现场。

后文 10 个排查点会反复回到这 7 条红线。

四、整体排查或实施思路

整体路径我推荐“两段式”——前 5 个点用来“快速定位大类”,后 5 个点用来“深入到具体资源或具体服务”。两段完成后,要再做一次“复盘观察”,避免刚刚定位的根因是表象。

4.1 阶段 A:快速定位大类(5 个点)

这 5 个点是:

  1. 机器是否真的活着?电源、健康状态、IPMI、云厂商控制台。
  2. 系统是否响应 SSH?控制台、串口、console 日志。
  3. load average 和 dmesg 是否有 kernel panic/OOM/soft lockup?
  4. CPU/内存/SWAP 是不是被打爆了?
  5. 磁盘空间 + inode 是不是满了?

每个点跑完都应该回答“是/否”+“如果有,具体哪个”。如果前 3 个点就定位到了,可以提前进入阶段 B 的修复环节,但不要跳过 4 和 5,因为它们常常是共因。

4.2 阶段 B:深入到服务和资源(5 个点)

这 5 个点是:

  1. 关键服务进程是否在跑?(systemd unit、supervisor、k8s pod)
  2. 网络层是否通?(本机 NIC、对端路由、DNS、上游认证)
  3. 磁盘 I/O 是否被打爆?(iostat、biolatency)
  4. fd/线程/proc 是否耗尽?
  5. 是否有定时任务/上游变更引发的雪崩?

这 5 个点的特点是:需要“读业务状态”,不能只看 OS 指标,需要联合业务方/SRE 同事一起判断。

4.3 阶段 C:止血、复盘、固化

排查出根因后,操作流程一定是这个顺序:

  1. 止血(临时把影响收紧):扩容、重启、限流、切换备用、降级——选代价最低的那个。
  2. 修复(真正解决问题):改配置、修代码、扩资源、变架构——一定要走变更流程。
  3. 回滚预案:无论止血还是修复,都要有“怎么回退”的预案,且要在执行前验证。
  4. 复盘(blameless):写事故时间线、延迟原因、监控缺口、流程缺口。
  5. 固化:把这次事故变成监控项、runbook、变更 checklist、值班剧本。

下面进入具体实战。

五、实战步骤(10 个点)

下面把 10 个点逐一展开。每个点都包含:检查目的、推荐命令、典型输出、异常表现、判断逻辑、下一步动作。

5.1 排查点 1:机器是否还活着(电源 / 健康灯 / 控制台)

目的:确认不是物理或虚拟化层出问题。这一步在云厂商占了相当比例——可能是宿主机故障、热迁移失败、磁盘 IO hang、本地盘硬件告警。

推荐命令:

# 1) 物理机/云主机的控制台是否黑屏、云厂商控制台是否显示 "running"
#    (这一步需要人去 Web 控制台看,无法远程命令替代)

# 2) 如果能 SSH,但不放心,先 ping 本机 IP,看是否丢包
ping -c 4 -W 2 <本机内网IP>

# 3) 看机器启动时间,如果太久没重启,本次宕机大概率不是硬件层面的"长死"
uptime
# 期望看到:
#  09:42:01 up 312 days, 14:23,  1 user,  load average: 0.05, 0.10, 0.08

# 4) 看 IPMI(物理机)或厂商带外管理口(云主机的 Serial Console)
#    物理机:BMC / iLO / iDRAC,通过浏览器登录查看传感器读数
#    云主机:在控制台的"实例 → 更多 → 串口控制台"

典型异常表现:

  • 控制台黑屏,但机器本身有电——大概率是 GPU/显卡故障或 BMC 故障。
  • 控制台报 “Hardware Error”——大概率是 CPU/MEM 硬件故障,需要更换硬件。
  • 云厂商控制台显示 “running”,但 SSH 进不去——通常是网络层或内核崩溃,要进 Serial Console 看内核日志。
  • 控制台显示 “stopped”——被人或系统停掉了,要确认是谁停的;如果是云厂商维护/告警触发的停机,要先评估是否要回滚。

判断逻辑:

  • load average 没有意义时(系统不响应),先看电源;一切“系统级的指标”在没电的时候都是噪声。
  • 如果有 BMC/iLO/iDRAC,先看传感器:CPU 温度、主板温度、电源、风扇、电压。
  • 如果是云主机,先看控制台实例状态:running / stopped / rebooting / starting / stopped-after-failure。

深入排查:云厂商控制台状态机差异

不同云厂商对实例状态的语义略有差异,下面把主流厂商的“看似相同但实际不同”的状态列一下,避免误读:

  • 阿里云 ECS:Running 是正常;Stopped 是关机;Starting 是开机中;Stopping 是关机中;Pending 是创建中;Rebooting 是重启中。带外控制台叫“Workbench”或者“ECS 终端”,在控制台“实例” → “更多” → “实例诊断” → “连接终端”。
  • 腾讯云 CVM:RUNNING、STOPPED、STARTING、STOPPING、REBOOTING、PENDING、RESHAPING。带外控制台叫“标准登录 / VNC 登录”,在“实例” → “登录”。
  • AWS EC2:pending、running、shutting-down、terminated、stopping、stopped。带外控制台叫“EC2 Serial Console”,在“Connect” → “Serial Console”。
  • 华为云 ECS:ACTIVE、SHUTOFF、REBOOTING、HARD_REBOOT、STARTING、STOPPING。带外控制台叫“CloudShell / VNC”。

为什么强调这一点:很多事故里,运维同学一看“状态正常”就跳过这一步,结果错过了“实例在重启中”或“实例 5 分钟前刚重启过”的关键信息。带外控制台 / Serial Console 的内核日志和正常 SSH 登入看到的不一样,它反映的是“系统级”视角。

下一步动作:

  • 如果机器没电,联系机房/硬件,这边继续做“动不了的”业务准备——切流量、扩大对外告警文案。
  • 如果机器跑着但控制台 hang,跳到排查点 2(SSH/console 是否可达)。
  • 如果云厂商在“维护中”状态,要立刻查官方公告,看是计划内维护还是事故。计划内维护需要业务方提前收到通知,事故则需要走应急流程。

5.2 排查点 2:系统是否响应 SSH / 是否能进 console

目的:确认“系统本身活着,但某些入口挂了”的具体状态。

推荐命令:

# 1) 先看 SSH 是不是被卡死(网络通,但 sshd 不响应)
#    可以用别的 IP / 别的端口尝试;如果是云主机,在控制台尝试 VNC/Serial Console
ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no <user>@<ip> 'date; uptime; who -b'

# 2) 如果 SSH 进不去,试试 console(物理机接显示器,云主机进 Serial Console)
#    进去后第一件事是看 dmesg 和当前 TTY 是否还活着

# 3) 看 systemd 启动的 sshd 是否活
systemctl status sshd
# 期望看到:
#  active (running)

# 4) 看登录历史与上次登录时间
last -n 20
who

典型异常表现:

  • SSH 完全连不上,但同网段其他机器能互相通——大概率是本机 sshd 挂了、防火墙把自己锁了、socket 占满。
  • SSH 连接超时,但同一台机器 web 服务可能还活着——sshd 单独挂,排查点 6 一起看。
  • 进了 console 后 journalctl 没日志、TTY 输出混乱、shell 退格不可用——大概率是内核层面的问题,跳到排查点 3。

判断逻辑:

  • SSH 不通但控制台通:sshd 自己挂了,但系统活。先抢救控制台,在控制台里救 sshd 或看其他服务。
  • 控制台也不通,但机器有电:内核挂死,跳排查点 3。

下一步动作:

  • 如果是 sshd 挂,在控制台 systemctl restart sshd,但要先看失败原因(journalctl -u sshd -n 200)。
  • 如果是控制台也不通,准备好物理/带外通道,直接进入排查点 3。
  • 如果 SSH 慢但能连,优先看 sshd 的 MaxStartups 配置:cat /etc/ssh/sshd_config | grep -iE 'MaxStartups|MaxSessions',默认值在并发连接高时不够。

深入排查:sshd 自身故障树

sshd 挂掉的常见原因按概率从高到低:

  1. 进程数打满:MaxStartups 默认 10:30:60,如果 SSH 连接数超过这个值,新的连接直接被拒绝。/var/log/auth.log 或 journalctl -u sshd 会出现 sshd: refused connect from ... (MaxStartups)。
  2. ListenAddress 改错:在多 IP 机器上,如果 sshd 监听了某个不再可达的 IP,会出现“无法登录”。ss -tlnp | grep sshd 看实际监听端口。
  3. PAM 模块故障:/etc/pam.d/sshd 改错或者 /etc/shadow 损坏,会导致密码登录失败但密钥登录也可能出问题。journalctl -u sshd 会有 PAM ... Authentication failure。
  4. 主机密钥变更:曾经 SSH 进去过,后来重装系统,客户端会因 host key 校验失败拒绝连接。这种“看上去登不上”的现象对老员工是常见误区。
  5. selinux / apparmor 阻止 sshd:SELinux 模式下,文件标签错误会让 sshd 起不来。ausearch -m avc -ts recent | grep sshd 看 SELinux 拒绝记录。
  6. 资源耗尽:MaxSessions、MaxStartups、fd 数三者都打到上限。
  7. 被 fail2ban / sshguard 误锁:长期反复登录失败触发了 fail2ban,把可信任的 IP 也封了。fail2ban-client status sshd 看。

判断逻辑进阶:

  • 完全连不上 + 控制台 sshd 状态 active:大概率是防火墙/路由层。iptables -L -n -v | grep :22 或 nft list ruleset | grep 22。
  • 连得上但立刻断开:检查 sshd 配置里的 ClientAliveInterval、ClientAliveCountMax,以及客户端 ~/.ssh/known_hosts 是否过期。
  • 密码正确但拒绝登录:/var/log/auth.log 会有 Failed password 或 Permission denied (publickey),对照 sshd 配置里的 PasswordAuthentication / PubkeyAuthentication。

5.3 排查点 3:内核是否挂死 / 是否有 panic、OOM、soft lockup

目的:把“内核层事故”和“用户态事故”区分开。这一步非常关键,因为内核事故和用户态事故的处理方式完全不同。

推荐命令:

# 1) 关键命令:dmesg,看内核环形缓冲区
dmesg -T | tail -200
# 期望看到:没有 Out of memory、kernel panic、soft lockup、Bug、segfault

# 2) 直接搜"故障关键词"
dmesg -T | grep -Ei 'panic|oom|lockup|bug|segfault|hung_task|rcu|stall' | tail -50

# 3) 如果是 systemd 的机器,看 journal
journalctl -k -n 200 --no-pager
# 等价于 dmesg 但更完整(systemd 会把内核日志统一管理)

# 4) 看"上次启动的退出原因"
journalctl -b -1 --no-pager
# -b -1 表示"上一次启动"的日志
# 内核挂死导致重启后,这里通常能看到 panic trace

# 5) /var/log/messages 或 /var/log/syslog(老式发行版)
tail -200 /var/log/messages
grep -Ei 'panic|oom|lockup|bug' /var/log/messages | tail -50

# 6) 看 vmcore(如果有 kdump)
ls -lh /var/crash/
# 有 dump 文件,意味着内核真的挂了

典型异常表现:

  • Out of memory (oom_kill) 出现,且反复出现——是内存不足在杀进程。
  • kernel panic - not syncing——内核挂了,机器很可能是自动重启过。
  • RCU CPU N stall detected——RCU 锁等待,通常有 CPU 被卡住。
  • BUG: soft lockup - CPU#N stuck for Ns——某 CPU 长时间不出让调度。
  • hung_task: blocked tasks——有进程在内核态阻塞(常见磁盘卡死)。
  • watchdog: BUG: soft lockup——和上一条相近。

判断逻辑:

  • 有 OOM 但有针对性:大概率是某个进程内存泄漏,联合排查点 4 一起找。
  • 反复 OOM:大概率是系统可用内存不够或某进程未配限额。
  • lockup / stall / hung_task:大概率是 CPU 卡在内核态,联合排查点 8(磁盘 I/O)一起看。
  • panic:已经重启过,优先找 panic traceback 和触发函数。

下一步动作:

  • 如果是 OOM:先止血——给该进程限内存或重启;再排查——看 /proc/PID/status、smem --sort=rss。
  • 如果是 panic:先保留 vmcore(防止被自动清理),再找调用栈和触发条件。
  • 如果是 stall/hung_task:联合排查点 8 磁盘 I/O——几乎都跟磁盘 hang 相关。

深入排查:OOM 详细分析

OOM killer 触发时,内核会按 oom_score 选择一个“代价最小”的进程杀掉。oom_score 越高越容易被杀,影响因素包括 RSS、子进程数、是否 root、是否特权等。可以通过 /proc/<PID>/oom_score 和 /proc/<PID>/oom_score_adj 看到和调整。

调整某个进程“不被 OOM killer 杀”:

# 让数据库主进程不被 OOM killer 杀
echo -1000 > /proc/<mysqld-pid>/oom_score_adj
# 或者用 systemd unit
[Service]
OOMScoreAdjust=-1000

但要小心,如果这个进程就是“内存泄漏的元凶”,反而会让系统更慢。

panic 后保留 vmcore:

# 确认 kdump 是否启用
systemctl status kdump
# 如果没启用,先 enable:
systemctl enable kdump
systemctl start kdump
# crash 内核是否配置了足够的内存
cat /proc/cmdline | grep crashkernel
# 重启后看上次是否有 dump
ls -lh /var/crash/
# 解析 crash 文件
crash /usr/lib/debug/lib/modules/$(uname -r)/vmlinux /var/crash/127.0.0.1-2024-01-01-00:00:00/vmcore
# 进去后 bt 看调用栈

soft lockup / hung_task 详细定位:

# 触发 stall 的进程
ps -eo pid,comm,stat | grep -E ' D|R '
# 看 /proc/PID/stack(需要 root,且内核开启了 CONFIG_STACKTRACE)
cat /proc/<PID>/stack
# 看 task 在哪个内核函数上
# 经典栈:
#   [<ffffffff8123a0b0>] wait_on_page_bit_killable+0x80/0xc0
#   [<ffffffff8123a3a1>] shrink_inactive_list+0x261/0x470
#   ... 表示在等 I/O

进一步:内核态 CPU 占用高(sys 高)的诊断:

# 抓 5 秒 perf 数据
perf record -a -g -F 99 sleep 5
perf report
# 进入 interactive 模式,看热点函数
# 如果热点是 spin_lock / mutex_lock 之类的锁函数,说明是锁竞争
# 如果热点是 _copy_to_user / _copy_from_user,可能是大块数据拷贝
# 如果热点是 tcp_sendmsg / tcp_recvmsg,可能网络层繁忙

如果是 NFS 客户端卡死:

NFS 客户端的 hung_task 很常见,因为 NFS 默认是硬挂载,服务端不可达会让客户端 hang。临时解决:mount -o remount,soft,timeo=10,retrans=3 /mnt/nfs,但会丢数据,慎用。长期解决:换 NFSv4、加 cache、换分布式文件系统。

5.4 排查点 4:CPU / 内存 / SWAP 是否被打爆

目的:这是最经典的“用户态资源竞争”线索来源。Load average 高、CPU 高、内存高、SWAP 高,任意一个都会让“机器看上去像挂了”。

推荐命令:

# 1) load average 大方向
uptime
# 期望看到 load average 与 CPU 核数一致或更低。
# 例如 4 核 CPU,load 平均高于 8 就要警惕;长时间高于 16 基本是有问题。

# 2) CPU 详情
top -b -n 1 | head -30
# 关注:
#   %Cpu(s): us, sy, ni, id, wa, hi, si, st
#   wa 高:磁盘 I/O 等待
#   sy 高:内核态占用高(系统调用/锁)
#   hi/si 高:硬/软中断

# 3) 内存详情
free -h
# 关注:available 是否够
# 注意:不要只看 "used",还要看"available"——后者才是"真可用"

# 4) SWAP 使用
swapon --show
cat /proc/meminfo | grep -E 'SwapTotal|SwapFree|SwapCached'

# 5) 单进程视角:top + 按内存排
top -b -n 1 -o %MEM | head -30
# 或者
ps -eo pid,ppid,user,pcpu,pmem,rss,vsz,comm --sort=-pmem | head -20

# 6) OOM killer 日志(辅助排查点 3 的 OOM)
dmesg -T | grep -i 'oom\|killed process' | tail -30

# 7) 内核参数 vm.overcommit_memory 是否配错(写 1 会让 OOM 概率升高)
sysctl vm.overcommit_memory
sysctl vm.overcommit_ratio

典型异常表现:

  • wa > 30%:CPU 多半在等磁盘,跳排查点 8 一起看。
  • sy > 30%:内核态繁忙,可能是锁、软中断、设备中断过多(/proc/softirqs、/proc/interrupts)。
  • available < 总内存 10%:内存吃紧,看是哪个进程在占。
  • SwapUsed > 0 且持续上升:已开始落 SWAP,内存是真的不够了。
  • SwapUsed > SwapTotal * 80%:快满了,程序可能在大量页调入/调出。
  • OOM killer 频繁触发:有进程超过 cgroup 限额或物理内存。

判断逻辑:

  • CPU 100%(us 高)+ 某个进程占满:大概率是应用层问题(比如某个 worker 死循环、某个查询太重)。
  • CPU 100%(sy 高)+ 上下文切换高:可能是锁/中断/系统调用问题,要查 /proc/interrupts 和 perf。
  • CPU 100%(wa 高):磁盘卡死,跳排查点 8 一起看。
  • 内存高 + 频繁 OOM:应用层泄漏或没限内存。
  • 可用内存低 + SWAP 在涨 + 整机慢:典型“内存吃完开始 SWAP,SWAP 又慢,系统被拖垮”模式。

下一步动作:

  • us 高:top 进程 → pidstat -p PID 1 → 拿到进程栈用 pstack/py-spy/jstack 进一步看。
  • sy 高:/proc/softirqs 看哪个软中断高;perf top -g 找热点;cat /proc/interrupts 看哪个硬件中断高。
  • wa 高:跳排查点 8。
  • 内存高:pmap <PID> | sort 看进程的内存段,smem --sort=rss 看进程汇总。

深入排查:内存子系统与 SWAP

内存类的故障要细看几类内存,它们行为不同:

  • RSS (Resident Set Size):进程实际占用的物理页。
  • VSZ (Virtual Size):进程虚拟地址空间,通常不准确,不要用。
  • PSS (Proportional Set Size):按共享比例分摊后的内存,反映“实际使用量”,smem 默认就是 PSS。
  • USS (Unique Set Size):进程独占的内存,通常反映“是否泄漏”。
  • Cache:内核 page cache,被回收,通常不影响应用;但内核会把匿名页换到 swap。

判断“内存是否真吃紧”的关键指标是 MemAvailable,而不是 MemFree:

# 详细内存指标
cat /proc/meminfo
# 重点字段:
#   MemTotal:总物理内存
#   MemFree:空闲内存
#   MemAvailable:可用内存(含可回收的 cache,推荐用这个)
#   Buffers:块设备 buffer
#   Cached:page cache
#   SwapCached:已换出但还在内存里的页
#   Active / Inactive:活跃 / 不活跃的页
#   Dirty:脏页(未刷到磁盘)
#   Writeback:正在回写的页
#   Mapped:被 mmap 的页
#   AnonPages:匿名页(进程堆栈、堆)
#   Slab:内核 slab 分配器
#   SReclaimable:可回收的 slab
#   SUnreclaim:不可回收的 slab
#   KernelStack:内核栈
#   PageTables:页表
#   CommitLimit / Committed_AS:承诺分配 / 已分配(虚拟内存)

AnonPages 高 + SwapUsed 上升:应用层在吃内存,而且匿名页被换出。

Cached 高 + MemAvailable 高:只是 page cache 占的多,不算内存吃紧,这种“低 available”会随着文件读写变化。

Slab 高 + SUnreclaim 高:内核自身在吃内存,常见 dcache 缓存,可以 slabtop 看具体哪个 cache 占。

vmstat 的内存指标:

vmstat 1 5
# 输出解读:
#   swpd:已用 swap(若持续上升,则内存不足)
#   free:空闲内存
#   buff:buffer
#   cache:page cache
#   si:从 swap 换入的页(>0 表示内存不够,需要从 swap 换入)
#   so:换出到 swap 的页(>0 表示匿名页被换出)
#   bi:从块设备读入的块
#   bo:写到块设备的块
#   in:每秒中断
#   cs:每秒上下文切换
#   us/sy/id/wa/st:CPU 占比

如果 si/so 持续 > 0:内存真的吃紧,需要尽快找出 RSS 高的进程,决定是杀、降配还是扩。

Swap 策略调优(止血用):

# 临时把 swappiness 调低(让内核更倾向用内存,而不是 swap)
sysctl vm.swappiness=10
# 持久化
echo 'vm.swappiness=10' >> /etc/sysctl.d/99-swap.conf
sysctl -p /etc/sysctl.d/99-swap.conf
# 注意:swappiness=0 反而会在内存吃紧时延迟换出,造成"内存压力突然爆"的体验;10 是个常用折中值。

5.5 排查点 5:磁盘空间 + inode 是否满了

目的:磁盘满和 inode 满在故障表现上很像,但根因完全不同。这一步几乎是“运维铁律”——任何宕机排查都要做。

推荐命令:

# 1) 磁盘空间
df -hT
# 期望看到:使用率 < 80%;90% 警戒;95% 业务已受影响

# 2) inode 使用率(常被遗忘)
df -i
# 期望看到:Use% < 80%
# inode 满会表现为 "磁盘写不进去" 但 df -h 看空间还有

# 3) 看哪个目录用得多(从 / 开始往下找)
du -sh /* 2>/dev/null | sort -h | tail -20
# 注意:这条递归扫描很慢,生产慎用,建议限定 /data、/var 这类业务目录

# 4) 限定路径的 du(更实用)
du -sh /var/log/* 2>/dev/null | sort -h | tail -20
du -sh /var/lib/docker 2>/dev/null
du -sh /home 2>/dev/null

# 5) 看磁盘空间趋势(如果有监控就更好,这里是命令版近似)
#    用 ls 看最老的几个文件
ls -ltr /var/log/ | tail -20

# 6) 找最近 7 天内写过的大文件(常见日志暴增)
find /var/log -type f -mtime -7 -size +100M -exec ls -lh {} \;
find /data -type f -mtime -7 -size +1G -exec ls -lh {} \;

# 7) 看挂载点(避免出现"挂载遮蔽":某个 mount 把另一个目录盖了)
mount | grep -E '/data|/var'
findmnt /data /var

# 8) 看大文件 + 已删除但还被进程占着(经典坑)
lsof +L1 | sort -k7 -n -r | head -20
# 显示已 unlink 但文件描述符未释放的文件

典型异常表现:

  • Use% 100%:磁盘已满,业务写入会失败。
  • Use% 看起来正常 + 写入失败:df -i 看 inode;mount 看是否只读 mount。
  • 某个目录(例如 /var/log)du 一下几十 G:logrotate 没配或归档目录没清理。
  • /var/lib/docker 巨大:docker 镜像层、log driver 的 json log 没清理。
  • 磁盘写满但重启后恢复:很可能是 log/coredump/heap dump 把磁盘打满后又被重启清掉,所以“看上去恢复了”。

判断逻辑:

  • 磁盘使用率 > 95%,且是 /var:大概率是日志、journal、core、ring buffer 之类的累积。
  • 磁盘使用率 < 80%,但写入失败:几乎肯定是 inode 满、mount 只读、磁盘配额(quota)。
  • 磁盘写满但 lsof +L1 显示有大文件被删除:进程持有 fd,空间其实已经被释放但要重启进程才能回收。

下一步动作:

  • journalctl --vacuum-size=200M(清理 systemd journal 老日志,只清 journal,不删业务日志)。
  • 改 logrotate 配置,但先确认是否有备份/归档需要。
  • Docker:docker system prune -a --volumes 前必须确认没有持久化卷(这一条在排查点 6 / 点 9 还会反复强调)。
  • inode 满:通常是 /var/spool/postfix/maildrop、/var/lib/php/sessions 这种小文件累积,*慎用 `rm -rf /var/spool/postfix/maildrop/`,要看 postfix 状态**(排查点 6 会再展开)。

深入排查:磁盘满的“看不见的凶手”

磁盘满常常有几个隐性的根因,新同学容易漏:

  1. 被进程持有但已 unlink 的大文件:
# 找已 unlink 但还被进程持有的文件
lsof +L1
# 字段解读:
#   COMMAND  PID  USER   FD  TYPE  DEVICE  SIZE/OFF  NLINK  NODE  NAME
#   mysqld   1234 mysql  5u  REG   253,1   12345678  0      5678  /tmp/ibtmp1 (deleted)
# 这里 SIZE/OFF 表示文件大小,即使 NAME 显示 (deleted),空间仍被占用。
# 解决:让 mysqld 自己 truncate 临时表空间,或者重启 mysqld(高风险,要做主从切换)。
  1. Docker 镜像和容器层:
# 看 docker 占多少
docker system df
# 列每个镜像/容器/卷的大小
docker system df -v
# 看是不是有过多的 <none> 镜像
docker images | grep '<none>'
# 看是否有 stopped 容器在占空间
docker ps -a --filter 'status=exited'
# 注意:docker system prune 会删除所有停止的容器、网络、悬空镜像、悬空构建缓存
# 如果有命名卷挂载,默认不会删(但 --volumes 会删,这是高风险)
  1. NFS / CIFS 挂载点的“看不到”的占用:

挂载点覆盖后,原本磁盘的内容还在,但被 mount 遮蔽。如果做了 df -h 看挂载点的占用,会显示 mount 的源(网络磁盘)的占用,本地盘的真实占用被遮蔽。用 du 时要绕过 mount。

  1. LVM thin pool 满:
# 看 LVM 信息
pvs
vgs
lvs
# 重点:Data% 列,接近 100% 会让所有 LV 写入失败
# 解决:扩展 VG,或者清理 thin pool 里的快照
  1. 文件系统预留块(reserved blocks):

ext4 默认会保留 5% 给 root 用户,这部分空间对 df 来说“已用”,但对普通用户不可见。如果磁盘看似 100%,但应用报“磁盘满”,可能是这 5% 被用光。tune2fs -m 1 /dev/sda1 可以调到 1%。

  1. tmpfs 满了:

/tmp、/dev/shm、/run 都是 tmpfs,会占物理内存。如果这些目录写满了,会出现“磁盘满但 df 不显示”的现象。df -hT | grep tmpfs 看大小。

inode 满的常见来源:

# 找 /var 下小文件最多的目录
find /var -xdev -type d -exec sh -c 'echo $(ls "$1" | wc -l) "$1"' _ {} \; | sort -rn | head -20
# 找 /tmp
find /tmp -xdev -type d -exec sh -c 'echo $(ls "$1" | wc -l) "$1"' _ {} \; | sort -rn | head -20
# 通常:邮件队列、PHP 会话、临时文件、应用 cache

5.6 排查点 6:关键服务进程是否在跑

目的:这一节是“OS 层 → 服务层”的过渡。OS 指标都是绿的,但服务层可能挂了,所以一定要看进程。

推荐命令:

# 1) systemd unit 状态(现代 Linux 主用)
systemctl list-units --type=service --state=failed
systemctl list-units --type=service --state=running
# 列失败的 unit,以及它们失败的原因

# 2) 单个 unit 的状态
systemctl status nginx.service
systemctl status mysqld.service
systemctl status docker.service
systemctl status kubelet.service

# 3) 经典老式 sysv / supervisor / tini
service --status-all 2>/dev/null
supervisorctl status

# 4) 在 Kubernetes 节点上,看节点状态(很重要)
systemctl status kubelet
journalctl -u kubelet -n 200 --no-pager

# 5) 用 ps 看核心进程
ps -ef | grep -E 'nginx|mysql|redis|kubelet|dockerd|node' | grep -v grep

# 6) 容器/进程之间的关系
pstree -ap | head -50

# 7) 看某个进程最近是不是反复重启
#    systemd 的 RestartSec / Restart 计数
systemctl show nginx.service | grep -E 'NRestarts|Restart='

# 8) 服务监听的端口有没有起来
ss -tlnp
# 期望看到:在监听的端口与正常业务端口一致
# 如果业务端口没在 LISTEN,服务可能没起来

# 9) 对进程做 lsof,看它在用什么文件
lsof -p <PID>

典型异常表现:

  • systemctl status xxx 报 active (failed)、inactive (dead)、auto-restart failed:服务在重启但失败。
  • NRestarts 高:服务在不停重启,通常是启动失败 / 启动后立刻 crash / OOM 后被 systemd 重启。
  • ss -tlnp 没有业务端口:服务没起来或者绑在别的 IP。
  • kubelet 是 running 但 kubectl get nodes 是 NotReady:kubelet 在跑但失联(常见 CNI/认证问题)。
  • 进程都活但流量进不来:大概率是网络层,跳排查点 7。

判断逻辑:

  • 运行状态 OK + 端口在听但业务报错:配置错、依赖缺失、上游不通。
  • 运行状态 failed:启动日志里一定写了原因,直接看 journalctl -u xxx -n 500。
  • 运行状态 OK + 端口未 listen:可能是配置文件 bind 没生效、权限、SELinux、capability。

下一步动作:

  • 启动失败:journalctl -u xxx -n 500 --no-pager 看完整启动日志。
  • 端口未监听:nginx -t、mysqld --help --verbose、redis-cli config get bind 验证配置。
  • kubelet NotReady:journalctl -u kubelet、crictl ps -a、cni 排查。

深入排查:systemd unit 启动失败的常见原因

服务启动失败一般 5 分钟内能定位到根因,因为 systemd 会把 stderr/stdout 收集起来。重点看这几个信号:

# 启动失败的 unit 详细信息
systemctl status <unit> -l --no-pager
# -l 让完整日志不省略

# 启动失败时的最近 200 行日志
journalctl -u <unit> -n 200 --no-pager -p err

# 看是否是依赖启动失败
systemctl list-dependencies <unit>

# 看进程退出码
systemctl show <unit> -p ExecMainStatus,ExecMainCode,Result
# ExecMainStatus=2 是协议错,ExecMainStatus=127 是 not found,ExecMainStatus=1 是普通失败

Unit 启动失败的常见原因清单:

  1. Type 配置错:Type=notify 但程序不支持 sd_notify;Type=forking 但程序不退到后台。修正:对照程序文档,改 Type。
  2. ExecStart 路径错:/usr/bin/myapp vs /usr/local/bin/myapp,which <program> 看一下。
  3. 环境变量缺失:Environment=KEY=VALUE 没配,程序报“key not found”。修正:把环境变量加到 unit 文件,或 EnvironmentFile=/etc/myapp.env(后者权限 600)。
  4. 权限不足:User=myapp 但 /opt/myapp 是 root 所有。修正:chown -R myapp:myapp /opt/myapp,或者把 unit 的 User 改成 root(慎用)。
  5. 资源限制:MemoryMax、CPUQuota 设得太紧,启动时就被 cgroup 杀掉。
  6. SELinux / AppArmor:audit2why < /var/log/audit/audit.log 看 SELinux 拒绝记录。
  7. 配置文件语法错:Nginx 用 nginx -t,Apache 用 apachectl configtest,MySQL 用 mysqld --verbose --help 看配置加载错误。
  8. 依赖循环:A 依赖 B,B 依赖 A。systemd-analyze verify <unit> 看依赖图。
  9. Listen 端口被占:bind 80 端口,被 Apache 占着,起不来。ss -tlnp | grep :80。

Restart=on-failure 的副作用:

如果服务本身有 bug(比如启动后立刻崩),systemd 会按 RestartSec=5s、StartLimitBurst=10、StartLimitIntervalSec=60s 反复重启,导致:

  • 监控看进程在跑,但每次都是新进程,业务间歇性挂。
  • 日志被快速覆盖,无法保留故障现场。
  • 资源被浪费在“启动-死-启动”循环。

建议:先把 Restart 改成 no,把问题修好,再改回 on-failure。

如果服务进程在但 CPU/内存都不高:

可能的原因:

  • 服务卡在某个系统调用上,例如等磁盘 I/O(见排查点 8)、等锁(见排查点 9)、等上游(见排查点 7)。
  • 服务的 worker 线程都满,无法接新请求,但还没到 OOM。jstack <PID> 看线程状态。
  • 服务在做 GC。jstat -gc <PID> 1s 看 GC 状态。

5.7 排查点 7:网络层是否通(本机 NIC / 路由 / DNS / 上游)

目的:服务活但不响应,大概率是网络层。先定位“卡在哪一跳”。

推荐命令:

# 1) 本机 NIC 与 IP
ip -br addr
ip -br link
# 期望:所有应该 up 的 NIC 都是 UP/UP

# 2) 路由表
ip route
# 期望:有 default via <gateway>

# 3) 邻居(ARP/ND)
ip neigh
# 期望:本子网邻居是 REACHABLE;可能有 INCOMPLETE 是 arp 解析失败

# 4) DNS 解析
cat /etc/resolv.conf
getent hosts baidu.com
dig +short baidu.com   # 需要 bind-utils
nslookup baidu.com     # 兼容性更好

# 5) 联通性测试
ping -c 4 -W 2 <gateway>
ping -c 4 -W 2 <上游IP>

# 6) 端口连通性
ss -tn dst <上游IP>:<端口>
# ESTABLISHED 表示已连上
# 业务端口:看 SYN_SENT / SYN_RECV 多不多

# 7) TCP 连接状态分布
ss -s
# 期望:大量 ESTABLISHED 正常,TIME_WAIT 是正常的连接回收,CLOSE_WAIT 多了是有进程没关连接(代码 bug)

# 8) 防火墙
iptables -L -n -v | head -50
# 或 firewalld:
firewall-cmd --list-all
# 或 nftables:
nft list ruleset | head -80

# 9) 用 tcpdump 抓包(慎用,生产要控制 pcap 大小)
tcpdump -i eth0 -nn -s 0 -w /tmp/cap.pcap host <上游IP> and port <端口>
# 抓 30 秒后用 wireshark / tshark 分析

典型异常表现:

  • 网卡 state DOWN / NO-CARRIER:网线或光模块问题。
  • default route 缺失:网关被人改、防火墙误配、DHCP 没拉到。
  • getent hosts 慢、DNS 查询超时:DNS 服务挂了、resolv.conf 写错。
  • ESTABLISHED 少、SYN_SENT 多:网络层丢包或对端不响应。
  • TIME_WAIT 多:net.ipv4.tcp_max_tw_buckets 用满或连接被频繁短连接占用。
  • CLOSE_WAIT 多:对端关了但本端没 close()——典型代码 bug。
  • iptables -L 看到 DROP/REJECT 自己的 IP:经典坑——防火墙把自己锁死。

判断逻辑:

  • 业务端口在 LISTEN + 客户端连不上:防火墙上没放行 / NAT 没回流 / 公网 IP / 负载均衡器层。
  • 业务端口在 LISTEN + 客户端能到但慢:带宽、TCP 重传、网络设备性能。
  • 整段网络不可达 + 宿主正常:同集群其他机器能不能访问对方?有没有跨网段路由?
  • 只有 DNS 慢/失败:DNS 通道问题,业务可能被卡在 getaddrinfo。

下一步动作:

  • 网卡 down:ip link set eth0 up,同时查为什么 down——被 ifdown / NetworkManager 动了?driver panic?
  • 路由缺失:对照备份配置恢复,/etc/sysconfig/network-scripts/ 或 /etc/netplan/*.yaml。
  • 防火墙锁自己:从控制台或云厂商串口进去,iptables -F 或回滚到上一次 saved 规则。
  • DNS 慢:对照上次“正确的” resolv.conf 恢复;考虑加 nscd 或 systemd-resolved 缓存。

5.8 排查点 8:磁盘 I/O 是否被打爆

目的:CPU 的 wa 高、磁盘慢、应用响应慢,可能都是磁盘 I/O 打爆。这一节展开排查点 4 提到的“磁盘 I/O 等待”。

推荐命令:

# 1) iostat 持续观察
iostat -xz 1 5
# 重点字段:
#   %util:设备利用率(并非线性指标,RAID 卡下可能永远不到 100)
#   await:平均每次 I/O 等待毫秒
#   r/s, w/s:读写 IOPS
#   rkB/s, wkB/s:读写吞吐

# 2) per-device 视图
iostat -xz -m 1 5

# 3) 找最忙的进程
iotop -ao -d 5 -n 3
# -a:累计 I/O;-o:只看有 I/O 的;-d:刷新间隔;-n:次数

# 4) per-process 累计 I/O(更稳)
cat /proc/<PID>/io
#   read_bytes / write_bytes(累计)

# 5) 看 block 设备的队列与延迟
cat /sys/block/sda/queue/nr_requests
cat /sys/block/sda/queue/avg_read_size
# 或
cat /proc/diskstats

# 6) 看 RAID / LVM(云主机通常看不到,但物理机要看)
cat /proc/mdstat
pvs
lvs
dmsetup ls --tree

# 7) 看 page cache / dirty page
cat /proc/vmstat | grep -E 'pgmajfault|pgfault|dirty|writeback'
# pgmajfault 高:大量需要从磁盘读回的页面(典型内存不足假象)

# 8) 看是否在做回写
cat /proc/pressure/io
# some:10:0  full:5:0 — 表示 10% 时间内有 io 等待

# 9) fio 测试(慎用,且不要在生产盘上跑)
fio --name=test --filename=/tmp/test --size=1G --rw=randread --bs=4k --runtime=10 --time_based

典型异常表现:

  • await > 10ms 但 r/s+w/s 不高:单次 I/O 慢(盘介质问题、HBA 卡问题)。
  • await 正常但 %util 接近 100%:并发到瓶颈(容量层 OK,IOPS 层瓶颈)。
  • pgmajfault 持续升高:实际内存不足在靠磁盘补。
  • nr_requests 队列堆积:内核层压力大。
  • RAID 卡 BBU 老化:写策略从 write-back 掉到 write-through,性能腰斩。

判断逻辑:

  • await > 50ms,业务很慢:几乎肯定卡在 I/O,先看是不是某个进程在做大事务。
  • 整盘慢,但所有应用均匀慢:介质问题或云盘 IOPS 配额满了;考虑快照后重建盘。
  • 整盘慢,但单个进程慢:该进程的事务/查询过重,联合排查点 9 看 fd/线程。

下一步动作:

  • 找进程:iotop → cat /proc/PID/io → 决定是不是要 kill 或限制 I/O。
  • 数据库 I/O 慢:SHOW ENGINE INNODB STATUS 看 pending I/O。
  • 云盘性能:hdparm -t /dev/vda 不要乱跑,看厂商控制台是不是有性能监控;考虑换 SSD 云盘或开突发型。

5.9 排查点 9:fd / 线程 / ulimit / proc 是否耗尽

目的:进程不响应,但 CPU 内存都不高,常是 fd / 线程 / 信号量被耗尽。这一节是很多“看着没事但服务挂了”的真凶。

推荐命令:

# 1) 全局 fd 使用
cat /proc/sys/fs/file-nr
# 输出例:
#   1536    0   3276043
# 第一个是已分配 fd,第二个保留,第三个是系统上限

# 2) 系统最大 fd
cat /proc/sys/fs/file-max
ulimit -n

# 3) 单进程 fd 数
ls /proc/<PID>/fd | wc -l
# 大于 1 万常常是有问题,具体阈值跟业务有关

# 4) 单进程 fd 上限
cat /proc/<PID>/limits | grep -E 'open files|processes'

# 5) 线程数
ls /proc/<PID>/task | wc -l
cat /proc/<PID>/limits | grep processes
# 几十万个线程通常是 Java 应用或 Go 应用配置错了

# 6) 系统进程数 vs 上限
cat /proc/sys/kernel/pid_max
cat /proc/loadavg
ps -ef | wc -l

# 7) 信号量/锁(IPC)
#    经典问题:MySQL 的 'Too many connections',某些与 fd 不是一回事
ipcs -s | head
ipcs -m | head

# 8) ulimit 与 systemd unit 配置核对
systemctl show <unit> | grep -E 'LimitNOFILE|LimitNPROC'

# 9) 看错误日志,比如 EMFILE / Too many open files
journalctl -u <unit> | grep -E 'Too many|EMFILE|file descriptor'

典型异常表现:

  • Too many open files 日志:fd 耗尽。
  • Resource temporarily unavailable(fork):进程数耗尽。
  • 进程在,但 ps 卡住或 cat /proc/PID/status 卡住:大概率是 /proc 或 fd 被锁。
  • 数据库连接被占满:SHOW PROCESSLIST 大量 Sleep 状态的连接,应用层连接池配错或代码 bug。

判断逻辑:

  • 系统级 fd 上限触发:要让进程 fd 上限 < 系统级;反之则需要重启进程。
  • 进程级 fd 触发:要看是不是某段代码打开 fd 后没关闭,例如文件句柄、网络连接。
  • 线程数耗尽:Java 应用线程池爆、Go 应用 worker 配错、文件 / 网络句柄泄漏。

下一步动作:

  • 临时:prlimit --pid <PID> --nofile=65535 临时调大,只用于止血。
  • 长期:修改 service unit 的 LimitNOFILE,然后 systemctl daemon-reload && systemctl restart <unit>。
  • 连接池:mysqladmin -uroot -p -e 'SHOW STATUS like "Threads_connected";' 看真实连接数。
  • 线程爆:Java 用 jstack <PID> 看线程状态分布;Go 用 pprof。

5.10 排查点 10:定时任务、上游变更、雪崩

目的:很多时候“宕机”不是 OS 层的故障,而是定时任务、上游变更、外部依赖在某个时间点同时发生作用。这一节把视野拉大,把“运维工程师最容易忘记的”几件事点出来。

推荐命令:

# 1) 看 crontab
crontab -l
ls -la /etc/cron.d/ /etc/cron.daily/ /etc/cron.hourly/
ls -la /var/spool/cron/

# 2) 看上次 cron 是否执行
grep CRON /var/log/syslog 2>/dev/null | tail -50
journalctl -u cron --since today --no-pager | tail -50

# 3) 看 systemd timer(替代老式 cron)
systemctl list-timers --all
# 老式 cron 多用 systemd-timer 替代,定时配置更明确

# 4) 看变更记录
#    公司的变更系统(自研、Jira、ITSM)上是"今日变更"
#    在没有变更系统的情况下,看 git log / 部署记录

# 5) 看外部依赖
curl -sS -m 5 https://<上游URL>/healthz
curl -sS -m 5 https://<上游URL>/version
# 拿到正常业务依赖的 HTTP / RPC 服务,逐个 ping

# 6) 看上游服务的 DNS 变化
#    域名解析前后对比:
getent hosts api.partner.com
# 与之前是否一致

# 7) 看应用层自己打的指标
#    Prometheus / OpenTelemetry / 自研监控
#    这里没法给通用命令,联系业务方

# 8) 看告警系统是否已经触发
#    这里也没通用命令,但要点是:如果告警一条都没触发,
#    要怀疑是不是告警系统本身挂了(经典"运维盲区")

典型异常表现:

  • 凌晨 3 点某 cron 启动,4 点开始报警:大概率是 cron 触发。
  • 同时多台机器不同时间点挂:大概率是同一上游故障。
  • 在某次发布后挂:看发布记录。
  • 监控指标全部不变,曲线是平的:可能就是 exporter 挂了,机器可能真有问题但你看不出来。
  • 监控一切正常,但业务说挂了:告警系统自身故障,或者监控覆盖不全。

判断逻辑:

  • 定时任务触发:crontab 时间、systemd-timer last-trigger 时间。
  • 上游变更:对照发布记录,联系上游运维。
  • 雪崩:单点故障扩散,看依赖链路、限流配置、熔断配置、慢调用比例。

下一步动作:

  • 临时禁用 cron:crontab -e 注释相关行;事先备份 /var/spool/cron/crontabs/。
  • 上游故障:降级、限流、切流量到备用源,这一步通常需要业务方配合。
  • 雪崩:检查 sentinel / hystrix / resilience4j 等限流中间件是否生效。

六、常用命令速查

下面是一个“一页纸”工具,贴到工作台或值班墙上,方便查:

# === 一、系统与电源 ===
uptime
who -b
last -n 10

# === 二、内核与日志 ===
dmesg -T | tail -200
journalctl -k -n 200 --no-pager
journalctl -b -1 --no-pager
journalctl -u <unit> -n 500 --no-pager
tail -200 /var/log/messages

# === 三、CPU / 内存 / 进程 ===
top -b -n 1
ps -eo pid,ppid,user,pcpu,pmem,rss,vsz,comm --sort=-%cpu | head -20
ps -eo pid,ppid,user,pcpu,pmem,rss,vsz,comm --sort=-%mem | head -20
free -h
cat /proc/meminfo
cat /proc/loadavg
vmstat 1 5
mpstat -P ALL 1 5
pidstat -p <PID> 1 5
perf top -g
pstack <PID>

# === 四、磁盘与文件系统 ===
df -hT
df -i
du -sh /var/log /data /home 2>/dev/null
ls -ltr /var/log/ | tail -20
lsof +L1
mount | grep -E '/data|/var'
findmnt

# === 五、I/O 与块设备 ===
iostat -xz 1 5
iotop -ao -d 5 -n 3
cat /sys/block/sda/queue/nr_requests
cat /proc/diskstats
cat /proc/pressure/io

# === 六、网络 ===
ip -br addr
ip -br link
ip route
ip neigh
ip -s link
ss -tlnp
ss -s
ss -tn dst <IP>:<port>
ping -c 4 -W 2 <ip>
traceroute -T <ip>
dig +short <domain>
getent hosts <domain>

# === 七、防火墙与规则 ===
iptables -L -n -v | head -100
firewall-cmd --list-all
nft list ruleset

# === 八、服务与单元 ===
systemctl list-units --type=service --state=failed
systemctl status <unit>
systemctl show <unit> | grep -E 'NRestarts|LimitNOFILE'
journalctl -u <unit> -n 500 --no-pager

# === 九、定时任务与变更 ===
crontab -l
ls -la /etc/cron.d/
systemctl list-timers --all
journalctl -u cron --since today --no-pager

# === 十、K8s / Docker(节点视角) ===
systemctl status kubelet
journalctl -u kubelet -n 200 --no-pager
crictl ps -a
docker ps
docker system df

七、配置示例

下面给几段“止血层”的高频配置示例,贴出来方便复用。

7.1 Systemd unit 模板(带 ulimit 的服务)

# /etc/systemd/system/myapp.service
[Unit]
Description=My Application
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/bin/myapp

# 资源与 fd 上限
LimitNOFILE=131072
LimitNPROC=65535
LimitCORE=infinity

# 优雅退出
TimeoutStopSec=30
KillMode=mixed
KillSignal=SIGTERM

# 自动重启(谨慎用,先排查)
Restart=on-failure
RestartSec=5
StartLimitBurst=10
StartLimitIntervalSec=60

# 安全(可选)
NoNewPrivileges=true
ProtectSystem=full
ProtectHome=true

[Install]
WantedBy=multi-user.target

生效方式:

systemctl daemon-reload
systemctl enable myapp.service
systemctl restart myapp.service
# 验证
systemctl status myapp.service
cat /proc/$(pidof myapp)/limits

7.2 OOM 抑制与日志守护(sysctl)

# /etc/sysctl.d/99-oom-tuning.conf

# 减少 OOM 误杀:让 OOM killer 更倾向杀大内存进程
vm.oom_score_adj = 0

# 某些守护进程不要被 OOM killer 杀
# 在 OOM killer 视角下,把"不要杀我"的进程 oom_score_adj 设为 -1000
# 例:数据库主进程
# cat /proc/<mysqld-pid>/oom_score_adj
# echo -1000 > /proc/<mysqld-pid>/oom_score_adj

生效方式:

sysctl -p /etc/sysctl.d/99-oom-tuning.conf
sysctl vm.oom_score_adj

回滚方式:

# 备份原值
sysctl vm.oom_score_adj > /tmp/before.txt
# 出问题后
echo $(cat /tmp/before.txt) > /proc/sys/vm/oom_score_adj

7.3 SSH 恢复用临时串口配置(物理机)

物理机的串口通常已经在 BIOS 配好,Linux 端要做的是把 console 输出引导到串口。

# /etc/default/grub
# 找到 GRUB_CMDLINE_LINUX_DEFAULT,追加 "console=tty0 console=ttyS0,115200n8"
# 例如:
# GRUB_CMDLINE_LINUX_DEFAULT="quiet splash console=tty0 console=ttyS0,115200n8"

# 重新生成 grub
update-grub    # Debian/Ubuntu
grub2-mkconfig -o /boot/grub2/grub.cfg  # RHEL/CentOS

# 验证
cat /proc/cmdline

生效方式:重启。但只在配置窗口期做,否则意外重启会触发事故。

7.4 Zabbix / Prometheus 抓 node_exporter 的常用配置片段

# prometheus.yml 片段
scrape_configs:
- job_name: 'node'
  static_configs:
  - targets:
    - '<IP>:9100'
  scrape_interval: 15s
  scrape_timeout: 10s
  relabel_configs:
  - source_labels: [__address__]
    target_label: instance

# 几条关键告警规则(只是示例)
# groups:
#   - name: node
#     rules:
#       - alert: NodeLoadHigh
#         expr: node_load5 > (count by (instance) (node_cpu_seconds_total{mode="idle"}) * 2)
#         for: 10m
#       - alert: NodeFilesystemAlmostFull
#         expr: (node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"} / node_filesystem_size_bytes) < 0.1
#         for: 5m
#       - alert: NodeOOM
#         expr: increase(node_vmstat_oom_kill[5m]) > 0

7.5 内核网络参数(配合 nginx / haproxy 等高并发服务)

# /etc/sysctl.d/99-net-tuning.conf

# 增加 SYN 队列长度
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535

# 加快 TIME_WAIT 回收(慎用,要评估业务)
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15

# 端口范围
net.ipv4.ip_local_port_range = 1024 65535

# TCP 缓冲区
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216

# conntrack 调大(高并发场景)
net.netfilter.nf_conntrack_max = 262144
net.netfilter.nf_conntrack_tcp_timeout_established = 7200

生效方式:

sysctl -p /etc/sysctl.d/99-net-tuning.conf

回滚方式:

# 备份原始值
sysctl -a > /tmp/sysctl.before
# 出问题后:
# 从 /tmp/sysctl.before 找原值,写回
sysctl -w net.core.somaxconn=128
# 或者:sysctl -p /etc/sysctl.d/99-net-tuning.conf.bak(前提是备份了文件)

7.6 /etc/security/limits.conf(全局资源限制)

# /etc/security/limits.d/90-myapp.conf
# 进程级 fd 与线程数
myapp  soft  nofile  131072
myapp  hard  nofile  131072
myapp  soft  nproc   65535
myapp  hard  nproc   65535

# root 也调高
root  soft  nofile  131072
root  hard  nofile  131072

生效方式:重新登录,或在 unit 里用 LimitNOFILE= 显式覆盖。注意:systemd unit 的 LimitNOFILE 优先级高于 limits.conf。

7.7 systemd-journald 限制 journal 大小(避免 journal 占满 /var)

# /etc/systemd/journald.conf
[Journal]
Storage=persistent
SystemMaxUse=2G
SystemKeepFree=1G
SystemMaxFileSize=200M
MaxRetentionSec=30day

生效方式:

systemctl restart systemd-journald
journalctl --verify

回滚:备份 /etc/systemd/journald.conf 后回退。

八、日志或指标观察方法

排查的“判断”环节要靠日志、指标、命令输出三类证据。常见对应关系如下:

8.1 日志与排查点对应

排查点 主要日志
1 电源 / 健康灯 BMC/IPMI 事件日志、厂商控制台
2 SSH / console /var/log/auth.log、sshd 日志、/var/log/secure
3 内核 panic / OOM dmesg、journalctl -k、/var/log/messages
4 CPU / 内存 / SWAP systemd-coredump、应用程序 core、sar 系列
5 磁盘 / inode /var/log 自身、logrotate 状态、journal --vacuum-size 输出
6 服务进程 journalctl -u、应用程序自己的 access / error / debug log
7 网络 内核日志(网卡 link 状态)、防火墙日志、应用层 access log
8 磁盘 I/O 内核日志(dm / raid / mdstat)、应用程序 I/O latency log
9 fd / 线程 / ulimit journalctl -u、应用 EMFILE / TOOMANY 日志
10 定时任务 / 上游 cron 日志、应用层上游访问日志、依赖监控

8.2 指标与排查点对应

排查点 核心指标
1 电源 / 健康灯 BMC sensor(温度/电压/风扇)
2 SSH / console SSH 登录成功/失败次数
3 内核 vmstat、pressure/cpu、pressure/memory、pressure/io
4 CPU / 内存 node_cpu_seconds_total、node_memory_MemAvailable_bytes、node_vmstat_pswpin、node_vmstat_pswpout
5 磁盘 / inode node_filesystem_avail_bytes、node_filesystem_files_free
6 服务进程 node_systemd_unit_state
7 网络 node_network_mtu、node_netstat_Tcp_ActiveOpens、node_netstat_Tcp_PassiveOpens
8 磁盘 I/O node_disk_io_now、node_disk_await_read_seconds_total、node_disk_await_write_seconds_total
9 fd / 线程 node_processes_max_processes、node_processes_open_fds
10 定时任务 / 上游 应用层 R.E.D.(Rate/Errors/Duration)指标

8.3 关键观察窗口

很多事故的根因会藏在“我开始排查之前”的时间窗口里。建议把观察窗口拉到“开始告警时间前 1 小时”,看是否已经出现异常征兆:

  • journalctl --since "1 hour ago" 看系统日志。
  • sar -n DEV 1 历史数据如果没有,用 ss -s 配合历史抓取的脚本来补。
  • 提前埋点的关键曲线:CPU 的 iowait、内存的 MemAvailable、网络的 TCP retransmit、磁盘 I/O 的 await。

8.4 减少日志噪声的实践

当 syslog 里全是调试日志,反而会让关键信号被淹没。建议:

  • 应用层明确分级,避免在生产开 DEBUG。
  • 日志切分要按“小时 / 天 / 大小”三维,不要只按大小,否则偶发高峰就会丢关键日志。
  • 关键事件单独打一条 MDC 标记的事务日志。

九、排查路径

下面给一个“端到端”的路径,从接收到告警开始,到给出修复方向结束。

9.1 标准路径(主线 + 备线)

  1. 收到告警(短信 / 群机器人 / 邮件 / IM):记录告警时间点。
  2. 登录控制台 / 看监控曲线:24h / 6h / 1h 多时间窗。
  3. 跑机器存活检查(排查点 1、2):uptime、ping、SSH、console。
  4. 跑内核与资源快速检查(排查点 3、4、5):dmesg、top、free、df -hT、df -i。
  5. 如果前 5 点命中:进入“止血 + 修复”主线;如未命中,继续。
  6. 跑服务与网络检查(排查点 6、7):systemctl、ss -tlnp、ip、iptables。
  7. 跑 I/O 与资源细查(排查点 8、9):iostat、iotop、lsof、ulimit。
  8. 跑定时任务与外部依赖(排查点 10):crontab、systemd-timer、上游。
  9. 定位到根因,给出修复方案:止血先,后修复。
  10. 执行变更并验证:对照 7 条高风险红线,每一步带回滚。
  11. 写复盘:时间线、监控缺口、变更缺口、流程缺口、固化项。

9.2 旁路:并发做事

在主线推进时,可同步让业务方、监控运维、DBA、应用 owner 各自跟进。常见分工:

  • 业务 owner:业务侧 R.E.D. 曲线、受影响页面/接口、用户报错。
  • 监控 owner:对告警的“是否覆盖、是否风暴、是否延迟”给出意见。
  • DBA:数据库连接数、慢查询、复制延迟、锁等待。
  • 应用 owner:进程是否在、是否 OOM、最近一次发布、最近一次 GC。

但不要让多个工程师同时改同一台生产机器,否则会互相破坏现场。

9.3 容易忽略的几个检查

下面这些事看起来“不归运维”,但都是宕机根因的常见候选:

  • 磁盘配额(quota):多用户共享一台机器,某用户配额满,会表现为“该用户写入失败但磁盘还有空间”。
  • cgroup 限制:systemd 的 MemoryMax、IOWeight,容器环境的 cgroup 都有可能触发。
  • AppArmor / SELinux:某些服务被拒绝读取文件,但日志只写 “Permission denied”。
  • 时钟跳变:NTP 失同步、闰秒、虚拟机时钟漂移会导致 TLS 握手失败、Kerberos 认证失败。
  • 资源调度器:cgroup v1 / v2 混用会导致配额统计错误。

十、风险提醒

10 个排查点中,以下动作有副作用,必须谨慎执行。每条都标注了红线。

10.1 journalctl --vacuum-size

影响:清理 systemd journal 老日志。风险:如果没把日志归档到别处,这里清理后运维将失去追溯凭证。替代:logrotate 配置归档到其他盘,或者 ELK/Loki 集中收集。

10.2 truncate -s 0 /var/log/xxx.log

影响:让大日志“看上去清零”,但被进程占的 fd 还能继续写,会出现“日志大小为 0,但实时还在写”的尴尬。最佳实践:用 logrotate -f xxx.conf 强制轮转,然后再清。

10.3 kill -9 <PID>

影响:不留进程退出时间,数据库 / 中间件 / 业务进程可能丢数据。替代:先 kill <PID>(SIGTERM),等到 30 秒还未退,再 kill -9。

10.4 echo c > /proc/sysrq-trigger

影响:同步刷盘 + 重启。如果磁盘已经压力极大,这步可能让重启也卡。只在电源级无法断电且系统无响应时使用,由两个人复核。

10.5 修改 /etc/sysctl.conf

影响:参数变化立即生效,但无法“撤销”。替代:sysctl -w key=value 设置,失败可以再 sysctl -w key=oldvalue,然后落盘。

10.6 重启 systemd unit

影响:进程重启,会有“业务中断”。风险:Restart=on-failure + 启动失败会让 systemd 反复重启,日志被快速覆盖。先停 unit,再改配置,再启动。

10.7 修改 iptables 规则

影响:可能锁死自己。替代:iptables-save > /tmp/bk-$(date +%s).rules 备份;用 -I 插入带“5 分钟自动回滚”的脚本。

# 临时插入一条规则,5 分钟后自动回滚(运维常用模式)
iptables -I INPUT 1 -p tcp --dport 8080 -j ACCEPT
( sleep 300 && iptables -D INPUT 1 ) &

10.8 rm -rf /var/log/* 或 rm -rf /tmp/*

影响:删除正在被进程使用的 fd(虽然不会立刻释放),但其他运维 / 应用可能需要这些文件。先看 lsof,先停服务。

10.9 改 LVM / RAID / 文件系统

影响:无法热回滚,数据风险。先快照,先备份;不要在挂载的活跃分区上跑 fsck 或 xfs_repair。

十一、验证方式

排查完成、止血动作做完之后,必须以可观察的方式验证。常见的“验证项”包括:

11.1 服务层验证

  • 业务接口 curl 200:curl -sS -m 5 https://<host>/healthz。
  • 业务主链路 curl 200:带上 token 的实际业务接口。
  • 端口在 LISTEN:ss -tlnp | grep <port>。
  • systemd unit active:systemctl is-active <unit>。
  • 进程存在且内存等正常:ps -o pid,pcpu,pmem,rss,stat -p <PID>。

11.2 系统层验证

  • load average 回归:uptime。
  • 内存可用 50% 以上:free -h。
  • CPU user 占比正常:top -b -n 1。
  • 磁盘使用率回到健康线以下:df -hT。
  • inode 有余量:df -i。
  • I/O 等待回落:iostat -xz 1 5 的 %util 和 await。

11.3 网络层验证

  • 默认路由存在:ip route。
  • 网卡 UP:ip -br link。
  • DNS 解析 getent hosts <domain> 正常。
  • 关键端口 ESTABLISHED:ss -tn dst <IP>:<port>。

11.4 监控层验证

  • 监控恢复:Prometheus / Zabbix 显示 Unchanged 或 Recovered。
  • 告警恢复:收一条 recovered 通知。
  • 业务指标 R.E.D. 正常:request rate, error rate, duration。

11.5 时间窗口观察

不要在止血后立刻宣布“恢复”。需要观察 5 / 15 / 60 分钟三个时间点:

  • 5 分钟:确认止血动作没让业务雪崩加剧。
  • 15 分钟:确认副作用没在新数据上重新出现(比如 OOM 后被 systemd 重启又被 OOM)。
  • 60 分钟:确认这不是“看上去恢复”,而是真恢复了。

十二、回滚方案

排查过程中任何“止血”动作都要带回滚。下面是常用止血动作的“匹配回滚”模板。

12.1 修改内核参数

# 改前
sysctl vm.dirty_ratio > /tmp/before.txt
# 改
sysctl -w vm.dirty_ratio=10
# 回滚
sysctl -w vm.dirty_ratio=$(cat /tmp/before.txt)

12.2 修改 systemd unit

# 备份
cp /etc/systemd/system/myapp.service /tmp/myapp.service.bak
# 改 + daemon-reload + restart
# 回滚
cp /tmp/myapp.service.bak /etc/systemd/system/myapp.service
systemctl daemon-reload
systemctl restart myapp.service

12.3 防火墙规则

# 备份当前规则
iptables-save > /tmp/iptables.bak
# 改
# 回滚
iptables-restore < /tmp/iptables.bak

12.4 磁盘扩容 / 文件系统变更

# 备份
xfsdump -f /tmp/data.dump /mnt/data   # 仅适用 xfs,且先停服
# 改
# 回滚
xfsrestore -f /tmp/data.dump /mnt/data

12.5 数据库参数

# MySQL 改前(写入 SET GLOBAL 是临时的,不持久化)
SET GLOBAL innodb_flush_log_at_trx_commit = 2;
# 回滚
SET GLOBAL innodb_flush_log_at_trx_commit = 1;
# (注意:生产参数变更应通过配置文件生效,而非 SET GLOBAL)

数据库层面的参数回滚,再往深处走就要结合 MySQL 的配置体系和日志机制来做了,这部分另有一套完整的排查逻辑,这里不再展开。

12.6 定时任务禁用

# 备份
crontab -l > /tmp/cron.bak
# 禁用
crontab -r
# 恢复
crontab /tmp/cron.bak

12.7 服务重启

服务重启的回滚就是“再启动一次旧配置”。所以关键是:保存好旧配置 + 旧二进制 + 旧数据。

# 二进制备份
cp /opt/myapp/bin/myapp /tmp/myapp.bak
# 配置备份
cp -a /opt/myapp/conf /tmp/conf.bak
# 数据备份
mysqldump -uroot -p<pass> --single-transaction --master-data=2 \
  --triggers --routines --events mydb > /tmp/mydb-$(date +%Y%m%d-%H%M%S).sql
# ... 然后再 restart

十三、生产环境注意事项

下面把“生产 ≠ 测试”的几个原则再说一遍,放在这里是因为有些事故就是因为打破这些原则造成的。

13.1 操作窗口与变更窗口

  • 大变更只能在业务低峰期跑(支付类的凌晨、电商类 0:00-5:00、工具类周末)。
  • 紧急止血,可以“先斩后奏”,但要在 24 小时内补评审。
  • 不能在变更窗口外做“非紧急变更”,否则一旦出问题,不止技术问题,流程也会被翻出来。

13.2 双人复核

  • 对数据库的 DDL、DROP、TRUNCATE,必须双人复核。
  • 对防火墙规则、服务重启、定时任务修改,也建议至少两个人看过。
  • 监控告警脚本的“批量脚本”,上线前必须 dry-run。

13.3 影响范围评估

  • 一台物理机 vs 一台虚拟机 vs 一个容器实例,影响面差 10 倍以上。
  • 一个 MySQL 主库的 failover 影响一个集群。
  • 一个 Redis 分片重启影响一片业务。

变更前必须写“我这条命令会影响多少台机器、多少个用户、多少笔业务”。

13.4 沟通

  • 变更前:通知业务方、上下游、对接的应用 owner。
  • 变更中:关键操作要让其他人在 IM 里看到。
  • 变更后:发结果通知 + 服务状态截图/粘贴。

13.5 备份与快照

  • 涉及数据前,必须有可恢复的备份。
  • 云盘改 LVM 改 RAID 改 FS,要先快照。
  • 数据库 schema 变更要 binlog 打开 + 历史 SQL 可回放。

13.6 变更记录

  • 任何变更必须有记录:变更人、变更时间、变更原因、变更影响、变更结果。
  • 推荐用公司自研的变更系统 / JIRA / GitLab CI,而不是文档里手写。

13.7 永远不要无证据改配置

  • “我看着像”不是改配置的理由。
  • 必须有“明确的现象 → 明确的指标异常 → 明确的根因 → 明确的方案 → 明确的回滚”。

13.8 操作的“可重放性”

  • 每个动作要能“再敲一次”。把命令敲成 script -t -c '<command>' 留个 trace,排查完后回看哪里错。
  • 大批量操作写成可版本化的脚本,放进 git,不要再用复制粘贴。
  • runbook 里的命令贴上真实参数,而不是“假设你已经替换了变量”。

13.9 沟通中的“消除歧义”原则

排查时要主动消除歧义:

  • “服务挂了” —— 哪个服务?哪个实例?哪个端口?
  • “内存满了” —— 哪个 MemAvailable 阈值?是从监控看还是从 free 命令看?
  • “网络不通” —— 哪个目标 IP?哪个端口?是从本机 ping 还是从对端 ping?

不要让听者猜。不要让自己记忆有歧义。所有动作附明确的时间戳、命名空间、节点名、PID。

13.10 知识沉淀的最低标准

每起事故至少沉淀到以下一项:

  1. runbook:新增排查步骤或报警项。
  2. 监控规则:补一条告警或调整一条阈值。
  3. 变更 checklist:新增一条“禁止动作”或“强制动作”。
  4. 新人培训材料:写入 onboarding 文档或 PPT。

否则下次同样问题再现,知识没积累起来,事故成本是复利的。

十四、总结

服务器宕机排查的 10 个点,在工程上是“分层兜底”的思路:

  • 第 1、2 点保住“还在不在”。
  • 第 3 点判断“内核 OK 还是 NG”,这一档决定后续修复是“重启”还是“深挖”。
  • 第 4、5 点把最常见的“用户态资源耗尽”全捞一遍。
  • 第 6 点切到服务层,确认 OS 没问题但服务死了的情况。
  • 第 7、8 点把网络和 I/O 两个“用户看不见但很凶”的事单列出来。
  • 第 9 点解决“看着没事但服务挂了”的“资源耗尽类”问题。
  • 第 10 点把视野拉大到“定时任务 / 上游 / 雪崩”。

但 10 个点只是骨架,真正在事故里拍板的,是以下 5 个态度:

  1. 顺序不能跳:前一个排查点没走完,不进下一个。
  2. 现象、根因、修复是三件事:不要在定位到表面后立刻动手修。
  3. 证据链:每个判断都要拿出日志、指标、命令输出做支撑。
  4. 回滚为王:任何动作都要先想“怎么撤回”。
  5. 复盘不甩锅:把这次事故固化成监控项 / 变更项 / 流程项。

把 10 个点背下来只是入门,把它们变成肌肉记忆,才是真正的一线运维。祝你在下一次夜里报警响起时,能稳住,把第 1 点敲下去。

附录 A:10 个排查点的“一行总结”

# 排查点 一行命令 关注指标
1 机器是否活着 ping -c 4 -W 2 <ip> 控制台状态
2 SSH/console ssh -o ConnectTimeout=5 <ip> 'date' sshd 状态、TTY 状态
3 内核 dmesg -T | grep -Ei 'panic|oom|lockup' 内核事件
4 CPU/内存/SWAP top -b -n 1; free -h load、wa、available、swp
5 磁盘 df -hT; df -i Use%、inode%
6 服务进程 systemctl status <unit> active/failed
7 网络 ip route; ss -tlnp; iptables -L -n -v 路由、端口、防火墙
8 磁盘 I/O iostat -xz 1 5 %util、await
9 fd/线程 cat /proc/sys/fs/file-nr; ls /proc/<PID>/fd | wc -l fd 上限、线程数
10 定时/上游 crontab -l; systemctl list-timers 触发时间、上游依赖

附录 B:每种现象的“命令链”

下面给一些典型现象的命令链,可以做成 playbook。

B.1 现象:整机慢 + 内存慢

uptime
free -h
vmstat 1 5
iostat -xz 1 5
# 决策树:
# wa 高 -> 排查点 8
# si/so 高 -> 排查点 4,内存问题
# us 高 -> 排查点 4
# iowait 0% -> 网络/应用层

B.2 现象:某应用 5xx 增多

systemctl status <app>
journalctl -u <app> -n 200 --no-pager -p err
ss -tlnp | grep <port>
# 看应用 access log
tail -200 /var/log/<app>/access.log
# 看应用是否触发 OOM
dmesg -T | grep -i oom
# 决策树:
# 进程死 -> 排查点 6
# 端口不在 -> 配置错
# OOM 触发 -> 排查点 4

B.3 现象:磁盘告警

df -hT
df -i
# 找大文件
find / -xdev -type f -size +1G 2>/dev/null | head -20
# 找被持有但 unlink 的文件
lsof +L1 | sort -k7 -n -r | head
# 决策树:
# 磁盘满 -> 清理 / 扩容
# inode 满 -> 找小文件密集目录

B.4 现象:网络抖动

ip -s link
ss -s
ss -tn dst <ip>:<port>
mtr -r -c 10 <ip>
# 决策树:
# 链路丢包 -> NIC / 交换机
# TCP 重传 -> 防火墙 / 中间设备
# DNS 慢 -> /etc/resolv.conf

附录 C:写给值班长的话

如果你是一个值班长,带新人做宕机处理,以下 5 件事可以提前布置:

  1. 接警时,先 5 分钟静默,不要直接上手:让新人 5 分钟内独立读完 10 个点的提纲,找到候选根因;再让你复核。
  2. 强制命令记录:每个新人在排查时开 script -t -c 'bash' <trace> 留痕,排查完回看。
  3. 双人复核清单:数据库 DDL、防火墙变更、binlog 删除,双人签字。
  4. 复盘模板:每个事故一页纸,字段包括“时间线 / 关键证据 / 根因 / 修复 / 监控缺口 / 流程缺口 / 行动项”。
  5. 每周一次 mock:给新人一次“模拟宕机”演练,看他是否按本文 10 个点顺序走下来。

新人培养不是教命令,是教“判断 + 顺序 + 证据 + 回滚”。10 个点是工具,真正的能力是用它。

附录 D:给资深 SRE 的“应急备忘”

下面是资深 SRE 才会用的“应急状态切换”清单,只在 P0 / P1 期间使用,不在日常使用:

  1. 冻结所有非紧急变更:在 ITSM / 变更系统 / IM 群里明确发布“系统进入 P1,变更暂停”。
  2. 启用战时通讯频道:专用 IM 频道,只聊这一个事故;避免在原频道里混着其他事。
  3. 全队开始写时间线:从接警开始,每个动作带时间戳,统一格式“HH:MM:SS - <动作>”。
  4. 每 15 分钟做一次状态广播:在频道里告诉所有相关方“当前根因候选 + 已做的止血 + 下一步”。
  5. 不允许“我觉得”、“应该是”、“也许”:每个结论必须带证据。
  6. 临时提高告警阈值:事故期间不要再被低优先级告警淹没;在频道里说明。
  7. 数据库主库切换是大事,必须有 DBA + SRE 双签,且必须验证从库 Seconds_Behind_Master = 0 才执行。
  8. 任何“重启服务”必须记录:重启前确认有日志、监控、备份三件套。
  9. 复盘会在事故结束后 24 小时内开,延迟越久记忆越不准。
  10. 复盘不是批斗会:blameless 原则,把责任放在流程上,而不是人上。

这些不是命令,是纪律。纪律到位,新人也能稳住事故;纪律不到位,老兵也会翻车。




上一篇:Meta Muse Wonder 深度评测:Agent 工作流第一,前端设计翻车
下一篇:MySQL主从延迟10秒:WRITESET+并行复制优化三步实战
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-10-18 20:25 , Processed in 0.167086 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表