Appearance
Linux 服务部署脚本进阶:从 3 行临时命令到生产级通用工具

引言
每次更新服务都要手动操作?systemctl stop、cp -r、systemctl start 三连击,看似简单,却暗藏风险。
本文从一段临时命令开始,逐步重构为支持参数化、自动备份、状态检查、快速回滚的生产级通用部署脚本。读完你将收获:
- Bash 脚本从临时到生产级的演进思路
- 5 个版本的迭代过程与设计取舍
- 软链接实现零停机切换与快速回滚
- 可直接使用的完整脚本源码
第一阶段:最简可用(v1.0)
原始脚本
bash
#!/bin/bash
systemctl stop my-service.service && cp -r my-service-20260617-1455 /usr/local/bin/my-service && systemctl start my-service.service说明:这里使用的是
&&逻辑与操作符,表示前一个命令执行成功后才执行下一个,因此整个流程是严格串行的。复制操作会等待systemctl stop完成,服务启动也会等待复制完成,不存在"后台复制导致服务读到不完整文件"的风险。但即便如此,这个脚本仍然存在下面列出的其他问题。
问题分析
| 问题 | 影响 |
|---|---|
| 无备份机制 | 新版本有问题时无法快速回滚,只能手动找回旧版本 |
| 版本号硬编码 | 每次更新需手动修改脚本,无法复用 |
| 相对路径 | 执行目录变化时复制失败,缺乏健壮性 |
| 无状态检查 | 复制失败或服务启动失败时没有任何提示,运维人员无法及时感知 |
| 无日志输出 | 操作过程不可见,排查问题困难 |
第二阶段:修复路径问题(v1.1)
改进
bash
#!/bin/bash
systemctl stop my-service.service && cp -r /opt/releases/my-service-20260617-1455 /usr/local/bin/my-service && systemctl start my-service.service改进点
- 使用绝对路径,避免执行目录变化导致复制失败
仍存在的问题
- 无备份,无法回滚
- 版本号硬编码,不通用
- 无状态检查,无日志输出
第三阶段:参数化改造(v2.0)
改进
bash
#!/bin/bash
set -e
# 默认配置
DEFAULT_DEST="/usr/local/bin/my-service"
SERVICE_NAME="my-service"
# 参数检查
if [ $# -lt 1 ]; then
echo "用法: $0 <新版本路径> [目标路径]"
echo "示例: $0 /opt/releases/my-service-20260617-1455"
exit 1
fi
# 源路径处理(支持相对/绝对路径)
SRC="$1"
[[ "$SRC" != /* ]] && SRC="$(pwd)/$SRC"
# 目标路径
DEST="${2:-$DEFAULT_DEST}"
# 验证源路径存在
[ ! -e "$SRC" ] && echo "错误: 源路径不存在" && exit 1
echo "部署: $SRC → $DEST"
# 停止服务
systemctl stop "$SERVICE_NAME" 2>/dev/null || true
# 备份
[ -e "$DEST" ] && mv "$DEST" "${DEST}.bak.$(date +%Y%m%d%H%M%S)"
# 复制
cp -r "$SRC" "$DEST"
chmod +x "$DEST" 2>/dev/null || chmod +x "$DEST"/* 2>/dev/null || true
# 启动
systemctl start "$SERVICE_NAME"
# 状态检查
sleep 1
systemctl is-active --quiet "$SERVICE_NAME" && echo "✅ 部署成功" || echo "❌ 部署失败"使用示例
bash
# 绝对路径
./deploy.sh /opt/releases/my-service-20260617-1455
# 相对路径
./deploy.sh my-service-20260617-1455
# 指定目标路径
./deploy.sh /opt/releases/my-service-20260617-1455 /opt/app/my-service改进点
- 参数化版本号,脚本可复用
- 支持相对/绝对路径自动转换
- 自动备份当前版本
set -e确保任意命令失败时脚本退出- 添加简单的状态检查
第四阶段:生产级增强(v3.0)
改进
bash
#!/bin/bash
set -e
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
DEFAULT_DEST="/usr/local/bin/my-service"
SERVICE_NAME="my-service"
if [ $# -lt 1 ]; then
log_error "缺少参数!"
echo "用法: $0 <新版本路径> [目标路径]"
echo "示例: $0 /opt/releases/my-service-20260617-1455"
exit 1
fi
SRC="$1"
[[ "$SRC" != /* ]] && SRC="$(pwd)/$SRC"
DEST="${2:-$DEFAULT_DEST}"
[ ! -e "$SRC" ] && log_error "源路径不存在: $SRC" && exit 1
log_info "========== 部署信息 =========="
log_info "源路径: $SRC"
log_info "目标路径: $DEST"
log_info "服务名称: $SERVICE_NAME"
log_info "=============================="
read -p "确认继续部署? (y/N) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
log_warn "部署已取消"
exit 0
fi
log_info "停止服务: $SERVICE_NAME"
systemctl stop "$SERVICE_NAME" 2>/dev/null || log_warn "服务未运行或不存在"
if [ -e "$DEST" ]; then
BACKUP="${DEST}.bak.$(date +%Y%m%d%H%M%S)"
log_info "备份当前版本到: $BACKUP"
mv "$DEST" "$BACKUP"
fi
log_info "复制新版本..."
if [ -d "$SRC" ]; then
cp -r "$SRC" "$DEST"
elif [ -f "$SRC" ]; then
cp "$SRC" "$DEST"
fi
if [ -d "$DEST" ]; then
chmod +x "$DEST"/* 2>/dev/null || true
elif [ -f "$DEST" ]; then
chmod +x "$DEST"
fi
log_info "启动服务: $SERVICE_NAME"
systemctl start "$SERVICE_NAME"
sleep 2
if systemctl is-active --quiet "$SERVICE_NAME"; then
log_info "✅ 服务启动成功!"
systemctl status "$SERVICE_NAME" --no-pager -l
else
log_error "❌ 服务启动失败!"
systemctl status "$SERVICE_NAME" --no-pager -l
log_error "请检查日志: journalctl -u $SERVICE_NAME -n 50"
exit 1
fi
log_info "========== 部署完成 =========="新增特性
- 彩色输出,信息清晰
- 交互确认,防止误操作
- 启动后自动检查服务状态
- 失败时显示错误日志排查命令
- 支持文件和目录两种来源类型
第五阶段:软链接与快速回滚(v4.0)
对于频繁更新的服务,每次复制整个目录效率低且占用磁盘空间。使用软链接方案,版本切换仅需修改链接指向,实现零停机回滚。
改进
bash
#!/bin/bash
set -e
SRC="$1"
[ -z "$SRC" ] && echo "用法: $0 <新版本目录>" && exit 1
[[ "$SRC" != /* ]] && SRC="$(pwd)/$SRC"
[ ! -d "$SRC" ] && echo "错误: 目录不存在" && exit 1
SERVICE_NAME="my-service"
LINK_PATH="/usr/local/bin/my-service"
BACKUP_DIR="/opt/backups/my-service"
mkdir -p "$BACKUP_DIR"
systemctl stop "$SERVICE_NAME"
if [ -L "$LINK_PATH" ]; then
CURRENT=$(readlink "$LINK_PATH")
[ -d "$CURRENT" ] && cp -r "$CURRENT" "$BACKUP_DIR/$(basename "$CURRENT").$(date +%Y%m%d%H%M%S)"
fi
rm -f "$LINK_PATH"
ln -s "$SRC" "$LINK_PATH"
systemctl start "$SERVICE_NAME"
systemctl status "$SERVICE_NAME" --no-pager
echo ""
echo "🔙 回滚命令:"
echo "rm -f $LINK_PATH && ln -s <旧版本路径> $LINK_PATH && systemctl restart $SERVICE_NAME"快速回滚示例
bash
# 当前链接指向新版本
ls -l /usr/local/bin/my-service
# /usr/local/bin/my-service -> /opt/releases/my-service-20260617-1455
# 发现问题,回滚到上一个版本
rm -f /usr/local/bin/my-service
ln -s /opt/releases/my-service-20260617-1440 /usr/local/bin/my-service
systemctl restart my-service.service各版本特性对比
| 特性 | v1.0 | v1.1 | v2.0 | v3.0 | v4.0 |
|---|---|---|---|---|---|
| 绝对路径 | ❌ | ✅ | ✅ | ✅ | ✅ |
| 参数化 | ❌ | ❌ | ✅ | ✅ | ✅ |
| 自动备份 | ❌ | ❌ | ✅ | ✅ | ✅ |
| 状态检查 | ❌ | ❌ | ✅ | ✅ | ✅ |
| 彩色输出 | ❌ | ❌ | ❌ | ✅ | ❌ |
| 交互确认 | ❌ | ❌ | ❌ | ✅ | ❌ |
| 快速回滚 | ❌ | ❌ | ❌ | ❌ | ✅ |
| 代码行数 | 1 | 1 | 30 | 90 | 40 |
选型建议
| 场景 | 推荐版本 | 理由 |
|---|---|---|
| 临时测试 | v2.0 | 轻量快速,参数灵活 |
| 生产环境 | v3.0 | 安全可控,状态可观测 |
| 频繁更新 | v4.0 | 切换快,回滚秒级 |
| 团队协作 | v3.0 或 v4.0 | 日志完善,操作规范 |
总结
从一个临时命令到生产级工具,核心演进思路:
- 先跑通:最简单的功能实现
- 找风险:无备份、硬编码、路径依赖
- 做通用:参数化,支持任意路径
- 加安全:备份、确认、状态检查
- 上体验:彩色输出、回滚能力
附录:完整源码
所有版本的完整脚本已整理,可直接复制使用:
本文脚本已在 CentOS 7/8、Ubuntu 20.04/22.04 环境测试通过。如有问题欢迎留言交流。
