网络调度与部署
# 第 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
# 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
# 综合思考题
网络自愈脚本:写一个脚本监控到外部 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/28, 17:55:19