Linux 基础 22:日志与故障排查方法论——从现象到根因的六步

发布于 2022-04-30 20:30 更新于 2022-04-30 22:10 1262 字 7 min read ... 访问量 手写 每个字都是我自己敲的。

Linux 基础 25:收尾——自查清单与继续深入的方向Linux 基础 24:启动流程与救援——UEFI、GRUB、initramfs 与 emergency modeLinux 基础 23:容器与内核边界——namespace、cgroup 与 docker exec 背后Linux 基础 22:日志与故障排查方法论——从现象到根因的六步Linux 基础 21:定时任务——crontab 的坑与 systemd timerLinux 基础 20:Shell 脚本(下)——trap、日志、幂等与并发锁Linux 基础 19:Shell 脚本(上)——引号、条件与退出码Linux 基础 18:性能观测与 strace——机器到底卡在哪Linux 基础 17:防火墙——iptables、nftables、firewalld 与 ufw 的关系Linux 基础 16:SELinux——当权限都对,服务还是被拒绝Linux 基础 15:权限进阶——sudoers 的通配陷阱、ACL 与 capabilitiesLinux 基础 14:SSH 与远程运维——密钥、权限与三种隧道Linux 基础 13:网络基础——自下而上的排错顺序Linux 基础 12:磁盘与文件系统——从块设备到挂载点Linux 基础 11:systemd——unit、systemctl 与 journalctl 的正确用法Linux 基础 10:包管理——apt 到底做了什么,以及四个高频报错Linux 基础 09:用户与组——passwd、shadow、sudoers 三件套Linux 基础 08:环境变量与 shell 启动文件——为什么 ssh 跑命令找不到你的 PATHLinux 基础 07:进程与信号——TERM 和 KILL 到底差在哪Linux 基础 06:管道与重定向——三条流、一个顺序问题Linux 基础 05:文本处理——把 grep/sort/uniq 变成你的分析工具Linux 基础 04:权限与属主——rwx 在文件和目录上不是一回事Linux 基础 03:ls、cp、mv、rm 的真实语义,以及那个由 shell 展开的 *Linux 基础 02:/ 底下为什么是这些目录,inode 里到底存了什么Linux 基础 01:终端、Shell,和命令是怎么被找到的
journald 与 /var/log 的分工、journalctl 的常用组合、logrotate 要点,以及一套六步排障法:描述现象、画时间线、找最近变更、缩小范围、验证假设、记录复盘;附磁盘满、端口被占、权限拒绝三个真实案例。

这一节要解决什么

出故障时最诱人的动作是 systemctl restart 一下,赌它自己好。这一下会把现场清掉——内存里的状态没了,连接计数重置了,崩溃前的最后几行日志可能也没了,之后再想查"为什么"就少了一半证据。

这一节讲清日志都在哪、怎么看,然后给一套能反复套用的六步排障法,最后用三个真实案例把它走一遍。

一、日志在哪:journald 与 /var/log 的分工

