feat: add troubleshooting flows and extend ops learning content
This commit is contained in:
@@ -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 / 相关认证日志",
|
||||||
|
"不要只盯着“密码错了”一个方向"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 || '';
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user