天天看點

Docker底層基石namespace與cgroup

作者:後端開發進階

Docker是使用容器container的平台,容器其實隻是一個隔離的程序,除此之外啥都沒有。這個程序包含一些封裝特性,以便和主機還有其他的容器隔離開。一個容器依賴最多的是它的檔案系統也就是image,image提供了容器運作的一切包括 code or binary, runtimes, dependencies, and 其他 filesystem 需要的對象。

容器在Linux上本地運作,并與其他容器共享主機的核心。它運作一個獨立的程序,占用的記憶體不比其他的filesystem多,是以它是輕量級的。相比之下,虛拟機(VM)運作一個成熟的“guest”作業系統,通過hypervisor對主機資源進行虛拟通路。一般來說,vm會産生大量開銷,超出應用程式邏輯所消耗的開銷。

container vm
Docker底層基石namespace與cgroup
Docker底層基石namespace與cgroup

容器本質上是把系統中為同一個業務目标服務的相關程序合成一組,放在一個叫做namespace的空間中,同一個namespace中的程序能夠互相通信,但看不見其他namespace中的程序。每個namespace可以擁有自己獨立的主機名、程序ID系統、IPC、網絡、檔案系統、使用者等等資源。在某種程度上,實作了一個簡單的虛拟:讓一個主機上可以同時運作多個互不感覺的系統。

Docker底層基石namespace與cgroup

此外,為了限制namespace對實體資源的使用,對程序能使用的CPU、記憶體等資源需要做一定的限制。這就是Cgroup技術,Cgroup是Control group的意思。比如我們常說的4c4g的容器,實際上是限制這個容器namespace中所用的程序,最多能夠使用4核的計算資源和4GB的記憶體。 簡而言之,Linux核心提供namespace完成隔離,Cgroup完成資源限制。namespace+Cgroup構成了容器的底層技術(rootfs是容器檔案系統層技術)。

namespace

一個namespace把一些全局系統資源封裝成一個抽象體,該抽象體對于本namespace中的程序來說有它們自己的隔離的全局資源執行個體。改變這些全局資源對于該namespace中的程序是可見的,而對其他程序來說是不可見的。 Linux 提供一下幾種 namespaces:

Namespace   Constant                           Isolates
  -  IPC            CLONE_NEWIPC            System V IPC, POSIX message queues     程序間通信隔離
  -  Network     CLONE_NEWNET           Network devices, stacks, ports, etc.       隔離網絡資源
  -  Mount        CLONE_NEWNS             Mount points                             隔離檔案系統
  -  PID            CLONE_NEWPID            Process IDs                            程序隔離
  -  User          CLONE_NEWUSER         User and group IDs                        隔離使用者權限
  -  UTS          CLONE_NEWUTS           Hostname and NIS domain name              (UNIX Time Sharing)用來隔離系統的hostname以及NIS domain name           

為了在分布式的環境下進行通信和定位,容器必然需要一個獨立的IP、端口、路由等等,自然就想到了網絡的隔離。同時,你的容器還需要一個獨立的主機名以便在網絡中辨別自己。想到網絡,順其自然就想到通信,也就想到了程序間通信的隔離。可能你也想到了權限的問題,對使用者和使用者組的隔離就實作了使用者權限的隔離。最後,運作在容器中的應用需要有自己的PID,自然也需要與主控端中的PID進行隔離。 Linux核心為以上6種namespace隔離提供了系統調用,具體就是namespacede API包括clone()、setns()以及unshare(),還有/proc下的部分檔案。為了确定隔離的到底是哪6項namespace,在使用這些API時,通常需要指定CLONE_NEWNS、CLONE_IPC、CLONE_NEWNET、CLONE_NEWPID、CLONE_USER和CLONE_UTS. 其中CLONE_NEWNS是mount,因為它是第一個linux namespace是以辨別位比較特殊。 使用clone()來建立一個獨立的namespace程序,是最常見的做法,也是Docker使用namespace最基本的方法,它的調用方式舉例如下:

int child_pid = clone(child_main, child_stack+STACK_SIZE, 
                        CLONE_NEWNS | CLONE_NEWPID | CLONE_NEWIPC | SIGCHLD , NULL);           

network namespace

