一次關於k8s kubectl top 和 contained ps 不一致的問題探究


k8s kubectl top命令和contained內部 ps 看到的進程內存占用不一致。下午的時候,我被這個問題問倒了。具體如圖

kubectltop-vmtop-vm

網上搜索了下,難得看到有認真研判問題的IT文章了。這篇帖子推薦給大家。

 

  • 一、問題背景

  • 二、Buffer & cache原理

  • 三、緩存測試

  • 四、生產環境內存飆升解決方案的建議

 

目錄

時間線:

  • 在上 Kubernetes 的前半年,只是用 Kubernetes,開發沒有權限,業務服務極少,忙着寫新業務,風平浪靜。
  • 在上 Kubernetes 的后半年,業務服務較少,偶爾會階段性被運維喚醒,問之 “為什么你們的服務內存占用這么高,趕緊查”。此時大家還在為新業務沖刺,猜測也許是業務代碼問題,但沒有調整代碼去嘗試解決。
  • 再后面,出過幾次OOM的問題,普遍增加了容器限額 Limits,出現了好幾個業務服務是內存小怪獸,因此如果不限制的話,服務過度占用會導致驅逐,因此反饋語也就變成了:“為什么你們的服務內存占用這么高,老被 OOM Kill,趕緊查”。

一、問題背景

kubernetes 運行的java應用,內存占用持續在增長。思路如下:

kubectl exec -it pod -n xxx /bin/bash

執行 top 命令查看下當前 pod 正在運行的進程,發現在容器里面有一個 7 號進程 VSZ 占用 6522m

應用內存占用 17 G之多,那很顯然,並不是這個進程在搗鬼,但整個容器里面確實就只有這個進程在運行着,並且該 Java 進程還設置了分配內存的限制,最大不會超過 4g,可是內存還是一直在漲。

容器內部ps

而且容器里面執行 top 看到的信息很少,我們對比下實際操作系統的 top 命令執行結果多了很多列,例如RES、 %MEM 等等。

top命令

小TIPS:

RSSVSZ指標相關的參數含義:

  • RSS是Resident Set Size(常駐內存大小)的縮寫,用於表示進程使用了多少內存(RAM中的物理內存),RSS不包含已經被換出的內存。RSS包含了它所鏈接的動態庫並且被加載到物理內存中的內存。RSS還包含棧內存和堆內存。
  • VSZ是Virtual Memory Size(虛擬內存大小)的縮寫。它包含了進程所能訪問的所有內存,包含了被換出的內存,被分配但是還沒有被使用的內存,以及動態庫中的內存。

使用top 需注意:

  • 虛擬內存通常並不會全部分配給物理內存;

  • 共享內存 SHR 並不一定是共享,例如程序的代碼段、非共享的動態鏈接庫,也都算在 SHR 里。其中,SHR 也包括了進程間真正共享的內存。

    因此,計算多個進程的內存使用時,不建議把所有進程的 SHR 直接相加得出結果。

所以只從 top 看是不准確的,/proc/pid/status會更精准顯示進程內存占用:

cat /proc/7/status

查看當前 pid 的狀態,其中有一個字段VmRSS 表示當前進程所使用的內存,然而我們發現用戶當前進程所占用的內存才2.3G 左右。

proc實際內存顯示

