编程进阶网 编程进阶网
首页
  • 在线工具
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 加解密
  • 时间日期
  • 网络工具
  • 颜色设计
  • 二维码
  • 开发实用
  • 计算机组成原理
  • 操作系统原理
  • 网络协议原理
  • 数据库系统原理
  • 序卷导读
  • 数据本质
  • 运行模型
  • 并发设计
  • 内存真相
  • 交互系统
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 体系建设
  • 代码品质
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 性能优化
  • 数据结构导论
  • 线性结构详解
  • 树哈希结构论
  • 容器设计实战
  • 经典算法思想
  • 工程案例剖析
  • 算法题库精练
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++编程技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript案例
  • JavaScript高级
  • Android库解读
  • Android专栏
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • 毛选解读
  • 自我精进
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 在线工具
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 加解密
  • 时间日期
  • 网络工具
  • 颜色设计
  • 二维码
  • 开发实用
  • 计算机组成原理
  • 操作系统原理
  • 网络协议原理
  • 数据库系统原理
  • 序卷导读
  • 数据本质
  • 运行模型
  • 并发设计
  • 内存真相
  • 交互系统
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 体系建设
  • 代码品质
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 性能优化
  • 数据结构导论
  • 线性结构详解
  • 树哈希结构论
  • 容器设计实战
  • 经典算法思想
  • 工程案例剖析
  • 算法题库精练
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++编程技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript案例
  • JavaScript高级
  • Android库解读
  • Android专栏
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • 毛选解读
  • 自我精进
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • 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

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

# 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  : 下载文件、断点续传、镜像网站、递归抓取

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

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

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

# 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

# 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

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

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

# 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 补上

# 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

# 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

# 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

# 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

# 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 仓库操作

# 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

# 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

# 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

# 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

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

陷阱 3:tcpdump 输出淹没终端

# ❌ 不加过滤直接抓——瞬间千行刷屏
tcpdump -i eth0

# ✅ 永远加过滤条件
tcpdump -i eth0 -nn -c 50 'host 192.168.1.10 and port 80'
# -nn 不解析主机名端口名  -c 50 限制 50 个包  filter 精准定位

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

陷阱 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. 网络自愈脚本:写一个脚本监控到外部 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/28, 17:55:19
用户与服务管理
调试与脚本规范

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

最近更新
01
科学方法实践论法
06-28
02
辩证思维矛盾论法
06-28
03
毛选中的调查观念
06-28
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 鄂ICP备2024073355号-1 | 鄂ICP备2024073355号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式