在Linux Programmer’s Manual裡對 Network Namespace 有一個段簡短的描述,在裡面就列出了最主要的幾部分資源,它們都是通過 Network Namespace 隔離的。

  • 第一種,網絡裝置,這裡指的是 lo,eth0 等網絡裝置。你可以通過 ip link指令看到它們。
  • 第二種是 IPv4 和 IPv6 協定棧。從這裡我們可以知道,IP 層以及上面的 TCP 和 UDP 協定棧也是每個 Namespace 獨立工作的。是以 IP、TCP、UDP 的很多協定,它們的相關參數也是每個 Namespace 獨立的,這些參數大多數都在 /proc/sys/net/ 目錄下面,同時也包括了 TCP 和 UDP 的 port 資源。
  • 第三種,IP 路由表,這個資源也是比較好了解的,你可以在不同的 Network Namespace 運作 ip route 指令,就能看到不同的路由表了。
  • 第四種是防火牆規則,其實這裡說的就是 iptables 規則了,每個 Namespace 裡都可以獨立配置 iptables 規則。 參考上面的例子,我們着重關注一下network namespace. 從Linux核心3.8版本開始,/proc/PID/ns 目錄下的檔案都是一個特殊的符号連結檔案,可以看出這些符号連結的其中一個用途是确定某兩個程序是否屬于同一個namespace。如果兩個程序在同一個namespace中,那麼這兩個程序/proc/PID/ns目錄下對應符号連結檔案的inode數字會是一樣的。 除此之外,/proc/PID/ns目錄下的檔案還有一個作用——當我們打開這些檔案時,隻要檔案描述符保持open狀态,對應的namespace就會一直存在,哪怕這個namespace裡的所有程序都終止運作了。這是什麼意思呢?之前版本的Linux核心,要想保持namespace存在,需要在namespace裡放一個程序(當然,不一定是運作中的),這種做法在一些場景下有些笨重(雖然kubernetes就是這麼做的)。是以,Linux核心提供的黑科技允許:隻要打開檔案描述符,不需要程序存在也能保持namespace存在!怎麼操作?請看下面的指令:
touch /my/net #建立一個檔案
mount --bind /proc/$/ns/net /my/net           

如上所示,把/proc/PID/ns目錄下的檔案挂載起來就能起到打開檔案描述符的作用,而且這個network namespace會一直存在,直到/proc/$/ns/net被解除安裝。那麼接下來,如何向這個namespace裡“扔”程序呢?Linux系統調用setns()int setns(int fd, int nstype)就是用來做這個工作的,其主要功能就是把一個程序加入一個已經存在的namespace中。ip netns exec這個子指令,也可以輕松進入一個network namespace,然後執行一些操作。 與namespace相關的最後一個系統調用是unshare(),該函數聲明為int unshare(int flags);,用于幫助程序“逃離”namespace。unshare()系統調用的工作機制是:先通過制定的flags建立相應的namespace,再把這個程序挪到這些新建立的namespace中,于是也就完成了程序從原先namespace的撤離。unshare()提供的功能很像clone(),差別在于unshare()作用在一個已經存在的程序上,而clone()會建立一個新的程序。該系統調用的應用場景是在目前shell所在的namespace外執行一條指令unshare [options] program [args] Linux會為需要執行的指令啟動一個新程序,然後在另外一個namespace中執行操作,這樣就可以起到執行結果和原(父)程序隔離的效果。

cgroups

Cgroups是control groups的縮寫,最初由google的工程師提出,後來被整合進Linux核心。Cgroups是Linux核心提供的一種可以限制、記錄、隔離程序組(process groups)所使用的實體資源(如:CPU、記憶體、IO等)的機制。對開發者來說,cgroups 有如下四個有趣的特點:

  • cgroups 的 API 以一個僞檔案系統的方式實作,即使用者可以通過檔案操作實作 cgroups 的組織管理。
  • cgroups 的組織管理操作單元可以細粒度到線程級别,使用者态代碼也可以針對系統配置設定的資源建立和銷毀 cgroups,進而實作資源再配置設定和管理。
  • 所有資源管理的功能都以“subsystem(子系統)”的方式實作,接口統一。
  • 子程序建立之初與其父程序處于同一個 cgroups 的控制組。

