我注意到,許多在生產(chǎn)環(huán)境中使用Kubernetes的工程師實際上從未親眼見過系統(tǒng)如何自我修復。他們知道Kubernetes確實具有這種能力,也讀過相關文檔,但他們從未見過ReplicaSet控制器被觸發(fā),也沒有通過kubectl describe命令觀察到系統(tǒng)因資源耗盡而自動關閉Pod,更沒有在系統(tǒng)發(fā)生級聯(lián)故障時看到Pod的端點信息消失。正是這類情況會在凌晨3點左右突然出現(xiàn),而這個教程會讓你親身體驗這些過程。

你將克隆一個代碼倉庫,搭建一個由3個節(jié)點組成的真實Kubernetes集群,然后故意引發(fā)7種不同的故障情況,觀察系統(tǒng)每次都是如何自我恢復的。這里沒有模擬數(shù)據(jù),也沒有虛假的集群環(huán)境——完全是真實的Kubernetes系統(tǒng)、真實的故障場景以及真實的恢復過程。通過這個教程的學習,當你遇到生產(chǎn)環(huán)境中的類似問題時,就能立刻識別出這些故障的模式。

目錄

什么是KubeLab?

KubeLab是一個開源的Kubernetes故障模擬實驗平臺。它運行著一個真實的Node.js后端服務、一個PostgreSQL數(shù)據(jù)庫,同時還集成了Prometheus和Grafana等監(jiān)控工具,所有這些組件都部署在一個真實的Kubernetes集群中。當你點擊“關閉Pod”按鈕時,后端會直接調(diào)用Kubernetes API來刪除正在運行的Pod——這一切都是真實發(fā)生的,沒有任何模擬成分。

模擬場景 學習內(nèi)容
隨機關閉Pod 了解ReplicaSet的自我修復機制以及Pod數(shù)據(jù)的不可變性
移除工作節(jié)點 體驗零停機時間的維護策略及PodDisruptionBudgets的作用
CPU壓力測試 了解限流與系統(tǒng)崩潰的區(qū)別,以及隱藏的延遲問題
內(nèi)存壓力測試 體驗OOMKill機制、退出碼137的含義,以及自動重啟循環(huán)的工作原理
數(shù)據(jù)庫故障 了解StatefulSets的作用以及PVC數(shù)據(jù)持久化機制
級聯(lián)Pod故障 明白為什么復制副本數(shù)量為2是不夠的
readiness probe失敗 理解liveness檢查與readiness check的區(qū)別,以及流量控制的重要性

完成整個實驗過程大約需要90分鐘的時間。如果你有特定的生產(chǎn)問題想要復現(xiàn),也可以直接進入相應的模擬環(huán)境進行操作。

KubeLab集群地圖——Pod按節(jié)點分組顯示,顏色根據(jù)狀態(tài)區(qū)分。在模擬過程中,芯片的顏色會實時變化,并在節(jié)點之間移動。

先決條件

你需要對Docker有基本的了解,并且能夠熟練使用命令行,但并不要求事先具備Kubernetes的使用經(jīng)驗。

硬件要求:建議配置至少8GB的RAM內(nèi)存,16GB會更為理想。該實驗環(huán)境可以在Mac、Linux或安裝了WSL2的Windows系統(tǒng)上運行。你需要安裝三款工具:Multipass用于創(chuàng)建集群所需的Ubuntu虛擬機;kubectl是進行所有模擬操作時使用的Kubernetes命令行工具;Git則用于克隆代碼倉庫。如果你無法同時運行這三臺虛擬機,代碼倉庫中還提供了setup/docker-compose-preview.md文件,其中包含了使用Docker Compose搭建的模擬環(huán)境配置信息,該環(huán)境使用模擬數(shù)據(jù)運行,因此不需要真正的Kubernetes集群。

如何啟動實驗環(huán)境

關于整個集群的設置步驟,請參考代碼倉庫中的setup/k8s-cluster-setup.md文件。該文檔詳細介紹了如何使用Multipass創(chuàng)建三臺虛擬機、安裝MicroK8s、將工作節(jié)點加入集群以及部署KubeLab的過程。請按照步驟操作,直到所有11個Pod都顯示為“Running”狀態(tài)為止:

kubectl get pods -n kubelab
# 此時應該能看到11個Pod都處于Running狀態(tài)

