feat: add troubleshooting flows and extend ops learning content

This commit is contained in:
likingcode
2026-03-10 09:31:09 +08:00
parent f3d5f92478
commit 89d2c3b4a3
3 changed files with 119 additions and 24 deletions

View File

@@ -699,9 +699,16 @@
"takeaways": [ "takeaways": [
"学完后应能做到:理解 Linux 中的进程概念,知道如何查看系统正在运行什么。", "学完后应能做到:理解 Linux 中的进程概念,知道如何查看系统正在运行什么。",
"易错提醒:只会看进程名,不会看状态", "易错提醒:只会看进程名,不会看状态",
"迁移场景:确认服务进程是否存在" "迁移场景:确认服务进程是否存在",
"形成分层排障顺序,而不是遇到问题就随手试命令。"
], ],
"after_class": "课后建议:回到真实或模拟环境里,再用 ps、top 做一次独立练习,并尝试自己解释每条输出的含义。" "after_class": "课后建议:回到真实或模拟环境里,再用 ps、top 做一次独立练习,并尝试自己解释每条输出的含义。",
"troubleshooting_flow": [
"先确认服务对应的进程是否存在",
"再看进程状态、CPU、内存占用是否异常",
"如果进程存在但服务不可用,再继续看端口和日志",
"不要把“有进程”误判成“服务正常”"
]
}, },
{ {
"id": "m4_l2_disk_memory", "id": "m4_l2_disk_memory",
@@ -767,9 +774,16 @@
"takeaways": [ "takeaways": [
"学完后应能做到:掌握查看磁盘使用、目录占用和内存情况的基础方法。", "学完后应能做到:掌握查看磁盘使用、目录占用和内存情况的基础方法。",
"易错提醒:只会看总磁盘,不会看哪个目录占用大", "易错提醒:只会看总磁盘,不会看哪个目录占用大",
"迁移场景:排查磁盘已满" "迁移场景:排查磁盘已满",
"形成分层排障顺序,而不是遇到问题就随手试命令。"
], ],
"after_class": "课后建议:回到真实或模拟环境里,再用 df、du、free 做一次独立练习,并尝试自己解释每条输出的含义。" "after_class": "课后建议:回到真实或模拟环境里,再用 df、du、free 做一次独立练习,并尝试自己解释每条输出的含义。",
"troubleshooting_flow": [
"先用 df 确认是哪个文件系统空间不足",
"再用 du 逐层定位哪个目录占用最大",
"必要时结合 find 找出大文件",
"清理前先确认文件用途与是否还能用于排障"
]
}, },
{ {
"id": "m4_l3_mount_history", "id": "m4_l3_mount_history",
@@ -835,9 +849,16 @@
"takeaways": [ "takeaways": [
"学完后应能做到:建立系统运行时间、挂载结构与命令习惯认知。", "学完后应能做到:建立系统运行时间、挂载结构与命令习惯认知。",
"易错提醒:不看历史重复犯错", "易错提醒:不看历史重复犯错",
"迁移场景:查看机器是否重启过" "迁移场景:查看机器是否重启过",
"形成分层排障顺序,而不是遇到问题就随手试命令。"
], ],
"after_class": "课后建议:回到真实或模拟环境里,再用 uptime、mount、history 做一次独立练习,并尝试自己解释每条输出的含义。" "after_class": "课后建议:回到真实或模拟环境里,再用 uptime、mount、history 做一次独立练习,并尝试自己解释每条输出的含义。",
"troubleshooting_flow": [
"先看 uptime 确认系统是否近期重启",
"再看 mount 判断关键目录属于哪个挂载点",
"最后回看 history 了解最近做过什么变更",
"把系统状态和操作历史结合起来看"
]
} }
] ]
}, },
@@ -904,9 +925,16 @@
"学完后应能做到:理解 Linux 服务的查看、启动、停止和重启。", "学完后应能做到:理解 Linux 服务的查看、启动、停止和重启。",
"易错提醒:改完配置却忘记重启服务", "易错提醒:改完配置却忘记重启服务",
"迁移场景:排查服务没起来", "迁移场景:排查服务没起来",
"服务问题先看状态,再决定下一步看日志、端口还是配置。" "服务问题先看状态,再决定下一步看日志、端口还是配置。",
"形成分层排障顺序,而不是遇到问题就随手试命令。"
], ],
"after_class": "课后建议:回到真实或模拟环境里,再用 systemctl 做一次独立练习,并尝试自己解释每条输出的含义。" "after_class": "课后建议:回到真实或模拟环境里,再用 systemctl 做一次独立练习,并尝试自己解释每条输出的含义。",
"troubleshooting_flow": [
"先看 systemctl status确认服务到底是不是 running",
"再看是否有明显的启动失败或退出提示",
"如果状态异常,再进入日志层和端口层",
"不要一上来就盲目 restart 多次"
]
}, },
{ {
"id": "m5_l2_journalctl", "id": "m5_l2_journalctl",
@@ -966,9 +994,16 @@
"学完后应能做到:理解如何查看服务日志和系统日志。", "学完后应能做到:理解如何查看服务日志和系统日志。",
"易错提醒:只看应用日志,不看 systemd 日志", "易错提醒:只看应用日志,不看 systemd 日志",
"迁移场景:查看服务启动失败原因", "迁移场景:查看服务启动失败原因",
"日志不是越多越好,关键是缩小范围看最近、看目标服务。" "日志不是越多越好,关键是缩小范围看最近、看目标服务。",
"形成分层排障顺序,而不是遇到问题就随手试命令。"
], ],
"after_class": "课后建议:回到真实或模拟环境里,再用 journalctl 做一次独立练习,并尝试自己解释每条输出的含义。" "after_class": "课后建议:回到真实或模拟环境里,再用 journalctl 做一次独立练习,并尝试自己解释每条输出的含义。",
"troubleshooting_flow": [
"先限定服务名缩小日志范围",
"优先看最近几十行,不要一开始把范围拉太大",
"定位到关键报错后再回溯上下文",
"边操作边用 -f 观察实时变化"
]
}, },
{ {
"id": "m5_l3_process_control", "id": "m5_l3_process_control",
@@ -1030,9 +1065,16 @@
"学完后应能做到:理解如何控制进程和让任务脱离终端运行。", "学完后应能做到:理解如何控制进程和让任务脱离终端运行。",
"易错提醒:直接粗暴 kill 掉关键进程", "易错提醒:直接粗暴 kill 掉关键进程",
"迁移场景:结束卡死进程", "迁移场景:结束卡死进程",
"进程控制的重点是知道为什么结束、结束谁、结束后系统会怎样。" "进程控制的重点是知道为什么结束、结束谁、结束后系统会怎样。",
"形成分层排障顺序,而不是遇到问题就随手试命令。"
], ],
"after_class": "课后建议:回到真实或模拟环境里,再用 kill、pkill、nohup 做一次独立练习,并尝试自己解释每条输出的含义。" "after_class": "课后建议:回到真实或模拟环境里,再用 kill、pkill、nohup 做一次独立练习,并尝试自己解释每条输出的含义。",
"troubleshooting_flow": [
"确认要处理的是哪个进程",
"评估结束进程会不会影响业务",
"优先选择合理方式终止,不要默认暴力 kill -9",
"需要后台运行任务时再考虑 nohup"
]
} }
] ]
}, },
@@ -1111,9 +1153,16 @@
"学完后应能做到理解网卡、IP 和连通性的基本概念。", "学完后应能做到理解网卡、IP 和连通性的基本概念。",
"易错提醒:能 ping 通就以为服务一定可用", "易错提醒:能 ping 通就以为服务一定可用",
"迁移场景:确认机器是否有正确 IP", "迁移场景:确认机器是否有正确 IP",
"网络排查第一步是先确认链路和地址,再看更上层。" "网络排查第一步是先确认链路和地址,再看更上层。",
"形成分层排障顺序,而不是遇到问题就随手试命令。"
], ],
"after_class": "课后建议:回到真实或模拟环境里,再用 ip addr、ifconfig、ping 做一次独立练习,并尝试自己解释每条输出的含义。" "after_class": "课后建议:回到真实或模拟环境里,再用 ip addr、ifconfig、ping 做一次独立练习,并尝试自己解释每条输出的含义。",
"troubleshooting_flow": [
"先确认机器有没有拿到正确 IP",
"再用 ping 验证基础连通性",
"如果 ping 不通,优先怀疑网络层或地址层问题",
"如果 ping 通,再继续检查端口和应用层"
]
}, },
{ {
"id": "m6_l2_ss_curl", "id": "m6_l2_ss_curl",
@@ -1186,9 +1235,16 @@
"学完后应能做到:建立监听端口和服务请求验证的能力。", "学完后应能做到:建立监听端口和服务请求验证的能力。",
"易错提醒:只看页面打不开,不查监听", "易错提醒:只看页面打不开,不查监听",
"迁移场景:查服务是否监听端口", "迁移场景:查服务是否监听端口",
"监听正常不代表业务正常,请求失败也不一定是服务没启动。" "监听正常不代表业务正常,请求失败也不一定是服务没启动。",
"形成分层排障顺序,而不是遇到问题就随手试命令。"
], ],
"after_class": "课后建议:回到真实或模拟环境里,再用 ss、netstat、curl、wget 做一次独立练习,并尝试自己解释每条输出的含义。" "after_class": "课后建议:回到真实或模拟环境里,再用 ss、netstat、curl、wget 做一次独立练习,并尝试自己解释每条输出的含义。",
"troubleshooting_flow": [
"先看端口是否监听",
"再用 curl 验证应用层是否有返回",
"如果监听正常但请求异常,再结合日志判断应用问题",
"如果根本没监听,先回到服务状态层排查"
]
}, },
{ {
"id": "m6_l3_name_route", "id": "m6_l3_name_route",
@@ -1256,9 +1312,16 @@
"学完后应能做到:建立链路定位和名称解析基础认知。", "学完后应能做到:建立链路定位和名称解析基础认知。",
"易错提醒:把 DNS 问题误判成应用问题", "易错提醒:把 DNS 问题误判成应用问题",
"迁移场景:排查域名异常", "迁移场景:排查域名异常",
"命令定位、解析路径和网络链路,都是“看不见的问题”的排查入口。" "命令定位、解析路径和网络链路,都是“看不见的问题”的排查入口。",
"形成分层排障顺序,而不是遇到问题就随手试命令。"
], ],
"after_class": "课后建议:回到真实或模拟环境里,再用 traceroute、dig、which、whereis 做一次独立练习,并尝试自己解释每条输出的含义。" "after_class": "课后建议:回到真实或模拟环境里,再用 traceroute、dig、which、whereis 做一次独立练习,并尝试自己解释每条输出的含义。",
"troubleshooting_flow": [
"先确认命令或服务实际路径",
"再检查域名解析是否正确",
"必要时查看路由链路是否异常",
"把“命令路径 / DNS / 路由”当成三类不同问题"
]
} }
] ]
}, },
@@ -1903,9 +1966,17 @@
"学完后应能做到:建立“先服务、再端口、再日志、再请求”的排查顺序。", "学完后应能做到:建立“先服务、再端口、再日志、再请求”的排查顺序。",
"易错提醒:只看浏览器打不开,不看服务状态", "易错提醒:只看浏览器打不开,不看服务状态",
"迁移场景:应用服务无法访问", "迁移场景:应用服务无法访问",
"服务不可用时,排障要按层进行:服务 → 进程 → 端口 → 日志 → 请求。" "服务不可用时,排障要按层进行:服务 → 进程 → 端口 → 日志 → 请求。",
"形成分层排障顺序,而不是遇到问题就随手试命令。"
], ],
"after_class": "课后建议:回到真实或模拟环境里,再用 systemctl、ps、ss、journalctl、curl 做一次独立练习,并尝试自己解释每条输出的含义。" "after_class": "课后建议:回到真实或模拟环境里,再用 systemctl、ps、ss、journalctl、curl 做一次独立练习,并尝试自己解释每条输出的含义。",
"troubleshooting_flow": [
"服务状态systemctl status",
"进程状态ps / 进程是否存在",
"端口监听ss 或 netstat",
"日志定位journalctl / 应用日志",
"请求验证curl 直接打本机或接口"
]
}, },
{ {
"id": "m10_l2_disk_full", "id": "m10_l2_disk_full",
@@ -1966,9 +2037,16 @@
"学完后应能做到:建立从 df 到 du 再到 find 的磁盘问题定位思路。", "学完后应能做到:建立从 df 到 du 再到 find 的磁盘问题定位思路。",
"易错提醒:只看 df 不继续追目录", "易错提醒:只看 df 不继续追目录",
"迁移场景:排查磁盘 100%", "迁移场景:排查磁盘 100%",
"磁盘排查的关键是先找文件系统,再找目录,再找大文件。" "磁盘排查的关键是先找文件系统,再找目录,再找大文件。",
"形成分层排障顺序,而不是遇到问题就随手试命令。"
], ],
"after_class": "课后建议:回到真实或模拟环境里,再用 df、du、find、sort 做一次独立练习,并尝试自己解释每条输出的含义。" "after_class": "课后建议:回到真实或模拟环境里,再用 df、du、find、sort 做一次独立练习,并尝试自己解释每条输出的含义。",
"troubleshooting_flow": [
"文件系统层df -h",
"目录层du -sh",
"文件层find + sort",
"处理层:确认是否可删、是否要备份、是否影响排障"
]
}, },
{ {
"id": "m10_l3_login_fail", "id": "m10_l3_login_fail",
@@ -2030,9 +2108,17 @@
"学完后应能做到:把身份、权限、日志三者串起来理解。", "学完后应能做到:把身份、权限、日志三者串起来理解。",
"易错提醒:只怀疑密码错误,不看日志", "易错提醒:只怀疑密码错误,不看日志",
"迁移场景SSH 登录失败", "迁移场景SSH 登录失败",
"登录失败排查要把身份、日志和权限一起看,不能只猜密码。" "登录失败排查要把身份、日志和权限一起看,不能只猜密码。",
"形成分层排障顺序,而不是遇到问题就随手试命令。"
], ],
"after_class": "课后建议:回到真实或模拟环境里,再用 whoami、id、passwd、grep、tail 做一次独立练习,并尝试自己解释每条输出的含义。" "after_class": "课后建议:回到真实或模拟环境里,再用 whoami、id、passwd、grep、tail 做一次独立练习,并尝试自己解释每条输出的含义。",
"troubleshooting_flow": [
"身份层whoami / id / 当前用户是谁",
"账户层:账号是否存在、是否被限制",
"权限层:文件和脚本权限是否正确",
"日志层auth.log / 相关认证日志",
"不要只盯着“密码错了”一个方向"
]
} }
] ]
} }

