Linux 高阶应用模块三
Linux 智能运维与管理 · 2026—2027 学年第一学期

Linux 高阶应用

模块三 —— 专题一 Shell 编程与运维(第 21—26 次课)+ 专题二 Nginx 服务器安装与部署(第 27—31 次课)。
11 次课 · 22 学时 · 理实一体,每次课一个综合作业覆盖全部知识点。

第 21—31 次课 任课教师:张娜
#!/bin/bash if [ -f x ] for i in "${arr[@]}" docker run -d -p 80:80
student@centos7: ~ — 终端
# 本模块你将学会 $ cat lib.sh check_disk() { df -h / | awk 'NR==2{print $5}'; } $ bash sysins.sh DISK:37% sshd active $ docker ps web nginx:latest Up 2 minutes 0.0.0.0:8080->80/tcp $ nginx -t syntax is ok / test is successful
模块三|专题地图

本模块地图 · 11 次课 22 学时Course Map

从 Shell 脚本基础一路走到 Docker 与 Nginx 部署 —— 把「手工操作」变成「可交付的服务」。每次课都有明确的关键点、操作演示,以及一个覆盖全部知识点的综合作业。

11 次课 · 22 学时 11 个综合作业 6 项实训任务 6 个思考题 每步均有验证要求
01

Shell 脚本基础

第 21 次课基础认知
Shebang指定解释器
解释执行逐行不用编译
$?退出状态 0 成功
var=value等号两侧无空格
$(cmd)命令替换
read -p读取用户输入

教学目标创建并运行基础 Shell 脚本,使用变量、命令替换和输入输出完成小任务。

重点 脚本结构、变量和输入输出
难点 解释执行、引用规则和退出状态

关键点 · 脚本机制 作业 脚本基础 + 变量与退出码
02

条件判断

第 22 次课分支逻辑
test / [ ]条件测试
-z / -n字符串空与非空
-eq / -gt数字比较
-f / -d文件与目录
if/elif/else多级分支
&& / ||短路执行

教学目标使用条件判断处理字符串、数字和文件状态,编写多分支脚本。

重点 test、[] 和条件分支语法
难点 判断类型、空值和边界条件

关键点 · 条件判断 作业 磁盘告警脚本(多分支)
03

循环与批处理

第 23 次课批量操作
for遍历已知列表
while条件为真时循环
until条件为假时循环
break跳出整个循环
continue跳过本次
while read逐行读文件

教学目标使用 for、while 完成批量文件处理或巡检,处理循环边界和异常输入。

重点 循环结构和批处理
难点 循环边界、读取输入和异常处理

关键点 · 批量处理 作业 批量创建用户(幂等脚本)
04

函数与模块化

第 24 次课工程复用
function封装可复用逻辑
$1 $# $@位置参数
return仅 0—255 整数
local局部变量
source引入函数库
lib.sh公共脚本拆分

教学目标将运维任务拆分为可复用函数,使用参数和返回值完成测试。

重点 函数、参数、返回值和模块化
难点 职责拆分与函数间数据传递

关键点 · 模块化 作业 巡检脚本函数化重构
05

数组与批量数据

