上午刚处理完一个线上告警,回工位路上我脑子里一直转着一句话:不少同事问我"Linux 命令到底怎么记",其实真正该问的是"遇到这个业务场景,我该从哪条命令下手"。命令背得再多,不会串成排查链路,到了服务器上照样抓瞎。这篇不打算做命令大全式的罗列,而是把我在业务服务器上高频用到的 Linux 命令,按真实故障和日常运维场景重新捋一遍。无论你是刚接手服务器的后端开发,还是长期跟 Linux 打交道的运维工程师,甚至只是自己买了台云主机在折腾个人项目,这套思路应该都能直接抄作业。
1. 一切从"卡"开始:CPU和内存该看哪些指标才算看对
业务反馈最多的第一句话通常是:"服务器卡了""接口好慢""机器是不是又出问题了"。这时候别急着重启,也别一上来就盯着某个命令的输出当圣旨。先把问题范围缩小到 CPU、内存、磁盘 IO 这三类,再决定下一步怎么查。
1.1 uptime和load average的真正读法
登录服务器后我会先敲 uptime:
bash复制uptime
# 输出示例
10:22:31 up 68 days, 4:12, 3 users, load average: 8.61, 6.32, 4.05
load average 后面三个数分别代表过去 1 分钟、5 分钟、15 分钟的平均负载。很多新手一听"平均负载高就是 CPU 满",这个判断不够准确。负载本质上是处于可运行状态和不可中断睡眠状态的进程平均数,它既包含正在 CPU 上跑的进程,也包含等待 IO 的进程。
所以看到 8.61 这个数字,第一反应应该是:机器逻辑核数是多少?可以用 nproc 查。如果是 8 核机器,负载 8.61 意味着队列已经略超核数,需要警惕;如果是 32 核机器,负载 8 其实相当闲。同一个数字在不同机器上含义完全不同,这也是我为什么反对把负载阈值写死在监控脚本里。
另外,如果 1 分钟负载很高但 15 分钟很低,说明是刚刚发生的突发状况,很可能是某个定时任务或者大批量请求突然进来;反之如果三个数都高,说明问题已经持续一段时间了,不是临时抖动。
1.2 vmstat按秒看趋势,而不是看一张截图
uptime 只能告诉我"现在很忙",但要判断忙在 CPU 还是忙在磁盘 IO,下一步我会执行:
bash复制vmstat 1 5
输出大致长这样:
text复制procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa
6 0 0 1856233 38012 4192820 0 0 112 65 1582 3219 63 12 22 3
7 0 0 1845102 38012 4193100 0 0 1210 32 2136 4012 78 9 12 1
逐列看关键项:
r:运行队列中的进程数。如果长期大于 CPU 核数,CPU 大概率不够用。b:不可中断睡眠的进程数。这里通常是被磁盘 IO 卡住,如果 b 经常大于 0,说明磁盘响应很慢。us/sy:用户态和内核态 CPU 占用。sy偏高时,要考虑是不是系统调用太频繁,比如大量网络小包、频繁上下文切换。wa:CPU 等待 IO 完成的时间占比。wa 高时,CPU 在干等磁盘,这时候加 CPU 没用,得查磁盘。si/so:换入换出内存。这两个长期非零,说明内存严重不足,系统在频繁用磁盘充当内存。cs:上下文切换次数。如果 cs 非常高,可能是线程数量过多或者锁竞争严重。
vmstat 一定要看一段时间内的趋势,比如 vmstat 1 10,隔 1 秒打一次打 10 次。只看一次输出经常会把瞬时抖动当故障,等到下一次再抓可能已经恢复了。如果 wa 长期高于 20%,再配合 iostat -x 1 5 去看具体磁盘的 %util 和 await,判断是磁盘硬件性能到瓶颈,还是某个进程在乱写日志。iostat 属于 sysstat 包,有些精简系统没有装,可以先用 yum install sysstat 或 apt install sysstat 补上。
1.3 top与pidstat:从进程到线程定位消耗源
vmstat 定位了大方向之后,要找出具体是哪个进程在消耗资源,就用 top:
bash复制top
进去后按 P 按 CPU 排序,按 M 按内存排序,按 E 切换内存显示单位。很多人习惯打开 top 截个图就完事,其实 top 更有用的动作是按 1 展开每个 CPU 核的使用率,看看是不是只有某一个核被打满,其他核都在闲着——这种情况常见于单线程应用,比如部分旧版 Redis 或 Node.js 单线程模型,加机器不一定解决,要看应用本身能不能多线程。
更精确的做法是用 pidstat 按进程和线程拆分:
bash复制pidstat -u -p 1234 1 5
pidstat -t -p 1234 1 5
-t 能看到进程内部各线程的 CPU 占用。比如 Java 应用某个线程 CPU 飙高,拿到线程 PID 后 printf '%x\n' 线程PID 转成十六进制,再对到 jstack 输出里的 nid=0x...,就能直接定位到代码里的哪一段线程逻辑在空转。这个套路我在排查线上"GC 线程狂转"和"业务线程死循环"时用了很多次,比瞎猜日志高效得多。
1.4 free和/proc/meminfo:别把cache误判成内存泄漏
内存排查最容易踩的坑就是看 free -m 的 used 列很高就以为内存不足。
bash复制free -h
关键要看 available 而不是 used。Linux 会把空闲内存尽量用作 page cache,用于缓存磁盘文件,这部分内存在应用需要时是可以回收的,所以 used 里包含了大量 cache 并不代表真的不够用。可用内存的准确口径是 available 这一列,它已经扣除了系统估计不可回收的部分。
如果实在想知道某个应用占了多少内存,不要只看 ps aux 里的 RSS,因为 RSS 包含了共享库重复计算的量。更接近实际的是:
bash复制ps -eo pid,rss,vsz,cmd --sort=-rss | head -20
RSS 单位是 KB,要读成 GB 就除以 1024 再除以 1024。如果出现进程无故消失或者被杀,去查内核日志里有没有 OOM 记录:
bash复制dmesg -T | grep -i out.of.memory
grep -i oom /var/log/messages
一旦确认是 OOM,单纯加大内存只是缓解,建议先查清楚是哪个进程触发,再看是不是该调 JVM 堆参数、限制单个进程内存,或者给系统配置 swap 空间。swap 能兜底但别指望它能救命,频繁 swap 比内存不足更伤性能。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 磁盘告警从df开始,但不能从df结束
磁盘告警应该是最常见的监控项了。相比 CPU 和内存,磁盘问题的排查链路更长,因为根因往往不在"当前目录占了多少",而在一些看不见的地方。
2.1 df与du对不上账是正常现象
收到磁盘空间告警后,常规操作是:
bash复制df -hT
-T 会显示文件系统类型,这个信息很有用。比如看到 /dev/vda1 是 ext4,而 /data 是 xfs,两边容量配额完全独立。有时候是某个挂载点目录下的文件把空间写满,但另一个挂载点还很空,这时候盲目删文件可能删错位置。
接下来要定位是哪个目录占用大,通常用 du:
bash复制du -h --max-depth=1 -x /data | sort -rh | head -20
重点说下 -x 参数:它让 du 不要跨越文件系统边界。如果没有 -x,/data 下面如果还有别的挂载点,du 会进入那个挂载点重新统计一遍,导致数据重复计算,看起来目录占用很夸张,但删文件又不释放。这个参数在实际生产环境里非常重要。
df 和 du 数字对不上也时有发生。df 统计的是文件系统层面已分配的块,du 是按目录遍历统计文件大小。如果某个文件被删除了,但还有进程持有它的文件描述符,文件系统会认为块仍然被占用,df 不会释放,但 du 已经看不到这个文件了。这就是下一节要单独说的情况。
2.2 找出大文件与坏目录结构
磁盘占用率下不来,先按目录层次逐层定位:
bash复制cd /var && du -h -d 1 /var 2>/dev/null | sort -rh | head
如果发现某个目录下面小文件很多,比如日志目录、临时目录、消息队列积压目录,直接用 du 可能要跑很久。这时可以试试用 find 按文件大小扫一遍,定位占用空间最大的那批文件:
bash复制find /data -xdev -type f -size +500M -exec ls -lh {} \; 2>/dev/null | sort -k5 -rh | head -30
这句的意思是查找 /data 下大于 500M 的普通文件,列出详细信息后按大小排序取前 30。实际定位时未必真有大文件,也可能是有几十万个碎文件。文件数量多到 inode 耗尽同样会导致磁盘写不进去,这个后面说。
2.3 空间没释放可能是"已删除文件"被进程占住
有一种特别迷惑人的情况:业务那边说已经删了日志,但 df -h 空间还是满的,甚至一点都没降。这种情况下,八成是某个进程还在持续写一个已经被删除的文件。
排查命令用 lsof:
bash复制lsof | grep deleted
输出里如果能看到类似 java 1234 root 35w REG 253,1 8589934592 524289 /data/log/app.log (deleted) 的记录,说明该文件已被 unlink,但 Java 进程的文件描述符还指着它。磁盘上的 inode 和 block 都没有真正释放,只有这个进程把文件描述符关掉才能释放空间。
如果确认这个日志文件已经不需要了,最直接的办法是重启相关服务,让进程重新打开新日志文件。如果暂时不能重启,也别用 echo > 去抢占那个 deleted 文件的路径,那不是同一个文件。还有人说可以用 /proc/<PID>/fd/<FD> 查看内容,但空间不会因此释放。这个经验我踩过:当时明明删了 30G 的旧日志,df 却纹丝不动,最后 lsof 一查,原来 nginx 的 error.log 还在被 worker 进程持有。
2.4 inode数量耗尽的隐藏故障
除了块空间满,还有一种"No space left on device"但 df -h 显示还有几十 G 的情况,这时要执行:
bash复制df -i
看 IUsed 和 IUse%。inode 是文件系统用来记录文件元数据的索引节点,每个文件或目录至少要占一个。如果某个目录下堆积了大量小文件,比如把每条消息写成一个独立文件的程序,几百万个文件很快就能把 inode 耗尽,磁盘上明明有空间,却什么文件都建不了。
遇到 inode 满的情况,先找到小文件最多的目录:
bash复制find /data -xdev -type f | wc -l
for d in /data/*/; do echo "$d $(find "$d" -xdev -type f | wc -l)"; done | sort -k2 -rn | head
如果确认都是可以清理的历史文件,再批量删除。删除海量小文件时,一条 rm -rf 可能要跑很久,可以先 find /data/bigdir -type f -name '*.tmp' -delete 分批删,或者用 rsync --delete 配合空目录做快速清空。操作前务必要先确认文件确实没用,这一条再怎么强调都不过分。
3. 端口被占、进程拉不起:最常踩的实际服务问题
业务上出问题,除了机器资源不足,更常见的是进程起不来、端口冲突、后台任务掉了。这些场景不涉及什么高深原理,但命令不对会绕很多弯路。
3.1 检查端口的命令对比:ss替代netstat
传统习惯是 netstat -lntp,但现在很多新系统默认不装 netstat,而且 netstat 在处理大量连接时明显偏慢。推荐直接用 ss:
bash复制ss -lntp
-l 表示只查监听端口,-n 不做域名反解,-t 只看 TCP,-p 显示对应进程。如果当前用户权限不够,可能看不到进程名和 PID,需要加上 sudo:
bash复制sudo ss -lntp | grep ':8080'
grep 到了 8080 端口,输出会有如下关键列:
text复制LISTEN 0 511 0.0.0.0:8080 0.0.0.0:* users:(("java",pid=3487,fd=114))
这就直接告诉我们 PID 是 3487,应用是 java。如果想看某个端口当前处于各种状态的所有连接,比如排查短连接风暴:
bash复制ss -ant | grep ':8080' | awk '{print $1}' | sort | uniq -c
这个命令能把连接状态按数量统计出来:哪些是 ESTAB、哪些是 TIME_WAIT、哪些是 SYN_SENT。如果大量 SYN_SENT 说明目标地址不可达或防火墙丢弃;如果大量 TIME_WAIT,说明服务主动关闭了大量连接,先排查应用侧连接池是否频繁创建和关闭。TIME_WAIT 本身不是故障,但数量大到一定级别会耗尽端口资源,这时要配合内核参数和应用连接池设置一起调优,只调 sysctl 不做应用侧改造,效果有限。
3.2 通过PID反向找服务与父进程
很多时候反过来:知道一个进程异常,但不知道它是干什么的。先看 PID:
bash复制ps -fp 3487
如果想看进程的启动命令、工作目录和父进程,直观的方法是看 /proc:
bash复制cat /proc/3487/cmdline | tr '\0' ' '
readlink /proc/3487/cwd
cat /proc/3487/status | grep -E '^(Name|PPid|State|Threads)'
查看进程树用 pstree 更清晰:
bash复制pstree -aup 3487
这样能看到 3487 是谁拉起来的,最终父进程是不是 systemd。这种父子关系在排查"为什么服务重启没生效"时特别有用。比如我遇到过:明明改完配置重启了 java 进程,ps 一看还是旧启动时间,再往上看父进程,发现有一个 shell 脚本循环拉起程序,重启只是杀了子进程,外层脚本立刻又拉起了一个,配置文件根本没加载新的。
3.3 nohup与setsid:怎么让任务在退出终端后继续跑
在日常业务中,经常需要临时跑一个长任务,比如在服务器上导出大批量数据、跑一个耗时脚本。直接执行,终端一关任务就没了,因为 SIGHUP 信号会发给终端关联的进程。最常用的处理是:
bash复制nohup ./export_data.sh > export.log 2>&1 &
要拆开理解:nohup 让进程忽略 SIGHUP,& 放到后台执行,> export.log 把标准输出写到文件,2>&1 把标准错误也重定向到同一个文件。很多人写 nohup ./export >> log & 但是忘了 2>&1,结果程序跑完才发现报错信息全丢在终端里,根本无法排查。
如果希望任务完全脱离当前会话,成为独立进程,更干净的方案是用 setsid:
bash复制setsid ./export_data.sh < /dev/null > export.log 2>&1 &
setsid 会让新进程成为新的会话首进程,不随终端退出而收到 SIGHUP,比 nohup 多了一层会话分离。不过对绝大多数场景而言,nohup 够用了,真正需要长期稳定运行的任务,我更建议直接写 systemd service 或放到 tmux/screen 里管理,单纯裸跑 nohup 进程的生命周期不好控制,后面收割也没那么方便。
3.4 僵尸进程是怎么产生,该怎么处理
ss -lntp 能看到年轻进程,但有一种进程几乎不占资源,却会在 ps 里堆积很多——僵尸进程。判断方法:
bash复制ps -eo pid,ppid,stat,cmd | awk '$3 ~ /^Z/'
STAT 状态是 Z 就说明该进程已经退出,但父进程还没调用 wait 回收它的退出状态,所以内核保留了最少量的进程信息等人来收尸。僵尸进程杀不掉,因为进程本体已经死了;kill -9 对僵尸无效。正确处理是找到它的父进程,然后让父进程结束或重启:
bash复制ps -o ppid= -p 僵尸PID
比如父进程 PID 是 1234,看它是什么:
bash复制ps -fp 1234
如果是一个长期运行的业务进程,把父进程重启,僵尸就能被系统 reaper 接管并清除。如果是某个脚本 fork 了子进程但没 wait,那要修代码。僵尸进程不像 CPU 占满那样症状剧烈,但数量多了会占用 PID 空间,极端情况下会导致系统无法创建新进程,处理方式主要靠定位父进程。
4. 排查业务日志:把grep、awk、journalctl串成一条流水线
服务器资源、进程状态都查过没问题,接下来基本就要进日志排查了。日志排查的核心不是背命令,而是先确定时间范围、关键词、上下文三个维度,然后再把统计命令用起来。
4.1 grep不只是找关键字,上下文才是关键
最原始的做法是 grep 'ERROR' app.log。如果日志文件几 GB,直接全量 grep 会等半天。正确姿势是先截取时间范围:
bash复制tail -n 50000 app.log > /tmp/recent.log
grep -E 'ERROR|Exception' /tmp/recent.log
或者直接只看某个时间段,比如下午 2 点到 2 点 10 分的日志:
bash复制sed -n '/2025-06-01 14:00:00/,/2025-06-01 14:10:00/p' app.log
在日志里找到一条报错后,不要只看这一行。异常往往有上下文,具体报错前后 10 行对判断根因非常重要:
bash复制grep -n -B 10 -A 30 'NullPointerException' app.log
-B 10 表示前 10 行,-A 30 表示后 30 行。如果业务日志里打了 traceId、requestId 这类链路标识,还可以直接把它作为关键词把整条请求链路拉出来:
bash复制grep 'traceId=abc123xyz' app.log
这是我最推荐大家养成的习惯:打日志时尽量带 traceId,排查时按 traceId 拉全文,比按时间瞎猜准确得多。
4.2 用时间线、正则表达式锁定问题区间
如果要排查的是"从几点开始报错变多",光靠人工翻日志效率太低。可以先按小时做统计:
bash复制grep 'ERROR' app.log | awk '{print $1, $2}' | cut -c1-13 | uniq -c
这里假设日志第一列是日期、第二列是时间。cut -c1-13 截取到分钟级别,类似 "2025-06-01 14"。如果发现某分钟 ERROR 突然暴增,马上再配合 4.1 的方法对该时间段做细查。需要留意的次数差异——同一时刻爆发的大量错误常有共同前缀,可以再提取错误分类:
bash复制grep 'ERROR' app.log | sed -E 's/.*\] (.*):.*/\1/' | sort | uniq -c | sort -rn | head -20
把 Error 后面的异常类名或错误码前几个字段归并,按数量排序,就能看出究竟是"数据库连不上"还是"空指针"还是"第三方接口超时"占主导。实际使用中 sed 正则要根据日志格式微调,这个方法的价值在于从一堆错误里先找出最大头,而不是一头扎进某一条细节。
4.3 awk+sort+uniq做Top统计
除了排错,平时做业务分析也经常用到组合命令。比如 Nginx 或网关的 access.log,想统计请求量最高的前 10 个接口:
bash复制awk '{print $7}' access.log | sort | uniq -c | sort -rn | head -10
$7 是 access log 里的请求路径字段,不同日志格式字段位置会不一样,如果自己不确定,可以先 awk '{print NR, $0}' access.log | head -3 数一数。想统计响应码分布:
bash复制awk '{print $9}' access.log | sort | uniq -c | sort -rn
想统计来源 IP 排行:
bash复制awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -20
这套组合本质上就三步:awk 提取目标列 -> sort 排序 -> uniq -c 去重计数 -> sort -rn 按次数倒序。只要换了提取字段,就能覆盖很多统计需求。遇到日志量特别大时,可以直接 grep 过滤一段时间内的行再统计,避免把全量日志都加载进去。
4.4 journalctl看systemd服务日志,避免只盯文件
现在不少服务直接由 systemd 托管,如果应用把日志写到 stdout,不落盘,用 journalctl 查最方便:
bash复制journalctl -u myapp.service --since "10 minutes ago"
更精细的按时间段:
bash复制journalctl -u myapp.service --since "2025-06-01 13:00:00" --until "2025-06-01 13:30:00" -p err
-p err 只输出严重级别为 error 的日志。有些进程崩溃后自动重启,想确认是不是不断重启,用:
bash复制systemctl status myapp.service
这个命令能看到进程当前状态、最近启动时间、主进程 PID,以及是否反复重启。如果要追看实时输出:
bash复制journalctl -u myapp.service -f
journalctl 默认会把日志写到 /var/log/journal,如果系统没有持久化目录,重启后再查旧的 journal 可能查不到。要长期留存日志还是建议配置 logrotate 把应用日志落盘,journalctl 更适合做短时追查和 systemd 服务本身的启停诊断。第 2 节里说的日志占满磁盘,也别忘了检查 journal 日志:
bash复制journalctl --disk-usage
如果 journal 占用过大,可以按策略清理旧的:journalctl --vacuum-time=7d 清理 7 天以前的。
5. 网络超时与连不上:用命令分层定位故障
每次排查"服务间调不通""数据库连不上",我一般按 DNS 解析、TCP 连通性、应用层响应三个层面逐步做排除。这样做的好处是三层里先在某一层找到结论,不用反复试。
5.1 先分清是DNS、TCP还是应用层
接到"某个第三方接口超时"的反馈,第一件事往往是解析域名看 IP 还在不在:
bash复制dig +short api.example.com
如果没有 dig,可以用 nslookup api.example.com。如果解析出来空结果或解析超时,问题大概率在 DNS,而不是服务本身。DNS 不通时先看 /etc/resolv.conf 里配置的 nameserver 是不是可达,自己的云主机是不是被 DHCP 覆盖了配置。
确认 IP 能解析后,测试 TCP 连通性。很多人习惯 telnet,但 telnet 未必装了,而且非交互式判断不够直观。可以用 curl 直接测端口:
bash复制timeout 3 curl -v telnet://1.2.3.4:3306
如果端口通,输出会显示 Connected;如果不通会卡住直到 timeout。更轻量的办法是直接用 /dev/tcp:
bash复制timeout 3 bash -c 'echo > /dev/tcp/1.2.3.4/3306' && echo "port open" || echo "port closed or timeout"
这句的原理是 shell 访问 /dev/tcp 会创建 TCP 连接,连接成功则输出 open,失败或超时则走 ||。做端口连通性检查时,我建议一定加上 timeout,不然遇到黑洞路由时命令会一直挂在那里。
5.2 端口与连接状态检查要会读TIME_WAIT
如果 TCP 能连上,但应用请求超时,下一步要看得更细。跑一下:
bash复制ss -tnp state established '( dport = :3306 or sport = :3306 )'
看本机到数据库 3306 的连接是不是处于正常 ESTAB 状态。如果连接刚建立就开始抖动,可能是连接数满或认证失败;如果在客户端能看到大量 SYN_SENT,说明 SYN 包发出去了但没有响应,此时关注目标服务器防火墙或负载均衡。
连接状态里 TIME_WAIT 是最常被误解的。它是主动关闭连接的一方停留在内核里的状态,目的是让延迟到达的报文可以安全丢弃。主动断开的一方产生 TIME_WAIT,所以要看连接是谁先关的。Nginx 作为反向代理时,后端连接大量处于 TIME_WAIT 其实是正常现象,只要数量没有导致端口耗尽或新连接失败,通常不用惊慌。
统计各状态数量:
bash复制ss -ant | awk 'NR>1 {print $1}' | sort | uniq -c | sort -rn
如果发现某个服务的连接数高达几万,先想想是不是连接池没复用;如果发现 CLOSE_WAIT 非常多,那是被动关闭方没有 close socket,代码层面有泄漏,重启服务只能临时缓解。
5.3 curl细化耗时项,确认瓶颈在哪个环节
测 HTTP 接口时不要只看"通了"或"通了但慢"。用 curl 拆解时间:
bash复制curl -o /dev/null -s -w "DNS解析: %{time_namelookup}s\nTCP建连: %{time_connect}s\nTLS握手: %{time_appconnect}s\n首字节: %{time_starttransfer}s\n总耗时: %{time_total}s\n" https://api.example.com/v1/health
输出例子:
text复制DNS解析: 0.012s
TCP建连: 0.008s
TLS握手: 0.042s
首字节: 0.620s
总耗时: 0.622s
如果总耗时长但 TCP 建连和 TLS 都很快,瓶颈在服务端业务处理;如果 TCP 建连就花了几秒,问题在网络链路或目标服务器 accept 队列满;如果 DNS解析慢,考虑换公共 DNS 或做本地 hosts 缓存。这种方法比自己猜"到底是网络慢还是程序慢"要客观得多。
再配合 iperf 或 mtr,基本能把网络层定位清楚。但业务服务器上不一定方便装 iperf,所以最常用的是 mtr:
bash复制mtr -rwc 20 1.2.3.4
mtr 能显示从当前机器到目标地址的每一跳丢包率和延迟,一旦发现某个中间节点丢包严重,就能大致猜出故障在运营商链路还是目标机房的入口。
5.4 路由与链路排查:走不通时怎么用mtr
本机去某个内网地址不通,先查路由是否正常:
bash复制ip route show
需要看具体目的地址走哪条路由:
bash复制ip route get 10.20.30.40
这条命令输出里会写明源地址、下一跳和网卡。内网服务互相调不通,很多情况是路由表被云平台的策略路由改了,或者本机开了多个网卡导致默认路由不对。ip route get 能很直观地告诉我们实际发包走的路径。
如果路由正常但 ping 不通,要看是整段链路都不通,还是只有某些协议被防火墙拦。有些安全组策略只放行特定端口,ICMP 直接被丢弃,这时候 ping 不通不代表 TCP 连不上,所以要回到 5.1 的端口检查方法。反过来,ping 通了也不代表业务端口通,很多故障就藏在"服务器能 ping 通但应用连不上"的中间地带。所以我的习惯是把 ping、端口、业务请求三层分开验证,不靠单一命令下结论。
6. 业务账号与最小权限:授权不是只用chmod 777
很多时候不是没有故障,而是权限太随意,给后续排障留下大坑。比如多个人共用 root,出了问题连审计都无从谈起。业务服务器上应当坚持"专人专号、最小权限"。
6.1 新建用户、加入组以及登录痕迹检查
新员工入职或者新服务上线,我会先建独立账号:
bash复制useradd -m -s /bin/bash deploy
passwd deploy
如果只是让某个账号能切换身份而不想暴露密码,也可以把公钥放到对应用户的 ~/.ssh/authorized_keys,然后关闭密码登录。这个操作适合批量给机器授权。查看一个用户属于哪些组:
bash复制id deploy
groups deploy
需要把用户加入 docker 组或者其他业务组,用 usermod:
bash复制usermod -aG docker deploy
-aG 表示追加到组,千万不要漏了 -a,否则会把用户从原来所有附属组里移除,直接导致权限变化。这个坑我亲眼见过:有人想把用户加到 docker 组,结果写成了 usermod -G docker user,用户被踢出了 sudo 组,管理后台立刻登不进去了。
排查问题时要确认某台机器有哪些人登录过:
bash复制last -20
如果想看登录失败记录,有些发行版在 /var/log/secure,有些在 /var/log/auth.log:
bash复制grep "Failed password" /var/log/secure | tail -20
我现在判断账号是否安全,都会先看这些日志。公网服务器只要开了 SSH,几乎每天都会被人扫,如果发现大量暴力尝试,就应该考虑改用密钥登录并限制 root 直接登录。
6.2 sudo配置务必用visudo编辑
出于安全考虑,线上服务不应随便给普通开发账号 root 权限。但某些业务又需要执行指定命令,这时在 /etc/sudoers.d/ 下建独立文件更好管理:
bash复制visudo -f /etc/sudoers.d/deploy
写入类似内容:
text复制deploy ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart myapp
deploy ALL=(ALL) NOPASSWD: /usr/bin/tail -f /var/log/myapp/*
意思是 deploy 用户不需要输入密码就能执行 systemctl restart myapp 和查看指定日志的命令,但无法执行其他 root 命令。配置完成后用 sudo -l 验证生效:
bash复制sudo -l -U deploy
这样既保证了业务能自行重启服务,又限制了高危操作范围。建议不要直接给普通用户 ALL=(ALL) ALL,更不要让所有人都知道 root 密码。root 账号应该只保留给极少数核心负责人,并且建议禁止 SSH 直接以 root 登录,日常用普通账号加 sudo 提权。
6.3 权限、属主和ACL
Linux 权限模型分属主、属组、其他。查看目录权限:
bash复制ls -ld /data/app/logs
输出 drwxr-x--- 之类的字段,第 1 位是文件类型,接下来 9 位每三位分别表示属主、属组、其他的读/写/执行权限。如果发现某个线上目录是 drwxrwxrwx,基本就是有人手滑执行了 chmod 777。这样所有系统账号都能读改该目录下内容,一旦目录里是业务配置或脚本,风险就很大。
真正需要多账号共享目录写入权限时,推荐用 ACL,而不是简单粗暴地 777。比如要让 deploy 用户对 logs 目录有读写执行权限,但不想改目录属主:
bash复制setfacl -m u:deploy:rwx /data/app/logs
getfacl /data/app/logs
ACL 可以在保留传统属主的同时给指定用户或组授权,比 chmod 精确得多。日志服务那个案例尤其典型:某个 Web 目录需要让 nginx 进程读取,让 deploy 用户写入,让运维组审核,用三个 chmod 很难表达,但用两条 ACL 规则就能实现。
还要留意目录是否带特殊权限位。比如 /tmp 一般是 drwxrwxrwt,权限末尾的 t 是 sticky bit,表示在 /tmp 目录里,即使某个文件的写权限对所有人开放,也只有文件属主或 root 能删除它。如果线上目录的 t 位丢了,任何人都能删除他人文件,同样是个隐患。排查时多用 ls -l 看看权限位,不要习惯性 chmod 777。
6.4 定时任务用crontab
业务服务器上的数据清理、日志压缩、健康检查,常用 crontab 来做。查看当前用户的定时任务:
bash复制crontab -l
编辑某个用户的定时任务:
bash复制crontab -e
注意千万别直接用编辑器打开 /var/spool/cron/root 去改,正规姿势一定是 crontab 命令。如果希望让特定用户跑某个脚本,可以先编辑 /etc/crontab 或放到 /etc/cron.d/ 下,并在里面指定执行用户。例如每天凌晨 3 点由 deploy 用户执行清理脚本:
bash复制echo "0 3 * * * deploy /data/scripts/cleanup.sh >/dev/null 2>&1" > /etc/cron.d/cleanup
定时任务执行失败时,日志可查 /var/log/cron。如果脚本里写了相对路径,或者依赖了某个未加载的环境变量,即使 crontab 看起来正确也可能不执行。我一般会在脚本开头加 export PATH=/usr/local/bin:/usr/bin:/bin,执行命令都写绝对路径,这个习惯让我少踩很多坑。
7. 容器服务场景下的常用操作和排障
如果业务已经容器化,日常操作会从 systemctl 变成 docker 或 kubectl 居多。命令虽然变了,排查思路仍然是"看状态、看日志、看资源、看网络"这四个维度。
7.1 Docker资源受限、重启恢复
容器里服务挂了,第一件事看全部容器状态:
bash复制docker ps -a
ps 默认只看运行中的容器,加 -a 才能看到退出的容器。输出里的 STATUS 列如果显示 Up 2 seconds 或 Restarting,说明容器在不停重启。查容器重启次数和退出码:
bash复制docker inspect -f '{{.Name}} {{.RestartCount}} {{.State.Status}}' myapp
再看启动日志:
bash复制docker logs --tail 200 myapp
docker logs 看到的是容器内 stdout 和 stderr 输出,如果应用日志是写到文件而非标准输出,就需要进容器或挂载目录去查:
bash复制docker exec -it myapp sh
容器内不一定有 bash,很多精简镜像只有 sh,所以 exec 统一用 sh 更稳。如果连 sh 都没有,可以用 nsenter 方式进入容器进程的命名空间,或临时换一个带工具的镜像打包进去查找,但这类操作要求宿主机有较高权限,日常排障尽量先用 docker logs。
查看容器资源占用:
bash复制docker stats --no-stream
docker stats 的数据能反映容器当前 CPU 和内存用量。如果容器内存持续涨到 limit 甚至被 OOM 杀掉,要去查应用本身是否存在内存泄漏,而不是一昧增加 docker run 的 -m 限制。容器的重启策略用 --restart 控制,常见值有 no、on-failure、always、unless-stopped。业务服务一般建议 always 或
