別再讓使用者看到「網站維護中」!WordPress 零停機部署終極實戰,從理論到腳本全解析

2025/08/22 | 架構與效能優化

別再讓使用者看到「網站維護中」!WordPress 零停機部署終極實戰,從理論到腳本全解析

嗨,我是浪花科技的資深工程師 Eric。不知道你是否也經歷過這種場景:半夜三點,盯著螢幕上的 FTP 上傳進度條,心裡默默祈禱這次更新不要把網站搞掛。客戶在睡覺,使用者最少,這是我們工程師公認的「黃金上線時段」。但說真的,誰喜歡在半夜工作?萬一出了錯,隔天還要頂著黑眼圈來救火,這根本是拿肝在寫 Code 啊!

傳統的部署方式,像是用 FTP 一個個檔案覆蓋,不僅效率低落,而且風險極高。在上傳過程中,網站很可能會處於一個「半新半舊」的尷尬狀態,輕則版面錯亂,重則直接噴出致命錯誤(White Screen of Death)。為了解決這個問題,很多開發者會選擇掛上「網站維護中」的頁面,但这無疑是告訴使用者:「我們現在很脆弱,請稍後再來。」這對使用者體驗、品牌形象甚至是 SEO 都是一種傷害。

今天,我就要來跟你聊聊一個能徹底終結這種半夜驚魂的部署魔法——零停機部署(Zero Downtime Deployment,簡稱 ZDD)。這不是什麼遙不可及的黑科技,而是現代化網站維運的標準作業流程。透過這篇文章,我會帶你從核心概念、實戰策略,一路拆解到部署腳本,讓你也能為你的 WordPress 網站實現優雅、安全、且隨時可進行的「無痛更新」。

為什麼你的 WordPress 網站需要「零停機部署」?

在我們深入技術細節之前,先來聊聊「為什麼」。你可能會想,我的網站流量不大,更新頻率也不高,有必要搞這麼複雜嗎?我的答案是:絕對有必要。這是一個專業與否的差異,也是一個能不能讓你睡個好覺的關鍵。

  • 保護使用者體驗 (UX): 想像一下,使用者正在你的 WooCommerce 網站上準備結帳,突然畫面就變成「系統維護中」。這筆訂單大概率就飛了。零停機部署確保使用者在更新過程中完全無感,流暢的體驗是留住使用者的第一步。
  • 避免商業損失: 對於電商、線上課程或任何有金流的網站來說,停機一分鐘都意味著白花花的銀子在流失。ZDD 直接將這種風險降到趨近於零。
  • SEO 友善: 搜尋引擎的爬蟲不喜歡看到網站頻繁出現 503 Service Unavailable 的錯誤。雖然短暫的維護影響不大,但頻繁或長時間的停機絕對會影響你的網站排名。
  • 解放開發流程: 這點是身為工程師最有感的!當你不再害怕「上線」這件事,你就能更頻繁、更小規模地進行部署。這完全符合敏捷開發(Agile)的精神:快速迭代、快速修正。你可以在白天隨時部署新功能,而不是把所有更新累積到半夜再一次「大爆炸」。

零停機部署的核心戰術:原子部署 (Atomic Deployment)

講了這麼多好處,那到底要怎麼做到呢?零停機部署的核心精神在於「原子性」(Atomicity)。這個詞源自資料庫領域,意思是一個操作要嘛就完全成功,要嘛就完全失敗,不存在中間狀態。套用在部署上,就是我們要讓網站版本的切換在「一瞬間」完成。

傳統 FTP 部署的災難現場

我們先來回顧一下傳統 FTP 部署為什麼不行。當你用 FTP 上傳新版檔案時,你是「一個一個檔案」覆蓋舊檔案。假設你有 100 個檔案要更新,在第 50 個檔案上傳完成時,你的網站程式碼庫就是一個混合了 50 個新檔案和 50 個舊檔案的「四不像」。如果新舊檔案的函式或類別不相容,網站就直接崩潰了。這不是部署,這是在玩踩地雷。

原子部署的魔法:善用 Symbolic Link (符號連結)

原子部署最常見且有效的實現方式,就是透過伺服器上的「符號連結」(Symbolic Link,或稱 symlink)。你可以把它想像成 Windows 上的「捷徑」或 macOS 上的「替身」。它本身不是真正的資料夾,而是一個指向另一個真實資料夾的「指標」。

我們的策略是,網站的根目錄(例如 Nginx 或 Apache 設定指向的目錄)本身是一個 symlink,它永遠指向當前「穩定且線上」的版本。當我們要部署新版本時,我們不是去動線上版本,而是在一個全新的資料夾裡把新版本準備好,準備完成後,只要「瞬間」修改 symlink 的指向,就完成了版本切換。整個過程快到使用者根本無法察覺。

一個典型的 ZDD 目錄結構會像這樣:

/var/www/my-site/
├── current -> /var/www/my-site/releases/20250822140000/  (這是個 symlink,指向當前的版本)
├── releases/
│   ├── 20250822130000/  (舊的版本)
│   └── 20250822140000/  (最新的版本,current 正指向這裡)
└── shared/
    ├── .env             (環境變數檔案)
    ├── wp-config.php    (WordPress 核心設定檔)
    └── web/app/uploads/ (使用者上傳的檔案目錄)

