生產環境發現不定時 Java 應用出現 coredump 故障,測試環境不定時出現寫入 /cgroup/memory 報 no space left on device 的故障,致使整個 kubernetes node 節點沒法使用。設置會隨着堆積的 cgroup 愈來愈多,docker ps 執行異常,直到把內存吃光,機器掛死。css
典型報錯:node
kubelet.ns-k8s-node001.root.log.ERROR.20180214-113740.15702:1593018:E0320linux
04:59:09.572336 15702 remote_runtime.go:92] RunPodSandbox from runtimegit
service failed: rpc error: code = Unknown desc = failed to start sagithub
ndbox container for pod 「osp-xxx-com-ljqm19-54bf7678b8-bvz9s」: Errordocker
response from daemon: oci runtime error: container_linux.go:247:json
starting container process caused 「process_linux.go:258: applyingvim
cgroup configuration for process caused 「mkdirapp
/sys/fs/cgroup/memory/kubepods/burstable/podf1bd9e87-1ef2-11e8-afd3-fa163ecf2dce/8710c146b3c8b52f5da62e222273703b1e3d54a6a6270a0ea7ce1b194f1b5053:less
no space left on device」」
或者
Mar 26 18:36:59 ns-k8s-node-s0054 kernel: SLUB: Unable to allocate
memory on node -1 (gfp=0x8020) Mar 26 18:36:59 ns-k8s-noah-node001
kernel: cache:
ip6_dst_cache(1995:6b6bc0c9f30123084a409d89a300b017d26ee5e2c3ac8a02c295c378f3dbfa5f),
object size: 448, buffer size: 448, default order: 2, min order: 0
該問題發生先後,進行過 kubernetes 1.6 到 1.9 的升級工做。排查了很久才定位到問題與 kubernetes 、內核有關。
1. 對比測試結果
使用一樣的測試方法,結果爲:
1)使用初次部署 k8s 1.6 版本測試,沒有出現 cgroup memory 遺漏問題;
2)從 k8s 1.6 升級到 1.9 後,測試沒有出現 cgroup memory 遺漏問題;
3)重啓 kubelet 1.9 node 節點重啓,再次測試,出現 cgroup memory 遺漏問題。
對比 k8s 1.6 和 1.9 建立的 POD 基礎容器和業務容器,runc、libcontainerd、docker inspect 的容器 json 參數都一致,沒有差別。
爲何一樣是 k8s 1.9(其餘 docker、kernel 版本一致)的狀況下,結果不同呢?重啓的影響是?
2.問題重現
對於 cgroup memory 報 no space left on device ,是因爲 cgroup memory 存在 64k(65535 個)大小的限制。
採用下面的測試方式,能夠發如今刪除 pod 後,會出現 cgroup memory 遺漏的問題。該測試方法經過留空 99 個 系統 cgroup memory 位置,來判斷引發問題的緣由是因爲 pod container 致使的。
1)填滿系統 cgroup memory
3.10.0-514.10.2.el7.x86_64
seq 1 65535
;do mkdir /sys/fs/cgroup/memory/test/test-${i}; done把系統 cgroup memory 填到 65535 個。
2)騰空 99 個 cgroup memory
# for i in `seq 1 100`;do rmdir /sys/fs/cgroup/memory/test/test-${i} 2>/dev/null 1>&2; done
seq 1 100
;do mkdir /sys/fs/cgroup/memory/test/test-${i}; donemkdir: cannot create directory ‘/sys/fs/cgroup/memory/test/test-100’: No space left on device
seq 1 100
;do rmdir /sys/fs/cgroup/memory/test/test-${i}; donememory 11 65436 1
在寫入第 100 個的時候提示沒法寫入,證實寫入了 99 個。
3)建立一個 pod 到這個 node 上,查看佔用的 cgroup memory 狀況
每建立一個 pod ,會佔用 3 個 cgroup memory 目錄:
# ll /sys/fs/cgroup/memory/kubepods/pod0f6c3c27-3186-11e8-afd3-fa163ecf2dce/ total 0 drwxr-xr-x 2 root root 0 Mar 27 14:14 6d1af9898c7f8d58066d0edb52e4d548d5a27e3c0d138775e9a3ddfa2b16ac2b drwxr-xr-x 2 root root 0 Mar 27 14:14 8a65cb234767a02e130c162e8d5f4a0a92e345bfef6b4b664b39e7d035c63d1
這時再次建立 100 個 cgroup memory ,由於 pod 佔用了 3 個,會出現 4 個沒法成功:
# for i in `seq 1 100`;do mkdir /sys/fs/cgroup/memory/test/test-${i}; done mkdir: cannot create directory ‘/sys/fs/cgroup/memory/test/test-97’: No space left on device <-- 3 directory used by pod mkdir: cannot create directory ‘/sys/fs/cgroup/memory/test/test-98’: No space left on device mkdir: cannot create directory ‘/sys/fs/cgroup/memory/test/test-99’: No space left on device mkdir: cannot create directory ‘/sys/fs/cgroup/memory/test/test-100’: No space left on device
memory 11 65439 1
寫入到的 cgroup memory 增長到 65439 個。
4)刪掉測試 pod ,看看 3 個佔用的 cgroup memory 是否有釋放
看到的結果:
# cat /proc/cgroups memory 11 65436 1
seq 1 100
;do mkdir /sys/fs/cgroup/memory/test/test-${i}; donemkdir: cannot create directory ‘/sys/fs/cgroup/memory/test/test-97’: No space left on device
mkdir: cannot create directory ‘/sys/fs/cgroup/memory/test/test-98’: No space left on device
mkdir: cannot create directory ‘/sys/fs/cgroup/memory/test/test-99’: No space left on device
mkdir: cannot create directory ‘/sys/fs/cgroup/memory/test/test-100’: No space left on device
能夠看到,雖然 cgroup memory 減小到 65536 ,彷佛 3 個位置釋放了。但實際上測試結果發現,並不能寫入,結果仍是 pod 佔用時的沒法寫入 97-100 。
這就說明,cgroup memory 數量減小,但被 pod container 佔用的空間沒有釋放。
反覆驗證後,發現隨着 pod 發佈和變動的增長,該問題會愈來愈嚴重,直到把整臺機器的 cgroup memory 用完。
$ cat /proc/cgroups
cpuset 10 229418 1 — 但 cpuset 數量很恐怖
cpu 6 118 1
cpuacct 6 118 1
memory 7 109 1 — 看上去很少,實際沒有釋放空間的
devices 3 229504 1
freezer 5 32 1
net_cls 4 32 1
blkio 9 118 1
perf_event 11 32 1
hugetlb 2 32 1
pids 8 32 1
net_prio 4 32 1
3.問題根源
通過大量的測試和對比分析,在兩個 k8s 1.9 環境中 kubelet 建立的 /sys/fs/cgroup/memory/kubepods 差別,發現:
沒問題的:
[root@k8s-node01 kubepods]# cat memory.kmem.slabinfo cat:
memory.kmem.slabinfo: Input/output error
有問題的:
[root@k8s-node01 kubepods]# cat memory.kmem.slabinfo slabinfo –
version: 2.1
name : tunables : slabdata
也就是說,在有問題的環境下,cgroup kernel memory 特性被激活了。
關於 cgroup kernel memory,在 kernel-doc 中有以下描述:
2.7 Kernel Memory Extension (CONFIG_MEMCG_KMEM) With the Kernel memory extension, the Memory Controller is able to limit the amount of kernel
memory used by the system. Kernel memory is fundamentally different
than user memory, since it can’t be swapped out, which makes it
possible to DoS the system by consuming too much of this precious
resource. Kernel memory won’t be accounted at all until limit on a
group is set. This allows for existing setups to continue working
without disruption. The limit cannot be set if the cgroup have
children, or if there are already tasks in the cgroup. Attempting to
set the limit under those conditions will return -EBUSY. When
use_hierarchy == 1 and a group is accounted, its children will
automatically be accounted regardless of their limit value. After a
group is first limited, it will be kept being accounted until it is
removed. The memory limitation itself, can of course be removed by
writing
-1 to memory.kmem.limit_in_bytes. In this case, kmem will be accounted, but not limited.
這是一個 cgroup memory 的擴展,用於限制對 kernel memory 的使用。但該特性在老於 4.0 版本中是個實驗特性,若使用 docker run 運行,就會提示:
# docker run -d --name test001 --kernel-memory 100M registry.vclound.com:5000/hyphenwang/sshdserver:v1 WARNING: You specified a kernel memory limit on a kernel older than 4.0. Kernel memory limits are experimental on older kernels, it won't work as expected and can cause your system to be unstable.
cat /sys/fs/cgroup/memory/docker/eceb6dfba2c64a783f33bd5e54cecb32d5e64647439b4932468650257ea06206/memory.kmem.limit_in_bytes
104857600
通過反覆驗證, 當使用 docker run –kernel-memory 參數啓動的容器,在刪除後也不會釋放 cgroup memory 佔用的位置 ,存在一樣的問題。
基於該現象,
對比 kubernetes 1.6 和 kubernetes 1.9 對 cgroup kernel memory 設置的差別。
$ git diff remotes/origin/vip_v1.6.4.3 remotes/origin/vip_v1.9.0 -- vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/memory.go @@ -29,14 +35,18 @@ func (s *MemoryGroup) Apply(d *cgroupData) (err error) { path, err := d.path("memory") if err != nil && !cgroups.IsNotFound(err) { return err + } else if path == "" { + return nil } if memoryAssigned(d.config) { - if path != "" { + if _, err := os.Stat(path); os.IsNotExist(err) { if err := os.MkdirAll(path, 0755); err != nil { return err } - } - if d.config.KernelMemory != 0 { // 刪除了這行的判斷,使得 1.9 默認就 enable cgroup kernel memory 特性 + // Only enable kernel memory accouting when this cgroup + // is created by libcontainer, otherwise we might get + // error when people use `cgroupsPath` to join an existed + // cgroup whose kernel memory is not initialized. if err := EnableKernelMemoryAccounting(path); err != nil { return err }
這就引起了 cgroup memory 也不能釋放的問題(與經過 docker run –kernel-memory 打開的狀況同樣。)
其實根據 libcontainerd 推薦的 kernel 4.3 以上版本,打開 cgroup kernel memory 應該也是沒問題的。惋惜咱們使用的kernel版本是3.10.0-514.16.1.el7.x86_64,kernel memory在這個版本是不穩定的。所以讓咱們踩了這個坑。
經驗證,CentOS 7.4(3.10.0-693.11.1.el7)存在一樣的問題。
4.尋根問底
附上同事對該問題在 kernel 中追蹤的結果。
memcg是Linux內核中用於管理cgroup中kernel 內存的模塊,整個生命週期應該是跟隨cgroup的,可是當在3.10.0-514.10.2.el7版本中,一旦開啓了kmem_limit就可能會致使不能完全刪除memcg和對應的cssid。
在建立過程當中,若是咱們設定了kmem_limit就會激活memcg
memcg和cssid的釋放的調用鏈很長,其中在這個過程當中,mem_cgroup_css_free的過程當中會調用kmem_cgroup_destroy
而kmem_cgroup_destroy函數會檢查memcg是否還佔用memory,若是發現還佔用,就不會調用mem_cgroup_put函數:
而mem_cgroup_put函數會將memcg的refcnt減去1,並查看是否等於0,若是爲0即表示沒有其餘東西引用memcg,memcg能夠放心釋放
惋惜當開啓了kmem_limit後,這個refcnt不會等於0,致使永遠沒法調用到free_rcu去釋放cssid。
5.解決問題
通過以上分析,形成該故障的緣由,是因爲 kubelet 1.9 激活了 cgroup kernel-memory 特性,而在 CentOS 7.3 kernel 3.10.0-514.10.1.el7.x86_64 中對該特性支持很差。
致使刪除容器後,仍有對 cgroup memory 的佔用,沒有執行 free_css_id() 函數的操做。
解決方式,就是修改 k8s 1.9 代碼,再次禁止設置 cgroup kernel-memory 配置,保持關閉狀態。
6.疑問
爲何一樣是 k8s 1.9 的版本,在不一樣的測試中沒有問題
緣由是第一次的 k8s 1.9 是在原來 k8s 1.6 的環境中,經過升級 kubelet 版原本測試的。
而 /sys/fs/cgroup/kubepods 是由 k8s 1.6 建立的,升級到 k8s 1.9 後,啓動服務時,判斷到該路徑已經存在,就沒有再建立新的。
因此,也就沒有激活 cgroup kernel-memory 特性。
接下來建立的 POD 會繼承該路徑的 cgroup memory 屬性,也沒有激活 cgroup kernel-memoy ,因此沒有引起問題。
相反,在重啓 k8s 1.9 node 後,kubelet 新建了 /sys/fs/cgroup/kubepods ,激活了 cgroup kernel-memoy ,致使後續的 POD 在刪除時也有問題。
詳情可進一步參考: kubernetes 1.9 與 CentOS 7.3 內核兼容問題