發表文章

灰度發布 (Progressive Delivery / Gradual Rollout) 是什麼?從 Canary、Blue-Green Deployment 到 Feature Flag

圖片
當新的版本服務要上 production 生產環境時,最直覺的做法就是把所有流量一次全部切過去上線,這是時常看到的,這種做法如果順利的話那就皆大歡喜(實際上不太可能),但只要新版本藏了一個沒測到的 critical bug,等於讓 100% 的使用者同時出問題。這種「一次全量發布」在生產環境裡最省事、卻也最危險,在短期快速迭代開發下很難有 100% 沒有任何缺陷的軟體應用服務,但現在還是時常看到不少企業在做這樣的事情。實際上為了控制這個風險,software engineering 圈子早已發展出「灰度發布」這個概念,底下可細分出 Canary Rollout、藍綠部署(Blue-Green Deployment)、A/B Testing、Feature Flag 幾種做法,但這幾個詞常常被混著用、甚至誤用。這系列文章會先在第一篇把名詞和框架釐清,後面幾篇再針對 AWS EKS + Kubernetes 的具體實作、以及 Web 通訊 App(含 WebSocket 長連線、Android/iOS 行動端)的灰度發布細節展開。在往下讀名詞定義之前,先介紹這系列文章會用的範例情境,方便對照後面出現的程式碼與設定。 本系列的範例情境 這系列文章會用一套虛構的雲端 Web 通訊 App (姑且稱它 ChatApp) 當作貫穿全系列的範例,情境設定盡量貼近實際專案常見的架構,方便對照後面出現的程式碼與設定檔。 ChatApp 提供三種客戶端,使用者可以自由選擇任一種登入使用: iOS App (原生 Swift) Android App (原生 Kotlin) 網頁版 (瀏覽器直接使用的 Web App) 三種客戶端底層都呼叫同一組後端 API,後端部署在 AWS EKS(Kubernetes)上,由幾個主要模組組成: Gateway / Ingress 層 :對外的統一入口,也是本文提到的 Canary 流量分流實際作用的地方 訊息服務(Message Service) :處理聊天訊息的收發,跟客戶端之間走 WebSocket 長連線 認證服務(Auth Service) :處理登入、身分驗證 資料庫(Database) :儲存使用者資料與歷史訊息記錄 後面所有的範例(包含 chatapp-v1 / chatapp-v2 、 ...

新創資安事件觀察:當環境變數變成整個平台的信任根

無意中看到整合 AI Agent 的 PaaS 平台爆發資安事件:攻擊者從一把雲端管理憑證出發,一路打穿到主資料庫,把所有租戶存在環境變數裡的第三方金鑰整批查出來、匯出去。問題的根源不是「被駭」這件事本身,而是架構設計把太多信任集中在同一個地方,環境變數欄位是整個平台唯一的機密儲存層,一次控制平面層級的入侵,就足以讓所有租戶的金鑰在同一時間曝險。由此有一些想法,寫了這篇技術文章探討金鑰該如何儲存。 (一) 從攻擊鏈看金鑰為什麼能走這麼遠 該平台官方在事故發生後陸續更新的攻擊鏈,是這起事件最值得拆解的部分:攻擊者取得一把 AWS 管理層級的憑證,先進入東京的一個共用叢集,再從叢集摸到控制平面的 VPN,最後連上主資料庫,把使用者存在環境變數裡的金鑰查出來、匯出去。時間軸上,平台在 8 月 28 日凌晨建立事故頁,一開始只說是「內部服務憑證」被用來查詢環境變數紀錄;直到隔天晚間,才更新出「AWS 管理憑證 → 共用叢集 → 控制平面 VPN → 主資料庫」這條完整路徑。 如果那把 AWS 憑證的有效權限,本身就同時涵蓋叢集存取、VPN 素材、資料庫連線——那麼圖面上的四個箭頭,實際上可能從頭到尾只被同一個身分牽著走,中間沒有一關是「前一個身分拿不到、簽不出、也改不掉」的獨立授權。 要驗證一組雲端身分的真實影響範圍,不能只看掛了哪些 policy 名稱,而要直接查它的 effective permissions: aws accessanalyzer list-findings \ --analyzer-arn <analyzer-arn> \ --filter '{"resource":{"contains":["<role-or-user-arn>"]}}' 這個指令會列出該身分實際可觸及的資源, 找出哪些資源被過度開放給外部身分, 而不是政策文件上「看起來」授權了什麼——這正是重建這類事故攻擊面時,第一件該做的事。 (二) 問題不在用環境變數,而在把它當成信任根 環境變數本身不是問題。幾乎所有 PaaS——Heroku、Render、Vercel 等——都用環境變數當作應用程式讀取設定的方式,這是產業慣例,沒有問題。真正的問...

SRE 視角解讀:Uber Software Factory 如何治理 AI Agent 時代的 Token 成本

AI coding agent 在過去兩年間,從輔助工具演變為軟體開發生命週期中的主要生產力來源,也讓 AI 支出從可預測的授權費用,轉變為隨用量複合成長的變動成本。Token 計費模式沒有天然的用量上限——工程師平行執行多個 agent、對整個 codebase 進行重構、或把重複性作業自動化,都會讓消耗量隨活躍度倍增。這個轉變,正把「AI 支出治理」推成一個新的工程管理課題。 Uber Engineering 在 2026 年 8 月發布的〈Running a Software Factory Efficiently at Uber Scale〉,公開了該公司如何在用量快速擴張的同時壓低單位成本。根據該文披露,從 2 月到 8 月中,全公司 agentic 功能的週活躍用戶成長 7 倍,agent 請求量成長 9。4 倍;若把模型版本固定下來比較,每千次請求的成本從高峰下降約 34%,每個 session 的成本從 6 月高峰下降 52%。 若拉開一層來看,這套框架並不是全新的方法論,而是把 SRE 領域行之有年的容量規劃與 error budget 治理,搬進了 AI agent 這個新的資源類型。過去容量規劃談的是運算資源與流量成長的關係,error budget 談的是可用性與變更速度的平衡;Uber 這篇文章處理的問題結構完全相同,只是把「運算資源」換成了「token」、把「可用性」換成了「單位成本」。理解這層對應關係,有助於判斷哪些做法是換皮的舊經驗,哪些才是 AI agent 帶來的新變數。以下先整理該文揭露的治理框架,再回頭對照這層關係。 分層治理模型:掌控力隨專用程度遞增 該文將公司內的 agent 使用場景分為四層,由最泛用到最受管理。層級愈高、愈受管理,團隊對成本、輸出品質與模型選擇的掌控力也愈強。互動式的個人 coding session 屬於最泛用的一層,routing 與模型選擇多半由工程師臨時決定;相對地,把工作交給「受管理的 agent」(例如自動 code review、CI 失敗自我修復、on-call alert 分診),則讓平台方能統一決定模型版本與執行環境。該文將此描述為核心策略轉向——從優化個別終端 session,轉向優化一支專門化的 managed agent 艦隊。 成本方程式:六個變數,三個...

Tips on Dockerfile

Dockerfile 看似簡單,幾行 FROM、RUN、COPY 就能把應用打包成 image,但寫得好不好,直接牽動 build 速度、image 大小、安全性與日後的可維護性。本文整理幾個實務中最常用、卻也最常被忽略的 Dockerfile 撰寫訣竅。 (一)善用 layer cache , 指令由 變動較少 排到 容易變化 (由上到下) Docker build 是逐層(layer)快取的,只要某一層的指令或內容改變,該層之後的所有 layer 都會失效重建。因此在Dockerfile內容中,指令順序(由上到下)應該從「變動頻率低」排到「變動頻率高」。最典型的例子,是先複製相依清單、安裝套件,最後才複製原始碼: # 先處理相依,讓 cache 命中率最大化 COPY package.json package-lock.json ./ RUN npm ci COPY . . 如此一來,只要原始碼變動、相依沒變, npm ci 這一層就能命中 cache,不必重跑。 (二)用 multi-stage build 縮小 image 把「編譯環境」與「執行環境」分開,只把最終產物帶進 runtime image,可大幅縮小體積,也降低受攻擊面(attack surface): FROM golang:1.22 AS builder WORKDIR /app COPY . . RUN go build -o server . FROM alpine:3.20 COPY --from=builder /app/server /server ENTRYPOINT ["/server"] runtime image 不再包含 compiler 與原始碼,從數百 MB 降到數十 MB 是常態。 (三)合併 RUN 並在同一層清理 cache 每一個 RUN 都是一層。把相關指令用 && 串接成一層,並在同一層清掉套件快取,避免多餘體積殘留在 image 裡: RUN apk add --no-cache curl \ && rm -rf /var/cache/apk/* Debian/Ubuntu 則常見 apt-get install 之後接 rm -rf /var/l...

熱門文章

Docker 環境下的 Proxy 配置