我的網站在 48 小時裡崩了兩次,然後我把整條部署鏈路重做了
8 月 19 日上午 11 點 14 分,我在飛書裡發出一句話。我們的網站,在外網又打不開了,看看是什麼原因?
8 月 19 日上午 11 點 14 分,我在飛書裡發出一句話。
我們的網站,在外網又打不開了,看看是什麼原因?
關鍵在那個「又」字。
兩天前它就崩過一次。當時我沒多想,手動把服務拉起來,網站活了,這事就翻篇了。我當時的判斷很簡單,偶發,運氣不好。
第二次,同樣的姿勢,同樣的時間段,同樣躺平。
同一個故障出現第二次的時候,它就不叫故障了,叫設計缺陷。
這篇文章記錄了從那天上午開始的四次架構升級。8 月 19 日重做部署鏈路,8 月 21 日上 HTTPS,8 月 27 日接上異地備份,還有昨天深夜,我在為寫這篇文章核對事實的時候,又挖出一個藏了 8 天的坑。
還有件事得先說清楚。上面這些操作,從排查到升級,幾乎全是 AI 助手在伺服器上執行的,我坐在飛書對話框這一頭,只做了 4 次決策。這條線我會貫穿全文講,包括它卡住的時候,和它判斷錯的時候。
五條命令,找到根因
報障之後我沒有打開終端。我把那句話發給 AI 助手,它開始排查。
命令 | 結果 |
|---|---|
| 空進程 |
| HTTP 000,Connection refused |
| 規則清空 |
| HTTP 000 |
+ | 伺服器 10:32 重啟過 |
五條命令,根因浮出來了。
當時的架構是這樣的。用 pnpm dev 跑一個 Astro 開發伺服器佔著 3000 埠,再用 iptables 把 80 埠的流量轉發過去。聽著能用,實際上有兩個天坑。
iptables 規則預設不持久化。伺服器一重啟,轉發規則清零。
pnpm dev 不是 systemd 服務。伺服器重啟後,沒有任何機制會把它拉起來。
這兩條都不是 bug。它們是我在拿開發工具幹生產的活。pnpm dev 這個命令從設計上就沒打算活過一次重啟,它是給你本地改代碼時熱更新用的,不是給生產環境扛流量用的。
我用它上線,等於把一輛卡丁車開上高速,然後抱怨它不安全。
三個方案,和我做的第一個決策
排查完,AI 助手給了我三個選項。
方案 | 做法 | 代價 |
|---|---|---|
A | systemd 單元 + iptables-persistent | 保住 dev server,但還是在給錯誤架構打補丁 |
B | PM2 + pm2-startup | 引入新依賴,為一個靜態站殺雞用牛刀 |
C |
+ Nginx 靜態託管 | 改動最大,但把問題連根拔了 |
我選了 C。
這是我在這次事故裡做的第一個決策,也是最關鍵的一個。
理由不複雜。A 和 B 的本質都是讓錯的東西更穩定,只有 C 是換成對的東西。
救火的時候,人的本能會選 A。改動小、見效快、十分鐘能收工。但 A 救的是這一次的火,不是下一次的。iptables-persistent 能讓規則活過重啟,可 dev server 依然在生產環境裡跑著,沒有進程守護,沒有資源限制,記憶體洩漏了沒人管,構建產物每次都要現場編譯。
我的網站是個純靜態站。它根本不需要一個 Node 進程常駐。讓 Nginx 直接吐靜態檔案,是這件事本來就該有的樣子。
這種取捨 AI 給不了。它可以把三個方案的利弊列得清清楚楚,但我願意為這次修復承擔多大的改動風險,這是我的事。
重做部署鏈路,六步和三個坑
方案定了,接下來是執行。這部分我基本沒插手,AI 助手在伺服器上一路做完。
1.
pnpm build生成dist/,5 個靜態頁面,耗時約 2 秒2.
apt-get install nginx,自帶 systemd 服務,enabled + active3.
建部署目錄
/var/www/xiaocao4.
寫 Nginx 配置
5.
清理老架構,kill dev 進程加
iptables -t nat -F PREROUTING6.
寫一鍵部署腳本
deploy.sh
過程中踩了三個坑,都值得單獨說。
第一個是許可權坑。我的專案源碼在 ~/.openclaw/workspace/ 下,而這個目錄是 700 許可權。Nginx 以 www-data 用戶運行,壓根讀不到。解法是用 rsync 把構建產物同步到 /var/www/xiaocao,再 chown 給 www-data。所以部署腳本裡必須有同步這一步,不能圖省事直接把 Nginx 的 root 指過去。
第二個是殭屍進程坑。kill 掉 Astro dev 的父進程後,子進程 35557 還活著,埠沒釋放,得手動補一刀。
第三個是路由坑。Astro 構建出來的是 /path/index.html 這種結構,Nginx 預設的 try_files 處理不了,得寫成這樣。
location /{try_files$uri$uri/ $uri.html $uri/index.html =404;}
除了 try_files,Nginx 配置裡還有三塊值得配,_astro/ 目錄掛一年長快取並標 immutable,images/ 掛 30 天,再打開 gzip。完整配置我放在官網了,文末點閱讀原文能看到可以直接抄的版本。
還有那個一鍵部署腳本,後來成了我更新網站唯一入口。核心就三步。
pnpm build sudorsync -a --delete "$SRC/dist/""$DEST/"sudo nginx -t &&sudo systemctl reload nginx
構建,同步,重載。加上 set -e 讓它一步出錯就停,再配上 chown 給 www-data,就是一個能用很久的部署腳本。
從 11:19 拍板到 11:26 域名接入完成,前後 7 分鐘。網站回來了,而且這次它能活過重啟。
上 HTTPS,兩個教程不會告訴你的坑
兩天後,8 月 21 日下午。網站穩了,但還在裸奔,瀏覽器地址欄掛著一個刺眼的「不安全」。我準備在公眾號文章末尾放官網連結,這個標記很掉分。
方案還是兩個。
A 是 Let's Encrypt 加 certbot,免費,90 天自動續期,國內直連穩定。B 是 Cloudflare 前置,順帶拿到 CDN 和 Analytics,但國內節點不穩。
我選了 A,這是第二個決策。理由很簡單,我的讀者主要在國內,Cloudflare 的免費節點在國內的表現我不敢賭。CDN 那點好處,抵不上訪問慢帶來的損失。
主流程真的只有一條命令。
certbot --nginx -d www.example.com -d example.com \ --non-interactive --agree-tos --email your@email.com --redirect
證書裝好了,Nginx 配置被自動改寫,本地 curl https://127.0.0.1 返回 200。
然後卡住了。
外網訪問 443 埠,逾時。
這就是第一個坑,也是我認為最值得寫下來的一個。騰訊雲 Lighthouse 的安全組預設不開 443 埠。
certbot 沒有任何問題,Nginx 也沒有任何問題。問題在雲廠商控制檯裡,那個 AI 助手進不去的地方。
幾乎所有 certbot 教程都不會提這一步。因為寫教程的人用的往往是自建機房,或者早就把埠開好了的機器。而你,一個剛買了雲伺服器準備上 HTTPS 的人,會卡在這裡,然後開始懷疑是不是 certbot 裝錯了。
這裡也是整篇文章裡人機邊界最清楚的一處。AI 助手能改伺服器上的任何配置,但它沒有我的雲控制檯帳號。它把問題定位到了安全組,剩下的必須我去點。我登入騰訊雲,放行 443,回來再測,200,103 毫秒。
第二個坑更隱蔽。certbot 自動改配置的時候,保留了我之前寫的裸域跳轉規則,而那條規則跳的是 http://www。結果就是,用戶訪問 https://裸域,先被跳到 http://www,再被跳到 https://www,白白多繞一跳。
修復只有一行。
sudosed -i 's|return 301 http://www.example.com|return 301 https://www.example.com|'\ /etc/nginx/sites-enabled/xiaocao
順手把部署腳本升級了,加了四行健康檢查,每次部署完自動跑。分別打本地 HTTPS、外網 HTTPS、HTTP 跳轉和裸域跳轉,四條全綠才算部署成功。
curl -s -o /dev/null -w "%{http_code} %{time_total}s\n" https://www.example.com/ curl -s -o /dev/null -w "%{http_code} -> %{redirect_url}\n" http://www.example.com/
完整的四條檢查同樣在官網全文裡。
到這裡我以為 HTTPS 這一章結束了。
八天後我才知道沒有。
藏了八天的坑,nginx -t 通過不等於你改的檔案在生效
昨天深夜,我為寫這篇文章核對事實,讓 AI 助手把四條跳轉鏈路重新實測一遍。結果不對,http://裸域 依然要繞兩跳。
AI 助手當場給了我一個解釋。8 月 21 日那次 sed 只修了一半,https://裸域 修好了,http://裸域 漏了。
聽著很合理。我差點就信了。
它自己也差點信了,直到它去改配置。
它按標準做法編輯 /etc/nginx/sites-available/xiaocao,nginx -t 語法檢查通過,systemctl reload nginx 重載成功。再測。
線上紋絲不動。
然後它 diff 了兩個檔案,真相才出來。
/etc/nginx/sites-enabled/xiaocao
這個檔案不是軟連結,是一個獨立的實體檔案。
Nginx 的標準做法裡,sites-enabled/ 下面放的應該是指向 sites-available/ 的軟鏈,一份配置一個真相源。但我這台機器上,兩個目錄裡各躺著一份內容幾乎相同的檔案,從 8 月 21 日起各自漂移。
而真相和那個聽著很合理的解釋正好相反。8 月 21 日那次 sed 修對了,它改的是 sites-enabled,恰好是線上真正生效的那份。而 sites-available 裡那份從頭到尾沒人碰過,至今還留著 http://www 的老寫法。
所以昨晚 AI 助手第一次改的,是一個死檔案。改了,測了,通過了,重載了,什麼都沒發生。
這就是我要單獨拿一節寫它的原因。
nginx -t 通過,不代表你改的那個檔案在生效。它只檢查語法。兩份配置都語法正確,它對哪一份都說 OK。它不報錯,它只是不生效。
一行命令自檢。
readlink -f /etc/nginx/sites-enabled/你的站點名
如果輸出的不是 /etc/nginx/sites-available/ 下的路徑,你就有兩份配置在各自漂移。
我做的第三個決策是根治而不是打補丁。以線上生效的那份為準覆蓋過去,刪掉實體檔案,建回標準軟鏈。現在四條鏈路全部一跳直達,https://www 響應 97 毫秒。
最後一塊拼圖,源碼不能只活在一台機器上
前面三次升級,解決的都是服務掛了怎麼辦。但有個更大的風險一直沒管,源碼只在這一台伺服器上。
伺服器掛了,網站是宕機,重啟能救。伺服器沒了,網站是消失,從頭再來。
8 月 27 日深夜補上了這一塊,GitHub 私有倉庫。
這次的分工感最強烈。AI 助手在伺服器上裝 gh CLI、配 git config、init 倉庫、寫 .gitignore、建遠端倉、推首次提交,一條龍。我全程只輸了三樣東西,信箱、倉庫名、還有 OAuth 的裝置碼。從開工到完成 19 分鐘,43 個源碼檔案,4314 行,dist、node_modules、.astro、.env 全被 .gitignore 攔在外面。
這裡有一個坑,可能只有 OpenClaw 用戶會踩到,但踩到就是大事。
我的 workspace 目錄 ~/.openclaw/workspace/ 本身已經是一個 git 倉庫,而網站專案是它的子目錄。在子專案裡跑 git status,git 會一路向上找到 workspace 那個倉,把整個工作區的檔案都列出來。
看起來複用現成的倉庫很省事。但那個 workspace 裡有什麼?有 MEMORY.md,AI 助手的長期記憶,裡面是我半年來所有的專案決策、協作約定、踩坑記錄。有 .agents 目錄,整套配置。
一個 git add -A,全推公網。
所以必須在子專案裡單獨 git init。這個不能複用的判斷,是我做的第四個決策,也是最不像技術決策的一個,它關乎的不是架構,是隱私邊界。
那麼,我到底做了什麼
四次升級,完整時間線。
日期 | 事件 | 架構狀態 |
|---|---|---|
8/17 | 第一次宕機 | pnpm dev + iptables,手動救火 |
8/19 | 第二次宕機 | 改成 build + Nginx + systemd + 一鍵部署 |
8/21 | 主動升級 | 加 HTTPS + 健康檢查 |
8/27 | 主動升級 | 加 GitHub 私倉異地備份 |
8/29 | 事實核對 | 修掉配置漂移,改回標準軟鏈 |
命令是 AI 助手敲的,配置是它寫的,坑是它踩的,也是它自己爬出來的。
我做了四次決策。
# | 決策 | 為什麼 AI 替不了 |
|---|---|---|
1 | 三方案裡選 C,連根拔而非打補丁 | 這是風險偏好,不是技術優劣 |
2 | 選 certbot 而非 Cloudflare | 需要對國內網路環境的判斷 |
3 | 去騰訊雲控制檯放行 443 | AI 沒有我的控制檯帳號 |
4 | 源碼上私倉,且不復用 workspace 倉 | 這是隱私邊界,不是工程問題 |
四條裡,只有第三條是能力邊界,AI 進不去那個控制檯。另外三條都是判斷邊界。它能把選項列全、把利弊說清,但選哪個,需要一個承擔後果的人。
還有一件事值得記下來。昨晚它給我的第一個解釋,也就是 8/21 只修了一半那個,是錯的。它當時手裡的證據只夠支撐一個事實,裸域有兩跳。但它順手補了一整套因果鏈,聽著專業,邏輯閉環,完全自洽。
不是我發現的,是它撞牆撞出來的。
改完發現線上沒反應,才回頭 diff,才挖出配置漂移。如果它當時沒去動手,只是把那個合理的解釋交給我,我就會帶著一個錯誤的結論把這篇文章寫完,甚至還會在文章裡教別人怎麼修一個根本不存在的问题。
這大概是人機協作裡最需要警惕的地方。AI 給出的錯誤答案,往往不是明顯離譜的那種,而是聽著特別合理的那種。唯一能拆穿它的,是讓它去撞真實世界的牆。跑一遍,測一遍,看看現實認不認。
回到開頭那個「又」字。
第一次宕機時我選擇了手動救火,成本是十分鐘。第二次宕機,我付出的是重做整條部署鏈路。如果我在第一次就動手,第二次根本不會發生。
每一次架構升級,都是一次故障開的帳單。區別只在於你主動付,還是被動付。
而當執行成本趨近於零,你唯一還需要付的,就是判斷。



