本文转载自米开朗基扬的博客


1. Kubernetes 内存资源限制实战

Kubernetes 对内存资源的限制实际上是通过 cgroup 来控制的,cgroup 是容器的一组用来控制内核如何运行进程的相关属性集合。针对内存、CPU 和各种设备都有对应的 cgroup。cgroup 是具有层级的,这意味着每个 cgroup 拥有一个它可以继承属性的父亲,往上一直直到系统启动时创建的 root cgroup。关于其背后的原理可以参考:深入理解Kubernetes资源限制:内存

今天我们将通过实验来探索容器在什么情况下会被 oom-killed。

1.1 实验准备

首先你需要一个 Kubernetes 集群,然后通过 kubectl 创建一个 Pod,内存限制为 123Mi。

$ kubectl run --restart=Never --rm -it --image=ubuntu --limits='memory=123Mi' -- sh
If you don't see a command prompt, try pressing enter.
root@sh:/#

重新打开一个 shell 窗口,找出刚创建的 Pod 的 uid:

$ kubectl get pods sh -o yaml | grep uid
uid: bc001ffa-68fc-11e9-92d7-5ef9efd9374c

在运行该 Pod 的节点上找出其 cgroup 的内存设置:

$ cd /sys/fs/cgroup/memory/kubepods/burstable/podbc001ffa-68fc-11e9-92d7-5ef9efd9374c

$ cat memory.limit_in_bytes
128974848

其中 memory.limit_in_bytes 表示当前限制的内存额度。128974848 正好等于 12310241024。

如果你查看一下这个 Pod 的 cgroup 目录,就会发现 Pod 中的每个容器都会在该目录下创建一个子 cgroup 目录:

$ ll /sys/fs/cgroup/memory/kubepods/burstable/podbc001ffa-68fc-11e9-92d7-5ef9efd9374c

总用量 0

drwxr-xr-x 2 root root 0 4月  28 18:46 64ae20d221399e618bbf8c15f3b5ae5050062d497971d0af5346d5532fa5c585
drwxr-xr-x 2 root root 0 4月 28 18:25 a398d3c012bb37dd9fe5fef524842a8699de931bce3a4e3753a49ef1694b33ee
-rw-r--r-- 1 root root 0 4月 28 18:46 cgroup.clone_children
--w--w--w- 1 root root 0 4月 28 18:46 cgroup.event_control
-rw-r--r-- 1 root root 0 4月 28 18:46 cgroup.procs
-rw-r--r-- 1 root root 0 4月 28 18:46 memory.failcnt
--w------- 1 root root 0 4月 28 18:46 memory.force_empty
-rw-r--r-- 1 root root 0 4月 28 18:46 memory.kmem.failcnt
-rw-r--r-- 1 root root 0 4月 28 18:24 memory.kmem.limit_in_bytes
-rw-r--r-- 1 root root 0 4月 28 18:46 memory.kmem.max_usage_in_bytes
-r--r--r-- 1 root root 0 4月 28 18:46 memory.kmem.slabinfo
-rw-r--r-- 1 root root 0 4月 28 18:46 memory.kmem.tcp.failcnt
-rw-r--r-- 1 root root 0 4月 28 18:46 memory.kmem.tcp.limit_in_bytes
-rw-r--r-- 1 root root 0 4月 28 18:46 memory.kmem.tcp.max_usage_in_bytes
-r--r--r-- 1 root root 0 4月 28 18:46 memory.kmem.tcp.usage_in_bytes
-r--r--r-- 1 root root 0 4月 28 18:46 memory.kmem.usage_in_bytes
-rw-r--r-- 1 root root 0 4月 28 18:24 memory.limit_in_bytes
-rw-r--r-- 1 root root 0 4月 28 18:46 memory.max_usage_in_bytes
-rw-r--r-- 1 root root 0 4月 28 18:46 memory.memsw.failcnt
-rw-r--r-- 1 root root 0 4月 28 18:46 memory.memsw.limit_in_bytes
-rw-r--r-- 1 root root 0 4月 28 18:46 memory.memsw.max_usage_in_bytes
-r--r--r-- 1 root root 0 4月 28 18:46 memory.memsw.usage_in_bytes
-rw-r--r-- 1 root root 0 4月 28 18:46 memory.move_charge_at_immigrate
-r--r--r-- 1 root root 0 4月 28 18:46 memory.numa_stat
-rw-r--r-- 1 root root 0 4月 28 18:46 memory.oom_control
---------- 1 root root 0 4月 28 18:46 memory.pressure_level
-rw-r--r-- 1 root root 0 4月 28 18:46 memory.soft_limit_in_bytes
-r--r--r-- 1 root root 0 4月 28 18:46 memory.stat
-rw-r--r-- 1 root root 0 4月 28 18:46 memory.swappiness
-r--r--r-- 1 root root 0 4月 28 18:46 memory.usage_in_bytes
-rw-r--r-- 1 root root 0 4月 28 18:46 memory.use_hierarchy
-rw-r--r-- 1 root root 0 4月 28 18:46 notify_on_release
-rw-r--r-- 1 root root 0 4月 28 18:46 tasks

