<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Kubernetes 服務(wù)質(zhì)量和 OOM 詳解

          共 11623字,需瀏覽 24分鐘

           ·

          2021-08-11 01:21

          用了這么久 Kubernetes 資源,你有沒有真正花時(shí)間研究過它們?cè)诠?jié)點(diǎn)級(jí)別的真正含義?本文將深入探討節(jié)點(diǎn)內(nèi)存限制是如何影響容器的。

          K8sMeetup

          資源的不同使用方式

          眾所周知,在 Kubernetes 中,每個(gè) Pod 都有 QoS 標(biāo)記,即服務(wù)質(zhì)量。QoS 有三個(gè)不同的類:

          • Guaranteed

          • Burstable

          • BestEffort

          它們之間的區(qū)別主要體現(xiàn)在兩個(gè)指標(biāo)上:一是 CPU,二是內(nèi)存。在實(shí)際運(yùn)行過程中,Kubernetes 會(huì)根據(jù) Pod 的不同 QoS 標(biāo)記采取不同的資源調(diào)度、驅(qū)逐策略。尤其是在資源稀缺時(shí),QoS 差異會(huì)直接影響驅(qū)逐 Pod 的優(yōu)先級(jí)。
          Guaranteed vs Burstable

          根據(jù)官網(wǎng)文檔, QoS 為 Guaranteed 的 Pod 需要滿足以下條件:

          • Pod 中的每個(gè)容器必須指定內(nèi)存限制和內(nèi)存請(qǐng)求,且兩者必須相等

          • Pod 中的每個(gè)容器必須指定 CPU 限制和 CPU 請(qǐng)求,且兩者必須相等

          下面是一個(gè)示例:

              apiVersion: v1    kind: Pod    metadata:      name: limits-and-requests      namespace: test    spec:      containers:        - name: container          image: busybox          command: [ /bin/sleep, 33d ]          resources:            limits:              cpu: 100m              memory: 10Mi            requests:              cpu: 100m              memory: 10Mi

          在以上配置中,我們把所有容器的請(qǐng)求和限制(內(nèi)存和 CPU)都設(shè)置成了相等的值,所以這個(gè) Pod 的 QoS 類為 Guaranteed。

              kubectl describe pod limits-and-requests
          Name: limits-and-requests Namespace: test Priority: 0 Status: Running QoS Class: Guaranteed

          對(duì)于 Burstable,文檔的定義是:

          • Pod 不符合 Guaranteed QoS 類標(biāo)準(zhǔn)

          • Pod 中至少一個(gè)有容器具備內(nèi)存或 CPU 請(qǐng)求

          也就是說,如果我們?nèi)∠笆纠械膬?nèi)存請(qǐng)求(將其設(shè)置為 0-unlimited),那么 Kubernetes 就會(huì)把 Pod 歸為 Burstable QoS 類:

              apiVersion: v1    kind: Pod    metadata:      name: limits      namespace: test    spec:      containers:        - name: container          image: busybox          command: [ /bin/sleep, 33d ]          resources:            limits:              cpu: 100m              memory: 10Mi            requests:              cpu: 100m              memory: 0

          這意味著 Kubernetes 在將該 Pod 調(diào)度到一個(gè)節(jié)點(diǎn)時(shí),它不會(huì)考慮內(nèi)存約束,因?yàn)楦緵]有內(nèi)存保留。我們可以驗(yàn)證 Pod 目前的 QoS 是不是 Burstable:

              kubectl describe pod limits
          Name: limits Namespace: test Priority: 0 Status: Running QoS Class: Burstable

          那么 Guaranteed 和 Burstable 在容器運(yùn)行時(shí)級(jí)別上有何不同?我們可以看一下為這些容器生成的 OCI 規(guī)范之間的差異。

          示例使用了 microk8s(它用了 containerd 實(shí)現(xiàn)的容器運(yùn)行時(shí)接口 CRI),所以我們可以通過 ctr(containerd 的 CLI 工具)來收集規(guī)范:

              # use `kubectl` to get the id of the container    #    function container_id() {            local name=$1
          kubectl get pod limits \ -o jsonpath={.status.containerStatuses[0].containerID} \ | cut -d '/' -f3 }
          # use `ctr` to get the oci spec # function oci_spec () { local id=$1
          microk8s.ctr container info $id | jq '.Spec' }

          spec $(container_id "limits-and-requests") > /tmp/guaranteed spec $(container_id "limits") > /tmp/burstable
          git diff --no-index /tmp/guaranteed /tmp/burstable

          可以看到,差異非常大:

              diff --git a/guaranteed.json b/burstable.json    index 046d16f..da7596a 100644    --- a/guaranteed.json    +++ b/burstable.json    @@ -14,15 +14,15 @@         ],         "cwd": "/",         "capabilities": {    @@ -92,7 +92,7 @@           ]         },    -    "oomScoreAdj": -998    +    "oomScoreAdj": 999       },
          "linux": { "resources": { "memory": { "limit": 10485760 }, @@ -247,25 +247,25 @@ "period": 100000 } }, - "cgroupsPath": "/kubepods/pod477062c0-1c.../05bef2...", + "cgroupsPath": "/kubepods/burstable/podfbb122d5-ca/59...",

          首先,cgroupsPath 完全不同:是 /kubepods/burstable,而不是 kubepods。

          其次,初始進(jìn)程的 oomScoreAdj 是根據(jù)執(zhí)行的計(jì)算進(jìn)行配置的,以便在 OOM 時(shí)降低優(yōu)先級(jí)。

          這里我們先記住這些變化,之后再分析具體原因。
          K8sMeetup

          Guaranteed vs BestEffort

          對(duì)于 QoS 類為 BestEffort 的 Pod,Pod 中的容器不得設(shè)置任何內(nèi)存、CPU 限制或請(qǐng)求。

          根據(jù)文檔定義,我們可以將請(qǐng)求和限制都設(shè)置為 0:

              apiVersion: v1    kind: Pod    metadata:      name: nothing      namespace: test    spec:      containers:        - name: container          image: busybox          command: [ /bin/sleep, 33d ]          resources:            limits:              cpu: 0              memory: 0            requests:              cpu: 0              memory: 0

          上述設(shè)置意味著在進(jìn)行調(diào)度時(shí),不應(yīng)對(duì) Pod 設(shè)置任何內(nèi)存和 CPU 約束,在運(yùn)行容器時(shí),也不對(duì)其施加任何限制。我們可以驗(yàn)證該 Pod 目前的 QoS 是不是 BestEffort:

              kubectl describe pod nothing
          Name: nothing Namespace: test Priority: 0 Status: Running QoS Class: BestEffort

          注:資源只是 Pod 是否能運(yùn)行的一個(gè)檢查項(xiàng),QoS 類為 BestEffort 的 Pod 并不是始終可調(diào)度的。

          那么 BestEffort 和 Guaranteed 又有什么不同呢?

          diff --git a/guaranteed.json b/besteffort.jsonindex 046d16f..bd16d6b 100644--- a/guaranteed.json+++ b/besteffort.json@@ -92,7 +92,7 @@       ]     },-    "oomScoreAdj": -998+    "oomScoreAdj": 1000    },    "linux": {     "resources": {    @@ -239,33 +239,33 @@         }       ],      "memory": {-        "limit": 10485760+        "limit": 0       },       "cpu": {-        "shares": 102,-        "quota": 10000,+        "shares": 2,+        "quota": 0,         "period": 100000        }     },-    "cgroupsPath": "/kubepods/pod477062c0-1c/05bef2cca07a...",+    "cgroupsPath": "/kubepods/besteffort/pod31b936/1435...",

          如上所示,CPU 資源調(diào)用和限制都是非零的,因?yàn)槲覀儧]有對(duì)它做任何設(shè)置(設(shè)置就成了 Burstable)。但這里我們要關(guān)注的并不是 CPU 資源被用了多少,而是在“unlimited CPU”的情況下,Pod 在使用 CPU 時(shí)幾乎沒有得到任何優(yōu)先級(jí)。

          此外,容器被放在了不同的 cgroup 路徑中,oomScoreAdj 也被更改了。要了解這些細(xì)節(jié)背后的原因,我們得回顧一下 Linux 中的 OOM 是怎么發(fā)生的。

          oom score

          在遇到較高內(nèi)存使用壓力時(shí),Linux 內(nèi)核會(huì)殺掉一些不太重要的進(jìn)程,騰出空間保障系統(tǒng)正常運(yùn)行。它會(huì)給每個(gè)進(jìn)程(/proc/$ pid / oom_score)分配一個(gè)得分(oom_score),分?jǐn)?shù)越高,被 OOM 的概率就越大。

          這個(gè)參數(shù)本身只反映該進(jìn)程的可用資源在系統(tǒng)中所占的百分比,并沒有“該進(jìn)程有多重要”的概念。例如,假設(shè)有一個(gè)雙進(jìn)程系統(tǒng),其中一個(gè)進(jìn)程(PROC1)需要占用系統(tǒng)中 95% 的內(nèi)存,其他進(jìn)程占用剩余內(nèi)存:

                      MEM
          PROC1 95% PROC2 1% PROC3 1%

          當(dāng)我們檢查分配給每個(gè)進(jìn)程的 oom score 時(shí),我們會(huì)發(fā)現(xiàn) PROC1 的分?jǐn)?shù)相比其他進(jìn)程會(huì)非常高:

                      MEM     OOM_SCORE
          PROC1 95% 907 PROC2 1% 9 PROC3 1% 9

          如果我們現(xiàn)在創(chuàng)建一個(gè) PROC4,給它分配 5% 的內(nèi)存,這時(shí)系統(tǒng)就會(huì)觸發(fā) OOM,殺死 PROC1(而不是 PROC4)

              [951799.046579] Out of memory:             Killed process 18163 (mem-alloc)             total-vm:14850668kB,             anon-rss:14133628kB,             file-rss:4kB,             shmem-rss:0kB    [951799.441402] oom_reaper:             reaped process 18163 (mem-alloc),             now anon-rss:0kB,             file-rss:0kB,             shmem-rss:0kB
          (that's the one we're calling PROC1 here)

          在大多數(shù)情況下,PROC1 肯定是系統(tǒng)里最重要的,無論內(nèi)存壓力有多大,我們都不希望它被殺死,因此這時(shí)就需要對(duì) OOM 的分?jǐn)?shù)進(jìn)行調(diào)整。

          oomScoreAdj

          手動(dòng)調(diào)整 oom_score 可以通過 oom_score_adj 來實(shí)現(xiàn),它允許開發(fā)者在內(nèi)存不足的情況下殺死指定進(jìn)程。

          具體做法是把可調(diào)參數(shù) /proc/pid/oom_score_adj 直接添加到 badness() 分?jǐn)?shù)中,范圍從 -1000(OOM_SCORE_ADJ_MIN)到 +1000(OOM_SCORE_ADJ_MAX),使某些任務(wù)總是會(huì)被考慮 OOM,某些任務(wù)則永遠(yuǎn)不會(huì)被 OOM。

          如果我們調(diào)整了 PROC1 的 oom_score_adj (echo "-1000" > /proc/$(pidof PROC1)/oom_score_adj),系統(tǒng)在 OOM 時(shí)就會(huì)先殺死其他進(jìn)程。

                      MEM     OOM_SCORE       OOM_SCORE_ADJ
          PROC1 95% 0 -1000 PROC2 1% 9 0 PROC3 1% 9 0

          放在 Kubernetes 的例子里,它決定的就是系統(tǒng) OOM 時(shí) Pod 被殺死的優(yōu)先級(jí):

          • Guaranteed 具有高優(yōu)先級(jí)

          • BestEffort 具有極低優(yōu)先級(jí)

          不同的 cgroup trees

          在之前提到的不同中,比較特別的是為 Guaranteed 和 BestEffort 形成的兩個(gè)不同的 cgroup trees。

              "cgroupsPath": "/kubepods/pod477062c0-1c.../05bef2...",    "cgroupsPath": "/kubepods/besteffort/pod31b936/1435...",    "cgroupsPath": "/kubepods/burstable/podfbb122d5-ca/59...",

          事實(shí)證明,對(duì)于這些不同的 tree,kubelet 能動(dòng)態(tài)調(diào)整 CPU 配額及內(nèi)存等不可壓縮資源,向 Guaranteed 類 Pod 提供所需資源,并允許開發(fā)者把資源傾向更重要的 Pod,讓 BestEffort 類 Pod 使用 Guaranteed 類和 Burstable 類占用后的剩余資源。

          這允許我們把資源細(xì)粒度分配給一組 Pod,同時(shí)能夠“將其余部分”分配給整個(gè)類。

              /kubepods            /pod123 (guaranteed)    cpu and mem limits well specified                            cpu.shares              = sum(resources.requests.cpu)                            cpu.cfs_quota_us        = sum(resources.limits.cpu)                            memory.limit_in_bytes   = sum(resources.limits.memory)
          /burstable all - (guaranteed + reserved) cpu.shares = max(sum(burstable pods cpu requests, 2)) memory.limit_in_bytes = allocatable - sum(requests guaranteed)
          /pod789 cpu.shares = sum(resources.requests.cpu) if all containers set cpu: cpu.cfs_quota_us if all containers set mem: memory.limit_in_bytes
          /besteffort all - burstable cpu.shares = 2 memory.limit_in_bytes = allocatable - (sum(requests guaranteed) + sum(requests burstable)) /pod999 cpu.shares = 2


          K8sMeetup

          per-cgroup 內(nèi)存統(tǒng)計(jì)

          為了根據(jù) memory.limit_in_bytes 的設(shè)置強(qiáng)制執(zhí)行內(nèi)存限制,內(nèi)核必須跟蹤資源分配情況,并以此判斷進(jìn)程是否可以繼續(xù)進(jìn)行。

          關(guān)于這一點(diǎn),我們可以舉一個(gè)小例子:

              #include <signal.h>    #include <stddef.h>    #include <stdio.h>    #include <stdlib.h>    #include <string.h>    #include <unistd.h>
          static const ptrdiff_t len = 1 << 25; // 32 MiB static const ptrdiff_t incr = 1 << 12; // 4KiB
          void handle_sig(int sig) { }
          void wait_a_bit(char* msg) { if (signal(SIGINT, handle_sig) == SIG_ERR) { perror("signal"); exit(1); }
          printf("wait: %s\n", msg); pause(); }
          int main(void) { char *start, *end; void* pb;
          pb = sbrk(0); if (pb == (void*)-1) { perror("sbrk"); return 1; }
          start = (char*)pb; end = start + len;
          wait_a_bit("next: brk");
          // "allocate" mem by increasing the program break. // if (!~brk(end)) { perror("brk"); return 1; }
          wait_a_bit("next: memset");
          // "touch" the memory so that we get it really utilized - at this point, // we should see the faults taking place, and both RSS and active anon // going up. // while (start < end) { memset(start, 123, incr); start += incr; }
          wait_a_bit("next: exit");
          return 0; }

          將進(jìn)程置于內(nèi)存 cgroup 下,然后跟蹤與 charging 相關(guān)的函數(shù)(mem_cgroup_try_charge),可以看到,charging 只在我們?cè)噲D訪問剛剛映射的新區(qū)域時(shí)發(fā)生。

              # leverage iovisor/bcc's `trace`    #    ./trace 'mem_cgroup_try_charge' -U -K -p $(pidof sample)
          PID TID COMM FUNC 18223 18223 sample mem_cgroup_try_charge
          mem_cgroup_try_charge+0x1 [kernel] do_anonymous_page+0x139 [kernel] __handle_mm_fault+0x760 [kernel] handle_mm_fault+0xca [kernel] do_user_addr_fault+0x1f9 [kernel] __do_page_fault+0x58 [kernel] do_page_fault+0x2c [kernel] page_fault+0x34 [kernel] main+0x57 [sample]


          K8sMeetup

          per-cgroup oom

          為了觀察 cgroup 的 OOM,我們可以對(duì)創(chuàng)建的 cgroup 設(shè)置一個(gè)限制,在示例中,就是把 memory.limit_in_bytes 設(shè)置得比 32Mib 小。

              echo "4M" > /sys/fs/cgroup/memory/test/memory.limit_in_bytes

          跟蹤 mem_cgroup_out_of_memory 函數(shù),我們可以找出所有這些情況是如何發(fā)生的:

              mem_cgroup_out_of_memory() {      out_of_memory() {        out_of_memory.part.0() {          mem_cgroup_get_max();          mem_cgroup_scan_tasks() {            mem_cgroup_iter() { }            css_task_iter_start() { }            css_task_iter_next() { }            oom_evaluate_task() {              oom_badness.part.0() { }            }            css_task_iter_next() { }            oom_evaluate_task() {              oom_badness.part.0() { }            }            css_task_iter_next() { }            css_task_iter_end() { }          }          oom_kill_process() { }        }      }    }


          K8sMeetup

          kubelet 的軟驅(qū)逐

          除了從內(nèi)核角度發(fā)生的驅(qū)逐之外,kubelet 也可以強(qiáng)制執(zhí)行 Pod 驅(qū)逐,這是基于 kubelet 級(jí)別的閾值配置的。

          Kubelet 可以主動(dòng)監(jiān)視并防止計(jì)算資源匱乏。當(dāng)資源不足時(shí),kubelet 可以通過主動(dòng)使一個(gè)或多個(gè) Pod 發(fā)生故障來回收其占用的資源。
          當(dāng)內(nèi)存消耗超過內(nèi)部配置的閾值時(shí),Kubernetes 會(huì)強(qiáng)制重新啟動(dòng) Pod。
          原文:https://ops.tips/notes/kubelet-qos-and-oom/


          K8S 進(jìn)階訓(xùn)練營(yíng)


           點(diǎn)擊屏末  | 即刻學(xué)習(xí)


          掃描二維碼獲取

          更多云原生知識(shí)





          k8s 技術(shù)圈

          瀏覽 45
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  肏屄视频免费在线观看 | 亚洲婷婷在线视频 | 亚洲成人视频网 | 夜夜躁狠狠躁日日躁麻豆护士 | 欧美日韩一区在线观看视频 |