k8s最佳實踐:部分業務POD內存持續泄露問題

K8S部分業務POD內存持續泄露問題

1.前言

線上K8S集羣有極少量的PHP業務,它們的POD內存持續走高直到OOM,相信與特殊代碼場景有關,需要展開分析。

我從POD的內存監控原理入手,分析到底內存用到了哪些地方。

2.分析過程

  • 第一步:分析pod的內存限制原理

    • 容器化依賴Cgroup限制內存資源,Docker採集容器的內存使用量也是基於Cgroup技術
    • 實際上,Cgroup標準做法是把每個子系統作爲一棵樹(Hierarchy),然後在樹裏面創建子cgroup做 資源限制。
    • Centos默認創建了這樣的N顆樹,每棵樹管理1個子系統,K8S就是在這些樹中創建子目錄來使用Cgroup能力。
  • 第二步:分析pod的內存限制是如何實現的(以內存memory爲例,我們知道POD可以設置resource limit)

    • 首先docker ps找到目標pod的相關容器,至少有2個容器,一個是pause容器,一個是應用容器

    • 拿着應用容器的container id,執行docker inspect 可以看到label裏有一個pod唯一標識uid

    • K8S創建了kubepods子cgroup,仍舊以memory爲例

      • ll /sys/fs/cgroup/memory/kubepods/
        
    • K8S資源限制是POD級的,所以K8S還會在這個cgroup下創建POD的子memory cgroup,進行POD級具體的資源限制。

      • 所有POD的總內存限制爲30.23G,宿主機是32G內存,其他1G多內存沒有納入cgroup是因爲kubelet配置的預留內存導致的。
    • 根據上面找到的POD,就可以繼續定位到POD級的cgroup了

    • 再往POD下面一級就是container的cgroup了,是繼承了POD級的限制,反正POD級就那麼多內存,裏面的單個容器最多也就用這些

  • 第二步:分析pod的內存都使用到了哪些地方

    • 發現應用容器佔了1.8G左右,快要把POD的內存限制用滿了。(也可以通過docker stats命令查看到容器內存佔用)

    • 拿着之前發現的pause容器ID,查看一下內存使用,只用了1M左右,因此pause容器的內存佔用可以忽略。

    • 詳細看應用容器的內存使用統計,會發現total_rss和total_cache加起來不過300MB+,其他內存跑哪裏去了?

      • [root@10-42-53-112 ~]# cat /sys/fs/cgroup/memory/kubepods/pod931369e9-2a87-4090-a304-dd02122e7acc/7e75c3921b2157ccecc5cff5055940c782f02cb8227ae080874220bb06124dad/memory.stat 
        
    • 經過了解,cgroup的memory.usage_in_bytes除了計算rss和swap外,還統計了kmem,也就是內核使用內存,我們查看一下實際kmem使用量

      • [root@10-42-53-112 ~]# cat /sys/fs/cgroup/memory/kubepods/pod931369e9-2a87-4090-a304-dd02122e7acc/7e75c3921b2157ccecc5cff5055940c782f02cb8227ae080874220bb06124dad/memory.kmem.usage_in_bytes 
        1564602368
        
      • 果然1.5G左右,和rss加起來大概就是1.8G了,發現這個應用容器大部分內存都被kernel使用了

    • 經驗告訴我,這些“看不到”的內存大概率是被 slab 使用了。slab allocator 是 Linux 內核的內存分配機制,是給內核對象分配內存的

    • 現在雖然知道內存是被 slab 所使用了,但是因爲 slab 裏面有各種不同的內核對象(object),還需要找到是哪些對象佔用了內存,可以查看 /proc/slabinfo 文件,發現佔用最多的是 dentry 對象:

    • 上述容器使用了790萬的dentry,佔了1.4G內存;宿主機執行slabtop可以看到整機分配了3000萬的dentry,佔了6G左右內存。

    • 我們只有個別的應用存在內存泄露情況,懷疑與代碼特殊行爲有關,嘗試strace了一下php-fpm,看是否有大量文件操作導致dentry增加:竟然真的在不停的創建臨時文件。

    • 其行爲是先讀取socket讀進來16384字節的數據:然後才創建了1個臨時文件開始寫入後續數據,最後再把所有數據從臨時文件裏讀進內存,纔開始進入PHP腳本的處理邏輯。

    • 我高頻抓取了一下/tmp目錄,抓到1個臨時文件看了一下內容:發現內容就是/comment/bgm_bulk_index接口的POST body體,懷疑PHP-FPM遇到太大的POST體會走臨時文件。

    • FPM處理POST表單時,大概會通過php_stream_temp_create_ex創建用於存放解析結果的request_body buffer,第2個參數是內存閾值,一旦超過內存閾值就會寫臨時文件;

      然後循環解析數據寫入這個Buffer,因爲上述case的POST body總大小是百K,所以就超過了內存閾值,寫了臨時文件。

      這個SAPI_POST_BLOCK_SIZE內存閾值是16進制定義的,實際就是16384