输出结果的前两个目录其实就是 pause 容器的 pause 进程和业务容器的 bash 进程创建的两个子 cgroup 目录。可以来证实一下,我的环境使用的容器运行时是 containerd,可以通过 crictl 工具来查看,如果你使用的是 docker,方法类似。

先在运行该容器的节点上找到该业务容器的 ID:

$ crictl ps|grep "CONTAINER_RUNNING   sh"

64ae20d221399       sha256:d131e0fa2585a7efbfb187f70d648aa50e251d9d3b7031edf4730ca6154e221e   17 hours ago        CONTAINER_RUNNING   sh                           0

查看该容器的 pid:

$ crictl inspect 64ae20d221399|grep pid

    "pid": 32308,
"pid": 1
"type": "pid"

查看该进程所属的 cgroup,即进程在 cgroup 树中的路径:

$ cat /proc/32308/cgroup

...
4:memory:/kubepods/burstable/podbc001ffa-68fc-11e9-92d7-5ef9efd9374c/64ae20d221399e618bbf8c15f3b5ae5050062d497971d0af5346d5532fa5c585
...

进入该目录,查看内存限制:

$ cd /sys/fs/cgroup/memory/kubepods/burstable/podbc001ffa-68fc-11e9-92d7-5ef9efd9374c/64ae20d221399e618bbf8c15f3b5ae5050062d497971d0af5346d5532fa5c585

$ cat memory.limit_in_bytes
128974848

可以看到该 cgroup 的内存限制和父 cgroup 一样,而父 cgroup 其实就是 Pod 级别的 cgroup。

按照预想,一旦 Pod 消耗的内存资源超过这个限制,cgroup 就会杀死容器进程,我们来测试一下。

1.2 压力测试

先在容器中安装压力测试工具:

root@sh:/# apt update; apt install -y stress

在另一个 shell 窗口中执行 dmesg -Tw 命令查看系统的 Syslog。

回到第一个 shell 窗口进行压力测试,限制内存在 100M 以内:

root@sh:/# stress --vm 1 --vm-bytes 100M &
[1] 271
root@sh:/# stress: info: [271] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd

执行第二次压力测试:

root@sh:/# stress --vm 1 --vm-bytes 50M
stress: info: [273] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd
stress: FAIL: [271] (415) <-- worker 272 got signal 9
stress: WARN: [271] (417) now reaping child worker processes
stress: FAIL: [271] (451) failed run completed in 7s

可以看到系统通过发送 signal 9(SIGKILL) 信号杀死了第一次压力测试的进程(进程 ID 为 271)。

可以在另一个 shell 窗口中看到系统的 syslog 日志输出:

