
2026年1月7日我这边的服务器运维环境又出了一堆不大不小的状况。照惯例当天处理完问题之后我会把关键的操作、踩坑过程和命令沉淀成一份Linux笔记。今天这份笔记正好赶上几个比较典型的场景磁盘空间告警、systemd服务启动失败、日志目录异常膨胀、脚本执行行为不符合预期。这些内容单拎出来都不算复杂但组合在一起就是一组非常贴近日常运维的实战场面。无论你是刚接触Linux的初学者还是有几年经验的基础运维这份笔记里提到的排查思路、命令组合和避坑细节可以直接拿过去用也能帮你少走我走过的弯路。先说清楚今天的测试环境形态。所有操作都在一台标准Linux发行版上进行内核版本在6.x附近日志系统使用systemd自带的journald数据盘独立挂载日常跑着几个Web服务和定时任务。以下所有命令都经过实测但实际使用时请根据你自己的发行版、挂载点和权限情况做调整。我习惯把所有排查过程按时间线记录这样回头看的时候每一步为什么这样做、当时看到了什么都会非常清晰。1. 今天这个笔记要解决什么问题1.1 项目背景与实际诉求早上刚到工位监控平台就推送了一条磁盘空间告警。告警内容是根分区使用率超过90%但奇怪的是应用数据明明都在独立的数据盘上根分区按理说只承载系统程序、日志和临时文件。这就引起了一个非常典型的排查需求根目录空间被什么吃掉了日志服务有没有异常膨胀哪些服务在持续写文件另一个需要处理的问题是某个定时备份脚本前一天晚上执行失败。脚本本身是正常的手动跑也没问题但在cron环境里就是报错退出。这种“手动正常、定时异常”的现象又是另一类高发问题环境变量缺失、PATH不对、shell引号处理差异。这些点不单独列出来往往会在半夜把你从睡梦中叫醒。所以今天这份笔记的定位不是泛泛地讲命令而是围绕“一张告警工单 一次任务失败”两条主线把对应的问题拆解清楚并把解决这些问题所需的工具、思路、参数选择和验证方法都记录完整。1.2 前置环境与约定在开始所有排查之前我习惯做三件事确认登录用户权限、确认当前负载快照、确认时间同步状态。这三件事的顺序是有讲究的。# 确认权限是否有sudo id # 抓取一个负载快照后面排查时对照使用 uptime cat /proc/loadavg # 确认时间同步避免日志时间线漂移 timedatectl status权限决定了你能执行多少命令负载快照决定了你的操作会不会进一步拖垮系统时间同步决定了你在查日志时能不能对上时间点。这三样确认完后面无论做什么操作心里都有一张底图。另外我下面的所有操作都尽量非侵入式优先能读配置就不改配置能单独验证就不影响全局。尤其在生产环境里先观察、再定位、最后修改顺序不能乱。2. 做系统体检从负载到空间的完整排查2.1 第一眼load average与wa占高先别急着杀进程第一眼看到uptime输出的时候我发现load average不算特别高但1分钟、5分钟、15分钟三个值有上升趋势。更值得注意的是如果你在终端里观察top输出看到CPU的waI/O wait占比偏高说明系统正在等待磁盘I/O。有经验的朋友一定会有共识这种时候千万别急着kill掉任何进程。wa偏高不代表某个进程出了问题更可能是磁盘本身忙不过来。你贸然杀掉进程可能杀掉了正在做正常I/O的应用引发更严重的故障。正确的观测顺序是先确认负载的组成来源# 查看进程层面的CPU和I/O状态 top -b -n 1 | head -30 # 更直观地看I/O等待比例 mpstat 1 3mpstat会直接展示%iowait这一列。如果%iowait持续超过30%基本上可以判断是I/O瓶颈。接下来就要锁定是谁在写盘、写在哪里、写了多少。2.2 磁盘空间与inode两个都要看磁盘告警首先看df但只看df会漏掉一个重要维度就是inode耗尽。我处理过的很多故障里磁盘明明还有空间但文件系统报错说“No space left on device”原因就是inode用完了。每个文件即使大小是0字节也要占用一个inode索引节点。所以排查空间问题我会同时看两个维度# 查看空间使用情况 df -hT # 查看inode使用情况 df -iT如果df -hT看到根分区90%以上df -iT发现inode使用率也接近100%那说明存在大量小文件。这种情况在Web服务临时目录、邮件队列、PHP会话目录里特别常见。其中邮件队列堵住或者某个程序反复创建临时文件但不清理都会让inode快速耗尽。接下来要用du找出到底是哪个目录最占空间# 从根目录开始逐层定位注意排除/proc、/sys等虚拟文件系统 du -h -d 2 -x / 2/dev/null | sort -hr | head -20这里-x参数非常关键它让du只在当前文件系统内遍历不会跨挂载点避免把数据盘的内容也算进根分区。加上2/dev/null是屏蔽权限不足的报错让输出更干净。sort -hr是按人类可读格式反序排列一眼就能看到最大的前20个目录。2.3 磁盘IO到底是谁在占用iotop与pidstat配合使用空间问题定位完之后接着要回答另一个问题为什么wa偏高是谁在持续写入我常用的工具是iotop和pidstat两者配合能形成“进程-线程-设备”三个层次的闭环。# 使用iotop观察实时进程I/O sudo iotop -o -P -b -n 3 # 使用pidstat查看每个进程的I/O统计 pidstat -d 3 5iotop的-o参数是只显示有I/O操作的进程-P参数是显示进程而不是线程-b配合-n可以输出批量的几个快照。pidstat的-d参数直接展示每个进程的读写速率和I/O等待时间。这两组数据拿回来之后做交叉比对很容易发现某个路径下的进程名是否可疑。比如说如果日志服务疯狂写入iotop会看到它的进程I/O很高如果某个PHP-FPM进程不停读会话文件pidstat会给出具体的读写速率。有了这张进程对照表你才知道下一步该限制什么、清理什么。3. 日志文件管理journald的脾气与处理思路3.1 journald为何越占越大排查到最后发现根分区的空间消耗大户居然是/var/log/journal目录。这几乎已经成了运维里的一个经典问题journald的日志文件越来越大不清理迟早把磁盘撑爆。journald的工作方式是把所有服务通过标准输出和标准错误输出的内容收集起来写到二进制的journal文件里。它的优势是结构化查询非常快不用一个文件一个文件去grep。但代价是默认配置下它不限制日志总量或者限制值设得很大系统运行越久日志积累越多。这里要补充一个判断技巧journald不仅收集服务日志还会收集内核日志和应用通过syslog发来的日志。在一台运行了Web服务、数据库、任务调度的机器上几个小时的详细日志就可能达到几个GB级别。如果服务里打印的内容又杂又多这个膨胀速度还会更快。3.2 配置journald大小上限的正确姿势我处理这个问题的建议是不要直接在命令行里删除journal日志文件因为journald进程持有文件句柄你删了它也不一定会释放空间而且可能导致当前正在写入的日志异常。正确做法是修改journald配置文件限制容量然后重启服务或重新加载配置。# 编辑journald配置 sudo vim /etc/systemd/journald.conf需要调整或新增的关键参数如下[Journal] # 限制journal总大小不超过500M SystemMaxUse500M # 单个日志文件大小 SystemMaxFileSize64M # 日志保留时间超过7天自动清理 MaxRetentionSec7daySystemMaxUse是总的日志容量上限这个值要根据你的磁盘量和业务需求来定。设到500M是我在普通业务机器上比较常用的数值如果磁盘紧张可以再降到200M。SystemMaxFileSize控制单个journal文件的大小拆分成小文件之后后续清理和轮转会更均匀。MaxRetentionSec是保留时间设置了之后超期日志会自动被清理。改完配置之后执行下面的命令让它生效sudo systemctl restart systemd-journald重启之后用journalctl查看磁盘占用journalctl --disk-usage如果输出显示的用量已经降下来说明配置生效。如果没有可以执行一次主动清理# 清理超过3天的日志 sudo journalctl --vacuum-time3d # 或者直接把日志总量压到300M以内 sudo journalctl --vacuum-size300M这两条命令都可以随时手动执行效果非常直接。注意主动清理和配置上限两者最好配合使用一个管当前一个管长期。3.3 那些你容易忽略的日志细节清理journal的时候还有几个细节值得记录不然下次还会栽在同一个坑里。第一个细节是journalctl默认显示的时区是系统本地时区但如果你查的是跨时区的服务日志时间校对会很麻烦。我习惯在查询的时候加上--utc参数统一用UTC时间观察这样和监控平台的告警时间对得上。第二个细节是journalctl查询支持按服务名、可执行文件路径、特定字段过滤。而这套过滤语法和grep是两回事它底层走的是索引查询。如果在大量日志里瞎grep效率会很低。# 查看某个服务的今天日志 journalctl -u nginx.service --since today # 查看某个可执行文件的日志 journalctl _COMMsshd # 按优先级查看0-3是emerg到err级别的错误 journalctl -p err -b-p err配合-b参数只看本次开机以来的错误级日志这个组合是定位启动故障的利器。就算日志量再大这两个参数一加输出量会立刻缩小到可控范围。4. 服务排查systemd单元文件与启动故障的节奏4.1 单元文件的依赖和顺序问题除了磁盘告警今天还遇到一个服务启动失败的问题。从systemctl status的输出看到服务状态是failed原因描述是“dependency failed”这个提示说明单元文件里的依赖关系没有满足。systemd单元文件的依赖关系影响着服务的启动顺序。最常见的情况是某个服务写明了Requires或After依赖的目标没有正常启动这个服务就会跟着失败。比如一个Web服务依赖网络就绪、依赖配置目录可写、依赖数据库服务先启动。排查依赖问题我会用下面这几个命令来展示单元文件的全貌# 查看服务状态与错误信息 systemctl status 某服务.service # 查看单元文件的完整定义 systemctl cat 某服务.service # 列出依赖关系 systemctl list-dependencies 某服务.servicelist-dependencies能展示这个服务依赖了哪些目标哪些依赖缺失通过这个输出可以快速判断是不是某个底层target没有起来。结合journalctl -u查出的日志基本能还原失败链路。4.2 服务启动失败的常规定位思路当服务的状态是failed时常规定位思路可以分为四步。第一步是看systemctl status的输出段。systemd已经把最近几条日志附在了status输出里这几条通常能直接告诉你服务为什么退出。如果这些信息不够再执行journalctl -u 服务名 -b -p err从开机记录里筛选当前服务启动相关的错误日志。第二步是确认配置文件语法。如果是我们自己编写的服务单元文件语法错误是常见原因。用下面这个命令可以校验单元文件是否存在语法问题sudo systemd-analyze verify /etc/systemd/system/某服务.service第三步是检查可执行文件的权限和路径。服务启动以系统用户身份运行常见问题是可执行文件没有执行权限或者解释器路径不对。手动用该用户身份执行一次命令很多问题会立刻暴露。第四步是查看SELinux或AppArmor等强制访问控制模块的拦截记录。这个在日志里不会显示为普通报错但会在内核审计日志里留下痕迹。遇到权限报错时别只盯着服务本身也要检查avc denied记录。4.3 用最小化重启代替盲目重启服务启动失败后最简单的做法是systemctl restart。但我的经验是盲目重启会掩盖真实原因也会让故障反复出现。我通常的做法是先调整配置再用daemon-reload重新加载然后用start把服务拉起来。# 修改或部署单元文件后必须重新加载 sudo systemctl daemon-reload # 启动服务 sudo systemctl start 某服务.service # 确认服务状态 systemctl status 某服务.service只有当你确认服务配置和运行环境都没问题时才用restart。restart包含stop和start两个动作中间任何一步失败都会直接报错。把这两个动作拆开你能清楚看到服务到底卡在哪一步。还有一个很小但很实用的点systemd服务启动时的工作目录和当前环境变量都是定义在单元文件里的。如果你在脚本里写了相对路径或者依赖了某个自定义环境变量手动跑脚本正常但service启动就失败这是非常典型的问题。用ExecStart写绝对路径、用Environment显式声明环境变量能省掉大量无谓的排查时间。5. shell脚本细节变量、引号与退出码5.1 引号与字段切割问题今天定时备份脚本失败的直接原因最后定位到shell脚本的引号处理上。这可能是所有shell脚本问题里最隐蔽、也最容易反复踩坑的一类。Shell会对字段做切割也就是根据空格、制表符等分隔符把字符串拆成多个单词。如果你在变量赋值或命令参数里没有加引号一旦路径里包含空格命令就会解释出错。在cron环境里PATH通常被精简到最小程度连/bin:/usr/bin都可能缺这缺那此时脚本里任何裸奔的路径都可能变成隐性问题。我举一个非常典型的例子。假设脚本写到LOG_DIR/var/log/my service logs如果没有引号包裹这个变量会被拆成多个词。正确写法是LOG_DIR/var/log/my service logs传递参数时也一样。文件名包含空格的场景不加大引号命令收到的参数数量会不对。这个问题在本地手动执行时可能因为当前目录不同而没暴露在定时任务里一跑就崩。5.2 set -euo pipefail的取舍我的脚本习惯会在开头写一行#!/usr/bin/env bash set -euo pipefail这一行解决了三个大问题。set -e让脚本在遇到未处理的错误时立即退出避免错误被后续命令掩盖。set -u让脚本在引用未定义变量时直接报错避免因为变量拼写错误而产生空值。set -o pipefail让管道命令中任何一个环节失败都会导致整个命令失败避免只看管道最后一环的退出码。但这里有个操作要点需要提醒set -e并不是万能的它会让人在调试时对报错位置产生误判。比如在条件判断里某个命令的失败退出码可能被if结构捕获脚本不会退出。此时你看到的后续行为并未终止容易让人以为是逻辑bug。所以我的习惯是先用bash -x或set -x追踪执行过程定位具体是哪一行在哪个环境下退出的而不是只在脚本里依赖-e来兜底。在这个笔记里我还要补充一个对新手安全感的细节在脚本里显式声明解释器写#!/usr/bin/env bash而不是只写#!/bin/sh。很多系统里sh可能被链接到dash语法行为有差异。用bash声明能保证本地调试和cron执行时用的是同一套解释器。5.3 退出码和trap清理第四个关于脚本的深刻教训是脚本退出码和清理动作之间的关系。定时任务调度器判断任务成不成功只看进程的退出码。如果脚本里的命令一路执行但最终没有用exit 0显式退出那么脚本的退出码由最后一条命令决定这很容易造成假成功或假失败。我现在的做法是在脚本的关键节点显式记录退出状态并用trap做统一清理#!/usr/bin/env bash set -euo pipefail CLEANUP_FILE/tmp/backup_$$.lock cleanup() { rm -f $CLEANUP_FILE echo cleanup done at $(date) } # 进程退出时统一执行清理 trap cleanup EXIT INT TERM echo backup started at $(date) # 具体业务逻辑...trap指定了当脚本退出正常退出、被中断、被终止时执行的清理函数。这样即使脚本中途失败临时文件也不会残留下次执行不会因为残留锁文件而中断。退出码的正确检查方式我习惯在脚本的关键节点输出状态if [ $? -eq 0 ]; then echo command success else echo command failed exit 1 fi实际上在set -e的脚本里这种模式可以精简但在不启用set -e的老脚本里显式判断仍是最稳妥的。排障时在关键位置加echo和exit会让整个脚本的走向一目了然。6. 笔记对应的完整实践脚本与命令清单6.1 诊断脚本主体基于今天整个排查过程我整理了一份可以直接使用的诊断脚本。它的作用是一次性输出系统负载、磁盘空间、inode使用、进程I/O和journald日志用量并标注关键阈值。日常巡检或故障发生时先跑一遍这个脚本就能形成一张完整的系统体检快照。#!/usr/bin/env bash # 2026-01-07 系统诊断脚本 set -uo pipefail echo 系统负载 uptime echo echo CPU与I/O等待 mpstat 1 3 2/dev/null || echo mpstat不可用可安装sysstat echo echo 磁盘空间与inode df -hT df -iT echo echo 占用空间最大的目录 du -h -d 2 -x / 2/dev/null | sort -hr | head -20 echo echo 活跃I/O进程 sudo iotop -o -P -b -n 3 2/dev/null || echo iotop不可用或需要sudo echo echo 每个进程I/O统计 pidstat -d 3 5 2/dev/null || echo pidstat不可用可安装sysstat echo echo journald日志用量 journalctl --disk-usage echo 诊断结束 脚本里我特意没有用set -e因为诊断过程中某一条命令失败不应该阻断后续命令。set -u我保留了用于捕捉未定义变量。每个命令都加了2/dev/null或回退提示确保在工具缺失时脚本不会直接发出一大堆报错。6.2 执行效果与输出解读执行这份脚本后你能看到几类关键输出。比如uptime里load average的走势mpstat里的%iowaitdf里的Use%列和IUse%列du定位出的路径排名iotop里活跃进程名以及journald的当前占用。把这些产出拼在一起判断链路就完整了。举例来说如果你看到%iowait连续三次采样都在60%以上同时iotop显示某服务进程每秒写入几十MB然后df又显示对应目录挂载点使用率超高那么问题定位就变得非常清晰该服务日志量过大导致磁盘I/O饱和进而拖慢整个系统。如果du定位出某个目录全是小文件df -iT又显示inode使用率到了95%以上说明该目录出现了大量零散文件。下一步可以进入那个目录按文件数量排序找源头。6.3 后续可以继续扩展的方向这份脚本目前定位是“快照式体检”但它完全可以扩展成一套迷你监控工具。第一个方向是把关键指标写成Prometheus文本格式配合node_exporter采集由Prometheus实时监控超过阈值自动告警。第二个方向是把脚本放到cron里每小时生成一次体检报告保留历史记录形成趋势数据。第三个方向是在脚本里加入邮件通知或webhook通知当关键指标超过阈值时主动推送消息。我个人更推荐第二和第三个方向结合。因为很多故障在发生时已经产生了影响但如果你有历史趋势数据可以在故障发生前就发现异常苗头。最后再分享两个小技巧第一个小技巧是关于journald的。如果你不想重启systemd-journald又想立刻腾出空间可以用journalctl --rotate让日志先轮转再执行--vacuum-size50M。轮转会把当前活跃日志切成归档文件之后清理归档会更安全。第二个小技巧是关于脚本排障的。当脚本在cron里失败手动跑却正常时不要急着改逻辑。先用cron的调度命令套一个日志输出把标准输出和标准错误都重定向到文件里拿到现场输出再分析。很多问题只在定时环境里暴露没有现场日志就只能靠猜。把下面这行加到crontab里问题通常会浮出水面* * * * * /路径/脚本.sh /路径/output.log 21这次看回2026年1月7日的经历我最大的体会是一个看似简单的磁盘告警背后的排查链路可以横跨负载分析、文件系统、日志系统、服务管理和shell脚本每个环节之间都有因果关系。把排查方法沉淀成笔记和脚本下一次再遇到类似问题就能更快定位。如果你想继续深入还可以从systemd的资源限制、文件系统的配额监控、脚本里的并发控制这几个方向发力每一个都是独立的大坑以后我再专门写笔记展开聊。