脚本中自动化发版脚本如何设计

wen 实用脚本 3

本文目录导读:

脚本中自动化发版脚本如何设计

  1. 核心设计原则 (Design Principles)
  2. 通用发版流程架构
  3. 脚本实现细节与示例 (Bash + Makefile)
  4. 不同技术栈的差异适配
  5. 高级特性与最佳实践
  6. 脚本设计检查清单

可靠、可重复、可回滚,一个好的发版脚本应该像流水线一样,将人为失误降到最低。

下面是一个通用的、生产级别的自动化发版脚本设计方案,涵盖了从代码检出到服务重启的全流程。


核心设计原则 (Design Principles)

  1. 幂等性:脚本多次执行,结果相同,比如第一次失败,修复后重跑不会造成冲突。
  2. 原子性:发版过程要么全成功,要么全失败,避免“半发布”状态。
  3. 最小影响:优先使用灰度发布或滚动更新,避免全量停服。
  4. 可观测性:每一步都有清晰的日志、耗时和状态输出。
  5. 安全性:敏感信息(密码、Token)从环境变量或密钥管理服务获取,不硬编码。

通用发版流程架构

一个标准的自动化发版脚本通常包含以下 5个阶段

graph TD
    A[开始] --> B{阶段1: 准备}
    B --> C[阶段2: 构建]
    C --> D[阶段3: 部署]
    D --> E[阶段4: 验证]
    E --> F[阶段5: 收尾]
    F --> G[结束]
    subgraph 准备
        B1[代码Checkout] --> B2[版本号管理]
        B2 --> B3[环境变量/配置注入]
    end
    subgraph 构建
        C1[编译/打包 (如 mvn/gradle/npm/pip)] --> C2[单元测试/代码扫描]
        C2 --> C3[构建产物归档 (如 tar.jar Docker Image)]
    end
    subgraph 部署
        D1[备份当前版本] --> D2[传输新版本]
        D2 --> D3[停止旧服务/容器]
        D3 --> D4[启动新服务/容器]
    end
    subgraph 验证
        E1[健康检查 (Health Check)] --> E2[接口冒烟测试 (Smoke Test)]
        E2 --> E3[依赖服务连通性检查]
    end
    subgraph 收尾
        F1[通知 (钉钉/飞书/邮件)] --> F2[清理旧备份]
        F2 --> F3[更新发布记录]
    end

脚本实现细节与示例 (Bash + Makefile)

我们将使用 Makefile 作为顶层调度器,配合 Bash 脚本 实现具体逻辑,这是 Linux 环境下最常见的组合。

目录结构设计

/opt/deploy/
├── deploy.sh          # 主入口脚本
├── lib/
│   ├── utils.sh       # 通用工具函数
│   ├── build.sh       # 构建模块
│   └── deploy.sh      # 部署模块
├── config/
│   ├── prod.env       # 生产环境变量
│   └── test.env       # 测试环境变量
└── logs/              # 发布日志

主入口脚本 deploy.sh

#!/bin/bash
set -euo pipefail   # 关键:出错即停,未定义变量报错,管道错误捕获
# 加载配置
source "./config/${ENV:-prod}.env"
source "./lib/utils.sh"
# 全局变量
WORKSPACE=$(pwd)
RELEASE_ID=$(date +%Y%m%d%H%M%S)  # 唯一ID,用于回滚
BACKUP_DIR="/data/backup/${PROJECT_NAME}/${RELEASE_ID}"
# 帮助信息
usage() {
    echo "Usage: $0 {deploy|rollback|status} [version]"
    exit 1
}
# 主函数
main() {
    local action=${1:-deploy}
    case $action in
        deploy)
            validate_env
            log_info "开始发布 ${PROJECT_NAME} - ${RELEASE_ID}"
            prepare
            build
            deploy_app
            validate
            notify
            ;;
        rollback)
            rollback_version=${2:-$(find /data/backup/${PROJECT_NAME} -maxdepth 1 -type d | tail -2 | head -1)}
            rollback $rollback_version
            ;;
        status)
            health_check
            ;;
        *)
            usage
            ;;
    esac
}
# 执行
main "$@"

各模块实现 lib/deploy.sh

#!/bin/bash
deploy_app() {
    log_info "开始部署..."
    # Step 1: 备份当前版本
    mkdir -p $BACKUP_DIR
    cp -r /data/app/${PROJECT_NAME} $BACKUP_DIR/current_bak
    log_info "备份完成: $BACKUP_DIR"
    # Step 2: 停服务 (优雅关闭)
    systemctl stop ${PROJECT_NAME}.service || true
    sleep 5
    if systemctl is-active --quiet ${PROJECT_NAME}.service; then
        log_error "服务无法停止,强制终止"
        systemctl kill ${PROJECT_NAME}.service
    fi
    # Step 3: 更新文件
    rsync -a --delete /tmp/build/${PROJECT_NAME}/ /data/app/${PROJECT_NAME}/
    # Step 4: 更新配置 (从KMS/Consul获取,不硬编码)
    update_config_from_vault
    # Step 5: 启动服务
    systemctl daemon-reload
    systemctl start ${PROJECT_NAME}.service
    log_info "部署完成,服务已启动"
}
rollback() {
    local version=$1
    log_info "开始回滚至: $version/current_bak"
    systemctl stop ${PROJECT_NAME}.service || true
    rsync -a --delete $version/current_bak/ /data/app/${PROJECT_NAME}/
    systemctl start ${PROJECT_NAME}.service
    log_info "回滚完成"
}