[Sat Apr 27 22:56:09 2019] stress invoked oom-killer: gfp_mask=0x14000c0(GFP_KERNEL), nodemask=(null), order=0, oom_score_adj=939
[Sat Apr 27 22:56:09 2019] stress cpuset=a2ed67c63e828da3849bf9f506ae2b36b4dac5b402a57f2981c9bdc07b23e672 mems_allowed=0
[Sat Apr 27 22:56:09 2019] CPU: 0 PID: 32332 Comm: stress Not tainted 4.15.0-46-generic #49-Ubuntu
[Sat Apr 27 22:56:09 2019] Hardware name: BHYVE, BIOS 1.00 03/14/2014
[Sat Apr 27 22:56:09 2019] Call Trace:
[Sat Apr 27 22:56:09 2019] dump_stack+0x63/0x8b
[Sat Apr 27 22:56:09 2019] dump_header+0x71/0x285
[Sat Apr 27 22:56:09 2019] oom_kill_process+0x220/0x440
[Sat Apr 27 22:56:09 2019] out_of_memory+0x2d1/0x4f0
[Sat Apr 27 22:56:09 2019] mem_cgroup_out_of_memory+0x4b/0x80
[Sat Apr 27 22:56:09 2019] mem_cgroup_oom_synchronize+0x2e8/0x320
[Sat Apr 27 22:56:09 2019] ? mem_cgroup_css_online+0x40/0x40
[Sat Apr 27 22:56:09 2019] pagefault_out_of_memory+0x36/0x7b
[Sat Apr 27 22:56:09 2019] mm_fault_error+0x90/0x180
[Sat Apr 27 22:56:09 2019] __do_page_fault+0x4a5/0x4d0
[Sat Apr 27 22:56:09 2019] do_page_fault+0x2e/0xe0
[Sat Apr 27 22:56:09 2019] ? page_fault+0x2f/0x50
[Sat Apr 27 22:56:09 2019] page_fault+0x45/0x50
[Sat Apr 27 22:56:09 2019] RIP: 0033:0x558182259cf0
[Sat Apr 27 22:56:09 2019] RSP: 002b:00007fff01a47940 EFLAGS: 00010206
[Sat Apr 27 22:56:09 2019] RAX: 00007fdc18cdf010 RBX: 00007fdc1763a010 RCX: 00007fdc1763a010
[Sat Apr 27 22:56:09 2019] RDX: 00000000016a5000 RSI: 0000000003201000 RDI: 0000000000000000
[Sat Apr 27 22:56:09 2019] RBP: 0000000003200000 R08: 00000000ffffffff R09: 0000000000000000
[Sat Apr 27 22:56:09 2019] R10: 0000000000000022 R11: 0000000000000246 R12: ffffffffffffffff
[Sat Apr 27 22:56:09 2019] R13: 0000000000000002 R14: fffffffffffff000 R15: 0000000000001000
[Sat Apr 27 22:56:09 2019] Task in /kubepods/burstable/podbc001ffa-68fc-11e9-92d7-5ef9efd9374c/a2ed67c63e828da3849bf9f506ae2b36b4dac5b402a57f2981c9bdc07b23e672 killed as a result of limit of /kubepods/burstable/podbc001ffa-68fc-11e9-92d7-5ef9efd9374c
[Sat Apr 27 22:56:09 2019] memory: usage 125952kB, limit 125952kB, failcnt 3632
[Sat Apr 27 22:56:09 2019] memory+swap: usage 0kB, limit 9007199254740988kB, failcnt 0
[Sat Apr 27 22:56:09 2019] kmem: usage 2352kB, limit 9007199254740988kB, failcnt 0
[Sat Apr 27 22:56:09 2019] Memory cgroup stats for /kubepods/burstable/podbc001ffa-68fc-11e9-92d7-5ef9efd9374c: cache:0KB rss:0KB rss_huge:0KB shmem:0KB mapped_file:0KB dirty:0KB writeback:0KB inactive_anon:0KB active_anon:0KB inactive_file:0KB active_file:0KB unevictable:0KB
[Sat Apr 27 22:56:09 2019] Memory cgroup stats for /kubepods/burstable/podbc001ffa-68fc-11e9-92d7-5ef9efd9374c/79fae7c2724ea1b19caa343fed8da3ea84bbe5eb370e5af8a6a94a090d9e4ac2: cache:0KB rss:48KB rss_huge:0KB shmem:0KB mapped_file:0KB dirty:0KB writeback:0KB inactive_anon:0KB active_anon:48KB inactive_file:0KB active_file:0KB unevictable:0KB
[Sat Apr 27 22:56:09 2019] Memory cgroup stats for /kubepods/burstable/podbc001ffa-68fc-11e9-92d7-5ef9efd9374c/a2ed67c63e828da3849bf9f506ae2b36b4dac5b402a57f2981c9bdc07b23e672: cache:0KB rss:123552KB rss_huge:0KB shmem:0KB mapped_file:0KB dirty:0KB writeback:0KB inactive_anon:0KB active_anon:123548KB inactive_file:0KB active_file:0KB unevictable:0KB
[Sat Apr 27 22:56:09 2019] [ pid ] uid tgid total_vm rss pgtables_bytes swapents oom_score_adj name
[Sat Apr 27 22:56:09 2019] [25160] 0 25160 256 1 28672 0 -998 pause
[Sat Apr 27 22:56:09 2019] [25218] 0 25218 4627 872 77824 0 939 bash
[Sat Apr 27 22:56:09 2019] [32307] 0 32307 2060 275 57344 0 939 stress
[Sat Apr 27 22:56:09 2019] [32308] 0 32308 27661 24953 253952 0 939 stress
[Sat Apr 27 22:56:09 2019] [32331] 0 32331 2060 304 53248 0 939 stress
[Sat Apr 27 22:56:09 2019] [32332] 0 32332 14861 5829 102400 0 939 stress
[Sat Apr 27 22:56:09 2019] Memory cgroup out of memory: Kill process 32308 (stress) score 1718 or sacrifice child
[Sat Apr 27 22:56:09 2019] Killed process 32308 (stress) total-vm:110644kB, anon-rss:99620kB, file-rss:192kB, shmem-rss:0kB
[Sat Apr 27 22:56:09 2019] oom_reaper: reaped process 32308 (stress), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB

