Skip to content

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

Linux 服务部署脚本从临时命令到生产级工具的演进之路

引言

每次更新服务都要手动操作?systemctl stopcp -rsystemctl 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.0v1.1v2.0v3.0v4.0
绝对路径
参数化
自动备份
状态检查
彩色输出
交互确认
快速回滚
代码行数11309040

选型建议

场景推荐版本理由
临时测试v2.0轻量快速,参数灵活
生产环境v3.0安全可控,状态可观测
频繁更新v4.0切换快,回滚秒级
团队协作v3.0 或 v4.0日志完善,操作规范

总结

从一个临时命令到生产级工具,核心演进思路:

  1. 先跑通:最简单的功能实现
  2. 找风险:无备份、硬编码、路径依赖
  3. 做通用:参数化,支持任意路径
  4. 加安全:备份、确认、状态检查
  5. 上体验:彩色输出、回滚能力

附录:完整源码

所有版本的完整脚本已整理,可直接复制使用:


本文脚本已在 CentOS 7/8、Ubuntu 20.04/22.04 环境测试通过。如有问题欢迎留言交流。

最后更新2026/06/17 08:43
如果你觉得这篇文章有帮助,或者想聊聊技术、工作,欢迎通过下面方式联系我:
contact fishfinal