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