综合练习
这一课你将能做到
- 按固定顺序独立排查四类生产故障:502、端口占用、Permission denied、磁盘打满,全程说出「命令依据」
- 完成「从裸机到上线」的 15 分钟全流程演练:装环境 → 配服务 → 开门 → 验收,一次通过
- 产出一份自己的《上线/排障检查单》(≤10 条),并完成一次限时故障演习(15 分钟内定位根因)
为什么要学
单课会命令不等于会运维。生产故障从不单挑:磁盘满引发 MySQL 写失败,MySQL 超时引发 API 5xx,5xx 期间你重启了应用导致配置错乱——故障是链式的,慌乱会毁掉证据。若每次都靠「重装面板/重启整机」,日志没了、根因永远查不到,下次同样慌。
本课把 intro 到 deploy 十二课串成肌肉记忆:四个故障场景(每个都来自真实运维),一次全流程上线演练,最后固化成检查单。练的是冷静的顺序,不是运气。
先建立图像
排障总图:先分层,再下钻
访客报障
│
├─ 1. 应用层 curl 本机端口(3000/8080) 上游活吗?接口健康吗?
├─ 2. 进程层 ss / pm2 list / systemctl 进程在吗?谁在监听?
├─ 3. 反代层 nginx -t + error.log 转发写对了吗?
├─ 4. 资源层 df -h / free -h / top 磁盘/内存/CPU 有没有爆?
├─ 5. 权限层 ls -l + 进程用户对照 读得到文件吗?写得了日志吗?
└─ 6. 门禁层 ufw status + 云安全组 门开对了吗?监听地址对吗?
顺序的意义:从应用层往下查,因为报障最常见的原因就是上游死了;每层都有「快刀」,30 秒内能定性。记住一句行话:先止血,再查因;证据在日志里,别用重启毁掉它。
跟着做
场景一:502 全流程(主推场景,务必走完整遍)
制造故障(实验环境):
pm2 stop platform 2>/dev/null # 或 sudo systemctl stop coderoadmap-backend
# 浏览器访问首页 → 502
标准排查链(每步解释现象):
# A. 上游还活着吗?(应用层)
curl -s -o /dev/null -w "front:%{http_code}\n" http://127.0.0.1:3000/ # 000=死
curl -s http://127.0.0.1:8080/actuator/health; echo
# B. 进程与监听(进程层)
pm2 list
sudo ss -tlnp | grep -E ':(3000|8080)\s' # 没有监听=没人服务
# C. Nginx 怎么说?(反代层)
sudo tail -20 /var/log/nginx/error.log | grep -i upstream
# connect() failed (111: Connection refused) while connecting to upstream → 印证 A 的结论
# D. 修复 + 验活
pm2 start platform
curl -s -o /dev/null -w "front:%{http_code}\n" http://127.0.0.1:3000/ # 200
解释题(写进笔记):若 A 中 3000 失败而 8080 正常,502 的是首页还是 /api/?为什么?
场景二:Address already in use(端口占用)
# 制造:用 python 占住 8080
python3 -m http.server 8080 & # 记下回显的 PID
# 此时重启后端 → journalctl 会看到 Web server failed to start. Port 8080 was already in use.
sudo ss -tlnp | grep 8080 # 定位:谁占的(进程名+PID)
ps -p <PID> -o user,pid,etime,cmd # 是谁、跑了多久、什么命令
# 处置分流:
# 旧实例没杀干净 → 停它:
sudo systemctl restart coderoadmap-backend
# 是别的服务占的 → 改自己端口(--server.port=8081)而不是杀别人
# 杀掉本场景的实验占用者:
kill %1 2>/dev/null || kill <PID>
原则:端口唯一,占者未必是错误方。先确认身份再动手,多实例机器上 pkill -f java 级别的命令是禁区。
场景三:Permission denied(文件课/用户课联动)
# 制造:把日志目录属主改成 root(模拟 root 手滑上传后 chown 漏做)
sudo chown -R root:root /www/wwwroot/coderoadmap/logs
sudo systemctl restart coderoadmap-backend
journalctl -u coderoadmap-backend -n 20 --no-pager | grep -i denied
# → Permission denied: /www/wwwroot/coderoadmap/logs/... (或 logback 初始化失败)
# 排查三步:
systemctl show coderoadmap-backend -p User # ① 进程是谁
ls -ld /www/wwwroot/coderoadmap/logs # ② 目录归谁、权限几位
sudo -u www touch /www/wwwroot/coderoadmap/logs/.t && echo OK # ③ 以进程身份复现
# 修复 + 验活:
sudo chown -R www:www /www/wwwroot/coderoadmap/logs
sudo systemctl restart coderoadmap-backend && sleep 12
curl -s http://127.0.0.1:8080/actuator/health; echo
场景四:磁盘打满(命令课 df/du 实战)
# 制造(造一个 1.5G 的大文件模拟失控日志):
df -h / | tail -1 # 先记下当前可用
sudo fallocate -l 1500M /www/wwwroot/coderoadmap/logs/backend.log.huge
df -h / # 观察骤降;真满时 MySQL 写失败 → API 5xx
# 定位元凶:从根下钻
sudo du -xh /www 2>/dev/null | sort -rh | head -10
sudo du -sh /var/log /tmp 2>/dev/null
# 处置:日志类直接清空(不 rm 正被写的文件!)
sudo truncate -s 0 /www/wwwroot/coderoadmap/logs/backend.log.huge
sudo rm /www/wwwroot/coderoadmap/logs/backend.log.huge # 无进程占用才 rm
df -h / | tail -1 # 恢复
# 预防:日志轮转(logrotate 一份配置管到底)
sudo vim /etc/logrotate.d/coderoadmap
# /www/wwwroot/coderoadmap/logs/*.log {
# daily
# rotate 7
# compress
# missingok
# notifempty
# copytruncate
# }
sudo logrotate -d /etc/logrotate.d/coderoadmap # -d 干跑验证语法
演练二:从裸机到上线(15 分钟计时)
新开一台按量付费实例(或重置的 WSL),纯命令走完主链——计时,目标 ≤15 分钟:
# 1) 环境(包管理课) ~3min
sudo apt update && sudo apt install -y openjdk-17-jdk-headless nginx mysql-server curl
java -version && systemctl is-active mysql nginx
# 2) 目录与身份(文件/用户课) ~2min
sudo useradd -r -s /usr/sbin/nologin www 2>/dev/null || true
sudo mkdir -p /www/wwwroot/coderoadmap/{logs,releases}
sudo chown -R www:www /www/wwwroot/coderoadmap
# 3) 产物与 systemd(SSH/进程课) ~4min
scp 本机JAR test:/tmp/app.jar && sudo mv /tmp/app.jar /www/wwwroot/coderoadmap/backend.jar
# 写入单元文件(deploy 课模板)→ daemon-reload → enable --now → curl health
# 4) 门禁(防火墙课) ~2min
sudo ufw allow OpenSSH && sudo ufw allow 80,443/tcp && sudo ufw enable
# 应用绑 127.0.0.1(server.address)→ restart → ss 验证监听地址
# 5) 验收(deploy 课清单) ~2min
curl -s -o /dev/null -w "%{http_code}\n" http://localhost/ # Nginx 通
sudo ss -tlnp | grep -E ':(80|8080)\s' # 0.0.0.0:80 / 127.0.0.1:8080
演练三:定稿《个人上线/排障检查单》
把下面模板抄进自己的笔记仓库,改成本项目的真实路径与单元名:
【上线检查单】
□ 1. cat /etc/os-release 确认系统与包管理器
□ 2. java -version / node -v 运行时版本对齐
□ 3. chown www:www + chmod 校对 产物与日志目录权限
□ 4. systemctl enable --now 服务托管 + 开机自启
□ 5. curl 127.0.0.1:8080/health 上游健康
□ 6. ss -tlnp 0.0.0.0 只剩 80/443
□ 7. ufw status + 云安全组 双层门禁同源
□ 8. nginx -t && reload 反代生效
□ 9. 公网 curl 首页 + /api/ 全链路 200
□ 10. releases/ 留有上一版本 回滚就绪
【502 排障顺序】curl 本机上游 → ss 监听 → pm2/systemctl 状态
→ nginx error.log → journalctl → df -h(磁盘连锁)
演练四:限时故障演习(自测)
让同伴(或用 cron 随机)制造一个故障:pm2 stop platform / chown root logs / fallocate 占磁盘 / python 占端口 四选一。你的目标:15 分钟内用检查单定位根因并恢复,同时说出「命令依据」。全程禁止重启整机、禁止重装。
# 起手三连(任何故障的第一分钟):
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:3000/ http://127.0.0.1:8080/actuator/health
sudo ss -tlnp | grep -E ':(80|443|3000|8080|3306)\s'
df -h / && free -m | head -2
# 三条命令覆盖:应用层 + 进程层 + 资源层,60% 的故障在此定性
讲清楚要点
- 因为报障大多源于上游,所以第一刀永远是 curl 本机端口——30 秒内把「Nginx 问题」和「应用问题」切开,比打开宝塔看红点高效十倍。
- 因为日志是唯一的时间机器,所以禁止先重启毁证——journalctl --since 锁故障时间窗、nginx error.log 看上游拒绝、应用日志看异常堆栈。重启只能发生在有回滚保障的发布里。
- 因为端口唯一,所以端口冲突的解法是「确认身份」而非「互相残杀」——ss 定位 → ps 看身份 → 要么停旧要么改配置,pkill java 是多实例环境的自爆按钮。
- 因为权限判断者是进程,所以 Permission denied 的解法是「身份对齐」而非 777——进程用户查 systemctl show User,目录属主查 ls -ld,sudo -u 复现验证,chown 收尾。
- 因为磁盘是硬限制,所以 df 必须进每张检查单——日志轮转(logrotate)是预防,truncate 清被占用文件是急救,rm 只留给无进程占用的文件。
- 因为故障是链式的,所以要把「分层排查」练成本能——应用 → 进程 → 反代 → 资源 → 权限 → 门禁,每层一把快刀;查不到就说明「问题在你以为的层之外」,回到总图换个方向。
你可能会懵的地方
- 本机 curl 全 200,公网还是 502?
问题在「外面」:Nginx 的 upstream 指向了别的机器/别的端口、HTTPS 证书配置与 HTTP 混淆、云上还有一层 LB/CDN 在报 502。
curl -I 看响应头里 server 字段是谁;nginx -T | grep proxy_pass 核对真实生效配置。
- journalctl 里查不到我的服务?
说明进程不是 systemd 托管(nohup/宝塔自定义/PM2 起的)。分流:PM2 用
pm2 logs;宝塔 Java 项目看它面板里配置的日志路径;nohup 的去你重定向的那个文件。先问「谁在管家」,再找对应的日志源。
- kill 会不会丢数据?
分对象:无状态 Web 进程(Java/Node)kill -15 几乎无损(Spring Boot 优雅停机兜底);数据库进程严禁 kill -9(InnoDB 有崩溃恢复但代价是慢和吓人)。规范顺序永远先 systemctl stop。
- 练习环境没有完整 CodeRoadMap 怎么办?
用替身练「顺序」:
python3 -m http.server 8080 当后端、python3 -m http.server 3000 当前端,Nginx 照常反代——502/端口/权限/磁盘四个场景全部可复现。工具不重要,流程才是肌肉。
- 检查单要多久更新一次?
每次真实故障后追加一条「这次踩的坑」;每次架构变化(换面板/上 Docker/多机)重审一遍。检查单是活文档,三个月不动的检查单等于没检查单。
错法对照
| 错法 | 问题 | 正解 |
|---|
| 一见 502 就重装系统/面板 | 毁掉全部日志证据 | 固定 A→B→C 顺序,先定性再动手 |
| 不确认 PID 直接 killall java | 误杀同机其他服务 | ss + ps 定位 → systemctl 精准操作 |
| 忽略 df -h | 把磁盘满误判成代码 bug | 起手三连必含 df;检查单固化 |
| 只看面板「运行中」绿灯 | 假活:进程在但接口 5xx | curl 业务健康接口才算数 |
| 改配置不留记录不备份 | 无法回滚,越修越乱 | cp x x.bak + 变更写进笔记 |
| 修完不做全链路验证 | 「我这里好了」≠用户好了 | 公网 curl 首页 + /api/ 双验收 |
| 排障时疯狂 restart 刷日志 | 真实报错被淹在启动日志里 | 一次失败取足证据,修好再一次启动 |
卡点急救
- 故障演习卡住超时:回到「起手三连」重新定性——大多数卡死是因为跳层(没 curl 本机就去改 Nginx)。把每一步的「预期输出」写下来对照,偏差处即问题层。
- Nginx error.log 权限不够读不了:
sudo tail -50 /var/log/nginx/error.log。若你是 deploy 用户且无 sudo,向管理员要 sudo 白名单(用户课的 NOPASSWD 可加 tail 场景)。
- 造的故障恢复不了(服务怎么都起不来):回到 deploy 课的分层排查——单元文件路径/ExecStart 绝对路径/端口占用/DB 连通性四查;极端情况
journalctl -u xxx -n 200 一定写明了失败原因,service 没骗你。
- 检查单写不出来:把本课演练记录原文抄一遍再删减——「现象 → 命令 → 结论」三列格式,先抄后改,别追求一次完美。
面试常问
-
线上服务 502,描述你的完整排查过程。
分层:① curl 127.0.0.1 上游端口(区分应用死/Nginx 错);② ss -tlnp + pm2/systemctl status 看进程与监听;③ tail nginx error.log 找 upstream 拒绝;④ journalctl -u 服务 看临终日志;⑤ df -h 排除磁盘连锁(MySQL 写失败→API 挂→502);⑥ 核对最近发布(是否半发布:传了没重启/多机发错台)。修复后公网双验收。关键表达:顺序固定、每步有判据、先止血再查因。
-
磁盘使用率 100% 时你的处理流程?
立即止血:定位最大可清项(du -xh / | sort -rh | head),日志类 truncate -s 0(不 rm 被占用文件),确认 MySQL 等核心服务恢复写入。根治:logrotate 轮转、备份迁移、清理旧镜像/旧安装包。复盘:监控告警阈值(80% 预警)+ 扩容评估。
-
如何证明「新版本」真的生效了?
四证据:ls -lt releases/ 新文件落位时间;软链指向(readlink backend.jar);systemctl status 的 ActiveEnterTimestamp 在发布时间之后;健康接口返回的新版本号(actuator/info 或响应头 X-Version)。浏览器再 Ctrl+Shift+R 排除缓存。四者齐了才说「上线完成」。
-
生产事故复盘里你贡献过什么?
用 STAR 讲一个:S(现象:API 大面积超时)T(我的职责:Linux 层排查)A(做了什么:df 发现日志撑爆磁盘,truncate 止血 + logrotate 根治)R(结果:5 分钟恢复,之后 30 天零复发)。面试官考察的是流程化思维与证据意识,不是运气好。
-
怎么评价一台你从没接触过的服务器的健康状况?
五分钟体检单:cat /etc/os-release(系统)、uptime(负载与重启史)、free -h / top(内存与 CPU)、df -h(磁盘)、ss -tlnp(暴露面)、systemctl --failed(失败单元)、journalctl -p err --since today(今日错误)、last | head(登录审计)。输出一份「哪里异常 + 建议动作」清单——这就是检查单思维的浓缩版。
自测三问
- 写出 502 排查前四步的命令(含每步的「判据」:看到什么算什么病)。
- 「scp 成功但用户看到的还是旧版本」可能漏了哪些环节?按概率排序。
- 为什么 Permission denied 不应该第一反应 chmod 777?给出规范修复路径的命令序列。
小结一句话
运维能力的分水岭不在命令量,而在顺序:起手三连定性、分层排查下钻、证据先于动作、检查单固化经验——十二课的所有命令,最终都服务这张排障总图。
延伸