部署流程大致如下:

  1. 建立新版本目錄: CI/CD 工具(如 GitHub Actions)會在 releases 資料夾內建立一個以時間戳命名的全新目錄,例如 20250822150000
  2. 部署程式碼: 將最新的程式碼從 Git Repository 拉取到這個新目錄中。
  3. 安裝相依套件: 在新目錄裡執行 composer installnpm install 等指令,建置前端資源。
  4. 連結共享檔案: 建立 symlink,將新版本目錄中需要持久化的檔案或資料夾(如 wp-config.phpuploads 目錄)連結到 shared 目錄下的對應位置。這樣可以確保每次部署,這些檔案都不會被覆蓋。
  5. 執行測試: 運行自動化測試,確保新版本程式碼沒有問題。
  6. 原子切換: 這是最關鍵的一步!執行一個指令,將 current 這個 symlink 原本地指向舊版本,瞬間改為指向我們剛才準備好的新版本目錄。指令大概像這樣:ln -sfn /var/www/my-site/releases/20250822150000 /var/www/my-site/current。這個操作是原子性的,幾乎不耗時。
  7. 後續清理: 清除伺服器快取(如 OPcache、Redis),並刪除過舊的版本備份,只保留最近的幾個版本以便快速回滾。

部署腳本範例 (Bash Script)

光說不練假把戲,這裡提供一個簡化的 Bash 腳本,讓你感受一下整個流程。在實際的 CI/CD 環境中,這會被寫入像是 .github/workflows/deploy.yml 的設定檔裡。

#!/bin/bash
set -e # 如果任何指令失敗,立即停止腳本

# --- 變數設定 ---
REPO_URL="git@github.com:your-repo/my-site.git"
DEPLOY_PATH="/var/www/my-site"
RELEASES_DIR="${DEPLOY_PATH}/releases"
SHARED_DIR="${DEPLOY_PATH}/shared"
CURRENT_SYMLINK="${DEPLOY_PATH}/current"
KEEP_RELEASES=5 # 保留最近 5 個版本

# --- 部署流程 ---
RELEASE_NAME=$(date +"%Y%m%d%H%M%S")
NEW_RELEASE_PATH="${RELEASES_DIR}/${RELEASE_NAME}"

_log() {
    echo "[DEPLOY] $(date): $1"
}

_log "🚀 開始部署新版本: ${RELEASE_NAME}"

_log "1. 建立新版本目錄..."
git clone --depth 1 ${REPO_URL} ${NEW_RELEASE_PATH}

_log "2. 進入新目錄並安裝相依套件..."
cd ${NEW_RELEASE_PATH}
composer install --no-dev --optimize-autoloader
# npm install && npm run build

_log "3. 連結共享檔案與目錄..."
ln -sfn ${SHARED_DIR}/.env ${NEW_RELEASE_PATH}/.env
ln -sfn ${SHARED_DIR}/web/app/uploads ${NEW_RELEASE_PATH}/web/app/uploads

_log "4. 賦予伺服器寫入權限..."
chown -R www-data:www-data ${NEW_RELEASE_PATH}

_log "5. ✨ 原子切換!將 current 指向新版本..."
ln -sfn ${NEW_RELEASE_PATH} ${CURRENT_SYMLINK}

_log "6. 清理伺服器快取 (以 WP-CLI 為例)..."
# 確保 WP-CLI 可以透過 current 路徑正確執行
cd ${CURRENT_SYMLINK}
wp cache flush
wp transient delete --all
# 清除 OPcache 需要伺服器權限,可能透過 reload php-fpm 達成
sudo systemctl reload php8.2-fpm

