网络调度与部署
# 第 10 章 网络调度与部署
# 目录介绍
# 10.1 网络诊断
# 10.1.1 ping / traceroute——连通性测试
#!/bin/bash
# ===== ping —— 测试主机是否可达 =====
ping -c 4 8.8.8.8 # 发 4 个包
ping -c 4 -i 0.5 8.8.8.8 # 间隔 0.5 秒(默认 1 秒)
ping -c 4 -W 2 8.8.8.8 # 超时 2 秒
ping -c 4 -s 1400 8.8.8.8 # 指定包大小(字节)
ping -c 10 -q 8.8.8.8 # 安静模式——只输出汇总
# 输出:10 packets transmitted, 10 received, 0% packet loss
# rtt min/avg/max/mdev = 12.3/15.6/18.9/2.1 ms
# ===== 快速连通性检测脚本 =====
check_host() {
local host="$1"
local timeout="${2:-2}"
if ping -c 1 -W "$timeout" "$host" > /dev/null 2>&1; then
echo "✅ $host 可达"
return 0
else
echo "❌ $host 不可达"
return 1
fi
}
# 批量连通性检测
check_hosts() {
local hosts=("8.8.8.8" "1.1.1.1" "baidu.com" "internal-api.local" "db-master.internal")
for host in "${hosts[@]}"; do
check_host "$host"
done
}
# ===== traceroute / mtr —— 路由追踪 =====
# traceroute 看数据包从源到目标经过哪些路由器
traceroute baidu.com # 传统 traceroute(UDP)
traceroute -I baidu.com # ICMP 模式(更快)
traceroute -T -p 80 baidu.com # TCP 模式(穿透防火墙)
traceroute -n baidu.com # 不解析主机名(更快)
# mtr = ping + traceroute 的结合(动态刷新)
mtr baidu.com # 交互式
mtr -r -c 10 baidu.com # 报告模式:发 10 包出报告
# 输出示例:
# HOST: host1 Loss% Snt Last Avg Best Wrst StDev
# 1. 192.168.1.1 0.0% 10 1.2 1.5 1.0 2.1 0.3
# 2. 10.0.0.1 0.0% 10 5.3 5.8 4.9 8.2 0.9
# 3. ??? 100.0% 10 0.0 0.0 0.0 0.0 0.0 ← 中间节点丢包可能正常
# 4. baidu.com 0.0% 10 15.2 16.0 14.8 18.1 1.1 ← 目标丢包=真正问题
# ===== 实战:网络质量检测脚本 =====
cat > net_quality_test.sh << 'SCRIPT'
#!/bin/bash
TARGET="${1:-8.8.8.8}"
COUNT=10
echo "===== 网络质量检测 ====="
echo "目标: $TARGET"
echo ""
# 延迟测试
echo "--- 延迟 (ping $COUNT 包) ---"
ping -c "$COUNT" -q "$TARGET" 2>/dev/null || echo "不可达"
# 路由追踪
echo ""
echo "--- 路由追踪 ---"
traceroute -n -m 15 "$TARGET" 2>/dev/null | tail -20
# 丢包率详细
echo ""
echo "--- 丢包详情 ---"
lost=$(ping -c "$COUNT" -q "$TARGET" 2>/dev/null | grep -oP '\d+(?=% packet loss)')
if [[ -n "$lost" ]]; then
if [[ "$lost" -eq 0 ]]; then
echo "✅ 丢包率 0%"
elif [[ "$lost" -lt 5 ]]; then
echo "⚠️ 丢包率 ${lost}%(轻微)"
else
echo "❌ 丢包率 ${lost}%(严重)"
fi
fi
SCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# 10.1.2 ss / netstat——端口与连接分析
#!/bin/bash
# ===== ss(推荐,替代 netstat)=====
# 查看所有 TCP 监听端口
ss -tlnp
# -t=TCP -l=监听 -n=数字格式 -p=显示进程
# 查看所有 TCP 连接(含 ESTABLISHED)
ss -tan
# 按状态过滤
ss -tan state established # 只看已建立的连接
ss -tan state time-wait # 只看 TIME_WAIT
ss -tan state listening # 只看监听端口
# 统计各状态连接数
ss -tan | awk 'NR>1 {state[$1]++} END {for(s in state) printf "%-12s %d\n", s, state[s]}'
# 查看 TCP 连接详情(含定时器/重传信息)
ss -tino # -i=内部信息 -o=定时器
# 输出示例:
# ESTAB 0 0 192.168.1.10:22 192.168.1.100:54321
# timer:(keepalive,119min,0) ← keepalive 定时器还剩 119 分钟
# 查看某个端口的连接
ss -tan sport = :80 # 源端口 80
ss -tan dport = :8080 # 目标端口 8080
ss -tan src 192.168.1.0/24 # 来自某网段
ss -tan dst 10.0.0.1 # 发往某 IP
# ===== netstat(传统,部分系统仍在使用)=====
netstat -tlnp # TCP 监听端口
netstat -an # 所有连接
netstat -ant | awk '{print $6}' | sort | uniq -c | sort -rn # 连接状态统计
# ===== 快速连接排查 =====
# 查看谁连了我的 80 端口
ss -tan sport = :80 | awk 'NR>1 {print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn | head -10
# 查看 TIME_WAIT 过多的端口
ss -tan state time-wait | awk '{print $4}' | awk -F: '{print $NF}' | sort | uniq -c | sort -rn | head -5
# ===== 监控连接数变化 =====
watch_connections() {
local port="${1:-80}"
echo "监控端口 $port 连接数(每 2 秒刷新),Ctrl+C 退出"
while true; do
local count=$(ss -tan sport = :$port 2>/dev/null | wc -l)
printf "\r$(date '+%H:%M:%S') 连接数: %d " $count
sleep 2
done
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# 10.1.3 curl / wget——HTTP 请求与调试
#!/bin/bash
# ===== curl —— 瑞士军刀级 HTTP 客户端 =====
# 基础请求
curl https://api.example.com # GET 请求
curl -I https://api.example.com # 只看响应头(HEAD 请求)
curl -v https://api.example.com # 详细输出(含请求/响应头)
curl -s https://api.example.com # 静默模式(不显示进度条)
# 输出控制
curl -o file.html https://example.com # 保存到文件
curl -O https://example.com/file.tar.gz # 按远程文件名保存
curl -w "\nHTTP:%{http_code} Time:%{time_total}s\n" https://api.example.com
# -w 格式化输出变量:
# %{http_code} HTTP 状态码
# %{time_total} 总耗时(秒)
# %{time_connect} TCP 连接耗时
# %{time_starttransfer} 到第一个字节的时间(TTFB)
# %{size_download} 下载字节数
# %{speed_download} 下载速率
# POST 请求
curl -X POST https://api.example.com/users # POST 请求
curl -d "name=alice&age=25" https://api.example.com/users # Form 数据
curl -H "Content-Type: application/json" -d '{"name":"alice"}' \
https://api.example.com/users # JSON 数据
# 认证与 Header
curl -u user:pass https://api.example.com # Basic Auth
curl -H "Authorization: Bearer TOKEN" https://api.example.com # Token
curl -H "X-API-Key: YOUR_KEY" https://api.example.com # 自定义头
# 调试
curl -L https://short.link # 跟随重定向
curl -k https://self-signed.badssl.com/ # 跳过 SSL 验证(仅测试!)
curl --resolve example.com:80:192.168.1.10 http://example.com # 指定 DNS 解析
# ===== 综合 API 测试脚本 =====
api_test() {
local url="$1"
echo "=== API 测试: $url ==="
curl -w "\n\
状态码: %{http_code}\n\
总耗时: %{time_total}s\n\
DNS 解析: %{time_namelookup}s\n\
TCP 连接: %{time_connect}s\n\
SSL 握手: %{time_appconnect}s\n\
首字节: %{time_starttransfer}s\n\
下载大小: %{size_download} bytes\n\
下载速率: %{speed_download} B/s\n" \
-o /dev/null -s "$url"
}
# ===== wget —— 擅长下载 =====
wget https://example.com/file.tar.gz # 下载文件
wget -O custom_name.tar.gz https://example.com/file.tar.gz # 指定文件名
wget -c https://example.com/large.iso # 断点续传
wget --limit-rate=1m https://example.com/large.iso # 限速 1MB/s
wget -r -l 2 https://example.com/docs/ # 递归下载(深度 2 层)
wget -i urls.txt # 从文件读取 URL 列表批量下载
wget --spider https://example.com # spider 模式(只检查链接不下载)
# ===== curl vs wget 场景速查 =====
# curl : API 测试、调试 HTTP、脚本中处理响应、管道输出
# wget : 下载文件、断点续传、镜像网站、递归抓取
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# 10.1.4 dig / nslookup——DNS 解析诊断
#!/bin/bash
# ===== dig(推荐,信息全面)=====
dig example.com # 基础查询
dig example.com +short # 只输出 IP
dig example.com A # 查询 A 记录(IPv4)
dig example.com AAAA # 查询 AAAA 记录(IPv6)
dig example.com MX # 查询邮件服务器
dig example.com NS # 查询域名服务器
dig example.com CNAME # 查询 CNAME 别名
dig example.com TXT # 查询 TXT 记录(SPF/DKIM 等)
dig example.com ANY # 查询所有记录
# 指定 DNS 服务器
dig @8.8.8.8 example.com # 用 Google DNS
dig @114.114.114.114 example.com # 用国内 DNS
dig @1.1.1.1 example.com +short
# 追踪解析过程
dig example.com +trace # 从根服务器逐级追踪
# 反向解析(IP → 域名)
dig -x 8.8.8.8 # PTR 反向查询
# ===== nslookup(交互式,传统)=====
nslookup example.com
nslookup example.com 8.8.8.8 # 指定 DNS
# ===== DNS 诊断脚本 =====
dns_diagnose() {
local domain="$1"
echo "===== DNS 诊断: $domain ====="
echo ""
echo "--- A 记录 ---"
dig +short "$domain" A
echo "--- AAAA 记录 ---"
dig +short "$domain" AAAA
echo "--- NS 记录 ---"
dig +short "$domain" NS
echo "--- MX 记录 ---"
dig +short "$domain" MX
echo "--- CNAME ---"
dig +short "$domain" CNAME
echo ""
echo "--- 解析速度 ---"
for dns in "8.8.8.8" "1.1.1.1" "114.114.114.114" "223.5.5.5"; do
local time=$(dig @"$dns" +stats "$domain" | grep "Query time:" | awk '{print $4}')
printf " DNS %-20s: %s ms\n" "$dns" "$time"
done
}
# ===== 检查 DNS 配置 =====
# 系统 DNS 配置文件
cat /etc/resolv.conf
# nameserver 8.8.8.8
# nameserver 1.1.1.1
# 测试 DNS 解析是否正常
test_dns_resolution() {
local domains=("google.com" "github.com" "baidu.com" "internal-service.local")
for d in "${domains[@]}"; do
if host "$d" > /dev/null 2>&1; then
echo "✅ $d ($(dig +short "$d" | head -1))"
else
echo "❌ $d 解析失败"
fi
done
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# 10.1.5 iptables / nftables——防火墙排查
#!/bin/bash
# ===== iptables 规则查看 =====
iptables -L -n -v # 列出规则(数字格式)
iptables -L INPUT -n -v # 只看 INPUT 链
iptables -L -n -v --line-numbers # 显示行号(方便删除)
# 保存/恢复
iptables-save > iptables_backup.rules # 保存当前规则
iptables-restore < iptables_backup.rules # 恢复规则
# ===== 常用规则 =====
# 允许特定 IP 访问 SSH
iptables -A INPUT -p tcp -s 192.168.1.0/24 --dport 22 -j ACCEPT
# 禁止某 IP 访问
iptables -A INPUT -s 10.0.0.5 -j DROP
# 允许已建立的连接
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# 允许 HTTP/HTTPS
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
# 删除规则
iptables -D INPUT 3 # 删除 INPUT 链第 3 条
iptables -F INPUT # 清空 INPUT 链(危险!)
# ===== 防火墙排查三步法 =====
# 1. 确认 iptables 是否在运行
iptables -L -n | head -5
# 如果输出为空 → 没有规则(可能用了 nftables 或其他)
# 2. 检查默认策略(Policy)
iptables -L -n | grep -E "Chain (INPUT|FORWARD|OUTPUT)"
# 如果 Policy 是 DROP → 默认拒绝,需要显式放行
# 3. 测试端口通断(从外部)
# 另一台机器:nc -zv target_ip 80
# 或同机器:ss -tlnp | grep :80 (先确认本地在监听)
# ===== nftables(新一代,替代 iptables)=====
nft list ruleset # 查看所有规则
nft list table inet filter # 查看 filter 表
# ===== 诊断防火墙是否阻断某端口 =====
check_firewall_block() {
local port="$1"
echo "检查端口 $port 是否被防火墙阻断..."
# 1. 确认本地在监听
if ss -tlnp | grep -q ":$port "; then
echo " ✅ 本地监听正常"
else
echo " ❌ 本地未监听端口 $port"
return 1
fi
# 2. 检查 iptables
local iptables_count=$(iptables -L -n 2>/dev/null | grep -c "dpt:$port")
if [[ "$iptables_count" -gt 0 ]]; then
echo " ⚠️ iptables 中有 $iptables_count 条规则涉及端口 $port"
iptables -L -n | grep "dpt:$port"
fi
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# 10.1.6 tcpdump——抓包分析
#!/bin/bash
# ===== tcpdump 基础 =====
# 查看可用网卡
tcpdump -D
# 抓指定网卡
tcpdump -i eth0 # 抓 eth0 所有流量(太多!)
tcpdump -i any # 抓所有网卡
# 限制抓包数量
tcpdump -i eth0 -c 100 # 抓 100 个包后停止
tcpdump -i eth0 -w capture.pcap # 保存到文件(Wireshark 可读)
tcpdump -r capture.pcap # 读取文件
# ===== 过滤器(必须加,否则输出太多)=====
# 按主机
tcpdump -i eth0 host 192.168.1.10 # 只看某主机的包
tcpdump -i eth0 src 192.168.1.10 # 只看源地址
tcpdump -i eth0 dst 192.168.1.10 # 只看目标地址
# 按端口
tcpdump -i eth0 port 80 # 只看 80 端口
tcpdump -i eth0 port 80 or port 443 # 80 或 443
tcpdump -i eth0 portrange 8000-9000 # 端口范围
# 按协议
tcpdump -i eth0 tcp # 只看 TCP
tcpdump -i eth0 udp port 53 # DNS 请求
# 组合过滤
tcpdump -i eth0 'host 192.168.1.10 and port 80 and tcp'
tcpdump -i eth0 '(host A or host B) and port 443'
# ===== 输出控制 =====
tcpdump -i eth0 -n # 不解析主机名(更快)
tcpdump -i eth0 -nn # 不解析端口名(数字显示)
tcpdump -i eth0 -X # 显示包内容(hex + ASCII)
tcpdump -i eth0 -A # 显示包内容(ASCII only)
tcpdump -i eth0 -v / -vv / -vvv # 详细程度递增
# ===== 实战场景 =====
# 1. 抓 HTTP 请求
tcpdump -i eth0 -A -s 0 'tcp port 80 and (((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0)'
# 2. 看谁在请求我的 8080 端口(只看 SYN)
tcpdump -i eth0 -nn 'tcp[tcpflags] == tcp-syn and dst port 8080'
# 3. 抓 DNS 查询(看域名解析请求)
tcpdump -i eth0 -nn 'udp port 53' -c 20
# 4. 抓特定时间段 TCP 连接建立(SYN/ACK 握手)
tcpdump -i eth0 -nn 'tcp[tcpflags] & (tcp-syn|tcp-ack) != 0 and host 192.168.1.10'
# ===== 快速抓包分析脚本 =====
tcpdump_quick() {
local port="${1:-80}"
local count="${2:-50}"
local outfile="/tmp/capture_${port}_$(date +%s).pcap"
echo "抓包端口 $port,共 $count 个包,写入 $outfile..."
timeout 30 tcpdump -i any -nn -c "$count" -w "$outfile" "port $port" 2>/dev/null
echo ""
echo "--- 摘要 ---"
tcpdump -r "$outfile" -nn 2>/dev/null | head -20
echo ""
echo "--- 会话统计 ---"
tcpdump -r "$outfile" -nn 2>/dev/null | awk '{print $3" → "$5}' | sort | uniq -c | sort -rn
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# 10.1.7 网络故障排查实战——从现象到根因
#!/bin/bash
# ===== 排查方法论:自下而上 7 层 =====
# 口诀:你我路接传会表应
# 物理层 → 数据链路层 → 网络层 → 传输层 → 会话层 → 表示层 → 应用层
cat > net_troubleshoot.sh << 'SCRIPT'
#!/bin/bash
# 网络故障排查脚本——从常见现象出发
TARGET="$1"
PORT="${2:-80}"
echo "===== 网络排查: $TARGET:$PORT ====="
# 第 1 步:DNS 解析
echo "[1/6] DNS 解析..."
if host "$TARGET" > /dev/null 2>&1; then
ip=$(dig +short "$TARGET" | head -1)
echo " ✅ $TARGET → $ip"
else
echo " ❌ DNS 解析失败——检查 /etc/resolv.conf 配置"
exit 1
fi
# 第 2 步:本地路由
echo "[2/6] 本地路由..."
default_gw=$(ip route | grep default | awk '{print $3}')
echo " 默认网关: $default_gw"
# 第 3 步:网关连通性
echo "[3/6] 网关连通性..."
if ping -c 1 -W 1 "$default_gw" > /dev/null 2>&1; then
echo " ✅ 网关可达"
else
echo " ❌ 网关不通——检查网线/网卡/交换机"
exit 1
fi
# 第 4 步:网络层——ping 目标
echo "[4/6] 网络层连通性 (ping)..."
if ping -c 2 -W 2 "$ip" > /dev/null 2>&1; then
echo " ✅ 目标 IP 可达"
else
echo " ❌ 目标 IP 不可达——可能防火墙拦截或路由问题"
echo " 尝试 traceroute:"
traceroute -n -m 10 "$ip" 2>/dev/null | tail -5
exit 1
fi
# 第 5 步:传输层——端口
echo "[5/6] 传输层——端口 $PORT ..."
if timeout 3 bash -c "echo > /dev/tcp/$ip/$PORT" 2>/dev/null; then
echo " ✅ 端口 $PORT 可达"
else
echo " ❌ 端口 $PORT 不可达——服务未启动或防火墙拦截"
# 检查是否是 iptables
iptables -L -n 2>/dev/null | grep -E "dpt:$PORT" && echo " ⚠️ iptables 有相关规则"
exit 1
fi
# 第 6 步:应用层——HTTP
echo "[6/6] 应用层——HTTP 响应..."
if [[ "$PORT" == "80" || "$PORT" == "443" || "$PORT" == "8080" ]]; then
proto="http"
[[ "$PORT" == "443" ]] && proto="https"
response=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 3 "${proto}://${TARGET}:${PORT}/" 2>/dev/null)
if [[ -n "$response" ]]; then
echo " ✅ HTTP $response"
else
echo " ❌ 无 HTTP 响应——检查 Web 服务器是否正常"
fi
fi
echo ""
echo "===== 排查结束 ====="
SCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# 10.2 定时任务
# 10.2.1 crontab 语法精讲
#!/bin/bash
# crontab 格式:分 时 日 月 周 命令
# * * * * * command
# │ │ │ │ │
# │ │ │ │ └── 星期 (0-7, 0 和 7 都代表周日)
# │ │ │ └──── 月份 (1-12)
# │ │ └────── 日期 (1-31)
# │ └──────── 小时 (0-23)
# └────────── 分钟 (0-59)
# ===== 常用时间表达式 =====
# 每分钟 * * * * *
# 每小时整点 0 * * * *
# 每天凌晨 2 点 0 2 * * *
# 每周日凌晨 3 点 0 3 * * 0
# 每月 1 号凌晨 1 点 0 1 1 * *
# 每 5 分钟 */5 * * * *
# 每 2 小时 0 */2 * * *
# 工作日上午 9 点 0 9 * * 1-5
# 每周一三五凌晨 1 点 0 1 * * 1,3,5
# 每月最后一天 0 0 L * * (部分 cron 不支持 L)
# ===== 管理 crontab =====
crontab -e # 编辑当前用户定时任务
crontab -l # 查看
crontab -r # 删除所有(危险!)
crontab -u username -e # 编辑指定用户(root)
# ===== 系统级 cron =====
# /etc/crontab 系统级定时任务(包含用户字段)
# /etc/cron.d/ 分片配置目录
# /etc/cron.daily/ 每天运行
# /etc/cron.hourly/ 每小时运行
# /etc/cron.weekly/ 每周运行
# /etc/cron.monthly/ 每月运行
# ===== crontab 条目示例 =====
# 每 5 分钟执行监控脚本
# */5 * * * * /opt/scripts/monitor.sh >> /var/log/monitor.log 2>&1
# 每小时备份
# 0 * * * * /opt/scripts/backup.sh
# 凌晨 3 点清理日志并压缩
# 0 3 * * * find /var/log -name "*.log" -mtime +7 -delete
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# 10.2.2 特殊字符与高级时间表达式
#!/bin/bash
# ===== 特殊字符 =====
# * = 所有可能的值(每分钟/每小时...)
# , = 枚举值(1,3,5 = 1 月 3 月 5 月)
# - = 范围值(1-5 = 周一~周五)
# / = 步长(*/5 = 每 5 分钟)
# L = 最后(L = 最后一天,仅部分 cron 支持)
# W = 最近的工作日(15W = 15 号最近的工作日)
# # = 第几个(2#3 = 第三个周二)
# ===== 实战时间表达式 =====
# 每 10 分钟
# */10 * * * *
# 工作时间每 30 分钟(周一到周五,8:00-18:00)
# */30 8-18 * * 1-5
# 每 2 小时的 15 分
# 15 */2 * * *
# 每月 1 号和 15 号
# 0 0 1,15 * *
# 每周六日每小时的 30 分
# 30 * * * 6,7
# 每年 1 月 1 日
# 0 0 1 1 *
# ===== 测试 cron 表达式 =====
# 在线工具:https://crontab.guru/
# 或脚本预览下一次执行时间(近似):
next_cron_times() {
local expr="$1" n="${2:-5}"
# 简单推荐:用 date 模拟(精确验证请用在线工具)
echo "cron: $expr"
echo "接下来 $n 次执行时间(模拟):"
local now=$(date +%s)
for i in $(seq 1 "$n"); do
echo " $(date -d @$((now + i*60*5)))" # 假设每 5 分钟(简化)
done
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# 10.2.3 环境变量陷阱与日志重定向
#!/bin/bash
# ===== 陷阱 1:PATH 极短 =====
# ❌ crontab 的 PATH 通常只有 /usr/bin:/bin
# 脚本中用到的命令可能找不到
# ✅ 解决方法 1:在 crontab 文件顶部设置
# PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# SHELL=/bin/bash
# ✅ 解决方法 2:在脚本中显式设置
# export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# ✅ 解决方法 3:使用绝对路径
# 0 3 * * * /usr/bin/python /opt/scripts/backup.py
# ===== 陷阱 2:脚本没有执行权限 =====
chmod +x /opt/scripts/backup.sh
# ===== 陷阱 3:相对路径问题 =====
# ❌ 脚本里 cd 到相对路径会失败(crontab 默认工作目录是 $HOME)
# ✅ 脚本开头 cd 到绝对路径
cd /opt/myapp || exit 1
# ===== 陷阱 4:日志重定向 =====
# ❌ 无日志输出 → 排查困难
# 0 3 * * * /opt/scripts/backup.sh
# ✅ stdout + stderr 都重定向
# 0 3 * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>&1
# 解释:
# >> file.log 追加 stdout 到文件
# 2>&1 将 stderr(2) 重定向到 stdout(1) 的目标(即 file.log)
# ===== 陷阱 5:% 号在 crontab 中 =====
# ❌ % 是 crontab 的换行符,会被解释
# 0 3 * * * echo "$(date +%Y-%m-%d)" >> /tmp/date.log
# ✅ 对 % 进行转义(前面加反斜杠)
# 0 3 * * * echo "$(date +\%Y-\%m-\%d)" >> /tmp/date.log
# ===== 完美 cron 条目模板 =====
# MAILTO="admin@example.com" ← 输出也会发邮件(可选)
# PATH=/usr/local/bin:/usr/bin:/bin
# SHELL=/bin/bash
#
# 分 时 日 月 周 命令 >> 日志 2>&1
# */5 * * * * /opt/scripts/monitor.sh >> /var/log/monitor.log 2>&1
# 0 2 * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>&1
# ===== 调试 cron 任务 =====
debug_cron() {
local script="$1"
echo "===== Cron 调试模式 ====="
echo "模拟 cron 环境执行: $script"
echo ""
# 模拟 cron 的最小环境
env -i HOME="$HOME" PATH=/usr/bin:/bin SHELL=/bin/bash bash -c "$script" 2>&1
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# 10.2.4 anacron——弥补 cron 的遗漏
#!/bin/bash
# ===== anacron vs cron =====
# cron : 精确时间执行,如果机器当时关机 —— 错过就是错过了
# anacron: 按"间隔"执行,机器开机后补上错过的任务
# 适用 : 个人电脑/非 7×24 运行的服务器
# ===== anacron 配置文件: /etc/anacrontab =====
# 格式:间隔(天) 延迟(分) 任务标识 命令
# 7 15 backup.daily /opt/scripts/backup.sh
# 30 30 backup.monthly /opt/scripts/monthly_backup.sh
cat > /etc/anacrontab << 'ANACRON'
# /etc/anacrontab: configuration file for anacron
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
START_HOURS_RANGE=3-22 # 只在 3:00~22:00 执行
# 天数 延迟(分) 标识 命令
1 5 cron.daily run-parts /etc/cron.daily
7 10 cron.weekly run-parts /etc/cron.weekly
30 15 cron.monthly run-parts /etc/cron.monthly
ANACRON
# ===== 手动触发 anacron =====
anacron -f # 强制执行所有任务(不管时间到没到)
anacron -n # 强制执行(不延迟)
anacron -u # 更新时间戳(标记为已执行)
# ===== cron + anacron 配合 =====
# 典型做法:cron 负责精确时间(如凌晨 3 点),anacron 负责兜底
# 如果机器凌晨关机,cron 错过备份,白天开机后 anacron 补上
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 10.2.5 at——一次性定时任务
#!/bin/bash
# ===== at 基础 =====
# 在指定时间执行一次命令
# 交互式(输入命令后 Ctrl+D 结束)
at 14:30
# at> /opt/scripts/backup.sh
# at> <EOT>
# 命令行模式
echo "/opt/scripts/backup.sh" | at 14:30
at -f /opt/scripts/backup.sh 14:30 # 从文件读取
# ===== 时间格式 =====
at 14:30 # 今天 14:30
at 14:30 tomorrow # 明天 14:30
at 14:30 + 2 days # 2 天后 14:30
at 14:30 next week # 下周 14:30
at now + 1 hour # 1 小时后
at now + 30 minutes # 30 分钟后
at midnight # 今晚 00:00
at noon # 今天 12:00
# ===== 管理 at 任务 =====
atq # 列出待执行的任务
# 输出:1 Tue Jun 10 14:30:00 2025 a root
atrm 1 # 删除任务 1
at -c 1 # 查看任务 1 的内容
# ===== 实战:延迟重启 =====
# echo "systemctl restart nginx" | at now + 5 minutes
# 5 分钟后重启 Nginx
# ===== at 的访问控制 =====
# /etc/at.allow 这个文件里的用户才能用 at(优先)
# /etc/at.deny 这个文件里的用户禁止用 at
# 两者都不存在 → 只有 root 能用 at
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# 10.2.6 实战:定时任务最佳实践模板
#!/bin/bash
# ===== cron 任务脚本模板 =====
cat > /opt/scripts/cron_template.sh << 'SCRIPT'
#!/bin/bash
# ============================================
# 任务名称: 每日数据备份
# 执行频率: 每天凌晨 3:00
# 负责人: alice
# 最后更新: 2025-06-10
# ============================================
set -euo pipefail
# ---- 环境设置 ----
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
cd /opt/scripts || exit 1
# ---- 配置 ----
LOCK_FILE="/tmp/cron_backup.lock"
LOG_FILE="/var/log/cron_backup.log"
# ---- 防重入 ----
exec 200>"$LOCK_FILE"
flock -n 200 || {
echo "[$(date)] 已有备份进程在运行,跳过本次执行" >> "$LOG_FILE"
exit 0
}
# ---- 日志函数 ----
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG_FILE"; }
# ---- 主任务 ----
log "======== 开始执行 ========"
if /opt/scripts/do_backup.sh >> "$LOG_FILE" 2>&1; then
log "✅ 执行成功"
else
log "❌ 执行失败,退出码: $?"
# 发送告警(如有需要)
exit 1
fi
log "======== 执行结束 ========"
SCRIPT
chmod +x /opt/scripts/cron_template.sh
# ===== 完整 crontab 配置示例 =====
cat > /tmp/crontab_example.txt << 'CRONTAB'
# 环境
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
MAILTO="admin@example.com"
# 每 5 分钟——服务监控
# */5 * * * * /opt/scripts/monitor.sh >> /var/log/monitor.log 2>&1
# 每小时——日志轮转检查
# 0 * * * * /opt/scripts/logrotate_check.sh >> /var/log/logrotate.log 2>&1
# 每天凌晨 2 点——数据库备份
# 0 2 * * * /opt/scripts/db_backup.sh >> /var/log/db_backup.log 2>&1
# 每天凌晨 3 点——文件备份 + 清理
# 0 3 * * * /opt/scripts/full_backup.sh >> /var/log/backup.log 2>&1
# 每周日凌晨 4 点——清理旧备份
# 0 4 * * 0 /opt/scripts/cleanup_old.sh >> /var/log/cleanup.log 2>&1
# 每月 1 号凌晨 1 点——生成月报
# 0 1 1 * * /opt/scripts/monthly_report.sh >> /var/log/report.log 2>&1
CRONTAB
# 安装这个 crontab
# crontab /tmp/crontab_example.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# 10.3 批量部署
# 10.3.1 SSH 免密登录完整配置
#!/bin/bash
# ===== 第一步:生成密钥对(控制机上执行)=====
ssh-keygen -t ed25519 -C "deploy@control-node" -f ~/.ssh/deploy_key -N ""
# 生成两个文件:
# ~/.ssh/deploy_key ← 私钥(保密)
# ~/.ssh/deploy_key.pub ← 公钥(分发到目标机器)
# ===== 第二步:分发公钥到所有目标机器 =====
# 目标机器列表
SERVERS=(
"deploy@web01.example.com"
"deploy@web02.example.com"
"deploy@web03.example.com"
"deploy@app01.example.com"
)
for server in "${SERVERS[@]}"; do
echo "分发公钥到 $server ..."
ssh-copy-id -i ~/.ssh/deploy_key.pub "$server" || {
# 备选方案:手动复制
cat ~/.ssh/deploy_key.pub | ssh "$server" \
"mkdir -p ~/.ssh; chmod 700 ~/.ssh; cat >> ~/.ssh/authorized_keys; chmod 600 ~/.ssh/authorized_keys"
}
done
# ===== 第三步:配置 SSH config(简化连接)=====
cat > ~/.ssh/config << 'SSHCONFIG'
Host web01
HostName web01.example.com
User deploy
IdentityFile ~/.ssh/deploy_key
Port 22
StrictHostKeyChecking no # 首次不提示确认指纹
Host web02
HostName web02.example.com
User deploy
IdentityFile ~/.ssh/deploy_key
Host web*
User deploy
IdentityFile ~/.ssh/deploy_key
SSHCONFIG
chmod 600 ~/.ssh/config
# 现在可以这样连接:
# ssh web01 ← 等价于 ssh -i ~/.ssh/deploy_key deploy@web01.example.com
# ===== 第四步:验证 =====
for server in "${SERVERS[@]}"; do
if ssh -o ConnectTimeout=5 -o BatchMode=yes "$server" "hostname" 2>/dev/null; then
echo "✅ $server 免密登录成功"
else
echo "❌ $server 免密登录失败"
fi
done
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# 10.3.2 ssh + for 循环——最轻量批量执行
#!/bin/bash
# ===== 方式 1:for 循环逐台执行(串行)=====
SERVERS=("web01" "web02" "web03")
for server in "${SERVERS[@]}"; do
echo "===== $server ====="
ssh "$server" "uptime; free -h | head -2; df -h /"
echo ""
done
# ===== 方式 2:并行执行(& + wait)=====
batch_ssh_parallel() {
local cmd="$1"; shift
local servers=("$@")
local failed=0
local pids=()
for server in "${servers[@]}"; do
(
output=$(ssh -o ConnectTimeout=5 "$server" "$cmd" 2>&1)
exit_code=$?
if [[ "$exit_code" -eq 0 ]]; then
echo "✅ $server: $output"
else
echo "❌ $server: $output"
fi
exit "$exit_code"
) &
pids+=($!)
done
# 等待所有后台任务
for pid in "${pids[@]}"; do
wait "$pid" || ((failed++))
done
echo ""
echo "执行完成: 成功 $(( ${#servers[@]} - failed )) / 总数 ${#servers[@]}"
return "$failed"
}
# 用法
# batch_ssh_parallel "systemctl status nginx" "${SERVERS[@]}"
# ===== 方式 3:批量查询系统信息 =====
cat > batch_sysinfo.sh << 'SCRIPT'
#!/bin/bash
SERVERS_FILE="${1:-/opt/deploy/server_list.txt}"
echo "===== 批量系统信息 ====="
printf "%-15s %8s %8s %8s %8s\n" "主机" "CPU%" "MEM%" "DISK%" "UPTIME"
printf "%-15s %8s %8s %8s %8s\n" "----" "----" "----" "-----" "------"
while read -r server; do
[[ -z "$server" || "$server" == \#* ]] && continue
info=$(ssh -o ConnectTimeout=3 "$server" "
cpu=\$(top -bn1 | grep 'Cpu(s)' | awk '{print 100 - \$8}')
mem=\$(free | awk 'NR==2{printf \"%.0f\", \$3*100/\$2}')
disk=\$(df / | awk 'NR==2{print \$5}' | tr -d '%')
upt=\$(uptime -p | sed 's/up //')
echo \"\${cpu} \${mem} \${disk} \${upt}\"
" 2>/dev/null)
if [[ -n "$info" ]]; then
IFS=' ' read -r cpu mem disk upt <<< "$info"
printf "%-15s %7s%% %7s%% %7s%% %s\n" "$server" "${cpu:-?}" "${mem:-?}" "${disk:-?}" "${upt:-?}"
else
printf "%-15s %8s %8s %8s %8s\n" "$server" "离线" "-" "-" "-"
fi
done < "$SERVERS_FILE"
SCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# 10.3.3 ansible 快速入门——声明式批量管理
#!/bin/bash
# ===== 安装 ansible =====
# Ubuntu/Debian
# apt install ansible
# CentOS/RHEL
# yum install epel-release && yum install ansible
# pip(跨平台推荐)
# pip install ansible
# ===== ansible 主机清单: /etc/ansible/hosts =====
cat > /opt/deploy/inventory.ini << 'INVENTORY'
# 基础分组
[webservers]
web01 ansible_host=192.168.1.10
web02 ansible_host=192.168.1.11
web03 ansible_host=192.168.1.12
[dbservers]
db01 ansible_host=192.168.1.20
# 嵌套分组
[production:children]
webservers
dbservers
# 变量
[all:vars]
ansible_user=deploy
ansible_ssh_private_key_file=/home/deploy/.ssh/deploy_key
ansible_python_interpreter=/usr/bin/python3
[webservers:vars]
app_port=8080
app_env=production
INVENTORY
# ===== ad-hoc 命令(一行解决)=====
# 检查连通性
ansible all -i /opt/deploy/inventory.ini -m ping
# 执行命令
ansible webservers -i /opt/deploy/inventory.ini -m shell -a "uptime"
ansible all -i /opt/deploy/inventory.ini -m shell -a "df -h / | tail -1"
# 复制文件
ansible webservers -i /opt/deploy/inventory.ini -m copy \
-a "src=/opt/config/nginx.conf dest=/etc/nginx/nginx.conf backup=yes"
# 重启服务
ansible webservers -i /opt/deploy/inventory.ini -m systemd \
-a "name=nginx state=restarted"
# ===== playbook: install_nginx.yml =====
cat > /opt/deploy/install_nginx.yml << 'PLAYBOOK'
---
- name: 安装并配置 Nginx
hosts: webservers
become: yes # sudo 权限
vars:
nginx_port: 80
tasks:
- name: 安装 Nginx
apt:
name: nginx
state: present
update_cache: yes
- name: 部署自定义配置
template:
src: /opt/deploy/templates/nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: reload nginx # 配置变更时触发 handler
- name: 启动 Nginx
systemd:
name: nginx
state: started
enabled: yes
- name: 开放防火墙端口
ufw:
rule: allow
port: "{{ nginx_port }}"
proto: tcp
handlers:
- name: reload nginx
systemd:
name: nginx
state: reloaded
PLAYBOOK
# 执行 playbook
# ansible-playbook -i /opt/deploy/inventory.ini /opt/deploy/install_nginx.yml --check # 干跑预览
# ansible-playbook -i /opt/deploy/inventory.ini /opt/deploy/install_nginx.yml # 正式执行
# ===== ansible 常用模块速查 =====
# shell : 执行 Shell 命令
# command : 执行命令(不经过 Shell,更快但无管道/重定向)
# copy : 复制文件
# template : 复制 + Jinja2 模板渲染
# fetch : 从远程拉取文件到本地
# file : 创建/删除文件/目录,修改权限
# systemd : 管理 systemd 服务
# apt / yum : 安装软件包
# service : 传统服务管理
# user : 用户管理
# cron : 定时任务管理
# git : Git 仓库操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# 10.3.4 文件分发与配置同步
#!/bin/bash
# ===== 方式 1:scp 批量复制 =====
# 分发配置文件到所有 Web 服务器
batch_scp() {
local src="$1"
local dst="$2"
local servers=("${@:3}")
for server in "${servers[@]}"; do
echo "分发 $src → $server:$dst"
scp "$src" "${server}:${dst}"
done
}
# batch_scp /opt/config/nginx.conf /etc/nginx/nginx.conf web01 web02 web03
# ===== 方式 2:rsync 批量同步 =====
# 同步代码目录
batch_rsync() {
local src_dir="$1"
local dst_dir="$2"
local servers=("${@:3}")
for server in "${servers[@]}"; do
echo "同步 $server ..."
rsync -avz --delete --exclude=".git" "$src_dir/" "${server}:${dst_dir}/"
done
}
# ===== 方式 3:配置模板 + 变量替换 =====
# 生成服务器特定的配置文件
cat > /opt/deploy/generate_configs.sh << 'SCRIPT'
#!/bin/bash
# 为每台服务器生成定制配置
TEMPLATE="/opt/deploy/templates/app.conf.tpl"
declare -A SERVER_CONFIG=(
["web01"]="port=8080;db_host=db01.internal;mem=4G"
["web02"]="port=8080;db_host=db01.internal;mem=4G"
["web03"]="port=8080;db_host=db02.internal;mem=8G"
)
for server in "${!SERVER_CONFIG[@]}"; do
IFS=';' read -ra pairs <<< "${SERVER_CONFIG[$server]}"
# 读取模板并替换变量
config=$(cat "$TEMPLATE")
for pair in "${pairs[@]}"; do
key="${pair%%=*}"
val="${pair#*=}"
config="${config//\{\{$key\}\}/$val}"
done
# 生成配置文件
echo "$config" > "/opt/deploy/generated/${server}_app.conf"
echo "✅ 已生成 ${server}_app.conf"
# 分发
scp "/opt/deploy/generated/${server}_app.conf" "${server}:/etc/app/config.conf"
done
SCRIPT
# ===== 方式 4:Git 仓库同步(推荐团队用)=====
# 服务器上配置 cron 自动 pull
# */5 * * * * cd /opt/app && git pull origin master --rebase && systemctl reload app
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# 10.3.5 版本发布与自动回滚
#!/bin/bash
# ===== 版本发布脚本 =====
cat > /opt/deploy/release.sh << 'SCRIPT'
#!/bin/bash
# 用法:./release.sh v2.1.0
# 特性:版本归档 + 一键回滚 + 发布校验
set -euo pipefail
APP_NAME="myapp"
DEPLOY_DIR="/opt/${APP_NAME}"
VERSIONS_DIR="/opt/${APP_NAME}_versions"
RELEASES_DIR="/opt/${APP_NAME}_releases"
LOG_FILE="/var/log/${APP_NAME}_deploy.log"
VERSION="$1"
[[ -z "$VERSION" ]] && { echo "用法: $0 <版本号>"; exit 1; }
log() { echo "[$(date '+%H:%M:%S')] $*" | tee -a "$LOG_FILE"; }
# ---- 1. 准备工作 ----
mkdir -p "$VERSIONS_DIR" "$RELEASES_DIR"
# 当前版本
CURRENT=$(cat "${DEPLOY_DIR}/.version" 2>/dev/null || echo "none")
log "当前版本: $CURRENT → 新版本: $VERSION"
# ---- 2. 打包当前版本(用于回滚)-----
if [[ "$CURRENT" != "none" ]] && [[ -d "$DEPLOY_DIR" ]]; then
log "备份当前版本..."
tar -czf "${VERSIONS_DIR}/${APP_NAME}_${CURRENT}.tar.gz" -C "$(dirname "$DEPLOY_DIR")" "$APP_NAME"
fi
# ---- 3. 部署新版本 ----
log "部署 $VERSION ..."
# 解压发布包(假设用 tar.gz 格式)
RELEASE_PKG="${RELEASES_DIR}/${APP_NAME}_${VERSION}.tar.gz"
if [[ ! -f "$RELEASE_PKG" ]]; then
log "错误:发布包不存在 $RELEASE_PKG"
exit 1
fi
# 原子替换:先解压到临时目录,再替换
TEMP_DIR="${DEPLOY_DIR}_new_${VERSION}"
rm -rf "$TEMP_DIR"
mkdir -p "$TEMP_DIR"
tar -xzf "$RELEASE_PKG" -C "$TEMP_DIR"
# ---- 4. 覆盖旧版本 ----
rm -rf "${DEPLOY_DIR}_old"
mv "$DEPLOY_DIR" "${DEPLOY_DIR}_old" 2>/dev/null || true
mv "$TEMP_DIR" "$DEPLOY_DIR"
echo "$VERSION" > "${DEPLOY_DIR}/.version"
# ---- 5. 重启服务 ----
log "重启服务..."
cd "$DEPLOY_DIR"
systemctl restart "$APP_NAME"
# ---- 6. 健康检查 ----
log "健康检查..."
for i in $(seq 1 30); do
if curl -sf http://localhost:8080/health > /dev/null 2>&1; then
log "✅ 健康检查通过"
break
fi
if [[ "$i" -eq 30 ]]; then
log "❌ 健康检查失败,自动回滚!"
# 回滚
rm -rf "$DEPLOY_DIR"
mv "${DEPLOY_DIR}_old" "$DEPLOY_DIR"
systemctl restart "$APP_NAME"
log "⚠️ 已回滚到 $CURRENT"
exit 1
fi
sleep 1
done
# ---- 7. 清理 ----
rm -rf "${DEPLOY_DIR}_old"
# 保留最近 5 个版本
ls -t "${VERSIONS_DIR}/${APP_NAME}_"*.tar.gz 2>/dev/null | tail -n +6 | xargs rm -f
log "===== 发布完成: $VERSION ====="
SCRIPT
# ===== 回滚脚本 =====
cat > /opt/deploy/rollback.sh << 'ROLLBACK'
#!/bin/bash
APP_NAME="myapp"
DEPLOY_DIR="/opt/${APP_NAME}"
VERSIONS_DIR="/opt/${APP_NAME}_versions"
TARGET="$1" # 回滚目标版本
# 列出可用版本
if [[ -z "$TARGET" ]]; then
echo "可用版本:"
ls -1 "$VERSIONS_DIR" | sed "s/${APP_NAME}_//;s/\.tar.gz//"
echo ""
echo "用法: $0 <版本号>"
exit 1
fi
BACKUP_PKG="${VERSIONS_DIR}/${APP_NAME}_${TARGET}.tar.gz"
if [[ ! -f "$BACKUP_PKG" ]]; then
echo "错误:版本 $TARGET 的备份不存在"
exit 1
fi
echo "回滚到版本 $TARGET ..."
# 停止服务
systemctl stop "$APP_NAME"
# 替换
rm -rf "$DEPLOY_DIR"
mkdir -p "$DEPLOY_DIR"
tar -xzf "$BACKUP_PKG" -C "$(dirname "$DEPLOY_DIR")"
echo "$TARGET" > "${DEPLOY_DIR}/.version"
# 启动
systemctl start "$APP_NAME"
echo "✅ 已回滚到 $TARGET"
ROLLBACK
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# 10.3.6 灰度发布策略与部署检查清单
#!/bin/bash
# ===== 灰度发布流程 =====
# 1% → 观察 → 10% → 观察 → 50% → 观察 → 100%
cat > /opt/deploy/canary_release.sh << 'SCRIPT'
#!/bin/bash
# 灰度发布脚本(配合 Nginx upstream 权重调整)
APP="$1"
VERSION="$2"
HEALTH_URL="http://localhost:8080/health"
METRICS_URL="http://localhost:8080/metrics"
declare -A STAGES=(
[1]="1%"
[10]="10%"
[50]="50%"
[100]="100%"
)
OBSERVE_TIME=300 # 每阶段观察 5 分钟
ERROR_THRESHOLD=2 # 错误率超过 2% 就回滚
update_upstream_weight() {
local weight="$1"
# 修改 Nginx upstream 权重(或调用 API)
sed -i "s/weight=[0-9]*/weight=${weight}/" /etc/nginx/conf.d/upstream.conf
nginx -t && systemctl reload nginx
}
check_health() {
curl -sf "$HEALTH_URL" > /dev/null 2>&1
}
check_error_rate() {
local rate=$(curl -sf "$METRICS_URL" | jq -r '.error_rate_5m // 0')
echo "$rate"
}
for pct in 1 10 50 100; do
echo "===== 灰度阶段: ${pct}% ====="
# 调整流量比例
# (这里简化:假设通过修改 Nginx upstream 权重实现)
echo " → 分配 ${pct}% 流量"
update_upstream_weight "$pct"
# 健康检查
for i in $(seq 1 10); do
if check_health; then
echo " ✅ 健康检查通过"
break
fi
sleep 2
done
# 观察期
echo " → 观察 ${OBSERVE_TIME}s ..."
sleep "$OBSERVE_TIME"
# 错误率检查
err_rate=$(check_error_rate)
echo " → 错误率: ${err_rate}%"
if (( $(echo "$err_rate > $ERROR_THRESHOLD" | bc -l) )); then
echo " ❌ 错误率过高,回滚!"
update_upstream_weight 0 # 撤回新版流量
exit 1
fi
echo " ✅ 阶段 ${pct}% 通过"
done
echo "===== 灰度发布完成: 100% ====="
SCRIPT
# ===== 部署检查清单 =====
# 发版前:
# ☐ 代码已通过 CI 测试
# ☐ 数据库迁移脚本已就绪(如需要)
# ☐ 配置变更已同步到配置中心
# ☐ 监控告警临时静默(避免部署误报)
# ☐ 回滚脚本已测试
#
# 发版中:
# ☐ 确认备份已生成
# ☐ 灰度按 1%→10%→50%→100% 逐步推进
# ☐ 每个阶段检查错误率、延迟、QPS
# ☐ 观察 5 分钟后再进入下一阶段
#
# 发版后:
# ☐ 削除部署产生的临时文件
# ☐ 恢复监控告警
# ☐ 通知相关人员
# ☐ 更新版本号文档
# ☐ 归档旧版本备份(保留最近 5 个)
# ===== 部署检查自动化脚本 =====
cat > /opt/deploy/deploy_checklist.sh << 'SCRIPT'
#!/bin/bash
echo "===== 部署前检查 ====="
checks=(
"测试是否通过:grep -q 'PASSED' /tmp/test_result.txt 2>/dev/null"
"磁盘空间充足:test \$(df / | awk 'NR==2{print \$5}' | tr -d '%') -lt 85"
"内存充足:test \$(free | awk 'NR==2{printf \"%.0f\", \$3*100/\$2}') -lt 90"
"无僵尸进程:test -z \"\$(ps aux | awk '\$8 ~ /Z/')\""
"Nginx 配置语法正确:nginx -t > /dev/null 2>&1"
"回滚脚本存在:test -x /opt/deploy/rollback.sh"
)
passed=0; failed=0
for check in "${checks[@]}"; do
name="${check%%:*}"
cmd="${check#*:}"
if eval "$cmd" 2>/dev/null; then
echo " ✅ $name"
((passed++))
else
echo " ❌ $name"
((failed++))
fi
done
echo ""
echo "结果: $passed 通过, $failed 失败"
[[ "$failed" -eq 0 ]] && echo "✅ 检查通过,可以部署" || echo "❌ 检查未通过,请修复后再部署"
exit "$failed"
SCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# 10.4 新手陷阱与思考题
# 陷阱 Top 5
陷阱 1:ping 不通 ≠ 网络不通
# ❌ ping 被很多运营商和防火墙禁用 ICMP——所以 ping 不通不代表服务不可用
ping baidu.com # 可能不通
# ✅ 用 tcping 或 curl 测试端口
timeout 3 bash -c "echo > /dev/tcp/baidu.com/443" && echo "通" || echo "不通"
curl -s -o /dev/null -w "%{http_code}" https://baidu.com
1
2
3
4
5
6
2
3
4
5
6
陷阱 2:crontab 环境变量与 shell 交互不同
# ❌ crontab 里写:echo $JAVA_HOME → 输出为空
# crontab 的 shell 是非交互式、非登录 shell,不加载 .bashrc / .profile
# ✅ 在 crontab 文件顶部声明变量,或脚本 source ~/.bashrc
# SHELL=/bin/bash
# PATH=/usr/local/bin:/usr/bin:/bin
# JAVA_HOME=/usr/lib/jvm/java-11
1
2
3
4
5
6
7
2
3
4
5
6
7
陷阱 3:tcpdump 输出淹没终端
# ❌ 不加过滤直接抓——瞬间千行刷屏
tcpdump -i eth0
# ✅ 永远加过滤条件
tcpdump -i eth0 -nn -c 50 'host 192.168.1.10 and port 80'
# -nn 不解析主机名端口名 -c 50 限制 50 个包 filter 精准定位
1
2
3
4
5
6
2
3
4
5
6
陷阱 4:scp 覆盖不提示
# ❌ scp 默认直接覆盖——不像 cp 有 -i 交互
scp config.conf server:/etc/nginx/nginx.conf # 直接覆盖!
# ✅ 先备份再 scp
scp config.conf server:/etc/nginx/nginx.conf.new
ssh server "cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak && mv /etc/nginx/nginx.conf.new /etc/nginx/nginx.conf"
1
2
3
4
5
6
2
3
4
5
6
陷阱 5:批量 SSH 没处理失败情况
# ❌ 并行执行时某个 SSH 失败了没捕获——整个脚本仍返回成功
for s in "${SERVERS[@]}"; do
ssh "$s" "restart_service" & # 后台,失败静默
done
wait # 不知道谁失败了
# ✅ 捕获每个 SSH 的退出码
for s in "${SERVERS[@]}"; do
ssh "$s" "restart_service" &
pids+=($!)
names+=("$s")
done
for i in "${!pids[@]}"; do
if wait "${pids[$i]}"; then
echo "✅ ${names[$i]} 成功"
else
echo "❌ ${names[$i]} 失败"
fi
done
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 综合思考题
网络自愈脚本:写一个脚本监控到外部 API 的连通性,如果连续 3 次失败则自动重启网络服务(
systemctl restart networking),同时发送告警。DNS 切换验证:切换域名 DNS 到新 IP 后,写脚本轮询多个公共 DNS 服务器(8.8.8.8/1.1.1.1/114.114.114.114),检查新解析是否已生效,全部生效后输出通知。
cron 任务链:设计一个 cron 任务依赖链——任务 B 必须在任务 A 成功后才执行,任务 C 依赖 B。用文件信号量实现(不能用
&&因为 cron 不支持跨条目依赖)。批量更新脚本:编写一个在所有 Web 服务器上"滚动更新"的脚本——每次只更新一台,更新完健康检查通过后才更新下一台,任何一台失败则停止并告警。
tcpdump 自动分析:写一个抓包分析脚本——抓取 80 端口 100 个包,自动统计:连接建立次数、平均 RTT、哪个 IP 请求最频繁、是否有 RST 包(异常断开)、HTTP 状态码分布。
上次更新: 2026/06/17, 12:47:39