iT邦幫忙

2026 iThome 鐵人賽

DAY 29
0
Kubernetes

初見 Kubernetes - 純網站後端開發踏入 K8s 世界的經驗分享系列 第 29 篇

Day 29 - 服務故障的問題查找方向

  • 分享至 

  • xImage
  •  

Day28 說了跨站流量與故障切換。當服務出現 timeout、502 或 503,可能卡在名稱解析、建立連線、入口路由,也可能是 Pod 或下游回應太慢。

今天用 gateway 呼叫 api-a 的情境,把幾個常見的檢查位置與機制說清楚。

先確認問題發生在哪個階段

開始調整設定前,先保留失敗的時間、URL、狀態碼與完整錯誤訊息。再分成兩種情況:

  • container 還沒啟動:Pod 的 Events 顯示 image 拉取失敗,就從 Node、container runtime 與 registry 的解析和連線開始查。
  • container 已經啟動:例如 gateway 呼叫 api-a 失敗,就從呼叫端所在的 Pod、實際請求與 log 開始查。

ImagePullBackOff 表示拉取失敗後正在延後重試。image 名稱、授權、憑證、網路與 DNS 都可能造成失敗,具體原因要看 Events。

Node 拉取 image 時,使用的是 Node/runtime 所在環境的解析設定。一般 Pod 則由自己的 DNS 設定決定查詢路徑;常見的 ClusterFirst 配置會透過叢集 DNS 解析 Service 名稱,通常由 CoreDNS 提供服務。

Node 拉取 image 與 Pod 呼叫服務的兩條 DNS 路徑,解析與請求分開呈現

同一個名稱在自己的電腦、Node 與 Pod 裡,可能使用不同的 resolver。問題出在 gateway 裡,就盡量從相同的環境測試。

DNS 取得的是 Service 位址

假設 gateway 與 api-a 位於 demo Namespace,api-a 使用一般 ClusterIP Service,以 8080 port 提供 /health。呼叫端可以用同 Namespace 的短名稱 api-a,或完整名稱 api-a.demo.svc.cluster.local;叢集網域以實際設定為準。

其中幾個角色分別處理:

  • DNS:把 Service 名稱解析成 ClusterIP。
  • Service:提供穩定的服務目的,設定 port、targetPort 與 selector。
  • EndpointSlice:記錄對應的後端位址與就緒資訊,供流量轉送元件使用。
  • Pod:由應用程式監聽連接埠,實際處理請求。

同一個 Service 存續期間,ClusterIP 通常維持不變。Pod 被替換時,Pod IP 的變動會反映在 EndpointSlice,呼叫端繼續使用原本的 Service 名稱。

這篇先聚焦一般 ClusterIP Service;Headless Service 可以直接回覆 Pod 位址,行為另有差異。

從呼叫端一路縮小範圍

先確認 gateway 使用的 DNS,再查名稱與 HTTP 回應:

kubectl -n demo exec deploy/gateway -- cat /etc/resolv.conf
kubectl -n demo exec deploy/gateway -- nslookup api-a.demo.svc.cluster.local
kubectl -n demo exec deploy/gateway -- curl -v --connect-timeout 3 --max-time 10 http://api-a:8080/health

指令假設容器具備這些工具,多 container 的 Pod 要選對 container。也可以用診斷 Pod,留意它的 Namespace、NetworkPolicy 與 proxy 配置是否接近原呼叫端。

觀察時可以分開看:

  • 查詢逾時或找不到名稱:確認 DNS 設定、查詢名稱與 Namespace,再看 DNS 服務是否可達。
  • 有取得 IP,連線仍失敗:往目的連接埠、網路、TLS 與應用程式監聽查。
  • 已收到 HTTP 狀態碼:表示某個 HTTP 元件有回應,接著找出回應來自入口代理或應用程式,以及命中的 Host、path 與 route。

HTTPS 的 DNS 位址、TLS SNI 與 HTTP Host 各有用途。測試時保留原網域,改成 IP 可能改變 TLS 或路由行為。