验证模块 lib/validate.sh

#!/bin/bash
validate() {
    local retries=0
    local max_retries=12   # 最大等待2分钟
    local sleep_seconds=10
    log_info "开始健康检查..."
    # 等待端口监听
    while [ $retries -lt $max_retries ]; do
        if curl -sf http://127.0.0.1:${PORT}/health | grep -q 'OK'; then
            log_success "健康检查通过"
            break
        fi
        retries=$((retries+1))
        sleep $sleep_seconds
    done
    if [ $retries -eq $max_retries ]; then
        log_error "健康检查失败,启动自动回滚"
        rollback
        exit 1
    fi
    # 执行冒烟测试 (可选)
    curl -s http://127.0.0.1:${PORT}/api/smoke && log_success "冒烟测试通过"
}

工具函数 lib/utils.sh

#!/bin/bash
log_info() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] $1"
}
log_error() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR] $1" >&2
}
log_success() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] [SUCCESS] ✔ $1"
}
validate_env() {
    if [[ -z "${PROJECT_NAME:-}" || -z "${PORT:-}" ]]; then
        log_error "环境变量缺失,请检查 config 文件"
        exit 1
    fi
}

不同技术栈的差异适配

Java (Spring Boot)

  • 构建: mvn clean package -DskipTestsgradle build
  • 产物: *.jar
  • 部署: 直接 java -jar app.jar 或 Docker 化。
  • 注意: 需配置 JVM 参数、GC 日志等。

Node.js (Express/Nest)

  • 构建: npm ci --only=production && npm run build
  • 产物: 编译后的 dist/ 文件夹 + node_modules
  • 部署: pm2 startOrReload ecosystem.config.js
  • 注意: Node 版本管理 (nvm),避免包缺失。

Python (Flask/Django)

  • 构建: pip install -r requirements.txt -t /tmp/deps
  • 产物: Python 代码 + 依赖包
  • 部署: 使用 UWSGI/Gunicorn + Nginx,重启 WSGI 进程。
  • 注意: 虚拟环境隔离。

静态前端 (Vue/React)

  • 构建: npm run build
  • 产物: dist/ 下的 index.html + js/css
  • 部署: 直接覆盖 Nginx/CDN 的静态文件目录。
  • 注意: 利用版本号或 hash 文件名避免缓存问题。

Docker 容器化

  • 构建: docker build -t myapp:$RELEASE_ID .
  • 部署: docker stack deploy / kubectl apply -f deployment.yaml
  • 注意: Kubernetes 中使用 imagePullPolicy: Always 或指定 tag。

高级特性与最佳实践

灰度/金丝雀发布

deploy.sh 中使用 if 逻辑:

if [[ "${ENV}" == "prod" && "${TRAFFIC}" == "10%" ]]; then
    deploy_to_canary
    sleep 300  # 观察5分钟
    deploy_to_rest
fi

安全与配置管理

  • 使用 Vault / AWS Secrets Manager 获取数据库密码。
  • 使用 GitOps (ArgoCD/Flux) 管理 K8s YAML,避免 SSH 到服务器。

回滚机制

  • 快照回滚:备份整个 /data/app/ 目录 + 数据库 Schema。
  • 版本回滚./deploy.sh rollback [version tag]
  • 数据库回滚:执行对应的 Vxxx__rollback.sql 脚本。

通知与告警

notify() 函数中集成 Webhook:

notify() {
    curl -X POST -H 'Content-Type: application/json' \
        -d '{"msgtype":"text","text":{"content":"发版成功: '$PROJECT_NAME' '$RELEASE_ID'"}}' \
        https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx
}

CI/CD 集成

Jenkinsfile / GitLab CI 中调用脚本:

stages:
  - build
  - deploy
deploy-prod:
  stage: deploy
  script:
    - scp deploy.sh user@prod-server:/opt/deploy/
    - ssh user@prod-server "cd /opt/deploy && bash deploy.sh deploy"
  only:
    - tags

脚本设计检查清单

  • [ ] 环境分离: 测试、预发、生产使用不同配置。
  • [ ] 错误处理: set -e,任何一步失败都中止并发送告警。
  • [ ] 备份: 部署前必须备份当前可运行版本。
  • [ ] 验证: 健康检查 + 冒烟测试,通过后才视为成功。
  • [ ] 回滚: 一键回滚到上一版本,且支持指定历史版本。
  • [ ] 审计: 每次发版记录日志到文件,包含开始时间、结束时间、版本号、操作人。
  • [ ] 幂等性: 多次执行结果一致,不会产生副作用。
  • [ ] 无锁: 避免同一时刻多人并发发版(可以通过 Jenkins 队列或 GitLab CI 锁)。

这个框架可以根据你的具体技术栈(Java、Node、Docker、K8s)调整构建和部署模块,但核心流程(准备->构建->部署->验证->收尾)是不变的。

抱歉,评论功能暂时关闭!