_log "7. 清理舊版本..."
purge_count=$(ls -1dt ${RELEASES_DIR}/* | tail -n +$((${KEEP_RELEASES}+1)) | wc -l)
if [ "${purge_count}" -gt 0 ]; then
    _log "刪除 ${purge_count} 個舊版本..."
    ls -1dt ${RELEASES_DIR}/* | tail -n +$((${KEEP_RELEASES}+1)) | xargs rm -rf
fi

_log "✅ 部署完成!"

零停機部署的隱藏魔王:三大挑戰與解法

看起來很美好,對吧?但身為一個資深工程師,我有義務告訴你實作過程中會遇到的一些「隱藏魔王」。光是會切換 symlink 是不夠的,你還得處理好這些棘手的問題。

挑戰一:資料庫遷移 (Database Migration)

這是最常被忽略,也是最致命的問題。如果你的新版程式碼需要資料庫結構的變更(例如新增一個資料表、修改欄位),你必須確保在切換 symlink 的前後,新舊版本的程式碼都能兼容當下的資料庫狀態。否則,在切換的瞬間,舊程式碼面對新資料庫(或反之)可能會直接報錯。

解決方案是採用「向後兼容」的遷移策略,通常分為兩次部署:
1. 擴展 (Expand): 第一次部署,只做「增加」的操作。例如,新增資料表、新增欄位(但允許為 NULL)。此時,舊程式碼因為不認識這些新東西,會自然忽略它們,所以不會出錯。
2. 收縮 (Contract): 第二次部署,在新程式碼已經完全上線並穩定運行後,再部署一次,這次的任務是移除舊的、不再使用的資料庫欄位或資料表。

挑戰二:使用者上傳與 Session

我們已經透過 shared 目錄解決了 uploads 資料夾的問題。但另一個問題是 PHP 的 Session。如果你的 Session 是儲存在檔案系統中,那麼在部署的短暫時間差內,使用者 A 的請求可能落在舊版本,而下一個請求就跳到新版本,如果 Session 的儲存路徑不同步,就可能導致使用者被強制登出。解決方案是將 Session 統一儲存到 Redis 或資料庫中,使其與程式碼目錄脫鉤。

挑戰三:快取清理 (Cache Invalidation)

程式碼是新的,但使用者看到的還是舊的?兇手就是「快取」。成功切換 symlink 後,你必須立刻、馬上、全面地清理所有快取層,包括:

  • PHP OPcache: PHP 用來快取已編譯腳本的機制,它會記住舊檔案的路徑和內容。必須強制清除它,才能讓 PHP 讀取新版本的程式碼。
  • 物件快取 (Object Cache): 如果你用了 Redis 或 Memcached,需要執行 wp cache flush 來清空所有快取。
  • 頁面快取 (Page Cache): 無論是 Nginx FastCGI Cache、Varnish,或是 WordPress 外掛產生的靜態頁面快取,都需要被清除。
  • CDN 快取: 如果你用了 Cloudflare 或其他 CDN 服務,最好透過 API 來觸發全站快取清除,確保全球使用者都能看到最新的內容。

結論:從「半夜上線」到「優雅部署」

實現 WordPress 的零停機部署,並不僅僅是學習一項新技術,它更代表著一種開發維運文化的轉變。它強迫我們思考自動化、可靠性與可回溯性。初期建置的過程或許會有些陣痛,需要你對 Linux 伺服器、Git 和自動化流程有一定的了解,但一旦這套系統建立起來,它所帶來的回報是巨大的。

你將告別半夜部署的恐懼,迎來隨時可以發布新功能的自由。你的網站將變得更加穩定可靠,使用者和搜尋引擎也會因此更信任你。這是一項對開發者、對企業、對使用者三贏的投資。別再拿你的肝和網站的穩定性開玩笑了,是時候擁抱現代化的部署流程了!


延伸閱讀

如果你對於如何為你的 WordPress 網站導入零停機部署(Zero Downtime Deployment)、建置自動化 CI/CD 流程,或是任何網站架構優化的問題感到頭痛,別擔心,這正是浪花科技的專業所在。我們協助過許多企業客戶打造穩定、高效能的網站基礎架構。歡迎與我們聯繫,讓我們的專業團隊為你評估並規劃最適合的解決方案!

常見問題 (FAQ)

Q1: 什麼是零停機部署 (Zero Downtime Deployment)?

A1: 零停機部署是一種網站更新策略,目標是在部署新版程式碼的過程中,完全不中斷對外服務,讓使用者在整個更新期間都能正常訪問網站,幾乎感受不到任何變化。這通常透過原子切換(如符號連結)等技術來實現,避免了傳統 FTP 上傳可能導致的網站暫時性錯誤或掛上「維護中」頁面的情況。

Q2: 為什麼我不能直接用 FTP 上傳檔案就好?

A2: 直接使用 FTP 覆蓋檔案的風險極高。因為檔案是一個一個上傳的,在過程中,你的網站會處於一個「半新半舊」的混合狀態。如果新舊版本的程式碼不相容,網站很可能會直接崩潰,顯示錯誤畫面。這不僅嚴重影響使用者體驗,也可能對 SEO 造成負面影響。零停機部署則能確保版本切換的完整性與瞬間性,避免了這種中間狀態的風險。

Q3: 實作零停機部署最關鍵的技術是什麼?

A3: 最核心且常見的技術是「原子部署 (Atomic Deployment)」,尤其是利用伺服器的「符號連結 (Symbolic Link)」。它的原理是將網站的公開目錄設定為一個符號連結,指向實際的程式碼版本目錄。部署時,先在一個全新的目錄中準備好所有新版檔案,完成後,只需一個指令將符號連結瞬間指向新目錄,即可完成版本切換。整個過程幾乎沒有時間差。

Q4: 處理資料庫變更時,零停機部署該注意什麼?

A4: 這是零停機部署中最具挑戰性的一環。關鍵原則是「向後兼容」。你必須確保在任何時間點,當下的資料庫結構都能同時被新、舊兩個版本的程式碼所兼容。這通常需要將資料庫的變更拆分成多次部署來完成,例如先做「增加」欄位或資料表的「擴展」部署,待新程式碼穩定上線後,再進行移除舊結構的「收縮」部署,以確保平滑過渡。

 
立即諮詢,索取免費1年網站保固