编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • ScriptHub 脚本工具箱
  • Python

  • Shell-Bash

    • Shell & Bash 从0到1实战专栏
    • Shell 入门与变量
    • 流程控制与函数
    • 数据与 IO 处理
    • grep 搜索实战
    • sed 与 awk 编程
    • 文件查找与统计
    • 日志监控与告警
    • 备份进程与磁盘
    • 用户与服务管理
    • 网络调度与部署
      • 10.1 网络诊断
        • 10.1.1 ping / traceroute——连通性测试
        • 10.1.2 ss / netstat——端口与连接分析
        • 10.1.3 curl / wget——HTTP 请求与调试
        • 10.1.4 dig / nslookup——DNS 解析诊断
        • 10.1.5 iptables / nftables——防火墙排查
        • 10.1.6 tcpdump——抓包分析
        • 10.1.7 网络故障排查实战——从现象到根因
      • 10.2 定时任务
        • 10.2.1 crontab 语法精讲
        • 10.2.2 特殊字符与高级时间表达式
        • 10.2.3 环境变量陷阱与日志重定向
        • 10.2.4 anacron——弥补 cron 的遗漏
        • 10.2.5 at——一次性定时任务
        • 10.2.6 实战:定时任务最佳实践模板
      • 10.3 批量部署
        • 10.3.1 SSH 免密登录完整配置
        • 10.3.2 ssh + for 循环——最轻量批量执行
        • 10.3.3 ansible 快速入门——声明式批量管理
        • 10.3.4 文件分发与配置同步
        • 10.3.5 版本发布与自动回滚
        • 10.3.6 灰度发布策略与部署检查清单
      • 10.4 新手陷阱与思考题
        • 陷阱 Top 5
        • 综合思考题
    • 调试与脚本规范
    • 安全与兼容处理
    • 性能与打包分发
  • 工具脚本

  • ScriptHub
  • Shell-Bash
杨充
2019-04-12
目录

网络调度与部署

# 第 10 章 网络调度与部署

# 目录介绍

  • 10.1 网络诊断
    • 10.1.1 ping / traceroute——连通性测试
    • 10.1.2 ss / netstat——端口与连接分析
    • 10.1.3 curl / wget——HTTP 请求与调试
    • 10.1.4 dig / nslookup——DNS 解析诊断
    • 10.1.5 iptables / nftables——防火墙排查
    • 10.1.6 tcpdump——抓包分析
    • 10.1.7 网络故障排查实战——从现象到根因
  • 10.2 定时任务
    • 10.2.1 crontab 语法精讲
    • 10.2.2 特殊字符与高级时间表达式
    • 10.2.3 环境变量陷阱与日志重定向
    • 10.2.4 anacron——弥补 cron 的遗漏
    • 10.2.5 at——一次性定时任务
    • 10.2.6 实战:定时任务最佳实践模板
  • 10.3 批量部署
    • 10.3.1 SSH 免密登录完整配置
    • 10.3.2 ssh + for 循环——最轻量批量执行
    • 10.3.3 ansible 快速入门——声明式批量管理
    • 10.3.4 文件分发与配置同步
    • 10.3.5 版本发布与自动回滚
    • 10.3.6 灰度发布策略与部署检查清单
  • 10.4 新手陷阱与思考题

# 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

# 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

# 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

# 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

# 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

# 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

# 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

# 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

# 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

# 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

# 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

# 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

# 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

# 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

# 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

# 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

# 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

# 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

# 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

# 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: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

陷阱 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

陷阱 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

陷阱 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

# 综合思考题

  1. 网络自愈脚本:写一个脚本监控到外部 API 的连通性,如果连续 3 次失败则自动重启网络服务(systemctl restart networking),同时发送告警。

  2. DNS 切换验证:切换域名 DNS 到新 IP 后,写脚本轮询多个公共 DNS 服务器(8.8.8.8/1.1.1.1/114.114.114.114),检查新解析是否已生效,全部生效后输出通知。

  3. cron 任务链:设计一个 cron 任务依赖链——任务 B 必须在任务 A 成功后才执行,任务 C 依赖 B。用文件信号量实现(不能用 && 因为 cron 不支持跨条目依赖)。

  4. 批量更新脚本:编写一个在所有 Web 服务器上"滚动更新"的脚本——每次只更新一台,更新完健康检查通过后才更新下一台,任何一台失败则停止并告警。

  5. tcpdump 自动分析:写一个抓包分析脚本——抓取 80 端口 100 个包,自动统计:连接建立次数、平均 RTT、哪个 IP 请求最频繁、是否有 RST 包(异常断开)、HTTP 状态码分布。

#Shell#运维
上次更新: 2026/06/17, 12:47:39
用户与服务管理
调试与脚本规范

← 用户与服务管理 调试与脚本规范→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式