View File

@@ -272,6 +272,11 @@
<div class="badge-row" id="relatedCommands"></div> <div class="badge-row" id="relatedCommands"></div>
</div> </div>
<div class="panel">
<h3>排障链路 / 处理顺序</h3>
<ul id="flowList"></ul>
</div>
<div class="panel"> <div class="panel">
<h3>课后总结</h3> <h3>课后总结</h3>
<ul id="takeawayList"></ul> <ul id="takeawayList"></ul>
@@ -380,6 +385,7 @@
renderList('conceptList', lesson.concepts || []); renderList('conceptList', lesson.concepts || []);
renderList('pitfallList', lesson.pitfalls || []); renderList('pitfallList', lesson.pitfalls || []);
renderList('scenarioList', lesson.scenarios || []); renderList('scenarioList', lesson.scenarios || []);
renderList('flowList', lesson.troubleshooting_flow || []);
renderList('takeawayList', lesson.takeaways || []); renderList('takeawayList', lesson.takeaways || []);
document.getElementById('classicView').textContent = lesson.classic_view || '教材视角:先理解问题,再选择命令。'; document.getElementById('classicView').textContent = lesson.classic_view || '教材视角:先理解问题,再选择命令。';
document.getElementById('afterClass').textContent = lesson.after_class || ''; document.getElementById('afterClass').textContent = lesson.after_class || '';

