Skip to content

部署脚本

本节实现一个自动化应用部署脚本,支持版本管理、滚动发布、健康检查和一键回滚。

功能设计

  • 从 Git 拉取指定版本代码
  • 执行构建和依赖安装
  • 优雅停止旧服务
  • 启动新版本并验证健康状态
  • 失败时自动回滚

完整部署脚本

bash
#!/usr/bin/env bash
#
# 应用自动化部署脚本
# 用法:./deploy.sh <版本/分支> [--env production|staging]
#

set -euo pipefail

# ============================================================
# 配置区
# ============================================================
APP_NAME="myapp"
APP_USER="www-data"
DEPLOY_BASE="/opt/${APP_NAME}"
RELEASES_DIR="${DEPLOY_BASE}/releases"
SHARED_DIR="${DEPLOY_BASE}/shared"
CURRENT_LINK="${DEPLOY_BASE}/current"
REPO_URL="git@github.com:company/myapp.git"
KEEP_RELEASES=5                          # 保留最近 N 个版本

HEALTH_CHECK_URL="http://localhost:8080/health"
HEALTH_CHECK_RETRY=10
HEALTH_CHECK_INTERVAL=3

# ============================================================
# 工具函数
# ============================================================
log()     { echo "[$(date '+%H:%M:%S')] $*"; }
success() { echo "[$(date '+%H:%M:%S')] ✅ $*"; }
warn()    { echo "[$(date '+%H:%M:%S')] ⚠️  $*" >&2; }
error()   { echo "[$(date '+%H:%M:%S')] ❌ $*" >&2; exit 1; }

# 执行命令(以指定用户)
run_as() {
    sudo -u "$APP_USER" bash -c "$*"
}

# ============================================================
# 部署步骤
# ============================================================

# 1. 检查依赖
check_dependencies() {
    log "检查依赖..."
    for cmd in git curl systemctl; do
        command -v "$cmd" >/dev/null 2>&1 || error "缺少依赖:$cmd"
    done
    success "依赖检查通过"
}

# 2. 拉取代码
pull_code() {
    local version="$1"
    local release_dir="${RELEASES_DIR}/${TIMESTAMP}"

    log "拉取代码:版本 $version$release_dir"
    mkdir -p "$release_dir"

    git clone --depth 1 --branch "$version" "$REPO_URL" "$release_dir" \
        || error "代码拉取失败"

    success "代码拉取完成(commit: $(git -C "$release_dir" rev-parse --short HEAD))"
    echo "$release_dir"
}

# 3. 安装依赖
install_dependencies() {
    local release_dir="$1"
    log "安装依赖..."

    if [[ -f "${release_dir}/package.json" ]]; then
        cd "$release_dir" && npm ci --production 2>&1 | tail -5
    elif [[ -f "${release_dir}/requirements.txt" ]]; then
        pip install -r "${release_dir}/requirements.txt" -q
    elif [[ -f "${release_dir}/pom.xml" ]]; then
        cd "$release_dir" && mvn package -q -DskipTests
    fi

    success "依赖安装完成"
}

# 4. 创建共享目录链接
link_shared() {
    local release_dir="$1"
    log "链接共享目录..."

    mkdir -p "${SHARED_DIR}/logs" "${SHARED_DIR}/uploads" "${SHARED_DIR}/config"

    ln -sf "${SHARED_DIR}/logs"    "${release_dir}/logs"
    ln -sf "${SHARED_DIR}/uploads" "${release_dir}/uploads"
    ln -sf "${SHARED_DIR}/config"  "${release_dir}/config"

    success "共享目录链接完成"
}

# 5. 切换当前版本
switch_current() {
    local release_dir="$1"
    log "切换当前版本:$release_dir"
    ln -snf "$release_dir" "$CURRENT_LINK"
    success "版本切换完成"
}

# 6. 重启服务
restart_service() {
    log "重启服务:$APP_NAME"
    systemctl restart "$APP_NAME" || error "服务重启失败"
    success "服务重启完成"
}

# 7. 健康检查
health_check() {
    log "健康检查:$HEALTH_CHECK_URL"
    local retry=0

    while (( retry < HEALTH_CHECK_RETRY )); do
        if curl -sf "$HEALTH_CHECK_URL" > /dev/null 2>&1; then
            success "健康检查通过"
            return 0
        fi
        (( retry++ ))
        log "等待服务就绪... (${retry}/${HEALTH_CHECK_RETRY})"
        sleep "$HEALTH_CHECK_INTERVAL"
    done

    error "健康检查失败,服务未在预期时间内就绪"
}

# 8. 清理旧版本
cleanup_releases() {
    log "清理旧版本(保留最新 $KEEP_RELEASES 个)..."
    local releases
    releases=$(ls -1t "$RELEASES_DIR" | tail -n +$(( KEEP_RELEASES + 1 )))

    if [[ -z "$releases" ]]; then
        log "无需清理"
        return 0
    fi

    echo "$releases" | while read -r rel; do
        log "删除旧版本:$rel"
        rm -rf "${RELEASES_DIR:?}/$rel"
    done
    success "旧版本清理完成"
}

# 回滚到上一个版本
rollback() {
    warn "部署失败,开始回滚..."
    local prev_release
    prev_release=$(ls -1t "$RELEASES_DIR" | sed -n '2p')

    if [[ -z "$prev_release" ]]; then
        error "没有可回滚的版本"
    fi

    ln -snf "${RELEASES_DIR}/$prev_release" "$CURRENT_LINK"
    systemctl restart "$APP_NAME" || true

    warn "已回滚到版本:$prev_release"
}

# ============================================================
# 主流程
# ============================================================
VERSION="${1:?请提供部署版本或分支名}"
ENV="${2:-production}"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)

log "================================================"
log "开始部署 $APP_NAME"
log "  版本:$VERSION"
log "  环境:$ENV"
log "  时间:$TIMESTAMP"
log "================================================"

# 注册回滚钩子
trap rollback ERR

mkdir -p "$RELEASES_DIR" "$SHARED_DIR"

check_dependencies
release_dir=$(pull_code "$VERSION")
install_dependencies "$release_dir"
link_shared "$release_dir"
switch_current "$release_dir"
restart_service
health_check
cleanup_releases

# 取消回滚钩子
trap - ERR

success "================================================"
success "部署成功!"
success "  版本:$VERSION"
success "  路径:$release_dir"
success "================================================"

使用方法

bash
chmod +x deploy.sh

# 部署指定版本
./deploy.sh v1.2.3

# 部署到 staging 环境
./deploy.sh main --env staging

# 查看当前版本
readlink /opt/myapp/current

# 手动回滚
ls /opt/myapp/releases/   # 查看所有版本
ln -snf /opt/myapp/releases/<旧版> /opt/myapp/current
systemctl restart myapp