# 检查运行用户if [ "$(id -u)" -ne 0 ]; then log_error "This script must be run as root" exit 1fi# 检查必要命令check_command() { if ! command -v "$1" &>/dev/null; then log_error "Required command not found: $1" exit 1 fi}check_command dockercheck_command nginxcheck_command systemctl# 检查磁盘空间check_disk_space() { local required_mb=$1 local available_mb=$(df -m / | awk 'NR==2 {print $4}') if [ "$available_mb" -lt "$required_mb" ]; then log_error "Insufficient disk space: ${available_mb}MB available, ${required_mb}MB required" exit 1 fi}check_disk_space 1024 # 需要至少 1GB
3.2 健壮的命令执行
# 带重试的命令执行retry() { local max_attempts=$1 local delay=$2 shift 2 local cmd=("$@") for ((i = 1; i <= max_attempts; i++)); do if "${cmd[@]}"; then return 0 fi log_warn "Attempt $i/$max_attempts failed. Retrying in ${delay}s..." sleep "$delay" done log_error "Command failed after $max_attempts attempts: ${cmd[*]}" return 1}# 使用示例retry 3 5 docker pull myapp:latest
第4节:部署脚本示例
4.1 完整部署脚本
#!/usr/bin/env bashset -euo pipefailIFS=$'\n\t'# 配置APP_NAME="myapp"DEPLOY_DIR="/opt/$APP_NAME"BACKUP_DIR="/opt/backups"LOG_FILE="/var/log/deploy_${APP_NAME}.log"DOCKER_IMAGE="registry.example.com/$APP_NAME"HEALTH_URL="http://localhost:8080/health"# 日志函数log_info() { echo -e "\033[0;32m[INFO]\033[0m $(date '+%Y-%m-%d %H:%M:%S') $*"; }log_error() { echo -e "\033[0;31m[ERROR]\033[0m $(date '+%Y-%m-%d %H:%M:%S') $*" >&2; }# 参数解析VERSION=""ENV="staging"DRY_RUN=falsewhile [[ $# -gt 0 ]]; do case $1 in -v|--version) VERSION="$2"; shift 2 ;; -e|--env) ENV="$2"; shift 2 ;; -d|--dry-run) DRY_RUN=true; shift ;; *) echo "Unknown: $1"; exit 1 ;; esacdone[ -z "$VERSION" ] && { log_error "Version required (-v)"; exit 1; }# 错误处理cleanup() { local exit_code=$? if [ $exit_code -ne 0 ]; then log_error "Deployment failed! Check log: $LOG_FILE" # 回滚逻辑 if [ -d "$BACKUP_DIR/latest" ]; then log_info "Rolling back to previous version..." rsync -a "$BACKUP_DIR/latest/" "$DEPLOY_DIR/" systemctl restart "$APP_NAME" fi fi exit $exit_code}trap cleanup EXIT# 主流程main() { log_info "Starting deployment: $APP_NAME v$VERSION ($ENV)" # 1. 预检查 log_info "Step 1/6: Pre-flight checks" [ "$(id -u)" -eq 0 ] || { log_error "Run as root"; exit 1; } command -v docker >/dev/null || { log_error "docker not found"; exit 1; } df -m / | awk 'NR==2 {if($4<1024) exit 1}' || { log_error "Low disk space"; exit 1; } # 2. 备份当前版本 log_info "Step 2/6: Backing up current version" mkdir -p "$BACKUP_DIR" rsync -a "$DEPLOY_DIR/" "$BACKUP_DIR/latest/" # 3. 拉取新镜像 log_info "Step 3/6: Pulling new image" if [ "$DRY_RUN" = false ]; then docker pull "$DOCKER_IMAGE:$VERSION" else log_info "[DRY RUN] Would pull: $DOCKER_IMAGE:$VERSION" fi # 4. 停止旧服务 log_info "Step 4/6: Stopping old service" systemctl stop "$APP_NAME" || true # 5. 启动新服务 log_info "Step 5/6: Starting new service" if [ "$DRY_RUN" = false ]; then docker run -d \ --name "$APP_NAME" \ --restart unless-stopped \ -p 8080:8080 \ -v "$DEPLOY_DIR/data:/app/data" \ "$DOCKER_IMAGE:$VERSION" fi # 6. 健康检查 log_info "Step 6/6: Health check" if [ "$DRY_RUN" = false ]; then for i in {1..30}; do if curl -sf "$HEALTH_URL" >/dev/null; then log_info "Health check passed!" log_success "Deployment completed successfully!" return 0 fi sleep 2 done log_error "Health check failed after 60 seconds" exit 1 fi}main "$@"