Skip to main content

APISIX Pod 重啟故障排查 SOP

· 17 min read

適用情境:

  • 已知 APISIX 某個 Pod 的 container 發生重啟。
  • 主要可用資料為 Prometheus metrics 與 Kibana logs。
  • 目標是區分「直接原因、觸發原因、放大因素」,而不是只記錄 CPU 或 latency 很高。

先說結論

調查時必須區分:

直接原因:是什麼機制讓 container 結束?

觸發原因:什麼事件觸發了這個機制?

放大因素:哪些限制或設定讓事件惡化?

例如:

直接原因:Liveness probe 連續失敗,kubelet 重啟 container
觸發原因:Route B 流量暴增,使 worker 無法及時回應 probe
放大因素:CPU limit 過低並發生明顯 throttling

CPU、latency、connection 都是調查訊號,不應單獨當成根因。


STEP 0:確認觀測資料可用

不同 APISIX、kube-state-metrics 與監控設定,可能具有不同的 metric labels。執行後續查詢前,先確認:

  • APISIX 與 kube-state-metrics 版本。
  • Prometheus scrape interval。
  • APISIX metrics 是否具有 podnamespacecluster 等額外 labels。
  • Kibana 中 access log 與 error log 的 index/data view。
  • request_timerequest_lengthbytes_sent 是否被解析成 numeric fields。
  • 是否有 Kubernetes events、kubelet logs 或 probe metrics。

先檢查 APISIX metric 的實際 labels:

count by(cluster, job, namespace, pod, instance)(
apisix_http_status
)

APISIX 原生 metrics 不保證包含 podnamespace。這些 labels 通常由 Prometheus service discovery 或 relabeling 加入。若環境沒有 pod label,後面的查詢需改用實際存在的 instance 或其他 target label。

查案順序

鎖定重啟時間 T

確認 container 的終止方式

排除 rollout、probe、node 等外部事件

檢查 CPU、memory 與 throttling

判斷單 Pod 或全域異常

定位 Route、Client、Request 與 Upstream

建立時間線並評估證據強度

不要一開始就從 Route latency 猜根因。


STEP 1:鎖定重啟時間

1.1 確認觀察區間內是否重啟

increase(
kube_pod_container_status_restarts_total{
namespace="$namespace",
pod="$pod",
container="$container"
}[10m]
)

kube_pod_container_status_restarts_total 是 counter。查詢結果大於 0,代表這段時間內有 restart;實際結果可能因 Prometheus 外插而不是整數。

若只是想在圖表上觀察數值發生過幾次變化,也可以使用:

changes(
kube_pod_container_status_restarts_total{
namespace="$namespace",
pod="$pod",
container="$container"
}[10m]
)

但 counter 或 time series 重建時,changes() 也可能把 reset 計為一次變化。

1.2 取得最後終止時間

若 kube-state-metrics 版本有提供此實驗性指標:

kube_pod_container_status_last_terminated_timestamp{
namespace="$namespace",
pod="$pod",
container="$container"
}

其值為 Unix timestamp。將最後終止時間記為 T,後續 Prometheus 與 Kibana 固定查看:

T-15m ~ T+5m

若沒有 timestamp metric,可從 restart counter 的變化點、告警時間或 container 啟動時間反推:

kube_pod_container_state_started{
namespace="$namespace",
pod="$pod",
container="$container"
}

STEP 2:確認 container 為什麼結束

2.1 Last Termination Reason

T 附近查詢:

kube_pod_container_status_last_terminated_reason{
namespace="$namespace",
pod="$pod",
container="$container"
} == 1

查詢結果中的 reason label 才是要判讀的值。

Reason可以確認的事下一步
OOMKilledContainer 因 OOM 被終止檢查 memory limit、working set 與流量/payload
ErrorProcess 以錯誤狀態結束檢查 exit code 與 APISIX error log
CompletedProcess 正常結束對長期運行的 APISIX 仍需查明為何退出
ContainerCannotRunContainer 無法啟動檢查 image、entrypoint、mount 與權限

不要用 max_over_time(...[1h]) 判斷「最後一次 reason」。它可能保留區間內出現過的多個 reason series,無法保證與本次重啟一一對應。

2.2 Last Exit Code

同樣在 T 附近查詢:

kube_pod_container_status_last_terminated_exitcode{
namespace="$namespace",
pod="$pod",
container="$container"
}

不要使用 max_over_time(exitcode[1h])。它取得的是區間內數值最大的 exit code,不是時間上最後一筆 exit code。

