如何在Ubuntu上编写自动化部署脚本?

来源:前端技术作者:王柏年头衔:网络博主
导读:本期聚焦于王柏年创作的《如何在Ubuntu上编写自动化部署脚本?》,敬请观看详情。手动登录服务器、拉取代码、安装依赖、重启服务,每次发布都要重复操作十几分钟,还容易漏掉步骤。把这一套流程写成脚本,一条命令完成部署,是Ubuntu服务器运维的基本功。本文围绕自动化部署脚本的编写,从环境准备、脚本结构、关键命令、错误处理到定时任务集成,给出可直接使用的Bash脚本模板,并说明如何通过参数化配置适配不同项目。文中会演示拉取Git仓库、安装依赖、构建前端、重启Systemd服务等典型步骤,也会讨论日志记录、失败回滚和权限控制等生产环境细节。

在Ubuntu服务器上做应用部署,手动执行ssh登录、cd到项目目录、git pull、重新安装依赖、重启服务这一连串操作,不仅耗时而且容易出错。尤其是当项目同时包含前端构建、后端迁移、静态资源同步等多步骤时,人工操作只要漏掉一步,线上环境就可能出现代码与依赖不一致的问题。自动化部署脚本的核心价值就是把重复操作固化下来,减少人为失误,同时让每次部署过程都有日志可查、可回滚。

如何在Ubuntu上编写自动化部署脚本?

一、部署脚本的基础结构与执行策略

编写部署脚本的第一步是确定执行环境。在Ubuntu中,Bash是默认的登录Shell,直接使用#!/usr/bin/env bash作为脚本首行可以保证在不同用户环境下都能正确解释脚本。紧接着需要设置严格的错误处理选项,其中set -euo pipefail是最常用的一组参数。-e表示任何命令返回非零状态时立即退出脚本,-u会在引用未定义变量时报错,-o pipefail则让管道命令中任意一个环节失败都触发退出。这三个选项组合起来能避免很多因为命令失败但脚本继续执行而导致的部署事故。

除了错误处理,变量和日志函数也是脚本的基础。把应用名称、部署目录、Git仓库地址、分支名、服务名等信息集中定义成变量,后续修改时只需要改动一处。日志函数建议统一封装,输出内容同时写到终端和日志文件,这样既能实时观察部署进度,也能事后排查问题。下面是一个基础模板:

#!/usr/bin/env bash
set -euo pipefail

APP_NAME="my-app"
DEPLOY_DIR="/var/www/${APP_NAME}"
GIT_REPO="git@github.com:user/my-app.git"
BRANCH="main"
LOG_FILE="/var/log/deploy_${APP_NAME}.log"

log() {
  echo "[$(date '+%F %T')] $1" | tee -a "$LOG_FILE"
}

log "开始部署 ${APP_NAME}"

if [ ! -d "$DEPLOY_DIR" ]; then
  git clone "$GIT_REPO" "$DEPLOY_DIR"
else
  cd "$DEPLOY_DIR"
  git fetch origin
  git reset --hard "origin/${BRANCH}"
fi

log "代码更新完成"

这个模板虽然简单,但已经体现了自动化部署脚本的两个核心原则:任何步骤失败立即停止,以及每一步都有日志记录。实际生产环境中还会加入目录权限检查、磁盘空间检查等前置条件,不过这些可以在基础结构之上逐步扩展。

二、完整的部署流程:从拉取代码到重启服务

一个完整的部署流程通常包含代码更新、依赖安装、资源构建、服务重启四个阶段。在Ubuntu上使用Systemd管理应用服务时,重启服务一般通过systemctl restart完成。部署脚本需要根据项目类型判断使用什么包管理器,例如存在package.json就执行npm安装和构建,存在requirements.txt就执行pip安装。

下面这个示例整合了Node.js项目和Python项目常见的依赖处理逻辑。脚本假设项目已经提前克隆到服务器,部署时只做增量更新,避免每次重新克隆带来的网络开销。构建完成后调用Systemd重启服务,整个流程一条命令执行完毕。

#!/usr/bin/env bash
set -euo pipefail

APP_DIR="/opt/my-app"
SERVICE_NAME="my-app"
LOG_FILE="/var/log/deploy-my-app.log"

log() {
  echo "[$(date '+%F %T')] $1" | tee -a "$LOG_FILE"
}

log "开始部署"

cd "$APP_DIR"
log "拉取最新代码"
git fetch origin
git reset --hard origin/main

log "安装项目依赖"
if [ -f package.json ]; then
  npm ci
  npm run build
fi

if [ -f requirements.txt ]; then
  pip install -r requirements.txt
fi

log "重启应用服务"
sudo systemctl restart "$SERVICE_NAME"

log "部署完成"