View File

@@ -548,6 +548,7 @@ class LinuxSandbox:
canned = { canned = {
"clear": "[screen cleared]", "clear": "[screen cleared]",
"id": "uid=1000(sandbox_user) gid=1000(sandbox_user) groups=1000(sandbox_user),999(docker)", "id": "uid=1000(sandbox_user) gid=1000(sandbox_user) groups=1000(sandbox_user),999(docker)",
"uptime": " 10:00:00 up 5 days, 2 users, load average: 0.10, 0.12, 0.08",
"w": " 10:00:00 up 5 days, 2 users, load average: 0.10, 0.12, 0.08\nUSER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT\nsandbox pts/0 localhost 10:00 1:20 0.01s 0.00s bash", "w": " 10:00:00 up 5 days, 2 users, load average: 0.10, 0.12, 0.08\nUSER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT\nsandbox pts/0 localhost 10:00 1:20 0.01s 0.00s bash",
"last": "sandbox_user pts/0 localhost Mon Mar 6 10:04 still logged in\nreboot system boot 5.15.0 Mon Mar 6 10:00", "last": "sandbox_user pts/0 localhost Mon Mar 6 10:04 still logged in\nreboot system boot 5.15.0 Mon Mar 6 10:00",
"passwd": "Changing password for sandbox_user... (simulated)", "passwd": "Changing password for sandbox_user... (simulated)",
@@ -612,6 +613,8 @@ class LinuxSandbox:
return canned["whereis"] return canned["whereis"]
if cmd_name == "dig": if cmd_name == "dig":
return canned.get("dig", ";; ANSWER SECTION:\nexample.com. 300 IN A 93.184.216.34") return canned.get("dig", ";; ANSWER SECTION:\nexample.com. 300 IN A 93.184.216.34")
if cmd_name in {"uptime", "lsof", "sort", "uniq", "cut", "awk", "sed"}:
return canned.get(cmd_name, f"{cmd_name}: simulated")
if cmd_name == "export" and args and "=" in args[0]: if cmd_name == "export" and args and "=" in args[0]:
key, value = args[0].split("=", 1) key, value = args[0].split("=", 1)
self.env[key] = value self.env[key] = value