containerManager 负责 node 节点上运行的容器的配置信息,如cgroup、cpu、device;
pod的创建流程参考:
http://www.tianfeiyu.com/?p=2825
一构成
1.QOSContainerManager维护pod对应的Qos,用于调度、驱逐、设置各资源的cgroup等模块
2.cgroupManager根据pod的Qos设置不同级别的cgroup参数,并不断根据pod的变化维护这些cgroup参数
3.devicemanager管理节点上的device,主要包含分配、回收、本地记录存储;
4.cpumanager管理节点上的cpu,主要包含分配、回收、本地记录存储
5.devicemanager通过topologymanager为container选择最优的device组合
6.cpumanager通过topologymanager为container选择最优cpuset
调用流程:
1.kubeRuntimeManager.SyncPod创建完container后,通过cpumanager设置container的启动参数;
2.kubeRuntimeManager.SyncPod.startContainer启动container时会调用containerManager获取container跑在device上的参数,最终回调用到devicemanager
3.Kubelet.SyncPod设置cgroup参数阶段-->podContainerManager-->cgroupManager(依赖QOSContainerManager设置QOS)-->runc.libcontainer.cgroupfs将设置的参数apply到container
二、Kubelet.syncPod vs kubeRuntimeManager.SyncPod:调用关系:
Kubelet.syncPod:
1.如果pod需要被删除,调用containerRuntime.KillPod杀掉pod
2.为第一次创建的pod记录创建时间,当pod状态变化时也记录当前时间
3.通过一系列admit判断pod是否能在当前node上运行,如安全相关的appArmorValidator、containerRuntime是否允许root Privilege等
4.设置pod的cgroup参数
5.为static pod创建mirror pod
6.创建相应的工作目录,如pod的工作目录、存储目录、插件plugins目录
7,等待volume挂载到pod的存储目录下
8.拉取下载container image的secret
9.调用kubeRuntimeManager.SyncPod为pod创建container
10.statusManager更新pod的状态
kubeRuntimeManager.SyncPod:
// 1. Compute sandbox and container changes. // 2. Kill pod sandbox if necessary. // 3. Kill any containers that should not be running. // 4. Create sandbox if necessary. // 5. Create ephemeral containers. // 6. Create init containers. // 7. Create normal containers.
这部分的修改不会导致pod重建
二cgroupmanager
cgroupmanager负责 node 节点上运行的容器的 cgroup 配置信息,kubelet 启动参数如果指定 --cgroups-per-qos
的时候,kubelet 会启动 goroutine 来周期性的更新 pod 的 cgroup 信息,维护其正确性,该参数默认为 true
,实现了 pod 的Guaranteed/BestEffort/Burstable 三种级别的 Qos。
目前 kubernetes 仅支持 cpu、memory、pids 、hugetlb 四个 cgroup 子系统
runtime 有两种 cgroup 驱动:一种是 systemd
,另外一种是 cgroupfs
:
cgroupfs
比较好理解,比如说要限制内存是多少、要用 CPU share 为多少,其实直接把 pid 写入到对应cgroup task 文件中,然后把对应需要限制的资源也写入相应的 memory cgroup 文件和 CPU 的 cgroup 文件就可以了;
另外一个是 systemd
的 cgroup 驱动,这个驱动是因为 systemd
本身可以提供一个 cgroup 管理方式。所以如果用 systemd
做 cgroup 驱动的话,所有的写 cgroup 操作都必须通过 systemd 的接口来完成,不能手动更改 cgroup 的文件;
Kubelet cgroup level:
/sys/fs/cgroup/cpu/kubepods – kubepods cgroup
/sys/fs/cgroup/cpu/kubepods/burstable – Qos level cgroup
/sys/fs/cgroup/cpu/kubepods/burstable/pod
– pod level cgroup /sys/fs/cgroup/cpu/kubepods/burstable/pod
/container – container level cgroup
1.cgroupmanager 会把本机的 allocatable 资源写入到 kubepods
下对应的 cgroup 文件中,比如 kubepods/cpu.share
2.
Qos level cgroup
有两个
cgroup
:
burstable
和
besteffort
,分别作为
burstable
级别
pod
和
besteffort
级别
pod
的父
cgroup
;而
Guaranteed Qos
对应的
pod
会直接在
kubepods
同级的
cgroup
中创建
pod cgroup,
原因:
guaranteed
级别的
pod
有明确的资源申请量
(request)
和资源限制量
(limit)
,不需要
qos
级别的
cgroup
来管理和限制资源
3.pod level cgroup
包含
pod
对应的
container level cgroup
4.
创建流程
kubepods>qos>pod>container
,设置流程
container->pod->qos->kubepods
,即设置好
container level cgroup
中各文件的限制,再层层往外更新其它
level
的限制
5.从级别和目录可以看出:burstable 的 cgroup 需要为比他等级高的 guaranteed 级别的 pod 的内存资源做预留,而 besteffort 需要为 burstable 和 guaranteed 都要预留内存资源。
例:
/sys/fs/cgroup/cpu/kubepods/burstable/pod
/container /cpu.shares=2 /sys/fs/cgroup/cpu/kubepods/burstable/pod
/cpu.shares=204 /sys/fs/cgroup/cpu/kubepods/burstable/cpu.shares=1126
/sys/fs/cgroup/cpu/kubepods/cpu.shares=49512
对于每一个 pod 设定的 requests 和 limits,kubernetes 都会转换为 cgroup 中的计算方式,CPU 的转换方式如下所示:
cpu.shares = (cpu in millicores * 1024) / 1000
cpu.cfs_period_us = 100000 (i.e. 100ms)
cpu.cfs_quota_us = quota = (cpu in millicores * 100000) / 1000
memory.limit_in_bytes
CPU 最终都会转换为以微秒为单位,memory 会转换为以 bytes 为单位
如果 pod 指定了 requests 和 limits,kubelet 会按以上的计算方式为 pod 设置资源限制,如果没有指定 limit 的话,那么 cpu.cfs_quota_us
将会被设置为 -1,即没有限制。而如果 limit 和 request 都没有指定的话,cpu.shares
将会被指定为 2,这个是 cpu.shares
允许指定的最小数值了,可见针对这种 pod,kubernetes 只会给它分配最少的 cpu 资源。而对于内存来说,如果没有 limit 的指定的话,memory.limit_in_bytes
将会被指定为一个非常大的值,一般是 2^64 ,可见含义就是不对内存做出限制。
参考:https://www.jianshu.com/p/924e3c48cb9b
三QOSContainerManager
QoS(Quality of Service) 即服务质量,QoS 是一种控制机制,它提供了针对不同用户或者不同数据流采用相应不同的优先级,或者是根据应用程序的要求,保证数据流的性能达到一定的水准。kubernetes 中有三种 Qos,分别为:
1、Guaranteed
:pod 的 requests 与 limits 设定的值相等;
2、Burstable
:pod requests 小于 limits 的值且不为 0;
3、BestEffort
:pod 的 requests 与 limits 均为 0;
三者的优先级如下所示,依次递增,OOM_SCORE_ADJ递减:
BestEffort -> Burstable -> Guaranteed
。
三种 Qos 在调度和底层表现上都不一样:
1、在调度时调度器只会根据 request 值进行调度;
2、二是当系统 OOM上时对于处理不同 OOMScore 的进程表现不同,OOMScore 是针对 memory 的,当宿主上 memory 不足时系统会优先 kill 掉 OOMScore 值低的进程,可以使用 $ cat /proc/$PID/oom_score
查看进程的 OOMScore。OOMScore 的取值范围为 [-1000, 1000],Guaranteed
pod 的默认值为 -998,Burstable
pod 的值为 2~999,BestEffort
pod 的值为 1000,也就是说当系统 OOM 时,首先会 kill 掉 BestEffort
pod 的进程,若系统依然处于 OOM 状态,然后才会 kill 掉 Burstable
pod,最后是 Guaranteed
pod;
3、三是 cgroup 的配置不同,kubelet 为会三种 Qos 分别创建对应的 QoS level cgroups,Guaranteed
Pod Qos 的 cgroup level 会直接创建在 RootCgroup/kubepods
下,Burstable
Pod Qos 的创建在 RootCgroup/kubepods/burstable
下,BestEffort
Pod Qos 的创建在 RootCgroup/kubepods/BestEffort
下,上文已经说了 root cgroup 可以通过 $ mount | grep cgroup
看到,在 cgroup 的每个子系统下都会创建 Qos level cgroups, 此外在对应的 QoS level cgroups 还会为 pod 创建 Pod level cgroups;
qos的计算方式:
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)
可见:pod的container使用memory越多,它的 OOMScore越小,越不容易被驱逐。
三 device manager
四topology manager
白话:为container的resource选择最优组合,如同组的cpuset,container所需的device来自同一个NUMA Node.
topology manager不是单独起作用的,当前k8s 1.17.3中,它和CPU manager,device manager共同为pod分配资源,优化资源访问。
topology manager从Hint Providers中以bitmask的形式接受NUMA拓扑信息(Topology Hint),包括当前可用的NUMA节点和倾向的资源分配指示。Topology manager策略对提供的hint进行一系列操作以后将所有的hint决定汇聚起来,得出一个最佳的hint(bestHint)。通过配置的策略(policy),主机节点可以选择接受或者拒绝pod。
topology manager当前所支持的策略有四个:None,BestEffort,Restricted和SingleNumaNode。
None是默认的策略,这个策略不会做任何的NUMA感知的资源对齐。
BestEffort对于pod里面的每一个container都会调用它们的hint provider来发现它们的资源可用性。topology manager计算存储这些container的NUMA亲和度的信息,如果亲和度不能被满足的话,manager也会存储亲和度信息同时也会允许pod加入这个主机节点。
Restricted同样也会对pod里面的每一个container计算NUMA亲和度。不同的是,如果亲和度不能被满足的话,主机节点会拒绝这个pod的加入。这样会导致pod处于Terminated的状态。如果pod被接受了,那么亲和度信息将会被用于container的资源分配。
SingleNumaNode策略用来判断单个NUMA节点的亲和度是否满足,如果满足的话hint provider即可利用亲和度信息进行资源分配;如果不满足的话主机节点会拒绝pod的加入,这样也会导致pod处于Terminated的状态。
参考:https://blog.csdn.net/qjm1993/article/details/103237944
五cpu manager
白话:根据NUMA为container选择最优的cpuset,让container从cpu方向得到更高的性能
目前CPU Manager支持两种Policy,分别为none和static,通过kubelet --cpu-manager-policy
设置,未来会增加dynamic policy做Container生命周期内的cpuset动态调整。
none: 为cpu manager的默认值,相当于没有启用cpuset的能力。cpu request对应到cpu share,cpu limit对应到cpu quota。
static: 目前,请设置--cpu-manager-policy=static
来启用,kubelet将在Container启动前分配绑定的cpu set,分配时还会考虑cpu topology来提升cpu affinity,后面会提到。
确保kubelet为--kube-reserved
和--system-reserved
都配置了值,可以不是整数个cpu,最终会计算reserved cpus时会向上取整。这样做的目的是为了防止CPU Manager把Node上所有的cpu cores分配出去了,导致kubelet及系统进程都没有可用的cpu了。
注意CPU Manager还有一个配置项--cpu-manager-reconcile-period
,用来配置CPU Manager Reconcile Kubelet内存中CPU分配情况到cpuset cgroups的修复周期。如果没有配置该项,那么将使用--node-status-update-frequency
(
default 10s
)
配置的值。
使用CPU Manager的Pod、Container具备以下两个条件:
Pod QoS为Guaranteed;
Pod中该Container的Cpu request必须为整数CPUs;
工作流程:
Kuberuntime调用容器运行时去create该Container。
创建完后,调用PreStartContainer将该Container交给CPU Manager处理。
创建业务container时,先把init container占用的cpu释放
CPU Manager为Container按照static policy逻辑进行处理。
CPU Manager从当前Shared Pool中挑选“最佳”Set拓扑结构的CPU,对于不满足Static Policy的Contianer,则返回Shared Pool中所有CPUS组成的Set。
挑选方式:根据Node上的NUMA CPU Topology划分cpu组,当container需要多个cpu时,让这几个cpu尽量来自同一个组(简单理解为一个插槽),这样同组访问能加快速度提高性能。
CPU Manager将对该Container的CPUs分配情况记录到Checkpoint State中,并且从Shared Pool中删除刚分配的CPUs。
CPU Manager再从state中读取该Container的CPU分配信息,然后通过UpdateContainerResources cRI接口将其更新到Cpuset Cgroups中,包括对于非Static Policy Container。
Kuberuntime调用容器运行时Start该容器。
Checkpoint文件内容就是CPUManagerCheckpoint结构体的json格式,其中Entries的key是ContainerID,value为该Container对应的Assigned CPU Set信息。
当这些通过CPU Manager分配CPUs的Container要Delete时,CPU Manager工作流大致如下:
Kuberuntime会调用CPU Manager去按照static policy中定义逻辑处理。
CPU Manager将该Container分配的Cpu Set重新归还到Shared Pool中。
Kuberuntime调用容器运行时Remove该容器。
CPU Manager会异步地进行Reconcile Loop,为使用Shared Pool中的Cpus的Containers更新CPU集合。
CPU Manager Reconcile按照--cpu-manager-reconcile-period
配置的周期进行Loop,Reconcile注意进行如下处理:
遍历所有activePods中的所有Containers,注意包括InitContainers,对每个Container继续进行下面处理。
检查该ContainerID是否在CPU Manager维护的Memory State assignments中,
如果不在Memory State assignments中:
再检查对应的Pod.Status.Phase是否为Running且DeletionTimestamp为nil,如果是,则调用CPU Manager的AddContainer对该Container/Pod进行QoS和cpu request检查,如果满足static policy的条件,则调用takeByTopology为该Container分配“最佳”CPU Set,并写入到Memory State和Checkpoint文件(cpu_manager_sate
)中,并继续后面流程。
如果对应的Pod.Status.Phase是否为Running且DeletionTimestamp为nil为false,则跳过该Container,该Container处理结束。不满足static policy的Containers因为不在Memory State assignments中,所以对它们的处理流程也到此结束。
如果ContainerID在CPU Manager assignments维护的Memory State中,继续后面流程。
然后从Memory State中获取该ContainerID对应的CPU Set。
最后调用CRI UpdateContainerCPUSet更新到cpuset cgroups中。
cpumanager调用State进行validate处理:
当Memory State中Shared(Default) CPU Set为空时,CPU Assginments也必须为空,然后对Memory State中的Shared Pool进行初始化,并写入到Checkpoint文件中(初始化Checkpoint)。
只要我们没有手动去删Checkpoint文件,那么在前面提到的state.NewCheckpointState中会根据Checkpoint文件restore到Memory State中,因此之前Assgned CPU Set、Default CPU Set都还在。
当检测到Memory State已经成功初始化(根据Checkpoint restore),则检查这次启动时reserved cpu set是否都在Default CPU Set中,如果不是(比如kube/system reserved cpus增加了),则报错返回,因为这意味着reserved cpu set中有些cpus被Assigned到了某些Container中了,这可能会导致这些容器启动失败,此时需要用户自己手动的去修正Checkpoint文件。
检测reserved cpu set通过后,再检测Default CPU Set和Assigned CPU Set是否有交集,如果有交集,说明Checkpoint文件restore到Memory State的数据有错,报错返回。
最后检查这次启动时从cAdvisor中获取到的CPU Topology中的所有CPUs是否与Memory State(从Checkpoint中restore)中记录的所有CPUs(Default CPU Set + Assigned CPU Set)相同,如果不同,则报错返回。可能因为上次CPU Manager停止到这次启动这个时间内,Node上的可用CPUs发生变化。
参考:https://blog.csdn.net/weixin_34050389/article/details/92574505