Exit Code可以確認的事不能直接確認的事
137通常表示收到 SIGKILL不等於一定 OOM,需搭配 termination reason
143通常表示收到 SIGTERM不代表一定是正常部署
139通常表示收到 SIGSEGV仍需 error log 或 core dump 證明 crash 位置
1Process 非零結束需從 log 找實際 application error
0Process 正常結束碼不代表 APISIX 的退出符合預期

2.3 排除 Kubernetes/平台事件

即使 APISIX 沒有 application error,container 仍可能因下列事件重啟:

  • Deployment rollout 或 image 更新。
  • Scale down、Pod deletion 或手動操作。
  • Node drain、shutdown、NotReady 或 eviction。
  • Liveness probe failure。
  • Pod termination grace period 到期後被強制 SIGKILL
  • ConfigMap/Secret/sidecar 行為造成 Pod 替換。

若 Kibana 有收 Kubernetes events 或 kubelet logs,應搜尋:

kubernetes.pod.name:"$pod" and
message:(
"Killing" or
"Unhealthy" or
"Liveness probe failed" or
"Evicted" or
"Preempting"
)

若只有 APISIX access/error log,沒有 Kubernetes event、kubelet log 或 probe metric,通常無法確認是誰觸發了 SIGTERMSIGKILL。此時應把結論標為 UnknownPossible


STEP 3:確認是否為資源問題

3.1 CPU 使用量

sum(
rate(
container_cpu_usage_seconds_total{
namespace="$namespace",
pod="$pod",
container="$container"
}[5m]
)
)

結果單位為 CPU cores,例如 1.5 代表平均使用約 1.5 cores。

3.2 CPU Limit 使用率

100 *
sum(
rate(
container_cpu_usage_seconds_total{
namespace="$namespace",
pod="$pod",
container="$container"
}[5m]
)
)
/
sum(
kube_pod_container_resource_limits{
namespace="$namespace",
pod="$pod",
container="$container",
resource="cpu",
unit="core"
}
)

若查不到 limit series,可能代表 container 沒有設定 CPU limit,此時不能計算百分比。

CPU 使用率高本身不會直接讓 container 重啟,但可能造成 probe timeout、latency 上升或 worker 無法及時處理請求。

3.3 CPU Throttled Periods

100 *
sum(
rate(
container_cpu_cfs_throttled_periods_total{
namespace="$namespace",
pod="$pod",
container="$container"
}[5m]
)
)
/
sum(
rate(
container_cpu_cfs_periods_total{
namespace="$namespace",
pod="$pod",
container="$container"
}[5m]
)
)

這是「發生 throttling 的 CFS periods 比例」,不是損失的 CPU 百分比。

同時觀察實際 throttled time:

sum(
rate(
container_cpu_cfs_throttled_seconds_total{
namespace="$namespace",
pod="$pod",
container="$container"
}[5m]
)
)

判讀時應同時看:

  • CPU usage 是否接近 limit。
  • Throttled periods 是否持續偏高。
  • Throttled seconds 是否同步增加。
  • Probe failure 或 latency 是否發生在 throttling 之後。

3.4 Memory Working Set

sum(
container_memory_working_set_bytes{
namespace="$namespace",
pod="$pod",
container="$container"
}
)

3.5 Memory Limit 使用率

100 *
sum(
container_memory_working_set_bytes{
namespace="$namespace",
pod="$pod",
container="$container"
}
)
/
sum(
kube_pod_container_resource_limits{
namespace="$namespace",
pod="$pod",
container="$container",
resource="memory",
unit="byte"
}
)

Memory working set 接近 limit 只代表 OOM 風險提高。要確認 container OOM,仍應以 reason="OOMKilled"、kernel/kubelet log 或 OOM event 為直接證據。

另外,重啟當下 memory 突然下降通常是重啟的結果,不應直接解讀為 memory 問題的原因。


STEP 4:判斷單 Pod 或全域異常

以下範例假設 APISIX Pod 名稱符合 apisix.*,且 metrics 具有 podnamespace labels。請依實際環境調整 selector。

4.1 CPU by Pod

sum by(pod)(
rate(
container_cpu_usage_seconds_total{
namespace="$namespace",
pod=~"apisix.*",
container="$container"
}[5m]
)
)

4.2 RPS by Pod

sum by(pod)(
rate(
apisix_http_status{
namespace="$namespace",
pod=~"apisix.*"
}[5m]
)
)

4.3 判讀方式