3.解決方案

  • 最後,在高內存POD所在的node,進行一次slab dentry cache清理,觀察POD內存是否下降:
  • POD內存從1.8G降到了346M,基本吻合了RSS實際佔用,說明kmem部分被釋放了。
  • 因此,PHP頻繁的新建+刪除文件,就會不停的分配新的dentry對象,舊的dentry會越來越多直到系統沒有更多內存可用纔會開始淘汰緩存。
  • 定時任務 drop cache,不過過幾天就會反彈
  • 這個案例告訴我們,docker默認將kmem算作cgroup的內存佔用是比較坑的,哪個cgroup創建出來的slab對象就會被算到誰的頭上,多多少少有點不合理。
  • 所以,也許禁止docker將kmem統計在memory usage內,是不是一個更好的做法呢?網上有諸多討論,就不贅述了。
  • 能不能關閉cgroup kmem counting來避免slab內存計入cgroup呢?是否有風險呢?

4.pod內存泄露的其他場景

1.場景1:nginx反向代理

該問題發生在nginx+php-fpm技術棧,但不限於此場景。

當訪問某URL時,其匹配邏輯如下:

  • 匹配location /,通過if檢查是否存在,如果存在就返回靜態文件,否則rewrite到index.php重新匹配。
  • 匹配location .php,反向代理請求給PHP-FPM。

也就是說,每個URL都會去磁盤上讀一次文件,無論文件是否存在。

這就意味着,有多少種URL,就有多少個slab dentry cache。

當遇到URL美化的場景就有問題了,比如:文章ID是URL的一部分,

/articles/detail/134543

/articles/detail/881929

這種URL的規模是無法估量的,經過nginx先查一次磁盤緩存到dentry,然後再轉發給php-fpm進行處理,就必然導致千百萬的dentry對象被緩存下來。

類似場景大家可以自行延伸,比如try_files指令也是先找磁盤文件,一樣會坑。

場景2:web框架

這個case比較個性化,但也作爲一種思路開拓提供給大家。

當我關閉了nginx反向代理先走文件的配置後,發現dentry仍舊在狂漲,因此我就進一步仔細看了一下php-fpm的strace日誌。

發現php-fpm每次請求都會去web框架下的cache目錄找一個md5樣子的文件,難道web框架開啓了cache特性?

function _display_cache(&$CFG, &$URI)
	{
		$cache_path = ($CFG->item('cache_path') == '') ? APPPATH.'cache/' : $CFG->item('cache_path');
 
		// Build the file path.  The file name is an MD5 hash of the full URI
		$uri =	$CFG->item('base_url').
				$CFG->item('index_page').
				$URI->uri_string;
 
		$filepath = $cache_path.md5($uri);
 
		if ( ! @file_exists($filepath))
		{
			return FALSE;
		}
}

翻了一下框架代碼,發現這個框架實現的確有點問題,在沒有開啓cache特性的情況下仍舊會去cache目錄嘗試加載一下緩存文件:

因爲文件名是URL的MD5,這就導致因爲query string的不同而千變萬化,即每次請求都將創建1個dentry cache。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章