前言

在kubernetes环境下,无论集群再大,对应的集群资源(cpu、memory、storage)总是有上限的。而默认情况下,我们启动的pod、以及pod中运行的容器,对应的资源是不加限制的。理论上每个pod,或者是pod内运行的容器,可以无限使用资源直到把所在节点上的资源耗尽,造成节点崩溃,进而影响集群的稳定性。为了防止这种情形出现,kubernetes给我们提供了相应的机制来限制单个pod可以使用的资源,主要的对象有如下两个

  • LimitRanger
  • ResourceQuota

LimitRanger

  1. LimitRanger是一种用来限制在特定命名空间下单个pod可以消耗资源的策略,主要防止单个pod将所在命名空间的资源全部抢占完的情况,通过以下几个方面进行配置
  • 限定单个pod或容器可消耗的计算资源(cpu、memory)在一定范围内(最大、最小)
  • 限定单个PersistentVolumeClaim可申请的storage在一定的范围内
  • 限定对特定资源的需求值(最小)和限定值(最大)的比例
  • 自动对处于同一命名空间下,没有追加资源限定的pod进行限定
  1. 使用流程如下

    1. 集群管理员创建在特定namespace下创建LimitRange
    2. 使用者在这个namespace下创建pod、容器、PersistentVolumeClaims对象
    3. LimitRanger准入控制器检查待创建对象的信息,如果没有资源配置,就会用默认值来装配这些对象
    4. 如果有资源配置,就检查这些配置是否和LimitRanger定义的规范相冲突,如果冲突就拒绝创建相应的对象
    5. 如果namespace下定义了ResourceQuota,LimitRanger没有定义默认的资源限额,待创建对象也没有资源配置,则拒绝创建

服务质量(QoS)

根据创建的pod或container是否指定资源request和limit值,kubernettes将pod划分成了3个等级

  • Guaranteed 当pod中的所有容器对所有资源都定义了Limits和Requests,并且所有容器的Limits值和Requests值全部相等
  • BestEffort pod中的所有容器都没有定义资源配置
  • Burstable 当一个pod不是Guaranteed也不是BestEffort时,该pod的QoS就是Burstable。例如 Pod中的一部分容器定义了Request值或者Limit值,或者都定义了,但是值不相等

kubernets在出现资源竞争时,会优先保证Guaranteed级别的pod正常运行,其次是Burstable,最后是BestEffort

ResourceQuota

ResourceQuota(资源配额)限定的是特定namespace下所有对象可用的资源总量,另外还能限定各种对象的创建数量

工作流程如下

  1. 因为ResourceQuota对整个namespace限定,所以不同的组工作在不同的namespace下方能相互不干扰
  2. 集群管理员对每个namespace创建相应的ResourceQuota
  3. 用户在自己namespace下创建pod、容器等对象
  4. 如果要创建对象申请的资源和ResourceQuota有冲突,则系统拒绝创建

实际需求

通过LimitRanger或者创建pod时限定对应的Resource信息,kubernetes在因为某些容器自身缺陷导致pod一直抢占系统资源时,自动帮我们发现问题,并停掉这些有问题pod,如果pod被controller控制,还会帮我们重新调度这个pod,使系统维持可用。

例如我们有如下代码块

@RequestMapping("/oom")
public String oom() {
log.info("request to oom");
PersonRepo pp = new PersonRepo();
pp.autoCreatePerson(); return "OK";
} public static class PersonRepo { private List<Person> repo = new ArrayList<>(); public void autoCreatePerson() {
for (long l = 0; ; l++) {
repo.add(new Person(l));
}
}
}

当调用oom方法时,由于程序编码bug,导致应用程序会持续的创建Person对象,放在内存中,如果不加限制,这个程序会不断增加内存,直到节点上分给kubernets的可用内存全部被消耗尽。

如果启动时加上resource信息

...
image: 192.168.0.107/k8s/resource-quotas-oom:0.0.4
ports:
- containerPort: 8080
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "582Mi"
cpu: "500m"
...

则当此容器消耗的内存达到582Mi时,k8s会直接kill掉这个容器,给出提示信息OOMKilled,并把pod重启(此pod中只有这一个容器)。

这虽然保证了系统的可用性,可是从这个信息中我们没有办法分析出到底是代码的什么地方出现了问题,正常情况下,我们启动java应用,都会在启动命令中加上如下jvm参数

 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/src/oomdump

告诉jvm,当系统出现oom时,给我们在特定的路径下生成dump文件,之后我们根据这个dump文件分析具体出现错误的代码。如果我们想在容器被k8s kill前生成dump文件,我们还需要设置jvm的可用内存大小

-server -Xms512m -Xmx512m

