【Kubernetes in Action读书笔记】通过测试用例探索调度器Scheduler调度Pod的过程
知识点
- (11.1.5)调度器Scheduler利用API服务器的监听机制,等待新建Pod的事件,然后为每个新的还没有节点的Pod分配节点
- (11.1.5)调度器不会命令选中的节点(或者节点上运行的Kubelet)去创建Pod,而是通过API服务器更新Pod的定义,然后由API服务器再去通知Kubelet(同样通过之前描述的监听机制〕该Pod己经被调度过。当目标节点上的Kubelet发现该Pod将被调度到这里,它就会创建并且运行Pod的容器
- (14.1)通过指定**容器(注意⚠️不是Pod)**对CPU资源和内存资源的请求量(即
requests
、最小需求量?)以及限制量(即limits
)来确保Pod公平地使用Kubernetes集群资源 - (14.1)
requests
不会限制容器(最多、最大)可以使用的CPU数量 - (14.1)调度器Scheduler在将Pod调度到节点的过程中只?会用到
requests
中的信息。Scheduler在调度时只考虑那些未分配资源量满足Pod需求量的节点 - (14.1)Scheduler在调度时并不关注各类资源在当前时刻的实际使用量,而只关心节点上部署的所有Pod的资源申请量(
requests
)之和。如果使用基于实际资源消耗量的调度算法,将打破系统为那些已部署成功的Pods提供足够资源的保证 - Kubernetes将Pod划分为3种QoS等级:BestEffort(优先级最低);Burstable;Guaranteed (优先级最高)
通过测试用例探索3种QoS等级的Pod如何调度
先来看一下测试用例的框架。
【待确认:选择节点的过程是否只涉及Fit
这个Plugin】
// kubernetes/pkg/scheduler/framework/plugins/noderesources/fit_test.go
func TestEnoughRequests(t *testing.T) {
enoughPodsTests := []struct {
pod *v1.Pod
nodeInfo *framework.NodeInfo
name string
args config.NodeResourcesFitArgs
wantInsufficientResources []InsufficientResource
wantStatus *framework.Status
}{
// 每个testcase一个struct literal
}
for _, test := range enoughPodsTests {
t.Run(test.name, func(t *testing.T) {
node := v1.Node{
Status: v1.NodeStatus{
Capacity: makeResources(10, 20, 32, 5, 20, 5).Capacity,
Allocatable: makeAllocatableResources(10, 20, 32, 5, 20, 5)
}
}
test.nodeInfo.SetNode(&node) // ①
// ...
p, err := NewFit(&test.args, nil, plfeature.Features{}) // ②
// ...
gotStatus := p.(framework.FilterPlugin).Filter(context.Background(), cycleState, test.pod, test.nodeInfo) // ③
// 对比 gotStatus 和 test.wantStatus
gotInsufficientResources := fitsRequest(computePodResourceRequest(test.pod), test.nodeInfo, p.(*Fit).ignoredResources, p.(*Fit).ignoredResourceGroups) // ④
// 对比 gotInsufficientResources 和 test.wantInsufficientResources
}
})
}
}
首先①创建了一个节点,并给出了该节点拥有的CPU核数和内存大小等资源。
②新建了一个名为Fit
的interface framework.Plugin
。Plugin
是FilterPlugin
等接口的父接口,所有下面才有p.(framework.FilterPlugin)
这样的类型转换。
③Filter()
的核心逻辑是检查给定的Node是否能提供足够的资源。函数fitsRequest()
的实现很直观,就是简单的数值比较。如果insufficientResources
为空,表示Node能提供Pod所需的资源;不为空时,Filter()
会将缺乏的资源(类型为InsufficientResource
)逐一转换成调度失败的原因insufficientResources[i].Reason
。
// kubernetes/pkg/scheduler/framework/plugins/noderesources/fit.go
// Filter invoked at the filter extension point.
// Checks if a node has sufficient resources, such as cpu, memory, gpu, opaque int resources etc to run a pod.
// It returns a list of insufficient resources, if empty, then the node has all the resources requested by the pod.
func (f *Fit) Filter(ctx context.Context, cycleState *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status {
// ...
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 i := range insufficientResources {
failureReasons = append(failureReasons, insufficientResources[i].Reason)
}
return framework.NewStatus(framework.Unschedulable, failureReasons...)
}
return nil
}
为了对比InsufficientResource
类型的值,④这里又调用了一遍函数fitsRequest()
。
了解了框架以后,就可以来看一些测试用例了。
BestEffort等级——没有设置任何requests
和limits
的Pod如何调度
BestEffort等级意味着:
- 容器没有任何资源保证,在最坏情况下,它们分不到任何CPU时间
- 在需要为其他Pod释放内存时,这些容器会第一批被杀死
- 不过因为BestEffort级别的Pod没有配置内存
limits
,当有充足的可用内存时,这些容器可以使用任意多的内存
// kubernetes/pkg/scheduler/framework/plugins/noderesources/fit_test.go
{
pod: &v1.Pod{},
nodeInfo: framework.NewNodeInfo(
newResourcePod(framework.Resource{MilliCPU: 10, Memory: 20})),
name: "no resources requested always fits",
wantInsufficientResources: []InsufficientResource{},
},
framework.NewNodeInfo(newResourcePod(framework.Resource{MilliCPU: 10, Memory: 20}))
表示节点已经使用了10毫核的CPU和20(KB?)的资源。
从这个测试用例可以看出,BestEffort等级的Pod总是能够调度成功。
Burstable等级和Guaranteed等级的Pod如何调度
由于调度时Scheduler只参考Pod的资源申请量(requests
),所以Burstable等级(既非BestEffort等级、也非Guaranteed等级)和Guaranteed等级(所有资源的request
和limits
都相等) 的Pod此时没有差异。
成功调度的用例。
{
pod: newResourcePod(framework.Resource{MilliCPU: 1, Memory: 1}),
nodeInfo: framework.NewNodeInfo(
newResourcePod(framework.Resource{MilliCPU: 5, Memory: 5})),
name: "both resources fit",
wantInsufficientResources: []InsufficientResource{},
},
失败调度的用例。
{
pod: newResourcePod(framework.Resource{MilliCPU: 1, Memory: 1}),
nodeInfo: framework.NewNodeInfo(
newResourcePod(framework.Resource{MilliCPU: 10, Memory: 20})),
name: "too many resources fails",
wantStatus: framework.NewStatus(framework.Unschedulable, getErrReason(v1.ResourceCPU), getErrReason(v1.ResourceMemory)),
wantInsufficientResources: []InsufficientResource{
{ResourceName: v1.ResourceCPU, Reason: getErrReason(v1.ResourceCPU), Requested: 1, Used: 10, Capacity: 10},
{ResourceName: v1.ResourceMemory, Reason: getErrReason(v1.ResourceMemory), Requested: 1, Used: 20, Capacity: 20},
},
},
附
apiVersion: v1
kind: Pod
metadata:
name: requests-pod
spec:
containers:
- image: busybox
command: ["dd", "if=/dev/zero", "of=/dev/null"]
name: main
resources:
requests:
cpu: 200m
memory: 10Mi
在minikube中,修改Scheduler的日志级别
minikube.sigs.k8s.io/docs/handbo…
$ minikube start --extra-config=scheduler.v=8