PubliclyAccessible=true 不等於對外曝露:同一台機器,RDS 連不到、kubectl 卻通

起因:一個旗標,讓 AI 一秒喊出了錯的資安結論 那天清掉一顆「砍了 EC2(Elastic Compute Cloud,AWS 的雲端虛擬機器)卻忘了收」的閒置 Elastic IP(彈性 IP — AWS 可保留、可重複指派的固定公網位址)後,我順手請我的 AI 助手盤點帳號還開著哪些服務。掃到一台正式環境的 RDS(Relational Database Service,AWS 託管的關聯式資料庫)PostgreSQL,設定裡寫著 PubliclyAccessible: true。 AI 第一個反應就回我:「正式資料庫直接掛公網,有資安風險。」這句話聽起來很合理——但它是錯的。真相要等到實際讀了 Security Group 規則、再從本機跑一次連線測試才浮現。 這篇文章講的就是這個落差:一個旗標的字面意思,跟它實際造成的曝露,是兩回事。 也順帶示範——在「AI 已經會幫你讀設定、寫指令」的年代,為什麼這種誤判依然會發生。 先說清楚:Security Group 是什麼 Security Group(安全群組,以下簡稱 SG)是 AWS 的虛擬防火牆,掛在資源的網路介面 ENI(Elastic Network Interface,彈性網路介面)上。它有三個關鍵性質: 預設全部拒絕:沒有明確放行的流量一律擋掉。你只能「加白名單」,沒有「黑名單」這種東西。 來源可以是 IP 網段,也可以是另一個 SG:後者意思是「凡是掛了某個 SG 的資源一律放行」,不必管它的 IP 是多少——這在 VPC(Virtual Private Cloud,虛擬私有雲)內部互連時非常好用。 stateful(狀態感知 — 放行了進來的連線,回程流量自動允許):不必另外開一條出站規則。 SG 的逐步設定我在 如何使用 psql 連線 AWS RDS 那篇有完整圖解(「SG 就像 RDS 的防火牆」)。這裡只談它跟「公開存取」旗標之間,那個最容易被誤會的互動。 旗標的陷阱:PubliclyAccessible=true 只給門牌,不給鑰匙 關鍵誤解就在這裡。PubliclyAccessible 這個旗標只決定一件事:RDS 要不要拿到一個公網 IP,以及一個可從網際網路解析的 DNS(網域名稱)。它完全不決定「誰連得進來」。 ...

June 25, 2026 · 3 分鐘 · Peter

EKS 維運的三個隱形陷阱:工具 Pod 消失、事件蒸發、審計空白

起因:工具 Pod 人間蒸發 某天要查資料庫,照慣例執行 kubectl exec -it psql-client,結果 Pod 不見了。 這個 psql-client 是用來連 RDS PostgreSQL 的工具容器,平常拿它跑 SQL 查詢、檢查資料表大小。問題是——沒有人記得刪過它,也沒有任何記錄顯示是誰或什麼原因讓它消失。 這件事本身影響不大,重建一個就好。但它暴露了三個更深層的問題:為什麼 Pod 會無聲消失?為什麼查不到是誰刪的?為什麼整個叢集沒有留下任何操作軌跡? 陷阱一:裸 Pod 沒有人管它的死活 問題本質 當初建立 psql-client 的指令大概是這樣: kubectl run psql-client --image=postgres:15-alpine --restart=Never -- sleep infinity 這行指令建立的是一個裸 Pod(Bare Pod)——直接建立 Pod 物件,不隸屬於任何 Deployment、ReplicaSet 或 StatefulSet。 裸 Pod 和 Deployment 管理的 Pod,差別在於有沒有 Controller 在背後看著它。Deployment 的 ReplicaSet Controller 會持續監控 Pod 數量,少一個就補一個。裸 Pod 沒有 Controller,它的生死完全取決於節點的命運。 flowchart TD subgraph 節點故障發生 A[Node 異常關機] end A --> B[裸 Pod] A --> C[Deployment Pod] B --> D[Pod 永久消失] D --> E[無人知曉] C --> F[ReplicaSet Controller 偵測] F --> G[在其他 Node 重建 Pod] G --> H[服務自動恢復] classDef default fill:#f5f5f5,stroke:#333,color:#333 classDef error fill:#f8d7da,stroke:#dc3545,color:#721c24 classDef success fill:#d4edda,stroke:#28a745,color:#155724 classDef info fill:#d1ecf1,stroke:#17a2b8,color:#0c5460 class A info class D,E error class G,H success 不處理會怎樣 裸 Pod 在以下情境會永久消失: ...

