用 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
在撰寫腳本之前,請確保目標伺服器已安裝必要的基礎工具,如 git、curl 以及 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 特性來確保穩定性:
set -euo pipefail:這是 Shell 腳本的最佳實踐。-e確保任何命令失敗立即退出;-u防止使用未定義變數;-o pipefail確保管線中任一命令失敗即視為整體失敗。- 變數化路徑:將路徑與名稱定義為變數,未來若要遷移伺服器或更改專案名稱,只需修改頂部變數即可,無需深入腳本內部尋找替換。
- 備份機制:在覆蓋檔案前,先將舊目錄複製至
/var/backups。這是防止部署失敗後無法回滾的安全網。 - 依賴檢查:在執行主邏輯前,先驗證
git、npm等工具是否存在,避免中途因環境缺失而產生難以除錯的錯誤。
常見問題與解決方案
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),進一步實現完整的自動化工作流。記住,自動化不僅是為了節省時間,更是為了確保每一次部署的一致性與可靠性。