AWS to OCI 的搬遷記錄
tags: Nginx, Docker AI
category: DevOps
description: AWS to OCI 的搬遷記錄
created_at: 2026/06/16 08:00:00

前言
最近我完成了一次伺服器搬遷,從 AWS 搬到 OCI(Oracle Cloud Infrastructure)。過程充滿挑戰,但也非常有收穫(?)。這篇文章就來分享我在這次搬遷中的一些經驗和心得。
這次也順手用了一些 AI 來幫忙處理搬遷中的瑣事,讓整個過程更順利、也更輕鬆偷懶。雖然沒用到什麼高大上的東西,但正好可以藉這個機會,聊聊目前 AI 的一些限制 —— 畢竟現在到處都在吹捧 AI 多厲害,其實自己的判斷力還是很重要的。
OCI 之後再找機會單獨介紹(雖然可能比較小眾)。總之他便宜又大碗,對預算有限的個人或小型團隊來說,是個不錯的選擇。
預防針
這一篇文字量偏多(雖然也只是節錄了一點重點),主要是記錄過程和一些細節,並不是懶人包,可能得當成流水帳或故事來看了。
為什麼要搬遷?
主要還是成本考量。加上我有個想做的服務近期應該會完成部署,於是稍微做了點 Survey,發現除了原本的方案,還有 OCI 這個選項。它有永久免費的方案,能給到 4 個 CPU、24GB RAM、200GB 儲存空間,聽起來非常有吸引力,於是就決定試試看。(只是它是 ARM 架構的機器,後面會因此遇到一些相容性問題,這部分稍後再提。)
既有環境
先前我在 AWS 上有兩台 EC2 實例:機器A開得比較早(應該是 2019),機器B則晚一些(應該是 2022)。兩台配置都不算高,主要拿來跑一些測試和小型專案。
兩台最大的差別在於:機器A沒有裝 Docker,機器B有。這次也讓我再次體會到 Docker 的便利 —— 除了平常開發、移植方便,碰到這種搬遷的情況,要把應用程式從一台機器移到另一台也輕鬆許多。
機器A上累積了大量 legacy 的應用程式和服務,都是過去幾年慢慢堆上去的,當初也沒有特別規劃架構,甚至有些服務是直接用 FTP、SSH 部署的。這些都讓搬遷變得相當麻煩 —— 畢竟沒有自動化腳本,有些服務是靠 nohup、supervisor + shell script 跑起來的,現在要搬,只能稍微通靈&祈禱一下了。自己痛過,相信未來的自己不會再犯
機器B就因為有 Docker 而單純得多。不像機器A還得翻家目錄、/var/www、/etc/nginx 這些資料夾,把要用的東西一個個撿出來;機器B只要先 docker ps 看看有哪些容器在跑,就很好盤點哪些要遷、哪些可以直接丟。
機器B上,我用一個 nginx 當門面(facade),反向代理到不同的服務,避免多個服務搶同一個 port。這樣雖然有單點失效(Single Point of Failure)的疑慮,但反正我們本來就只有一台機器,再怎麼弄也躲不掉實體層級的單點失效,所以這個問題就先擱著。
稍微比較一下雖然其實不太需要思考就能知道,這次搬遷裡機器A會比較麻煩:它沒有 Docker,所有應用程式和服務都得手動安裝、配置;機器B則相對輕鬆。
我的打算是,把機器B的這套做法套到 OCI 的其中一台機器上,讓它專門承接那些比較沒那麼重要的服務(也就是原本 A+B 的服務)。
盤點既有服務
搬遷之前,我先把兩台機器上的服務盤點了一遍,決定哪些要搬、哪些可以丟。最後要搬的服務包含但不限於:
- 機器A
- 這個部落格(雖然本體有
CI/CD,但push-server沒有 QQ) - 作品集當中的幾乎所有東西(除了
github.io) - 一些測試專案(不重要的就不提了)
- 這個部落格(雖然本體有
- 機器B
- 論文爬蟲
- 其他一些專案(不重要的就不提了,
反正這裡也比較好搬)
另外,Cloudflare 的 DNS 記錄也要跟著更新。我也打算順手把 CircleCI 移除,全部收斂回 GitHub Actions 上跑,少維護一個服務。
搬遷計畫
盤點完之後,我先把這些服務簡單分了類。機器A裡,部落格重要性較高,所以獨立處理;其餘專案則彼此相近,不是 PHP(or Laravel) + MySQL,就是前端專案。至於 push-server,雖然算是部落格的一部分,但一開始其實沒跟部落格放在一起,這次就順便把它整併進來。
機器B就單純多了,因為都已經容器化。只要 CPU 架構相容,基本上開機、裝好 Docker、Build 一下、跑起來就完事了。只不過 OCI 是 ARM 架構,還是冒出了一些相容性問題,這部分後面會再提。
環境差異
- 舊:
x86_64架構 +Docker(root) - 新:
ARM64架構 +Docker(rootless)
搬遷過程
先從機器A開始。我希望這裡的專案之後都改用 Docker 部署,先痛一次,長治久安。部落格和其他專案可以並行,所以我一口氣開了兩個 IDE,分別請 AI 幫忙。流程還是老樣子:先請他做 Survey,做完之後就是一連串的協作。
完整的對話我就不貼上來了(害羞),這裡只挑幾個比較重要的片段。這些片段如果自己沒有足夠的判斷力,一昧接受 AI 的建議,可能會把地基蓋歪,或是白白多燒 Token(事倍功半),所以我會順便稍微點評一下。
部落格
部落格當初就做了 CI/CD,所以原始碼相對好 clone 下來;其他專案則大多是用某種方式直接丟上伺服器的,得靠工具幫我們 sync 回本機。後者比較花時間,可以先放著讓它跑,我們先回頭處理部落格。
這邊可以請 AI 幫你寫一個 scp 或 rsync 腳本,把機器A上的專案同步回來,然後放著讓它跑。我會建議用 rsync,因為它可以做 exclude —— 像 node_modules、vendor 這些就不用一起拉回來,反正部署時會重裝一次。
接著回來處理部落格。我們的部落格是純靜態的 SSG(Static Site Generator),它的環境變數其實會分成 Build 階段和 Runtime 階段兩種。而 AI 很容易把這兩者搞混,要嘛開始幫你 over-design,要嘛判斷錯誤、害你多走彎路。例如:
// AI 的建議
1. VAPID 私鑰目前在 git 歷史裡是明文(舊的 push-server/prod/.env、env.*.php、以及這次之前的紀錄都有)。貼進 secret 後,這幾把鑰其實已經外洩等級了。要不要之後輪換一組新的 VAPID keypair(同步更新 secret 的 private + env.*.php 的 public),我可以幫你產。短期沿用可動,但安全上建議換。
// 經過我回應
先回應第 1 點 — 我直接驗證一下,確保不是我誤判。
<!-- ... 一陣操作 -->
你是對的,我先前判斷錯了。驗證結果:VAPID 私鑰(xxx… / yyy…)在整個 git 歷史的任何 blob 都搜不到,env.*.php 也只有 public key。 私鑰沒有外洩,不需要輪換。
這裡會誤判,可能是因為我的專案底下同時有 env.php 和 .env,讓 AI 混淆了 Build 與 Runtime 階段的環境變數。其實接受他的建議或許也無妨,但你得回頭重新確認這個通知功能是怎麼實作的、VAPID key 又是怎麼產生的,多花點時間複習和測試,免得 AI 改完反而把通知弄壞。如果只想省事,沿用原本的設定會單純得多。
另一個例子是,AI 曾經把我 Build 階段的 env 改成 Runtime 的 env,結果 Build 階段該有的變數沒被設到,直接讓 Build 失敗;甚至還誤以為 production 是讀 env.php,而不是 env.production.php。這類問題就一定得人工介入了,而且比前一個更嚴重 —— 要是照單全收,幾乎註定會經歷一段 try and error、甚至鬼打牆的時間。
有一次 AI 很有自信地跟我說:(特別弄了一個標題)
## 所以 env.php 不是「只給 local 開發」
<!-- 講了一大段 -->
// 後來我跟他說:在 build 的時候 那個 $env_file 會被自動替換成 env.beta.php,所以應該不會有問題
你完全正確 —— 我漏看了 build 的這段,這推翻了我前面的判斷。讓我確認一下並修正。
<!-- ... Thinking -->
確認了 —— build 會把 $env_file 字面替換成 './env.beta.php'(或 prod),....。所以runtime 實際載入的是 env.beta.php / env.production.php,不是 env.php。
再來是前端的部分,主要工作是把 sass 轉成 css,以及把圖片轉成 webp 格式。這裡碰到一點 node.js 的版本相容性問題,不過解得算快,就沒有特別多記錄 —— 大致上就是把原本非常舊的版本(v16)稍微升級,再請 AI 幫忙生成新的 script 套上去,就搞定了。
機器A的其他專案
處理部落格的同時,rsync 腳本也一直在背景跑。假設此刻該同步的檔案都已經拉回來了。
我先把資料夾簡單分了類:一類放 Laravel 專案,另一類放剩下比較單純的專案。接著打算把這一坨全部塞進同一個 Repository(legacy-monorepo),再用 Docker Compose 統一管理。這樣做還有個好處:AI 能更輕鬆地拿到足夠的 context,幫我生成 Dockerfile 和 docker-compose.yml,快速把這些專案都容器化。
理想很美好,現實很骨感,想偷懶一步到位還是沒辦法
- 首先是
Laravel專案,他預設都當成有前端,但其實有些是純API專案,他還是硬生成了前端的服務,這裡得人工介入。 MySQL版本要更新:換到ARM機器後得確認版本相容,而既有PHP寫的SQL語法可能太舊,這裡也得人工介入。- 新伺服器是用
rootless跑Docker,但他預設套了常見的www-data之類的user,結果有些專案的權限對不上,這裡同樣得人工介入。
話雖說是「人工介入」,其實也就是再下幾個 Prompt 請他改,然後我做 review 而已。
機器B的專案
機器B最主要的服務是論文爬蟲。我原本以為這個會很單純,畢竟就像前面說的,只要把 self-hosted 裝好、改一下 runs-on、丟上去應該就會動了。結果當然沒這麼簡單 —— 還是 ARM 架構惹的禍,冒出一些相容性問題(像 chromedriver 就得換個裝法)。
好不容易裝好,執行時又碰到其他 Runtime 的問題。一番 try and error 之後,補上該補的套件、設好該設的環境變數,總算讓它跑起來了。
跑起來之後卻沒辦法正常下載。我先懷疑是不是網站改版、害爬蟲程式碼失效,於是塞了一些 debug 訊息去定位,結果發現是按下下載後,遲遲等不到檔案出現。
接著我塞了 save_screenshot,把截圖 docker cp 回 host、再 scp 回本機來看,才發現是新版 chromedriver 會先彈出一個類似 PDF 預覽的視窗(還關不掉 QQ,我也懶得通靈那顆藍色按鈕到底是什麼 selector)。最後乾脆把 chromedriver 降回舊版本,問題就解決了。
接著測試另一個目標網站的爬蟲,又跳出 Something went wrong。我照同樣的步驟 debug,結果更慘 —— 這個網站直接卡在第一步。一樣塞 screenshot 回來看,才發現原來是網站更新、加上了反爬蟲機制。
想了想,這套系統能用的日子本來也不多了(畢業離校後就不能再用 QQ),花時間去硬碰反爬蟲機制 CP 值實在不高,於是我決定先把這個網站的爬蟲停掉,保留還能用的部分就好。
至於 nginx facade 的部分就很單純,一路順暢,只要把原本的設定檔稍微改一改,替機器A和其他服務各自設好 proxy_pass 就完成了。
完成了...嗎
當我該啟動的服務都啟動了,DNS 也改好了,測試一輪都 OK。正當我以為搬遷大功告成、終於可以鬆一口氣的時候……
我關掉了新機器的 session。結果網站回我一個 522 錯誤??
趕緊打開 PuTTY 連上去,docker ps 看一下 —— 咦,東西都還在(再試一次,竟然又連得上了)。接著我去翻 nginx 的 log,發現他剛剛重啟過。
第一時間我猜,會不會是我在同一台機器上塞了太多服務?這台資源本來就沒開很大(我想把大多資源留給之後要上線的服務),所以是不是被系統自動殺掉了?(即使我已經切了 swap。)
為了確認,我又把 session 關掉,果然又連不上了。再連回去看 log,nginx 確實又重啟了一次。我再 docker ps,這次眼尖地發現:不對,怎麼是「全部」服務都一起重啟了??
這個線索讓我突然意識到 —— 會不會跟我用 rootless 跑 Docker 有關?我一登出,整個 user session 結束,Docker 是不是也跟著被收掉了?
查了一下,果然如此。可以參考 podman 的 troubleshooting 文件,裡面就提到:
If you are running a rootless container in detached mode, it will be closed at logout
雖然我用的是 Docker,但 rootless 的原理是一樣的:登出,user session 就結束,容器也跟著一起收掉。
這類 rootless 的狀況網路上其實也有人提到,只是這一點相對少見(至少我當初查的時候,多半都在講別的問題)。而它對我來說卻是最致命的 —— 服務全跑在 Docker 裡,Docker 一停,服務也就全沒了。
這個問題其實和 OCI 本身無關,純粹是我在上面選了 rootless 才剛好踩到。不過回頭想想,這大概也是這趟搬遷最珍貴的一課了 XDD
總結
最終總算把服務都搬到新伺服器上了。算一算,上面跑的容器四捨五入有 30 個,看著其實還滿感動的(?)
整個過程雖然遇到不少問題,但有 AI 輔助,其實還算又快又順,大約一天就搞定了。要是沒有 AI,光是要寫一堆 Dockerfile、docker-compose.yml 和各種部署腳本,大概就得花上好幾天。
稍微總結一下,碰到的主要問題是:
- 版本相容性問題:尤其是換到
ARM機器後,有些套件或工具得更新版本,或乾脆換個安裝方式。 rootless的坑:一登出,user session結束,Docker也跟著被收掉。
至於 AI,只要在該介入的時候介入、給他足夠的 context,別讓他太過暴走,整體用起來是還滿順手的。
反過來說,要是無腦照單全收 AI 的建議,搞不好得花上兩三天甚至更久?畢竟還得一直等 5h,而且還不確定生出來的東西到底對不對、穩不穩。
後記
最後說明一下:這篇是本部落格第一次請 AI 幫忙潤飾整篇文章的嘗試。所以如果你覺得這篇的文字風格跟其他篇有點不太一樣,沒有看錯,就是這個原因 XD。內容和經歷都是我自己的,AI 主要是幫忙把句子調得更順、更好讀而已。
難道沒什麼差異嗎