接下來,在不同的終端標簽頁中分別打開兩個端口轉(zhuǎn)發(fā)配置,確保在整個實驗過程中這些配置一直處于運行狀態(tài):

# 標簽頁1——KubeLab用戶界面,地址為http://localhost:8080
kubectl port-forward -n kubelab svcfrontend 8080:80

# 標簽頁2——Grafana,地址為http://localhost:3000
kubectl port-forward -n kubelab svc/grafana 3000:3000

登錄Grafana的用戶名和密碼分別為:admin / kubelab-grafana-2026

將KubeLab用戶界面和Grafana并排顯示在屏幕上。屏幕的左半部分是KubeLab應用,右半部分則是Grafana。從第3個模擬實驗開始,你可以同時觀察這兩個界面。

模擬實驗1:隨機終止一個Pod

在這個模擬實驗中,我們會通過Kubernetes API來終止一個正在運行的后端Pod。如果沒有Kubernetes,就需要通過SSH登錄到服務器,手動查找出現(xiàn)故障的進程并重新啟動它——通常這種情況會在凌晨3點左右由系統(tǒng)警報提示用戶。

在開始操作之前:先運行命令kubectl get pods -n kubelab -w,觀察是否有Pod進入“Terminating”狀態(tài),隨后會有新的Pod出現(xiàn)。

在點擊“Run”按鈕之前,兩個終端并排運行,實時顯示各種操作結(jié)果以及Pod的狀態(tài)變化。前端應用和Grafana也處于運行狀態(tài)。

kubectl get pods -n kubelab -w
# backend-abc123  1/1   正在終止    0   2分鐘
# backend-xyz789  1/1   正在運行     0   0秒   ← 復制集已創(chuàng)建了替代Pod

發(fā)生的情況:復制集控制器發(fā)現(xiàn)實際存在的Pod數(shù)量與預期數(shù)量不一致,因此會在終止原有Pod的同時創(chuàng)建新的替代Pod。端點控制器會在發(fā)送SIGTERM信號之前將即將終止的Pod從服務中移除,因此沒有流量會到達這些即將終止的Pod。

生產(chǎn)環(huán)境中的常見問題:如果缺少就緒性檢測探針,新創(chuàng)建的Pod在建立數(shù)據(jù)庫連接之前就會開始接收請求,從而導致500錯誤響應出現(xiàn),這種情況會在每次部署后持續(xù)2到3秒鐘。

解決方法:將`replicas`參數(shù)設置為2,添加就緒性檢測探針,并將`terminationGracePeriodSeconds`參數(shù)設置為你所設定的最長請求超時時間。

模擬測試2:釋放一個工作節(jié)點

這個模擬測試會先將某個工作節(jié)點隔離出來,然后將其上的所有Pod遷移到其他節(jié)點上。

“隔離”一個工作節(jié)點意味著將該節(jié)點標記為不可調(diào)度狀態(tài)。當你運行`kubectl cordon `命令時,Kubernetes控制平面會在該節(jié)點上添加`node.kubernetes.io/unschedulable:NoSchedule`標簽。(這種標簽會告訴調(diào)度器避免將Pod部署到該節(jié)點上,除非這些Pod具有相應的“容錯配置”。)這樣就可以確保不再有新的Pod被部署到這個節(jié)點上。不過,這一操作不會影響已經(jīng)在該節(jié)點上運行的Pod。

隔離節(jié)點是準備對節(jié)點進行維護的第一步,也是最安全的步驟。這樣做可以確保在釋放節(jié)點資源的過程中,調(diào)度器不會嘗試將新的工作負載分配到該節(jié)點上,從而避免影響維護工作的順利進行。

如果沒有Kubernetes,你就需要手動完成這些操作:首先停止服務器服務,等待正在處理的請求全部完成后再進行修復,最后再重新啟動服務器。這樣一來,服務器的停機時間就完全無法預測了。

在開始操作之前:請先運行`kubectl get pods -n kubelab -o wide -w`命令,查看每個Pod實際運行在哪個節(jié)點上。

kubectl get pods -n kubelab -o wide -w
名稱                     節(jié)點               狀態(tài)
backend-abc123-xk2qp    kubelab-worker-1   正在終止    ← 已被遷移
backend-abc123-n7mw3    kubelab-worker-2   正在運行     ← 已重新調(diào)度