再檢查服務與後端:

kubectl -n demo get svc api-a -o yaml
kubectl -n demo get endpointslice -l kubernetes.io/service-name=api-a -o yaml
kubectl -n demo get pods -l app=api-a -o wide

這裡假設 Service selector 使用 app=api-a,重點看:

  • Service 的 selector 是否選到正確 Pod,port 與 targetPort 是否對應程式監聽的連接埠。
  • EndpointSlice 的 addresses、ports 與 conditions.ready 是否符合預期。
  • Pod 的 Ready 狀態、readiness probe、程式監聽位址與 log。

Pod 顯示 Running 時,仍要把就緒條件與實際請求一起看。服務轉送也會受到叢集網路元件與 NetworkPolicy 影響。

DNS 快取與連線池保存的東西不同

有時診斷工具測試已正常,程式卻還在連舊位址。這時可以分清兩件事:

  • DNS 快取:保存名稱解析的結果,依 TTL 與 resolver 行為更新。
  • connection pool:保存已建立的連線,讓後續請求重用,降低 TCP/TLS 建立連線的成本。

沿用前一天的外部 API 網域情境,假設 api.example.com 的 DNS 回答從 A 改成 B。程式如果仍重用連到 A 的既有連線,就可能繼續向 A 發出請求。重新建立連線時,才有機會取得更新的解析結果。

DNS 更新後仍可重用既有連線,連線退場後建立新連線時才再次解析

以 .NET 的 HttpClient 為例:

  • 建立新連線時才解析 DNS,HttpClient 本身不依 DNS TTL 追蹤位址更新。
  • PooledConnectionLifetime 可以限制連線被沿用的時間。達到生命週期並完成目前相關請求後,連線會退場;需要新連線時再解析 DNS。
  • 使用長期共用的 HttpClient 搭配連線生命週期,或由 IHttpClientFactory 管理 handler 與連線池。每個請求都直接 new HttpClient(),再釋放它與所屬連線池,可能增加成本或耗盡連接埠;factory 建立的 client 可重用 handler 與連線池。

設定值要依 DNS/網路變動頻率與服務需求選擇。重新解析仍會受到 resolver 快取影響,DNS TTL、連線生命週期與 request timeout 分別管理。

前面一般 Service 的 Pod 更新,主要由 endpoints 更新處理。這裡的 DNS 變動以外部網域為例;既有連線的恢復,另看結束、失敗與重連行為。

Nginx 與 Istio 各自幫忙什麼?

如果請求經過 Nginx,也要看它如何取得與更新 upstream 位址:

  • 檢查實際的 proxy_pass/upstream 寫法,確認何時解析主機名稱。
  • 使用 upstream 的動態 resolve 時,要搭配 resolver 與共享的 zone;Nginx 開源版從 1.27.3 起支援這項能力。
  • 使用變數的 proxy_pass 也有自己的名稱解析與 URI 處理方式,調整時一起確認 Host 與轉送路徑。

Istio 可以提供流量路由、逾時與重試,作用範圍取決於請求是否經過對應 proxy,以及設定是否命中。應用程式的 DNS 查詢、proxy 的服務探索與 Node 拉取 image,各有自己的處理路徑。

最後調整 timeout 與 retry 時,我會先看三件事:

  • 等待的是 DNS、建立連線,還是下游回應?整個請求最多允許多久?
  • 重試會增加多少流量?應用程式與代理都重試時,要一起計算總次數與時間。
  • 寫入型 API 能否安全重試?需要避免同一個操作重複執行。

先記錄症狀、找出查詢與連線發生的位置,再依 DNS、服務後端、代理與連線池逐段確認。這樣調整設定時,就能知道這次改動要解決哪一段問題。

參考資料


上一篇
Day 28 - GSLB - 多叢集站點的處理
系列文
初見 Kubernetes - 純網站後端開發踏入 K8s 世界的經驗分享 共 29 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言