本質上來說,cgroups 是核心附加在程式上的一系列鈎子(hooks),通過程式運作時對資源的排程觸發相應的鈎子以達到資源追蹤和限制的目的。實作 cgroups 的主要目的是為不同使用者層面的資源管理,提供一個統一化的接口。從單個程序的資源控制到作業系統層面的虛拟化。Cgroups 提供了以下四大功能:

  • 資源限制(Resource Limitation):cgroups 可以對程序組使用的資源總額進行限制。如設定應用運作時使用記憶體的上限,一旦超過這個配額就發出 OOM(Out of Memory)。 資源限制最重要的是CPU限制,CPU Cgroup 一般在 Linux 發行版裡會放在 /sys/fs/cgroup/cpu 這個目錄下。在這個子系統的目錄下,每個控制組(Control Group) 都是一個子目錄,各個控制組之間的關系就是一個樹狀的層級關系(hierarchy)。例如,我們到docker run指令啟動的容器對應的CPU cgroup檔案目錄/sys/fs/cgroup/cpu/docker/下,看到其中三個檔案cpu.cfs_period_us,cpu.cfs_quota_us和 cpu.shares.其中cpu.cfs_period_us表示CFS 算法(Completely Fair Scheduler,即完全公平排程器,linux中普遍的程序排程算法)的一個排程周期;cfs_quota_us表示 CFS 算法中,在一個排程周期裡這個控制組被允許的運作時間。這兩個量的機關是納秒,都是絕對量值。而cpu.shares是 CPU Cgroup 對于控制組之間的 CPU 配置設定比例,它的預設值是 1024。
  • 舉例說明cpu.cfs_period_us,cpu.cfs_quota_us的意義: 第一個參數是 cpu.cfs_period_us,它是 CFS 算法的一個排程周期,一般它的值是 100000,以 microseconds 為機關,也就 100ms。第二個參數是 cpu.cfs_quota_us,它“表示 CFS 算法中,在一個排程周期裡這個控制組被允許的運作時間,比如這個值為 50000 時,就是 50ms。如果用這個值去除以排程周期(也就是 cpu.cfs_period_us),50ms/100ms = 0.5,這樣這個控制組被允許使用的 CPU 最大配額就是 0.5 個 CPU。從這裡能夠看出,cpu.cfs_quota_us 是一個絕對值。如果這個值是 200000,也就是 200ms,那麼它除以 period,也就是 200ms/100ms=2。你看,結果超過了 1 個 CPU,這就意味着這時控制組需要 2 個 CPU 的資源配額。
  • cpu.shares。這個值是 CPU Cgroup 對于控制組之間的 CPU 配置設定比例,它的預設值是 1024。假設我們前面建立的 group3 中的 cpu.shares 是 1024,而 group4 中的 cpu.shares 是 3072,那麼 group3:group4=1:3。因為 cpu.shares 是幾個控制組之間的 CPU 配置設定比例,而且一定要到整個節點中所有的 CPU 都跑滿的時候,它才能發揮作用。

以上為配置Limit CPU 就是容器所在 Cgroup 控制組中的 CPU 上限值,Request CPU 的值就是控制組中的 cpu.shares 的值,提供了功能基礎。

  • 優先級配置設定(Prioritization):通過配置設定的 CPU 時間片數量及硬碟 IO 帶寬大小,實際上就相當于控制了程序運作的優先級。
  • 資源統計(Accounting): cgroups 可以統計系統的資源使用量,如 CPU 使用時長、記憶體用量等等,這個功能非常适用于計費。
  • 程序控制(Control):cgroups 可以對程序組執行挂起、恢複等操作。 Docker正是使用cgroup進行資源劃分,每個容器都作為一個程序運作起來,每個業務容器都會有一個基礎的pause容器也就是POD作為基礎容器。pause容器提供了劃分namespace的内容,并連通同一POD下的所有容器,共享網絡資源。檢視容器的PID,對應/proc/pid/下是該容器的運作資源,每一個檔案保持打開的話,對應的namespace就會一直存在。 例如最大程序數限制,系統的最大程序數可以通過檢視檔案/proc/sys/kernel/pid_max來看,當然還受限于最大打開檔案數、最大使用者空間程序數(ulimit -a). 對應容器裡最大的程序數,以Docker run指令啟動的容器為例,可以通過檢視檔案 /sys/fs/cgroup/pids/docker//pids.max 來确認,如果太小會導緻容器有很多defunct程序(僵屍)。 在分析 K8s、Docker 等 cgroup 相關操作時。比如 docker run xxx 時,可以看到 /sys/fs/cgroup/cpuset/docker/xxx/cpuset.cpus、/sys/fs/cgroup/cpuset/docker/xxx/cpuset.mems 等 cgroup 檔案被打開,也可以檢視 kube-proxy 在周期性重新整理 cgroup 相關檔案。這些都是資源劃分相關檔案。 可以通過cat /proc/mount 來檢視 cgroups 挂載的目錄,一般在 /sys/fs/cgroup。

