接手这套系统也有几年时间了,最烦看那种把几十条命令铺开背一遍的文章。真正到了线上,CPU 突然飙到 90%、磁盘明明删了大文件却还是满、服务半夜自己挂掉,这时候你脑子里能快速调出来并且敢直接敲下去的命令,才是最有价值的。
这一篇“Linux系统运维相关命令实践(二)”延续一贯的实用风格,不系统背命令,而是按真实运维场景来拆。哪些命令能救命,哪些命令容易踩坑,命令跑完之后输出怎么理解,我会尽量讲透。进程管理、磁盘清理、网络排查、日志检索、systemd 服务管理、用户权限这几个方向,基本覆盖了日常遇到的大部分故障类型。适合刚接触 Linux 服务端的同学,也适合已经上岗一段时间、想补强排查思路的运维朋友。
1. 负载飙高时的第一反应:用对命令才能快速锁定元凶
不少刚入行的同事一看到 load average 过 5 就紧张,马上跑去 kill 进程,这个处理顺序其实是反的。负载高不一定只是 CPU 忙,还可能出在 IO 等待、内存换页、进程状态异常这些隐性因素上。你不看指标下判断,很容易误杀业务进程,甚至把正常的批量任务中断掉。
1.1 三个指标看方向,别只看 top 第一行
登录服务器之后,我的习惯是先敲三个命令,每个都只看数秒,再做下一步判断:
bash复制uptime
top -b -n 1 | head -15
vmstat 1 3
第一个命令用来看负载历史趋势。load average 后面三个数字分别是 1 分钟、5 分钟、15 分钟的平均负载。如果 1 分钟明显高于 15 分钟,说明是刚刚突发的;如果三个数字都高,说明持续了一段时间,不可能敲一条命令就恢复,得认真排查。
第二个命令用来确认当前 CPU 使用率的构成。top 输出里需要注意三列:us 用户态占用、sy 内核态占用、wa IO 等待。如果 wa 很高,那么 CPU 其实在等磁盘,单纯扩容 CPU 没有用,要先查日志落盘是不是过猛、磁盘是不是有坏道。
第三个命令我很推荐保留这个习惯。vmstat 1 3 里的 r 表示可运行进程数,b 表示不可中断睡眠进程数,si/so 表示内存交换页。如果 r 长期大于 CPU 核心数,说明确实是计算密集,再看 so 如果持续不为 0,大概率是内存不够导致频繁换页,CPU 大量时间耗在换页上,这种情况就得先加内存而不是加 CPU。
1.2 定位具体进程,用一条组合命令
指标方向确认之后,需要找出是哪一个进程在捣乱。虽然 top 交互界面按一下大写 P 可以按 CPU 排序,但在故障现场我更习惯直接输出一条排序后的快照:
bash复制ps -eo user,pid,ppid,pcpu,pmem,stat,comm --sort=-pcpu | head -20
pcpu 列是按照 CPU 使用率降序排列的,pmem 列可以顺带看内存。这条命令的优势是可以直接记录现场,后续写故障报告、比对历史数据都有依据。
如果进程不固定,一会儿 PID 是 A,过一会儿又被新进程替代,那有可能是脚本或者定时任务在疯狂拉起进程。这时候就得往上查它的父进程 PID。ppid 那一列就是关键线索,翻到父进程以后再用 pstree -ap 看父子关系,基本能判断出是谁在背后反复拉起子进程。我遇到过最典型的案例是 crontab 里写了一个死循环脚本,每分钟执行一次,每次都不退出,结果服务器上一堆同名脚本进程,负载直接被拖爆。
1.3 遇到 D 状态进程,先别急着 kill
ps 输出里的 STAT 列如果显示 D,代表进程处于不可中断睡眠状态,绝大多数情形是在等磁盘 IO 返回。这种状态有个特点,你发 kill -9 给它,它也可能一直杀不掉,因为内核在等待底层 IO 完成,进程根本不会响应信号。
碰到 D 状态进程大量堆积,正确思路是顺着 wa 指标往下查。可以用 iostat -x 1 3 看磁盘的 %util 和 await,%util 到 100%、await 超过几百毫秒,基本坐实 IO 瓶颈。再往下就是看哪块盘在忙、是不是有云盘因为快照或备份被拖慢。这类问题多半要靠扩容、迁移或优化读写模式解决,而不是靠结束进程解决。
1.4 顺手记录故障现场,别等复盘时抓瞎
每当服务器异常,我会先把以下信息存到临时文件:
bash复制date > /tmp/fault_time.txt
uptime >> /tmp/fault_time.txt
ps -eo pid,ppid,pcpu,pmem,stat,comm --sort=-pcpu | head -30 >> /tmp/fault_time.txt
ss -antp >> /tmp/fault_time.txt
等恢复之后,这些记录就是排查报告的原始素材。很多时候你当时觉得能记住,但连续处理两个故障后记忆就混淆了,有一个只读快照,后续复盘效率会高很多。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 磁盘明明删了文件却不释放空间:找到隐藏的句柄才是关键
磁盘运维大概是日常工单里最频繁的一类。真正有坑的不是“空间满”本身,而是“删了大文件之后空间还是满”。很多初级运维在这个问题前会反复执行 rm -f,越删越怀疑人生,最后发现是文件被运行中的进程占用着,目录里看不到了,但空间一直被占住。
2.1 先用 du 找到真正的大目录
排查磁盘空间,第一步永远是确认是哪个分区满了:
bash复制df -h
然后顺着挂载点往下找大目录。比较高效的做法是逐步缩小范围:
bash复制du -x --max-depth=1 -h /var | sort -hr | head -20
du -x --max-depth=1 -h /var/log | sort -hr | head -20
-x 参数的含义是不跨越文件系统,避免把 /var 下的其他挂载点也统计进来,否则很容易误判。sort -hr 是按人类可读单位倒序排,比如 2.3G 会排在 800M 前面。
如果发现某个目录体积异常,可以用 find 直接找出超大文件:
bash复制find /var/log -type f -size +500M -exec ls -lh {} \;
2.2 删除后空间不释放的经典排查链
这是本节的重点。Windows 用户很难理解“文件删了还在”这种现象,但 Linux 下确实常见。原因很简单:文件是否被删除,看的是目录项是否消失;文件数据是否真正释放,看的是还有没有进程持有这个文件的文件描述符。
业务日志、核心转储文件最容易陷入这种状态。比如 nginx 还在运行,你执行了 rm -f /var/log/nginx/access.log,但 nginx worker 进程仍然保留着那个文件的句柄,新日志会继续往里写。你用 df -h 看,空间没变少,用 ls 看,目录里也没有这个文件,很诡异。
排查命令是:
bash复制lsof +L1 | grep deleted
+L1 的含义是列出链接数小于 1 的文件,也就是文件已经不在目录树里的打开文件。输出里会显示进程名、PID、文件大小。确认是哪个进程之后,最稳妥的处理方向有两种。
如果能通过服务正常重载释放句柄,就优先走正常途径。例如 nginx 可以用 /usr/sbin/nginx -s reload,HUP 信号会让 worker 进程重新打开日志文件;syslog 类的服务可以发 HUP 信号,让进程关闭旧的文件描述符并重新按配置打开。切忌不分青红皂白直接 kill -9 业务进程,那等于主动制造一次服务中断。
2.3 日志轮转配置比手动删除更可靠
手动清理日志容易踩坑,还容易误删,生产环境我更推荐用 logrotate 做自动轮转。一个典型的 nginx 日志配置大致是这样:
text复制/var/log/nginx/*.log {
daily
rotate 14
compress
delaycompress
missingok
notifempty
sharedscripts
postrotate
/bin/kill -USR1 $(cat /var/run/nginx.pid 2>/dev/null) 2>/dev/null || true
endscript
}
daily 表示按天轮转,rotate 14 表示保留最近 14 个归档,compress 表示压缩旧日志。关键点是轮转后要通知 nginx 重新打开日志文件,不然进程还抱着旧文件句柄不放。这条 postrotate 里的信号处理,其实就是把前面说的句柄问题用自动化方式解决掉。
生产环境配置完 logrotate 之后,建议手动执行一次验证:
bash复制logrotate -d /etc/logrotate.d/nginx
-d 为 debug 模式,会打印执行过程但不实际产生结果,确认没问题后再去掉 -d 真正跑一次。
3. 网络排查的正确顺序:ping 通只代表主机活着,不代表业务可用
网络层面的问题排查,是运维日常中最考验基本功的场景。关键是要理解分层排查的思路,不要一上来就怀疑 IP 配置有问题。我见过不少人看到业务连不上数据库,就先一顿 ping,ping 通之后便不知道下一步怎么走了,这就是缺少网络排查顺序感的典型表现。
3.1 用 ping 判断主机状态,用端口探测判断业务状态
ping 测的是 ICMP 协议,只要目标主机网络配置正确且没有防火墙过滤 ICMP,就能通。但业务访问走的是 TCP/UDP 协议,而且是在特定端口上。所以 ping 通之后,业务端口是否可达还需要单独验证。
最传统的探测命令就是 telnet:
bash复制telnet 192.168.1.100 3306
如果目标是开放的,终端会显示类似 Connected to 192.168.1.100,然后进入一个等待输入的界面。如果端口不通,通常会一直卡住,直到超时提示 Connection refused 或 No route to host。
还有一种更快、更适合脚本化探测的工具是 nc:
bash复制nc -vz -w 3 192.168.1.100 3306
-v 输出详细信息,-z 只做端口探测不发数据,-w 3 表示超时 3 秒。这个命令在写脚本检查多个端口时非常好用,返回码 0 表示端口可用,非 0 表示不可用。
3.2 端口不通,先用 ss 看本机监听情况
很多端口“不通”是服务器自身服务没起来造成的。所以做远端探测之前,先在目标机器上确认服务有没有在听端口:
bash复制ss -lntp
-l 只看监听状态,-n 不解析服务名直接显示端口号,-t 仅显示 TCP,-p 显示进程信息。输出列里会看到类似 LISTEN 0 128 0.0.0.0:3306 0.0.0.0:* 这样的行。如果 3306 根本没出现在监听列表里,那问题就不是防火墙,而是数据库服务没起来或者配置绑定了别的地址。
如果监听地址是 127.0.0.1:3306,远端访问不了就非常正常了,这是 MySQL 绑定回环地址导致的,需要去改数据库配置文件,把 bind-address 改成实际业务网段或 0.0.0.0,然后重启服务。这个例子很经典,它说明了一个问题:查网络要从本机向外一层层推进,不要跳过本机状态直接怀疑外部环境。
3.3 curl -v 看 HTTP 业务的详细交互过程
遇到 HTTP 接口访问异常,curl 是我最常用的调试工具,而且必须加 -v:
bash复制curl -v http://192.168.1.100:8080/api/health
-v 会把 TCP 连接建立过程、HTTP 请求行、响应状态码、响应头全部打印出来。如果停留在 Connected to ... port 8080 之后没有后续内容,可能是服务端处理超时;如果直接出现 Connection refused,大概率端口没监听;如果出现 Connection reset by peer,可能是服务端主动断连,比如超时配置、反代配置、防火墙规则都可能导致。
检查一段带 TLS 的 HTTPS 服务时,curl 还可以加 -k 跳过证书校验,但只建议调试用,完整校验是生产环境必须保留的默认行为。
3.4 域名解析的命令细节
访问域名前,如果想要确认解析到了哪台服务器,当前主流 Linux 发行版自带的命令略有差异。老一些的机器上常用 nslookup,新系统可能不带这个工具,但 getent hosts 基本都存在:
bash复制getent hosts www.example.com
返回的第一列是 IP 地址,能直接看出解析结果。如果是 127.0.0.1 且你没配本地 hosts,那就是内网 DNS 的问题;如果想看详细解析链路,再上 dig 也不迟。生产环境我一般先用 getent hosts,因为它同时会读取 /etc/hosts,更贴合实际运行环境。要知道 /etc/hosts 文件也参与域名解析,容易让人忽略。
4. 分析日志常驻命令:grep 和 awk 的组合能应付九成场景
日志就是运维的眼睛,但原始日志文件动辄几个小时几百 MB,肉眼是看不完的。文本处理三剑客 grep、awk、sed 在这时候最能发挥价值。这一节不展开讲三者全部语法,只讲我在生产中反复使用的几种日志场景。
4.1 统计状态码与接口请求量
访问日志一般按固定格式记录。拿常见的 nginx 默认格式来说,状态码通常在第九个字段,请求 URL 在第七个字段左右。统计一下今天的请求状态分布:
bash复制awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn
输出类似这样:
text复制456123 200
3210 304
128 404
53 502
一眼就能看出 502 有 53 次,如果这本是一个不该出现的比例,就要顺藤摸瓜查后端。统计某个接口被调用了多少次:
bash复制grep '/api/order' /var/log/nginx/access.log | awk '{print $9}' | sort | uniq -c
如果你用的日志格式里最后一列是响应时间,想求某个接口的平均耗时,可以用 awk 做累加:
bash复制awk '$7 == "/api/order" {sum += $NF; count++} END {if (count > 0) printf "%.2f\n", sum/count}'
一段一段解释:$7 是请求路径字段,匹配成功就把最后一个字段加到 sum 里,同时 count 加一。END 块在文件读完后执行,打印平均耗时。很多时候接口慢不是看单次日志就能确认的,这种批量统计能快速显示整体趋势。
4.2 按时间段提取日志
默认的 grep 是全文件扫描。如果想看某个时间窗口的日志,可以用 grep 加字符串条件,比如:
bash复制grep '2024-06-01T12:0' /var/log/app/app.log
这个表达式能匹配下午 12:00 到 12:09 的日志。但时间边界更精确时,用 sed 做区间匹配更合适:
bash复制sed -n '/2024-06-01 12:00:00/,/2024-06-01 12:10:00/p' /var/log/app/app.log
-n 表示默认不输出每一行,只有落在起始模式与结束模式之间的行才会被打印。注意,这个写法依赖日志本身存在这两个时间点,如果 12:10:00 秒整没日志,可能就会一直打印到文件末尾。
4.3 实时跟踪与异常上下文
排查线上问题,经常要一边复现一边盯日志。tail -f 是最基本的,但只盯着输出容易淹没在海量日志里。这时候可以用管道组合:
bash复制tail -F /var/log/app/app.log | grep --line-buffered -E "ERROR|Exception"
-F 比 -f 强的地方是,即使日志文件被 logrotate 改名重建,它也能自动跟随新生成的文件。--line-buffered 让 grep 每收到一行就立即输出,而不是等缓冲区满了再吐,这样实时性才够。
如果想在报错日志之外再带上前后的关联日志,用 grep 的上下文参数:
bash复制grep -n -B 5 -A 20 "OutOfMemoryError" /var/log/app/app.log
-B 5 打印匹配行前 5 行,-A 20 打印后 20 行。排查堆栈异常类问题基本靠这个组合。
4.4 去重与提取关键信息
当天错误日志有大量重复堆栈时,直接看全文效率很低。我可以先提取错误大类,再去重排序:
bash复制grep -E "ERROR|WARN" /var/log/app/app.log | awk '{print $3, $4, $5}' | sort | uniq -c | sort -rn | head
这段命令的实用性在于,它能帮你快速把错误类型归类,而不是被几千条类似日志淹没。字段位置得按照自己系统的日志格式微调,但只要调好一次,后面很多值班分析都能复用。
5. systemd 服务日常:从启动失败到开机自启排查
现代主流 Linux 发行版都在用 systemd。平时发布项目、修改配置、重启服务,都离不开 systemctl 和 journalctl。但在接手新环境时,我发现不少人只会机械地执行 systemctl restart service,等项目起不来就直接懵住。
5.1 查看状态要带原因
服务异常时,最直接的命令是:
bash复制systemctl status nginx.service
如果服务启动失败,status 输出里通常会直接显示进程退出码,比如 code=exited, status=127。127 在 shell 环境里表示命令找不到,在这类场景下很可能就是 nginx 二进制文件的动态库加载失败,或者启动脚本里写了不存在的路径。
如果状态显示是 activating (auto-restart),说明服务反复重启,systemd 在按重启策略拉起。这时候先用以下命令看最近日志,再定位原因:
bash复制journalctl -u nginx.service --since "10 minutes ago" -n 100
5.2 systemd 的配置加载规则
systemd 服务单元文件的搜索路径有好几处,/lib/systemd/system、/usr/lib/systemd/system、/etc/systemd/system。当几个目录下存在同名单元文件时,/etc/systemd/system 优先级最高。很多软件通过包管理器安装后,单元文件在 /usr/lib/systemd/system 下,但我们调整配置时,不应该直接改那个目录下的文件,而应该在 /etc/systemd/system/ 下建覆盖文件。
查看当前生效的配置可以用:
bash复制systemctl cat nginx.service
它会打印最终合并后的单元文件内容和来源路径,这样能确定当前配置到底来自哪里。
当我们自己写了单元文件,比如给一个 Python 服务写的配置文件 /etc/systemd/system/myapp.service,修改后必须让 systemd 重新加载:
bash复制systemctl daemon-reload
systemctl restart myapp.service
daemon-reload 这步经常有人漏掉。你改了文件但不执行它,直接 systemctl restart 时 systemd 用的可能还是旧配置,表现出来的现象就是配置改了但没生效,特别容易让人误以为改错文件。
5.3 管理开机自启与常见错误
新服务上线时,我习惯按三步走:
bash复制systemctl enable myapp.service
systemctl start myapp.service
systemctl --failed
最后一条是检查有没有服务处于失败状态。一个服务器上服务数量一旦多了,重启之后总有几个因为依赖顺序、端口被占、权限问题起不来。systemctl --failed 能一眼列出来。
如果某个服务不希望被手动或自动启动,可以 disable 掉,但更暴力一点的 mask 需要谨慎用。执行了 mask 之后,这项服务在这个系统上基本等于被屏蔽了,手工 start 也未必能拉起来;想恢复又得 unmask。除非你很确定某项服务永远不需要,否则不要用 mask。
5.4 查看启动耗时和依赖顺序
系统重启很慢,也是常见的性能问题。优化之前先用:
bash复制systemd-analyze
systemd-analyze blame
blame 会按启动耗时从高到低列出单元,方便找出最耗时的服务。经常能看到一些不需要开机自启的 MySQL 备份脚本或者监控 agent 被配置成开机等待网络就绪,白白拖慢了几十秒。确认非必要之后,按上面说的 disable 处理即可。
6. 用户与权限命令别瞎敲:创建、密码策略、sudo 授权一次配好
用户管理是运维里绕不开的活儿。新同事入职要建账号,外包人员离职要删权限,定时任务要指定运行用户,日志文件要让多个服务正常读写。这些工作中出现最多的坑,集中在 useradd 参数遗漏、usermod 少了 -a、sudo 授权文件写错三处。
6.1 useradd 和 adduser 的差别
不同发行版对 adduser 的处理不一样。Debian/Ubuntu 系列里,adduser 是交互式脚本,会一步步问你密码、用户信息等;RHEL/CentOS 系列里 adduser 可能只是 useradd 的软链接,不交互、不创建家目录。所以跨平台操作时,我习惯直接用底层的 useradd,参数明确控制所有行为。
一个比较完整的创建运维账号的命令:
bash复制useradd -m -d /home/zhangsan -s /bin/bash -c "ops user" zhangsan
-m 表示创建家目录,-d 指定家目录路径,-s 指定登录 shell,-c 添加账号备注。如果不带 -m,很多新用户会面临没有家目录的尴尬,登录后甚至进不了自己的目录,写不了个人配置文件。
用户建完需要设置密码:
bash复制passwd zhangsan
如果要求首次登录强制改密码,用 chage:
bash复制chage -d 0 zhangsan
-d 0 意思是将密码最后修改日期归零,用户下次登录时系统会强制要求修改密码。这个技巧在给外部人员开临时账号时很好用,能避免他们沿用初始密码太久。
6.2 用户组和 sudo 授权
创建用户时如果已经知道归属,可以直接设置附属组:
bash复制useradd -m -G nginx,ops -s /bin/bash zhangsan
已经建好的用户,想追加到新的组里,必须记住 -a 参数:
bash复制usermod -aG nginx zhangsan
-aG 表示追加到附加组。如果不小心写成 -G nginx zhangsan,用户就会被从原本的附加组中移除,原有的权限会莫名其妙消失。这个问题我在不少机器上见过,排查起来要花不少时间,关键是有可能当时不会立刻暴露,直到用户说要访问某目录时才发现权限不对。
想要给某个用户授予 sudo 权限,比较推荐的做法是在独立文件里配置,而不是直接改 /etc/sudoers:
bash复制visudo -f /etc/sudoers.d/ops-users
文件内容可以这样写:
text复制zhangsan ALL=(ALL) NOPASSWD:ALL
%ops ALL=(ALL) NOPASSWD:ALL
第二行的 %ops 表示整个 ops 组的成员都可以执行 sudo 且不用输密码。对临时账号,我通常不给 NOPASSWD,反而会特意去掉这个参数,让每次 sudo 都要验证密码,多少能起到一点提示作用。
如果想要更细的授权,可以只允许执行特定命令:
text复制zhangsan ALL=(ALL) /usr/bin/systemctl restart nginx, /usr/bin/tail
逗号分隔命令列表。这样用户能做的事被限制在一定范围内,就算账号泄露,破坏半径也小得多。
6.3 账号删除和权限清理
删除用户前先想清楚,是不是只要移除组权限就行,不需要删除账号本体。如果确实要删:
bash复制userdel -r zhangsan
-r 会同时删除家目录和邮件池。使用前最好是先确认没有重要数据,确认方式可以先用 tar 打包家目录里的业务文件,再执行删除。
权限层面常见的命令也值得顺手复习。文件权限列在 ls -l 里是 10 个字符,第一位是类型,后面九位分成三组:属主、属组、其他用户。比如:
bash复制chown root:nginx /data/logs/app.log
chmod 750 /data/logs
chmod 750 表示属主有完整权限、属组有读和执行权限、其他用户完全没有权限。修改权限时一个常见的误区是给得过宽,比如图省事直接 chmod -R 777 /data。这样确实能解决眼前的访问问题,但等于是给服务器留了一个隐患。正常思路是尽量把属主和属组配正确,再按实际需要收紧权限位。
7. 命令并不是越多越好,把场景拆开才能用得准
我不太建议去背那种几百条命令的清单,因为多数命令你根本用不上,真正到了故障场景,反而不容易想起来。Linux 系统运维中有价值的东西,是你知道当前处于什么阶段,这个阶段有哪些工具能帮你收集信息,以及收集到的信息该怎么解读。
比如这一篇里我选了进程、磁盘、网络、日志、systemd、用户权限这六类场景,其实是有原因的。进程和磁盘对应的是“资源型故障”,网络和日志对应的是“链路型故障”,systemd 和用户权限对应的是“配置型故障”。每类故障的排查节奏并不相同。资源型要看指标和趋势,链路型要看端到端可达性,配置型要看系统和实际运行状态的差异。
最后再分享一点实际经验:不要在生产环境试新命令。哪怕你觉得某个命令非常安全,也尽量先到测试容器里跑一下,确认输出格式和系统版本匹配。很多命令在不同发行版之间参数差异明显,比如 ss 和 netstat、adduser 和 useradd、systemctl 的单元文件路径,版本不同行为就可能不同。你要有一套自己反复验证过的命令集,形成肌肉记忆,再遇到突发故障就不容易被各种带偏。