在執(zhí)行`kubectl get nodes`命令后,該節(jié)點的狀態(tài)會顯示為“Ready,SchedulingDisabled”,直到你執(zhí)行`kubectl uncordon`命令為止。

發(fā)生的情況:該節(jié)點的配置參數(shù)`spec.unschedulable`被設置為`true`,因此系統(tǒng)會針對每個Pod分別執(zhí)行遷移操作。與直接刪除Pod不同,這一過程會先經(jīng)過PodDisruptionBudget策略的檢查。而直接的`kubectl delete pod`命令則會完全跳過這些檢查步驟——正因如此,在進行維護時使用`kubectl drain`命令來釋放節(jié)點資源總是比手動刪除Pod更安全。

生產(chǎn)環(huán)境中的陷阱:當副本設置中沒有啟用反親和性機制時,兩個副本往往會被分配到同一個節(jié)點上。如果從該節(jié)點中移除資源,這兩個副本會同時被強制驅(qū)逐出去,從而導致系統(tǒng)完全停止運行。盡管配置的副本數(shù)量為2個,但仍然會出現(xiàn)這種停機現(xiàn)象。

解決方法: 使用帶有拓撲鍵 `kubernetes.io/hostname` 的 Pod 反親和機制,同時配置一個 `PodDisruptionBudget`,并將其 `minAvailable` 參數(shù)設置為 `1`。

節(jié)點資源消耗情況:被隔離的節(jié)點會顯示為“Ready”狀態(tài),且“SchedulingDisabled”;相關 Pod 會被重新調(diào)度到其他節(jié)點上。

模擬測試 3:CPU 負載與限流機制

在這個模擬場景中,后端 Pod 會持續(xù)消耗 CPU 資源,60 秒后其使用量就會達到 200 MB 的上限。如果沒有 Kubernetes,某個失控的進程就會占用主機上的所有 CPU 資源,從而導致其他服務無法正常運行。

在開始測試之前: 先運行命令 `watch -n 2 kubectl top pods -n kubelab`,然后打開 Grafana 中的“CPU 使用情況”面板進行觀察。

kubectl top pods -n kubelab
# backend-abc123   200m   ← 60 秒內(nèi)一直處于上限狀態(tài);另一個 Pod 的 CPU 使用量約為 15 MB

實際發(fā)生的情況: Linux 的 CFS 調(diào)度機制會通過每 100 毫秒分配 20 毫秒的 CPU 資源來執(zhí)行限流操作,然后讓該容器中的所有進程暫停 80 毫秒。因此,這個 Pod 并不是因為出現(xiàn)故障而運行緩慢,而是因為有 80% 的時間都在處于暫停狀態(tài)才導致性能低下。

在實際生產(chǎn)環(huán)境中可能遇到的問題: 使用命令 `kubectl top` 查看 Pod 的 CPU 使用情況時,可能會看到其使用量介于 95 到 150 MB 之間,這看起來似乎很正常。但實際上,這些數(shù)據(jù)反映的是上限值,并不能真實反映實際的限流情況。因此,開發(fā)人員往往會花費大量時間檢查應用程序代碼,試圖找出導致延遲的問題,而實際上問題往往出在 CPU 使用限制設置得太低上。

解決方法: 對于那些對延遲非常敏感的工作負載來說,應該直接設置 CPU 資源請求量,而不需要設置限流值。這樣,調(diào)度機制就可以根據(jù)這些請求來安排 Pod 的運行位置,而不會在運行時進行限流操作。要確認是否存在限流現(xiàn)象,可以運行命令 `rate(container_cpu_cfs_throttled_seconds_total{namespace="kubelab"}[5m])`。

在一個后端 Pod 中,CPU 使用量始終穩(wěn)定地保持在 95 到 150 MB 之間,持續(xù)了 60 秒。而一個正常的 Pod 的 CPU 使用量會有所波動,這種固定的上限值實際上就是限流機制在起作用。

模擬測試 4:內(nèi)存壓力與 OOMKill 機制

在這個模擬場景中,后端 Pod 會不斷分配內(nèi)存資源,每次分配 50 MB 的內(nèi)存,直到操作系統(tǒng)因為內(nèi)存不足而終止該進程。如果沒有 Kubernetes,這個進程就會直接死亡,導致服務器崩潰,進而引發(fā)一系列問題。

在開始測試之前: 先運行命令 `kubectl get pods -n kubelab -l app=backend -w`,然后打開 Grafana 中的“內(nèi)存使用情況”面板進行觀察。