May 16, 2026 · 3 分鐘 · Peter

Jenkins Multibranch Pipeline 的 Tag 大掃除

前言:Tags (173) 的紅燈海 打開 Jenkins 的 Multibranch Pipeline 頁面,映入眼簾的是一整排紅色叉叉——173 個 tag,幾乎全是失敗的歷史建置記錄。這些 tag 從 dev-0.0.74+143 一路排到 dev-0.0.74+181,光是同一個版本就堆了 39 個。 三個 repo(Flutter、Strapi、Vue)加起來超過 1000 個 tag。這不只是視覺上的噪音,更是 Jenkins 每次 scan 都要花時間處理的負擔。 核心觀念:Jenkins 上的 Tag 不是 Jenkins 的 第一個直覺可能是「到 Jenkins 上刪」,但這是錯的。 Jenkins Multibranch Pipeline 的運作方式是定期掃描 Git repository,把發現的 branch 和 tag 當作獨立的 pipeline 來建置。換句話說,Jenkins 頁面上看到的 tag 就是 Git remote 上的 tag——Jenkins 只是一面鏡子。 flowchart LR A[Git Remote<br/>Tags] -->|Scan| B[Jenkins<br/>Multibranch Pipeline] B -->|顯示| C[Tags 列表<br/>含建置狀態] D[刪除 Git Tag] -->|Re-scan| E[Jenkins 自動移除] classDef default fill:#f5f5f5,stroke:#333,color:#333 classDef info fill:#d1ecf1,stroke:#17a2b8,color:#0c5460 class A,D info 為什麼不能從 Jenkins 刪? 因為即使你在 Jenkins UI 手動移除某個 tag 的建置記錄,下一次 scan 時 Jenkins 發現 remote 上 tag 還在,又會重新建立。要斬草除根,必須從 Git remote 下手。 ...

April 9, 2026 · 3 分鐘 · Peter

Kubernetes 節點 DiskPressure 事故:部署失敗到緊急救援的完整記錄

事發:部署成功但服務掛了 一次例行的 Strapi 後端部署,Jenkins build 成功、Docker image 推上 ECR、kubectl set image 也順利執行。但 rollout 等了 300 秒後超時。 以下是整個部署流程,可以看到問題出在最後一步: flowchart LR A[Jenkins Build] -->|✅ 成功| B[推送 Image 到 ECR] B -->|✅ 成功| C[kubectl set image] C -->|✅ 成功| D[rollout status 等待] D -->|❌ 超時 300s| E[自動回滾] classDef success fill:#d4edda,stroke:#28a745,color:#155724 classDef error fill:#f8d7da,stroke:#dc3545,color:#721c24 class A,B,C success class D,E error 具體的錯誤訊息: error: timed out waiting for the condition Jenkins 回報「部署失敗,已自動回滾」。奇怪的是,build 每一步都成功了,問題出在 rollout 階段。 調查:Pod 起不來的真正原因 問題排查的過程如下,從 pod 狀態開始一路追到節點層級: ...

March 26, 2026 · 4 分鐘 · Peter

Docker BuildKit Cache Mount 的隱形陷阱

症狀:部署成功,程式碼卻是舊的 一個新功能已經 commit 並推上 Git,Jenkins pipeline 顯示建置成功、部署完成,Kubernetes Pod 也順利啟動。進入 Pod 檢查——程式碼還是舊的。 這不是網路延遲、不是 image pull policy、不是 tag 衝突。問題藏在 Dockerfile 裡一行看起來「很聰明」的快取優化。 背景知識:Docker 建置的關鍵概念 在進入除錯過程之前,先釐清幾個 Docker 建置中的核心概念: Docker Image 與 Layer(映像檔與分層) Docker 映像檔由多個唯讀的「層」(layer)堆疊而成。Dockerfile 裡的每一條指令(如 COPY、RUN)都會產生一層。Docker 會對每一層計算 hash,下次建置時如果指令和輸入都沒變,就直接重用該層——這就是 layer cache。 BuildKit BuildKit 是 Docker 18.09 之後引入的新一代建置引擎(透過 DOCKER_BUILDKIT=1 啟用)。相比傳統引擎,BuildKit 支援平行建置、更聰明的快取策略,以及本文主角——--mount=type=cache(cache mount)語法。 Cache Mount(--mount=type=cache) BuildKit 專有的功能。它在 RUN 指令執行期間,將一個持久化的目錄掛載到容器內的指定路徑。這個目錄由 BuildKit 管理,不會被寫入最終的 image layer,但內容會跨越不同次的 docker build 保留下來。常見用途是快取套件管理器的下載目錄(如 yarn cache),避免每次建置都重新下載。 Inline Cache 與 --cache-from 另一種 BuildKit 快取策略。透過 --build-arg BUILDKIT_INLINE_CACHE=1 把快取 metadata 嵌入產出的 image,再用 --cache-from 從遠端 registry 拉取。這讓不同機器(例如 CI runner)也能共享建置快取。 ...

