題目與適用情境
一台 64 GiB Linux 節點仍有 18 GiB MemAvailable,但某容器在 6 小時內反覆結束。執行環境記錄為 OOMKilled 與 exit code 137。該工作負載所在 cgroup v2 的 memory.max 為 4 GiB,故障前 memory.current 接近上限,memory.events 中 oom_kill 計數增加。同一期間 memory.stat 的 anon 從 1.2 GiB 增至 3.5 GiB,file 約為 280 MiB。請說明如何證明觸發原因、區分 cgroup OOM 與全域 OOM、定位記憶體增長來源,並安全止損與防止復發。
64 GiB、18 GiB、4 GiB、6 小時與各項用量都是面試案例假設,不代表容量基準。題目假設 Linux 使用 cgroup v2,容器狀態與 cgroup 檔案來自同一次故障時窗。本題歸入 general,因為核心能力是作業系統記憶體 回收、cgroup 記帳、OOM 證據與行程處置;容器平台只是呈現情境。
2026 年公開的 Linux 與作業系統面試資料都直接收錄 OOM 情境,要求候選人從核心日誌、資源限制與行程行為 解釋故障。完整回答不能停在「加記憶體」或「發現 137」,而要建立證據鏈、保護服務、找出增長來源,並以 可重現測試證明修正有效。
面試官考察重點
第一個重點是能否正確解釋證據。依 Bash 的結束狀態慣例,137 可由 128 加訊號 9 得到,只能說明行程因 SIGKILL 結束;管理員、逾時控制器與 OOM killer 都可能發出此訊號。執行環境的 OOMKilled、同一時窗 增加的 memory.events:oom_kill 與核心日誌,才能把原因收斂到 OOM。
第二個重點是能否區分資源範圍。memory.max 是 cgroup 的硬上限,達到上限且回收無法降低用量時,核心可在 該 cgroup 內觸發 OOM。此時節點仍有 18 GiB 可用並不矛盾。全域 OOM 則由節點可配置記憶體耗盡等整體壓力 觸發,候選行程集合與復原動作都不同。
第三個重點是記憶體記帳能力。只看單一行程的 RSS 會漏掉同 cgroup 的子行程、檔案快取、tmpfs、共享記憶體、 socket buffer 與核心結構。好的回答會用 memory.current、memory.peak、memory.stat、行程級指標與 應用程式剖析互相驗證,不把 VSZ、RSS 與 cgroup charge 混為一談。
最後看故障治理。暫時提高上限可能恢復服務,也可能把持續洩漏轉成節點級事故。候選人需要說明何時限流、 重新啟動、擴容或提高上限,如何區分正常尖峰與洩漏,以及如何用壓力、長時間執行與故障注入驗證容量和復原策略。
回答前需要釐清的問題
- 故障記錄是否來自同一容器實例與同一時窗? 舊的
OOMKilled狀態、目前 cgroup 計數與另一次結束不能
拼成因果鏈,要對齊容器 ID、啟動時間、結束時間與 cgroup 路徑。
- 執行環境確實是 cgroup v2 嗎? v1、v2 的檔名與語意不同;也要確認讀到的是容器自身 cgroup,還是
包含多個子工作負載的父 cgroup。
oomkill與oom哪個計數改變?oom表示達到限制且配置即將失敗,oomkill才記錄有行程
遭 OOM killer 終止;階層計數也要與 memory.events.local 交叉檢查。
- 節點同時出現全域記憶體壓力嗎? 應查看核心日誌、節點
MemAvailable、swap、PSI、驅逐事件與其他
工作負載。cgroup OOM 與節點壓力可能在相近時間發生。
- 4 GiB 是容器、Pod 還是父 cgroup 的上限? 父層上限會讓子層尚未達到自己的上限時仍受約束;應沿
cgroup 階層讀取有效限制與事件。
- 記憶體增長與什麼負載相關? 請求併發、訊息積壓、批次大小、快取鍵數、連線數、輸入大小與版本發布,
可協助區分會隨負載下降的工作集與隨時間單調增長的保留記憶體。
- 同一 cgroup 有多少行程? sidecar、worker 與衍生行程都參與總記帳。只剖析主行程可能找不到真正的
增長者。
- 服務的復原目標與資料安全要求是什麼? 多行程工作負載只終止一個行程可能留下不一致狀態;重啟、降載、
流量切換與整組終止要依冪等性及復原時間決定。
30 秒回答框架
「我先把 137 當作 SIGKILL 線索,而非 OOM 結論。對齊容器 ID 與故障時間,檢查執行環境的 OOMKilled、 目標 cgroup 的 memory.events.local、memory.current、memory.max 與核心日誌。這裡節點仍有可用記憶體, 而 cgroup 的 oom_kill 增加且用量觸頂,證據優先指向 cgroup OOM。先限流或切流並保留故障資料,必要時在 確認節點餘量後暫時提高上限。接著用 memory.stat 拆開匿名記憶體、檔案、共享記憶體與核心記憶體,再依行程 和業務負載做時間序列與剖析,區分洩漏、無界快取、併發尖峰與限額過小。修正後用代表性尖峰、長時間 soak test 和受控限額測試驗證,同時對 memory.high、壓力、峰值與 OOM 事件告警。」
分步深入解答
第一步:把結束結果整理成同一時間線。
記錄容器 ID、行程 PID、啟動與結束時間、部署版本、重啟次數和 cgroup 路徑。137 在常見 shell/容器結束狀態 中對應 SIGKILL,但不能單獨證明 OOM:人工 kill -9、平台逾時或節點代理也會得到相同狀態。需要把執行環境 reason: OOMKilled、故障前後的 cgroup 事件差值和同一秒附近的核心日誌關聯起來。
優先讀取 memory.events.local,避免父 cgroup 的階層計數把後代事件混入。若 oom_kill 在本次結束時從 7 變成 8,memory.current 接近 4 GiB,且核心日誌指出 memory cgroup out of memory,便構成完整的 cgroup OOM 證據鏈。若只有 137,而 oom_kill 不變、執行環境也沒有 OOM 原因,就繼續調查人工訊號、健康 檢查逾時、systemd-oomd、驅逐或執行環境終止。
第二步:確定 OOM 的資源範圍。
memory.max 限制該 cgroup 及其後代的已記帳記憶體。用量觸頂、直接回收仍無法滿足配置時,核心可只在該 cgroup 的候選行程中選擇犧牲者,因此主機還有 18 GiB MemAvailable 並不能保護這個容器。應沿父 cgroup 往上檢查每層 memory.max 與事件,確認實際命中的邊界。
全域 OOM 的證據不同:節點可用記憶體與 swap 接近耗盡、記憶體 PSI 顯著升高、核心日誌包含整體記憶體狀態 與被選行程,且其他工作負載也受影響。節點壓力下也可能先發生平台驅逐。復原時,cgroup OOM 著重工作負載 用量與限額;全域 OOM 還要處理節點超賣、request/預留、系統常駐程式與工作負載分布。
第三步:解釋「4 GiB 用在哪裡」。
先看 memory.current 與 memory.peak,再用 memory.stat 分解占用。本題中 anon 在 6 小時從 1.2 GiB 增至 3.5 GiB,而 file 約 280 MiB,初步把調查重點放在 heap、匿名映射及相關行程,但仍不能直接宣告洩漏。 核心文件說明 cgroup 還會統計檔案快取、tmpfs/共享記憶體、核心結構與 socket buffer;父層也包含後代。
把 cgroup 總量與每個行程的 RSS、PSS、匿名映射、執行緒/行程數、語言執行環境 heap 指標及業務指標放在 同一時間軸。若應用程式 heap 持續增長且存活物件增加,調查引用洩漏或無界快取;若 heap 穩定而 RSS 不回落, 調查配置器碎片、本機程式庫或 mmap;若 file、shmem、sock 或 slab 增長,則轉向 tmpfs 檔案、 快取、連線積壓或核心物件。
VSZ 只是虛擬位址空間,不等於實際駐留或 cgroup charge。單點快照也不夠:正常服務可能在批次處理時到峰值並 在回收後下降,洩漏通常在相似負載下跨多個週期持續上升。應比較相同吞吐下的基線、增長斜率與釋放後穩態。
第四步:把假設轉成可否證檢查。
列出少量候選並為每個候選寫預測:若是併發尖峰,記憶體應隨進行中請求數變化並在請求完成後下降;若是無界 快取,快取項目與 anon 會一起增長,設定容量後趨穩;若是消費積壓,佇列深度、批次物件與記憶體同步變化; 若是限額過小,代表性穩定負載的工作集總是接近上限,但長時間執行沒有持續增長。
使用適合執行環境的 heap profile、allocation profile 或物件直方圖,但要評估採集成本。線上先取低風險 指標或切流後的副本;需要 dump 時確保磁碟、隱私與暫停時間可接受。結合版本比較與流量重播,一次只改一個 因素,避免同時提高上限、關閉快取與降低併發後無法確定真正原因。
第五步:止損與修正要分開。
先保護使用者與節點:限制進入該實例的併發、暫停高記憶體批次、切走流量,或重啟至已知安全版本。若服務可 安全重播,多行程狀態不會損壞,重啟可快速釋放記憶體;若只終止一個 worker 會留下共享狀態,應考慮讓整個 工作負載一致結束。cgroup v2 的 memory.oom.group=1 可讓 OOM killer 把工作負載視為不可分割單元,但須先 驗證復原語意。
只有在量得節點餘量、其他工作負載保護與預計峰值後,才暫時提高 memory.max。它應有到期時間、監控與回復 門檻。持續洩漏時,單純增加限額只是延後下一次 OOM。長期修正可能是有界快取、釋放引用、限制批次和併發、 修正子行程生命週期,或依量得的工作集與突發量重新設定 request/limit。
不要把一般業務行程設為 oomscoreadj=-1000 來「解決」問題。該值會讓任務免於 OOM 選擇,可能迫使核心 終止更重要或更多行程;它只適合經整體故障策略評估的必要系統任務。swap 也只是改變回收與延遲特性,可能 緩解短尖峰,卻無法修正無界增長。
第六步:驗證容量並建立早期訊號。
在與正式環境相同的 cgroup 記帳與限制下重播代表性流量,涵蓋正常、尖峰、大輸入、積壓復原與多行程情境。 短壓測驗證峰值,長時間 soak test 驗證增長斜率與釋放後穩態,受控降低限額來驗證告警、終止、重啟及資料 一致性。成功標準包含:在目標吞吐與延遲下有明確餘量;memory.current 可回落;oom_kill 不增加;節點 壓力與其他工作負載不惡化。
可把 memory.high 設為早於硬上限的治理邊界:超過它會節流並觸發直接回收,但不會直接觸發 OOM,便於在 硬失敗前告警和採取行動。監控 memory.current/memory.max 比例、memory.peak、memory.events 的 high、max、oom、oom_kill、記憶體 PSI、重啟率與應用程式 heap。限額應來自峰值與持續執行測量, 並隨版本、併發和輸入分布變化複查。
高品質示範回答
「我會先確認這次結束是否真的由 OOM 導致。137 表示行程收到 SIGKILL,人工終止與逾時也可能產生它,所以 我會對齊容器 ID、結束時間與 cgroup 路徑,查看執行環境 OOMKilled、memory.events.local 的差值及核心 日誌。如果本次結束時 oom_kill 增加、memory.current 觸及 4 GiB,日誌也指向 memory cgroup,證據便 支持 cgroup OOM。
這能解釋為何節點還有 18 GiB 可用。memory.max 是該 cgroup 的硬上限,回收無法降低用量時,核心會在這個 資源範圍內選擇行程,不要求整台機器先耗盡。我仍會檢查父 cgroup、節點 PSI、swap 與全域 OOM 日誌,排除 同時發生的節點壓力或平台驅逐。
止損方面,我會先限流或切走高記憶體流量,保留故障前指標與低成本 profile。若重啟安全,就恢復至已知版本; 只有確認節點餘量與其他工作負載保護後,才暫時提高限額,並設定到期及回復條件。不能把增加記憶體當長期修正。
定位時,我先用 memory.current、memory.peak 與 memory.stat 解釋 cgroup 總量,再依行程看 RSS/PSS、 匿名映射及執行環境 heap。本題 anon 從 1.2 GiB 增到 3.5 GiB,檔案記憶體約 280 MiB,因此優先調查 heap、 匿名 mmap、子行程與配置器,但會用 profile 證明。把記憶體與併發、佇列深度、快取項目、批次大小及版本放在 同一時間軸:相似負載下持續增長較像洩漏,隨負載下降並穩定則較像正常工作集或限額過小。
修正後,我會在同樣的 cgroup 限制下做尖峰壓測與長時間 soak test,驗證用量會回落、事件計數不再增加、延遲 與吞吐達標。最後在硬上限前設定 memory.high 與告警,監控峰值、壓力、OOM 事件及增長斜率,並依量得工作集 調整併發、快取與容量。多行程服務也要測試部分行程遭終止後的資料一致性,再決定是否啟用整組 OOM 終止。」
常見錯誤
- 看到 137 就斷言 OOM →
SIGKILL可能來自人工或逾時控制器 → 結合執行環境原因、cgroup 事件與核心日誌。 - 主機有可用記憶體就排除 OOM → cgroup 可在節點仍有餘量時觸及硬上限 → 先確定實際資源範圍。
- 只看單一行程 RSS → cgroup 還可能包含子行程、檔案、共享記憶體和核心記帳 → 核對總量並拆
memory.stat。 - 把 VSZ 當實體占用 → 位址空間大小不等於駐留或已記帳記憶體 → 使用 RSS/PSS、映射與 cgroup 指標驗證。
- 一看到
anon增長就宣告洩漏 → 正常工作集和批次尖峰也會增長 → 比較相似負載下的斜率與釋放後穩態。 - 直接把限額加倍 → 可能延後洩漏並侵占節點安全餘量 → 限額調整要有容量證據、期限與回復門檻。
- 把應用設成 OOM 不可終止 → 會把傷害轉移給其他任務或整台節點 → 只依整體故障策略調整
oomscoreadj。 - 只做一分鐘壓力測試 → 短測無法暴露緩慢洩漏和碎片 → 同時做尖峰測試與長時間 soak test。
- 只在硬上限告警 → 到
memory.max才回應通常太遲 → 利用memory.high、PSI 與增長斜率提前處置。 - 終止一個 worker 後就認為恢復 → 多行程共享狀態可能已不一致 → 驗證整組終止、重啟與資料復原語意。
追問與應對
追問一:exit code 137,但 oom_kill 沒有增加,下一步查什麼?
先確認 cgroup 路徑與事件時間是否相符,並檢查 memory.events.local,避免讀錯父層或新實例。若仍無 OOM 證據, 查看執行環境終止原因、部署或健康檢查逾時、人工操作稽核、systemd-oomd、節點驅逐與核心日誌。137 只把原因 限定為 SIGKILL,不能取代發送者證據。
追問二:如何區分記憶體洩漏與限額過小?
在相似吞吐、輸入與併發下比較多個週期。洩漏通常表現為基線或存活物件持續上升,低負載後也不回落;限額過小 較可能在可重現尖峰觸頂,但完成後回到穩定工作集。結合 heap/allocation profile、快取項目、子行程、批次與 memory.stat 分項驗證,不依賴單次曲線。兩者也可能同時存在。
追問三:什麼時候可以提高 memory.max?
當代表性尖峰與長測證明工作負載正常、所需工作集確實高於目前限額,且節點容量、預留和鄰居保護仍有餘量時, 可以調整。事故中的暫時提高要設定期限、監控及回復門檻;若用量持續無界增長,提高上限只能作為短期止損, 必須同時限流並修正增長源。
追問四:memory.high 與 memory.max 應如何配合?
memory.high 是節流與直接回收邊界,越過它不會直接呼叫 OOM killer,適合保留觀察與自動處置時窗; memory.max 是最終硬隔離邊界,回收失敗時可能觸發 cgroup OOM。兩者應依實測工作集、峰值、延遲容忍與 節點餘量設定,並對 high、max、oom、oom_kill 事件分別告警。
追問五:多行程服務為何可能使用 memory.oom.group?
若一個 worker 遭終止後其餘行程會保留不一致的共享狀態、鎖或未完成交易,把 cgroup 當成不可分割工作負載 可讓失敗與復原更清楚。啟用前要驗證整組重啟時間、任務冪等性與資料復原;設為 oomscoreadj=-1000 的 任務屬於例外,因此也要檢查是否會留下殘缺行程。