每一个都是踩坑换来的
从"重启能解决99%的问题"到"根因分析才是运维的真正价值",这条路我走了5年。
为什么写这篇文章
招聘网站上,"中级Linux运维"的JD千篇一律:熟悉Linux系统、会写Shell脚本、懂网络、用过Docker。但这些描述就像在说"会呼吸"一样——门槛低得离谱。
真正的中级运维和初级运维之间的鸿沟,不在于你会用多少命令,而在于你面对一个线上故障时,脑子里有没有一套系统化的分析框架。
初级运维靠经验碎片解决问题:上次重启好了,这次也重启试试。中级运维靠方法论解决问题:先看什么、再看什么、每个现象背后的根因是什么。
这篇文章不讲命令大全,不讲面试题。讲的是7个真正拉开差距的能力——每一个,都是用线上事故换来的。
必杀技一:性能分析,你得会"读系统的心电图"
性能问题是最常见的线上故障。一个服务慢了,初级运维会说"加机器吧",中级运维会先用工具定位瓶颈到底在哪一层。
CPU分析:不是看负载高不高,而是看高在哪里
很多人打开 top 看到 load average 飙升就慌了。但 load 高不一定有问题——它只是表示"等待运行的进程多"。真正要看的是 CPU 各状态的分布:
# 查看CPU各状态占用,us/sy/wa/si各有含义top -bn1 | head -5# us = 用户态 CPU,跑业务逻辑# sy = 内核态 CPU,系统调用/上下文切换# wa = IO 等待,CPU在等磁盘# si = 软中断,网络包处理# 如果 wa 长期 > 20%,问题不在 CPU 而在磁盘# 如果 sy 占比异常高,可能在疯狂做上下文切换进阶工具是 perf。当一个进程 CPU 占用高但你完全看不出它在干嘛:
# 抓取进程的 CPU 采样数据perf record -p <PID> -g -- sleep 30 perf report# 能看到具体在哪个函数上耗时最多# 结合火焰图(Flame Graph)可视化分析内存分析:free 命令的 buffers/cache 你真的看懂了吗
free -h# 关注 available 列,而不是 free# buff/cache 被占满不一定是坏事# Linux 的设计哲学就是"闲着的内存就是浪费"# 它会尽量把空闲内存拿来做缓存,需要时自动释放真正危险的信号是 swap 的使用量和 si/so 的频繁交换:
vmstat 1 5# si(swap in)/ so(swap out)持续非零# = 内存真的不够了,系统在疯狂换页# 这才是该加内存的信号,而不是 free 少了就加磁盘IO:iostat 的 await 才是关键指标
iostat -x 1# %util 高不一定有问题(多队列设备下可以超过100%)# await 才是延迟指标,> 20ms 就要关注了# 如果 svctm 和 await 差距大,说明队列在排队📋 真实案例
某天数据库响应突然变慢,DBA 查了半天 SQL 执行计划没问题,索引也正常。最后用 iostat 一看,await 飙到 200ms——根因是同机器上一个日志清理脚本在用 dd 全盘扫描,把磁盘 IOPS 吃光了。
性能分析的关键不是记住多少命令,而是知道每个指标背后指向什么、异常值意味着什么。
必杀技二:网络排障,像侦探一样追踪每一个包
网络问题最考验逻辑思维。一个"接口超时"的工单,背后可能是 DNS、是防火墙、是 TCP 连接队列满了、是 MTU 不匹配、是路由环路。
排查链路要分层,不要乱试
# 第一层:连通性ping <IP># ping 不通?可能是 ICMP 被禁,不代表网络不通# 要继续往下验证# 第二层:端口连通nc -zv <IP><端口># 或 telnet <IP> <端口># 端口不通?检查防火墙和安全组# 第三层:路由追踪mtr -n <IP># mtr 比 traceroute 好,能持续探测# 能看到丢包是从哪一跳开始的抓包是终极武器
当上面三层都"看起来正常"但服务还是超时,就该上 tcpdump 了:
# 抓取指定端口的包,看 TCP 握手过程tcpdump -i eth0 -nn 'port 8080' -c 50# -nn 不解析 DNS 和端口名,速度快# 看到只有 SYN 没有 SYN-ACK = 服务端没收到或没响应# 看到 SYN-ACK 但客户端不回 ACK = 客户端问题# 看到 RST = 对方主动拒绝连接# 进阶:写入 pcap 文件用 Wireshark 分析tcpdump -i eth0 -w /tmp/capture.pcap 'host 10.0.0.1'连接队列积压是隐形杀手
# 查看 TCP 全连接和半连接队列状态ss -lnt# Recv-Q 非零 = 已完成握手但应用没 accept# Send-Q = 全连接队列最大长度# 如果 Recv-Q 持续接近 Send-Q = 队列快满了# 新连接会被内核丢弃,表现就是偶发超时📋 经典场景
服务偶尔超时,监控一切正常,CPU不高、内存够用。用 ss 一看,Recv-Q 周期性堆积——应用处理慢导致全连接队列满,新连接被内核直接丢。根因不在网络层,而在应用层的处理能力。
核心心法:网络排障要做到"分层排查、逐段验证、不要跳步"。跳了一步,你可能在错误的层面花几个小时。
必杀技三:Shell脚本,从"能跑就行"到"生产级"
初级运维写的脚本有个共同特征:没有错误处理,没有日志,变量不加引号,set -e 是什么不知道。这种脚本在线上跑,迟早出事。
生产级脚本的四个底线
#!/bin/bash# 第一行:明确解释器,不要用 sh# bash 和 sh 的行为不同,特别是在数组和管道处理上set -euo pipefail# -e:任何命令失败立即退出,不要继续往下跑# -u:使用未定义变量直接报错(这是救命的)# -o pipefail:管道中任何一环失败都算失败# 默认情况下管道只看最后一个命令的退出码变量永远加双引号,这是一个救命的习惯:
❌ 危险写法 rm -rf $path/ 如果 $path 为空,变成 rm -rf /灾难性后果 | ✅ 正确写法 rm -rf "$path/" 加了引号,即使为空也只会报错不会误删 |
日志是脚本的眼睛
# 函数化的日志输出log() { local level=$1; shiftlocal msg="$*"local timestamp=$(date +'%F %T') echo"[$timestamp] [$level] $msg" | tee -a /var/log/myscript.log } log INFO "开始处理数据"log WARN "内存使用率超过80%"log ERROR "文件不存在: $file_path"# 有时间、有级别、有落盘,出了问题能追溯并发处理提升效率
# 用 xargs 实现并行执行echo -e "host1\nhost2\nhost3\nhost4" | \ xargs -P 4 -I {} bash -c 'ssh {} "uptime"'# -P 4 表示4个并发进程# 比一个一个串行快4倍# 更可控的方案用 GNU parallel# 支持进度条、超时控制、结果收集📋 教训深刻的案例
有人写了个日志清理脚本,用 rm -rf $variable/*,变量在某些异常场景下为空,变成了 rm -rf /*。set -u 本可以在第一时间阻止这个灾难,但脚本里没有写。生产环境的脚本,每一个变量、每一个路径,都要当它可能为空来防御。
必杀技四:systemd,服务管理的"隐形操作系统"
很多人对 systemd 的认知停留在 systemctl start/stop/restart。但 systemd 是 Linux 的 init 系统,是 PID 1,理解它能解决很多"玄学问题"。
服务挂了为什么自动重启不了
# /etc/systemd/system/myservice.service[Service] ExecStart=/opt/app/start.sh Restart=on-failure RestartSec=5s# Restart 策略:no / on-failure / on-watchdog / always# 很多人没配 Restart,服务进程崩了就真崩了# 直到有人发现服务不可用才手动拉起# cgroup 级别的资源限制,比 ulimit 更可靠LimitNOFILE=65536 MemoryMax=2G CPUQuota=200%# OOM 时 systemd 能感知并重启查看服务为什么启动失败,journalctl 是你最好的朋友:
# 查看服务最近50条日志journalctl -u myservice -n 50 --no-pager# 精确查时间范围journalctl -u myservice --since "10 min ago"# 只看错误级别以上journalctl -u myservice -p err# 跟踪实时日志journalctl -u myservice -f用 systemd timer 替代 cron
# /etc/systemd/system/backup.timer[Unit] Description=Daily backup at 2AM [Timer] OnCalendar=*-*-* 02:00:00# 比 cron 的四大优势:# 1. 日志自动记录在 journal 里,不用自己写# 2. 支持 misfire 策略:错过的任务下次补跑# 3. 支持 OnUnitActiveSec 做相对时间定时# 4. 有依赖管理:可以等某服务启动后再执行[Install] WantedBy=timers.target📋 实际场景
某定时任务偶尔不执行,查了 cron 日志也看不出名堂——因为 cron 只记成功失败,不记详细输出。迁到 systemd timer 后,每次执行的退出码、耗时、stdout/stderr 全部记录在 journal 里,排查变得有据可查。
必杀技五:安全加固,防线要建在平时
安全不是出事了才补,而是架构设计时就要考虑。中级运维需要知道每一道防线在哪、能挡住什么攻击。
最小权限原则
# 不要用 root 跑一切服务# 创建专用用户,按需给 sudo 权限useradd -r -s /sbin/nologin appuser# sudo 权限要精细化,不要给 ALL# /etc/sudoers.d/appuserappuser ALL=(root) /bin/systemctl restart myservice appuser ALL=(root) /bin/journalctl -u myservice# 只给需要的命令,降低被提权后的影响面SSH 加固清单
# /etc/ssh/sshd_configPort 22022 # 改默认端口,挡住大量扫描脚本PermitRootLogin no # 禁止 root 直接登录PasswordAuthentication no # 强制使用密钥认证MaxAuthTries 3 # 限制认证尝试次数ClientAliveInterval 600 # 10分钟无操作断开ClientAliveCountMax 0 # 不允许重试# 重启生效systemctl restart sshd# 配合 fail2ban 自动封禁暴力破解# 对连续失败认证的 IP 自动加入防火墙黑名单文件完整性监控
# AIDE(Advanced Intrusion Detection Environment)# 建立系统文件指纹基线aide --init mv /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz# 定期比对,发现关键文件被篡改aide --check# 如 /etc/passwd /usr/bin/ssh 等关键路径被修改# 会立即报告,帮助发现入侵痕迹核心思路:纵深防御。单点被攻破不要紧,后面还有权限隔离、审计日志、完整性校验等多层防线。攻击者要突破所有防线才能拿到核心权限,每多一层就多一道门槛。
必杀技六:监控与日志,让故障"无所遁形"
监控不是装个 Prometheus 就完事了。中级运维要思考的是:监控什么、怎么告警、告警之后怎么办。
监控指标的金字塔
底层:基础设施 — CPU、内存、磁盘、网络(很多人只盯这一层)
中层:中间件 — 连接数、队列深度、缓存命中率、慢查询
顶层:业务指标 — 请求量、错误率、响应时间、转化率
很多团队只盯底层监控,中间件和业务指标缺失。结果就是——磁盘没满、CPU 不高,但业务已经报错了,监控面板一片绿。绿色不等于健康,可能只是你看不到的层面出了问题。
告警的克制
告警泛滥是监控最大的敌人。一天收 200 条告警,你会对每一条都麻木。一个好的告警系统应该满足:
1每条告警都需要人处理(不需要处理的就别告)
2告警信息包含上下文:哪个服务、什么指标、当前值、影响范围、处理建议
3设置合理的窗口和聚合(避免抖动告警风暴)
4有分级机制:P0 电话/短信,P1 钉钉/企微,P2 邮件
日志分析三板斧
规模不大时,命令行三板斧往往比 ELK 更高效:
# 1. 实时追踪 + 过滤tail -f /var/log/app.log | grep --line-buffered -E 'ERROR|WARN'# 2. 按时间范围提取sed -n '/2025-07-27 14:00:00/,/2025-07-27 14:30:00/p' app.log > incident.log# 3. 统计分析——快速定位最高频错误grep 'ERROR' app.log | \ awk -F']''{print $NF}' | \ sort | uniq -c | sort -rn | head -20# 一行命令就能看出哪类错误出现最多规模上来后,ELK/EFK 是标配。但不要为了用而用——日志量不大的场景,journalctl 配合 grep 和 awk 往往更快更直接。
必杀技七:故障排查的方法论,这是最难修炼的
前六个必杀技是"硬技能",这一个是最难修炼的"软实力"——面对一个从未见过的故障,你的大脑如何运转。
排障的黄金法则:先止血,再治本
线上着火了,第一反应不是找根因,而是快速恢复。因为业务中断的每一分钟都在亏钱。
止血手段的优先级:
1重启服务(最快,但会丢失现场信息)
2回滚变更(如果是刚发版引起的,最有效)
3限流降级(保护核心链路,非核心功能熔断)
4切换备用节点(如果有容灾架构)
止血的同时保护现场
在重启之前,花10秒保存现场——这可能帮你省10小时的排查时间:
# 在重启前,快速保存现场快照# 1. 抓取进程状态和资源占用ps aux | grep <process> > /tmp/snapshot_ps.txt# 2. 抓取网络连接状态ss -antp > /tmp/snapshot_ss.txt# 3. 抓取系统资源快照top -bn1 > /tmp/snapshot_top.txt iostat -x 1 3 > /tmp/snapshot_iostat.txt# 4. 如果条件允许,触发 core dump# 保留进程内存现场,事后用 gdb 分析gcore -o /tmp/core_dump <PID>根因分析的"五个为什么"
故障恢复后才是真正的修行。不要停留在"重启就好了",要往下追问:
●为什么会慢? → 某个 SQL 全表扫描
●为什么会全表扫描? → 索引被误删
●索引为什么被删? → 上线脚本有 bug
●脚本 bug 为什么没发现? → 没有 code review
●为什么没有 code review? → 上线流程缺失这一环
每一层追问,都会指向一个系统性的改进点。可能是一个监控补全,可能是一个架构调整,可能是一个流程优化。只追问到第一层就停下的,叫"处理了故障";追问到第五层的,才叫"解决了问题"。
变更管理是最大的防线
70% 的线上故障是变更引起的。中级运维要建立条件反射:
1任何变更都要有回滚方案(想清楚怎么退)
2变更要有窗口期(避开业务高峰时段)
3变更要有灰度(不要一次性全量推)
4变更要有验证(变更后主动检查关键指标)
写在最后:从"救火"到"防火"
中级运维和初级运维的本质区别,不在于工具数量,而在于思维方式:
初级运维 面对故障:重启 → 好了 → 下一个工单 | 中级运维 面对故障:定位 → 止血 → 根因 → 改进 → 防止复发 |
这种思维转变不会一蹴而就。它需要你在每一次故障复盘里反思,在每一次深夜值班里积累,在每一个看似不起眼的指标异常里保持敏感。
运维的终极目标不是快速救火,而是让火根本烧不起来。
把上面 7 个必杀技练扎实,你就具备了从"救火队员"进化为"系统守护者"的基础。剩下的,就是实战和时间的馈赠。
如果这篇文章对你有启发,欢迎转发给你身边还在靠"重启治百病"的运维同行。成长的路上,结伴总比独行好。