第 25 次课数据组织
arr=(a b c)定义数组
${arr[0]}下标从 0 开始
${arr[@]}取所有元素
${#arr[@]}数组长度
arr+=(d)追加元素
unset arr[1]删除元素

教学目标正确遍历数组,处理空值、错误和边界情况,形成可验证脚本。

重点 数组遍历与批量数据处理
难点 空值、越界和异常分支

关键点 · 边界处理 作业 数组脚本 + 边界测试记录
06

综合脚本工程化

第 26 次课交付闭环
需求拆解拆成单一职责函数
bash -x逐行调试
时间戳归档报告按天存
exit 0/1/2明确退出码
README使用说明
crontab定时执行

教学目标完成需求拆解、脚本编写、调试、验证和文档化,提交可运行运维脚本。

重点 需求到脚本的完整闭环
难点 多知识点整合、调试和结果说明

关键点 · 工程化 作业 系统巡检脚本(脚本+日志+说明)
07

Docker 概念与安装

第 27 次课容器认知
Image只读模板(类)
Container运行实例(对象)
Registry镜像仓库
共享内核容器与虚拟机差异
pull→run→stop→rm生命周期
enable --now开机自启+启动

教学目标区分镜像、容器和仓库,完成 Docker 安装并运行 hello-world 验证环境。

重点 容器基本概念和 Docker 安装
难点 容器与虚拟机的差异及环境验证

关键点 · 容器基础 作业 Docker 安装 + hello-world
08

容器生命周期

第 28 次课服务交付
docker pull拉取镜像
docker run创建并启动
-p 宿主:容器端口映射方向
docker ps -a含已停止容器
docker stop停止但保留
docker rm/rmi删除容器/镜像

教学目标掌握容器生命周期命令,使用 Docker 运行 Nginx 并完成端口映射和访问验证。

重点 pull、run、ps、stop、rm 及端口映射
难点 生命周期状态与服务验证

关键点 · 生命周期 作业 Nginx 容器运行/停止/删除
09

Nginx 与静态站点

第 29 次课Web 服务
nginx -t改前测语法
systemctl reload平滑重载
http/server/location四层嵌套
listen监听端口
root / index站点根与首页
conf.d/站点配置目录

教学目标完成 Nginx 安装、服务启停和静态测试页面发布,理解主配置文件结构。

重点 Nginx 服务管理和静态站点配置
难点 主配置文件层级与核心指令作用

关键点 · 服务部署 作业 静态站点发布 + 配置报错复现
10

虚拟主机

第 30 次课多站点
server 块一个站点
server_name域名匹配
default_server默认兜底
conf.d 分文件一站点一文件
curl -H Host模拟域名访问
access_log命中证据

教学目标配置两个测试站点,理解请求匹配规则并实现多站点访问。

重点 server 块、server_name 和站点目录
难点 请求匹配和配置优先级

关键点 · 多站点 作业 双站点配置 + 日志验证
11

反向代理与负载均衡

第 31 次课综合部署
proxy_pass转发到后端
末尾斜杠影响 URL 拼接
upstream后端池定义
weight/ip_hash分发策略
502后端无有效响应
配置/连通/响应三层排障

教学目标配置反向代理或负载均衡,完成测试、排障和部署文档。

重点 proxy_pass、反向代理和负载均衡
难点 跨组件故障定位与配置复盘

关键点 · 综合交付 作业 代理部署 + 502 排障记录
学习路径 专题一Shell 编程与运维 · L21—L26▶ 专题二Nginx 服务器安装与部署 · L27—L31
01

Shell 脚本基础

Shell Script Basics
第 21 次课2 学时理实一体
重点脚本结构、Shebang、变量定义与引用、输入输出、命令替换与退出状态。
难点解释执行机制、引用规则(单引号/双引号/反引号)与退出状态——脚本写错一行就整体不执行,且报错信息常指向别处。

核心方法:从「最小可运行脚本」开始:先让 bash hello.sh 打印一行字,再逐步加变量、命令替换、用户输入,每加一步就用 echo 和 $? 验证结果。

教学目标
01脚本机制:说清 Shebang 的作用,理解 Shell 是逐行解释执行而非编译执行。
02变量:掌握定义、引用与算术运算,区分单引号与双引号的展开差异。
03输入输出:会用 read 读取用户输入,用 echo 输出结果。
04退出状态:理解 $? 的含义,会用 bash -x 调试脚本。
思考题:脚本退出状态如何帮助自动化判断?作业:编写并运行一个基础 Shell 脚本
核心机制|不编译,逐行解释

脚本、Shebang 与解释执行

Shell 脚本不是「程序」,是「写给 Shell 读的命令清单」。它没有编译期,语法错误往往在运行到那一行时才暴露。

SH脚本是什么

把多条命令写进一个文本文件,交给 Shell 逐行读取并调用系统调用执行。

.sh 后缀只是约定

修改后立即可测,无需编译

#!Shebang

脚本第一行指定解释器。

#!/bin/bash

没有它,系统会用默认 Shell 执行,行为可能不一致

$?退出状态

$? 保存上一条命令的返回值。

0 = 成功,非 0 = 失败或异常

是脚本分支判断的基础

▶三种执行方式

source 影响当前 Shell 环境

bash a.sh 开子进程

./a.sh 需先 chmod +x

关键认知:为什么推荐 bash a.sh 而不是 ./a.sh:前者不依赖可执行权限,也不会因文件权限或格式(CRLF 行尾)导致「Permission denied / bad interpreter」,教学与排错阶段最省心。
变量|定义、引用与算术

变量的定义与三种引用规则

变量默认全是字符串。引用方式决定了内容是否被展开——这是初学者最常踩坑的地方。

=定义变量

格式:var=value

等号两侧不能有空格

name='alice'

带空格的值要用引号包住

$引用规则

$var 直接引用

${var} 用花括号界定边界

echo ${name}ok

单引号不展开,双引号会展开

()算术运算

变量默认是字符串,算术需显式声明。

echo $((a + b))
let c=a+b

$(( )) 是最常用写法

写法含义示例注意
var=value定义变量name='alice'等号两侧不能有空格
$var / ${var}引用变量echo $name / echo ${name}花括号避免边界混淆
'$name'单引号引用echo '$name'原样输出,不做变量替换
"$name"双引号引用echo "$name"会解析变量,但整体仍是一个参数
$((a+b))算术求值echo $((a+b))变量先按整数解释再运算
注意:单引号与双引号的区别只在变量与命令替换是否展开。需要展开用双引号,需要原样保留(如含 $ 的密码、含空格的正则)用单引号。
输入输出|和用户、和其他程序打交道

read 与 echo:脚本的交互两端

脚本的价值在于「自动跑」。但很多运维任务需要参数,参数从哪来?——从命令行参数或用户输入来。

in读取用户输入

read -p '提示语' 变量名

read -p '请输入用户名: ' username

-p 显示提示语,-s 静默(密码场景)

输入结束需回车

out输出到屏幕

echo "内容" 输出到 stdout

echo "欢迎, $username!"

> 重定向到文件,>> 追加

脚本里不要用 echo 传值给别的程序,用 stdout 重定向更规范

关键认知:命令行参数是更「运维化」的输入方式:./backup.sh /etc /backup。用户输入适合交互式脚本(如菜单选择),参数方式适合无人值守的定时任务。
进阶|让脚本能拿到「结果」

命令替换与退出状态

命令替换让脚本把别的命令的输出当数据用;退出状态让脚本知道命令成功没有。两者结合,脚本才具备判断能力。

student@centos7: ~ — 第一个脚本
#!/bin/bash # 文件名: hello.sh # 功能: 读取用户名并输出欢迎信息 $ read -p '请输入用户名: ' username $ now=$(date +%F %H:%M:%S) $ echo "欢迎, $username!" $ echo "当前时间: $now" # 执行方式 $ bash hello.sh $ chmod +x hello.sh && ./hello.sh

命令替换

格式:$(命令),把命令的标准输出取回来当字符串。

today=$(date +%F)

现代写法,优于反引号 `cmd`——可嵌套、可读性好。

配套的还有 $(cmd 2>&1) 合并错误输出。

注意:命令替换取的是输出,不是「执行了没有」。命令失败时输出为空,脚本会安静地拿到一个空变量——所以必须同时检查 $?。

退出状态 $?

每条命令执行后都会给 Shell 一个退出码。

ping -c1 8.8.8.8; echo $?

0 表示成功,非 0 表示失败(不同命令的具体值含义不同,如 grep 是 1=无匹配、2=出错)。

脚本中常配合 if 或 && / || 使用。

关键认知:排错技巧:脚本报错看不懂时,用 bash -x hello.sh 打印每行执行前的展开结果,一眼就能看出变量是不是你以为的那个值。
课后作业基础 Shell 脚本:问候脚本约 25 分钟 · 覆盖本课 5 个知识点 · 每步均有验证
  1. 建立工作目录 ~/stu序号_姓名拼音/l21/,新建 hello_序号.sh。验证:文件首行是 #!/bin/bash。
  2. 脚本中定义变量 name 与 now=$(date +%F),并用 echo "..." 输出。验证:双引号内变量被正确展开,等号两侧无空格。
  3. 用 read -p 读取用户输入的用户名并输出欢迎语。验证:运行后提示语出现,输入后显示欢迎信息。
  4. 在脚本末尾加一行 echo $? 或 exit 0,用 bash -x 执行一次。验证:能说明 -x 输出中哪部分是变量展开结果。
  5. 把脚本中的输出改为写入 out_序号.txt(用 > 重定向)。验证:重定向后终端不再直接打印内容,文件内容与原来一致。
提交:终端中运行脚本并显示结果的截图 + cat hello_序号.sh 的全文截图 + cat out_序号.txt 的内容截图(文件名与目录均带你的序号,勿直接抄袭同学文件)。
02

条件判断

Conditional Judgment
第 22 次课2 学时理实一体
重点test 命令、[ ] 单方括号、if-then-else 分支结构与短路逻辑。
难点区分字符串、数字、文件三类判断;理解空值导致的语法错误与边界条件。

核心方法:先背真值表,再做正例、反例、空值三组对照测试,每写一个判断都用 echo $? 验证返回值。

教学目标
01判断工具:掌握 test 与 [ ] 的等价写法,知道条件为真即退出码 0。
02三类判断:分清字符串(-z/-n/=/!=)、数字(-eq/-gt/-lt)、文件(-e/-f/-d/-r/-x)。
03分支结构:能写出 if-then-else 与 elif 多分支脚本。
04短路逻辑:理解 && 与 || 的条件执行语义。
思考题:字符串判断和整数判断的写法有何区别?作业:编写包含多种条件判断的脚本
判断工具|test 与 [ ]

test 命令与三类判断

test 是一个专门做「条件测试」的命令:条件成立返回 0,不成立返回非 0。[ ] 是它的语法糖写法,中间必须有空格。

S字符串判断

-z str 为空

-n str 非空

str1 = str2 相等

str1 != str2 不等

[ -z "$name" ]

N数字判断

-eq 等于 -ne 不等于

-gt 大于 -ge 大于等于

-lt 小于 -le 小于等于

[ "$n" -gt 80 ]

F文件判断

-e 存在 -f 普通文件

-d 目录 -r 可读

-w 可写 -x 可执行

[ -f /etc/fstab ]

注意:两个高频错误:① [ $n -gt 80 ] 中 [ 后必须有空格,写成 [$n 会被当成命令名;② 变量必须加双引号——[ $name = alice ] 在 name 为空时会变成 [ = alice ] 从而报语法错误。
分支结构|if / elif / else

if-then-else 与多级分支

Shell 遵循「命令成功即真」的 Unix 哲学——条件不用写 ==,直接用命令的退出码作为判断依据。

if基本结构

if [ 条件 ]; then
  命令1
else
  命令2
fi

条件为真(退出码 0)执行 then,否则执行 else。

结束关键字是 fi(if 的倒写)。

elif多级分支

if 条件1; then
  A
elif 条件2; then
  B
else
  C
fi

从上往下只匹配一个分支,命中即执行并跳出整个 if 块。

判断类型常用测试示例含义
字符串为空-z str[ -z "$name" ]变量未赋值或为空串
字符串非空-n str[ -n "$name" ]变量有值
字符串相等str1 = str2[ "$a" = "$b" ]字符串<b>逐字</b>比较,非数值比较
整数相等-eq[ "$n" -eq 5 ]两边必须都是整数
整数大于-gt[ "$n" -gt 80 ]配合 -ge / -lt / -le 使用
文件存在-e[ -e /etc/fstab ]任何类型都算存在
是普通文件-f[ -f /etc/passwd ]排除目录与设备文件
是目录-d[ -d /var/log ]常用于目录存在性检查
关键认知:字符串比较用 =,整数比较用 -eq——这是最容易混的一处。[ "08" -eq 8 ] 会报「整数表达式预期」,因为 08 被当成八进制非法数字。
组合逻辑|&& 与 ||

短路逻辑与条件执行

条件不一定要写成 if。Shell 提供两个「短路」运算符,直接把命令串成条件链。

&&& 与 ||

cmd1 && cmd2:只有 cmd1 成功才执行 cmd2。

cmd1 || cmd2:只有 cmd1 失败才执行 cmd2。

ping -c1 8.8.8.8 && echo 通 || echo 断

||典型用途

守卫式执行:[ -f /tmp/x ] || { echo 缺失; exit 1; }

串联初始化:cd /opt && make && make install

兜底默认值:VAL=${VAL:-default}

关键认知:把 && / || 当成「if 的行内版」,能让脚本短一半。但分支超过三层就改用 if——可读性比省行数重要得多。
实战|磁盘告警脚本

完整示例:磁盘空间告警

把「取值 → 提取数字 → 多级判断 → 输出结论」串成一条完整链路,是运维脚本最常见的形态。

student@centos7: ~ — disk_alert.sh
#!/bin/bash # 提取根分区使用率并去掉 % 号 $ usage=$(df -h / | awk 'NR==2 {print $5}' | tr -d '%') $ if [ "$usage" -gt 80 ]; then $ echo "警告: 根分区使用率 $usage%, 超过阈值 80%" $ elif [ "$usage" -gt 60 ]; then $ echo "注意: 根分区使用率 $usage%" $ else $ echo "正常: 根分区使用率 $usage%" $ fi

脚本结构拆解

  • 取值:df -h / 拿到磁盘表
  • 提取:awk 'NR==2 {print $5}' 取第 2 行第 5 列
  • 清洗:tr -d '%' 去掉百分号,得到纯数字
  • 判断:-gt 做整数比较,elif 承接中间档
  • 输出:双引号包裹变量,把结论打印出来
注意:条件判断中的变量务必加双引号:[ "$usage" -gt 80 ]。若 usage 取值为空,写成 [ $usage -gt 80 ] 会直接抛语法错误,而不是安静地跳过。
课后作业多条件判断脚本:服务状态检测约 25 分钟 · 覆盖本课 6 个知识点 · 每步均有验证
  1. 在 ~/stu序号_姓名拼音/l22/ 新建 check_序号.sh,用 df -h / 取出根分区使用率并去掉 %。验证:echo $usage 输出纯数字。
  2. 用 if 判断:使用率超过 80 输出「警告」,60—80 输出「注意」,否则输出「正常」。验证:三条分支各能用 usage 赋值分别触发一次。
  3. 加入文件存在判断:[ -f /etc/fstab ],并用 && || 写出一行短路版本。验证:两种写法输出结果一致。
  4. 加入服务状态判断:用 systemctl is-active sshd 取状态,判断为 active 时输出「服务运行中」,否则提示「服务未运行(注意:第 19 课学过 firewall,这里只查服务本身)」。验证:本机 sshd 为 active 时输出正确。
  5. 为脚本加 exit 0 与 exit 1 两种退出码(正常 0、异常 1),用 bash -x 跑一次。验证:能指出哪一步决定了最终退出码。
提交:脚本全文截图 + 三档使用率分别触发的输出截图 + bash -x 调试输出截图(文件名带序号,脚本内注释写明你的思路)。
03

循环与批量处理

Loops & Batch Processing
第 23 次课2 学时理实一体
重点for / while / until 三种循环、break / continue 控制语句、文件批量处理与逐行读取。
难点循环边界控制、输入读取方式选择,以及空变量导致的批量误操作风险。

核心方法:先用小数据集(如 3 个文件名)跟踪变量变化,确认循环体逻辑正确后再扩大数据量并加入错误分支。

教学目标
01循环选型:知道 for 遍历列表、while 条件为真、until 条件为假时循环。
02控制语句:会用 break 跳出循环、continue 跳过本次。
03文件批处理:能用通配符与 while read 遍历文件、逐行处理。
04安全边界:掌握空变量防护与 dry-run 演练习惯。
思考题:如何避免批处理脚本无限循环?作业:编写批量文件处理或巡检脚本
三种循环|选对结构再写代码

for / while / until

循环的三种形态对应三种场景:次数或对象已知用 for,次数未知用 while,条件反转用 until。

forfor 循环

遍历已知列表或范围。

for i in 1 2 3; do
  echo $i
done

也可遍历文件、目录、命令输出

whilewhile 循环

条件为真时循环,次数由条件决定。

while [ $i -lt 10 ]; do
  i=$((i+1))
done

必须保证条件最终会变假

untiluntil 循环

条件为假时循环,用法与 while 相反。

until [ -f /tmp/flag ]; do
  sleep 5
done

典型用途:等待某条件成立

语句功能示例说明
for i in {1..5}C 风格范围循环for i in {1..5}; do echo $i; donebash 4+ 支持
for i in $(seq 1 5)seq 生成序列for i in $(seq 1 5); do echo $i; done兼容旧 Shell,等价于 {1..5}
for f in *.txt遍历文件for f in *.txt; do cat $f; done通配符匹配当前目录
while read line逐行读取文件while read line; do echo $line; done < file适合日志处理
break跳出当前循环if [ $i -eq 5 ]; then break; fi退出整个循环
continue跳过本次迭代if [ $i -eq 3 ]; then continue; fi进入下一次迭代
注意:循环体内操作文件前,先确认变量非空:rm $f 在 f 为空时等于 rm(不带参数会报错,但写成 rm -rf $f/ 就会作用到当前目录)。规范写法是 rm -rf -- "$f"/ 并且循环前先判断 [ -n "$f" ]。
控制语句|break 与 continue

提前退出与跳过

两个关键字解决两类问题:找到答案就走(break)与 这条不合格、下一条继续(continue)。

×break

立即跳出整个循环。

for f in *.log; do
  if grep -q ERROR $f; then
    echo $f
    break
  fi
done

典型场景:找到第一个匹配就停

→continue

跳过本次迭代,进入下一轮。

for u in $users; do
  id $u &>/dev/null || continue
  echo "新建 $u"
done

典型场景:跳过已存在、无需处理的项

关键认知:break 只跳出当前一层循环。若嵌套循环里想连外层一起退出,需要配合标志变量(如 found=1; break)或把内层写成函数用 return。
逐行读取|while read 是日志处理标配

for $(cat file) 与 while read 的取舍

两者都能读文件,但处理含空格的行时结果完全不同——这是批量脚本最常见的翻车点。

student@centos7: ~ — 两种读法对比
# userlist.txt 内容: # zhangsan # li si # 写法一:for + 命令替换(按空格拆分会出错) $ for user in $(cat userlist.txt); do $ echo "[$user]" $ done [zhangsan] [li] [si] # 写法二:while read(按整行读取,正确) $ while read -r user; do $ echo "[$user]" $ done < userlist.txt [zhangsan] [li si]

为什么用 while read

for user in $(cat f) 会先做分词,含空格的一行被拆成多个变量。

while read -r user 逐行读取,整行作为一个变量。

-r 表示反斜杠不转义(不加时 \n 会被吃掉)。

结论:处理含空格的行一律用 while read。

注意:两个高频坑:① 循环体内的 read 会吃掉标准输入,导致外层 while read 只读到第一行——解决:在循环内用 read ... </dev/null 或改用 <> /dev/null;② 文件末尾没有换行符时,最后一行会被跳过——解决:循环前加 { cat f; echo; } | while read ...。
实战|批量创建用户

带幂等性的批量脚本

好的批处理脚本要能重复运行而不产生副作用——这就是幂等性。运维脚本尤其需要。

student@centos7: ~ — batch_user.sh
#!/bin/bash # 从 userlist.txt 读取用户名并创建 $ while read -r user; do $ [ -z "$user" ] && continue $ if id "$user" >/dev/null 2>&1; then $ echo "$user 已存在, 跳过" $ else $ useradd -m "$user" $ echo "$user 创建成功" $ fi $ done < userlist.txt

关键设计点

  • 幂等:先 id "$user" 查存在,重复执行不会重复建号
  • 空行跳过:[ -z "$user" ] && continue 挡掉空行与空变量
  • 静默错误:>/dev/null 2>&1 隐藏 id 的报错输出
  • 逐行读取:while read -r user 处理含空格的用户名
  • 自动建家目录:useradd -m 显式创建 home 目录
注意:批量脚本执行前必须确认身份:whoami 看看是不是 root。用普通用户执行 useradd 会因权限不足逐条失败,而脚本本身不报错退出——这正是「每步都要验证退出码」的意义。
关键认知:演练技巧:把 useradd -m "$user" 换成 echo '[dry-run] useradd' "$user" 先跑一遍,确认要处理的用户列表完全正确,再去掉 echo 真正执行。先演练后执行是批量操作的铁律。
课后作业批量处理脚本:批量改名与巡检约 30 分钟 · 覆盖本课 6 个知识点 · 每步均有验证
  1. 在 ~/stu序号_姓名拼音/l23/ 新建 batch_序号.sh,用 for 遍历目录下 *.txt 文件,逐个打印文件名与大小。验证:输出条目数与 ls *.txt | wc -l 一致。
  2. 脚本中加入 continue:跳过大小为 0 的空文件,并统计跳过数量。验证:人为创建一个空文件,脚本能识别并报告跳过。
  3. 用 while read -r 读取 names_序号.txt(内容含至少一个带空格的名字),逐行打印。验证:带空格的名字完整输出,未被拆成两行。
  4. 写一个 if 分支:若某项以 ERROR 开头则用 continue 跳过,否则输出到 report_序号.txt。验证:报告文件中不含被跳过的条目。
  5. 先用 echo '[dry-run] ...' 演练整个循环,确认无误后再去掉 echo 真正执行。验证:dry-run 输出与真实执行的条目清单完全一致。
提交:脚本全文截图 + dry-run 输出与真实执行输出对照截图 + report_序号.txt 内容截图(文件名带序号)。
04

函数与模块化

Functions & Modularity
第 24 次课2 学时理实一体
重点函数定义与调用、位置参数、返回值、以及用 source 拆分公共脚本实现模块化。
难点复杂任务的职责拆分,以及函数之间如何传递数据——函数之间隐式依赖是脚本失控的根源。

核心方法:先画任务分解图,把一个长脚本拆成 3—5 个单一职责的函数;再逐个函数单独测试,最后组合运行。

教学目标
01函数:会用 function name() { ... } 或 name() { ... } 定义并调用函数。
02参数:掌握 $1 $2 $# $@ 的含义,知道如何给函数传参。
03返回值:理解 return 只能返回 0—255 整数,大数据要用 stdout + 命令替换。
04模块化:用 source 把公共函数抽到 lib.sh,主脚本引入。
思考题:函数化如何提升脚本可维护性?作业:提交函数化 Shell 脚本及测试结果
核心机制|把重复逻辑封装起来

函数:脚本的复用单元

函数的价值不只是「少写几行」,而是给一段逻辑起名字。巡检脚本里 check_disk 比一串 df 管道更能让读的人秒懂意图。

fn函数定义

两种写法等价:

function name() { ... }
name() { ... }

第二种是 POSIX 风格,更通用

()定义与调用

先定义后调用,顺序不能颠倒。

greet() { echo hi; }
greet

调用时不写括号

loc局部与全局

函数内定义的变量默认是全局的。

local tmp=1

用 local 限定作用域,防污染

ret退出即返回

函数执行到 return 立刻结束,并返回退出码。

没有 return 时,返回最后一条命令的状态

关键认知:函数名用动词开头(check_disk / backup_db / send_alert)能直接表达职责。运维团队里这个约定几乎是共识,也是脚本可读性的关键。
数据传递|参数进、结果出

位置参数与返回值

函数和脚本的参数机制是同一套——Shell 把命令行参数暴露成 $1 $2 ...,这让函数天然可测试。

$1位置参数

$1 第一个参数,$2 第二个

$# 参数个数

"$@" 所有参数(每个保持独立)

"$*" 所有参数(合成一个)

ret返回值

return N 只能返回 0—255 的整数。

约定:0 成功,非 0 为各类错误码

需要返回数据(如一行文本)→ 用 stdout + $( )

loc局部变量

写函数时把临时变量声明为 local。

local usage=$(df -h / | awk 'NR==2{print $5}')

避免与主脚本变量重名互相覆盖

写法含义示例注意
$1 / $2第 1、第 2 个位置参数backup() { echo $1; }超过 9 个要用 ${10}
$#参数个数if [ $# -lt 2 ]; then echo 用法; exit 1; fi必做参数校验
$@所有参数(各自独立)for f in "$@"; do echo $f; done遍历参数用这个
$*所有参数(合成一个)echo "$*"含空格参数会粘连
return N返回退出码check() { [ -f x ] && return 0; return 1; }仅 0—255 整数
local var函数内局部变量local tmp=$1防变量污染
注意:最常见的函数化错误:把数据用 stdout 打印出来,又用 return 返回值,两者混用导致调用处拿到一堆多余文本。约定要统一——要么用 stdout 传数据,要么用 return 传状态,不要混。
模块化|source 引入公共脚本

把公共函数抽到 lib.sh

当多个脚本都要用同一批函数时,就该把它抽出来。source 让公共脚本在当前 Shell 中执行,函数随之可见。

student@centos7: ~ — lib.sh 与主脚本
# ===== lib.sh(公共函数库) ===== $ check_disk() { $ df -h / | awk 'NR==2 {print "DISK:" $5}' $ } $ check_mem() { $ free -h | grep Mem $ } # ===== inspect.sh(主脚本) ===== #!/bin/bash $ source ./lib.sh $ check_disk $ check_mem

source 与 bash 的区别

source ./lib.sh:在当前 Shell 中执行,函数与变量留在当前环境,后续脚本能直接调用。

bash ./lib.sh:开子进程执行,函数定义随子进程结束而消失,主脚本调用不到。

结论:引入函数库必须用 source(或 .)。

注意:source 会把被引入脚本里的所有内容都执行一遍。所以 lib.sh 里只放函数定义,不要放直接执行语句,否则引入时就会跑一遍逻辑。可以用 if [ "${BASH_SOURCE[0]}" = "$0" ]; then main; fi 做「直接执行时才运行」的守卫。

目录规划

典型项目结构:

project/
├─ lib.sh 公共函数
├─ inspect.sh 主脚本
├─ config.conf 配置
└─ logs/ 运行日志

主脚本用 source "$(dirname "$0")/lib.sh" 引入,路径不依赖当前工作目录。

实战|把长脚本重构成函数

重构前后对照

同一份巡检逻辑,写成一个大脚本 vs 拆成若干单一职责函数——差别不在行数,在可测试性。

✗❌ 未拆分的写法

#!/bin/bash
df -h / | awk 'NR==2{print "DISK:" $5}'
free -h | grep Mem
systemctl is-active sshd
systemctl is-active firewalld

问题:

· 只能整体跑,无法单独测某一项

· 加一项要改整段

· 出错时不知道错在哪一行

✓✅ 函数化的写法

check_disk() { df -h / | awk 'NR==2{print "DISK:" $5}'; }
check_mem() { free -h | grep Mem; }
check_svc() { systemctl is-active $1; }

check_disk
check_mem
check_svc sshd

好处:

· 可单独测:check_svc sshd

· 可复用:换个服务名即可

· 出错定位到函数级

关键认知:重构方法论:先画任务分解图(每个方框一个函数,标出输入输出),再逐个函数写和测,最后组合。这套流程和第 26 课的工程化闭环完全一致。
课后作业函数化重构:巡检脚本约 30 分钟 · 覆盖本课 7 个知识点 · 每步均有验证
  1. 在 ~/stu序号_姓名拼音/l24/ 建立 lib_序号.sh,写入至少三个函数:check_disk、check_mem、check_svc(check_svc 接受服务名参数)。验证:source 后直接调用 check_svc sshd 能输出 active/inactive。
  2. 新建主脚本 inspect_序号.sh,用 source "$(dirname "$0")/lib_序号.sh" 引入函数库并依次调用。验证:主脚本输出包含磁盘、内存、sshd 服务三项结果。
  3. 把磁盘检查函数改为接受阈值参数(如 check_disk 80),超过阈值时输出告警文字。验证:传入不同阈值得到不同结论。
  4. 对函数做参数校验:check_svc 未传参数时输出用法提示并 return 1。验证:不传参数调用时退出码为 1。
  5. 用 bash -x inspect_序号.sh 调试一次,并写出「本次重构拆出了哪些函数、各自职责是什么」的说明。验证:说明中每个函数职责单一、互不重叠。
提交:lib_序号.sh 与 inspect_序号.sh 全文截图 + 函数调用输出截图 + -x 调试输出截图 + 一段函数职责说明(可写在脚本注释里)。
05

数组与批量数据

Arrays & Bulk Data
第 25 次课2 学时理实一体
重点Shell 数组的定义、索引、遍历、长度、追加与删除;用数组处理成组的批量数据。
难点空数组、越界索引与含空格元素的处理——数组越界不会报错,只会静默返回空值。

核心方法:用边界数据驱动测试:空数组、单元素数组、含空格元素、越界索引,每组都记录预期与实际结果。

教学目标
01数组操作:掌握定义、取值、取长度、追加与删除五种基本操作。
02安全遍历:会用 "${arr[@]}" 加引号遍历,正确处理含空格元素。
03边界处理:能识别空数组、越界索引,并写出防御式检查。
04工程习惯:养成先检查后使用的顺序,脚本不因异常数据静默出错。
思考题:数组为空时脚本应如何处理?作业:编写数组处理脚本并提交测试记录
数组基础|从一维列表开始

数组的五种基本操作

Shell 数组下标从 0 开始。它擅长表达「一组同类项」,但不擅长表达「键值对」——关联数组需要额外声明。

()定义

arr=(web db cache)

圆括号内用空格分隔

元素含空格要加引号

[ ]取值

echo ${arr[0]}
echo ${arr[@]}
echo ${#arr[@]}

下标从 0 开始

+=追加

arr+=(nginx)

在末尾追加一个元素

不会覆盖原数组

−删除

unset arr[1]

删除指定下标的元素

不会重新排序

语法功能示例注意
arr=(a b c)定义数组arr=(web db cache)圆括号内空格分隔
${arr[0]}取第 1 个元素echo ${arr[0]}索引从 0 开始
${arr[@]}取所有元素for i in "${arr[@]}"; do echo $i; done@ 保留元素完整性
${#arr[@]}数组长度echo ${#arr[@]}元素个数
arr+=(d)追加元素arr+=(nginx)在末尾追加
unset arr[1]删除元素unset arr[1]删除指定索引,不重排
关键认知:需要「键 → 值」映射时用关联数组:declare -A conf 然后 conf[ip]=192.168.1.10,取值 ${conf[ip]}。普通数组下标是数字,关联数组下标是字符串,两者语法相似但别混用。
安全遍历|引号是必选项

遍历数组的正确姿势

数组遍历只有一种推荐写法:for i in "${arr[@]}。少了双引号,含空格的元素就会被拆散——而且不报错。

student@centos7: ~ — 引号的影响
#!/bin/bash $ arr=('web server' db cache) # 正确:加双引号,元素保持完整 $ for i in "${arr[@]}"; do $ echo "[$i]" $ done [web server] [db] [cache] # 错误:漏了双引号,被按空格拆开 $ for i in ${arr[@]}; do $ echo "[$i]" $ done [web] [server] [db] [cache]

两条铁律

① 遍历一律加双引号:"${arr[@]}"。不加引号时 Shell 会做分词与通配符展开,含空格或 * 的元素必然出错。

② 下标越界不报错:echo ${arr[99]} 静默输出空行。只有 ${arr[99]:-未设置} 这类带默认值的写法才能识别。

这正是「数组越界是难点」的根源——它不抛异常,只是给你空值。

注意:空数组时 "${arr[@]}" 会展开成零个参数(不是空字符串),所以循环体一次都不执行——这通常正是你想要的。但如果你写成 "${arr[*]}",它会展开成一个空字符串参数,循环体执行一次并拿到空值。遍历用 @,不用 *。
边界处理|空、越界、含空格

三类必须显式处理的边界

数组相关的 bug 很少来自逻辑错误,更多来自「数据不是我以为的那样」。防御式检查要在使用之前,而不是出错之后。

0空数组

检查长度:[ ${#arr[@]} -eq 0 ]

或直接判断:[ -z "${arr[*]}" ]

空数组时不要执行遍历和删除

?越界索引

带默认值取值:

${arr[9]:-未设置}

取值前先校验下标范围

不要依赖「不报错」当作「没问题」

␣含空格元素

定义时加引号:arr=('a b' c)

遍历时加引号:"${arr[@]}"

输出时加引号:echo "$i"

关键认知:防御式三步法:① 先检查长度 → ② 再取带默认值的元素 → ③ 最后才执行操作。把这三步写进脚本开头,能挡掉绝大多数数组相关事故。
实战|用数组管理批量目标

批量目标清单脚本

运维中常见需求:对一组目标做同样的事——检查服务、采集指标、批量配置。把目标放进数组,脚本逻辑就与具体目标解耦了。

student@centos7: ~ — batch_check.sh
#!/bin/bash # 用数组管理待检查的服务清单 $ services=('sshd' 'firewalld' 'chronyd') # 边界一:数组为空则直接退出 $ if [ ${#services[@]} -eq 0 ]; then $ echo "服务清单为空, 退出" $ exit 1 $ fi # 边界二:遍历时加双引号 $ for svc in "${services[@]}"; do $ state=$(systemctl is-active "$svc") $ if [ "$state" = "active" ]; then $ echo "[OK] $svc -> $state" $ else $ echo "[FAIL] $svc -> $state" $ fi $ done

脚本设计要点

  • 数据与逻辑分离:改目标只改数组,不改循环体
  • 空数组守卫:长度检查 + 明确退出码
  • 引号齐全:"${services[@]}"、"$svc"、"$state"
  • 统一输出格式:[OK] / [FAIL] 前缀,便于 grep 统计
  • 可扩展:新增服务只需往数组里加一项
注意:扩展提示:不要把结果只 print 出来。改成 echo "..." >> result_序号.txt 追加到文件,再用 grep -c '^\[FAIL\]' 统计失败数——这就从「打印脚本」升级成了「可被其他脚本调用的巡检工具」,也是第 26 课工程化的起点。
课后作业数组处理脚本与边界测试约 30 分钟 · 覆盖本课 7 个知识点 · 每步均有验证
  1. 在 ~/stu序号_姓名拼音/l25/ 新建 array_序号.sh,定义一个至少含 3 个元素的数组,其中必须有一个元素含空格(如 'web server')。验证:打印数组长度与每个元素,含空格元素完整输出。
  2. 用 "${arr[@]}" 遍历输出,并故意写一版漏引号的版本对比。验证:能指出漏引号版本把一个元素拆成了两个。
  3. 追加两个元素、删除其中一个,用 ${#arr[@]} 验证长度变化。验证:长度数字与预期一致,删除后其余元素顺序不变。
  4. 加入空数组守卫:把数组临时置空,脚本应输出提示并以退出码 1 结束。验证:空数组场景下有明确提示且 echo $? 为 1。
  5. 做越界访问测试:访问一个不存在的下标,用 ${arr[99]:-未设置} 取得默认值。验证:输出「未设置」而非空白。
  6. 写一份测试记录表(可用表格),记录每组输入的「预期结果 / 实际结果 / 是否通过」。验证:表中至少覆盖 4 组边界数据。
提交:脚本全文截图 + 含空格元素的对比输出截图(正确版 vs 漏引号版)+ 空数组与越界测试截图 + 测试记录表截图(文件名带序号)。
06

综合脚本工程化

Production-Grade Scripting
第 26 次课2 学时理实一体
重点需求拆解 → 编码 → 调试 → 验证 → 文档化的完整闭环;输出格式统一、带时间戳、可被其他工具消费。
难点多知识点整合与调试;把「能跑」变成「敢交给别人跑」——健壮性与规范性是分水岭。

核心方法:用项目任务单组织工作:先写清需求与验收标准,再编码;用 bash -x 与分段验证调试;最后按「功能 / 健壮性 / 规范性」三条验收。

教学目标
01需求拆解:能把一个运维需求拆成若干单一职责的检查项,并写清验收标准。
02脚本实现:综合运用函数、循环、条件、数组与输出重定向。
03健壮性:处理命令失败、空值、依赖缺失等异常,给出明确退出码。
04交付规范:脚本、日志与使用说明三件套齐全。
思考题:运维脚本交付应包含哪些说明?作业:提交 Shell 运维脚本、运行结果和使用说明
工程起点|先拆需求,再写代码

从需求到脚本的五步闭环

学生 常见的失败模式是「先写代码再想干什么」。运维脚本一旦要长期运行,方向错了维护成本极高。

1① 拆需求

把「做一次巡检」拆成具体检查项:

磁盘 / 内存 / 关键服务 / 端口

每项一个函数,职责单一

2② 定标准

每项的合格线是什么?

磁盘 > 80% 告警
sshd 非 active 告警

没有标准,脚本无法自动判定

3③ 编码调试

函数化实现,bash -x 逐段验证

先用小范围试,再上真实数据

4④ 输出归档

结果写入带时间戳的文件

report_2026-10-26.log

格式统一,可 grep

5⑤ 文档说明

用途 / 用法 / 依赖 / 退出码 / 示例输出

让别人能直接接手使用

6⑥ 定时执行

crontab -e 每日自动跑

0 8 * * * /opt/inspect.sh

输出邮件或告警

关键认知:第 26 课是本模块的收口课:前五课的变量、条件、循环、函数、数组,在这里全部用上。写不出这个脚本,就说明前面每个知识点都还停留在「会用」而不是「会组合」。
实战|系统巡检脚本

sysins.sh:一份可交付的巡检脚本

注意两个设计细节:报告文件名带日期(每天不覆盖)、每个检查项独立成函数(单独可测)。

student@centos7: ~ — sysins.sh
#!/bin/bash # 系统巡检脚本 $ report=/tmp/inspection.$(date +%F).log $ check_disk() { $ df -h / | awk 'NR==2{print "DISK:" $5}' $ } $ check_mem() { $ free -h | grep Mem $ } $ check_svc() { $ systemctl is-active sshd $ } $ { $ echo '巡检开始: $(date)' $ check_disk $ check_mem $ check_svc $ echo '巡检结束: $(date)' $ } > $report $ cat $report

设计要点

  • 函数化:每个函数只做一件事,可单独调用测试
  • 时间戳:$(date +%F) 让报告按天归档,不互相覆盖
  • 重定向:{ ... } > $report 把整块输出写入文件
  • 统一格式:DISK: 前缀便于后续 grep / 报表
  • 服务检查:systemctl is-active 是判断服务状态的标准做法
注意:echo '巡检开始: $(date)' 用的是单引号,所以 $(date) 不会展开,日志里会原样打印这串字符。要输出真实时间必须用双引号:echo "巡检开始: $(date)"。这是单引号与双引号混用最典型的一次翻车。
工程规范|从能跑到敢交付

健壮性四条与规范清单

同样的功能,两个脚本可能一个只能自己用,一个能进运维手册。差别在下面这些细节。

健壮性四条

  • ① 依赖检查:运行前确认 df、awk 等命令存在
  • ② 失败可见:关键命令加 set -e 或显式判 $?,不让错误静默
  • ③ 空值防护:命令替换结果先判非空再参与比较
  • ④ 明确退出码:0 成功、1 业务异常、2 用法错误

#脚本头部注释

#!/bin/bash
# ====================================
# 名称: 系统巡检脚本
# 作者: stu序号_姓名拼音
# 版本: v1.0
# 日期: 2026-10-26
# 用法: bash sysins.sh [阈值]
# 依赖: df / free / systemctl
# 退出码: 0 正常 1 超阈值 2 用法错误
# ====================================

交付三件套

  • ① 脚本本体:含头部注释、只读权限之外的合理权限
  • ② 运行结果:一次真实运行的完整输出(含正常与异常分支)
  • ③ 使用说明:用途、依赖、参数、退出码、常见问题
关键认知:规范不是束缚,是降低他人接手成本。头部注释写全,半年后你自己回来改也省一半时间。
验收|三条标准逐项过

交付前的三关验收

教师验收与同伴互测都用同一张表——功能是否达成、异常是否可控、代码是否规范。

验收维度检查项合格标准不合格的典型表现
功能需求覆盖拆解出的每个检查项都有输出漏项、某项恒为空
功能结果正确与人工执行命令的输出一致数值错位、单位混乱
健壮性异常分支命令失败、文件缺失时有明确提示静默跳过、输出半截
健壮性退出码正常 0、异常非 0 且含义稳定永远返回 0
健壮性空值防护变量引用前判非空[ $var -gt 0 ] 直接崩
规范性注释与命名头部注释完整、函数名见名知意无注释、命名随意
规范性可复现他人按说明能跑出同样结果依赖本地私有环境
关键认知:同伴互测是最有效的检验方式:让同学照着你的使用说明跑一遍,他卡住的每一步都是你文档的漏洞。
课后作业系统巡检脚本(工程化交付)约 40 分钟 · 覆盖本课 8 个知识点 · 每步均有验证
  1. 在 ~/stu序号_姓名拼音/l26/ 建立目录 inspect_序号/,在其中创建 lib_序号.sh(公共函数)与 sysins_序号.sh(主脚本)。验证:主脚本通过 source 引入函数库并能独立运行。
  2. 实现至少四个检查项:根分区使用率、内存使用量、sshd 服务状态、监听端口数,每个一个函数。验证:报告中四项结果齐全且格式统一(带前缀)。
  3. 为磁盘检查加入阈值告警(阈值可由命令行参数传入,默认 80)。验证:分别传入 50 与 95 得到「正常」与「警告」两种结论。
  4. 把结果写入 report_序号_$(date +%F).log,并在脚本末尾用 grep -c 统计超阈值项数。验证:统计数字与报告内容一致。
  5. 做异常测试:临时把 check_svc 里的服务名改成不存在的名字,确认脚本有明确提示且退出码非 0。验证:异常场景不静默通过。
  6. 编写 README_序号.md(或同目录 README.txt),写明用途、依赖、用法、退出码含义、常见问题。验证:说明中包含一条可直接复制的运行命令。
提交:lib 与主脚本全文截图 + 正常阈值与告警阈值两次运行输出截图 + 异常场景输出与退出码截图 + README 说明截图(所有文件名前缀带序号;报告文件名日期部分保留真实日期)。
07

Docker 概念与安装

Docker Concepts & Install
第 27 次课2 学时理实一体
重点镜像、容器、仓库三大概念与生命周期;Docker 的安装配置与 hello-world 环境验证。
难点容器与虚拟机的本质差异——容器共享宿主机内核,不携带完整操作系统,这是所有容器特性的根源。

核心方法:用生命周期图示串起概念:pull → run → stop → rm;安装后依次检查版本、服务状态、测试容器三项。

教学目标
01核心概念:准确区分镜像(模板)、容器(运行实例)、仓库(存储分发)。
02差异理解:说清容器与虚拟机的核心差异,建立容器化思维。
03生命周期:记住 pull → run → stop → rm 与各状态之间的关系。
04环境验证:完成 Docker 安装,并跑通 hello-world。
思考题:镜像和容器的关系是什么?作业:安装 Docker 并运行 hello-world
核心概念|模板、实例、仓库

镜像、容器、仓库

这三个概念是 Docker 的全部地基。类比编程语言最容易记:镜像像类,容器像对象,仓库像包管理器。

IMG镜像 Image

只读模板,包含应用与运行环境。

docker images

可复用、可分层(共享相同层,节省空间)

类比:类(class)

CNT容器 Container

镜像的运行实例。

docker ps

可创建、启停、删除,有自己的文件系统与进程

类比:对象(object)

REG仓库 Registry

镜像的集中存储与分发。

docker pull nginx

公共 Docker Hub / 私有 Harbor

类比:npm / Maven 仓库

关键认知:一句话记住关系:镜像是模板,容器是模板造出来的实例,仓库是模板的存放处。删掉容器镜像还在(可再次 run),删掉镜像(若被引用)则要先删容器。
思维转变|容器不是小虚拟机

容器与虚拟机的本质差异

这是本课真正的难点。虚拟机虚拟的是硬件,每个虚拟机跑完整的操作系统内核;容器虚拟的是操作系统,所有容器共享宿主机内核。

虚拟机 Virtual Machine

  • 虚拟化层级:硬件层(Hypervisor 虚拟出 CPU/内存/磁盘)
  • 内核:每个虚拟机有完整独立的操作系统内核
  • 体积:完整系统镜像,通常GB 级
  • 启动时间:开机加载内核,分钟级
  • 隔离强度:强,硬件级隔离
  • 代表:VMware、KVM、Hyper-V

容器 Container

  • 虚拟化层级:操作系统层(命名空间隔离 + cgroups 资源限制)
  • 内核:共享宿主机内核,不含自己的 OS
  • 体积:只含应用与依赖,通常MB 级
  • 启动时间:进程级启动,秒级
  • 隔离强度:弱于虚拟机,但足够隔离进程与文件系统
  • 代表:Docker、Podman、containerd
注意:正因为共享宿主机内核,容器才有「秒级启动」「轻量」这些优势,也带来两个限制:① 不能运行不同内核的 OS(Linux 容器不能跑 Windows 内核);② 容器里不该放完整系统——只放应用和一个精简运行时。
生命周期|pull → run → stop → rm

容器的生命周期

把这条链记熟,后面所有 Docker 操作都是它的变体。

↓pull

从仓库拉取镜像到本地。

docker pull nginx:latest

本地已有则秒级完成

▶run

基于镜像创建并启动新容器。

docker run -d -p 8080:80 nginx

每次 run 都是新容器

❚❚stop / start

停止 / 再次启动容器。

docker stop web
docker start web

容器保留,数据还在

×rm / rmi

删除容器 / 删除镜像。

docker rm web
docker rmi nginx

删除容器后镜像仍在

关键认知:核心认知:run 会创建新容器,stop 保留容器可重启,rm 彻底删除容器,镜像保留可再次 run。「一次构建,到处运行」——同一个镜像可以同时跑出 N 个互相隔离的容器,这就是容器编排的起点。
实操|安装与三项验证

安装 Docker 并跑通 hello-world

安装后不要急着往下走,先做三项检查:版本、服务状态、测试容器。三项全过才算环境可用。

student@centos7: ~ — 安装与验证
# 1 · 安装 Docker $ yum install -y docker-ce $ systemctl enable --now docker # 2 · 检查一:版本 $ docker --version Docker version 24.0.7, build afdd53b # 3 · 检查二:服务状态 $ systemctl is-active docker active # 4 · 检查三:测试容器 $ docker run hello-world Hello from Docker! This message shows that your installation works.

三项验证清单

  • 版本:docker --version 能输出版本号
  • 服务:systemctl is-active docker 返回 active
  • 容器:docker run hello-world 打印欢迎信息
注意:systemctl enable --now docker 中的 --now 表示「同时启用开机自启并立即启动」,等价于 enable + start 两条命令。忘记 enable 会导致重启后 Docker 不会自启——这类问题在虚拟机课程里最常见。
课后作业Docker 安装与 hello-world 验证约 25 分钟 · 覆盖本课 5 个知识点 · 每步均有验证
  1. 安装 Docker 并设置开机自启:systemctl enable --now docker。验证:systemctl is-enabled docker 返回 enabled,is-active 返回 active。
  2. 记录版本信息:docker --version。验证:输出含 Docker version 字样,把版本号记入报告。
  3. 运行测试容器 docker run hello-world。验证:输出 Hello from Docker!。
  4. 用 docker images 查看本地镜像,用 docker ps -a 查看容器(hello-world 容器已退出但仍保留)。验证:能看到 hello-world 镜像与一个 Exited 状态的容器。
  5. 删除该测试容器 docker rm <容器ID>,再查 docker ps -a。验证:容器列表为空,但镜像仍存在。
提交:hello-world 运行成功的终端截图 + docker --version 截图 + docker images 与 docker ps -a 截图(截图中需能看到镜像与容器状态)。
08

容器生命周期与 Nginx 容器

Container Lifecycle
第 28 次课2 学时理实一体
重点docker pull/run/ps/stop/rm/exec 的完整用法;用 Docker 运行 Nginx 并完成端口映射与访问验证。
难点容器生命周期状态的流转逻辑,以及「容器在跑但访问不通」的排查路径。

核心方法:用容器状态表跟踪每条命令前后的状态变化;访问验证采用浏览器与 curl 双重确认。

教学目标
01命令掌握:熟练使用 pull / run / ps / stop / rm / exec。
02端口映射:理解 -p 宿主端口:容器端口 的方向,能验证映射是否生效。
03状态跟踪:能说清每个命令执行后容器状态如何变化。
04双重验证:浏览器与 curl 两种方式都能确认服务可访问。
思考题:停止容器与删除容器有何区别?作业:完成 Nginx 容器运行、停止、删除和访问验证
命令速查|六个命令管住容器

Docker 常用命令

掌握这六个命令,就能完成绝大多数容器操作任务。

命令功能示例说明
docker pull拉取镜像docker pull nginx:latest从仓库下载镜像
docker images列出本地镜像docker images查看已下载的镜像
docker run运行容器docker run -d -p 80:80 nginx-d 后台运行,-p 端口映射
docker ps查看运行中容器docker ps -a-a 包含已停止的容器
docker stop / start停止 / 启动容器docker stop web停止后容器保留,数据不丢
docker rm / rmi删除容器 / 镜像docker rm web先 stop 再 rm;rmi 需无容器引用
docker exec进入容器执行命令docker exec -it web bash-it 分配交互式终端
docker logs查看容器日志docker logs -f web排错第一手段
注意:docker rm 只能删除已停止的容器,运行中的容器删不掉(会报「cannot remove container: container is running」)。标准流程是 docker stop web && docker rm web。这与「先 stop 再 rm」的 Docker 通用惯例一致。
关键机制|端口映射

端口映射:让外部能访问容器

容器有自己独立的网络命名空间,docker run 不加 -p 时,外部主机无法通过浏览器访问容器里的服务。

→映射方向

-p 8080:80 的含义是:

宿主端口 8080 → 容器端口 80

顺序不能反!写反了浏览器就访问不到。

docker run -d -p 8080:80 nginx

浏览器访问 localhost:8080

⇄多个端口

可以同时映射多个:

docker run -d -p 8080:80 -p 8443:443 nginx

也可以指定绑定地址:

-p 127.0.0.1:8080:80

只允许本机访问,更安全

关键认知:排查「容器在跑但访问不通」的标准顺序:① 容器状态是否 Up → ② 映射端口是否正确(docker ps 看 PORTS 列)→ ③ 容器内服务是否监听(docker exec web ss -tlnp)→ ④ 防火墙是否放行(firewall-cmd --list-ports,第 19 课内容)→ ⑤ 本机 curl 宿主端口。
方法论|用状态表跟踪命令

容器状态跟踪表

把每条命令执行前后容器状态与镜像是否保留记录成表,就不容易把 stop 和 rm 搞混。

命令容器状态镜像是否保留数据是否保留可再次 run 吗
docker pull nginx无容器新增 nginx 镜像—可以
docker run -d -p 8080:80 nginxUp(运行中)保留容器内数据在可以(另建新容器)
docker stop webExited(已退出)保留<b>保留</b>(容器还在)可以(docker start web)
docker start webUp(重新启动)保留保留可以
docker rm web容器被删除保留<b>丢失</b>可以
docker rmi nginx—<b>镜像被删除</b>—需重新 pull
关键认知:这张表回答了作业思考题:停止容器与删除容器的区别——停止只是让容器进程退出,容器本身还在、数据还在、可以用 docker start 复活;删除是把容器对象销毁,容器内数据一并消失。
实操|用 Docker 运行 Nginx

完整流程:安装 → 运行 → 验证 → 清理

这一节是第 28 课的核心练习,也是作业的主体。注意最后一步必须做——只跑起来不清理,作业不算完成。

student@centos7: ~ — Docker 运行 Nginx
# 1 · 安装 Docker $ yum install -y docker-ce $ systemctl enable --now docker # 2 · 拉取并运行 Nginx $ docker pull nginx:latest $ docker run -d --name web -p 8080:80 nginx # 3 · 验证 $ docker ps CONTAINER ID IMAGE STATUS PORTS web nginx:latest Up 2 minutes 0.0.0.0:8080->80/tcp $ curl http://localhost:8080 <h1>Welcome to nginx!</h1> # 4 · 停止与清理 $ docker stop web && docker rm web $ docker ps -a (空列表)

验证清单

  • 服务已启动:systemctl is-active docker = active
  • 镜像已拉取:docker images 能看到 nginx
  • 容器为 Up:docker ps 显示 web 为 Up
  • 访问有返回:curl localhost:8080 返回欢迎页
  • 清理彻底:docker ps -a 为空
注意:用容器名而不是容器 ID 管理容器,脚本可读性会好很多:docker run -d --name web 之后所有命令都用 web。端口如果被占用,docker run 会报 port is already allocated——换端口或先停掉占用者。
课后作业Nginx 容器运行、停止、删除与访问验证约 30 分钟 · 覆盖本课 6 个知识点 · 每步均有验证
  1. 拉取镜像:docker pull nginx:latest。验证:docker images 中出现 nginx 镜像。
  2. 运行容器,容器名为 web序号,端口映射为 -p 80+序号:80(用你自己的端口,避免冲突)。验证:docker ps 显示状态 Up 且 PORTS 列映射正确。
  3. 用 curl http://localhost:端口 验证,并再用浏览器访问同一地址截图。验证:两种方式都能看到 Nginx 欢迎页。
  4. 用 docker exec -it web序号 bash 进入容器,查看容器内的进程与端口监听(ss -tlnp),再 exit 退出。验证:截图中能看到容器内的监听信息。
  5. docker stop web序号 后查 docker ps -a,确认状态变为 Exited 且容器仍在。验证:此时 curl 应连接失败(证明停止确实生效)。
  6. docker rm web序号 删除容器,再查 docker ps -a。验证:列表为空,但 docker images 中 nginx 镜像仍在。
提交:curl 返回欢迎页截图 + 浏览器访问截图 + docker ps(Up 状态)截图 + docker exec 进入容器截图 + docker ps -a(Exited 状态)截图 + 删除后空列表截图(容器名与端口均带你的序号)。
09

Nginx 安装与静态站点

Nginx & Static Site
第 29 次课2 学时理实一体
重点Nginx 安装、服务启停管理、主配置文件层级结构与静态站点发布。
难点主配置文件的层级与核心指令作用;理解「server 块」与「location」的嵌套关系。

核心方法:从最小配置开始逐项增加指令,每加一项都用 nginx -t 测试语法、用 systemctl reload 重载、再用 curl 复测。

教学目标
01服务管理:完成安装,掌握 start / stop / reload / status 与 nginx -t。
02配置结构:说清主配置 → http → server → location 的层级关系。
03指令理解:知道 listen、server_name、root、index 各管什么。
04站点发布:能发布一个静态测试页面并通过浏览器验证。
思考题:nginx -t 在部署流程中起什么作用?作业:安装 Nginx 并发布静态测试页面
服务器软件|为什么是 Nginx

Nginx 与 Web 服务

Nginx 是高性能的 HTTP 服务器与反向代理服务器,以事件驱动架构和低资源占用著称,在静态站点与反向代理场景中是主流选择。

WEB静态 Web 服务

处理 HTML、CSS、JS、图片等静态资源。

文件存在就直接返回,无需应用进程

root /var/www/html

↔反向代理

接收客户端请求并转发给后端服务。

proxy_pass http://backend

隐藏后端、统一入口

LB负载均衡

upstream 定义后端池,proxy_pass 指向它。

策略:轮询、权重、ip_hash

server 10.0.0.1;

VH虚拟主机

一个 Nginx 服务多个站点。

按 server_name 或端口区分

每个站点一个 server 块

关键认知:课程思政落点:Nginx 这类开源基础设施支撑了我国大量互联网服务。掌握并用好开源基础设施,既是对技术生态的贡献,也体现技术服务数字化的责任。
安装部署|装、启、测

安装与服务管理

Nginx 的运维动作只有一个核心要求:改配置前先测语法。

student@centos7: ~ — 安装与服务管理
# 1 · 安装 $ yum install -y nginx # 2 · 启动并设置开机自启 $ systemctl enable --now nginx $ systemctl is-active nginx active # 3 · 测试配置语法(改动前必做) $ nginx -t nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful # 4 · 重载配置(不中断服务) $ systemctl reload nginx # 5 · 验证服务 $ curl -I http://localhost HTTP/1.1 200 OK Server: nginx/1.20.1

三条命令的分工

nginx -t:只检查不加载。配置有语法错误时它会指出行号,是 reload 的前置关卡。

systemctl reload nginx:平滑重载,不断开已有连接,用户无感知。

systemctl restart nginx:完全重启,会中断正在处理的请求,只在改了监听端口等必须重启的场景使用。

注意:标准流程记牢:改配置 → nginx -t → reload → curl 复测。跳过第一步是运维事故的经典起点:语法错误的配置一旦 reload 成功,Nginx 会直接退出,所有站点同时不可用。
配置结构|四层嵌套关系

主配置文件的层级

Nginx 的配置是嵌套块结构。理解层级,就知道一条指令该写在哪个块里、作用域覆盖谁。

/etc/nginx/nginx.conf — 结构示意
user nginx; # 运行身份(全文件) worker_processes auto; # 工作进程数(全文件) $ http { # ← HTTP 全局配置 include /etc/nginx/conf.d/*.conf; server { # ← 虚拟主机(一个站点) listen 80; # 监听端口 server_name site1.local; # 匹配域名 root /var/www/site1; # 站点根目录 location / { # 路径匹配规则 index index.html; } } $ }

核心指令速记

  • listen:监听端口,listen 80;
  • server_name:匹配的域名,可写多个
  • root:站点根目录,URL 拼到它后面找文件
  • index:默认首页文件名
  • location:路径匹配块,可放多条规则
  • include:把其他文件引进来(放站点配置)
关键认知:层级记忆法:http 管全局 → server 管站点 → location 管路径。写在里层的指令覆盖外层的同名指令。这是所有配置类软件的通用套路(Apache 的 Directory、systemd 的 Unit 段也类似)。
实操|发布一个静态站点

发布静态测试页面

从最小配置开始:先让它能返回内容,再逐步加上自定义目录、首页文件。

student@centos7: ~ — 发布静态站点
# 1 · 创建站点目录与首页 $ mkdir -p /var/www/site1 $ echo '<h1>site1 ok</h1>' > /var/www/site1/index.html # 2 · 编写站点配置 $ cat > /etc/nginx/conf.d/site1.conf <<'EOF' server { listen 80; server_name site1.local; root /var/www/site1; index index.html; } EOF # 3 · 测试 → 重载 → 复测 $ nginx -t && systemctl reload nginx $ curl -H 'Host: site1.local' http://localhost <h1>site1 ok</h1>

关键点

  • 配置文件放 conf.d/:被主配置的 include 自动引入,不必改主文件
  • root 指向真实目录:目录不存在会返回 404
  • index 决定默认首页:访问 / 时自动补全
  • 用 Host 头测试:本机没配 DNS 时,用 curl -H 'Host: xxx' 模拟域名请求
  • 改完必须 reload:不重载,配置不生效
注意:若 curl 返回 404,先查两处:① root 路径写对了吗(ls -l /var/www/site1/index.html);② index 文件名匹配吗(Nginx 默认 index.html,写成 index.htm 就找不到)。
课后作业Nginx 安装与静态站点发布约 30 分钟 · 覆盖本课 6 个知识点 · 每步均有验证
  1. 安装 Nginx 并设为开机自启:systemctl enable --now nginx。验证:systemctl is-active nginx 返回 active,curl -I localhost 返回 200。
  2. 创建站点目录 /var/www/site序号,写入首页 index.html(内容含你的姓名拼音与序号)。验证:cat 该文件能看到正确内容。
  3. 编写配置文件 /etc/nginx/conf.d/site序号.conf,包含 listen、server_name site序号.local、root、index 四条指令。验证:nginx -t 返回 syntax is ok。
  4. 执行 systemctl reload nginx,用 curl -H 'Host: site序号.local' http://localhost 访问。验证:返回你在 index.html 中写入的内容。
  5. 故意写错一次配置(例如漏掉分号)并执行 nginx -t,记录报错信息与行号,然后改正。验证:能说明 nginx -t 的作用是在加载前拦截错误配置。
  6. 用浏览器访问站点(可在本地 hosts 中加 127.0.0.1 site序号.local 后访问 http://site序号.local)。验证:浏览器显示自己的页面内容。
提交:nginx -t 成功截图 + curl 返回页面内容截图 + 浏览器访问页面截图 + 故意写错时的报错信息截图(配置名与站点名均带序号)。
10

虚拟主机配置

Virtual Hosts
第 30 次课2 学时理实一体
重点server 块、server_name、站点目录与多站点访问;请求匹配规则与配置优先级。
难点理解 Nginx 的请求匹配顺序(server_name 匹配 → location 匹配),以及多站点共存时的优先级差异。

核心方法:用「域名 / 端口 → 站点目录」映射表梳理请求路径,逐站点测试并检查访问日志。

教学目标
01多站点配置:能在同一 Nginx 上配置两个及以上 server 块并分别访问。
02匹配规则:说清 server_name 精确匹配、通配符与默认服务器的关系。
03目录规划:掌握站点目录、首页文件与日志配置的对应关系。
04验证闭环:会用 curl -H 逐站点验证,并读 access log 确认命中了哪个 server。
思考题:多个 server 块如何确定匹配项?作业:配置两个测试站点并提交访问结果
原理|一个服务,多个站点

虚拟主机:按域名或端口区分站点

虚拟主机的价值是资源复用:一个 Nginx 进程、一份安装、一套运维,同时对外提供多个站点。

DNS按域名区分

最常用方式。

server_name site1.local;

每个 server 块一个域名

需要客户端能解析该域名(hosts 或 DNS)

PORT按端口区分

不需要域名也能测。

listen 8081;

listen 8082;

访问 localhost:8081 与 :8082

PATH按路径区分

同一域名下用 location 分流。

location /app/ { root ...; }

与虚拟主机的「按域名」是两种不同维度

关键认知:课程思政落点:虚拟主机体现的是资源共享与绿色部署——一套服务承载多个站点,减少服务器数量即减少资源消耗,节约资源、可持续发展是 IT 行业的责任。
核心机制|请求怎么找到站点

匹配规则与优先级

Nginx 收到请求后,先在 server 块层面匹配,命中一个后再进入其内部的 location 匹配。两层匹配,各有各的规则。

匹配方式写法示例说明
精确匹配server_name site1.local;site1.local优先级最高,完全相同才匹配
通配符前缀server_name *.example.com;*.example.com匹配子域名
正则匹配server_name ~^www\d+\.com$;带 ~ 的正则按正则表达式匹配
默认服务器listen 80 default_server;第一个 server 块未匹配任何域名时的兜底
location 精确location = /index.html { ... }带 = 的路径优先级最高的 location
location 前缀location /images/ { ... }路径前缀最长前缀优先匹配
注意:默认服务器很关键:当客户端用 IP 直接访问(如 curl http://192.168.1.10)时,Host 头是 IP 而非域名,不会命中任何 server_name,此时由 default_server 或第一个 server 块响应。测试时用 curl -H 'Host: 域名' 才能确保命中目标站点。
实操|两个站点并行

配置 site1 与 site2

每个站点一个独立配置文件放在 conf.d/ 下,互不干扰——这是最清晰、最易维护的组织方式。

student@centos7: ~ — 两个虚拟主机
# 1 · 创建两个站点目录与首页 $ mkdir -p /var/www/site1 /var/www/site2 $ echo 'site1' > /var/www/site1/index.html $ echo 'site2' > /var/www/site2/index.html # 2 · 编写 vhost.conf $ cat > /etc/nginx/conf.d/vhost.conf <<'EOF' server { listen 80; server_name site1.local; root /var/www/site1; index index.html; } server { listen 80; server_name site2.local; root /var/www/site2; index index.html; } EOF # 3 · 验证 $ nginx -t && systemctl reload nginx $ curl -H 'Host: site1.local' http://localhost site1 $ curl -H 'Host: site2.local' http://localhost site2

配置组织建议

  • 一站点一文件:site1.conf、site2.conf 分离,便于单独改、单独回滚
  • 不要直接改主配置:主配置由包管理器维护,升级会被覆盖
  • 改前备份:cp vhost.conf vhost.conf.bak
  • 改后必测:nginx -t → reload → curl 三步走
注意:cat > 文件 <<'EOF' 的引号很关键:写成 EOF(无引号)会先做变量展开,配置里的 $ 会被当成变量而吞掉。用 'EOF'(单引号)保证内容原样写入——这是写配置文件的标准姿势。
验证闭环|访问 + 日志双证据

用日志证明命中了正确的站点

页面显示对了,只能证明「返回了内容」;日志显示命中了哪个 server,才能证明「路由是对的」。这是排障思维的关键升级。

LOGaccess log 读法

默认路径:/var/log/nginx/access.log

127.0.0.1 - - [26/Oct/2026:10:00:00] "GET / HTTP/1.1" 200 612

依次是:来源 IP、时间、请求方法与路径、状态码、响应字节数

每个 server 块可单独指定 access_log 路径

✓验证三件套

① curl -H:curl -H 'Host: site1.local' http://localhost

② 浏览器:hosts 加映射后访问真实域名

③ 日志:tail -f access.log 观察请求是否落到目标站点的日志

三者一致才算验证完成

关键认知:为每个 server 块单独指定日志文件是很好的习惯:access_log /var/log/nginx/site1.access.log;。出问题时能直接定位是哪个站点的请求,与第 20 课「按证据分层排障」的思路完全一致。
课后作业双站点虚拟主机配置与验证约 30 分钟 · 覆盖本课 6 个知识点 · 每步均有验证
  1. 创建两个站点目录 /var/www/site序号a 与 site序号b,各自写入内容可区分的 index.html。验证:两个文件内容不同且都含你的序号。
  2. 编写 /etc/nginx/conf.d/vhost序号.conf,含两个 server 块,server_name 分别为 site序号a.local 与 site序号b.local。验证:nginx -t 返回 syntax is ok。
  3. systemctl reload nginx 后,分别用 curl -H 'Host: ...' 访问两个站点。验证:两次返回的内容分别对应各自的 index.html。
  4. 在本地 hosts 文件中加入两个域名映射,用浏览器分别访问。验证:浏览器能分别显示两个不同页面(截图含地址栏)。
  5. 为两个 server 块分别配置 access_log 到不同文件,tail 两个日志确认请求各自落到对应日志。验证:两个日志文件中各能查到自己的访问记录。
  6. 做一次负向测试:用 curl http://localhost(不带 Host 头)访问,观察命中了哪个站点。验证:能解释为什么默认返回的是第一个 server 块的内容。
提交:nginx -t 截图 + 两个站点 curl 输出截图 + 浏览器分别访问(含地址栏)截图 + 两个日志文件截图 + 负向测试输出截图(域名均带序号)。
11

反向代理与负载均衡

Reverse Proxy & Load Balancing
第 31 次课2 学时理实一体
重点proxy_pass、反向代理原理、upstream 负载均衡配置;综合部署的测试与排障。
难点跨组件故障定位——代理通了但返回 502,说明问题在后端而不是 Nginx;分层排障是本课的核心能力。

核心方法:画前后端拓扑图,按「配置 → 连通 → 响应」三层逐层复测,用日志证据定位每一层。

教学目标
01代理配置:能用 proxy_pass 把请求转发到后端,理解末尾斜杠的影响。
02负载均衡:能用 upstream 定义后端池并配置分发策略。
03综合部署:完成「后端服务 + Nginx 代理」的完整链路。
04分层排障:面对 502 等故障,能按配置 / 连通 / 响应三层定位并给出证据。
思考题:反向代理出现 502 时应从哪些层次排查?作业:完成反向代理/负载均衡配置,提交部署文档和排障记录
核心机制|隐藏后端,统一入口

反向代理的工作方式

客户端只知道 Nginx 的地址,Nginx 再把请求转给真正的后端服务,并把关后端的响应返回给客户端。后端的地址与拓扑对客户端完全隐藏。

→为什么用代理

① 隐藏后端:客户端只看到一个入口

② 统一入口:加认证、限流、日志都在一层做

③ 负载分担:多台后端轮询处理

④ 故障转移:后端挂了自动跳过

pp核心指令

location /api/ {
  proxy_pass http://backend;
}

proxy_pass 指向后端地址或 upstream 名

/末尾斜杠

proxy_pass http://x/(有斜杠):替换匹配到的前缀

proxy_pass http://x(无斜杠):保留原路径

这是最容易踩的坑

hdr常用扩展

proxy_set_header Host $host; 传递真实域名

proxy_set_header X-Real-IP $remote_addr; 传递真实客户端 IP

后端取不到真实 IP 就靠它

注意:proxy_pass 末尾斜杠直接决定 URL 拼接结果。请求 /api/users:proxy_pass http://backend;(无斜杠)→ 后端收到 /api/users;proxy_pass http://backend/;(有斜杠)→ 后端收到 /users。两者不等价,必须与后端约定一致。
扩展能力|upstream 后端池

负载均衡:upstream 与分发策略

把多台后端放进一个具名的池子,Nginx 按策略分发请求——这就是最基础的服务高可用与扩容手段。

student@centos7: ~ — upstream 配置
# 在 http 块内定义后端池 $ upstream backend { $ server 10.0.0.1:8080; $ server 10.0.0.2:8080; $ server 10.0.0.3:8080; $ } # 在 server 块内引用 server { listen 80; server_name proxy.local; location / { proxy_pass http://backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }

四种分发策略

  • 轮询(默认):请求依次分配,适合后端性能一致
  • 权重 weight:server 10.0.0.1:8080 weight=3; 按比例分配
  • ip_hash:同一客户端 IP 固定到同一后端,适合有会话粘性的场景
  • 最少连接 least_conn:优先给当前连接最少的后端,适合请求耗时差异大
注意:upstream 必须定义在 http 块内(与 server、location 同级),写在 server 块里会报语法错误。另外 upstream backend 里的 backend 是名字不是地址——真正的地址写在 proxy_pass http://backend; 里,两处必须一致。
综合部署|本模块的收官之作

后端服务 + Nginx 代理的完整链路

本课要完成的是一条真实可用的链路:后端服务监听一个端口,Nginx 代理到它,客户端通过 Nginx 访问。

1第一步:起后端

用 Python 起一个最简单的 HTTP 服务做后端:

cd /var/www/backend
echo 'backend is up' > index.html
python3 -m http.server 8000 &

确认:curl localhost:8000 有返回

2第二步:写代理

server {
  listen 80;
  server_name proxy.local;
  location / {
    proxy_pass http://127.0.0.1:8000;
    proxy_set_header Host $host;
  }
}

nginx -t → reload

3第三步:验证

curl -H 'Host: proxy.local' http://localhost

应返回后端的 backend is up

再在浏览器中访问同一地址

4第四步:加固

确认防火墙放行 80:

firewall-cmd --add-port=80/tcp --permanent
firewall-cmd --reload

第 19 课的内容在这里派上用场

关键认知:这条链路把本模块全部内容串了起来:Shell 脚本(启动后端、自动化验证)、Docker(把后端跑成容器)、Nginx(统一入口与代理)、防火墙(端口放行)、日志(排障证据)。这就是「智能运维」的真实含义——用工具把服务可靠地交付出去。
核心能力|分层排障

502 怎么查:配置 / 连通 / 响应三层

502 Bad Gateway 的含义是:Nginx 收到了请求,但没能从后端拿到有效响应。问题几乎必然在后端侧——但要靠证据确认,不能靠猜。

层次检查内容命令典型现象与结论
① 配置层代理配置本身是否正确nginx -t语法错误 → 配置未生效,先改配置
① 配置层上游地址与端口是否正确cat /etc/nginx/conf.d/*.conf地址写错 → 代理指向不存在的后端
② 连通层Nginx 能否连上后端curl -I http://127.0.0.1:8000连不上 → 后端没启动或端口不对
② 连通层后端是否在监听ss -tlnp | grep 8000无监听 → 后端进程挂了
② 连通层防火墙是否放行firewall-cmd --list-ports端口未放行 → 外网访问不通
③ 响应层后端自身是否报错curl http://127.0.0.1:8000/具体路径后端 500 → 代理只是如实转达
③ 响应层代理日志怎么说tail /var/log/nginx/error.logupstream timed out → 后端超时无响应
注意:排障铁律:先定位层次,再动手改。最常见的错误是在 502 时去反复改 Nginx 配置——但配置往往是对的,改了反而引入新问题。用 curl 直连后端这一招,能在 10 秒内把范围从「整条链路」缩小到「后端服务」或「代理配置」的一半。
课后作业反向代理与负载均衡部署 + 排障记录约 40 分钟 · 覆盖本课 8 个知识点 · 每步均有验证
  1. 启动一个后端服务(python3 -m http.server 80+序号,内容写入含你序号的首页),并先直连验证:curl localhost:端口 有返回。验证:后端独立可访问。
  2. 编写代理配置 /etc/nginx/conf.d/proxy序号.conf:server 块 + location / + proxy_pass 指向后端端口,并加 proxy_set_header Host $host;。验证:nginx -t 返回 ok。
  3. systemctl reload nginx 后用 curl -H 'Host: proxy序号.local' http://localhost 访问。验证:返回的是后端内容(证明代理链路打通)。
  4. 定义 upstream backend序号,把后端加入池中,用 proxy_pass http://backend序号; 引用。验证:连续访问 4 次均正常,nginx -t 无错。
  5. 制造一次 502:临时把 proxy_pass 端口改成一个未监听端口,reload 后复现 502,并抓取 error.log 证据。验证:日志中能看到 upstream 连接失败信息。
  6. 按配置 / 连通 / 响应三层逐层排查定位这次 502 的根因,写出排障记录(现象 → 逐层证据 → 根因 → 修复 → 复测结果)。验证:记录中每一层都有对应命令输出作为证据。
  7. 修复配置并 reload,复测确认恢复正常,同时确认防火墙已放行 80。验证:curl 与浏览器均正常,firewall-cmd --list-ports 含 80/tcp。
  8. 编写 DEPLOY_序号.md 部署文档:架构图(文字版)、配置说明、启动步骤、排障手册、验收结果。验证:文档含架构、配置、排障三部分,且他人可照做。
提交:直连后端与走代理的对比输出截图 + upstream 配置与 nginx -t截图 + 502 复现时的 curl 输出与 error.log截图 + 修复后复测截图 + 排障记录(三层证据齐全)与部署文档截图(文件名、端口、域名均带你的序号)。
模块小结|从脚本到服务

本模块核心回顾Summary

11 节课串成一条线:先让机器听懂你的意图(脚本、条件、循环、函数、数组),再把服务可靠地交付出去(Docker、Nginx),最后学会用证据定位问题。

01

脚本机制层

L21—L22

一句话Shell 逐行解释执行;$? 是判断一切的基础;条件判断分字符串、数字、文件三类。

口诀 等号两侧无空格,引号定展开;字符串用 =,数字用 -eq。

易错 [ $n -gt 80 ] 变量不加引号会崩;test 与 [ ] 括号后必须有空格。

覆盖 L21—L22验收 脚本能跑 + 条件分支正确
02

批量与复用层

L23—L25

一句话for/while 管批量,函数管复用,数组管成组数据;遍历一律加双引号。

口诀 while read 逐行读,双引号护空格;数组越界不报错,只给空值。

易错 for $(cat f) 拆含空格的行;空数组遍历零次而非报错。

覆盖 L23—L25验收 批量脚本幂等 + 边界有守卫
03

工程交付层

L26

一句话需求 → 拆解 → 编码 → 调试 → 归档 → 文档,六步闭环才叫可交付。

口诀 先演练后执行;改配置先备份;退出码 0/1/2 要分明。

易错 单引号里写 $(date) 不展开;脚本只有输出没有说明文档。

覆盖 L26验收 脚本+日志+说明三件套齐全
04

容器与部署层

L27—L31

一句话镜像是模板、容器是实例;Nginx 用 server 块管站点、用 proxy_pass 管转发。

口诀 改配置先 nginx -t;stop 保留 rm 删除;映射方向宿主在前。

易错 proxy_pass 末尾斜杠改变拼接;upstream 必须写在 http 块内。

覆盖 L27—L31验收 容器可访问 + 代理链路打通
贯穿主线:从脚本到服务,始终贯穿「规范操作、最小权限、来源可信、闭环验证」——批量操作先 dry-run、配置改动先备份、每次判断都要退出码、每次排障都要证据链。
实训任务|边做边练

模块实训任务Hands-on Tasks

以下任务覆盖本模块核心能力,建议按顺序完成;差异化参数用你的 序号 与 姓名拼音 替换,避免雷同。

任 1

Shell 脚本基础与条件

脚本约 25 分钟

编写 hello_序号.sh:Shebang、变量、read 输入、$(date) 命令替换、双引号输出,再用 if 判断磁盘使用率分三档告警,用 bash -x 调试。

产出 脚本 + 三档输出 l21_task_序号.txt

验收标准 分支正确 + 引号使用规范
任 2

批量处理与函数化

批量约 30 分钟

用 for 遍历文件批量处理(continue 跳过空文件),用 while read -r 逐行读取含空格的名单;再把检查项拆成函数,source 引入 lib_序号.sh。

产出 批量脚本 + 函数库 l23_l24_task_序号.sh

验收标准 含空格名单完整 + 幂等可重跑
任 3

数组与巡检脚本

工程约 30 分钟

用数组管理待检查服务清单(含一个含空格元素),处理空数组与越界;综合函数、数组、循环与条件,完成带时间戳归档的系统巡检脚本,并写出 README 说明。

产出 巡检脚本 + 报告 + 说明 l26_task_序号/

验收标准 四类检查齐全 + 三件套交付
任 4

Docker 容器部署

容器约 20 分钟

安装 Docker 并设开机自启,跑通 hello-world;拉取 nginx 镜像,运行容器 web序号 并映射端口 80+序号,curl 与浏览器双重验证,最后 stop 与 rm 完成清理。

产出 容器运行证据 l27_l28_task_序号.png

验收标准 Up 状态可访问 + 清理后列表为空
任 5

Nginx 静态站点与虚拟主机

站点约 25 分钟

安装 Nginx,在 conf.d/ 中配置两个 server 块(site序号a.local 与 site序号b.local),各自发布静态页面;用 curl -H 与浏览器验证,分别配置 access_log 确认命中。

产出 双站点配置与日志 l29_l30_task_序号.conf

验收标准 两站内容不同 + 日志各归其位
任 6

反向代理与分层排障

综合约 30 分钟

启动后端服务并直连验证,配置 proxy_pass 与 upstream 打通代理链路;故意制造一次 502,按「配置 / 连通 / 响应」三层抓取证据定位根因,修复复测并写部署文档。

产出 部署文档 + 排障记录 l31_task_序号.md

验收标准 链路通 + 排障三层证据齐全
思考题|检验理解

本模块思考题Questions

以下题目用于自查;建议先独立思考,再对照展开的答案。

1. 脚本的退出状态如何帮助自动化判断?$? 什么时候会被覆盖?

$? 保存上一条命令的退出码,0 表示成功、非 0 表示失败或异常。脚本靠它判断分支、串联 &&/||。关键:它只保留最近一条命令的状态——一旦执行了别的命令(如 echo),前一条的状态就被覆盖。所以正确做法是判断完立刻使用:cmd; if [ $? -eq 0 ]; then ...; fi。

2. 字符串判断与整数判断的写法有什么区别?为什么不能混用?

字符串用 = / !=(或 -z/-n),做逐字比较;整数用 -eq/-ne/-gt/-lt 等。不能混用是因为 [ "10" = "10" ] 与 [ 10 = 10 ] 语义完全不同,且 [ "08" -eq 8 ] 会因 08 被当作八进制非法数字而报「整数表达式预期」。另外字符串比较中 = 两侧有空格是运算符,没有空格就不是比较。

3. 镜像和容器是什么关系?容器与虚拟机最本质的差异是什么?

关系:镜像是只读模板(类),容器是镜像的运行实例(对象),仓库存放并分发镜像。删掉容器,镜像仍可再次 run 出新容器。
本质差异:虚拟机虚拟的是硬件,每个虚拟机跑完整独立的操作系统内核,体积 GB 级、启动分钟级;容器虚拟的是操作系统,共享宿主机内核,体积 MB 级、启动秒级。正因共享内核,容器不能运行不同内核的 OS,也不该在容器里塞完整系统。

4. 停止容器与删除容器有什么区别?为什么 docker rm 会失败?

stop 只是让容器主进程退出,容器对象与容器内数据都保留,可用 docker start 复活;rm 销毁容器对象,容器内数据一并消失。rm 只能删已停止的容器,运行中会报「cannot remove container: container is running」,所以标准流程是 docker stop web && docker rm web。镜像则独立于容器生命周期,容器全删后镜像仍在。

5. 为什么说「不要直接把防火墙关掉」,Nginx 部署后放行端口的正确做法是什么?

防火墙是服务对外暴露的唯一闸门,关掉等于取消所有访问控制,还会让已配置的安全策略全部失效,违背「最小权限」原则。正确做法是按需放行具体端口:firewall-cmd --add-port=80/tcp --permanent(永久生效)+ firewall-cmd --reload(重载生效),再用 firewall-cmd --list-ports 验证。排查访问不通时,应检查端口是否放行,而不是关闭防火墙。

6. 反向代理出现 502 时,应从哪些层次排查?proxy_pass 末尾的斜杠有什么影响?

502 的含义是 Nginx 收到了请求但没拿到后端的有效响应,问题通常在后端侧。按三层排查:① 配置层(nginx -t、上游地址端口是否写对)→ ② 连通层(直连后端 curl、ss -tlnp 看监听、firewall-cmd --list-ports)→ ③ 响应层(后端自身是否 500、tail error.log 看 upstream 报错)。
斜杠影响:proxy_pass http://backend; 保留原路径,proxy_pass http://backend/; 会把匹配到的前缀替换掉——请求 /api/users 时后端分别收到 /api/users 与 /users,两者不等价,必须与后端约定一致。

下节预告:第 32 次课进入综合复习与实操考核——把模块一的安装与命令、模块二的磁盘与网络管理、模块三的 Shell 与 Docker/Nginx 全部串起来,以任务单的形式完成一次完整的综合实操。
作业双提示:① 每次课一个综合作业,覆盖本课全部知识点且每步要求验证;② 个人工作区统一用 ~/stu序号_姓名拼音,脚本名、配置文件名、容器名、端口号均带你的 序号,便于批改与防抄袭。