觀察初步假設還要驗證
單 Pod CPU 高流量不均、Pod local state、worker/plugin 或 node 問題Pod RPS、connections、node resource
全 Pod CPU 高全域流量、共用 plugin 或設定變更Route RPS、change event、plugin dependency
全 Pod upstream latency 高共用 backend 或網路路徑異常upstream_addr、不同 backend、健康檢查
單 Pod latency 高Pod、Node 或 connection pool 問題同 Route 跨 Pod 比較

這些現象只能形成假設,不能直接當成根因。


STEP 5:定位流量來源

5.1 Pod RPS

sum(
rate(
apisix_http_status{
namespace="$namespace",
pod="$pod"
}[5m]
)
)

5.2 Route RPS

topk(
10,
sum by(route)(
rate(
apisix_http_status{
namespace="$namespace",
pod="$pod"
}[5m]
)
)
)

Top 10 只能找到流量最大者,不能直接找到異常者。調查時應比較:

  • 事件前後。
  • 前一小時的相同長度區間。
  • 昨日或過去七天的相同時段。
  • 同 Route 在其他 Pod 的表現。

例如,找出相較一小時前增加最多的 Route:

topk(
10,
sum by(route)(
rate(
apisix_http_status{
namespace="$namespace",
pod="$pod"
}[5m]
)
)
-
sum by(route)(
rate(
apisix_http_status{
namespace="$namespace",
pod="$pod"
}[5m] offset 1h
)
)
)

offset 1h 只是一個方便的比較基準,不一定代表正常 baseline。週期性明顯的服務應比較昨日或過去多日的相同時段。

5.3 HTTP Status

先看完整分布:

sum by(route, code)(
rate(
apisix_http_status{
namespace="$namespace",
pod="$pod"
}[5m]
)
)

再聚焦錯誤與限流:

sum by(route, code)(
rate(
apisix_http_status{
namespace="$namespace",
pod="$pod",
code=~"429|4..|5.."
}[5m]
)
)

重點觀察:

  • 429:限流或 retry amplification。
  • 499:client 在 response 完成前關閉連線。
  • 500:APISIX、plugin 或 upstream application error。
  • 502503504:upstream connection、availability 或 timeout。

流量暴增也可能全部回傳 2xx,因此不能只查 4xx5xx


STEP 6:拆解 Latency

APISIX 的 latency histogram 單位為 milliseconds:

histogram_quantile(
0.99,
sum by(le, type, route)(
rate(
apisix_http_latency_bucket{
namespace="$namespace",
pod="$pod"
}[5m]
)
)
)

Latency type 定義

Type定義調查方向
requestClient 到 APISIX 再回到 client 的端到端時間整體 request path
upstream等待 upstream response 的時間Backend、DNS、網路、upstream connection
apisixrequest - upstreamDownstream 傳輸、NGINX/APISIX processing、plugin

apisix latency 不只代表 Lua plugin 或 CPU。它也包含 downstream/client 傳輸及 NGINX 的非 upstream 時間。

判讀原則:

  • upstream 上升:優先檢查 backend 與 APISIX 到 backend 的網路路徑。
  • apisix 上升:檢查 CPU throttling、plugin、slow client、大 request/response。
  • request 上升但 upstream 正常:較偏向 downstream 或 APISIX 的非 upstream 部分。
  • p99 上升時必須同時看 RPS/sample count;低流量 Route 的 p99 容易抖動。

STEP 7:檢查 Connection

只加總代表目前連線狀態的 series,避免把 acceptedhandled 等累積值混入:

sum by(state)(
apisix_nginx_http_current_connections{
namespace="$namespace",
pod="$pod",
state=~"active|reading|writing|waiting"
}
)
現象可能代表驗證方式
active流量增加、長連線或 request 堆積RPS、request latency、upstream latency
readingClient 傳送較慢或 request 較大request_length、client IP、method
writingClient 接收較慢或 response 較大bytes_sent、request time
waitingKeepalive idle connectionsRPS、keepalive 設定、連線上限

Connection state 是線索,不是大 request/response 或 request 堆積的直接證據。


STEP 8:使用 Kibana 驗證假設

8.1 固定 Pod 與時間

kubernetes.pod.name:"$pod"

時間範圍固定為:

T-15m ~ T+5m

若 APISIX access log 與 error log 位於不同 data view,應分別查詢。

8.2 Route 與 Request

依實際 log schema 查看:

route_id
route_name
uri
method
status
upstream_addr

建立 Top values 或聚合,確認:

  • 哪個 Route 的 request count 增加最多。
  • 哪些 URI/method 與高 latency 或錯誤同時出現。
  • 問題是否集中於特定 upstream_addr

8.3 Client