kubectl get pods -n kubelab -l app=backend -w
# backend-abc123   0/1   OOMKilled   3   5m   ← 沒有進入終止階段;SIGKILL 信號會直接導致進程死亡,不會執(zhí)行優(yōu)雅關閉流程

實際發(fā)生的情況: 容器中的 cgroup 內(nèi)存限制被突破了,超過了 256 MiB。Linux 內(nèi)核的 OOM Killer 機制會識別出這些占用過多內(nèi)存的進程,并向它們發(fā)送 SIGKILL 信號(退出代碼為 137),從而導致進程立即死亡。需要注意的是,這是由內(nèi)核直接執(zhí)行的操作,而不是 Kubernetes 的功能。由于 SIGKILL 信號無法被攔截或處理,因此預先設置的 `preStop` 鉤子程序不會被執(zhí)行,導致內(nèi)存中的數(shù)據(jù)或未完成的事務可能會丟失。Kubernetes 只是觀察到了進程的死亡情況,并將其標記為“OOMKill”,然后重新創(chuàng)建了一個新的容器來繼續(xù)運行任務。

生產(chǎn)環(huán)境中的問題:該容器可以正常運行8個小時,之后會因為內(nèi)存不足而自動重啟。此時內(nèi)存會被重置為0,一切看起來都恢復正常了。這種現(xiàn)象每8小時就會重復一次。重啟次數(shù)會逐漸增加,從7次增加到15次、30次,但由于在兩次崩潰之間各項指標看起來都很正常,因此并不會觸發(fā)任何警報。直到有用戶發(fā)來郵件,抱怨應用程序“最近運行得不太穩(wěn)定”,你才會發(fā)現(xiàn)問題。

解決方法:rate(kube_pod_container_status_restarts_total{namespace="kubelab"}[1h]) > 3時立即觸發(fā)警報,這樣在用戶注意到問題之前就能及時發(fā)現(xiàn)異常。kube_pod_container_status_restarts_total這個Prometheus指標表示:在過去一小時內(nèi),kubelab命名空間中的容器重啟了多少次,然后計算這一數(shù)值每秒增加的速度;如果該速率超過了每小時3次重啟的標準,就會觸發(fā)警報。一個正常運行的容器很少會頻繁重啟,因此如果一小時內(nèi)有多次重啟,通常說明容器的內(nèi)存已經(jīng)達到上限,導致它自動重啟,然后又重新開始運行這個循環(huán)。這種警報機制能夠在用戶發(fā)現(xiàn)問題之前,及時捕捉到這種隱性的內(nèi)存不足導致的自動重啟現(xiàn)象。

如何確認這個問題確實發(fā)生了:

kubectl describe pod -n kubelab  | grep -A 5 "Last State:"
# 原因:內(nèi)存不足導致容器自動重啟
# 結(jié)束代碼:137

要查看在內(nèi)核強制終止進程之前的日志信息,可以運行kubectl logs -n kubelab --previous。此時日志流會突然停止,而且不會顯示任何關閉提示信息,因為SIGKILL信號會導致系統(tǒng)立即終止,根本沒有時間進行清理或記錄最后的日志信息。

一個后端容器的內(nèi)存使用量會先上升,然后在內(nèi)存不足導致自動重啟時下降;而另一個容器的內(nèi)存使用量在整個過程中始終保持穩(wěn)定

模擬場景5:數(shù)據(jù)庫故障

在這個模擬場景中,我們將PostgreSQL StatefulSet的副本數(shù)量設置為0,這樣該容器就會完全終止。如果沒有Kubernetes的支持,數(shù)據(jù)庫服務器會直接崩潰,而數(shù)據(jù)的恢復情況就取決于是否保存了備份以及備份是在什么時候進行的。

在開始操作之前:請先運行kubectl get pods,pvc -n kubelab命令,確認PVC確實存在。

kubectl get pods,pvc -n kubelab
# postgres-0   (已終止)
# postgres-data-postgres-0   已綁定到PVC;數(shù)據(jù)仍然保存在該卷上

