路线图

面试 · 场景排查型

星辉 2026-07-02 阅读 8 min 1,699 字 路线图
面试 · 场景排查型 封面

考察目标:实战排障能力,每题含决策树式排查路径 15 题


题目1:SSH 连不上服务器,怎么排查?#

故障现象ssh user@host 卡住无响应或直接 Connection refused。

排查决策树

text
SSH 连不上
├── 网络层
│   ├── ping <ip> → 通?→ 通则 IP 可达,不通则查网络/防火墙
│   ├── telnet <ip> 22 → 通?→ 通则 SSH 端口正常,不通则端口未开
│   └── traceroute <ip> → 哪一跳断了?
├── 服务层
│   ├── 确认 sshd 是否运行:systemctl status sshd
│   ├── 确认 sshd 监听:ss -tlnp | grep 22
│   │   └── 监听 127.0.0.1:22?→ 只本机可连,改 /etc/ssh/sshd_config ListenAddress 0.0.0.0
│   └── 确认端口号:是否改了默认 22?
├── 认证层
│   ├── 密码登录 → /etc/ssh/sshd_config PasswordAuthentication yes?
│   ├── 密钥登录 → authorized_keys 权限 600?.ssh 目录权限 700?
│   └── 查日志:tail -f /var/log/auth.log 看拒绝原因
├── 防火墙
│   ├── iptables -L -n | grep 22
│   ├── ufw status(Ubuntu)
│   └── 云平台安全组/防火墙规则
├── 资源限制
│   ├── 连接数满了?ss -an | grep ':22' | wc -l
│   └── MaxStartups/MaxSessions 配置
└── SSH 配置问题
    ├── /etc/ssh/sshd_config PermitRootLogin yes?AllowUsers 白名单?
    └── 改了配置后:systemctl reload sshd

题目2:磁盘写满了怎么处理?#

故障现象df -h 显示 / 分区 100%,写入报 "No space left on device"。

排查决策树

