AWS to OCI 的搬遷記錄

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

cover image


前言

最近我完成了一次伺服器搬遷,從 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 的應用程式和服務,都是過去幾年慢慢堆上去的,當初也沒有特別規劃架構,甚至有些服務是直接用 FTPSSH 部署的。這些都讓搬遷變得相當麻煩 —— 畢竟沒有自動化腳本,有些服務是靠 nohupsupervisor + shell script 跑起來的,現在要搬,只能稍微通靈&祈禱一下了。自己痛過,相信未來的自己不會再犯

機器B就因為有 Docker 而單純得多。不像機器A還得翻家目錄、/var/www/etc/nginx 這些資料夾,把要用的東西一個個撿出來;機器B只要先 docker ps 看看有哪些容器在跑,就很好盤點哪些要遷、哪些可以直接丟。

機器B上,我用一個 nginx 當門面(facade),反向代理到不同的服務,避免多個服務搶同一個 port。這樣雖然有單點失效(Single Point of Failure)的疑慮,但反正我們本來就只有一台機器,再怎麼弄也躲不掉實體層級的單點失效,所以這個問題就先擱著。

nginx facade 反向代理示意圖

稍微比較一下雖然其實不太需要思考就能知道,這次搬遷裡機器A會比較麻煩:它沒有 Docker,所有應用程式和服務都得手動安裝、配置;機器B則相對輕鬆。

我的打算是,把機器B的這套做法套到 OCI 的其中一台機器上,讓它專門承接那些比較沒那麼重要的服務(也就是原本 A+B 的服務)。


盤點既有服務

搬遷之前,我先把兩台機器上的服務盤點了一遍,決定哪些要搬、哪些可以丟。最後要搬的服務包含但不限於:

  • 機器A
    • 這個部落格(雖然本體有 CI/CD,但 push-server 沒有 QQ)
    • 作品集當中的幾乎所有東西(除了 github.io)
    • 一些測試專案(不重要的就不提了)
  • 機器B
    • 論文爬蟲
    • 其他一些專案(不重要的就不提了,反正這裡也比較好搬)

另外,CloudflareDNS 記錄也要跟著更新。我也打算順手把 CircleCI 移除,全部收斂回 GitHub Actions 上跑,少維護一個服務。


搬遷計畫

盤點完之後,我先把這些服務簡單分了類。機器A裡,部落格重要性較高,所以獨立處理;其餘專案則彼此相近,不是 PHP(or Laravel) + MySQL,就是前端專案。至於 push-server,雖然算是部落格的一部分,但一開始其實沒跟部落格放在一起,這次就順便把它整併進來。

機器B就單純多了,因為都已經容器化。只要 CPU 架構相容,基本上開機、裝好 DockerBuild 一下、跑起來就完事了。只不過 OCIARM 架構,還是冒出了一些相容性問題,這部分後面會再提。


環境差異

  • 舊: x86_64 架構 + Docker(root)
  • 新: ARM64 架構 + Docker(rootless)

AWS 到 OCI 搬遷前後架構總覽


搬遷過程

先從機器A開始。我希望這裡的專案之後都改用 Docker 部署,先痛一次,長治久安。部落格和其他專案可以並行,所以我一口氣開了兩個 IDE,分別請 AI 幫忙。流程還是老樣子:先請他做 Survey,做完之後就是一連串的協作。

完整的對話我就不貼上來了(害羞),這裡只挑幾個比較重要的片段。這些片段如果自己沒有足夠的判斷力,一昧接受 AI 的建議,可能會把地基蓋歪,或是白白多燒 Token(事倍功半),所以我會順便稍微點評一下。

部落格

部落格當初就做了 CI/CD,所以原始碼相對好 clone 下來;其他專案則大多是用某種方式直接丟上伺服器的,得靠工具幫我們 sync 回本機。後者比較花時間,可以先放著讓它跑,我們先回頭處理部落格。

這邊可以請 AI 幫你寫一個 scprsync 腳本,把機器A上的專案同步回來,然後放著讓它跑。我會建議用 rsync,因為它可以做 exclude —— 像 node_modulesvendor 這些就不用一起拉回來,反正部署時會重裝一次。

接著回來處理部落格。我們的部落格是純靜態的 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 混淆了 BuildRuntime 階段的環境變數。其實接受他的建議或許也無妨,但你得回頭重新確認這個通知功能是怎麼實作的、VAPID key 又是怎麼產生的,多花點時間複習和測試,免得 AI 改完反而把通知弄壞。如果只想省事,沿用原本的設定會單純得多。

Build 階段與 Runtime 階段環境變數的載入流程