PVC(PersistentVolumeClaim)實際上是用戶提出的一種存儲請求。可以把它理解為容器在請求“我需要一定量的持久化存儲空間”。對于像PostgreSQL這樣的狀態(tài)ful應用來說,PVC至關重要。當數(shù)據(jù)庫容器被刪除時,PVC以及與之綁定的持久化存儲空間仍然會保留下來,因為實際的數(shù)據(jù)庫文件就是保存在這些存儲空間中的。當新的postgres-0容器被創(chuàng)建時,StatefulSet會自動重新綁定原來的PVC,這樣新容器就能訪問所有舊數(shù)據(jù),從而避免數(shù)據(jù)丟失。

發(fā)生的情況是: StatefulSet控制器刪除了相應的Pod,但并未更改與PersistentVolumeClaim的綁定關系。StatefulSets能夠確保Pod名稱的穩(wěn)定性以及PVC綁定關系的持久性。postgres-0始終會掛載postgres-data-postgres-0這個卷。當您恢復數(shù)據(jù)時,相同的Pod名稱會被重新使用,同樣的卷也會被重新掛載到該Pod上。PostgreSQL會重新執(zhí)行 WAL日志中的操作,從而恢復到一致的狀態(tài)。

生產(chǎn)環(huán)境中的常見問題:那些沒有連接重試邏輯的應用程序在出現(xiàn)故障后會返回500錯誤代碼,而且即使PostgreSQL恢復了正常運行,這些應用程序依然會處于故障狀態(tài)。而那些在獲取連接時不會檢查是否存在無效連接的連接池,也會導致這些無效連接永遠被保留下來。

解決方法:在您的應用程序中添加帶有指數(shù)退避機制的連接重試功能。在生產(chǎn)環(huán)境中使用網(wǎng)絡附加存儲設備(如EBS、GCE PD),這樣當某個Pod出現(xiàn)故障時,系統(tǒng)就可以將其重新調(diào)度到其他節(jié)點上繼續(xù)運行。

模擬測試6:Pod級聯(lián)故障

在這個模擬測試中,兩個后端副本會被同時刪除。如果沒有任何監(jiān)控機制,那么在沒有Kubernetes的情況下,您將不得不手動重啟所有的服務,并且還需要確保它們能夠按照正確的順序重新啟動。

在開始操作之前:請先運行命令kubectl get endpoints -n kubelab backend-service -w,觀察輸出的IP地址列表。

kubectl get endpoints -n kubelab backend-service -w
# 輸出結(jié)果中會顯示“ENDPOINTS ”,這意味著在此期間所有請求都會收到“連接被拒絕”的錯誤響應

實驗結(jié)果:兩個Pod都被刪除后,該服務就不再有任何可用的端點。ReplicaSet雖然會同時創(chuàng)建兩個新的副本,但直到這兩個新副本都通過準備就緒檢查之前,流量仍然無法正常傳輸。端點列表會先變?yōu)榭眨缓笤僦匦嘛@示出來。您可以在Grafana的“HTTP請求率”面板中看到具體的故障時間范圍。

在發(fā)生級聯(lián)故障時,系統(tǒng)會出現(xiàn)5xx錯誤代碼,實際故障時間為5到15秒,具體時間范圍也有明確標注

生產(chǎn)環(huán)境中的常見問題:配置replicas: 2只能防止一個Pod同時發(fā)生故障,但如果兩個副本都位于同一個節(jié)點上,而該節(jié)點出現(xiàn)故障,那么就會導致所有服務都無法正常運行。現(xiàn)在就可以通過命令kubectl get pods -n kubelab -o wide | grep backend來檢查這種情況;如果兩個Pod顯示的是同一個節(jié)點名稱,那就意味著只要有一個節(jié)點發(fā)生故障,整個系統(tǒng)就會陷入癱瘓。

解決方法:使用Pod反親和策略,強制將副本分配到不同的節(jié)點上;同時設置minAvailable: 1的PodDisruptionBudget參數(shù),以防止任何可能導致副本數(shù)量變?yōu)榱愕牟僮靼l(fā)生。

模擬測試7:準備就緒檢查失敗

在這個模擬測試中,其中一個后端Pod會在120秒的時間內(nèi)持續(xù)無法通過準備就緒檢查,而系統(tǒng)并不會重啟這個Pod。在沒有Kubernetes的情況下,您將無法在不關閉該Pod的情況下將其從流量調(diào)度機制中移除。在生產(chǎn)環(huán)境中,當您的應用程序在啟動時嘗試連接數(shù)據(jù)庫但數(shù)據(jù)庫響應速度很慢時,就會出現(xiàn)這種情況:雖然該Pod仍然處于運行狀態(tài),但它并沒有準備好接收請求,因此系統(tǒng)會暫時不讓它參與流量調(diào)度。

