QoS(Quality of Service)
Kubernetes针对不同服务质量的预期,通过QoS(Quality of Service)来对pod进行服务质量管理,提供了个采用requests和limits两种类型对资源进行分配和使用限制。对于一个pod来说,服务质量体现在两个为2个具体的指标:CPU与内存。实际过程中,当NODE节点上内存资源紧张时,kubernetes会根据预先设置的不同QoS类别进行相应处理。
如果未做过节点 nodeSelector,亲和性(node affinity)或pod亲和、反亲和性(pod affinity/anti-affinity)等Pod高级调度策略设置,我们没有办法指定服务部署到指定机器上,如此可能会造成cpu或内存等密集型的pod同时分配到相同Node,造成资源竞争。另一方面,如果未对资源进行限制,一些关键的服务可能会因为资源竞争因OOM(Out of Memory)等原因被kill掉,或者被限制CPU使用。
对于每一个资源,container可以指定具体的资源需求(requests)和限制(limits),requests申请范围是0到node节点的最大配置,而limits申请范围是requests到无限,即0 <= requests <=Node Allocatable, requests <= limits <= Infinity。
对于CPU,如果pod中服务使用CPU超过设置的limits,pod不会被kill掉但会被限制。如果没有设置limits,pod可以使用全部空闲的cpu资源。
对于内存,当一个pod使用内存超过了设置的limits,pod中container的进程会被kernel因OOM kill掉。当container因为OOM被kill掉时,系统倾向于在其原所在的机器上重启该container或本机或其他重新创建一个pod。
Kubelet提供QoS服务质量管理,支持系统级别的OOM控制。在Kubernetes中,pod的QoS级别:Guaranteed, Burstable与 Best-Effort。下面对各级别分别进行相应说明:
Guaranteed:pod中所有容器都必须统一设置limits,并且设置参数都一致,如果有一个容器要设置requests,那么所有容器都要设置,并设置参数同limits一致,那么这个pod的QoS就是Guaranteed级别。
注:如果一个容器只指明limit而未设定request,则request的值等于limit值。
Guaranteed举例1:容器只指明了limits而未指明requests)。
Guaranteed举例2:requests与limit均指定且值相等。
containers:
name: foo
resources:
limits:
cpu: 10m
memory: 1Gi
requests:
cpu: 10m
memory: 1Gi
name: bar
resources:
limits:
cpu: 100m
memory: 100Mi
requests:
cpu: 100m
memory: 100Mi
Burstable: pod中只要有一个容器的requests和limits的设置不相同,该pod的QoS即为Burstable。举例如下:
Container bar没有指定resources
containers:
name: foo
resources:
limits:
cpu: 10m
memory: 1Gi
requests:
cpu: 10m
memory: 1Gi
name: bar
Burstable举例2:对Container foo与bar不同的resources(foo为memory,而bar为cpu)设置了limits。
containers:
name: foo
resources:
limits:
memory: 1Gi
name: bar
resources:
limits:
cpu: 100m
Burstable举例3:Container foo没有设置limits,而bar requests与 limits均未设置。
containers:
name: foo
resources:
requests:
cpu: 10m
memory: 1Gi
name: bar
Best-Effort:如果对于全部的resources来说requests与limits均未设置,该pod的QoS即为Best-Effort。举例如下:
containers:
name: foo
resources:
name: bar
resources:
Kubernetes根据资源能否伸缩进行分类,划分为可压缩资源和不可以压缩资源2种。CPU资源是目前支持的一种可压缩资源,而内存资源和磁盘资源为目前所支持的不可压缩资源。
3种QoS优先级从有低到高(从左向右):
Best-Effort pods -> Burstable pods -> Guaranteed pods
在Kubernetes中有一种DaemonSet类型pod,此类型pod可以常驻某个Node运行,由该Node上kubelet服务直接管理而无需api server介入。静态pod也无需关联任何RC,完全由kubelet服务来监控,当kubelet发现静态pod停止时,kubelet会重新启动静态pod。
当kubernetes集群中某个节点上可用资源比较小时,kubernetes提供了资源回收策略保证被调度到该节点pod服务正常运行。当节点上的内存或者CPU资源耗尽时,可能会造成该节点上正在运行的pod服务不稳定。Kubernetes通过kubelet来进行回收策略控制,保证节点上pod在节点资源比较小时可以稳定运行。
可压缩资源:CPU
在压缩资源部分已经提到CPU属于可压缩资源,当pod使用超过设置的limits值,pod中进程使用cpu会被限制,但不会被kill。
不可压缩资源:内存
Kubernetes通过cgroup给pod设置QoS级别,当资源不足时先kill优先级低的pod,在实际使用过程中,通过OOM分数值来实现,OOM分数值从0-1000。
OOM分数值根据OOM_ADJ参数计算得出,对于Guaranteed级别的pod,OOM_ADJ参数设置成了-998,对于BestEffort级别的pod,OOM_ADJ参数设置成了1000,对于Burstable级别的POD,OOM_ADJ参数取值从2到999。对于kuberntes保留资源,比如kubelet,docker,OOM_ADJ参数设置成了-999,表示不会被OOM kill掉。OOM_ADJ参数设置的越大,通过OOM_ADJ参数计算出来OOM分数越高,表明该pod优先级就越低,当出现资源竞争时会越早被kill掉,对于OOM_ADJ参数是-999的表示kubernetes永远不会因为OOM而被kill掉。
QoS pods被kill掉场景与顺序
使用建议
heidsoft-nginx-7ddf9c5bcf-qqsnk:~# ls -alh /proc/1/oom_*
-rw-r--r-- 1 root root 0 Oct 1 13:28 /proc/1/oom_adj
-r--r--r-- 1 root root 0 Oct 1 13:28 /proc/1/oom_score
-rw-r--r-- 1 root root 0 Oct 1 13:28 /proc/1/oom_score_adj
本页介绍如何使用 kubelet
配置资源不足时的处理方式。
当可用计算资源较少时,kubelet
需要保证节点稳定性。这在处理如内存和硬盘之类的不可压缩资源时尤为重要。如果任意一种资源耗尽,节点将会变得不稳定。
kubelet
支持按照以下表格中描述的信号触发驱逐决定。每个信号的值在 description 列描述,基于 kubelet
摘要 API。
驱逐信号 | 描述 |
---|---|
memory.available | memory.available := node.status.capacity[memory] - node.stats.memory.workingSet |
nodefs.available | nodefs.available := node.stats.fs.available |
nodefs.inodesFree | nodefs.inodesFree := node.stats.fs.inodesFree |
imagefs.available | imagefs.available := node.stats.runtime.imagefs.available |
imagefs.inodesFree | imagefs.inodesFree := node.stats.runtime.imagefs.inodesFree |
pid.available | pid.available := node.stats.rlimit.maxpid - node.stats.rlimit.curproc |
上面的每个信号都支持字面值或百分比的值。基于百分比的值的计算与每个信号对应的总容量相关。
memory.available
的值从 cgroupfs 获取,而不是通过类似 free -m
的工具。这很重要,因为 free -m
不能在容器中工作,并且如果用户使用了 节点可分配资源 特性,资源不足的判定将同时在本地 cgroup 层次结构的终端用户 Pod 部分和根节点做出。这个脚本 复现了与 kubelet
计算 memory.available
相同的步骤。 kubelet
将 inactive_file
(意即活动 LRU 列表上基于文件后端的内存字节数)从计算中排除, 因为它假设内存在出现压力时将被回收。
kubelet
只支持两种文件系统分区。
nodefs
文件系统,kubelet 将其用于卷和守护程序日志等。imagefs
文件系统,容器运行时用于保存镜像和容器可写层。imagefs
可选。kubelet
使用 cAdvisor 自动发现这些文件系统。 kubelet
不关心其它文件系统。当前不支持配置任何其它类型。例如,在专用 filesytem
中存储卷和日志是 不可以 的。
在将来的发布中,kubelet
将废除当前存在的 垃圾回收 机制,这种机制目前支持将驱逐操作作为对磁盘压力的响应。
func computePodResourceRequest(pod *v1.Pod) *preFilterState {
result := &preFilterState{}
for _, container := range pod.Spec.Containers {
result.Add(container.Resources.Requests)
}
// take max_resource(sum_pod, any_init_container)
for _, container := range pod.Spec.InitContainers {
result.SetMaxResource(container.Resources.Requests)
}
// If Overhead is being utilized, add to the total requests for the pod
if pod.Spec.Overhead != nil && utilfeature.DefaultFeatureGate.Enabled(features.PodOverhead) {
result.Add(pod.Spec.Overhead)
}
return result
}
...
func (f *Fit) PreFilter(ctx context.Context, cycleState *framework.CycleState, pod *v1.Pod) *framework.Status {
cycleState.Write(preFilterStateKey, computePodResourceRequest(pod))
return nil
}
...
func getPreFilterState(cycleState *framework.CycleState) (*preFilterState, error) {
c, err := cycleState.Read(preFilterStateKey)
if err != nil {
// preFilterState doesn't exist, likely PreFilter wasn't invoked.
return nil, fmt.Errorf("error reading %q from cycleState: %v", preFilterStateKey, err)
}
s, ok := c.(*preFilterState)
if !ok {
return nil, fmt.Errorf("%+v convert to NodeResourcesFit.preFilterState error", c)
}
return s, nil
}
...
func (f *Fit) Filter(ctx context.Context, cycleState *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status {
s, err := getPreFilterState(cycleState)
if err != nil {
return framework.NewStatus(framework.Error, err.Error())
}
insufficientResources := fitsRequest(s, nodeInfo, f.ignoredResources, f.ignoredResourceGroups)
if len(insufficientResources) != 0 {
// We will keep all failure reasons.
failureReasons := make([]string, 0, len(insufficientResources))
for _, r := range insufficientResources {
failureReasons = append(failureReasons, r.Reason)
}
return framework.NewStatus(framework.Unschedulable, failureReasons...)
}
return nil
}
func GetContainerOOMScoreAdjust(pod *v1.Pod, container *v1.Container, memoryCapacity int64) int {
if types.IsCriticalPod(pod) {
// Critical pods should be the last to get killed.
return guaranteedOOMScoreAdj
}
switch v1qos.GetPodQOS(pod) {
case v1.PodQOSGuaranteed:
// Guaranteed containers should be the last to get killed.
return guaranteedOOMScoreAdj
case v1.PodQOSBestEffort:
return besteffortOOMScoreAdj
}
// Burstable containers are a middle tier, between Guaranteed and Best-Effort. Ideally,
// we want to protect Burstable containers that consume less memory than requested.
// The formula below is a heuristic. A container requesting for 10% of a system's
// memory will have an OOM score adjust of 900. If a process in container Y
// uses over 10% of memory, its OOM score will be 1000. The idea is that containers
// which use more than their request will have an OOM score of 1000 and will be prime
// targets for OOM kills.
// Note that this is a heuristic, it won't work if a container has many small processes.
memoryRequest := container.Resources.Requests.Memory().Value()
oomScoreAdjust := 1000 - (1000*memoryRequest)/memoryCapacity
// A guaranteed pod using 100% of memory can have an OOM score of 10. Ensure
// that burstable pods have a higher OOM score adjustment.
if int(oomScoreAdjust) < (1000 + guaranteedOOMScoreAdj) {
return (1000 + guaranteedOOMScoreAdj)
}
// Give burstable pods a higher chance of survival over besteffort pods.
if int(oomScoreAdjust) == besteffortOOMScoreAdj {
return int(oomScoreAdjust - 1)
}
return int(oomScoreAdjust)
}
https://www.cncf.io/blog/2020/06/10/kubernetes-resources-management-qos-quota-and-limitrangeb/
https://kubesphere.io/zh/blogs/deep-dive-into-the-k8s-request-and-limit/
Kubernetes中的调度策略主要分为全局调度与运行时调度2种。其中全局调度策略在调度器启动时配置,而运行时调度策略主要包括选择节点(nodeSelector),节点亲和性(nodeAffinity),pod亲和与反亲和性(podAffinity与podAntiAffinity)。Node Affinity、podAffinity/AntiAffinity以及后文即将介绍的污点(Taints)与容忍(tolerations)等特性,
Label是Kubernetes核心概念之一,其以key/value的形式附加到各种对象上,如Pod、Service、Deployment、Node等,达到识别这些对象,管理关联关系等目的,如Node和Pod的关联。
获取当前集群中的全部节点:
kubectl get nodes
为指定节点设置label:
kubectl label nodes <node-name> <label-key>=<label-value>
确认节点label是否设置成功:
kubectl get nodes -l ‘label_key=label_value’
亲和性(Affinity)与非亲和性(anti-affinity)则更加灵活的指定pod调度到预期节点上,相比nodeSelector,Affinity与anti-affinity优势体现在:
亲和性主要分为3种类型:node affinity与inter-pod affinity/anti-affinity,下文会进行详细说明。
Node affinity在Kubernetes 1.2做为alpha引入,其涵盖了nodeSelector功能,主要分为requiredDuringSchedulingIgnoredDuringExecution与preferredDuringSchedulingIgnoredDuringExecution 2种类型。前者可认为一种强制限制,如果 Node 的标签发生了变化导致其没有符合 Pod 的调度要求节点,那么pod调度就会失败。而后者可认为理解为软限或偏好,同样如果 Node 的标签发生了变化导致其不再符合 pod 的调度要求,pod 依然会调度运行。
node affinity举例
设置节点label:
$ kubectl label nodes bjo-ep-dep-040.dev.fwmrm.net cpu=high
node "bjo-ep-dep-040.dev.fwmrm.net" labeled
$ kubectl label nodes bjo-ep-svc-017.dev.fwmrm.net cpu=mid
node "bjo-ep-svc-017.dev.fwmrm.net" labeled
$ kubectl label nodes bjo-rpt-re-002.dev.fwmrm.net cpu=low
node "bjo-rpt-re-002.dev.fwmrm.net" labeled
部署pod的预期是到非master节点(role!=master)、且CPU高配的机器上(cpu=high)。
查看满足条件节点:
$ kubectl get nodes -l 'cpu=high, role!=master'
NAME STATUS AGE VERSION
bjo-ep-dep-040.dev.fwmrm.net Ready 41d v1.7.1
pod.yaml文件内容如下:
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: role
operator: NotIn
values:
- master
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: cpu
operator: In
values:
- high
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
检查结果符合预期,pod nginx成功部署到非master节点且CPU高配的机器上。
$ kubectl get po nginx -o wide
NAME READY STATUS RESTARTS AGE IP NODE
nginx 1/1 Running 0 32s 10.244.2.185 bjo-ep-dep-040.dev.fwmrm
inter-pod affinity与anti-affinity由Kubernetes 1.4引入,当前处于beta阶段,其中podAffinity用于调度pod可以和哪些pod部署在同一拓扑结构之下。而podAntiAffinity相反,其用于规定pod不可以和哪些pod部署在同一拓扑结构下。通过pod affinity与anti-affinity来解决pod和pod之间的关系。
与Node affinity类似,pod affinity与anti-affinity同样分为requiredDuringSchedulingIgnoredDuringExecution and preferredDuringSchedulingIgnoredDuringExecution等2种类型,前者被认为是强制约束,而后者后者可认为理解软限(soft)或偏好(preference)。
pod affinity与anti-affinity举例
本示例中假设部署场景为:期望is服务与oltp服务就近部署,而不希望与solr服务部署同一拓扑结构上。
yaml文件部分内容:
spec:
replicas: 1
template:
metadata:
labels:
app: is
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: NotIn
values:
- solr
topologyKey: kubernetes.io/hostname
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- oltp
topologyKey: beta.kubernetes.io/os
查看部署结果,is服务与oltp部署到了同一台机器,而solr被部署在其他机器上。
$ kubectl get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE
is-3059482752-5s14t 0/1 Running 1 1m 10.244.1.60 bjo-ep-svc-017.dev.fwmrm.net
oltp-282283610-kdvnp 1/1 Running 0 1m 10.244.1.53 bjo-ep-svc-017.dev.fwmrm.net
solr-908150957-rswlm 1/1 Running 0 1m 10.244.3.5 bjo-rpt-re-002.dev.fwmrm.net
亲和性/反亲和性调度策略比较
调度策略 匹配标签 操作符 拓扑域支持 调度目标
nodeAffinity 主机 In, NotIn, Exists, 否 pod到指定主机
DoesNotExist, Gt, Lt
podAffinity Pod In, NotIn, Exists, 是 pod与指定pod同一拓扑域
DoesNotExist
PodAntiAffinity Pod In, NotIn, Exists, 是 pod与指定pod非同一拓扑域
DoesNotExist
对于Node affinity,无论是强制约束(hard)或偏好(preference)方式,都是调度pod到预期节点上,而Taints恰好与之相反,如果一个节点标记为 Taints ,除非 Pod也被标识为可以耐受污点节点,否则该Taints节点不会被调度pod。Taints)与tolerations当前处于beta阶段,
Taints节点应用场景比如用户希望把Kubernetes Master节点保留给 Kubernetes 系统组件使用,或者把一组具有特殊资源预留给某些 pod。
pod不会再被调度到taint标记过的节点。taint标记节点举例如下:
$ kubectl taint nodes bjo-ep-dep-039.dev.fwmrm.net key=value:NoSchedule
node "bjo-ep-dep-039.dev.fwmrm.net" tainted
如果仍然希望某个pod调度到taint节点上,则必须在 Spec 中做出Toleration 定义,才能调度到该节点,举例如下:
tolerations:
- key: "key"
operator: "Equal"
value: "value"
effect: "NoSchedule"
effect 共有三个可选项,可按实际需求进行设置:
1. NoSchedule:pod不会被调度到标记为taints节点。
2. PreferNoSchedule:NoSchedule的“preference”或“soft”版本。
3. NoExecute:该选项意味着一旦Taint 生效,如该节点内正在运行的 Pod 没有对应 Tolerate 设置,会直接被逐出。
https://kubernetes.io/blog/2017/03/advanced-scheduling-in-kubernetes/