这段脚本适合大多数单体应用。如果项目使用Docker部署,可以把systemctl restart替换成docker compose up -d --build,本质仍然是拉取最新代码后重新构建镜像并启动容器。对于前后端分离的项目,可以分别维护两个部署脚本,或者在一个脚本里按目录分别执行前端和后端的构建命令。关键是流程要清晰,每一步之间有明确的依赖关系,避免并行执行导致状态不一致。

三、错误捕获与自动回滚设计

部署脚本最大的风险不是失败,而是失败后留下一个半更新状态,导致线上服务不可用且难以快速恢复。为了避免这种情况,生产级部署脚本需要加入错误捕获和回滚机制。Bash提供了trap命令,可以捕获ERR信号,在脚本遇到错误时执行清理或回滚操作。结合版本目录和软链接切换,可以实现比较可靠的回滚方案。

回滚设计的基本思路是保留上一个可用版本,部署时先将新代码克隆到一个独立目录,构建完成后通过软链接切换当前版本。如果构建或重启失败,脚本自动将软链接指向旧版本并恢复服务。下面是一个带版本目录管理功能的示例:

#!/usr/bin/env bash
set -Eeuo pipefail

APP_DIR="/opt/my-app"
RELEASES_DIR="$APP_DIR/releases"
CURRENT_DIR="$APP_DIR/current"
SERVICE_NAME="my-app"
LOG_FILE="/var/log/deploy-my-app.log"

log() {
  echo "[$(date '+%F %T')] $1" | tee -a "$LOG_FILE"
}

cleanup() {
  if [ $? -ne 0 ]; then
    log "部署失败,开始回滚"
    if [ -d "$CURRENT_DIR" ]; then
      if [ -d "$RELEASES_DIR/previous" ]; then
        rm -rf "$CURRENT_DIR"
        mv "$RELEASES_DIR/previous" "$CURRENT_DIR"
        sudo systemctl restart "$SERVICE_NAME"
        log "回滚完成"
      else
        log "没有可用的回滚版本,请人工介入"
      fi
    fi
  fi
}
trap cleanup ERR

log "创建新版本目录"
mkdir -p "$RELEASES_DIR"
NEW_RELEASE="$RELEASES_DIR/$(date +%Y%m%d%H%M%S)"
git clone --depth 1 git@github.com:user/my-app.git "$NEW_RELEASE"

log "构建新版本"
cd "$NEW_RELEASE"
if [ -f package.json ]; then
  npm ci
  npm run build
fi

log "切换软链接并重启服务"
if [ -L "$CURRENT_DIR" ]; then
  mv "$CURRENT_DIR" "$RELEASES_DIR/previous"
fi
ln -sfn "$NEW_RELEASE" "$CURRENT_DIR"
sudo systemctl restart "$SERVICE_NAME"

log "部署成功"

这个方案的核心是利用时间戳创建独立版本目录,每次部署都保留旧版本。软链接current始终指向正在使用的版本,切换操作是原子性的,不会出现中间状态。即使新版本构建失败,由于trap cleanup ERR的存在,脚本会自动把旧版本重新链接回来并重启服务。这种设计在中小规模项目中足够使用,如果项目规模更大,可以考虑引入专门的发布工具,但原理是相通的。

四、多环境配置与定时自动部署

同一个项目往往需要部署到开发、测试、生产等多个环境,不同环境的数据库连接、API地址、域名等配置各不相同。把环境差异从脚本中剥离出来,通过外部配置文件注入,可以让同一套部署脚本在多个环境复用。Ubuntu下常见的做法是在项目目录放置.env文件,脚本在执行前加载该文件中的变量。

加载.env文件时使用set -a和set +a可以让文件中的所有变量自动导出到当前Shell环境,方便后续命令使用。这样生产环境的.env文件可以与代码仓库分离,只存在于服务器本地,避免敏感信息进入版本控制。

ENV_FILE="$APP_DIR/.env"
if [ -f "$ENV_FILE" ]; then
  set -a
  source "$ENV_FILE"
  set +a
fi

对于需要定期发布的场景,可以把部署脚本加入crontab定时任务。例如每天凌晨3点自动执行部署,适合内容更新频率较高的应用。将以下内容写入当前用户的crontab即可:

0 3 * * * /usr/local/bin/deploy.sh

定时任务执行时没有交互式终端,因此脚本中的所有命令都必须保证非交互模式可用,例如git操作需要使用SSH密钥认证而不是密码认证,sudo命令需要配置NOPASSWD或者使用具备相应权限的系统用户执行。同时日志文件要设置合理的轮转策略,避免长时间运行后日志无限增长占用磁盘空间。

自动化部署脚本Ubuntu部署Bash脚本修改时间:2026-09-28 18:58:06

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/0928/63096.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。