text
磁盘写满
├── Step 0: 先分清是"没空间"还是"没 inode"(易漏!)
│   └── df -h 显示有空间、写入却报 No space left on device?
│       └── → df -i 看 IUse%:inode 耗尽(海量小文件:session/缓存/邮件队列/大量空文件)
│           └── 定位:for d in /*; do echo "$(find $d -xdev 2>/dev/null|wc -l) $d"; done | sort -rn
├── Step 1: df -h 确认哪个分区满了(空间维度)
├── Step 2: du -sh /* 2>/dev/null | sort -rh | head -20
│   ├── 找最大的目录
│   │   ├── /var/log 很大 → 检查日志
│   │   │   ├── 容器日志?→ /var/lib/docker/containers/ 的 json.log
│   │   │   ├── journal?→ journalctl --disk-usage
│   │   │   └── 应用日志未轮转?→ logrotate 配置
│   │   ├── /tmp 很大 → 清理 > 10 天的文件
│   │   ├── /var/lib/docker 很大 → docker system prune -a
│   │   └── 某个应用目录很大 → 联系开发确认
├── Step 3: du 和 df 差距很大?
│   └── lsof | grep deleted → 找到幽灵文件 → kill/重启进程释放
├── Step 4: 应急清理
│   ├── 删除 /tmp 旧文件:find /tmp -mtime +7 -delete
│   ├── 清理 journal:journalctl --vacuum-size=500M(保留最新 500M)
│   ├── 清理 apt 缓存:apt-get clean(Debian/Ubuntu)
│   └── 清理 docker:docker system prune -af
├── 修复后
│   ├── 加磁盘监控告警:df -h | awk '$5>85{print}' → 钉钉/邮件
│   └── 调整 logrotate:/etc/logrotate.d/ 配置应用日志轮转

关键命令

bash
df -h                                    # 看哪个分区满了(空间)
df -i                                    # inode 使用率(df 有空间却报 No space 时必查)
du -sh /* 2>/dev/null | sort -rh | head  # 哪一级目录最大
lsof | grep deleted                      # 幽灵文件
docker system prune -a -f                # 清理 Docker 垃圾
journalctl --disk-usage                  # journal 占多少

题目3:CPU load 很高但 CPU 利用率不高,怎么排查?#

故障现象top 显示 load average: 20.5, 但 CPU %idle 还有 70%。

排查决策树

text
CPU load 高但利用率不高
├── load average 含义:= R 状态 + D 状态进程数
├── 区分 R 还是 D
│   ├── ps aux | awk '$8~/R/{print}' → 纯 R 进程数量
│   ├── ps aux | awk '$8~/D/{print}' → D 状态进程数量
│   └── D 状态多 → IO 瓶颈!
├── D 状态 → 查 IO
│   ├── iostat -x 1 → 看 %util 和 await
│   ├── iotop → 哪个进程大量 IO?
│   ├── 检查 NFS 挂载:df -h | grep nfs → 是否 NFS 卡住
│   └── 检查存储:磁盘是否故障/降级
├── R 状态多但 CPU idle 高 → 锁等待/繁忙等待
│   ├── top -H -p <pid> → 看哪个线程占 CPU
│   ├── strace -p <pid> -c → 系统调用分布
│   └── 检查是否线程数过多导致负载高
└── 确认虚拟化层
    └── 云主机 CPU steal time:top 看 %st(被 hypervisor 偷的时间)

关键认知:load average 包含 D 状态进程——这意味着磁盘 IO 慢也会导致 load 高,而 CPU 利用率看起来正常。


题目4:内存使用率很高但没 OOM,怎么分析?#

故障现象free -h 显示 used 接近 total,但还没触发 OOM。慢,但没死。

排查决策树

text
内存高但没 OOM
├── Step 1: free -h 区分 used / buff/cache / available
│   ├── buff/cache 很高?→ 正常,Linux 主动用空闲内存做缓存
│   ├── available 很低 (<200MB)?→ 真的内存不足,马上要 OOM
│   └── used 高但 available 也高?→ 没事,系统正常
├── Step 2: 找内存大户
│   ├── ps aux --sort=-%mem | head -10 → 内存前 10 的进程
│   ├── smem -t → 按 PSS(实际分配)排序(更准)
│   └── 检查容器:docker stats / kubectl top pods -A
├── Step 3: 检查 Swap
│   ├── free -h 看 Swap 用量
│   ├── vmstat 1 看 si/so → 大量换入换出 → 内存真的不够
│   └── cat /proc/sys/vm/swappiness → 调整 swap 倾向
├── Step 4: OOM 风险评估
│   ├── dmesg | grep -i "killed process" → 曾经 OOM 过的进程
│   ├── cat /var/log/syslog | grep oom
│   └── 查看 K8s Pod 的 OOMKilled 状态:kubectl describe pod | grep OOMKilled
└── 治理
    ├── 加内存告警:available < 10% 告警
    └── 容器设合理的 memory limit

关键认知available 才是真正可用的内存,不是 free。Linux 的 buff/cache 可以在需要时回收。


题目5:服务启动失败,怎么排查?#

故障现象systemctl start myapp 失败或启动后立刻退出。

排查决策树

text
服务启动失败
├── Step 1: systemctl status myapp
│   ├── 显示 Active: failed → 看下面的错误信息
│   └── 显示 Active: activating → 卡在启动中
├── Step 2: journalctl -u myapp --since "5 min ago"
│   ├── 有错误日志 → 直接定位原因
│   ├── 无日志 → 应用没写日志或日志级别太低
│   └── 有 OOM 相关 → 内存不足被杀
├── Step 3: 手动启动验证
│   ├── 直接运行启动命令(如 /usr/bin/myapp)→ 看报错
│   ├── 检查依赖:ldd /usr/bin/myapp → 缺库?
│   └── /usr/bin/myapp --version → 二进制是否完整?
├── Step 4: 常见原因
│   ├── 端口冲突:ss -tlnp | grep <port> → 端口已被占用
│   ├── 权限不足:无法打开日志文件/监听 <1024 端口/访问数据目录
│   ├── 配置错误:/etc/myapp/config.yaml 格式错误或路径不存在
│   ├── 依赖未就绪:数据库/Redis/Kafka 连不上 → 加等待逻辑或 depends
│   └── 资源限制:ulimit -n 不够 → Too many open files
├── Step 5: systemd 相关
│   ├── systemctl cat myapp → 看 ExecStart 命令是否正确
│   ├── Type= 是否正确?(simple/forking/notify)
│   └── 环境变量是否正确?Environment= 或 EnvironmentFile=

题目6:某个端口被占用但不知道是哪个进程,怎么查?#

排查路径

bash
# 方法1: ss
ss -tlnp | grep :8080
# 输出: LISTEN 0 128 0.0.0.0:8080 users:(("java",pid=12345,fd=42))

# 方法2: lsof
lsof -i :8080
# 输出: java 12345 app 42u IPv4 123456 0t0 TCP *:8080 (LISTEN)

# 方法3: fuser
fuser 8080/tcp
# 输出: 12345/java
fuser -k 8080/tcp  # 直接杀掉

# 方法4: netstat(net-tools,已弃用,新系统最小安装可能没装;优先用 ss)
netstat -anp | grep 8080

找到 PID 后:ps -p 12345 -o pid,ppid,user,cmd 看进程详情。


题目7:系统响应慢,从哪开始排查?#

排查思路(四步法)

text
系统慢
├── Step 1: top/htop — 整体快照
│   ├── load average 高?→ 查 D/R 状态进程
│   ├── %CPU 谁最高?→ 按 P 排序
│   ├── %MEM 谁最高?→ 按 M 排序
│   ├── %wa (iowait) 高?→ IO 问题
│   └── %st (steal) 高?→ 云主机被 hypervisor 偷时间
├── Step 2: 资源维度
│   ├── CPU:top 看利用率 + 进程消耗
│   ├── 内存:free -h + vmstat 1
│   ├── IO:iostat -x 1(%util/await/svctm)
│   └── 网络:ss -s + iftop/nethogs
├── Step 3: 应用层
│   ├── 日志中是否有大量 timeout/retry?
│   ├── 数据库连接池是否满了?
│   ├── GC 日志是否频繁 Full GC?
│   ├── 是否有线程死锁?(jstack <pid> / python pprof)
│   └── 上游是否有重试风暴?
├── Step 4: 对比变更
│   ├── 最近有没有发布?→ 回滚
│   ├── 流量有没有突增?→ 扩容
│   └── 有没有配置变更?→ 对比 git diff

题目8:删了一个大文件但空间没释放,怎么处理?#

故障现象rm -f /app/logs/big.logdf -h 空间没变化。

排查步骤

bash
# 1. 确认是幽灵文件
lsof | grep deleted | grep big.log
# 输出: app 12345 user 5w REG 253,1 10737418240 12345 /app/logs/big.log (deleted)

# 2. 找到持有句柄的进程 PID=12345
# 3. 处理
# 方式A:重定向清空 > /proc/12345/fd/5
# 方式B:重启进程 systemctl restart app
# 方式C:kill -HUP 12345(如果能触发日志重开)

# 4. 确认释放
df -h /app

根因:进程在写这个文件时,rm 只删了目录项,inode 要等所有文件描述符关闭才释放。常见于日志文件被 rm 但应用日志框架还在往旧 fd 写。

治理:日志用 logrotate(配置 copytruncatepostrotate 信号),或者写 stdout 由容器运行时采集。


题目9:/etc/sudoers 被写坏了,sudo 不工作怎么办?#

应急修复

bash
# 前提:有 root 密码或物理/Console 访问
su -                                 # 切换到 root(需要 root 密码)
visudo                               # 修复 sudoers

# 如果是 WSL/容器/无 root 密码
pkexec visudo                        # PolicyKit 方式修复(如果安装了)

# 云主机
# 通过云平台 Console/VNC 登录,用 root 修复
# AWS: EC2 → Connect → EC2 Instance Connect
# Aliyun: 远程连接 → VNC

预防:只通过 visudo 编辑(编辑完会检查语法),永不用 vim /etc/sudoers。准备一个备用 sudoer(如 wheel 组的另一个用户)。


题目10:crontab 任务不执行,怎么排查?#

排查决策树

text
crontab 不执行
├── Step 1: 检查 cron 服务
│   └── systemctl status cron/crond → 是否运行?
├── Step 2: 检查语法
│   ├── crontab -l 查看 → 时间格式是否正确?
│   ├── * * * * * → 分 时 日 月 周
│   └── 常见错误:星期三写成 3(应该是 3,0=周日)
├── Step 3: 检查环境
│   ├── cron 的环境是极简环境(PATH=/usr/bin:/bin)
│   ├── 命令用绝对路径:/usr/bin/python3 /path/to/script.py
│   └── 或脚本开头加:PATH=/usr/local/bin:/usr/bin:/bin
├── Step 4: 检查日志
│   ├── grep CRON /var/log/syslog → 看 cron 是否调起了任务
│   ├── 有 "(root) CMD" 行但没执行 → 脚本本身的错误
│   └── 脚本加日志:echo "$(date): started" >> /tmp/cron.log
├── Step 5: 脚本权限
│   ├── chmod +x /path/to/script.sh
│   └── 脚本中引用的文件/目录权限
└── Step 6: 特殊问题
    ├── % 在 crontab 中会被解释为换行 → 需转义 \%
    └── crontab 最后的空行不能省(部分 cron 要求)

题目11:网络不通,从应用到物理层怎么排查?#

排查路径(OSI 分层倒查)

text
应用层  → curl -v http://target:port
传输层  → ss -tlnp | grep <port> / telnet <ip> <port>
网络层  → ping <ip> / traceroute <ip> / ip route
链路层  → arp -a / ethtool <iface>
物理层  → ip link show / dmesg | grep -i link
DNS     → dig <domain> / nslookup
防火墙  → iptables -L -n / ufw status / 云安全组

每层一个命令,从高层往低层排查。生产中最常见的三大原因:DNS 解析失败防火墙拦截应用没监听正确 IP/端口


题目12:服务日志中出现大量 TCP 连接 TIME_WAIT,怎么处理?#

排查

bash
# 确认 TIME_WAIT 数量
ss -s                              # 快速统计
netstat -an | awk '/TIME_WAIT/{count++} END{print count}'

# 看具体连接
ss -tan state time-wait | head -20

根因:服务作为主动关闭方,大量短连接频繁建立和关闭。TIME_WAIT 是主动关闭方的正常状态(等 2×MSL 确保对端收到最后的 ACK、旧连接报文消散)。高并发下它会耗尽本地端口(默认端口范围 32768–60999,约 2.8 万个)。

处理

bash
# 复用 TIME_WAIT:仅对【主动发起连接的一方,即客户端出站】生效,且需两端开启 tcp_timestamps
sysctl -w net.ipv4.tcp_tw_reuse=1

# 扩大可用端口范围(缓解端口耗尽)
sysctl -w net.ipv4.ip_local_port_range="1024 65535"

两个高频误区(面试常挖)

  • net.ipv4.tcp_fin_timeout 控制的是 FIN_WAIT_2 状态,不是 TIME_WAIT!TIME_WAIT 时长在内核里硬编码为 60s(TCP_TIMEWAIT_LEN,sysctl 改不了(除非重编内核)。所以想靠 tcp_fin_timeout 缩短 TIME_WAIT 是无效的。
  • net.ipv4.tcp_tw_recycle 已在内核 4.12 移除(NAT 后会错误丢弃连接),任何版本都别开。

根治:应用层用连接池、长连接、反向代理(Nginx upstream keepalive),从源头减少短连接——比调 sysctl 更有效。


题目13:系统启动卡住,如何进入救援模式排查?#

救援模式步骤

bash
# GRUB 启动时按 'e' 编辑启动项
# 在 linux 行末尾加:
systemd.unit=rescue.target    # 救援模式(基本服务+root shell)
systemd.unit=emergency.target # 紧急模式(只有 root shell)

# 或加 init=/bin/bash 直接到 bash

# 进入后常见操作:
mount -o remount,rw /          # 重新挂载根分区为读写
journalctl -xb                 # 看启动日志
systemctl list-units --failed  # 看失败的服务
vi /etc/fstab                  # 检查挂载配置

云主机:通过云平台 Console/VNC 登录操作 GRUB。


题目14:容器内 top 看到的 CPU 和宿主机不一致?#

根因:容器内 /proc/cpuinfo 默认显示的是宿主机所有 CPU,没做隔离。需要容器正确配置 cgroup CPU 限制。

验证

bash
# 宿主机看容器的 cgroup 限制
cat /sys/fs/cgroup/cpu,cpuacct/kubepods/.../cpu.cfs_quota_us

# 容器内看实际可用 CPU
nproc                                    # 可能会显示全节点核数(误导)
cat /sys/fs/cgroup/cpu/cpu.cfs_quota_us  # 实际调度配额

K8s 中:resources.limits.cpu=2 对应 cgroup cpu.cfs_quota_us=200000(2 核 = 200ms/100ms × 2)。应用应读 cgroup 而非 /proc/cpuinfo 来获取真实可用核数(Go 用 runtime.GOMAXPROCS,JVM 用 -XX:ActiveProcessorCount)。


题目15:K8s 节点 NotReady,SSH 进去怎么排查?#

排查决策树

text
Node NotReady
├── Step 1: kubectl describe node <node> → 看 Conditions
│   ├── MemoryPressure=TRUE → 内存不足 → free -h / dmesg | grep oom
│   ├── DiskPressure=TRUE → 磁盘 >85% → df -h / du -sh /*
│   ├── PIDPressure=TRUE → 进程数过多 → ps aux | wc -l
│   ├── NetworkUnavailable=TRUE → CNI 插件问题 → kubelet logs
│   └── KubeletReady=FALSE → kubelet 异常
├── Step 2: systemctl status kubelet
│   ├── 没运行 → systemctl restart kubelet
│   └── 运行但报错 → journalctl -u kubelet --since "10m ago"
├── Step 3: 常见根因
│   ├── 磁盘满(容器日志/镜像 → docker system prune)
│   ├── kubelet 证书过期 → kubeadm certs check-expiration
│   ├── containerd/docker 挂了 → systemctl restart containerd
│   ├── GPU 驱动崩溃 → nvidia-smi 无响应 → 重装驱动
│   └── 系统 OOM 导致 kubelet 被 kill → dmesg | grep oom
├── Step 4: 恢复步骤
│   ├── 清理磁盘 → df -h 确认降到 80% 以下
│   ├── 重启 kubelet → systemctl restart kubelet
│   ├── 如不行 → reboot
│   └── 仍不行 → drain + 重装节点

小结#

Linux 排障的核心能力不是记住命令,而是建立从现象到根因的排查路径。每类问题(磁盘/网络/进程/内存)都有标准的排查链,掌握了这些链,面对新问题也能快速定位。