用 Shell 腳本自動化伺服器部署 文章首圖

用 Shell 腳本自動化伺服器部署

用 Shell 腳本自動化伺服器部署

在現代 DevOps 實踐中,重複手動執行部署指令不僅效率低落,更容易因人為疏失導致服務中斷。對於擁有基礎 Linux 操作經驗的管理者而言,將部署流程封裝成 Shell 腳本,是邁向自動化與可重複性(Reproducibility)的關鍵一步。本文將引導你建立一個標準化的自動化部署腳本,適用於 Ubuntu 22.04 或 Debian 12 環境。

前置準備與環境設定

首先,我們需要建立一個專門存放腳本的目錄,並賦予其執行權限。假設我們要在 /opt/scripts 下建立一個名為 deploy.sh 的文件。

sudo mkdir -p /opt/scripts
sudo touch /opt/scripts/deploy.sh
sudo chmod +x /opt/scripts/deploy.sh

在撰寫腳本之前,請確保目標伺服器已安裝必要的基礎工具,如 gitcurl 以及 systemd(Debian/Ubuntu 預設已包含)。若需使用特定軟體版本,建議在腳本開頭加入版本檢查機制。

腳本結構設計

一個健壯的部署腳本應包含以下核心區塊:錯誤處理、依賴檢查、程式碼拉取、依賴安裝、服務重啟與日誌記錄。以下是完整的腳本範例:

#!/bin/bash

# 設定嚴格模式,任何指令失敗即中止腳本
set -euo pipefail

# 變數定義
APP_NAME="myapp"
APP_DIR="/var/www/${APP_NAME}"
BACKUP_DIR="/var/backups/${APP_NAME}"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)

# 顏色定義,用於終端輸出提示
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m' # No Color

# 函式:記錄日誌
log() {
    echo -e "$(date '+%Y-%m-%d %H:%M:%S') - $1"
}

# 函式:錯誤處理
error_exit() {
    echo -e "${RED}ERROR: $1${NC}" >&2
    exit 1
}

# 主流程開始
main() {
    log "開始部署 ${APP_NAME}..."

    # 1. 檢查依賴
    check_dependencies

    # 2. 建立備份目錄
    mkdir -p "${BACKUP_DIR}"

    # 3. 備份當前版本(若存在)
    if [ -d "${APP_DIR}" ]; then
        log "正在備份當前版本至 ${BACKUP_DIR}/${TIMESTAMP}"
        cp -r "${APP_DIR}" "${BACKUP_DIR}/${TIMESTAMP}"
    fi

    # 4. 拉取最新程式碼
    log "正在拉取最新程式碼..."
    # 假設使用 Git,請根據實際版本控制系統調整
    if [ -d "${APP_DIR}/.git" ]; then
        cd "${APP_DIR}" || error_exit "無法進入應用程式目錄"
        git pull origin main || error_exit "Git pull 失敗"
    else
        # 若是首次部署,需先 clone
        log "首次部署,正在初始化倉庫..."
        git clone https://github.com/example/${APP_NAME}.git "${APP_DIR}" || error_exit "Git clone 失敗"
    fi

    # 5. 安裝依賴(以 Node.js 為例,請依專案語言調整)
    log "正在安裝依賴..."
    if [ -f "${APP_DIR}/package.json" ]; then
        cd "${APP_DIR}" || error_exit "無法進入應用程式目錄"
        npm ci --production || error_exit "依賴安裝失敗"
    fi

    # 6. 重啟服務
    log "正在重啟服務..."
    systemctl restart "${APP_NAME}" || error_exit "服務重啟失敗"

    # 7. 驗證服務狀態
    if systemctl is-active --quiet "${APP_NAME}"; then
        log -e "${GREEN}部署成功!服務狀態正常。${NC}"
    else
        error_exit "部署失敗,服務未正常啟動。請檢查日誌。"
    fi
}

# 函式:檢查必要工具
check_dependencies() {
    local deps=("git" "systemctl" "npm") # 依需求增加
    for dep in "${deps[@]}"; do
        if ! command -v "$dep" &> /dev/null; then
            error_exit "缺少必要工具: ${dep}。請先安裝。"
        fi
    done
}

# 執行主函式
main "$@"

腳本邏輯解析

上述腳本運用了幾個關鍵的 Shell 特性來確保穩定性:

  1. set -euo pipefail:這是 Shell 腳本的最佳實踐。-e 確保任何命令失敗立即退出;-u 防止使用未定義變數;-o pipefail 確保管線中任一命令失敗即視為整體失敗。
  2. 變數化路徑:將路徑與名稱定義為變數,未來若要遷移伺服器或更改專案名稱,只需修改頂部變數即可,無需深入腳本內部尋找替換。
  3. 備份機制:在覆蓋檔案前,先將舊目錄複製至 /var/backups。這是防止部署失敗後無法回滾的安全網。
  4. 依賴檢查:在執行主邏輯前,先驗證 gitnpm 等工具是否存在,避免中途因環境缺失而產生難以除錯的錯誤。

常見問題與解決方案

Q1:腳本執行時出現 "Permission denied" 錯誤?

這通常發生在嘗試寫入系統目錄(如 /var/www)或重啟服務時。請確保執行腳本的使用者具有 sudo 權限,或者在腳本開頭使用 sudo 提升權限。另一種做法是將應用程式目錄的所有者改為普通使用者,並授予該使用者對 /var/www 的寫入權限,但需注意安全性風險。

Q2:如何處理不同環境(開發、測試、生產)的配置差異?

硬編碼配置會導致部署困難。建議使用環境變數或配置文件。例如,在腳本開頭讀取 .env 文件:

if [ -f .env ]; then
    set -a
    source .env
    set +a
fi

這樣可以在不同環境的伺服器上放置不同的 .env 文件,腳本自動讀取對應的配置,無需修改腳本本身。

小結

透過撰寫結構化的 Shell 腳本,我們不僅將部署過程自動化,更將其標準化與可視化。雖然本文範例以 Node.js 為例,但此邏輯可輕鬆擴展至 Python、Go 或 Java 專案。建議讀者將此腳本結合 Cron 或 CI/CD 工具(如 GitHub Actions 或 Jenkins),進一步實現完整的自動化工作流。記住,自動化不僅是為了節省時間,更是為了確保每一次部署的一致性與可靠性。