查看:

client_ip
x_forwarded_for
consumer
user_agent

注意:只有在可信任的 proxy 會覆寫 X-Forwarded-For 時,才應把它當成 client identity。否則該 header 可能被 client 偽造。

8.4 慢 Request

標準 NGINX $request_time 單位是秒,精度到毫秒。若 Kibana field 為 numeric:

kubernetes.pod.name:"$pod" and request_time > 1

代表 request time 大於 1 秒。

查看:

request_time
upstream_response_time
status
route_id
uri
upstream_addr
client_ip

request_time 被 ingest pipeline 轉為 milliseconds,門檻才應使用 1000。文件與 dashboard 必須標明實際單位。

8.5 大 Request

例如搜尋大於 1 MiB 的 request:

kubernetes.pod.name:"$pod" and request_length > 1048576

8.6 大 Response

例如搜尋大於 10 MiB 的 response:

kubernetes.pod.name:"$pod" and bytes_sent > 10485760

以上門檻只是範例,應依服務的正常分布調整。最好同時比較 p50、p95、p99 與事件前後的分布,而不是只用單一固定門檻。

8.7 Error Log

kubernetes.pod.name:"$pod" and
message:(
"worker" or
"signal" or
"segmentation" or
"lua" or
"memory" or
"worker_connections" or
"too many open files" or
"upstream timed out"
)

特別注意:

  • Worker exit/signal。
  • Segmentation fault。
  • Lua exception。
  • worker_connections are not enough
  • too many open files
  • Upstream connect/read timeout。
  • Logger plugin queue 或 shared dict 錯誤。

STEP 9:建立事件時間線

固定整理每個訊號第一次明顯偏離 baseline 的時間:

時間觀察證據來源初步解釋
T-120sRoute B RPS 上升 4 倍Prometheus可能的觸發事件
T-90sActive connections 上升Prometheus流量或 request duration 增加
T-60sCPU 接近 limitPrometheus資源壓力
T-55sThrottled seconds 上升PrometheusCPU quota 開始產生影響
T-40sp99 latency 上升Prometheus使用者可見影響
T-10sLiveness probe failedEvent/kubelet log直接重啟機制
TContainer terminatedkube-state-metrics重啟時間

必須回答:

  1. 哪個訊號最早出現?
  2. 它是否有合理機制導致後續事件?
  3. 是否有其他假設也能解釋相同現象?
  4. 有沒有直接證據或反證?

最早出現的事件不一定是根因;時間先後只能建立因果假設,仍需搭配機制與其他證據。


STEP 10:結案格式

10.1 調查結論

分類結論證據反證/資料缺口信心
直接原因例:Liveness probe failure 後由 kubelet 重啟Probe event、termination timestamp無 kubelet logHighly Likely
觸發原因例:Route B 流量暴增RPS 在 CPU 前 60 秒上升尚未重播 requestHighly Likely
放大因素例:CPU limit 過低CPU 接近 limit 且 throttled time 上升尚未壓測新 limitPossible

10.2 信心程度

等級定義
Confirmed有直接 lifecycle、event、log、core dump 或可重現證據
Highly Likely時間線與機制一致,且沒有主要反證
Possible只有相關性,或仍存在同樣合理的替代解釋
Unknown現有 observability 無法判定

10.3 後續行動

結案時至少列出:

  • 立即緩解措施。
  • 永久修正措施。
  • 需要新增的 metrics、logs 或 alerts。
  • 驗證修正的方法與成功條件。
  • Owner 與預定完成時間。

查案 Checklist

事件與 lifecycle

  • 確認 APISIX metrics 的 labels 與 log schema
  • 鎖定 restart 時間 T
  • Termination reason
  • Exit code
  • Rollout/scale/Pod deletion
  • Liveness probe
  • Node/eviction/kubelet event

資源與影響範圍

  • CPU usage 與 CPU limit
  • CPU throttled periods 與 throttled seconds
  • Memory working set 與 memory limit
  • 是否只有單一 Pod
  • Pod RPS 是否分配不均

流量與依賴

  • Route RPS 與 baseline 增幅
  • HTTP status(包含 2xx429
  • Request/upstream/APISIX latency
  • Connection states
  • Upstream address/backend

Logs 與結案

  • Kibana Route/URI/method
  • Kibana Client/Consumer
  • 大 request/response
  • APISIX error log
  • 建立事件時間線
  • 寫出直接原因
  • 寫出觸發原因
  • 寫出放大因素
  • 記錄反證與資料缺口
  • 標示信心程度
  • 建立後續行動

參考資料