在開始操作之前:在一個標簽頁中運行命令kubectl get pods -n kubelab -w,在另一個標簽頁中運行命令kubectl get endpoints -n kubelab backend-service -w,觀察實驗結(jié)果。

# “Pods”標簽頁:狀態(tài)為“Running”,重啟次數(shù)為0——幾乎沒有任何變化
# “Endpoints”標簽頁:會有一個IP地址消失——說明該Pod雖然還在運行,但并沒有接收任何請求

發(fā)生的情況: `/ready` 請求返回了 503 錯誤代碼。Kubelet 將該 Pod 的狀態(tài)標記為 `Ready=False`。Endpoints 控制器也從相關的 Service 中去除了該 Pod 的 IP 地址。盡管用于檢測 Pod 是否存活的 `/health` 請求仍然返回 200 狀態(tài)碼,因此系統(tǒng)并未嘗試重啟該 Pod。120 秒后,`/ready` 請求終于能夠正常響應,該 Pod 也重新加入了服務集群。運行命令 `kubectl logs -n kubelab -f` 可以查看相關日志,會發(fā)現(xiàn)在該 Pod 處于“運行中”狀態(tài)但無法接收任何請求時,與 readiness 相關的日志確實會顯示 503 錯誤代碼。

生產(chǎn)環(huán)境中的常見問題: 當應用程序依賴的外部服務出現(xiàn)故障時,那些用于檢測 Pod 是否準備就緒的探針會立即導致所有相關 Pod 都無法參與服務輪詢。這樣一來,整個應用程序就會徹底停止運行,而無法實現(xiàn)平滑降級。

解決方法: readiness 探針應該僅用于檢測 Pod 本身能夠控制的部分功能。對于那些依賴外部服務的檢查,應該使用專門的、深入的診斷工具來進行;絕不能將 Pod 的準備就緒狀態(tài)與外部服務的可用性直接關聯(lián)起來。

4. 如何在 Grafana 中查看相關數(shù)據(jù)

Grafana 儀表盤的截圖

`kubectl` 命令可以顯示當前系統(tǒng)狀態(tài),而 Grafana 則能展示這些狀態(tài)隨時間的變化過程。當你在調(diào)試四小時前發(fā)生的故障時,這樣的歷史數(shù)據(jù)就顯得尤為重要。

四個關鍵監(jiān)控指標

Pod 重啟情況: 如果線條呈平穩(wěn)狀態(tài),說明一切正常;但如果每隔幾小時線條就會突然上升,那就意味著系統(tǒng)陷入了無聲的 OOMKill 循環(huán)——這是生產(chǎn)環(huán)境中最常見的隱性故障現(xiàn)象。

CPU 使用情況: 正常運行的 Pod 的 CPU 使用率會有一定的波動,而那些被限制資源使用的 Pod 的 CPU 使用率則會始終保持在極限值附近,呈現(xiàn)平穩(wěn)狀態(tài)。真正值得關注的是這種平穩(wěn)狀態(tài)本身,而不是具體的數(shù)值。

內(nèi)存使用情況: 如果發(fā)現(xiàn)某條曲線呈穩(wěn)步上升趨勢后突然消失,那就說明系統(tǒng)發(fā)生了 OOMKill 故障;而當這條曲線從零開始重新上升時,就意味著 Pod 已經(jīng)被重啟。

HTTP 請求頻率: 在發(fā)生級聯(lián)故障時,你會看到在 5 到 15 秒的時間內(nèi) HTTP 請求錯誤率會突然升高,這個時間窗口會被精確記錄下來。

5. 如何解讀終端輸出信息

在每次模擬測試進行期間及結(jié)束后,終端輸出的日志信息能提供 Grafana 所無法提供的詳細信息。有五個命令特別重要:

使用 `kubectl get pods -n kubelab -w` 命令時,加上 `-w` 選項可以實時查看 Pod 狀態(tài)的變化情況。其中最重要的三個字段是 `READY`、`STATUS` 和 `RESTARTS`:`READY` 字段顯示了處于準備就緒狀態(tài)的容器數(shù)量占所有容器的比例——例如 `1/2` 表示只有一個容器處于正常運行狀態(tài),但未能通過 readiness 探針檢測;`STATUS` 字段表示 Pod 當前的生命周期階段,可能是“運行中”、“待啟動”或“正在終止”等;在生產(chǎn)環(huán)境中,`RESTARTS` 字段最為關鍵。如果某個數(shù)值在幾天內(nèi)持續(xù)緩慢上升,那就很可能說明存在內(nèi)存泄漏問題,或者系統(tǒng)陷入了無限循環(huán)故障中。

