解讀Kubernetes常見退出碼
一、退出碼歷史
退出碼的歷史可以追溯到Unix操作系統的早期。在Unix系統中,進程退出碼是進程終止時向其父進程傳遞的一個整數值,用于表示進程的終止狀態。這個整數值通常在0到255之間,其中0表示進程成功終止,其他值通常用來表示不同的錯誤或異常情況。
進程退出碼最初被設計用于提供一種簡單的機制,使父進程能夠了解子進程的執行結果。這使得父進程能夠根據子進程的退出碼來采取適當的行動,比如處理錯誤情況或繼續執行其他操作。
在Unix系統中,特定的退出碼值通常具有特定的含義,例如:
- 0:表示成功執行,沒有錯誤。
- 1:通常表示通用的錯誤。
- 2:表示命令的語法錯誤。
- 127:表示命令未找到。
隨著時間的推移,Unix操作系統的發展和不同的實現,進程退出碼的含義可能有所不同,但基本的概念保持不變。
在Linux系統中,進程退出碼的使用與Unix系統類似。Linux繼承了Unix的進程管理機制,并在其基礎上進行了擴展和改進。因此,Linux中的進程退出碼仍然是一個重要的概念,用于幫助理解和診斷進程的執行狀態。
進程退出碼的歷史可以追溯到早期的Unix系統,是Unix和Linux操作系統中的一個重要概念,為進程間通信提供了一種簡單而有效的機制。當應用程序或命令因致命錯誤而終止或執行失敗時,將產生 128 系列退出碼(128+n),其中 n 為信號編號。n 包括所有類型的終止代碼,如 SIGTERM、SIGKILL 等。
二、退出碼 127
退出碼 127 不是特定于 Kubernetes 的錯誤代碼,而是 Linux 和類 Unix 操作系統中使用的標準退出碼。當然,我們在Kubernetes中經常看到它,并且通常表示容器內執行的命令或二進制文件找不到。
一些標準的退出碼包括:
圖片
常見原因
讓我們看一下退出碼 127 的一些常見原因:
- 命令或二進制文件未安裝Kubernetes 容器的 command 字段中指定的可執行文件未安裝在容器的文件系統中。需要確保所需的二進制文件或命令可用。
- 路徑或命令不正確Pod 定義中指定的命令不正確或在指定的路徑中不存在。這是錯誤的最常見原因之一,通常是由于 Dockerfile 或 pod spec中的entrypoint或command輸入不正確造成的。
- 缺少依賴在容器內運行的應用程序或腳本未安裝相關依賴。需要確保所有必需的依賴項包含在容器映像中。
- shell 解釋器如果指定了腳本作為命令,需要確保腳本有效 (例如#!/bin/bash),且在容器中可用。
- shell 腳本語法錯誤如果 shell 腳本退出碼是127,請檢查腳本是否存有語法錯誤或可能阻止其執行的問題。
- 權限不足在容器內運行命令的用戶可能沒有執行指定命令所需的必要權限。確保容器以適當的特權運行。
- 鏡像兼容性問題確保使用的容器鏡像與宿主機架構和操作系統兼容。不匹配的映像可能導致命令找不到,比如x86的鏡像運行在arm的機器上
- 卷掛載如果命令是卷掛載的文件,請檢查卷掛載是否配置正確,且所需的文件可以被訪問到。
- 環境變量一些命令可能依賴于特定的環境變量。確保必需的環境變量設置正確。
- Kubernetes RBAC 策略 如果啟用了RBAC,需要確保具有執行指定命令所需的權限。
如何排查
要排除問題,可以使用以下命令檢查 Pod 的日志:
kubectl logs -f <pod-name>
還可以檢查 Pod 狀態,該狀態提供有關 Pod 的詳細信息,包括其當前狀態、最近事件和任何錯誤消息。
kubectl describe pod <pod-name>
還可以為把調試容器attach到Pod 中,該容器包括一個 shell(例如 BusyBox)。這允許您進入容器并手動檢查環境、路徑和命令的可用性。
使用 BusyBox 進行調試的示例:
containers:
- name: my-container
image: my-image:latest
command: ["/bin/sleep", "infinity"]
- name: debug-container
image: busybox:latest
command: ["/bin/sh"]
tty: true
stdin: true
如果是高版本K8s,也可以使用Ephemeral Containers,它就是一個臨時容器。這是一個自Kubernetes v1.16中作為alpha引入的新功能,啟用臨時容器的特性也非常簡單,在kubernetes v1.16之后的版本中將啟動參數--feature-gates=EphemeralCnotallow=true配置到kube-api和kubelet服務上重啟即可。
通過仔細查看日志并排查上述幾個方向,應該能夠確定退出碼 127 問題的原因。
如何修復
我們知道了退出碼 127 的常見原因以及排查方式,現在讓我們看看如何修復它們。
- 命令或二進制文件未安裝
如果所需的命令或二進制文件丟失,則可能需要在容器鏡像中安裝。修改 Dockerfile 或構建過程安裝所需軟件。
示例:
FROM alpine:latest
RUN apk --no-cache add <package-name>
- 路徑或命令不正確
在 Pod 定義中指定命令時,考慮使用二進制文件的絕對路徑。這有助于確保不受當前工作目錄的影響, runtime可以找到二進制文件。
示例:
containers:
- name: my-container
image: my-image:latest
command: ["/usr/local/bin/my-command"]
- 缺少依賴項
導致命令無法運行的原因可能是容器鏡像需要安裝額外的軟件。如果命令需要額外的設置或安裝步驟,可以使用init容器在主容器啟動之前執行這些任務。
示例(使用init容器安裝軟件包):
initContainers:
- name: install-package
image: alpine:latest
command: ["apk", "--no-cache", "add", "<package-name>"]
volumeMounts:
- name: shared-data
mountPath: /data
- shell解釋器
如果指定了腳本作為命令,需要確保腳本有效 (例如#!/bin/bash),且在容器中可用。
示例:
#!/bin/bash
- 卷掛載
檢查Pod的配置,確保卷已正確掛載。驗證卷名稱、掛載路徑和 subPaths是否正確。
示例:
volumes:
- name: my-volume
emptyDir: {}
containers:
- name: my-container
image: my-image:latest
volumeMounts:
- name: my-volume
mountPath: /path/in/container
同時我們需要確認Pod 定義指定的卷存在且可用。如果是持久卷(PV),需要檢查其狀態。如果是 emptyDir 或其他類型的卷,需要驗證其是否正確創建和掛載。如果在卷掛載中使用了 subPaths,需要確保源目錄或文件中存在指定的 subPaths。
示例:
volumeMounts:
- name: my-volume
mountPath: /path/in/container
subPath: my-file.txt
三、退出碼 137
在Kubernetes中,137退出碼表示進程被強制終止。在Unix和Linux系統中,當進程由于信號而終止時,退出碼由信號編號加上128確定。信號編號為9,意味著“SIGKILL”,因此將9加上128,得到137退出碼。
當Kubernetes集群中容器超出其內存限制時,它可能會被Kubernetes系統終止,并顯示“OOMKilled”錯誤,這表示進程因內存不足而被終止。此錯誤的退出碼為137OOM代表“內存耗盡(out-of-memory)”。
如果Pod狀態將顯示為“OOMKilled”,你可以使用以下命令查看:
kubectl describe pod <podname>
OOMKiller
OOMKiller是Linux內核中的一種機制,它負責通過終止消耗過多內存的進程來防止系統耗盡內存。當系統內存耗盡時,內核會調用OOMKiller來選擇一個要終止的進程,以釋放內存并保持系統運行。
內核中有兩種不同的OOM Killer;一種是全局的OOM Killer,另一種是基于cgroup內存控制器的OOM Killer,可以是cgroup v1或cgroup v2。
簡單來說是,當內核在分配物理內存頁面時遇到問題時,全局的OOM Killer 會觸發。當內核嘗試分配內存頁面(無論是用于內核使用還是用于需要頁面的進程),并且最初失敗時,它將嘗試各種方式來回收和整理內存。如果這種嘗試成功或者至少取得了一些進展,內核將繼續重試分配;如果無法釋放頁面或者取得進展,在許多情況下它將觸發OOM Killer。
一旦OOMKiller選擇要終止的進程,它會向該進程發送信號,要求其優雅地終止。如果進程不響應信號,則內核會強制終止該進程并釋放其內存。
注意:由于內存問題而被終止的Pod不一定會被節點驅逐,如果其設置的重啟策略設置為“Always”,它將嘗試重新啟動Pod。
在系統層面,Linux內核為運行在主機上的每個進程維護一個oom_score。進程被終止的機率取決于分數有多高。
oom_score_adj值允許用戶自定義OOM進程,并定義何時應終止進程。Kubernetes在定義Pod的Quality of Service(QoS)時使用oom_score_adj值。
K8s針對Pod定義了三種QoS,每個類型具有對應的oom_score_adj值:
- Guaranteed: -997
- BestEffort: 1000
- Burstable: min(max(2, 1000 — (1000 * memoryRequestBytes) / machineMemoryCapacityBytes), 999)
其中Pod為Guaranteed QoS,則其oom_score_adj的值是-997,因此它們在節點內存不足時最后一個被終止。BestEffort Pod配置的是1000,所以它們第一個被被終止。
要查看Pod的QoS,可以通過下述命令:
kubectl get pod -o jsnotallow='{.status.qosClass}'
下面是定義PodGuaranteed QoS 類型的計算策略:
- Pod 中的每個容器必須有內存 limit 和內存 request。
- 對于 Pod 中的每個容器,內存 limit 必須等于內存 request。
- Pod 中的每個容器必須有 CPU limit 和 CPU request。
- 對于 Pod 中的每個容器,CPU limit 必須等于 CPU request。
退出碼137通常有兩種情況:
1. 最常見的原因是與資源限制相關。通常情況下,Kubernetes超出了容器的分配內存限制。
2. 另一種情況是手動干預 - 用戶或腳本可能會向容器進程發送“SIGKILL”信號,導致此退出碼。
如何排查
- 檢查Pod日志
診斷OOMKilled錯誤的第一步是檢查Pod日志,查看是否有任何內存相關的錯誤消息。
kubectl describe pod <podname>
State: Running
Started: Fri, 12 May 2023 11:14:13 +0200
Last State: Terminated
Reason: OOMKilled
Exit Code: 137
...
您還可以查詢Pod日志:
cat /var/log/pods/<podname>
當然也可以通過(標準輸出)
kubectl logs -f <podname>
- 監視內存使用情況
使用監視系統(如Prometheus或Grafana)監視Pod和容器中的內存使用情況。這可以幫助我們排查出哪些容器消耗了過多的內存從而觸發了OOMKilled錯誤,同時也可以在容器宿主機使用dmesg查看當時oomkiller的現場
- 使用內存分析器
使用內存分析器(如pprof)來識別可能導致過多內存使用的內存泄漏或低效代碼。
如何修復
以下是OOMKilled Kubernetes錯誤的常見原因及其解決方法。
- 容器內存限制已達到
這可能是由于在容器指定的內存限制值設置不當導致的。解決方法是增加內存限制的值,或者調查導致負載增加的根本原因并進行糾正。導致這種情況的常見原因包括大文件上傳,因為上傳大文件可能會消耗大量內存資源,特別是當多個容器在一個Pod內運行時,以及突然增加的流量量。
- 因為應用程序內存泄漏,容器內存使用達到上限
需要調試應用程序來定位內存泄漏的原因,
- 所有Pod使用的總內存大于節點可用內存
通過增加節點可用內存來增加節點內存,或者將Pod遷移到內存更多的節點。當然也可以調整運行在節點上的Pod的內存限制,使其符合內存限制,注意你還應該注意內存請求設置,它指定了Pod應該使用的最小內存量。如果設置得太高,可能不是有效利用可用內存,關于資源配置相關的建議,可以參看VPA組件
在調整內存請求和限制時,當節點過載時,Kubernetes按照以下優先級順序終止Pod:
- 沒有請求或限制的Pod。
- 具有請求但沒有限制的Pod。
- 使用超過其內存請求值的內存 - 指定的最小內存值 - 但低于其內存限制的Pod。
- 使用超過其內存限制的Pod。
如何預防
有幾種方法可以防止OOMKilled的發生:
- 設置適當的內存限制
通過壓測及監控來確定應用程序的內存使用,通過上述方式配置容器允許使用的最大內存量。過度保守可能會導致因資源利用率低效而造成資金的浪費,同時低估會導致頻繁出現OOMKilled現象。
- HPA
最佳做法是利用K8s提供的HPA機制,當應用程序的內存使用升高時自動增加Pod副本數量。
- 節點資源分配
確保節點具有足夠的資源來處理業務。
- 優化應用程序內存使用
監視應用程序并進行適當優化,以減少內存消耗。
- 避免應用程序中的內存泄漏
從應用程序來看,需要長期檢查并修復內存泄漏。
由于筆者時間、視野、認知有限,本文難免出現錯誤、疏漏等問題,期待各位讀者朋友、業界專家指正交流。
參考文獻
1. https://spacelift.io/blog/oomkilled-exit-code-137
2. https://spacelift.io/blog/exit-code-127
3. https://cloud.tencent.com/developer/news/1152344
4. https://utcc.utoronto.ca/~cks/space/blog/linux/OOMKillerWhen
本文轉載自微信公眾號「 DCOS」,作者「DCOS」,可以通過以下二維碼關注。
轉載本文請聯系「DCOS」公眾號。