分析一個k8s Pod

這裡我以一個default命名空間的Pod bizagent-599bb4bcbf-l2gvk為例,來分析它的namespace和cgroup,這個Pod是用calico網絡插件啟動建立起來的。本機IP位址是192.168.1.2,calico給Pod配置設定的IP位址是10.8.188.4.

[root@test ~]# docker ps |grep bizagent-599bb4bcbf-l2gvk
4c251b76dbcb   891d874693ed                                             "/docker-entrypoint.…"   5 days ago     Up 5 days                                                                                                                                                    k8s_nginx_bizagent-599bb4bcbf-l2gvk_default_1e2cf7c4-2bfc-4b50-8f20-1203c2c9b08e_2
f82774c4ae42   k8s.gcr.io/pause:3.2                                     "/pause"                 5 days ago     Up 5 days                                                                                                                                                    k8s_POD_bizagent-599bb4bcbf-l2gvk_default_1e2cf7c4-2bfc-4b50-8f20-1203c2c9b08e_3
[root@tes ~]# docker inspect f82774c4ae42|grep Pid
            "Pid": 20560,
            "PidMode": "",
            "PidsLimit": null,
[root@test ~]# docker inspect 4c251b76dbcb|grep Pid
            "Pid": 25180,
            "PidMode": "",
            "PidsLimit": null,
[root@test ~]#            

通過以上指令得到pause容器的pid是20560, 業務容器(我也不太清楚另外一個容器該怎麼叫,暫且叫它業務容器吧)的pid是25180, 然後列出它們所有的ns:

[root@test ~]# lsns |grep 20560
4026537282 uts        1   20560 root      /pause
4026537284 mnt        1   20560 root      /pause
4026537285 ipc        3   20560 root      /pause
4026537286 pid        1   20560 root      /pause
4026537288 net        3   20560 root      /pause
[root@test ~]# lsns |grep 25180
4026537534 mnt        2   25180 root      nginx: master process nginx -g daemon off
4026537535 uts        2   25180 root      nginx: master process nginx -g daemon off
4026537536 pid        2   25180 root      nginx: master process nginx -g daemon off
[root@test ~]#           

可以看到業務容器比pause容器少了兩個隔離資源,分别是ipc和net,事實證明,lsns 不是檢查程序名稱空間的最佳工具。相反,要檢查某個程序使用的命名空間,可以參考 /proc/${pid}/ns 位置:

[root@test ~]#  ls -l /proc/20560/ns
total 0
lrwxrwxrwx 1 root root 0 Apr 12 10:47 ipc -> ipc:[4026537285]
lrwxrwxrwx 1 root root 0 Apr 13 15:54 mnt -> mnt:[4026537284]
lrwxrwxrwx 1 root root 0 Apr 12 10:47 net -> net:[4026537288]
lrwxrwxrwx 1 root root 0 Apr 13 15:54 pid -> pid:[4026537286]
lrwxrwxrwx 1 root root 0 Apr 13 15:54 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Apr 13 15:54 uts -> uts:[4026537282]
[root@test ~]# 
[root@test ~]#  ls -l /proc/25180/ns
total 0
lrwxrwxrwx 1 root root 0 Apr 13 15:54 ipc -> ipc:[4026537285]
lrwxrwxrwx 1 root root 0 Apr 13 15:54 mnt -> mnt:[4026537534]
lrwxrwxrwx 1 root root 0 Apr 13 15:54 net -> net:[4026537288]
lrwxrwxrwx 1 root root 0 Apr 13 15:54 pid -> pid:[4026537536]
lrwxrwxrwx 1 root root 0 Apr 13 15:54 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Apr 13 15:54 uts -> uts:[4026537535]
[root@test ~]#            

