一、引言:为什么你需要一个"简单"的自动化脚本?
在 Nginx 运维的日常中,我们每天都在重复这样的操作:构建前端资源、测试配置、推送到服务器、重载 Nginx。当这些步骤还停留在手动敲命令的阶段时,不仅效率低下,更埋下了人为失误的隐患------路径拼错、权限遗漏、忘记重载,任何一个疏忽都可能导致线上故障。
很多教程直接抛出复杂的 CI/CD 流水线或 Ansible Playbook,但对于中小项目、个人站点或快速验证场景,这些方案显得过于沉重。你真正需要的,是一个足够简单、开箱即用、又能覆盖核心安全边界的 Shell 脚本。
本文将带你从零构建一个 Nginx-rsync 自动化部署脚本,从最基础的单行命令逐步演进为具备参数校验、日志记录、错误处理和安全防护的生产级工具。所有代码均可直接复制使用,无需额外依赖。
二、V1.0:最小可用版本(30秒上手)
如果你只是想快速摆脱手动输入,这个版本已经够用:
bash
#!/bin/bash
# deploy.sh - 最简版 Nginx rsync 部署脚本
SRC="/data/build/dist/"
DEST="deploy@192.168.1.100:/var/www/html/"
rsync -avz --delete "$SRC" "$DEST"
ssh deploy@192.168.1.100 "sudo nginx -t && sudo systemctl reload nginx"
⚠️ V1.0 的致命缺陷
- 无错误处理:rsync 失败后仍会执行 nginx reload,可能用旧配置重启服务
- 无路径校验 :源目录不存在时静默同步空内容,目标被
--delete清空 - 硬编码:换环境需改脚本源码,易出错
- 无日志:出问题后无法追溯
📌 定位 :仅适合本地开发环境的临时调试,严禁直接用于生产。
三、V2.0:生产级自动化脚本(推荐直接使用)
以下是经过实战打磨的完整脚本,兼顾简洁性与可靠性:
bash
#!/bin/bash
#================================================================
# Nginx-rsync 自动化部署脚本 v2.0
# 用法: ./deploy.sh [dev|prod]
#================================================================
set -euo pipefail
# ==================== 配置区 ====================
declare -A CONFIG=(
[dev_src]="/data/build/dist/"
[dev_dest]="deploy@192.168.1.100:/var/www/html/"
[dev_nginx_user]="www-data"
[prod_src]="/data/build/dist/"
[prod_dest]="deploy@10.0.1.50:/var/www/html/"
[prod_nginx_user]="nginx"
)
SSH_KEY="$HOME/.ssh/nginx_deploy_key"
LOG_FILE="/var/log/nginx_deploy.log"
EXCLUDE_FILE=".rsync-exclude"
# ================================================
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"; }
die() { log "❌ FATAL: $*"; exit 1; }
# ---------- 参数校验 ----------
ENV="${1:-}"
[[ -z "$ENV" ]] && die "用法: $0 <dev|prod>"
[[ ! "${CONFIG[${ENV}_src]+_}" ]] && die "未知环境: $ENV (可选: dev, prod)"
SRC="${CONFIG[${ENV}_src]}"
DEST="${CONFIG[${ENV}_dest]}"
NGINX_USER="${CONFIG[${ENV}_nginx_user]}"
# ---------- 前置检查 ----------
[[ ! -d "$SRC" ]] && die "源目录不存在: $SRC"
[[ ! -f "$SSH_KEY" ]] && die "SSH密钥不存在: $SSH_KEY"
[[ ! -f "$EXCLUDE_FILE" ]] && log "⚠️ 排除文件不存在: $EXCLUDE_FILE (将同步全部文件)"
# ---------- 核心同步 ----------
log "🔄 开始部署 [$ENV]: $SRC → $DEST"
RSYNC_OPTS=(-avz --progress --chown="$NGINX_USER:$NGINX_USER")
[[ -f "$EXCLUDE_FILE" ]] && RSYNC_OPTS+=(--exclude-from="$EXCLUDE_FILE")
# 注意:仅在确认目标目录专用于本次同步时才加 --delete
# RSYNC_OPTS+=(--delete)
if ! rsync "${RSYNC_OPTS[@]}" \
-e "ssh -i $SSH_KEY -o StrictHostKeyChecking=accept-new -o BatchMode=yes" \
"$SRC" "$DEST"; then
die "rsync 同步失败,终止部署"
fi
log "✅ 文件同步完成"
# ---------- Nginx 重载 ----------
REMOTE_HOST="${DEST%%:*}"
log "🔧 验证并重载 Nginx..."
if ! ssh -i "$SSH_KEY" -o BatchMode=yes "$REMOTE_HOST" \
"sudo nginx -t 2>&1 && sudo systemctl reload nginx"; then
die "Nginx 配置验证失败或重载异常,请检查远程日志"
fi
log "🎉 [$ENV] 部署成功!"
核心设计解析
| 特性 | 实现方式 | 解决的问题 |
|---|---|---|
| 多环境支持 | declare -A 关联数组 + 命令行参数 |
避免维护多个脚本副本 |
| 严格错误处理 | set -euo pipefail + 显式 die() |
任何环节失败立即终止,防止脏状态 |
| SSH 安全 | BatchMode=yes + 专用密钥 |
杜绝交互式密码提示,自动化不卡死 |
| 权限自动修正 | --chown 参数 |
消除部署后 403 Forbidden |
| 可观测性 | 带时间戳的结构化日志 | 故障追溯有据可查 |
| 安全防护 | 默认注释 --delete |
防止误删非托管文件 |
四、关键细节深度拆解
1. 为什么必须用 BatchMode=yes?
在自动化脚本中,SSH 连接绝对不能 出现任何交互式提示。BatchMode=yes 确保:
- 密码/密钥短语缺失时直接报错退出,而非挂起等待输入
- 主机指纹变更时拒绝连接,而非弹出确认对话框
这是区分"能跑的脚本"和"能在 Cron/CI 中稳定运行的脚本"的关键分水岭。
2. --delete 的安全使用策略
脚本中默认注释了 --delete,这是刻意的设计。启用前请完成以下检查清单:
- 目标目录是否仅包含本次同步管理的文件?
- 是否有用户上传内容、SSL 证书、日志等非同步文件?
- 是否已先用
--dry-run验证过删除列表? - 是否在
.rsync-exclude中保护了关键路径?
若答案有任何一项为"否",请勿启用 --delete。增量同步(默认行为)对大多数 Nginx 场景已足够安全。
3. 排除文件的最佳实践
创建 .rsync-exclude 并纳入版本管理:
# .rsync-exclude
.git/
node_modules/
*.log
*.tmp
.env*
.DS_Store
uploads/
*.pem
*.key
通过 --exclude-from 引用,比在命令行堆砌多个 --exclude 更易维护和审查。
4. 日志轮转
长期运行的部署脚本必须配合日志轮转,避免磁盘写满:
bash
# /etc/logrotate.d/nginx-deploy
/var/log/nginx_deploy.log {
daily
rotate 30
compress
missingok
notifempty
}
五、进阶扩展方向
当基础脚本稳定运行后,可按需叠加以下能力:
- 原子部署 :同步到临时目录 →
mv替换 → 回滚机制 - 多节点扇出:循环遍历服务器列表,并行或串行推送
- 健康检查:reload 后 curl 验证关键接口返回 200
- 通知集成:成功/失败时发送钉钉/企微/邮件告警
- 版本标记 :在目标目录写入
.deploy_version文件,便于排查
📌 原则:先让 V2.0 稳定运行至少一个月,再考虑扩展。过早优化是自动化脚本腐化的常见原因。
六、常见踩坑速查表
| 现象 | 根因 | 解决方案 |
|---|---|---|
| 脚本卡在密码提示 | 未配置免密或未加 BatchMode |
配置 SSH 密钥 + 添加 -o BatchMode=yes |
| 部署后 Nginx 403 | 文件所有者不正确 | 添加 --chown=nginx_user:nginx_user |
| 目标目录结构多一层 | 源路径末尾缺少 / |
确保 $SRC 以 / 结尾 |
--delete 清空了上传文件 |
目标目录混用了非托管内容 | 移除 --delete 或使用 exclude 保护 |
| Cron 中执行失败但手动正常 | 环境变量/PATH 差异 | 脚本中使用绝对路径,Cron 中 source 环境文件 |
| inotify watch 耗尽(实时场景) | 默认上限过低 | sysctl fs.inotify.max_user_watches=524288 |
七、结语
感谢您的阅读!如果你有任何疑问或想要分享的经验,请在评论区留言交流!