- Published on
ALB 又回 502 了:先別急著加機器,log 會告訴你是哪一種
- Authors
-
-
- Name
- Alex Yu
-
前陣子監控又跳出 ALB 的 502/504:59 筆,90 秒後自動恢復。上一次看到 5xx 是爬蟲把 search 頁打爆那次——7,262 筆,錯誤率一度衝到 50%。
兩次的 alert 在 dashboard 上長得一模一樣。但一種加機器有用,另一種加了純浪費錢。
分辨方法藏在 ALB access log 的兩個欄位裡,30 秒就能判完。
同一套環境,我遇過三種 ALB 5xx
同樣的 K8s + ALB,三種完全不同的成因:
- 健檢誤殺:app 自己的 bug 害 readiness 失敗,Pod 被 ALB 判死——IntersectionObserver 跑進 server side 那次
- 容量過載:流量真的把所有 Pod 打爆——search 頁被爬蟲打掛那次
- Pod 輪替的 race condition:Pod 下線和 ALB 停止導流沒對上時間——這篇要講的
前兩種寫過了,這篇補第三種,順便整理一張三種通用的判讀表。
先看 target_ip:ALB 把 request 送去哪了
ALB 的 access log 每一行都記著這個 request 被送到哪個目標(target_ip),以及對方花了多久處理(target_processing_time,-1 代表連線根本沒建立、或沒等到回應)。出 5xx 的時候,這兩個欄位會透露 ALB 當下的處境:
# 容量型:ALB 手上找不到任何健康的目標,request 連送都沒送出去
elb_status_code=503 target_ip="-" target_processing_time=-1
# Pod 輪替型:ALB 有明確目標,但對方正在關門、拒接
elb_status_code=502 target_ip="10.x.x.x" target_processing_time=-1再搭配量和發生時間:
| log 特徵 | 代表什麼 | 該查什麼 |
|---|---|---|
量大、錯誤率高、target_ip="-" | 沒有健康的 Pod 可送(過載或健檢全掛) | 容量、健檢為什麼失敗 |
量少、集中在部署或 scale-in 的時間點、target_ip 集中在特定幾個 Pod | 流量被送給關閉中的 Pod | graceful shutdown 設定 |
| 平時零星出現、跟著特定 endpoint 走 | app 層 bug 拖垮健檢 | 應用程式本身 |
這次的 59 筆對著表看:全部指向同兩個 Pod、發生在 Pod 輪替當下、同時段其他 8 個 Pod 零錯誤、90 秒自動恢復。教科書等級的 Pod 輪替型——加機器?其他 Pod 明明閒著。
為什麼 Pod 輪替會掉 502
K8s 關 Pod 有個流程:先送一個叫 SIGTERM 的訊號給程式,意思是「準備收工,把手上的事收尾」。大多數 web server 收到後會立刻拒收新連線,跑完手上的 request 就退出。
問題是 ALB 不知道這件事:
Pod 收到 SIGTERM → 拒收新連線
↓(同一時刻)
ALB 的導流名單還沒更新 → 新 request 照送
↓
connection refused → 502/504K8s 和 ALB 是兩套獨立系統,「這個 Pod 要下線了」從 K8s 傳到 ALB 需要時間。空窗期內送過去的 request,全部變 502。
白話版:店員到點下班先鎖了門,但門口的取號機還在發號碼牌。
修法:先停止被導流,再真正退出
這套做法叫 graceful shutdown。關鍵是 K8s 和 ALB 兩端各設一半,只設一邊等於沒設。
Pod 端用 preStop hook——K8s 送出 SIGTERM 之前,會先執行你指定的指令,跑完才真的通知程式關閉。設定寫在 Deployment 裡:
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
# ↓ 整個關閉流程的時限(含 preStop 的 30 秒)。超時 K8s 直接 SIGKILL
# 強制終止,所以這個值必須大於 sleep
terminationGracePeriodSeconds: 40
containers:
- name: web
lifecycle:
preStop:
exec: # ← SIGTERM 之前先跑這段
command: ['sh', '-c', 'sleep 30'] # ← 掛 30 秒等 ALB 把流量排開ALB 端——ALB 移除一個目標前會先進入排空(draining)階段:新 request 不再送過去,處理中的讓它跑完。願意等多久,這就是 target group 的 deregistration_delay(預設 300 秒)。用 AWS Load Balancer Controller 的話,這個值寫在 Ingress 的 annotation 上:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
# ↓ 排空最多等 30 秒,跟 preStop 的 sleep 對齊
alb.ingress.kubernetes.io/target-group-attributes: deregistration_delay.timeout_seconds=30這個值要大於等於 preStop 的 sleep。Pod 撐著不關的時間就是留給 ALB 排空用的,排空時間比 sleep 短的話,還沒跑完的連線會在 Pod 明明活著時就被切斷,前面的 sleep 等於白設了。
三個值的關係,帶上這次的設定:
preStop sleep (30) ≤ deregistration_delay (30) < terminationGracePeriodSeconds (40)最後加一道保險。K8s 有個資源叫 PodDisruptionBudget(PDB),用來規定「同一時間最多允許幾個 Pod 下線」——node 回收、kubectl drain 這類要趕走 Pod 的操作,動手前都得先過這條限制。設了它,就不會一次把半數 Pod 同時帶進上面說的空窗期:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: web-pdb
spec:
maxUnavailable: 1 # ← 同一時間最多允許 1 個 Pod 下線
selector:
matchLabels:
app: web # ← 套用在哪一群 Pod 上順手把探針分層
K8s 的探針(probe)是定期戳 Pod 的健康檢查,三種各管一件事:liveness 問「還活著嗎」(失敗直接重啟 Pod)、readiness 問「現在能接流量嗎」(失敗只是暫時移出導流名單)、startup 問「開好機了嗎」(通過之前另外兩個不會開始戳)。
這次順手調了兩件事:
- liveness 改淺層(nginx 自己回 200 就好)、readiness 維持深層(真的去戳應用)
- startupProbe 給冷啟動 90 秒預算,避免 Pod 還在暖機就被判不健康
分層的理由是「忙」不該被判成「死」。過載時 app 回應變慢,深層的 liveness 也會跟著 timeout——K8s 一判定失敗就重啟 Pod,容量瞬間又少一台,剩下的 Pod 分到更多流量、變得更慢、更容易被判死。本來只是忙不過來,被 liveness 重啟一輪之後,變成真的全掛。
還有一個試了又拆的設定:slow_start。它跟 deregistration_delay 一樣是 target group 的屬性(寫在同一個 annotation 裡),作用是新 Pod 進導流名單後,流量從低漸進拉升,避免一進來就分到滿載。排查時順手加了,但它防的情境——Pod 還沒暖好就被打滿——startupProbe 加 readiness 已經擋掉了,留著只是多一個要對齊的參數,後來便拆了。
「90 秒自動恢復」不是沒事
這種小事故最容易被當雜訊:量少、自動恢復、dashboard 上一個小小的 spike。但它的觸發條件是 Pod 輪替——rollout、scale-in、node 回收,每一次都會重演。
也就是說,只要有部署,就固定有一批使用者的 request 會失敗:這次是 59 筆,等於 90 秒內有 59 次操作直接看到錯誤畫面。不會多花錢、機器也不會變慢,但這個數字會跟著部署次數一直累積。花一次功夫把 graceful shutdown 對齊,之後每一次部署都少掉這批錯誤。
重點整理
- ALB 5xx 先看
target_ip:"-"是找不到人送(容量或健檢),具體 IP 是送錯人(Pod 輪替) - graceful shutdown 要兩端對齊:
preStop sleep ≤ deregistration_delay < terminationGracePeriodSeconds,只設一邊等於沒設 - liveness 淺、readiness 深——「忙」和「死」分開判
- 自動恢復的零星 5xx 不是雜訊,每次部署都會重演一輪
延伸閱讀
同一套環境的另外兩種 5xx:
- 6,401 個 IP、3 分鐘、7,262 個錯誤:一隻普通的爬蟲怎麼打掛我的 search 頁——容量型
- Next.js IntersectionObserver 導致 ALB 健康檢查失敗問題——健檢誤殺型
如果上面那張表判出來是容量型,下一步:
- 每個 IP 只發 2 個 request 的攻擊,rate limit 要掛在什麼上面?——邊緣指紋辨識、拆掉「一變多」的結構、止血隔離,三層防禦各自能擋到哪