如果兩個程序指向的namespace編号相同,就說明它們在同一個namespace下,否則便在不同namespace裡面。上面的示例可以看出業務容器實際上重用了 pause 容器的 net 和 ipc 命名空間!我認為上述發現完美的解釋了同一個 Pod 中容器具有的能力:

  • 能夠互相通信,共享網絡, 通過nsenter -t <容器程序号> -n -F -- ip a指令可以看出它們的網絡配置完全一樣
  • 使用 IPC(共享記憶體,消息隊列等) /proc/[pid]/ns裡設定這些link的另外一個作用是,一旦上述link檔案被打開,隻有打開的檔案描述符(fd)存在,那麼就算該namespace下的所有程序都已經結束,這個namespace也會一直存在。後續程序也可以加進來。在Docker中,通過檔案描述符定位和加入一個存在的namespace是最基本的方式。例如,docker exec 指令在已經運作着的容器中執行一個新的指令,就使用了setns()加入一個已經存在的namespace.通常為了不影響程序的調用者,也為了使新加入的pid namespace生效,會在setns()函數執行後使用clone()建立子程序繼續執行指令,讓原先的程序結束運作。

再來看一個本地的host網絡模式的容器kube-proxy-kfsm6

[root@test ~]# docker ps |grep kube-proxy-kfsm6
bcace94d9a6b   358e7e6ecf20                                             "/usr/local/bin/kube…"   6 days ago     Up 6 days                                                                                                                                                    k8s_kube-proxy_kube-proxy-kfsm6_kube-system_4aa6ff92-3b26-49db-9e52-9c0170adaf41_1
7801c9d080fe   k8s.gcr.io/pause:3.2                                     "/pause"                 6 days ago     Up 6 days                                                                                                                                                    k8s_POD_kube-proxy-kfsm6_kube-system_4aa6ff92-3b26-49db-9e52-9c0170adaf41_1
[root@test ~]# docker inspect bcace94d9a6b|grep Pid
            "Pid": 12711,
            "PidMode": "",
            "PidsLimit": null,
[root@test ~]# docker inspect 7801c9d080fe|grep Pid
            "Pid": 12414,
            "PidMode": "",
            "PidsLimit": null,
[root@test ~]# 
[root@test ~]# lsns |grep 12414
4026534907 mnt        1   12414 root      /pause
4026534908 uts        1   12414 root      /pause
4026534909 ipc        2   12414 root      /pause
4026534910 pid        1   12414 root      /pause
[root@test ~]# 
[root@test ~]# lsns |grep 12711
4026534924 mnt        1   12711 root      /usr/local/bin/kube-proxy --config=/var/lib/kube-proxy/config.conf --hostname-override=test
4026534925 pid        1   12711 root      /usr/local/bin/kube-proxy --config=/var/lib/kube-proxy/config.conf --hostname-override=test
[root@test ~]# 
[root@test ~]# ls -l /proc/12414/ns
total 0
lrwxrwxrwx 1 root root 0 Apr 12 10:47 ipc -> ipc:[4026534909]
lrwxrwxrwx 1 root root 0 Apr 13 15:54 mnt -> mnt:[4026534907]
lrwxrwxrwx 1 root root 0 Apr 12 10:47 net -> net:[4026532004]
lrwxrwxrwx 1 root root 0 Apr 13 15:54 pid -> pid:[4026534910]
lrwxrwxrwx 1 root root 0 Apr 13 15:54 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Apr 13 15:54 uts -> uts:[4026534908]
[root@test ~]# 
[root@test ~]# 
[root@test ~]# ls -l /proc/12711/ns
total 0
lrwxrwxrwx 1 root root 0 Apr 13 15:54 ipc -> ipc:[4026534909]
lrwxrwxrwx 1 root root 0 Apr 13 15:54 mnt -> mnt:[4026534924]
lrwxrwxrwx 1 root root 0 Apr 13 15:54 net -> net:[4026532004]
lrwxrwxrwx 1 root root 0 Apr 13 15:54 pid -> pid:[4026534925]
lrwxrwxrwx 1 root root 0 Apr 13 15:54 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Apr 13 15:54 uts -> uts:[4026531838]
[root@test ~]# 
[root@test ~]# ls -l /proc/1/ns
total 0
lrwxrwxrwx 1 root root 0 Apr 13 15:54 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 Apr 13 15:54 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0 Apr 13 15:54 net -> net:[4026532004]
lrwxrwxrwx 1 root root 0 Apr 12 10:46 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 Apr 13 15:54 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Apr 13 15:54 uts -> uts:[4026531838]
[root@test ~]#            

最後,我檢視了1号程序,可以看出kube-proxy的pause容器和業務容器都共享了主機的net、user, 但是為啥業務容器共享了主機的uts而pause容器卻隔離了uts呢?歡迎大家讨論。

