適用情境:
- 已知 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 是否具有
pod、namespace、cluster等額外 labels。 - Kibana 中 access log 與 error log 的 index/data view。
request_time、request_length、bytes_sent是否被解析成 numeric fields。- 是否有 Kubernetes events、kubelet logs 或 probe metrics。
先檢查 APISIX metric 的實際 labels:
count by(cluster, job, namespace, pod, instance)(
apisix_http_status
)
APISIX 原生 metrics 不保證包含
pod與namespace。這些 labels 通常由 Prometheus service discovery 或 relabeling 加入。若環境沒有podlabel,後面的查詢需改用實際存在的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 | 可以確認的事 | 下一步 |
|---|---|---|
OOMKilled | Container 因 OOM 被終止 | 檢查 memory limit、working set 與流量/payload |
Error | Process 以錯誤狀態結束 | 檢查 exit code 與 APISIX error log |
Completed | Process 正常結束 | 對長期運行的 APISIX 仍需查明為何退出 |
ContainerCannotRun | Container 無法啟動 | 檢查 image、entrypoint、mount 與權限 |
不要用
max_over_time(...[1h])判斷「最後一次 reason」。它可能保留區間內出現過的多個reasonseries,無法保證與本次重啟一一對應。
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 位置 |
1 | Process 非零結束 | 需從 log 找實際 application error |
0 | Process 正常結束碼 | 不代表 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,通常無法確認是誰觸發了 SIGTERM/SIGKILL。此時應把結論標為 Unknown 或 Possible。
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 具有 pod 與 namespace 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。502/503/504:upstream connection、availability 或 timeout。
流量暴增也可能全部回傳 2xx,因此不能只查 4xx/5xx。
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 | 定義 | 調查方向 |
|---|---|---|
request | Client 到 APISIX 再回到 client 的端到端時間 | 整體 request path |
upstream | 等待 upstream response 的時間 | Backend、DNS、網路、upstream connection |
apisix | request - upstream | Downstream 傳輸、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,避免把 accepted、handled 等累積值混入:
sum by(state)(
apisix_nginx_http_current_connections{
namespace="$namespace",
pod="$pod",
state=~"active|reading|writing|waiting"
}
)
| 現象 | 可能代表 | 驗證方式 |
|---|---|---|
active 高 | 流量增加、長連線或 request 堆積 | RPS、request latency、upstream latency |
reading 高 | Client 傳送較慢或 request 較大 | request_length、client IP、method |
writing 高 | Client 接收較慢或 response 較大 | bytes_sent、request time |
waiting 高 | Keepalive idle connections | RPS、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-120s | Route B RPS 上升 4 倍 | Prometheus | 可能的觸發事件 |
T-90s | Active connections 上升 | Prometheus | 流量或 request duration 增加 |
T-60s | CPU 接近 limit | Prometheus | 資源壓力 |
T-55s | Throttled seconds 上升 | Prometheus | CPU quota 開始產生影響 |
T-40s | p99 latency 上升 | Prometheus | 使用者可見影響 |
T-10s | Liveness probe failed | Event/kubelet log | 直接重啟機制 |
T | Container terminated | kube-state-metrics | 重啟時間 |
必須回答:
- 哪個訊號最早出現?
- 它是否有合理機制導致後續事件?
- 是否有其他假設也能解釋相同現象?
- 有沒有直接證據或反證?
最早出現的事件不一定是根因;時間先後只能建立因果假設,仍需搭配機制與其他證據。
STEP 10:結案格式
10.1 調查結論
| 分類 | 結論 | 證據 | 反證/資料缺口 | 信心 |
|---|---|---|---|---|
| 直接原因 | 例:Liveness probe failure 後由 kubelet 重啟 | Probe event、termination timestamp | 無 kubelet log | Highly Likely |
| 觸發原因 | 例:Route B 流量暴增 | RPS 在 CPU 前 60 秒上升 | 尚未重播 request | Highly Likely |
| 放大因素 | 例:CPU limit 過低 | CPU 接近 limit 且 throttled time 上升 | 尚未壓測新 limit | Possible |
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(包含
2xx與429) - Request/upstream/APISIX latency
- Connection states
- Upstream address/backend
Logs 與結案
- Kibana Route/URI/method
- Kibana Client/Consumer
- 大 request/response
- APISIX error log
- 建立事件時間線
- 寫出直接原因
- 寫出觸發原因
- 寫出放大因素
- 記錄反證與資料缺口
- 標示信心程度
- 建立後續行動

