擴縮是慢半拍的回授
desired 算得出來,容量卻要等下一期取樣與暖機才接得住這一波。
自動擴縮不是一顆「把機器變大」的按鈕。它是一條控制迴路:取樣、跟目標比、增減實例、等新實例真的能接流量、再取下一次樣。面試要講的不是演算法名稱,而是這條迴路比流量慢在哪裡,慢的那段由誰吸收。
目標追蹤(target tracking)把「要幾台」收成一個除法。訊號是這一期看得到的負載,單台容量是它在目標利用率下願意承擔的量,不夠就向上取整:
單台每分鐘能做 200 筆請求、目標利用率 60% 時,每台只計畫承擔 120 筆。訊號是 400,desired = ceil(400 / 120) = 4。訊號衝到 1600,desired = ceil(1600 / 120) = 14。公式本身沒有時間;時間在「訊號是上一期的」以及「新的那 10 台還在暖機」。
flowchart LR
Load[負載] --> Metric[指標]
Metric --> Policy[政策]
Policy --> Boot[啟動]
Boot --> Cap[容量]
Cap --> Metric
容量指回的是指標,不是負載。多出來的實例要等下一期取樣才會改變決策,所以這一期的尖峰用不到這一期才啟動的容量。
ASG 或 ECS service 的 desired count 上升,不表示這一分鐘的請求有地方去。RUNNING 還沒跟上、或新任務還在拉映像檔時,溢出已經記在延遲、佇列長度或拒絕數上。
指標會遲到,也會被截斷
CPU 到頂之後不再告訴你超載多少;佇列在機器還是零台時就存在。
政策只看得到你餵它的訊號。訊號選錯,公式再漂亮也是在追一個謊。實務上三條常用訊號差在「會不會飽和」以及「沒有實例時還看不看得到」。
| 訊號 | 看得到的量 | 飽和之後 | 零實例時 |
|---|---|---|---|
| CPU / 記憶體 | 已在跑的實例有多忙 | 停在 100%,超載幅度消失 | 沒有樣本,政策不會醒 |
| 每目標請求數 | ALB 實際打進來的請求 | 不截斷,尖峰高度還在 | 沒有目標就沒有「每目標」;群組至少留 1,或改看負載平衡器總請求 |
| 佇列堆積 | 可見訊息、或每任務堆積 | 堆積會繼續長,幅度還在 | 佇列還在,可以從 0 叫醒 |
CPU 是落後且會截斷的忙碌度;請求率看得到高度;佇列比 CPU 更早,因為工作在處理器忙起來之前就已經堆在外面。
截斷會把一次補齊變成連乘。沿用第 1 節的單台 200、目標 60%:真實需求停在 1600,未截斷的 desired 是 14。若訊號是 CPU,觀測值不會高過「目前就緒台數 × 200」。從 4 台就緒開始,每一輪都假設上一輪加進去的機器已經能服務:
| 輪次 | 期初就緒 | CPU 觀測 | desired |
|---|---|---|---|
| 0 | 4 | min(1600, 800) = 800 | 7 |
| 1 | 7 | min(1600, 1400) = 1400 | 12 |
| 2 | 12 | 1600(容量已超過需求) | 14 |
同樣 1600 筆,請求率指標第一輪就會把 desired 訂在 14。CPU 要連走三輪,每輪之間還隔著一個取樣週期加暖機。
請求率也有自己的謊:它假設每筆請求一樣貴。健康檢查、快取命中與一隻會打跨服務的重端點混在同一個 ALBRequestCountPerTarget 裡,目標追蹤會對平均成本擴,對那隻重端點仍然不夠。佇列指標的經典寫法是可見訊息數除以執行中任務數,目標是「每任務可接受的堆積」。執行中任務為 0 時這個除法沒有定義,必須另給一個醒法(直接用可見訊息數,或在除數為 0 時填一個很大的堆積),否則群組會停在 min = 0。
p99 與 5xx 是結果,不是負載。下游壞了也會讓它們變差,政策會在錯誤的方向加機器。擴縮對不齊的那一段,用佇列吸收或用熔斷、艙壁隔離與降級拒絕;那不是把 desired 再乘二能補上的。
目標追蹤、冷卻與震盪
冷卻是故意少做一次決策;它同時擋住擴與縮。
EC2 Auto Scaling Group 上常見三種動態政策。Simple 過了門檻就做一次固定增減,然後進入 cooldown。Step 依超出的幅度分級(例如 CPU 高一點加 1 台、高很多加 3 台),同樣受 cooldown 或暖機約束。Target tracking 只收一個目標值,由服務去算 desired;ASG 這條路不使用群組預設的 cooldown(預設 300 秒那條是給 simple 用的),改看 EstimatedInstanceWarmup。暖機中的實例不計入 CloudWatch 平均,避免新機器的低使用率把平均值拉下來、立刻觸發縮容;但計算「還要加幾台」時把它們算進已啟動的數量,避免同一波缺口連開兩輪。
ECS Service Auto Scaling 走的是 Application Auto Scaling,不是 ASG 那組 API。目標追蹤可以分別設擴出冷卻與縮入冷卻,預設指標有 ECSServiceAverageCPUUtilization、記憶體,以及 ALBRequestCountPerTarget。名字像,時間常數不要混:ASG 用暖機穩定,ECS 仍讓你對擴與縮各設一段冷卻。
Kubernetes 的對應物是 HPA 的 behavior。縮容穩定視窗預設 300 秒:這段時間內看到的建議值取最保守的那個,避免一個尖峰過去就立刻拆掉 Pod。物件本身(指標來源、行為欄位)由容器編排負責;這裡只借它的時間常數,說明「穩定視窗」與 cooldown 是同一種阻尼。
sequenceDiagram
participant L as 負載
participant M as 指標
participant P as 政策
participant N as 新實例
L->>M: 尖峰到達
M->>P: 上期超標
P->>N: 啟動
Note over N: 暖機,尚不能接
N-->>P: 納入已啟動數
P->>M: 下期才重看
決策用的是上一期指標。新實例在暖機結束前既接不到流量,也不該拿來稀釋平均值;下一次能不能再動,還要看冷卻過了沒有。
阻尼的代價要用兩次尖峰才看得清楚。中間的山谷若允許縮容,第二個峰會從很小的台數重新暖機,方向反轉變多、溢出變多。若冷卻把山谷蓋住,第一個峰加上去的機器還在,第二個峰接得住,反轉消失,空轉上升。第 4 節把雙尖峰的冷卻從 0 調到 5 時,反應式的方向反轉從 3 次掉到 0,溢出從 2400 降到 1200,空轉從 6000 升到 12800。冷卻不是「讓你錯過第二個峰」的開關,它是把兩次峰黏成一次容量、用錢換少震一次。
它不只阻止縮。上一刀擴完之後,下一刀要等冷卻結束;等得到的時候負載可能已經從尖峰掉下來,最終台數會小於尖峰當下的 desired,溢出卻已經記完。預設那組單尖峰裡,未截斷的尖峰 desired 是 14,反應式實際最高只到 10,就是第二刀被冷卻推到需求已回落之後。
同一條尖峰,兩種政策
反應式用上一分鐘的誤差去追;預測用提前量去買尖峰前的空轉。
下面這組並排把第 1 到第 3 節的延遲放進同一條 16 分鐘的曲線。左欄是反應式目標追蹤,右欄是帶提前量的預測。兩邊共用流量、目標利用率、冷卻與暖機;提前量只餵右欄。縱軸共用,柱高可以直接比。
規則就這些,沒有另一套隱藏劇本。每格 1 分鐘,單台就緒後每分鐘 200 筆。開場台數用基準負載套第 1 節的公式,所以預設從 4 台就緒開始。每一格先決策、再接這一格的流量。反應式的訊號是上一格的需求(第 0 格用基準負載);預測的訊號是從這一格起、往後「提前量」格以內(含這一格)的最大需求。暖機 W 表示這一格啟動的實例從第 t+W 格開始可服務,W = 0 就是當格計入。冷卻 C 表示一次增減之後,要再等超過 C 格才能下一次;C = 0 時下一格就能再動。CPU 截斷把訊號壓到「決策前已就緒的台數 × 200」。縮容先取消還沒就緒的,再減正在服務的,最少留 1 台。已啟動總數(含暖機)拿來和 desired 比,所以同一波不會連開兩輪;訊號用的是外部需求,不會被新實例的低使用率稀釋。
綠柱是這一分鐘真正能接的容量,黃柱是已啟動但仍在暖機的部分,藍線是需求。藍線上的紅點表示需求高過綠柱。提前量只作用右欄。
橫軸是第 0 到第 15 分鐘,兩邊共用縱軸。預設讀數:反應式溢出 1200、空轉 7000、實例分鐘 91;預測溢出 0、空轉 10200、實例分鐘 121。右欄用尖峰前的空轉換掉溢出。
三組對照都從重置後的預設出發,只改講到的旋鈕。
提前量與暖機。預設提前量 3、暖機 2,右欄溢出是 0。暖機改成 4、提前量維持 3,右欄溢出變成 200,左欄從 1200 升到 1600。提前量改成 0、暖機回到 2,右欄溢出是 600,左欄仍是 1200:提前量 0 仍看得到這一分鐘,反應式只看上一分鐘,兩欄不會重合。提前量要蓋住的是「決策到可服務」的整段,不是跟暖機數字比大小就結束;中間若被冷卻擋下一刀,蓋住的時間還要再長。
雙尖峰與冷卻。流量改成雙尖峰(兩個中心相隔 5 分鐘)、冷卻改成 0:左欄方向反轉 3 次,溢出 2400,空轉 6000,兩個峰都從低檔重新暖機。冷卻改成 5:左欄反轉 0 次,溢出降到 1200,空轉升到 12800。右欄在提前量 3 時兩種冷卻的溢出都是 0,差別在反轉與空轉,不在有沒有接住。
CPU 截斷。流量改成階梯維持,冷卻與暖機都歸零,尖峰位置維持 6。請求率下,左欄在階梯出現的下一分鐘從 4 直接到 14,溢出 800;右欄因提前量 3,在階梯開始前就到 14,溢出 0。改成 CPU 截斷後,左欄變成 7、12、14,溢出 1000;右欄也是 7、12、14,只是發生在階梯之前,溢出仍是 0。截斷限的是看得到的高度,提前量限的是什麼時候開始看。這組 4、7、12、14 就是第 2 節那張表。
排程、預測、縮到零與垂直擴縮
看得到的日週期用時鐘;重複出現的形狀用預測;單行程尺寸不是平行度。
第 4 節的右欄是把預測收成「提前看 N 分鐘的最大值」。產品裡的預測比這個粗,也比這個依賴歷史。AWS Predictive Scaling 用最多約兩週的負載去推未來約 48 小時,排程緩衝(scheduling buffer)決定提早多久把容量準備好,這段緩衝要不短於暖機。Forecast only 只給你看曲線;forecast and scale 會真的改 desired。它通常與一條目標追蹤並存,追蹤負責預測沒吃掉的殘差。並存時把追蹤的縮容關掉(DisableScaleIn),否則追蹤會把預測剛墊上去的緩衝縮掉。
| 政策 | 何時動作 | 提前量 | 主要失敗 |
|---|---|---|---|
| Target tracking | 指標偏離目標 | 沒有,暖機後才補上 | 尖峰比迴路短就溢出;CPU 會截斷 |
| Step / Simple | 告警門檻 | 沒有,冷卻期間拒絕下一次 | 門檻太密就震;太疏就補太慢 |
| Scheduled | 時鐘 | 你自己把 min 提前到暖機之前 | 日曆外的尖峰完全沒反應 |
| Predictive | 歷史負載的預測 | 緩衝應蓋過暖機 | 沒見過的活動日會低估;和追蹤同時縮會拆掉緩衝 |
| 佇列 backlog | 每任務堆積 | 訊息在 CPU 忙之前就看得到 | 執行中任務為 0 時除法沒定義 |
| 垂直(改規格 / VPA) | 單行程長期不夠 | 通常要替換實例或重啟 | 不增加平行度,接不住 QPS 尖峰 |
排程與預測買的是提前量;目標追蹤買的是殘差。垂直擴縮改的是單台尺寸,不在這條台數公式裡。
縮到零是目標追蹤的死角,不是一種更積極的目標。CPU 或記憶體在一台實例都沒有時沒有樣本,政策永遠看不到該擴。排程可以在營業開始前把 min 從 0 抬到 1,讓樣本重新出現;佇列指標則因為訊息不依賴消費者而能自己醒。第一台醒過來仍要付冷啟動:拉映像、建連線池、填本地快取,這就是暖機的極端形。函式平台依並發自動長實例,是另一種控制迴路,不在這篇的 desired count 裡展開。
垂直擴縮(換更大的實例規格,或 VPA 調整 Pod 的 request)解決的是單行程記憶體或單執行緒上限。它通常要替換或重啟,對一個比暖機還短的 QPS 尖峰沒有幫助。水平擴縮才是在加平行度。兩者可以同時存在:垂直把單台容量定在一個不會頻繁 OOM 的尺寸,水平再依請求或佇列改台數。不要用垂直擴縮去回答「尖峰時要加幾台」。
任務層與節點層要相加
desired 上去但 running 不動,先看有沒有機器可排,再看應用。
到目前為止的台數,預設「加一台就有地方跑」。自管節點時這不成立。任務層與節點層各是一條迴路,暖機串聯,不是取最大值。
上面兩層都會擴,不代表容量相加。任務 desired 上升而節點滿了,新任務停在 PENDING,就緒容量維持原樣。
ECS on EC2 是這兩條的標準拼法:服務自動擴縮改任務數,capacity provider 的 managed scaling 再依預留容量去推 ASG。Fargate 把節點那一條收進平台,你只剩任務啟動的暖機。運算模型光譜比較的是這幾種運算你還擁有哪一層;這裡只計「你還握著 desired count 時,延遲加在哪」。GKE Standard 是 HPA 加節點池自動擴縮;Autopilot 同樣把節點層收走。GCE 託管實例群可以做預測式擴縮,GKE 的 Cluster Autoscaler 本身仍看排不進去的 Pod,不預測請求量。
Cluster Autoscaler 不看 CPU 目標。它看的是因 CPU 或記憶體 request 放不進現有節點的 Pod,以及節點是不是空到可以收。縮節點還有一段 unneeded 等待(預設約 10 分鐘)和 PDB。Karpenter 依 pending Pod 的資源形狀挑機型,consolidation 比傳統 autoscaler 積極,觸發條件仍是「有 Pod 排不進去」,不是負載預測。Pod 的 request 設太高,HPA 覺得利用率還低、節點卻先滿;設太低,很多 Pod 擠進同一台,節點層很晚才被叫醒。這組數字要跟 HPA 的指標一起看,不是只看叢集 CPU。
desired 上升、running 不動:節點層或映像拉取。running 已跟上、p99 仍壞:應用、鎖、或下游。後者繼續擴任務只是把更多連線打進已經壞的依賴。
面試時怎麼取捨
先問尖峰比暖機長還是短、訊號會不會截斷、節點是不是你的。
| 聽到的需求 | 先問 | 傾向 |
|---|---|---|
| 每天固定時段變忙 | 峰比暖機長還是短 | 短就用排程把 min 提前抬起;長再加目標追蹤吃噪音 |
| 不定期短尖峰 | 暖機幾分鐘、指標會不會到頂 | 預測或排程;純 CPU 反應式一定慢半拍 |
| 佇列消費者 | 堆積的是訊息還是 CPU | 每任務堆積,並定義零任務時怎麼醒 |
| 擴了還是逾時 | running 有沒有跟上 desired | 沒跟上先看節點與映像;跟上了就看下游,改走降級而不是再擴 |
四種開場白對應四個先問的問題。沒有「一律目標追蹤 CPU 50%」這種答案。
可以主動講的取捨就三個數字:目標利用率、冷卻(或穩定視窗)、暖機。目標調低,台數變多、比較不會截斷,帳單上升。冷卻調長,反轉變少、山谷裡的空轉變多,下一次擴容也變慢。暖機若估太短,新實例被算進容量但其實還不能接,下一期又補一輪;估太長,政策遲遲不把他們當容量,容易過度擴。預測的緩衝要比這段暖機長,並且不要讓另一條政策把緩衝縮掉。
它不會在暖機結束前變出容量,不會在 CPU 已經 100% 時告訴你還差幾倍,也不會分辨「負載真的上來了」和「下游壞了所以變慢」。第三件交給熔斷與降級;前兩件要在選指標與提前量時就承認。