
k8s 调度
1、 k8s 架构
k8s的架构如图:

我们都知道k8s分为master、node,其中:
master 主要有如下几个组件:
Kubernetes Controller Manager(kube-controller-manager):Kubernetes资源对象的”大总管”,是所有资源对象的自动化控制中心,比如Deployment中的pod副本数
Kubernetes API Server (kube-apiserver):Kubernetes的“心脏”,是集群控制的入口进程,也是所有资源增、删、查、改等操作的唯一入口
Kubernetes Scheduler(kube-scheduler):Kubernetes的”调度室“,负责资源调度(Pod调度)
etcd:是用于共享配置和服务发现的分布式,一致性的KV存储系统,被用作Kubernetes集群后端数据的持久化存储
node 主要包含以下组件:
kubelet:负责Pod对应容器的创建、启停和销毁等任务
kube-proxy:与Kubernetes Service通信与负载均衡机制
Container Runtime:Docker Engine,负责本机容器的创建、启停与销毁等工作
2、k8s 调度
2.1 Pod 调度

这个过程看起来似乎比较简单,但实际生产环境的调度过程中,有很多问题需要考虑:
首先,如何保证全部计算节点调度的公平性?如何保证每个节点都能被分配资源?
其次,计算资源如何能够被高效利用?集群所有计算资源如何才能被最大化的使用?
再次,如何保证Pod调度的性能和效率?如何能够快速的对大批量的Pod完成调度到较优的计算节点之上?
最后,用户最了解自己的业务,用户是否可以根据实际需求定制自己的调度逻辑和策略?
2.2 Pod 调度过程
调度过程分为2个阶段:
第一阶段:预选过程,过滤节点,调度器用一组规则过滤掉不符合要求的主机。比如Pod指定了所需要的资源量,那么可用资源比Pod需要的资源量少的主机会被过滤掉。
第二阶段:优选过程,节点优先级打分,对第一步筛选出的符合要求的主机进行打分,在主机打分阶段,调度器会考虑一些整体优化策略,比如把容一个Replication Controller的副本分布到不同的主机上,使用最低负载的主机等。

代码位置(1.10 ):
https://github.com/kubernetes/kubernetes/tree/release-1.10/pkg/scheduler/algorithm
优选(Priorities)
经过预选策略(Predicates)对节点过滤,获取节点列表,再对符合需求节点列表进行打分,最终选择Pod调度到一个分值最高节点。
最终主机的得分用以下公式计算得出:
finalScoreNode = (weight1 * priorityFunc1) + (weight2 * priorityFunc2) + … + (weightn * priorityFuncn)

2.3 Node 定义
查看一个node的资源信息:
apiVersion: v1kind: Nodemetadata:labels:beta.kubernetes.io/arch: amd64beta.kubernetes.io/os: linuxkubernetes.io/hostname: node-n1name: node-n1spec:externalID: node-n1status:addresses:- address: 10.162.197.135type: InternalIPallocatable:cpu: "8"memory: 16309412Kipods: "110"capacity:cpu: "8"memory: 16411812Kipods: "110"conditions: {...}daemonEndpoints:kubeletEndpoint:Port: 10250images: {...}nodeInfo: {...}
2.4 Pod 定义
查看一个pod的资源信息:
kubectl explain pod.spec

我们看这个pod:
requests:申请范围是0到node节点的最大配置
limits:申请范围是requests到无限大,即0 <= requests <=Node Allocatable, requests <= limits <= Infinity。
说明:resoureces.limits影响pod的运行资源上限,不影响调度。
注释:
红色部分表示资源分配
浅蓝色表示采用的调度器
灰色表示普通调度策略
荧光色表示高级调度策略
2.5 k8s 调度器资源分配机制
基于Pod 中容器 request 资源“总和”调度
a. resoureces.limits 影响 pod 的运行资源上限,不影响调度
b. initContainer 取最大值,container 取累加值,最后两者中取大,即 Max( Max(initContainers.requests), Sum(containers.requests) )
c. 未指定request资源时(QoS Guaranteed除外), 按资源需求为0进行调度
基于资源声明量的调度,即:request 字段值,而非实际占用
i. 不依赖监控,系统不会过于敏感
ii. 能否调度成功:pod.request < node.allocatable - node.requested
Kubernetes node 资源的盒子模型