从宿主机的视角来看,PID 为 32308 的进程被 oom-killed 了,我们需要重点关注最后一段日志输出:

对于刚刚创建的 Pod 而言,有好几个进程作为 OOM killer 的候选人,其中最重要的进程是 pause,用来为业务容器创建共享的 network namespace,其 oom_score_adj 值为 -998,可以确保不被杀死

(为什么 pause 进程的 oom_score_adj 为 -998?)

oom_score_adj 值越低就越不容易被杀死。关于 Pod 的 QoS 与 OOM 值的对应关系,可以参考:Kubernetes 资源管理概述。

除了 pause 进程外,剩下的进程 oom_score_adj 值均为 939,我们可以根据 Kubernetes 官方文档中公式来验证一下:

min(max(2, 1000 - (1000 * memoryRequestBytes) / machineMemoryCapacityBytes), 999)

进程的 oom_score_adj 值可以通过以下命令来查看:

$ cat /proc/32308/oom_score_adj

939

其中 memoryRequest 是 pod 申请的资源,memoryCapacity 是节点的内存总量。可以看到,申请的内存越多,oom 值越低,也就越不容易被杀死。

查看运行该 Pod 的节点内存总量:

$ kubectl describe nodes k3s | grep Allocatable -A 5
Allocatable:
cpu: 1
ephemeral-storage: 49255941901
hugepages-1Gi: 0
hugepages-2Mi: 0
memory: 2041888Ki

如果只设置了 limits,Kubernetes 会自动把 Pod 的 requests 设置成和 limits 一样。所以其他进程的 oom_score_adj 值为 1000–123*1024/2041888=938.32,这个值已经很接近 syslog 中输出的 939 了。

OOM killer 会根据进程的内存使用情况来计算 oom_score 的值,并根据 oom_score_adj 的值来进行微调。

进程的 oom_score 值可以通过以下命令来查看:

$ cat /proc/32308/oom_score

1718

因为业务容器内所有进程的 oom_score_adj 值都相同,所以谁的内存使用量最多,oom_score 值就越高,也就越容易被杀死。因为第一个 stress 进程使的内存使用量最多(100M),oom_score 值最高(值为 1718),所以被杀死。

1.3 总结

Kubernetes 通过 cgroup 和 OOM killer 来限制 Pod 的内存资源,在实际使用中我们需要小心区分 OS 级别的 OOM 和 Pod 级别的 OOM。