并且如果jvm的最大内存(Xmx)值和k8s resource.limits.memory值一样或着小的话,实际运行时在jvm 出现oom之前,会先触发k8s的OOMKill,还是无法生成dump,所以要想生成dump文件,resource.limits.memory的值要比Xmx的值大一些(50M~100M)。

可是这样设置后,因为还没有达到resource.limits.memory这个值,虽然应用程序出现了oom,但是容器不会被k8s集群停掉,pod也不会重新启动,造成系统响应变慢(容器的内存一直被占着,访问其他请求也会变慢,并出现oom),这也不是我们想要的结果,所以在jvm中再追加一个参数,出现oom后让应用直接停止

-XX:+ExitOnOutOfMemoryError 

这样,出现oom后,应用会先生成dump,之后会终止,系统日志信息


2020-02-24 10:25:15.832 INFO 6 --- [nio-8080-exec-2] c.falcon.resource.quotas.oom.RunOom : request to oom
java.lang.OutOfMemoryError: Java heap space
Dumping heap to /usr/src/oomdump ...
Heap dump file created [625772870 bytes in 25.588 secs]
Terminating due to java.lang.OutOfMemoryError: Java heap space

从日志可以看到,系统先生成dump,然后停止掉自己

pod 变化信息

root@master:~# kubectl get pod -w
NAME READY STATUS RESTARTS AGE
jenkins-68d8b54c45-7pdhs 1/1 Running 0 2d5h
oom-deployment-64664f9454-kp65n 1/1 Running 6 26h
oom-deployment-64664f9454-kp65n 0/1 Error 6 26h
oom-deployment-64664f9454-kp65n 1/1 Running 7 26h

可以看到pod出现error,然后又自动恢复成Running状态,k8s会自动将pod中停掉的容器再启动起来,看下容器中/usr/src/oomdump目录

root@master:~# kubectl exec -it oom-deployment-64664f9454-kp65n ls /usr/src
resource-quotas-oom-0.0.1-SNAPSHOT.jar
start.sh

怎么没有我们生成的dump文件,因为容器被重新启动了,工作目录也成了新容器的工作目录,不包含已停掉的容器文件。

查看完整pod信息,有如下片段

root@master:~# kubectl get pod oom-deployment-64664f9454-kp65n -o yaml

...
containerStatuses:
- containerID: docker://70daa47c0f45c9d014e0f70b80a41ca6bc9fd219ca957cc035ee2a049d46166b
image: 192.168.0.107/k8s/resource-quotas-oom:0.0.4
imageID: docker-pullable://192.168.0.107/k8s/resource-quotas-oom@sha256:3ab65f8b5d17182abb0ca0ccc164e5f40bb1d0fcf14006c06af795d0419daa58
lastState:
terminated:
containerID: docker://95776b99ec7a27c03327e45f9e1fc1d3f058c9b814400eabafa9606534c6bc2a
exitCode: 3 ...

有一个已经停掉的容器,对应的容器ID:95776b99ec7a27c03327e45f9e1fc1d3f058c9b814400eabafa9606534c6bc2a

到pod所在节点上,通过以下命令获取对应的dump

root@slave:/opt/k8s/work# docker ps -a |grep 95776b99ec7a
95776b99ec7a ec0a1d08eb22 "/bin/sh -c ./start.…" 3 hours ago Exited (3) 13 minutes ago k8s_oom_oom-deployment-64664f9454-kp65n_default_9a47dc18-14bf-4d77-9ed4-5ac2c11d2bf7_6
root@slave:/opt/k8s/work# docker cp 95776b99ec7a:/usr/src/oomdump .
root@slave:/opt/k8s/work# ls -alh oomdump
-rw------- 1 root root 597M 2月 24 18:26 oomdump
  • 其中95776b99ec7a是通过kubectl获取到的容器ID的前12位(docker 默认显示12位ID)

执行过程中发现,如果多次系统出现oom,k8s只会帮我们保留最近停掉的一个容器,再往直前的停掉的容器会被自动回收掉。发现这是因为kubelet有一个垃圾回收策略,这个参数可以通过kubelet的配置参数 maximum-dead-containers-per-container来设定,默认值是1,所以只会帮我们保留一个,详情可参考Configuring kubelet Garbage Collection

疑惑

虽然通过jvm参数的设定,实现了程序oom时生成dump,之后再重启,可是这样设置后,其实k8s提供的resources信息就没有起到应有的作用了。不知道是否是用错了方法还是k8s还有别的机制,在被OOMKill之前再执行个什么动作(preStop hook),等深入研究后看有没有更优雅的实现方式