journalctl -n 50                     # 最近 50 行
journalctl -p err -b                 # 本次开机以来 err 级别以上
journalctl --since '1 hour ago'      # 时间窗
journalctl -b -1                     # 上一次开机,崩溃或重启后查这个
journalctl -u nginx -f               # 跟某个服务的实时输出
journalctl -o json-pretty -n 1       # 结构化字段:_PID、_COMM、unit 等
位置谁写的
journaldsystemd 服务与内核消息,用 journalctl 统一查
/var/log/syslog、messages传统 syslog 汇总,Debian 与 RHEL 命名不同
/var/log/auth.log、secure登录、sudo、sshd
/var/log/dpkg.log、yum.log包管理操作记录
/var/log/nginx/*.log应用自己的日志

关键结论:先 journalctl -u <服务> 看服务视角,再 journalctl -b -1 或 -p err 看系统视角;而应用日志(nginx、数据库)不在 journald 里,必须去它自己的目录看。

二、logrotate 要点

/var/log/app/*.log {
    daily          # 或写 size 100M
    rotate 14      # 保留 14 份
    compress       # 旧的 gzip
    missingok
    notifempty
    copytruncate   # 服务不支持重开文件时用:先复制再清空原文件
    postrotate
        systemctl reload app >/dev/null 2>&1 || true
    endscript
}

批注:服务能重开文件就用 postrotate 加 reload,最干净;不能重开就用 copytruncate——代价是有一个小小的丢日志窗口,但不用重启服务。日志目录也要做容量监控,否则 rotate 策略写错时磁盘照样会满。

三、六步排障法

  1. 描述现象:要可观测、可复现——"什么时间、什么操作、看到什么、期望什么"。含糊的"它坏了"没法查。
  2. 画时间线:用 journalctl --since/--until/-b -1 确定"从何时开始"。
  3. 找最近变更:发布、配置、证书、依赖升级、磁盘空间。八成故障紧跟在一次变更之后。
  4. 缩小范围:属于哪一层——应用、网络、磁盘、权限、SELinux。用前面几节的工具逐层排除。
  5. 验证假设:一次只改一个变量,改完再看。同时改三样,永远说不清是哪样起了作用。
  6. 记录与复盘:写下现象、根因、动作;下次同类问题直接查。

先别急着重启,先保留现场。 重启会清掉内存里的状态、重置连接计数、丢掉崩溃前的上下文。先把 journalctl、ss、df、ps 各类快照截一遍,再决定动不动手。

四、三个真实案例

案例 A:磁盘满。 服务突然写不进数据,df -h 显示根分区 100%。但 du -sh /* 加起来远不到满——因为有进程抓着已删除的大文件:df 算的是块占用,du 看不见已经 unlink 的 inode。sudo lsof +L1 列出这些"删了但没释放"的文件,重启那个进程就能回收空间。

案例 B:端口被占用。 新服务起不来,日志里是 address already in use。ss -ltnp | grep :8080 找到占用者的 PID,sudo fuser -k 8080/tcp 或直接 kill。也可能是旧进程没退干净或残留的 TIME_WAIT(这种等超时即可,不用杀)。先看清楚是谁,别上来就重启整个服务。

案例 C:权限拒绝。 Permission denied,可 ls -l 看着完全正常。用 namei -l /data/app/upload/file 从根开始逐级看每层目录的权限——最常见的坑是中间某一级目录缺了 x 位。如果权限都对,再 getenforce 看是不是 SELinux 拦的(见第 16 节)。

五、动手清单

journalctl -p err -b --no-pager | tail -30          # 1. 本次开机的错误
journalctl -u myapp --since '30 min ago'            # 2. 目标服务近况
df -h; df -i                                        # 3. 空间与 inode
sudo lsof +L1 | head                                # 4. 被删但未释放的文件
ss -ltnp | head                                     # 5. 监听与占用
namei -l /path/to/file                              # 6. 逐级权限
sudo logrotate -d /etc/logrotate.d/app              # 7. 干跑日志切割配置

小结

  • 优先 journalctl(-u、-p、-b -1、--since、-o json-pretty);应用日志去 /var/log 下自己的目录。
  • logrotate 三件套:daily/size 加 rotate N 加 compress,再加 postrotate 或 copytruncate。
  • 六步:描述现象 → 画时间线 → 找变更 → 缩小范围 → 验证假设 → 记录复盘。
  • 变更之后出故障不是巧合;一次只改一个变量才能归因。
  • 磁盘满先 df -h 再 df -i 再 lsof +L1;端口占用先 ss -ltnp;权限问题先 namei -l。
  • 先保留现场再动手,重启是最后手段,不是第一反应。

下一节预告

下一节:容器与内核边界——容器其实是普通进程加上 namespace 与 cgroup、docker exec 和 nsenter 的关系、cgroup v2 的 cpu/memory 限额,以及 docker 组为什么几乎等于 root。

执笔 手写

喜欢的话,留下你的评论吧~

加载中...