February 18, 2026 · 3 分鐘 · Peter

功能部署後憑空消失?一場 Jenkins Workspace 的除錯之旅

問題:功能「先在後不在」 我們替 Strapi 後台新增了 MFA(Multi-Factor Authentication)雙因素認證功能。第一次部署到 STG 時一切正常——登入攔截、TOTP 驗證、QR Code 設定流程都運作良好。 但幾天後,因為其他功能的修改推了新的 git tag stg-1.24,部署流程順利完成,Jenkins 顯示綠燈——打開 STG 後台,MFA 的登入流程卻完全不見了。 沒有錯誤訊息、沒有 crash log,功能就這樣無聲無息地消失。 這比「功能從未出現」更令人困惑:明明之前部署是好的,程式碼也沒有人動過 MFA 相關的部分,為什麼會突然不見? 調查過程:逐層排除 確認 Git Tag 內容 第一個直覺是——程式碼真的有被包進 tag 嗎? # 確認 tag 指向的 commit 包含 MFA 程式碼 git show stg-1.24:src/index.ts | grep -i mfa 結果確認 src/index.ts 中有 import { registerMfaRoutes } from './mfa/controller',src/mfa/ 目錄也完整存在。Git tag 本身沒問題。 檢查 Pod 內的實際檔案 既然 tag 正確,問題可能出在建置或部署階段。直接進到 Kubernetes Pod 裡看: # 進入 STG pod 檢查 kubectl exec -it deployment/strapi-stg -- sh # 檢查編譯後的檔案 ls dist/src/mfa/ # 結果:ls: dist/src/mfa/: No such file or directory # 檢查原始碼 ls src/mfa/ # 結果:ls: src/mfa/: No such file or directory MFA 相關檔案完全不存在於 Pod 中。 甚至連原始碼都沒有被複製進 Docker image。 ...

February 12, 2026 · 3 分鐘 · Peter

AWS S3 Upload Failed: The Bucket Does Not Allow ACLs

問題發生 在將 Production 環境複製到 Staging 環境後,發現 Strapi CMS 無法上傳圖片到媒體庫,畫面只顯示 Internal Server Error。 Strapi 是一個開源的 Headless CMS(無頭內容管理系統),可以讓開發者快速建立 API,並提供管理後台來管理內容。在這個專案中,我們使用 Strapi 搭配 AWS S3 來儲存上傳的圖片和檔案。 追查過程 第一步:查看 Kubernetes Logs 由於 Strapi 部署在 EKS(Elastic Kubernetes Service,AWS 的託管 Kubernetes 服務)上,我透過 kubectl 指令查看 Pod 的日誌: kubectl logs -f deployment/strapi-stg --tail=100 kubectl 是 Kubernetes 的命令列工具,用來與 Kubernetes 叢集互動。logs 指令可以查看容器的輸出日誌。 第二步:找到錯誤訊息 在日誌中發現關鍵錯誤: error: The bucket does not allow ACLs AccessControlListNotSupported: The bucket does not allow ACLs 這個錯誤訊息指出 S3 Bucket(AWS 的物件儲存服務中的儲存桶)不允許使用 ACL。 ...

January 30, 2026 · 3 分鐘 · Peter

資料庫同步的隱藏陷阱:Link Table 的重要性