资源分配相关算法
A. GeneralPredicates(主要是PodFitsResources)
B. LeastRequestedPriority
C. BalancedResourceAllocation,平衡cpu/mem的消耗比例
3、k8s 调度策略
3.1 普通调度策略
nodeSelector【将来会被废弃】:将 Pod 调度到特定的 Node 上:
apiVersion: v1kind: Podmetadata:labels:pod-template-hash: "4173307778"run: my-podname: my-podnamespace: defaultspec:containers:- image: nginximagePullPolicy: Alwaysname: my-podports:- containerPort: 80protocol: TCPresources: {}nodeSelector:disktype: ssdnode-flavor: s3.large.2
匹配node.labels
排除不包含nodeSelector中指定label的所有node
匹配机制 :完全匹配
3.2 高级调度策略
1. podAffinity:让某些 Pod 分布在同一组 Node 上:
apiVersion: v1kind: Podmetadata:name: with-pod-affinityspec:affinity:podAffinity:requiredDuringSchedulingIgnoredDuringExecution:- labelSelector:matchExpressions:- key: securityoperator: Invalues:- S1topologyKey: kubernetes.io/zonepreferredDuringSchedulingIgnoredDuringExecution:- weight: 100podAffinityTerm:labelSelector:matchExpressions:- key: securityoperator: Invalues:- S2topologyKey: kubernetes.io/hostnamecontainers:- name: with-pod-affinityimage: k8s.gcr.io/pause:2.0
与nodeAffinity的关键差异:
定义在PodSpec中,亲和与反亲和规则具有对称性
labelSelector的匹配对象为Pod
对node分组,依据label-key = topologyKey,每个label-value取值为一组
硬性过滤规则,条件间只有逻辑与运算
硬性过滤:排除不具备指定pod的node组
requiredDuringSchedulingIgnoredDuringExecution:- labelSelector:matchExpressions:- key: securityoperator: Invalues:- S1topologyKey: kubernetes.io/zone
软性:不具备指定pod的node组打低分,降低该组node被选中的几率
- weight: 100podAffinityTerm:labelSelector:matchExpressions:- key: securityoperator: Invalues:- S2topologyKey: kubernetes.io/hostname
2. podAntiAffinity:避免某些 Pod 分布在同一组 Node 上:
apiVersion: v1kind: Podmetadata:name: with-pod-affinityspec:affinity:podAntiAffinity:requiredDuringSchedulingIgnoredDuringExecution:- labelSelector:matchExpressions:- key: securityoperator: Invalues:- S1topologyKey: kubernetes.io/zonepreferredDuringSchedulingIgnoredDuringExecution:- weight: 100podAffinityTerm:labelSelector:matchExpressions:- key: securityoperator: Invalues:- S2topologyKey: kubernetes.io/hostnamecontainers:- name: with-pod-affinityimage: k8s.gcr.io/pause:2.0
与podAffinity的差异:
匹配过程相同
最终处理调度结果时取反,podAffinity中可调度节点,在podAntiAffinity中为不可调度,podAffinity中高分节点,在podAntiAffinity中为低分
3. Taints:避免 Pod 调度到特定 Node 上:
apiVersion: v1kind: Nodemetadata:labels:beta.kubernetes.io/arch: amd64beta.kubernetes.io/os: linuxkubernetes.io/hostname: node-n1name: node-n1spec:externalID: node-n1taints:- effect: NoSchedulekey: acceleratortimeAdded: nullvalue: gpu
带effect的特殊label,对Pod有排斥性
1. 硬性排斥 NoSchedule(如果一个pod没有声明容忍这个Taint,则系统不会把该Pod调度到有这个Taint的node上)
2. 软性排斥 PreferNoSchedule(如果一个Pod没有声明容忍这个Taint,则系统会尽量避免把这个pod调度到这一节点上去,但不是强制的)
系统创建的taint附带时间戳
1. effect为NoExecute
2. 便于触发对Pod的超时驱逐
典型用法:预留特殊节点做特殊用途
kubectl taint node node-n1 foo=bar:NoSchedulekubectl taint node node-n1 foo:NoSchedule-
4. Tolerations:允许 Pod 调度到有特定 taints 的 Node 上:
apiVersion: v1kind: Podmetadata:labels:run: my-podname: my-podnamespace: defaultspec:containers:- name: my-podimage: nginxtolerations:- key: acceleratoroperator: Equalvalue: gpueffect: NoSchedule
可以无视排斥:
apiVersion: v1kind: Nodemetadata:labels:beta.kubernetes.io/arch: amd64beta.kubernetes.io/os: linuxkubernetes.io/hostname: node-n1name: node-n1spec:externalID: node-n1taints:- effect: NoSchedulekey: acceleratortimeAdded: nullvalue: gpu
完全匹配
例:<key>=<value>:<effect>
匹配任意taint value
Operator为Exists,value为空
例:<key>:<effect>
匹配任意 taint effect
effect为空
例:<key>=<value>
3.3 无调度器调度 pod
1. nodeName:将Pod手动调度到特定的 Node 上:

2. DaemonSet:
apiVersion: apps/v1kind: DaemonSetmetadata:name: my-daemonsetspec:selector:matchLabels:name: my-daemonsettemplate:metadata:labels:name: my-daemonsetspec:containers:- name: containerimage: k8s.gcr.io/pause:2.0
等同于:
apiVersion: apps/v1kind: Deploymentmetadata:name: my-deployspec:replicas: <# of nodes>selector:matchLabels:podlabel: daemonsetteplate:metadata:labels:podlabel: daemonsetspec:affinity:podAntiAffinity:requiredDuringSchedulingIgnoredDuringExecution:- labelSelector:matchExpressions:- key: podlabeloperator: Invalues:- daemonsettopologyKey: kubernetes.io/hostnamecontainers:- name: containerimage: k8s.gcr.io/pause:2.0
3.4 调度结果和失败原因分析
查看调度结果:
kubectl get po podname –o wide
查看调度失败原因:
kubectl describe po podname
调度错误列表:

3.5 自定义调度


例子:
https://kubernetes.io/blog/2017/03/advanced-scheduling-in-kubernetes/
前面讲的调度是指资源节点的调度,优先级也是指节点的优先级。高优先级的Pod会优先被调度,或者在资源不足低情况牺牲低优先级的Pod,以便于重要的Pod能够得到资源部署。
为了定义Pod优先级,需要先定义PriorityClass对象,该对象没有Namespace限制,官网示例:

然后通过在Pod的spec. priorityClassName中指定已定义的PriorityClass名称即可:

当节点没有足够的资源供调度器调度Pod、导致Pod处于pending时,抢占(preemption)逻辑会被触发。
Preemption会尝试从一个节点删除低优先级的Pod,从而释放资源使高优先级的Pod得到节点资源进行部署。
欢迎大家关注个站哟:damon8.cn。
最后介绍新公号:天山六路折梅手,欢迎关注。
往期回顾
ArrayList、LinkedList 你真的了解吗?
浅谈 Java 集合 | 底层源码解析
Spring Cloud Kubernetes之实战一配置管理
Spring Cloud Kubernetes之实战二服务注册与发现
Spring Cloud Kubernetes之实战三网关Gateway


关注公众号,回复入群,获取更多惊喜!公众号(程序猿Damon)里回复 ES、Flink、Java、Kafka、MQ、ML、监控、大数据、k8s 等关键字可以查看更多关键字对应的文章。
如有收获,点个在看,谢谢