另一個例子是,AI 曾經把我 Build 階段的 env 改成 Runtimeenv,結果 Build 階段該有的變數沒被設到,直接讓 Build 失敗;甚至還誤以為 production 是讀 env.php,而不是 env.production.php。這類問題就一定得人工介入了,而且比前一個更嚴重 —— 要是照單全收,幾乎註定會經歷一段 try and error、甚至鬼打牆的時間。

把 build env 當成 runtime env 會發生什麼事

有一次 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,幫我生成 Dockerfiledocker-compose.yml,快速把這些專案都容器化。

理想很美好,現實很骨感,想偷懶一步到位還是沒辦法

  1. 首先是 Laravel 專案,他預設都當成有前端,但其實有些是純 API 專案,他還是硬生成了前端的服務,這裡得人工介入。
  2. MySQL 版本要更新:換到 ARM 機器後得確認版本相容,而既有 PHP 寫的 SQL 語法可能太舊,這裡也得人工介入。
  3. 新伺服器是用 rootlessDocker,但他預設套了常見的 www-data 之類的 user,結果有些專案的權限對不上,這裡同樣得人工介入。

話雖說是「人工介入」,其實也就是再下幾個 Prompt 請他改,然後我做 review 而已。


機器B的專案

機器B最主要的服務是論文爬蟲。我原本以為這個會很單純,畢竟就像前面說的,只要把 self-hosted 裝好、改一下 runs-on、丟上去應該就會動了。結果當然沒這麼簡單 —— 還是 ARM 架構惹的禍,冒出一些相容性問題(像 chromedriver 就得換個裝法)。

好不容易裝好,執行時又碰到其他 Runtime 的問題。一番 try and error 之後,補上該補的套件、設好該設的環境變數,總算讓它跑起來了。

跑起來之後卻沒辦法正常下載。我先懷疑是不是網站改版、害爬蟲程式碼失效,於是塞了一些 debug 訊息去定位,結果發現是按下下載後,遲遲等不到檔案出現。

接著我塞了 save_screenshot,把截圖 docker cphost、再 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 看一下 —— 咦,東西都還在(再試一次,竟然又連得上了)。接著我去翻 nginxlog,發現他剛剛重啟過。

第一時間我猜,會不會是我在同一台機器上塞了太多服務?這台資源本來就沒開很大(我想把大多資源留給之後要上線的服務),所以是不是被系統自動殺掉了?(即使我已經切了 swap。)

為了確認,我又把 session 關掉,果然又連不上了。再連回去看 lognginx 確實又重啟了一次。我再 docker ps,這次眼尖地發現:不對,怎麼是「全部」服務都一起重啟了??

這個線索讓我突然意識到 —— 會不會跟我用 rootlessDocker 有關?我一登出,整個 user session 結束,Docker 是不是也跟著被收掉了?

查了一下,果然如此。可以參考 podmantroubleshooting 文件,裡面就提到:

If you are running a rootless container in detached mode, it will be closed at logout

雖然我用的是 Docker,但 rootless 的原理是一樣的:登出,user session 就結束,容器也跟著一起收掉。

rootless Docker 登出後容器一起被收掉的示意圖

這類 rootless 的狀況網路上其實也有人提到,只是這一點相對少見(至少我當初查的時候,多半都在講別的問題)。而它對我來說卻是最致命的 —— 服務全跑在 Docker 裡,Docker 一停,服務也就全沒了。

這個問題其實和 OCI 本身無關,純粹是我在上面選了 rootless 才剛好踩到。不過回頭想想,這大概也是這趟搬遷最珍貴的一課了 XDD


總結

最終總算把服務都搬到新伺服器上了。算一算,上面跑的容器四捨五入有 30 個,看著其實還滿感動的(?)

整個過程雖然遇到不少問題,但有 AI 輔助,其實還算又快又順,大約一天就搞定了。要是沒有 AI,光是要寫一堆 Dockerfiledocker-compose.yml 和各種部署腳本,大概就得花上好幾天。

稍微總結一下,碰到的主要問題是:

  1. 版本相容性問題:尤其是換到 ARM 機器後,有些套件或工具得更新版本,或乾脆換個安裝方式。
  2. rootless 的坑:一登出,user session 結束,Docker 也跟著被收掉。

至於 AI,只要在該介入的時候介入、給他足夠的 context,別讓他太過暴走,整體用起來是還滿順手的。

反過來說,要是無腦照單全收 AI 的建議,搞不好得花上兩三天甚至更久?畢竟還得一直等 5h,而且還不確定生出來的東西到底對不對、穩不穩。


後記

最後說明一下:這篇是本部落格第一次請 AI 幫忙潤飾整篇文章的嘗試。所以如果你覺得這篇的文字風格跟其他篇有點不太一樣,沒有看錯,就是這個原因 XD。內容和經歷都是我自己的,AI 主要是幫忙把句子調得更順、更好讀而已。

難道沒什麼差異嗎




最後更新時間: 2026年06月16日.