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:
RSS
、VSZ
指標相關的參數含義:
- 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原理
要繼續排查這個問題,我們就需要先看看容器的內存統計是如何計算的了。
眾所周知,操作系統系統的內存設計,有二級,三級物理緩存。為加快運算速度,緩存被大量應用,常用數據會被buffer
、cache
之類占用,在 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
監控非常重要,針對應用本身的資源使用情況和系統的各項監控指標要完善,便於及時發現問題。