Kubernetes 内存资源限制实战的更多相关文章

  1. Kubernetes K8S之CPU和内存资源限制详解

    Kubernetes K8S之CPU和内存资源限制详解 Pod资源限制 备注:CPU单位换算:100m CPU,100 milliCPU 和 0.1 CPU 都相同:精度不能超过 1m.1000m C ...

  2. kubernetes学习资源

    参考文章: 1.kubernetes学习资源 1. <Kubernetes与云原生应用>系列之Kubernetes的系统架构与设计理念 2.[docker专业介绍的网站dockerinfo ...

  3. Kubernetes 学习22 kubernetes容器资源需求资源限制及HeapSter(翻车章节)

    一.概述 1.接下来介绍在k8s上运行pod对象时我们如何去监控我们系统级的资源指标以及业务级别的资源指标.数据如何获取和监控.在此之前先介绍一下Pod对象的资源请求和资源限制.即容器的资源需求和资源 ...

  4. 基于Kubernetes 的Cloud Native 实战 培训课程安排

    课程安排: 基于Kubernetes 的Cloud Native 实战 课程介绍: 云计算.虚拟化.容器微服务PaaS 技术已经广泛应用于新兴互联网企业(如电商平台.搜索引擎.社交平台网站.位置服务平 ...

  5. k8s入坑之路(13)kubernetes重要资源(namespace隔离 resources资源管理 label)

    Namespace --- 集群的共享与隔离 语言中namespace概念 namespace核心作用隔离 以上是隔离的代码.namespace隔离的是: 1.资源对象的隔离:Service.Depl ...

  6. kubernetes之资源限制及QOS服务质量

    1.什么是资源限制? 1.1在kubernetes集群中,为了使得系统能够稳定的运行,通常会对Pod的资源使用量进行限制.在kubernetes集群中,如果有一个程序出现异常,并且占用大量的系统资源, ...

  7. C语言 二级指针内存模型混合实战

    //二级指针内存模型混合实战 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #i ...

  8. Hadoop Yarn内存资源隔离实现原理——基于线程监控的内存隔离方案

    注:本文以hadoop-2.5.0-cdh5.3.2为例进行说明.   Hadoop Yarn的资源隔离是指为运行着不同任务的“Container”提供可独立使用的计算资源,以避免它们之间相互干扰.目 ...

  9. Linux下如何查看哪些进程占用的CPU内存资源最多

    linux下获取占用CPU资源最多的10个进程,可以使用如下命令组合: ps aux|head -1;ps aux|grep -v PID|sort -rn -k +3|head linux下获取占用 ...

  10. PHP无锁内存nosql---Yac的实战

    无锁内存nosql---Yac的实战 最近在工作使用了yac,所以比较了下Memcache和Yac的高并发读写性能测试,发现Yac要比Memcache快很多(这里没有比较Yac和Apc的性能情况, 不 ...

随机推荐

  1. 数字孪生结合GIS能给物流行业带来怎样的改变

    随着科技的不断发展和创新,数字孪生技术和地理信息系统(GIS)正日益在各个行业中发挥重要作用.其中,物流行业作为一个复杂而庞大的系统,也能从数字孪生和GIS的结合中获得许多益处和改变. 数字孪生是指通 ...

  2. NetSuite 开发日记:解决导入 CSV 与 Excel 文件时中文乱码问题

    Backgroud 在使用SheetJS库导入带有中文的CSV文件时,中文被解析为了乱码 Analysis 乱码肯定是编码问题 确定CSV的编码,可使用VS Code.记事本来查看 修改CSV文件编码 ...

  3. NetSuite 开发日记 —— 事务处理行限制

    一.创建行限制 在 NetSuite 中处理事务时,一次可访问的记录.事务处理或数据行数限制为 10,000.除非另有说明,此限制适用于所有交易类型.超过 1,000 行可能会对 Netsuite 的 ...

  4. 浅谈android的activity

    说道activity,大家可以说是熟悉的不能再熟悉,首先,先来个镇楼图, 个人觉得谷歌的这张图,比别的什么生命周期图都好;说下各个生命周期注意的: 1:onstart()时,activity可见; 2 ...

  5. Javascript实现Canvas绘图 —— 2D绘图之填充、描边及绘制矩形

    Canvas绘图的实现: <canvas>元素负责在页面中设定一个区域,通过JS动态地在这个区域中绘制图形. IE9+.Firefox1.5+.Safari2+.Opera9+.Chrom ...

  6. 子类Dog根据自己的需要,重写了Animal方法

    子类Dog根据自己的需要,重写了Animal方法 package com.guoba.method; class Animal{ public void move(){ System.out.prin ...

  7. Python——第二章:替换和切割

    strip() 用法: .strip() 是字符串方法之一,在 Python 中用于移除字符串开头和结尾的空白字符(包括空格.制表符 \t.换行符\n等).这个方法返回一个新的字符串,原始字符串本身不 ...

  8. 【OpenCV】在MacOS上使用OpenCvSharp

    前言   OpenCV是一个基于Apache2.0许可(开源)发行的跨平台计算机视觉和机器学习软件库,它具有C++,Python,Java和MATLAB接口,并支持Windows,Linux,Andr ...

  9. RocketMQ 的基本使用

    RocketMQwiki是一个分布式消息和流数据平台,具有低延迟.高性能.高可靠性.万亿级容量和灵活的可扩展性.RocketMQ是2012年阿里巴巴开源的第三代分布式消息中间件,2016年11月21日 ...

  10. Blazor入门100天 : 身份验证和授权之 OpenID 与 OAuth2

    目录: OpenID 与 OAuth2 基础知识 Blazor wasm Gitee 码云登录 Blazor wasm GitHub 登录 Blazor wasm Google 登录 Blazor w ...