kubernetes 资源管理的更多相关文章

  1. Kubernetes资源管理

    目录贴:Kubernetes学习系列 1.资源模型 虛拟化技术是云计算平台的基础,其目标是对计算资源进行整合或划分,这是云计算管理平台中的关键技术.虚拟化技术为云计算管理乎台的资源管理提供了资源调配上 ...

  2. [Kubernetes]资源模型与资源管理

    作为 Kubernetes 的资源管理与调度部分的基础,需要从它的资源模型说起. 资源管理模型的设计 我们知道,在 Kubernetes 里面, Pod 是最小的原子调度单位,这就意味着,所有和调度和 ...

  3. Kubernetes学习系列

    这段时间项目组内想要引入Kubernetes,作为第二代容器调度引擎,故最近在系统的学习Kubernetes.整理了一些学习笔记,心得,放到博客中,一来记录自己的学习经过,二来看能否帮到有需要的同学. ...

  4. Kubernetes 资源对象

    概述 我将它们简单的分类为以下几种资源对象: 类别 名称 资源对象 Pod.ReplicaSet.ReplicationController.Deployment.StatefulSet.Daemon ...

  5. Kubernetes 多集群在开源项目 KubeSphere 的应用

    Kubernetes 多集群使用场景 随着容器的普及和 Kubernetes 的日渐成熟,企业内部运行多个 Kubernetes 集群已变得颇为常见.概括起来,多个集群的使用场景主要有以下几种. 多集 ...

  6. Apache Spark 3.0 将内置支持 GPU 调度

    如今大数据和机器学习已经有了很大的结合,在机器学习里面,因为计算迭代的时间可能会很长,开发人员一般会选择使用 GPU.FPGA 或 TPU 来加速计算.在 Apache Hadoop 3.1 版本里面 ...

  7. Spark(一)Spark简介

    一.官网介绍 1 什么是Spark 官网地址:http://spark.apache.org/ Apache Spark 是专为大规模数据处理而设计的快速通用的计算引擎.Spark是UC Berkel ...

  8. Kubernetes-运维指南

    Node隔离与恢复 cat unschedule_node.yaml apiVersion: kind: Node metadata: name: k8s-node-1 labels: kuberne ...

  9. Apache Spark 3.0 预览版正式发布,多项重大功能发布

    2019年11月08日 数砖的 Xingbo Jiang 大佬给社区发了一封邮件,宣布 Apache Spark 3.0 预览版正式发布,这个版本主要是为了对即将发布的 Apache Spark 3. ...

随机推荐

  1. Linux vi & bash使用笔记

    f 1.vi入门级命令 打开或新建 vi filename 有三种模式,刚开始进去的是一般模式,在一般模式下按 I 之后进入编辑模式 ,按Esc进入命令模式 在命令模式下按 :wq 保存退出 多个窗口 ...

  2. ROS机器人话题之自定义消息

    ROS提供了丰富的内建消息,std_msgs包定义了一些基本的类型. 具体例子 首先定义一个消息类型的文件叫做Complex 例Complex.msg float32 real float32 ima ...

  3. python中更人性化的一个单元测试框架:nose2

    如果你学过 python 进行自动化测试,你一定使用过 unittest.今天我们要讲的 nose2 是一个高级版本的 unittest.他比 unittest 更容易理解,用起来也更加方便一些. 快 ...

  4. VS debug下为什么多此一举jmp函数地址?

    VS debug下为什么call 函数后,会jmp函数地址?多此一举? http://blog.csdn.net/viper/article/details/6332934 在写跑在main之前的时候 ...

  5. GORM CRUD指南

    CRUD通常指数据库的增删改查操作,本文详细介绍了如何使用GORM实现创建.查询.更新和删除操作. CRUD CRUD通常指数据库的增删改查操作,本文详细介绍了如何使用GORM实现创建.查询.更新和删 ...

  6. Codeforces_723

    A.取中间那个点即可. #include<bits/stdc++.h> using namespace std; ]; int main() { ios::sync_with_stdio( ...

  7. HDU_5094_dfs

    http://acm.hdu.edu.cn/showproblem.php?pid=5094 bfs,vis[x][y][z],z表示钥匙的状态,用二进制来表示,key[x][y]储存当前位置钥匙的二 ...

  8. JAVA编程思想——分析阅读

    需要源码.JDK1.6 .编码风格参考阿里java规约 7/12开始 有点意识到自己喜欢理论大而泛的模糊知识的学习,而不喜欢实践和细节的打磨,是因为粗心浮躁导致的么? cron表达式使用 设计能力.领 ...

  9. Java中类的关系

    在java里类的关系大致分为三种, 1.继承(a is b):继承extends,实现implement 2.包含(a has b):组合>聚合>关联.关系亲密度越来越小,一个类在另一个类 ...

  10. Xamarin.Forms 二维码扫描实践

    开发环境: Visual Studio 2019 版本 16.4.5 公用平台nuget ZXing.Net.Mobile.Forms 2.4.1 Plugin.Permissions 5.0.0-b ...