但事實是 kubectl top pod` 查看 pod 的內存占用 確實發現該 pod 內存占用確實高達 17 G ,推斷並不是容器內進程內存泄露導致的問題,那這就奇怪了,是什么原因導致占用這么多內存呢?

二、Buffer & cache原理

要繼續排查這個問題,我們就需要先看看容器的內存統計是如何計算的了。

眾所周知,操作系統系統的內存設計,有二級,三級物理緩存。為加快運算速度,緩存被大量應用,常用數據會被buffercache之類占用,在 Linux 操作系統中會把這部分內存算到已使用。

對於容器來講,也會把某容器引發的cache占用算到容器占用的內存上,要驗證這個問題,我們可以啟動一個容器, dd 創建一個大文件觀察下內存變化。

[root@8e3715641c31 /]# dd if=/dev/zero of=my_new_file count=1024000 bs=3024

1024000+0 records in
1024000+0 records out
3096576000 bytes (3.1 GB, 2.9 GiB) copied, 28.7933 s, 108 MB/s

你會發現,系統的 buff/cache 這一列會不斷的增大。

[root@8e3715641c31 /]# free -h
              total        used        free      shared  buff/cache   available
Mem:          3.7Gi       281Mi       347Mi       193Mi       3.1Gi       3.0Gi
Swap:            0B          0B          0B

三、緩存測試

回歸上述問題,QQ靚號出售地圖會不會是 Java 程序在不停的往磁盤寫文件,導致 cache 不斷的增大呢?

kubectl logs -f pod-name -n namespace-name 

查看,發現整屏幕不斷的輸出 debug 日志。然后回到開頭的圖,我們會發現cache 空間高達 20g 左右。

topinfo

我們嘗試把 cache 清掉下看看內存是否會下降,通過配置 drop_caches強行清楚系統緩存。

sync; echo 3 > /proc/sys/vm/drop_caches
sync; echo 1 > /proc/sys/vm/drop_caches
sync; echo 2 > /proc/sys/vm/drop_caches  

當執行完如上命令后,該 pod 的內存瞬間變小,同時磁盤 I/O 持續飆升,印證是 cache 問題導致的。告知開發把日志級別從  debug 改成 info,內存問題得到解決。

TIPS

/proc/sys是虛擬文件系統,是與kernel實體間進行通信的橋梁。通過修改/proc中的文件,來對當前kernel的行為做出調整。通過調整/proc/sys/vm/drop_caches來釋放內存。其默認數值為0。

  • 1

    表示僅清除頁面緩存(PageCache):

  • 2

    表示清除目錄項和inode

  • 3

    //表示清空所有緩存(pagecache、dentries 和 inodes

四、生產環境內存飆升解決方案的建議

  • 建議1

合理的規划資源,對每個 Pod 配置Limit,限制資源使用。kubernetes 提供了針對 pod 級別的資源限制功能,但默認沒有 CPU 和內存的限額。這意味着系統中的任何 Pod 將能夠像執行該 Pod 所在的節點一樣,消耗足夠多的 CPU 和內存。這容易導致node節點的資源被無限占用。k8s官方也並不推薦該方式。

建議方案:通過 limits 來限制 Pod 的內存和 CPU ,這樣一來一旦內存達到使用限制,pod 會自動重啟,而不會影響到其他 pod。

resources:
      requests:
        cpu: "200m"
        memory: "128Mi"
      limits:
        cpu: "500m"
        memory: "512Mi"
  • 建議2

針對應用本身也需要加上資源使用限制,例如 Java 程序可以限制堆內存和非堆內存的使用:

堆內存分配:

  • JVM 最大分配的內存由**-Xmx** 指定,默認是物理內存的 1/4;

  • JVM 初始分配的內存由**-Xms** 指定,默認是物理內存的 1/64;

  • 默認空余堆內存小於 40% 時,JVM 就會增大堆直到-Xmx 的最大限制;空余堆內存大於 70% 時,JVM 會減少堆直到 -Xms 的最小限制;

    因此,服務器的推薦設置是:-Xms、-Xmx 相等以避免在每次 GC 后調整堆的大小。對象的堆內存由稱為垃圾回收器的自動內存管理系統回收。

非堆內存分配:

  • 由 XX:MaxPermSize 設置最大非堆內存的大小,默認是物理內存的 1/4;
  • JVM 使用**-XX:PermSize** 設置非堆內存初始值,默認是物理內存的 1/64;
  • -Xmn2G:設置年輕代大小為 2G;
  • -XX:SurvivorRatio,設置年輕代中 Eden 區與 Survivor 區的比值。
  • 建議3

應用本身優化,如本案例中所類似的問題要盡量避免在生產環境發生,即在生產環境開 debug 模式。即有安全風險,又會因為頻繁的寫日志會把 cache 打的非常高。建議將日志收集到專業的日志管理工具中,例如 ELK或SLS

  • 建議4

監控非常重要,針對應用本身的資源使用情況和系統的各項監控指標要完善,便於及時發現問題。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM