寫一個自動部署 Web 應用的腳本 文章首圖

寫一個自動部署 Web 應用的腳本

寫一個自動部署 Web 應用的腳本

在現代 DevOps 實踐中,自動化部署不僅能減少人為錯誤,更能顯著提升開發與營運的效率。對於剛接觸 Linux 自動化的小白或資深管理員而言,撰寫一個健壯的 Shell 腳本來自動化 Web 應用(例如基於 Nginx 和 Node.js 或 Python Flask 的應用)的部署流程,是一個極佳的入門專案。

今天,我們將撰寫一個通用型的自動部署腳本。這個腳本將模擬典型的 CI/CD 流程:拉取最新程式碼、安裝依賴、停止舊服務、重新啟動新服務,並記錄日誌。我們將以 Ubuntu 22.04 或 Debian 12 為基礎環境進行示範。

腳本設計思路

一個可靠的部署腳本應該包含以下核心步驟:

  1. 環境檢查:確認必要工具(如 git, systemctl)是否存在。
  2. 變數配置:將易變的路徑、專案名稱等參數化,方便重複使用。
  3. 版本控制:拉取最新程式碼並記錄當前 Git Commit ID。
  4. 依賴安裝:根據應用類型執行 npm installpip install
  5. 服務重啟:安全地重啟應用服務並檢查狀態。
  6. 錯誤處理與日誌:確保任何失敗都能被捕捉並記錄,避免系統處於不一致狀態。

完整部署腳本範例

以下是一個名為 deploy.sh 的完整腳本。請將其儲存至你的專案根目錄,並賦予執行權限。

#!/bin/bash

# 自動部署 Web 應用腳本
# 適用於 Ubuntu 22.04 / Debian 12

set -e  # 遇到錯誤立即退出

# --- 配置區 ---
APP_NAME="my-web-app"
DEPLOY_DIR="/opt/${APP_NAME}"
REPO_URL="https://github.com/example/${APP_NAME}.git"
SERVICE_USER="www-data"
LOG_FILE="/var/log/${APP_NAME}_deploy.log"

# --- 輔助函數 ---
log() {
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    echo "[${timestamp}] $1" | tee -a "${LOG_FILE}"
}

error_exit() {
    log "ERROR: $1"
    exit 1
}

# --- 主要邏輯 ---
log "開始部署 ${APP_NAME} ..."

# 1. 環境檢查
if ! command -v git &> /dev/null; then
    error_exit "未找到 git,請先執行 sudo apt install git"
fi

if ! command -v systemctl &> /dev/null; then
    error_exit "未找到 systemctl,請確認系統為 systemd 環境"
fi

# 2. 建立目錄結構
if [ ! -d "${DEPLOY_DIR}" ]; then
    log "建立部署目錄: ${DEPLOY_DIR}"
    sudo mkdir -p "${DEPLOY_DIR}"
    sudo chown -R "${SERVICE_USER}:${SERVICE_USER}" "${DEPLOY_DIR}"
fi

# 3. 拉取最新程式碼
cd "${DEPLOY_DIR}" || error_exit "無法切換至目錄 ${DEPLOY_DIR}"

if [ -d ".git" ]; then
    log "拉取最新程式碼..."
    sudo -u "${SERVICE_USER}" git pull origin main || error_exit "Git pull 失敗"
else
    log "初始化 Git 倉庫..."
    sudo -u "${SERVICE_USER}" git init
    sudo -u "${SERVICE_USER}" git remote add origin "${REPO_URL}"
    sudo -u "${SERVICE_USER}" git fetch origin
    sudo -u "${SERVICE_USER}" git checkout -b main origin/main || error_exit "Git checkout 失敗"
fi

# 記錄當前部署的版本
CURRENT_COMMIT=$(sudo -u "${SERVICE_USER}" git rev-parse --short HEAD)
log "當前部署版本: ${CURRENT_COMMIT}"

# 4. 安裝依賴 (以 Node.js 為例,請依實際應用調整)
log "安裝依賴..."
# 假設專案中有 package.json
if [ -f "package.json" ]; then
    sudo -u "${SERVICE_USER}" npm install --production || error_exit "npm install 失敗"
elif [ -f "requirements.txt" ]; then
    sudo -u "${SERVICE_USER}" pip install -r requirements.txt || error_exit "pip install 失敗"
fi

# 5. 重新啟動服務
log "重啟服務 ${APP_NAME} ..."
sudo systemctl restart "${APP_NAME}" || error_exit "服務重啟失敗"

# 6. 驗證服務狀態
sleep 2
if sudo systemctl is-active --quiet "${APP_NAME}"; then
    log "部署成功!服務 ${APP_NAME} 正在運行。"
else
    error_exit "服務 ${APP_NAME} 啟動後未處於 active 狀態,請檢查日誌。"
fi

log "部署流程結束。"

如何使用與設定

  1. 建立系統服務檔案: 為了讓腳本能透過 systemctl 管理應用,你需要建立一個 systemd unit 檔案。例如 /etc/systemd/system/my-web-app.service

    [Unit]
    Description=My Web Application
    After=network.target
    
    [Service]
    User=www-data
    WorkingDirectory=/opt/my-web-app
    ExecStart=/usr/bin/node /opt/my-web-app/index.js
    Restart=always
    Environment=NODE_ENV=production
    
    [Install]
    WantedBy=multi-user.target
  2. 賦予執行權限

    chmod +x deploy.sh
  3. 執行部署: 由於腳本中使用了 sudo 來操作系統資源,建議以 root 或具有 sudo 權限的使用者執行:

    sudo ./deploy.sh

常見問題與解決方案

1. 權限不足導致部署失敗

問題:腳本在 git pullnpm install 時報錯 Permission denied原因:Web 應用通常由非 root 使用者(如 www-datanode)運行,但部署過程可能需要寫入目錄或執行系統級命令。 解決方案

  • 確保腳本中所有寫入目錄的操作都使用 sudo -u <service_user> 來模擬服務使用者。
  • 在部署前,確認 /opt/my-web-app 的擁有者是正確的服務使用者,而非 root。若為 root 擁有,後續 git pull 可能會因為權限衝突而失敗。

2. 服務重啟後立即失敗

問題:腳本顯示 Restart=always 成功,但 systemctl status 顯示服務處於 failed 狀態。 原因:應用啟動需要時間初始化(如連接資料庫、載入模型),腳本中的 sleep 2 可能不足,或者應用本身配置有誤。 解決方案

  • 在腳本中增加更嚴格的檢查機制,例如使用 systemctl is-active 輪詢多次。
  • 檢查應用日誌:journalctl -u my-web-app -n 50 --no-pager,確認錯誤原因通常是環境變數缺失或資料庫連線超時。

小結

透過撰寫這個自動部署腳本,我們不僅實現了程式碼的自動化更新,更建立了一套標準化的部署規範。這個腳本結合了 set -e 的錯誤處理、日誌記錄以及權限管理,是構建更複雜 CI/CD 管道(如使用 GitHub Actions 或 Jenkins)的基礎。

建議後續可以將此腳本整合進 Git Hook 中,當程式碼推送到主分支時自動觸發部署,進一步實現真正的持續整合與持續部署(CI/CD)。記住,在自動化之前,先確保手動流程的穩定性,這是 Linux 系統管理的重要原則。