問題現象:登入成功卻被拒於門外 最近在 Staging 環境遇到一個詭異的問題:使用者登入成功,拿到了有效的 JWT Token,但存取任何需要認證的 API 都回傳 401 Unauthorized。 # 登入成功,拿到 token POST /api/auth/local → 200 OK { "jwt": "eyJhbGc...xxxxx...your-jwt-token", "user": { "id": 1001, "email": "[email protected]" } } # 但存取個人資料失敗 GET /api/users/me → 401 Unauthorized Token 驗證通過、使用者存在、帳號未被封鎖。問題到底在哪? 根本原因:遺失的 Link Table 經過一番追查,發現問題出在資料庫同步時漏掉了關聯表(Link Table)。 什麼是 Link Table? 在關聯式資料庫中,多對多關係需要透過中間表來建立。這個中間表就是 Link Table(也稱為 Junction Table、Join Table、或 Pivot Table)。 erDiagram users ||--o{ users_roles_lnk : has roles ||--o{ users_roles_lnk : assigned_to users { int id PK string email string password boolean confirmed } roles { int id PK string name string type } users_roles_lnk { int user_id FK int role_id FK int user_ord } 使用者與角色的關係: ...

January 20, 2026 · 4 分鐘 · Peter

Flutter CI/CD Debugging: Three Build Failures in One Day

前言:當建置一直紅燈 CI/CD pipeline 亮紅燈是開發日常,但連續遇到三個不同層面的問題,從 iOS codesigning、Android Gradle、到 Google Play API,這就值得記錄下來了。 這篇文章記錄我在同一天內遇到的三個建置失敗,以及逐步排除的過程。每個問題都有其獨特的根因,但也反映出 CI/CD 環境的複雜性。 問題一:iOS Keychain 解鎖失敗 症狀 Jenkins 建置在 iOS 階段失敗,錯誤訊息: [!] Error unlocking keychain at path: fastlane_keychain Command failed with exit status 51 macOS Keychain 運作機制 在深入問題之前,先了解 macOS Keychain 的運作方式: flowchart TD subgraph Keychain["macOS Keychain 系統"] KC1[login.keychain<br/>使用者預設] KC2[fastlane_keychain<br/>CI 專用] KC3[fastlane_tmp_keychain<br/>臨時] end subgraph Certs["憑證與密鑰"] CERT[Apple Distribution<br/>Certificate] KEY[Private Key] end KC2 --> CERT KC2 --> KEY subgraph Access["存取控制"] LOCK[🔒 Locked<br/>密碼保護] UNLOCK[🔓 Unlocked<br/>可存取憑證] end LOCK -->|正確密碼| UNLOCK LOCK -->|錯誤密碼| ERROR[❌ Exit 51] classDef default fill:#f5f5f5,stroke:#333,color:#333 classDef error fill:#f8d7da,stroke:#dc3545,color:#721c24 classDef success fill:#d4edda,stroke:#28a745,color:#155724 class ERROR error class UNLOCK success 關鍵概念: ...

January 17, 2026 · 4 分鐘 · Peter

AWS 跨區域遷移後的技術債清理:Strapi URL 的隱藏陷阱

接手專案,先看帳單 因為老闆信用卡到期了要換新卡,我順便看了一下 AWS 帳單金額,發現比預期高。之前詢問外包商技術長(已離職),得到的回覆是:「服務都已經從新加坡遷移到台北了,除了 S3 有保留做備份,其他都刪除了。」 身為工程師,最不能接受的就是「應該是這樣」。我決定親自盤點。 名詞解釋 在繼續之前,先解釋一下會提到的 AWS 服務: 服務 說明 費用特性 S3 (Simple Storage Service) 物件儲存服務,用來存放檔案、圖片、影片 按儲存容量和請求次數計費 NAT Gateway 讓私有子網路的資源能存取網際網路 按小時計費,即使沒流量也要錢 Elastic IP 固定的公開 IP 位址 使用中免費,未關聯則收費 VPC (Virtual Private Cloud) 虛擬私有網路,隔離你的雲端資源 VPC 本身免費,但相關資源收費 Network Load Balancer 負載平衡器,分散流量到多台伺服器 按小時和處理的資料量計費 ECR (Elastic Container Registry) Docker 映像檔儲存庫 按儲存容量計費 重點是:有些資源即使沒有流量,只要存在就會收費。NAT Gateway 和未關聯的 Elastic IP 就是典型的「隱形殺手」。 盤點遺留資源 # 檢查 EKS 叢集(Kubernetes 服務) aws eks list-clusters --region ap-southeast-1 # 結果:空的 ✓ # 檢查 RDS(資料庫) aws rds describe-db-instances --region ap-southeast-1 # 結果:空的 ✓ # 檢查 NAT Gateway aws ec2 describe-nat-gateways --region ap-southeast-1 \ --filter "Name=state,Values=available" # 結果:2 個還在跑 完整盤點結果: ...

January 10, 2026 · 3 分鐘 · Peter