前言

在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. 指定HTML标签属性 |Specifying HTML Attributes| 在视图中生成输出URL |高级路由特性 | 精通ASP-NET-MVC-5-弗瑞曼

    结果呢: <a class="myCSSClass" href="/" id="myAnchorID">This is an o ...

  2. Dart语言学习(十二) Dart面向对象

    Dart作为一种高级语言,支持面向对象的很多特性,并且支持基于mixin的继承方式. 基于mixin的继承方式是指:一个类可以继承自多个父类,相当于其他语言里的多继承. 所有的类都有同一个基类Obje ...

  3. Dockers 部署 MongoDB + mongo-express

    1. 拉取 Mongo 镜像 docker pull mongo: 2.  运行镜像 docker run -d --name mongodb --volume /usr/local/mongodat ...

  4. 量子搜索算法 Grover search

    问题定义: Problem: \(f: \{ 0,1,2,3,--,N-1 \} \rightarrow \{0,1\}\) 找到 \(f(x)=1\) 的x 解法 经典解法: 经典解法很简单,就是把 ...

  5. ros之参数的使用与编程方法

    参数模型 ROS Master (Parameter Server) /robot_name: "my_robot"        ----{Node A   Node B} /r ...

  6. CCF_ 201409-3_字符串匹配

    水. #include<cstdio> #include<iostream> #include<cstring> using namespace std; int ...

  7. 快速了解Lambda表达式-Java

    目录 lambda表达式 前言 简介 简单入门 用法 好处 总结 lambda表达式 前言 最近因为疫情,也不能正常返校什么的,希望大家都能好好的,希望武汉加油,中国加油,在家也看了很多视频,学了一点 ...

  8. Codeforces 1065C Make It Equal (差分+贪心)

    题意:n个塔,第i个塔由$h_i$个cube组成,每次可以切去某高度h以上的最多k个cube,问你最少切多少次,可以让所有塔高度相等 k>=n, n<=2e5 思路:差分统计每个高度i有的 ...

  9. Shiro过滤器

    Shiro内置过滤器 anon.authBasic.authc.user.logout perms.roles.ssl.port spring.xml <bean id="shiroF ...

  10. Golang设置https访问,以及http如何重定向到https

    设置https访问: 原始代码为http监听: func main() { server := &http.Server{ Addr: ":8080", ... } go ...