命令 `kubectl get events -n kubelab –sort-by=.lastTimestamp` 可以幫助你了解控制平面的活動記錄。集群執(zhí)行的每一項操作都會被記錄在這里,包括“終止容器”、“成功創(chuàng)建新 Pod”、“安排任務執(zhí)行”等。當系統(tǒng)出現(xiàn)故障而你不知道原因時,仔細閱讀這些事件日志吧。從某個“終止容器”操作到下一個“開始運行”操作之間的時間間隔,就是實際的故障持續(xù)時間——這是一個精確的數(shù)值,而不是估算值。

kubectl describe pod -n kubelab 這一命令能夠提供關于單個Pod的最詳細信息。其中有三個部分尤為重要:Conditions(Ready: True/False可以說明該Pod是否已被納入服務端點的范圍)、Last State(會顯示上一個容器退出的原因,可能是OOMKill、退出代碼為137,或者發(fā)生了系統(tǒng)崩潰),以及底部顯示的Events(調(diào)度器做出各種放置決策時的依據(jù))。當某個Pod出現(xiàn)異常行為時,這是首先應該運行的命令。

kubectl get endpoints -n kubelab backend-service這一命令可以用來查看目前哪些Pod的IP地址正在接收流量。有時,在kubectl get pods的結(jié)果顯示中,某個Pod被標記為“Running”,但實際上它并不在上述列表中——這通常意味著該Pod的 readiness probe檢測失敗了。如果這個列表為空,那么無論有多少Pod被標記為“Running”,向該服務發(fā)送請求都會失敗。每當用戶報告錯誤,但相關Pod看起來卻很正常時,都應該檢查這一列表。

kubectl logs -n kubelab 這一命令可以顯示容器的標準輸出和標準錯誤信息。使用-f選項可以實時查看日志流。當一個Pod重新啟動后,可以使用--previous選項來查看剛剛退出的那個容器留下的日志——這對于了解在容器發(fā)生OOMKill或系統(tǒng)崩潰之前它在執(zhí)行什么操作來說非常有用。需要注意的是,這些日志是針對每個容器單獨記錄的,而且一旦Pod被替換掉,這些日志就會消失,因此最好在ReplicaSet創(chuàng)建新的、名稱不同的Pod之前及時保存這些日志。

Kill Pod恢復過程中的完整事件序列如下:

kubectl get events -n kubelab --sort-by=.lastTimestamp | tail -10

REASON MESSAGE
Killing Stopping container backend ← 發(fā)送了SIGTERM信號
SuccessfulCreate Created pod backend-xyz789 ← ReplicaSet開始創(chuàng)建新的Pod
Scheduled Successfully assigned to worker-2 ← 調(diào)度器將新Pod分配到了worker-2節(jié)點上
Pulled Container image already present ← 沒有出現(xiàn)拉取延遲
Started Started container backend ← 新Pod已經(jīng)啟動并開始運行

“Killing”和“Started”這兩條記錄之間的時間間隔,其實就是Pod恢復所花費的實際時間。在一個使用緩存鏡像的正常集群中,這個時間通常在3到8秒之間。如果恢復時間過長,就需要檢查“Scheduled”這條記錄——可能是調(diào)度器在尋找適合的節(jié)點時遇到了問題。

值得記住的兩條Prometheus查詢語句

