Kubernetes集群管理
接下来,需要为这两个工作组分别定义一个 Context,即运行环 境。这个运行环境将属于某个特定的命名空间
通过kubectl config set-context命令定义Context,并将Context置于之 前创建的命名空间中
使用kubectl config use-context <context_name>命令设置当前运行环 境。
下面的命令将把当前运行环境设置为ctx-dev
kubectl config use-context ctx-dev
为了避免系统挂掉,该Node会选择“清理”某些Pod来释放资源,此 时每个Pod都可能成为牺牲品。但有些Pod担负着更重要的职责,比其他 Pod更重要,比如与数据存储相关的、与登录相关的、与查询余额相关 的,即使系统资源严重不足,也需要保障这些Pod的存活,Kubernetes中 该保障机制的核心如下
◎ 通过资源限额来确保不同的Pod只能占用指定的资源
◎ 允许集群的资源被超额分配,以提高集群的资源利用率。
◎ 为Pod划分等级,确保不同等级的Pod有不同的服务质量 (QoS),资源不足时,低等级的Pod会被清理,以确保高等级的Pod稳 定运行。
1.详解Requests和Limits参数.以CPU为例,图10.3显示了未设置Limits和设置了Requests、Limits 的CPU使用率的区别
尽管Requests和Limits只能被设置到容器上,但是设置Pod级别的 Requests和Limits能大大提高管理Pod的便利性和灵活性,因此在 Kubernetes中提供了对Pod级别的Requests和Limits的配置对于CPU和 内存而言,Pod的Requests或Limits是指该Pod中所有容器的Requests或 Limits的总和(对于Pod中没有设置Requests或Limits的容器,该项的值 被当作0或者按照集群配置的默认值来计算)。下面对CPU和内存这两 种计算资源的特点进行说明
基于Requests和Limits的Pod调度机制
当一个Pod创建成功时,Kubernetes调度器(Scheduler)会为该Pod 选择一个节点来执行。对于每种计算资源(CPU和Memory)而言,每 个节点都有一个能用于运行Pod的最大容量值。调度器在调度时,首先 要确保调度后该节点上所有Pod的CPU和内存的Requests总和,不超过该 节点能提供给Pod使用的CPU和Memory的最大容量值
这里需要注意:可能某节点上的实际资源使用量非常低,但是已运 行Pod配置的Requests值的总和非常高,再加上需要调度的Pod的 Requests值,会超过该节点提供给Pod的资源容量上限,这时Kubernetes 仍然不会将Pod调度到该节点上。如果Kubernetes将Pod调度到该节点 上,之后该节点上运行的Pod又面临服务峰值等情况,就可能导致Pod资 源短缺。
kubelet在启动Pod的某个容器时,会将容器的Requests和Limits值转化为相应的容器启动参数传递给容器执行器(Docker或者rkt)
如果容器的执行环境是Docker,那么容器的如下4个参数是这样传 递给Docker的
1)spec.container[].resources.requests.cpu
这个参数会转化为core数(比如配置的100m会转化为0.1),然后 乘以1024,再将这个结果作为--cpu-shares参数的值传递给docker run命 令。在docker run命令中,--cpu-share参数是一个相对权重值(Relative Weight),这个相对权重值会决定Docker在资源竞争时分配给容器的资 源比例
这里需要区分清楚的是:这个参数对于Kubernetes而言是绝对值, 主要用于Kubernetes调度和管理;同时Kubernetes会将这个参数的值传递 给docker run的--cpu-shares参数。--cpu-shares参数对于Docker而言是相对 值,主要用于资源分配比例
2)spec.container[].resources.limits.cpu
这个参数会转化为millicore数(比如配置的1被转化为1000,而配置 的100m被转化为100),将此值乘以100000,再除以1000,然后将结果 值作为--cpu-quota参数的值传递给docker run命令。docker run命令中另 外一个参数--cpu-period默认被设置为100000,表示Docker重新计量和分 配CPU的使用时间间隔为100000μs(100ms)
Docker的--cpu-quota参数和--cpu-period参数一起配合完成对容器 CPU的使用限制:比如Kubernetes中配置容器的CPU Limits为0.1,那么 计算后--cpu-quota为10000,而--cpu-period为100000,这意味着Docker在100ms内最多给该容器分配10ms×core的计算资源用量,10/100=0.1 core 的结果与Kubernetes配置的意义是一致的
注意:如果kubelet的启动参数--cpu-cfs-quota被设置为true,那么 kubelet会强制要求所有Pod都必须配置CPU Limits(如果Pod没有配置, 则集群提供了默认配置也可以)。从Kubernetes 1.2版本开始,这个-- cpu-cfs-quota启动参数的默认值就是true
计算资源相关常见问题分析
(1)Pod状态为Pending,错误信息为FailedScheduling。如果Kubernetes调度器在集群中找不到合适的节点来运行Pod,那么这个Pod 会一直处于未调度状态,直到调度器找到合适的节点为止。每次调度器 尝试调度失败时,Kubernetes都会产生一个事件
对大内存页(Huge Page)资源的支持
在现代操作系统中,内存是以Page(页,有时也可以称之为 Block)为单位进行管理的,而不以字节为单位,包括内存的分配和回 收都基于Page。典型的Page大小为4KB,因此用户进程申请1MB内存就需要操作系统分配256个Page,而1GB内存对应26万多个Page!
为了实现快速内存寻址,CPU内部以硬件方式实现了一个高性能的 内存地址映射的缓存表——TLB(Translation Lookaside Buffer),用来 保存逻辑内存地址与物理内存的对应关系。若目标地址的内存页物理地 址不在TLB的缓存中或者TLB中的缓存记录失效,CPU就需要切换到低 速的、以软件方式实现的内存地址映射表进行内存寻址,这将大大降低 CPU的运算速度。针对缓存条目有限的TLB缓存表,提高TLB效率的最 佳办法就是将内存页增大,这样一来,一个进程所需的内存页数量会相 应减少很多。如果把内存页从默认的4KB改为2MB,那么1GB内存就只 对应512个内存页了,TLB的缓存命中率会大大增加。这是不是意味着 我们可以任意指定内存页的大小,比如1314MB的内存页?答案是否定 的,因为这是由CPU来决定的,比如常见的Intel X86处理器可以支持的 大内存页通常是2MB,个别型号的高端处理器则支持1GB的大内存页
在Linux平台下,对于那些需要大量内存(1GB以上内存)的程序 来说,大内存页的优势是很明显的,因为Huge Page大大提升了TLB的 缓存命中率,又因为Linux对Huge Page提供了更为简单、便捷的操作接 口,所以可以把它当作文件来进行读写操作。Linux使用Huge Page文件 系统hugetlbfs支持巨页,这种方式更为灵活,我们可以设置Huge Page的 大小,比如1GB、2GB甚至2.5GB,然后设置有多少物理内存用于分配 Huge Page,这样就设置了一些预先分配好的Huge Page。可以将 hugetlbfs文件系统挂载在/mnt/huge目录下,通过执行下面的指令完成设 置
mkdir /mnt/huge mount -t hugetlbfs nodev /mnt/huge
在设置完成后,用户进程就可以使用mmap映射Huge Page目标文件 来使用大内存页了,Intel DPDK便采用了这种做法,测试表明应用使用 大内存页比使用4KB的内存页性能提高了10%~15%
Kubernetes 1.14版本对Linux Huge Page的支持正式更新为GA稳定 版。我们可以将Huge Page理解为一种特殊的计算资源:拥有大内存页 的资源。而拥有Huge Page资源的Node也与拥有GPU资源的Node一样, 属于一种新的可调度资源节点(Schedulable Resource Node)
Huge Page也支持ResourceQuota来实现配额限制,类似CPU或者 Memory,但不同于CPU或者内存,Huge Page资源属于不可超限使用的 资源,拥有Huge Page能力的Node会将自身支持的Huge Page的能力信息 自动上报给Kubernetes Master
为此,Kubernetes引入了一个新的资源类型hugepages-<size>,来表 示大内存页这种特殊的资源,比如hugepages-2Mi表示2MiB规格的大内 存页资源。一个能提供2MiB规格Huge Page的Node,会上报自己拥有 Hugepages-2Mi的大内存页资源属性,供需要这种规格的大内存页资源 的Pod使用,而需要Huge Page资源的Pod只要给出相关的Huge Page的声 明,就可以被正确调度到匹配的目标Node上了。相关例子如下
apiVersion: v1 kind: Pod metadata: generateName: hugepages-volume- spec: containers: - image: fedora:latest command: - sleep: - inf name: example volumeMounts: - mountPath: /hugepages name: hugepage resource: limits: hugepages-2Mi: 100Mi memory: 100Mi request: memory:100Mi volumes: - name: hugepage emptyDir: medium: HugePages
Kubernetes提供了LimitRange机制对Pod和容器的 Requests和Limits配置进一步做出限制。在下面的示例中,将说明如何 将LimitsRange应用到一个Kubernetes的命名空间中,然后说明 LimitRange的几种限制方式,比如最大及最小范围、Requests和Limits的 默认值、Limits与Requests的最大比例上限等
apiVersion: v1 kind: LimitRange metadata: name: mylimits spec: limits: - max: cpu: "4" memory: 2Gi min: cpu: 200m memory: 6Mi maxLimitRequestRatio: cpu: 3 memory: 2 type: Pod - default: cpu: 300m memory: 200Mi defaultRequest: cpu: 200m memory: 100Mi max: cpu: "2" memory: 1Gi min: cpu: 100m memory: 3Mi maxLimitRequestRatio: cpu: 5 memory: 4 type: Container
不论是CPU还是内存,在LimitRange中,Pod和Container都可 以设置Min、Max和Max Limit/Requests Ratio参数。Container还可以设置 Default Request和Default Limit参数,而Pod不能设置Default Request和 Default Limit参数
对Pod和Container的参数解释如下
◎ Container的Min(上面的100m和3Mi)是Pod中所有容器的 Requests值下限;Container的Max(上面的2和1Gi)是Pod中所有容器的 Limits值上限;Container的Default Request(上面的200m和100Mi)是 Pod中所有未指定Requests值的容器的默认Requests值;Container的 Default Limit(上面的300m和200Mi)是Pod中所有未指定Limits值的容 器的默认Limits值。对于同一资源类型,这4个参数必须满足以下关系: Min ≤ Default Request ≤ Default Limit ≤ Max
◎ Pod的Min(上面的200m和6Mi)是Pod中所有容器的Requests 值的总和下限;Pod的Max(上面的4和2Gi)是Pod中所有容器的Limits 值的总和上限。当容器未指定Requests值或者Limits值时,将使用
◎ Container的Max Limit/Requests Ratio(上面的5和4)限制了Pod 中所有容器的Limits值与Requests值的比例上限;而Pod的Max Limit/Requests Ratio(上面的3和2)限制了Pod中所有容器的Limits值总 和与Requests值总和的比例上限
如果设置了Container的Max,那么对于该类资源而言,整个 集群中的所有容器都必须设置Limits,否则无法成功创建。Pod内的容器 未配置Limits时,将使用Default Limit的值(本例中的300m CPU和 200MiB内存),如果也未配置Default,则无法成功创建。
如果设置了Container的Min,那么对于该类资源而言,整个集 群中的所有容器都必须设置Requests。如果创建Pod的容器时未配置该类 资源的Requests,那么在创建过程中会报验证错误。Pod里容器的 Requests在未配置时,可以使用默认值defaultRequest(本例中的200m CPU和100MiB内存);如果未配置而又没有使用默认值 defaultRequest,那么会默认等于该容器的Limits;如果此时Limits也未 定义,就会报错。
对于任意一个Pod而言,该Pod中所有容器的Requests总和必 须大于或等于6MiB,而且所有容器的Limits总和必须小于或等于1GiB; 同样,所有容器的CPU Requests总和必须大于或等于200m,而且所有容 器的CPU Limits总和必须小于或等于2
Pod里任何容器的Limits与Requests的比例都不能超过 Container的Max Limit/Requests Ratio;Pod里所有容器的Limits总和与 Requests的总和的比例不能超过Pod的Max Limit/Requests Ratio
apiVersion: v1 kind: Pod metadata: name: invalid-pod namespace: test spec: containers: - name: kubernetes-serve-hostbame image: nginx resources: limits: cpu: "3" memory: 100Mi
apiVersion: v1 kind: Pod metadata: name: limit-test-nginx namespace: test labels: name: limit-test-nginx spec: containers: - name: limit-test-nginx image: nginx resources: limits: cpu: "1" memory: 512Mi requests: cpu: "0.8" memory: 250Mi
Kubernetes中Pod的Requests和Limits资源配置有如下特点
(1)如果Pod配置的Requests值等于Limits值,那么该Pod可以获得 的资源是完全可靠的。
(2)如果Pod的Requests值小于Limits值,那么该Pod获得的资源可 分成两部分:
◎ 完全可靠的资源,资源量的大小等于Requests值;
◎ 不可靠的资源,资源量最大等于Limits与Requests的差额,这 份不可靠的资源能够申请到多少,取决于当时主机上容器可用资源的余 量
通过这种机制,Kubernetes可以实现节点资源的超售(Over Subscription),比如在CPU完全充足的情况下,某机器共有32GiB内存 可提供给容器使用,容器配置为Requests值1GiB,Limits值为2GiB,那 么在该机器上最多可以同时运行32个容器,每个容器最多可以使用2GiB 内存,如果这些容器的内存使用峰值能错开,那么所有容器都可以正常 运行
超售机制能有效提高资源的利用率,同时不会影响容器申请的完全 可靠资源的可靠性
Kubernetes根据Pod配置的Requests值来调度Pod,Pod在成功调度之 后会得到Requests值定义的资源来运行;而如果Pod所在机器上的资源有 空余,则Pod可以申请更多的资源,最多不能超过Limits的值。下面看一 下Requests和Limits针对不同计算资源类型的限制机制的差异。这种差 异主要取决于计算资源类型是可压缩资源还是不可压缩资源
1)可压缩资源
◎ Kubernetes目前支持的可压缩资源是CPU
◎ Pod可以得到Pod的Requests配置的CPU使用量,而能否使用超 过Requests值的部分取决于系统的负载和调度。不过由于目前Kubernetes 和Docker的CPU隔离机制都是在容器级别隔离的,所以Pod级别的资源 配置并不能完全得到保障;Pod级别的cgroups正在紧锣密鼓地开发中, 如果将来引入,就可以确保Pod级别的资源配置准确运行
◎ 空闲CPU资源按照容器Requests值的比例分配。举例说明:容 器A的CPU配置为Requests 1 Limits 10,容器B的CPU配置为Request 2 Limits 8,A和B同时运行在一个节点上,初始状态下容器的可用CPU为 3cores,那么A和B恰好得到在它们的Requests中定义的CPU用量,即 1CPU和2CPU。如果A和B都需要更多的CPU资源,而恰好此时系统的 其他任务释放出1.5CPU,那么这1.5CPU将按照A和B的Requests值的比 例1∶2分配给A和B,即最终A可使用1.5CPU,B可使用3CPU
◎ 如果Pod使用了超过在Limits 10中配置的CPU用量,那么 cgroups会对Pod中的容器的CPU使用进行限流(Throttled);如果Pod没 有配置Limits 10,那么Pod会尝试抢占所有空闲的CPU资源(Kubernetes 从1.2版本开始默认开启--cpu-cfs-quota,因此在默认情况下必须配置 Limits)
2)不可压缩资源
◎ Kubernetes目前支持的不可压缩资源是内存
◎ Pod可以得到在Requests中配置的内存。如果Pod使用的内存量 小于它的Requests的配置,那么这个Pod可以正常运行(除非出现操作系 统级别的内存不足等严重问题);如果Pod使用的内存量超过了它的 Requests的配置,那么这个Pod有可能被Kubernetes杀掉:比如Pod A使 用了超过Requests而不到Limits的内存量,此时同一机器上另外一个Pod B之前只使用了远少于自己的Requests值的内存,此时程序压力增大, Pod B向系统申请的总量不超过自己的Requests值的内存,那么 Kubernetes可能会直接杀掉Pod A;另外一种情况是Pod A使用了超过 Requests而不到Limits的内存量,此时Kubernetes将一个新的Pod调度到 这台机器上,新的Pod需要使用内存,而只有Pod A使用了超过了自己的 Requests值的内存,那么Kubernetes也可能会杀掉Pod A来释放内存资 源
◎ 如果Pod使用的内存量超过了它的Limits设置,那么操作系统内 核会杀掉Pod所有容器的所有进程中使用内存最多的一个,直到内存不 超过Limits为止。
对调度策略的影响
Kubernetes的kubelet通过计算Pod中所有容器的Requests的总和来决定对Pod的调度
不管是CPU还是内存,Kubernetes调度器和kubelet都会确保节点上所有Pod的Requests的总和不会超过在该节点上可分配给容器使用的资源容量上限
服务质量等级(QoS Classes)
在一个超用(Over Committed,容器Limits总和大于系统容量上 限)系统中,由于容器负载的波动可能导致操作系统的资源不足,最终 可能导致部分容器被杀掉。在这种情况下,我们当然会希望优先杀掉那 些不太重要的容器,那么如何衡量重要程度呢?Kubernetes将容器划分 成3个QoS等级:Guaranteed(完全可靠的)、Burstable(弹性波动、较 可靠的)和BestEffort(尽力而为、不太可靠的),这三种优先级依次递 减
从理论上来说,QoS级别应该作为一个单独的参数来提供API,并 由用户对Pod进行配置,这种配置应该与Requests和Limits无关。但在当 前版本的Kubernetes的设计中,为了简化模式及避免引入太多的复杂 性,QoS级别直接由Requests和Limits来定义。在Kubernetes中容器的 QoS级别等于容器所在Pod的QoS级别,而Kubernetes的资源配置定义了 Pod的三种QoS级别,如下所述
1)Guaranteed
如果Pod中的所有容器对所有资源类型都定义了Limits和Requests, 并且所有容器的Limits值都和Requests值全部相等(且都不为0),那么该Pod的QoS级别就是Guaranteed。注意:在这种情况下,容器可以不定 义Requests,因为Requests值在未定义时默认等于Limits
2)BestEffort
如果Pod中所有容器都未定义资源配置(Requests和Limits都未定 义),那么该Pod的QoS级别就是BestEffort
3)Burstable
当一个Pod既不为Guaranteed级别,也不为BestEffort级别时,该Pod 的QoS级别就是Burstable。Burstable级别的Pod包括两种情况。第1种情 况:Pod中的一部分容器在一种或多种资源类型的资源配置中定义了 Requests值和Limits值(都不为0),且Requests值小于Limits值;第2种 情况:Pod中的一部分容器未定义资源配置(Requests和Limits都未定 义)。注意:在容器未定义Limits时,Limits值默认等于节点资源容量 的上限
4)Kubernetes QoS的工作特点
Pod的CPU Requests无法得到满足(比如节点的系统级任务占用过 多的CPU导致无法分配足够的CPU给容器使用)时,容器得到的CPU会 被压缩限流
由于内存是不可压缩的资源,所以针对内存资源紧缺的情况,会按 照以下逻辑进行处理:
(1)BestEffort Pod的优先级最低,在这类Pod中运行的进程会在系 统内存紧缺时被第一优先杀掉。当然,从另外一个角度来看,BestEffort Pod由于没有设置资源Limits,所以在资源充足时,它们可以充分使用所 有的闲置资源
(2)Burstable Pod的优先级居中,这类Pod初始时会分配较少的可 靠资源,但可以按需申请更多的资源。当然,如果整个系统内存紧缺又没有BestEffort容器可以被杀掉以释放资源,那么这类Pod中的进程可 能会被杀掉
(3)Guaranteed Pod的优先级最高,而且一般情况下这类Pod只要 不超过其资源Limits的限制就不会被杀掉。当然,如果整个系统内存紧 缺,又没有其他更低优先级的容器可以被杀掉以释放资源,那么这类 Pod中的进程也可能会被杀掉
5)OOM计分系统
OOM(Out Of Memory)计分规则包括如下内容
◎ OOM计分的计算方法为:计算进程使用内存在系统中占的百 分比,取其中不含百分号的数值,再乘以10的结果,这个结果是进程 OOM的基础分;将进程OOM基础分的分值再加上这个进程的OOM分数 调整值OOM_SCORE_ADJ的值,作为进程OOM的最终分值(除root启 动的进程外)。在系统发生OOM时,OOM Killer会优先杀掉OOM计分 更高的进程
◎ 进程的OOM计分的基本分数值范围是0~1000,如果A进程的 调整值OOM_SCORE_ADJ减去B进程的调整值的结果大于1000,那么A 进程的OOM计分最终值必然大于B进程,会优先杀掉A进程
◎ 不论调整OOM_SCORE_ADJ值为多少,任何进程的最终分值 范围也是0~1000
在Kubernetes,不同QoS的OOM计分调整值规则如表10.1所示
◎ BestEffort Pod设置OOM_SCORE_ADJ调整值为1000,因此 BestEffort Pod中容器里所有进程的OOM最终分肯定是1000
◎ Guaranteed Pod设置OOM_SCORE_ADJ调整值为-998,因此 Guaranteed Pod中容器里所有进程的OOM最终分一般是0或者1(因为基 础分不可能是1000)
◎ Burstable Pod规则分情况说明:如果Burstable Pod的内存 Requests超过了系统可用内存的99.8%,那么这个Pod的 OOM_SCORE_ADJ调整值固定为2;否则,设置OOM_SCORE_ADJ调 整值为1000-10×(% of memory requested);如果内存Requests为0,那么 OOM_SCORE_ADJ调整值固定为999。这样的规则能确保 OOM_SCORE_ADJ调整值的范围为2~999,而Burstable Pod中所有进程 的OOM最终分数范围为2~1000。Burstable Pod进程的OOM最终分数始 终大于Guaranteed Pod的进程得分,因此它们会被优先杀掉。如果一个 Burstable Pod使用的内存比它的内存Requests少,那么可以肯定的是它 的所有进程的OOM最终分数会小于1000,此时能确保它的优先级高于 BestEffort Pod。如果在一个Burstable Pod的某个容器中某个进程使用的 内存比容器的Requests值高,那么这个进程的OOM最终分数会是1000, 否则它的OOM最终分会小于1000。假设在下面的容器中有一个占用内 存非常大的进程,那么当一个使用内存超过其Requests的Burstable Pod 与另外一个使用内存少于其Requests的Burstable Pod发生内存竞争冲突 时,前者的进程会被系统杀掉。如果在一个Burstable Pod内部有多个进 程的多个容器发生内存竞争冲突,那么此时OOM评分只能作为参考, 不能保证完全按照资源配置的定义来执行OOM Kill。
资源配额管理(Resource Quotas)
如果一个Kubernetes集群被多个用户或者多个团队共享,就需要考 虑资源公平使用的问题,因为某个用户可能会使用超过基于公平原则分 配给其的资源量
Resource Quotas就是解决这个问题的工具。通过ResourceQuota对 象,我们可以定义资源配额,这个资源配额可以为每个命名空间都提供一个总体的资源使用的限制:它可以限制命名空间中某种类型的对象的 总数目上限,也可以设置命名空间中Pod可以使用的计算资源的总上 限
典型的资源配额使用方式如下
◎ 不同的团队工作在不同的命名空间下,目前这是非约束性的, 在未来的版本中可能会通过ACL(Access Control List,访问控制列表) 来实现强制性约束
◎ 集群管理员为集群中的每个命名空间都创建一个或者多个资源 配额项
◎ 当用户在命名空间中使用资源(创建Pod或者Service等)时, Kubernetes的配额系统会统计、监控和检查资源用量,以确保使用的资 源用量没有超过资源配额的配置
◎ 如果在创建或者更新应用时资源使用超过了某项资源配额的限 制,那么创建或者更新的请求会报错(HTTP 403 Forbidden),并给出 详细的出错原因说明
◎ 如果命名空间中的计算资源(CPU和内存)的资源配额启用, 那么用户必须为相应的资源类型设置Requests或Limits;否则配额系统 可能会直接拒绝Pod的创建。这里可以使用LimitRange机制来为没有配 置资源的Pod提供默认资源配置
在使用资源配额时,需要注意以下两点
◎ 如果集群中总的可用资源小于各命名空间中资源配额的总和, 那么可能会导致资源竞争。资源竞争时,Kubernetes系统会遵循先到先 得的原则
◎ 不管是资源竞争还是配额的修改,都不会影响已经创建的资源 使用对象
1.在Master中开启资源配额选型
资源配额可以通过在kube-apiserver的--admission-control参数值中添 加ResourceQuota参数进行开启。如果在某个命名空间的定义中存在 ResourceQuota,那么对于该命名空间而言,资源配额就是开启的。一个 命名空间可以有多个ResourceQuota配置项
2)存储资源配额(Volume Count Quota)
可以在给定的命名空间中限制所使用的存储资源(StorageResources)的总量,目前支持的存储资源名称如表10.3所示
3)对象数量配额(Object Count Quota)
指定类型的对象数量可以被限制。表10.4列出了ResourceQuota支持 限制的对象类型
例如,我们可以通过资源配额来限制在命名空间中能创建的Pod的 最大数量。这种设置可以防止某些用户大量创建Pod而迅速耗尽整个集 群的Pod IP和计算资源
2.配额的作用域(Quota Scopes)
每项资源配额都可以单独配置一组作用域,配置了作用域的资源配 额只会对符合其作用域的资源使用情况进行计量和限制,作用域范围内 超过了资源配额的请求都会报验证错误。表10.5列出了ResourceQuota的 4种作用域
其中,BestEffort作用域可以限定资源配额来追踪pods资源的使用, Terminating、NotTerminating和NotBestEffort这三种作用域可以限定资源 配额来追踪以下资源的使用
◎ cpu ◎ limits.cpu ◎ limits.memory ◎ memory ◎ pods ◎ requests.cpu ◎ requests.memory
3.在资源配额(ResourceQuota)中设置Requests和Limits
如果在资源配额中指定了requests.cpu或requests.memory,那么它会 强制要求每个容器都配置自己的CPU Requests或CPU Limits(可使用LimitRange提供的默认值)
同理,如果在资源配额中指定了limits.cpu或limits.memory,那么它 也会强制要求每个容器都配置自己的内存Requests或内存Limits(可使 用LimitRange提供的默认值)
4.资源配额的定义
apiVersion: v1 kind: ResourceQuota metadata: name: compute-resources namespace: test spec: hard: pods: "4" requests.cpu: "1" requests.memory: 1Gi limits.cpu: "2" limits.memory: 2Gi
apiVersion: v1 kind: ResourceQuota metadata: name: object-counts namespace: test spec: hard: configmaps: "2" persistentvolumeclaims: "4" replicationcontrollers: "2" secrets: "10" services: "4" services.loadbalancers: "2"
资源配额与集群资源总量的关系
资源配额与集群资源总量是完全独立的。资源配额是通过绝对的单 位来配置的,这也就意味着如果在集群中新添加了节点,那么资源配额 不会自动更新,而该资源配额所对应的命名空间中的对象也不能自动增 加资源上限
在某些情况下,我们可能希望资源配额支持更复杂的策略,如下所 述
◎ 对于不同的租户,按照比例划分整个集群的资源
◎ 允许每个租户都能按照需要来提高资源用量,但是有一个较宽 容的限制,以防止意外的资源耗尽情况发生
◎ 探测某个命名空间的需求,添加物理节点并扩大资源配额值
资源管理总结
Kubernetes中资源管理的基础是容器和Pod的资源配置(Requests和 Limits)。容器的资源配置指定了容器请求的资源和容器能使用的资源 上限,Pod的资源配置则是Pod中所有容器的资源配置总和上限
通过资源配额机制,我们可以对命名空间中所有Pod使用资源的总 量进行限制,也可以对这个命名空间中指定类型的对象的数量进行限 制
如果需要对用户的Pod或容器的资源配置做更多的限制,则可以使 用资源配置范围(LimitRange)来达到这个目的。LimitRange可以有效 地限制Pod和容器的资源配置的最大、最小范围,也可以限制Pod和容器的Limits与Requests的最大比例上限,此外LimitRange还可以为Pod中的 容器提供默认的资源配置
Kubernetes基于Pod的资源配置实现了资源服务质量(QoS)。不同 QoS级别的Pod在系统中拥有不同的优先级:高优先级的Pod有更高的可 靠性,可以用于运行可靠性要求较高的服务;低优先级的Pod可以实现 集群资源的超售,可以有效地提高集群资源利用率
资源紧缺时的Pod驱逐机制
驱逐策略
kubelet持续监控主机的资源使用情况,并尽量防止计算资源被耗 尽。一旦出现资源紧缺的迹象,kubelet就会主动终止一个或多个Pod的 运行,以回收紧缺的资源。当一个Pod被终止时,其中的容器会全部停 止,Pod的状态会被设置为Failed
驱逐信号
在表10.6中提到了一些信号,kubelet能够利用这些信号作为决策依 据来触发驱逐行为。其中,描述列中的内容来自kubelet Summary API; 每个信号都支持整数值或者百分比的表示方法,百分比的分母部分就是 各个信号相关资源的总量
memory.available的值取自cgroupfs,而不是free -m命令,这是因为 free -m不支持在容器内工作。如果用户使用了node allocatable功能,则 除了节点自身的内存需要判断,还需要利用cgroup根据用户Pod部分的 情况进行判断。下面的脚本展示了kubelet计算memory.available的过程
kubelet假设inactive_file(不活跃LRU列表中的file-backed内存,以 字节为单位)在紧缺情况下可以回收,因此对其进行了排除
kubelet支持以下两种文件系统
(1)nodefs:保存kubelet的卷和守护进程日志等
(2)imagefs:在容器运行时保存镜像及可写入层
驱逐阈值
kubelet可以定义驱逐阈值,一旦超出阈值,就会触发kubelet进行资 源回收操作
阈值的定义方式为
◎ 在表10.6中列出了驱逐信号的名称。
◎ 当前仅支持一个operator(运算符):< (小于)
◎ quantity需要符合Kubernetes的数量表达方式,也可以用以%结 尾的百分比表示
例如,如果一个节点有10GiB内存,我们希望在可用内存不足1GiB 时进行驱逐,就可以用下面任一方式来定义驱逐阈值
◎ memory.available<10%
◎ memory.available<1GiB
驱逐阈值又可以通过软阈值和硬阈值两种方式进行设置
1.驱逐软阈值
驱逐软阈值由一个驱逐阈值和一个管理员设定的宽限期共同定义。 当系统资源消耗达到软阈值时,在这一状况的持续时间达到宽限期之 前,kubelet不会触发驱逐动作。如果没有定义宽限期,则kubelet会拒绝 启动。
另外,可以定义终止Pod的宽限期。如果定义了这一宽限期,那么 kubelet会使用pod.Spec.TerminationGracePeriodSeconds和最大宽限期这两 个值之间较小的数值进行宽限,如果没有指定,则kubelet会立即杀掉 Pod
软阈值的定义包括以下几个参数
◎ --eviction-soft:描述驱逐阈值(例如 memory.available<1.5GiB),如果满足这一条件的持续时间超过宽限 期,就会触发对Pod的驱逐动作
◎ --eviction-soft-grace-period:驱逐宽限期(例如 memory.available=1m30s),用于定义达到软阈值之后持续时间超过多 久才进行驱逐
◎ --eviction-max-pod-grace-period:在达到软阈值后,终止Pod的 最大宽限时间(单位为s)
2.驱逐硬阈值
硬阈值没有宽限期,如果达到了硬阈值,则kubelet会立即杀掉Pod 并进行资源回收
硬阈值的定义包括参数--eviction-hard:驱逐硬阈值,一旦达到阈 值,就会触发对Pod的驱逐操作
kubelet的默认硬阈值定义如下
--eviction-hard mapStringString A set of eviction thresholds (e.g. memory.available<1Gi) that if met would trigger a pod eviction. (default imagefs.available<15%,memory.available<100Mi,nodefs.available<10%,nodefs.inodesFree<5%)
kubelet的--housekeeping-interval参数定义了一个时间间隔,kubelet 每隔一个这样的时间间隔就会对驱逐阈值进行评估(默认10s)
节点的状况
kubelet会将一个或多个驱逐信号与节点的状况对应起来
无论触发了硬阈值还是软阈值,kubelet都会认为当前节点的压力太 大,如表10.7所示为节点状况与驱逐信号的对应关系
kubelet会持续向Master报告节点状态的更新过程,这一频率由参数--node-status-update-frequency指定,默认为10s
节点状况的抖动
如果一个节点的状况在软阈值的上下抖动,但是又没有超过宽限 期,则会导致该节点的相应状态在True和False之间不断变换,可能会对 调度的决策过程产生负面影响
要防止这种状况,可以使用参数--eviction-pressure-transition- period(在脱离压力状态前需要等待的时间,默认值为5m0s),为 kubelet设置在脱离压力状态之前需要等待的时间
这样一来,kubelet在把压力状态设置为False之前,会确认在检测周 期之内该节点没有达到驱逐阈值
回收Node级别的资源
如果达到了驱逐阈值,并且也过了宽限期,kubelet就会回收超出限 量的资源,直到驱逐信号量回到阈值以内
kubelet在驱逐用户Pod之前,会尝试回收Node级别的资源。在观测 到磁盘压力的情况下,基于服务器是否为容器运行时定义了独立的 imagefs,会导致不同的资源回收过程
1.有Imagefs的情况
(1)如果nodefs文件系统达到了驱逐阈值,则kubelet会删掉死掉的 Pod、容器来清理空间。
(2)如果imagefs文件系统达到了驱逐阈值,则kubelet会删掉所有 无用的镜像来清理空间
2.没有Imagefs的情况
如果nodefs文件系统达到了驱逐阈值,则kubelet会按照下面的顺序 来清理空间。
(1)删除死掉的Pod、容器。
(2)删除所有无用的镜像。
驱逐用户的Pod
如果kubelet无法通过节点级别的资源回收获取足够的资源,就会驱 逐用户的Pod
kubelet会按照下面的标准对Pod的驱逐行为进行判断
◎ Pod要求的服务质量。
◎ 根据Pod调度请求的被耗尽资源的消耗量。
接下来,kubelet按照下面的顺序驱逐Pod。
◎ BestEffort:紧缺资源消耗最多的Pod最先被驱逐
◎ Burstable:根据相对请求来判断,紧缺资源消耗最多的Pod最 先被驱逐,如果没有Pod超出它们的请求,则策略会瞄准紧缺资源消耗 量最大的Pod。
◎ Guaranteed:根据相对请求来判断,紧缺资源消耗最多的Pod最 先被驱逐,如果没有Pod超出它们的请求,策略会瞄准紧缺资源消耗量 最大的Pod。
Guaranteed Pod永远不会因为其他Pod的资源消费被驱逐。如果系统 进程(例如kubelet、docker、journald等)消耗了超出system-reserved或 者kube-reserved的资源,而在这一节点上只运行了Guaranteed Pod,那么 为了保证节点的稳定性并降低异常消耗对其他Guaranteed Pod的影响, 必须选择一个Guaranteed Pod进行驱逐
本地磁盘是一种BestEffort资源。如有必要,kubelet会在 DiskPressure的情况下,对Pod进行驱逐以回收磁盘资源。kubelet会按照 QoS进行评估。如果kubelet判定缺乏inode资源,就会通过驱逐最低QoS 的Pod的方式来回收inodes。如果kubelet判定缺乏磁盘空间,就会在相同 QoS的Pod中,选择消耗最多磁盘空间的Pod进行驱逐。下面针对有 Imagefs和没有Imagefs的两种情况,说明kubelet在驱逐Pod时选择Pod的 排序算法,然后按顺序对Pod进行驱逐
1.有Imagefs的情况
如果nodefs触发了驱逐,则kubelet会根据nodefs的使用情况(以Pod 中所有容器的本地卷和日志所占的空间进行计算)对Pod进行排序
如果imagefs触发了驱逐,则kubelet会根据Pod中所有容器消耗的可 写入层的使用空间进行排序
2.没有Imagefs的情况
如果nodefs触发了驱逐,则kubelet会对各个Pod中所有容器的总体磁 盘消耗(以本地卷+日志+所有容器的写入层所占的空间进行计算)进行 排序
资源最少回收量
在某些场景下,驱逐Pod可能只回收了很少的资源,这就导致了 kubelet反复触发驱逐阈值。另外,回收磁盘这样的资源,是需要消耗时 间的
要缓和这种状况,kubelet可以对每种资源都定义minimum-reclaim。 kubelet一旦监测到了资源压力,就会试着回收不少于minimum-reclaim的 资源数量,使得资源消耗量回到期望的范围
◎ 当memory.available超过阈值触发了驱逐操作时,kubelet会启动资源回收,并保证memory.available至少有500MiB。
◎ 如果是nodefs.available超过阈值并触发了驱逐操作,则kubelet 会恢复nodefs.available到至少1.5GiB。
◎ 对于imagefs.available超过阈值并触发了驱逐操作的情况, kubelet会保证imagefs.available恢复到最少102GiB。
1.调度器的行为
在节点资源紧缺的情况下,节点会向Master报告这一状况。在 Master上运行的调度器(Scheduler)以此为信号,不再继续向该节点调 度新的Pod。如表10.8所示为节点状况与调度行为的对应关系
2.Node的OOM行为
如果节点在kubelet能够回收内存之前遭遇了系统的OOM(内存不 足),节点则依赖oom_killer的设置进行响应(OOM评分系统详见10.4 节的描述)
kubelet根据Pod的QoS为每个容器都设置了一个oom_score_adj值, 如表10.9所示
如果kubelet无法在系统OOM之前回收足够的内存,则oom_killer会 根据内存使用比率来计算oom_score,将得出的结果和oom_score_adj相 加,得分最高的Pod首先被驱逐
这个策略的思路是,QoS最低且相对于调度的Request来说消耗最多 内存的Pod会首先被驱逐,来保障内存的回收
与Pod驱逐不同,如果一个Pod的容器被OOM杀掉,则是可能被 kubelet根据RestartPolicy重启的
3.对DaemonSet类型的Pod驱逐的考虑
通过DaemonSet创建的Pod具有在节点上自动重启的特性,因此我 们不希望kubelet驱逐这种Pod;然而kubelet目前并没有能力分辨 DaemonSet的Pod,所以无法单独为其制定驱逐策略,所以强烈建议不 要在DaemonSet中创建BestEffort类型的Pod,避免产生驱逐方面的问 题