另外,Pod 的 cgroups 是什麼樣的?systemd-cgls 可以很好地可視化 cgroups 層次結構:

[root@test ~]# systemd-cgls
├─1 /usr/lib/systemd/systemd --switched-root --system --deserialize 22
├─kubepods
│ ├─besteffort
│ │ ├─pod4aa6ff92-3b26-49db-9e52-9c0170adaf41
│ │ │ ├─bcace94d9a6bc2046e3109d0c6cf608c33c9074fe8d7c1934f90e58c0db783eb
│ │ │ │ └─12711 /usr/local/bin/kube-proxy --config=/var/lib/kube-proxy/config.conf --hostname-override=test-ko-mix-master-1
│ │ │ └─7801c9d080fec92eaff6f7df7ddbb887ccd2a198c1b63b91b228acf96b882f54
│ │ │   └─12414 /pause           

其中,Pod 中的容器沒有設定記憶體和 CPU 限制或請求,則就是 BestEffort. k8s拉起的BestEffort容器 cgroup 配置放在了 /sys/fs/cgroup/cpuset/kubepods.slice/kubepods-besteffort.slice/ ,這個檔案夾下面檔案如下所示:

Docker底層基石namespace與cgroup

這裡每個 kubepods-besteffort-pod表示一個POD,每個POD裡面可以包含多個 docker 容器. 我們以第一個為例,進到裡面檔案夾如下所示:

Docker底層基石namespace與cgroup

注意,這裡的檔案大小都是 0,即内容為空,他們是用他們的存在以及 stat 屬性來表示 cgroup 的。如下所示:

# stat docker-017b45b0440376470bc8d1d1c8179541e6ca8c942b6a993c4024895d652ea2b4.scope
  File: docker-017b45b0440376470bc8d1d1c8179541e6ca8c942b6a993c4024895d652ea2b4.scope
  Size: 0           Blocks: 0          IO Block: 4096   directory
Device: 2bh/43d Inode: 1300        Links: 2
Access: (0755/drwxr-xr-x)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2022-06-06 07:21:26.043183651 +0000
Modify: 2022-06-06 07:21:12.859174044 +0000
Change: 2022-06-06 07:21:12.859174044 +0000
 Birth: -           

這裡面的Inode: 1300 就是cgroupid. 每個容器ID 對應 一個 cgroupID, 同樣的,一個 cgroupID 也對應一個 containerID. 在有些應用中,可以應用這種關系通過 cgroupID 來找到 containerID.

關于記憶體限制的幾個知識點

  • Memory Cgroup 中每一個控制組可以為一組程序限制記憶體使用量,一旦所有程序使用記憶體的總量達到限制值,預設情況下,就會觸發 OOM Killer。這樣一來,控制組裡的“某個程序”就會被殺死。

如果容器使用的實體記憶體超過了 Memory Cgroup 裡的 memory.limit_in_bytes (/sys/fs/cgroup/memory/目錄下)值,那麼容器中的程序會被 OOM Killer 殺死。

在發生 OOM 的時候,Linux 到底是根據 oom_badness() 函數來選擇被殺的程序:用系統總的可用頁面數,去乘以 OOM 校準值 oom_score_adj(/proc//oom_score_adj ),再加上程序已經使用的實體頁面數,計算出來的值越大,那麼這個程序被 OOM Kill 的幾率也就越大。

  • 每個容器的 Memory Cgroup 在統計每個控制組的記憶體使用時包含了兩部分,RSS 和 Page Cache。
RSS 是每個程序實際占用的實體記憶體,它包括了程序的代碼段記憶體,程序運作時需要的堆和棧的記憶體,這部分記憶體是程序運作所必須的。Page Cache 是程序在運作中讀寫磁盤檔案後,作為 Cache 而繼續保留在記憶體中的,它的目的是為了提高磁盤檔案的讀寫性能。有時會看到這樣一種情況:容器裡的應用有很多檔案讀寫,你會發現整個容器的記憶體使用量已經很接近 Memory Cgroup 的上限值了,但是在容器中我們接着再申請記憶體,還是可以申請出來,并且沒有發生 OOM。那是因為容器中有部分是PageCache,當容器需要更多記憶體時,釋放了PageCache,是以總大小并沒有變化。

參考文檔

  1. k8s官網
  2. 《kubernetes網絡權威指南》
  3. 《Docker容器與容器雲》中國工信出版集團

原文連結:https://www.cnblogs.com/janeysj/p/11274515.html