第一條查詢語句:用于檢測容器是否頻繁重啟。rate(kube_pod_container_status_restarts_total{namespace="kubelab"}[1h])這一命令會統(tǒng)計過去一小時內(nèi),該命名空間中的容器重新啟動的次數(shù),并以每秒重新啟動次數(shù)的形式顯示結(jié)果。在一個正常的系統(tǒng)中,容器的重啟頻率應該很低。如果這個數(shù)值很高(例如每小時超過3次重啟),那就說明肯定有某種原因?qū)е氯萜鞑粩嘀貑ⅲ畛R姷脑蚩赡苁荗OMKill或系統(tǒng)崩潰。因此,當這個數(shù)值超過某個閾值時,就應該立即發(fā)出警報,這樣用戶報告問題之前,你就能及時發(fā)現(xiàn)這種異常情況。
第二條查詢語句:用于檢測CPU是否被過度限制。rate(container_cpu_cfs_throttled_seconds_total{namespace="kubelab"}[5m])這一命令會統(tǒng)計過去5分鐘內(nèi),Linux調(diào)度器在該命名空間中限制容器運行的時間占總時間的比例。如果這個數(shù)值為0.25,那就意味著容器有25%的時間處于被限制運行的狀態(tài)。如果系統(tǒng)出現(xiàn)高延遲現(xiàn)象,但容器并沒有重新啟動,同時kubectl top顯示的CPU使用率也“正常”,那么很可能是因為CPU的限制值設置得太低了,導致內(nèi)核不得不對進程進行限制。因此,當這個數(shù)值超過0.25時,也應該立即發(fā)出警報。

# 當每小時重啟次數(shù)超過3次時發(fā)出警報
rate(kube_pod_container_status_restarts_total{namespace="kubelab"}[1h])

# 當資源限制被觸發(fā)且持續(xù)時間超過25%時發(fā)出警報
rate(container_cpu_cfs_throttled_seconds_total{namespace="kubelab"}[5m])

請在您自己的集群上運行這些查詢命令,而不僅僅是KubeLab環(huán)境。這些都是用于生產(chǎn)環(huán)境的檢測腳本。

6. 如何在生產(chǎn)環(huán)境中利用這些工具進行故障排查

該倉庫中包含了文件docs/diagnose.md,其中列出了各種問題與相應模擬測試之間的對應關系。請找到能夠重現(xiàn)您遇到的問題的模擬測試,在KubeLab環(huán)境中運行它,先了解其工作原理,然后再將其應用到生產(chǎn)環(huán)境中。

如果程序的退出代碼為137,且Pod正在重啟,請運行內(nèi)存壓力測試模擬。可以通過命令kubectl describe pod | grep -A 5 "Last State:"進行驗證,如果出現(xiàn)“Reason: OOMKilled”這一提示,說明CPU資源已被耗盡,需要調(diào)整資源限制或查找資源泄漏問題。該模擬測試能夠同時顯示這兩種情況。

container_cpu_cfs_throttled_seconds_total這個指標的值。如果這個數(shù)值持續(xù)上升,說明您的CPU資源限制設置得太低了,導致Pod被CFS機制限制住了。

kubectl get endpoints進行檢查,會發(fā)現(xiàn)某個Pod的IP地址雖然顯示為“Running”,但實際上并沒有接收任何請求。

kubectl describe pod 并查看相關事件信息,調(diào)度器會說明為什么無法將Pod放置到目標節(jié)點上,原因通常是由于目標節(jié)點的資源不足,或者該節(jié)點上沒有可用的PVC資源。

總結(jié)

您剛剛用七種不同的方式破壞了一個真實的Kubernetes集群,然后觀看了它每次如何自動恢復正常狀態(tài)。您看到了ReplicaSet控制器的工作過程,通過kubectl describe命令了解了OOMKill機制,也觀察到了在發(fā)生級聯(lián)故障時端點信息會如何發(fā)生變化,同時還明白了為什么一個Pod可以處于“Running”狀態(tài)卻沒有任何請求流量。

在這里練習的方法同樣適用于其他類型的集群,無論是測試環(huán)境還是生產(chǎn)環(huán)境。這些知識雖然可以閱讀學習,但并不適合隨意在真實環(huán)境中進行試驗。當凌晨3點出現(xiàn)問題時,正是這些經(jīng)驗(比如事件日志、端點信息、重啟次數(shù)統(tǒng)計等)會派上用場。KubeLab為您提供了安全的環(huán)境來培養(yǎng)這種應對問題的能力。

該倉庫中包含的內(nèi)容遠不止本文介紹的這些。探索模式允許您在沒有引導流程的情況下運行各種模擬測試。在文件docs/interview-prep.md中,提供了針對13個最常見的Kubernetes面試問題的解答。而docs/observability.md則詳細介紹了如何配置Prometheus和Grafana。

如果這些內(nèi)容對您有幫助,請在https://github.com/Osomudeya/kube-lab地址將這個倉庫添加為星標,并與那些正在艱難學習Kubernetes的人分享這些資源。

Comments are closed.