kubernetes调度策略

辰星2年前技术文章452

1、背景

在 Kubernetes 中,调度 (scheduling) 指的是确保 Pod 匹配到合适的节点,以便 kubelet 能够运行它们。调度的工作由调度器和控制器协调完成。

调度器通过 Kubernetes 的监测(Watch)机制来发现集群中新创建且尚未被调度到节点上的 Pod。调度器会将所发现的每一个未调度的 Pod 调度到一个合适的节点上来运行。调度器会依据下文的调度原则来做出调度选择。控制器则会将调度写入 Kubernetes 的 API Server 中。

一般而言是基于最大资源空闲率的均衡调度来实现,我们还可以基于自己的调度行为、调度标识影响预选和优选的调度结果,进而来完成高级调度行为。

2、操作前了解相关配置和要求

Pod 调度流程:


当用户向 APIServer 请求创建 Pod 时,APIServer 检查相关权限没问题后,将请求交给 Scheduler,由 Scheduler 在众多节点当中,选择一个匹配的节点做为运行此 Pod 的工作节点。Scheduler 的选择结果并不直接反映到节点之上,而是会告诉 APIServer ,并将相关信息保存到 etcd 当中。APIServer 指挥着被选定节点的 kubelet,或者说 kubelet 始终 Watch 着 APIServer 当中与当前节点相关联的事件变动,进而根据配置模版运行 Pod。


Pod 调度步骤:


  1. Predicate(预选)
    先排除完全不符合 Pod 运行法则的节点(最低资源需求、最大资源限额 limit)

  1. Priority(优选)
    基于一系列的算法函数,计算节点的优先级,找出最佳匹配节点

  1. Select(选定)
    将 Pod 绑定在优选后的节点之上


Pod 高级调度:


一般而言是基于最大资源空闲率的均衡调度来实现,我们还可以基于自己的调度行为、调度标识影响预选和优选的调度结果,进而来完成高级调度行为。


  • 偶尔有特殊偏好的 Pod 要运行在特定节点(SSD 节点、GPU 节点)之上,可以对节点使用标签进行分类

$ kubectl explain pods.spec
......
   nodeName     <string>
   nodeSelector <map[string]string>   # 预选
......

  • 节点亲和性/反亲和性调度

  • Pod 亲和性/反亲和性调度(某一组 Pod 运行在同一节点或相邻节点)

  • 污点、污点容忍调度(Taints-nodes,Tolerations-pod)

常见的预选策略:


  • CheckNodeCondition
    检查节点是否可以在磁盘、网络不可用或未准备好的前提下把 Pod 调度到此节点

  • GeneralPredicates(通用预选策略)

    • HostName:检查 Pod 对象是否定义 pod.spec.hostname

    • PodFitsHostPort:检查 Pod 对象是否定义 pod.spec.containers.ports.hostPort

    • MatchNodeSelector:检查节点标签是否适配 pods.spec.nodeSelector

    • PodFitsResources:检查 Pod 的资源需求是否能被节点所满足

  • NoDiskConflict(默认没有启用)
    检查 Pod 依赖的存储卷能否能满足需求

  • PodToleratesNodeTaints
    检查 Pod 上的 spec.tolerations  可容忍的污点是否完全包含节点上的污点

  • PodToleratesNodeNoExecuteTaints(默认没有启用)
    默认节点污点变动后,容忍之前调度在该节点之上的 Pod 存在。而该项则不容忍 Pod,会驱离 Pod

  • CheckNodeLabelPresence(默认没有启用)
    检查节点标签存在性

  • CheckserviceAffinity(默认没有启用)
    检查服务亲和性,将相同 Service 的 Pod 对象尽可能放在一块

  • MaxEBSVolumeCount
    亚马逊弹性存储卷最大数量,默认 39

  • MaxGCEPDVolumeCount
    谷歌容器引擎最大存储卷数量,默认 16

  • MaxAzureDiskVolumeCount
    Azure 最大磁盘数量,默认 16

  • CheckVolumeBinding
    检查节点上已绑定和未绑定的 PVC

  • NoVolumeZoneConflict
    没有数据卷空间冲突(逻辑限制)

  • CheckNodeMemoryPressure
    检查节点是否存在内存压力

  • CheckNodePIDPressure
    检查节点是否存在进程压力

  • CheckNodeDiskPressure
    检查节点是否存在磁盘压力

  • MatchInterPodAffinity
    检查节点是否能满足 Pod 亲和性或反亲和性条件

常见的优选函数:


  • LeastRequested

# 节点空闲资源/节点总容量: 根据空闲比例评估
(cpu(capacity-sum(requested))*10/capacity)+(memory(capacity-sum(requested))*10/capacity)/2

capacity-sum(requested) # (总容量-已经被pod拿走的总容量)*10/总容量。*10是因为每一个优选函数的得分是10分,最后计算得分。

  • BalancedResourceAllocation
    CPU 和内存资源的被占用率相近的胜出,目的是平衡节点资源的使用率。

  • NodePreferAvoidPods
    节点亲向不要运行这个 Pod(优先级高),根据节点注解信息 "scheduler.alpha.kubernetes.io/preferAvoidPods" 判定,没有注解信息得分为 10,权重为 10000,存在此注解信息时,由控制器管控的 Pod 得分为 0。

  • TaintToleration
    将 Pod 对象的 spec.tolerations 与节点的 taints 列表项进行匹配度检查,匹配的条目越多得分越低。

  • SelectorSpreading: 调度器将 pod 分散调度。

  • InterPodAffinity: 根据 Pod 间的亲和性。

  • NodeAffinity: 根据节点亲和性。

  • MostRequested: 根据最多被请求的节点。

  • NodeLabel: 根据节点标签。

  • ImageLocality: 根据满足当前 Pod 对象需求的已有镜像的体积大小之和。


3、操作步骤


高级调度设计机制:


  • 节点选择器:nodeSelector

  • 节点亲和器:nodeAnffinity

  • 污点容忍:taints / tolerations


3.1 nodeSelector


"新增节点标签"
$ kubectl label nodes worker2 disk=ssd
node/worker2 labeled

"创建测试Pod"
$ cat demo-pod.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: demo-pod
  namespace: qingyun
spec:
  containers:
  - name: myapp
    image: zhangyyhub/myapp:v1.0
    imagePullPolicy: IfNotPresent
  nodeSelector:  # 节点选择
    disk: ssd

$ kubectl apply -f demo-pod.yaml 
pod/demo-pod created

"验证测试Pod: 在节点标签所在节点之上"
$ kubectl get pods -n qingyun -o wide
NAME       READY   STATUS    RESTARTS   AGE   IP              NODE      NOMINATED NODE   READINESS GATES
demo-pod   1/1     Running   0          14s   10.244.189.98   worker2   <none>           <none>


3.2 nodeAnffinity


"nodeAnffinity"
$ kubectl explain pods.spec.affinity
   nodeAffinity <Object>
      preferredDuringSchedulingIgnoredDuringExecution      <[]Object>  # 软亲和性,尽量满足
         preference   <Object> -required-
            matchExpressions     <[]Object>      # 匹配表达式
            matchFields  <[]Object>              # 匹配字段
               key  <string> -required-          # key
               operator     <string> -required-  # 操作符(In, NotIn, Exists, DoesNotExist, Gt, and Lt.)
               values       <[]string>           # value
         weight       <integer> -required-
      requiredDuringSchedulingIgnoredDuringExecution       <Object>    # 硬亲和性,必须满足
         nodeSelectorTerms    <[]Object> -required-
            matchExpressions     <[]Object>      # 匹配表达式
            matchFields  <[]Object>              # 匹配字段
               key  <string> -required-          # key
               operator     <string> -required-  # 操作符(In, NotIn, Exists, DoesNotExist, Gt, and Lt.)
               values       <[]string>           # value
   podAffinity  <Object>
   podAntiAffinity      <Object>


实例:


"创建测试Pod"
cat demo-pod-nodeAffinity.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: demo-pod-affinity
  namespace: qingyun
spec:
  containers:
  - name: myapp
    image: zhangyyhub/myapp:v1.0
    imagePullPolicy: IfNotPresent
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:  # 硬亲和性,必须满足
        nodeSelectorTerms:
        - matchExpressions:    # 匹配表达式
          - key: disk          # key
            operator: In       # 操作符
            values:            # value
            - ssd
            - essd

$ kubectl apply -f demo-pod-nodeAffinity.yaml
pod/demo-pod-affinity created

"验证测试Pod"
$ kubectl get pods -n qingyun -o wide
NAME                READY   STATUS    RESTARTS   AGE   IP               NODE      NOMINATED NODE   READINESS GATES
demo-pod            1/1     Running   0          41m   10.244.189.98    worker2   <none>           <none>
demo-pod-affinity   1/1     Running   0          19s   10.244.189.123   worker2   <none>           <none>


"部署测试Pod"
$ cat demo-pod-nodeAffinity2.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: demo-pod-affinity2
  namespace: qingyun
spec:
  containers:
  - name: myapp
    image: zhangyyhub/myapp:v1.0
    imagePullPolicy: IfNotPresent
  affinity:
    nodeAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:  # 软亲和性,尽量满足
      - preference:
          matchExpressions:    # 匹配表达式
          - key: disk          # key
            operator: In       # 操作符
            values:            # value
            - hssd
            - essd
        weight: 60
        
$ kubectl apply -f demo-pod-nodeAffinity2.yaml
pod/demo-pod-affinity2 created

"验证测试Pod"
$ kubectl get nodes --show-labels | grep hssd
$ kubectl get nodes --show-labels | grep essd

$ kubectl get pods -n qingyun -o wide
NAME                 READY   STATUS    RESTARTS   AGE   IP               NODE      NOMINATED NODE   READINESS GATES
demo-pod             1/1     Running   0          52m   10.244.189.98    worker2      <none>           <none>
demo-pod-affinity    1/1     Running   0          10m   10.244.189.123   worker2      <none>           <none>
demo-pod-affinity2   1/1     Running   0          12s   10.244.235.255   k8s-master   <none>           <none>


3.3 podAffinity


Pod 亲和性/反亲和性调度:一组 Pod 运行在一起,允许调度器将第一个 Pod 随机选择一个节点,第二个 Pod 根据第一个 Pod 实现调度。


可以根据标签分类:机架(rack)、一排(row)、机房(zone),来判断是否为同一位置。


"podAnffinity"
$ kubectl explain pods.spec.affinity
   nodeAffinity <Object>
   podAffinity  <Object>
      preferredDuringSchedulingIgnoredDuringExecution      <[]Object>  # 软亲和性,尽量满足
         preference   <Object> -required-
            matchExpressions     <[]Object>      # 匹配表达式
            matchFields  <[]Object>              # 匹配字段
               key  <string> -required-          # key
               operator     <string> -required-  # 操作符(In, NotIn, Exists, DoesNotExist, Gt, and Lt.)
               values       <[]string>           # value
         weight       <integer> -required-
      requiredDuringSchedulingIgnoredDuringExecution       <[]Object>  # 硬亲和性,必须满足
         labelSelector        <Object>           # 选定要亲和的Pod
            matchExpressions     <[]Object>
            matchLabels  <map[string]string>
         namespaceSelector    <Object>           # 选定要亲和的Namespace
         namespaces   <[]string>                 # 选定要亲和的Namespace
         topologyKey  <string> -required-        # 位置拓扑键
   podAntiAffinity      <Object>


实例: podAffinity


"创建测试Pod"
$  cat demo-pod-podAffinity.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-frontend
  namespace: qingyun
  labels:
    app: myapp
    tier: frontend
spec:
  containers: 
  - name: myapp
    image: zhangyyhub/myapp:v1.0
    imagePullPolicy: IfNotPresent
---
apiVersion: v1
kind: Pod
metadata:
  name: pod-afterend
  namespace: qingyun
  labels:
    app: db
    tier: afterend
spec:
  containers:
  - name: myapp
    image: zhangyyhub/myapp:v1.0
    imagePullPolicy: IfNotPresent
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:  # 硬亲和性,必须满足
      - labelSelector:
          matchExpressions:                  # 匹配表达式
          - key: app                         # key
            operator: In                     # 操作符
            values:                          # value
            - myapp
        topologyKey: kubernetes.io/hostname  # 位置拓扑键

$ kubectl apply -f demo-pod-podAffinity.yaml
pod/pod-frontend unchanged
pod/pod-afterend created

"验证测试Pod"
$ kubectl get pods -n qingyun -o wide
NAME           READY   STATUS    RESTARTS   AGE     IP               NODE      NOMINATED NODE   READINESS GATES
pod-afterend   1/1     Running   0          14s     10.244.235.141   worker1   <none>           <none>
pod-frontend   1/1     Running   0          3m26s   10.244.235.171   worker1   <none>           <none>


实例: podAntiAffinity


"创建测试Pod"
$ cat demo-pod-podAffinity2.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-frontend
  namespace: qingyun
  labels:
    app: myapp
    tier: frontend
spec:
  containers: 
  - name: myapp
    image: zhangyyhub/myapp:v1.0
    imagePullPolicy: IfNotPresent
---
apiVersion: v1
kind: Pod
metadata:
  name: pod-afterend
  namespace: qingyun
  labels:
    app: db
    tier: afterend
spec:
  containers:
  - name: myapp
    image: zhangyyhub/myapp:v1.0
    imagePullPolicy: IfNotPresent
  affinity:
    podAntiAffinity:   # Pod反亲和
      requiredDuringSchedulingIgnoredDuringExecution:  # 硬亲和性,必须满足
      - labelSelector:
          matchExpressions:
          - key: app
            operator: In
            values:
            - myapp
        topologyKey: kubernetes.io/hostname

$ kubectl apply -f demo-pod-podAffinity2.yaml
pod/pod-frontend created
pod/pod-afterend created

"验证测试Pod"
$ kubectl get pods -n qingyun -o wide
NAME           READY   STATUS    RESTARTS   AGE   IP               NODE         NOMINATED NODE   READINESS GATES
pod-afterend   1/1     Running   0          14s   10.244.235.143   worker1      <none>           <none>
pod-frontend   1/1     Running   0          14s   10.244.235.210   k8s-master   <none>           <none>


3.4 容忍调度


容忍调度/污点调度:


kubectl explain nodes.spec.taints
   effect        <string> -required-  # 定义对Pod排斥效果
      NoSchedule         # 仅影响调度过程,对现有Pod不影响
      PreferNoSchedule   # 最好不影响调度,如果必须调度也行
      NoExecute          # 即影响调度过程,也影响现有的Pod
   key  <string> -required-
   timeAdded     <string>
   value         <string>


实例:


"打污点:生产环境专用"
$ kubectl taint node worker1 node-type=production:NoSchedule


apiVersion: v1
kind: Deployment
metadata:
  name: deploy-myapp
  namespace: qingyun
spec:
  replicas: 2
  selector:
    matchLabels:
      app: myapp
      release: canary
  template:
    metadata:
      labels:
        app: myapp
        release: canary
    spec:
      containers:
      - name: myapp
        image: zhangyyhub/myapp:v1.0
        imagePullPolicy: IfNotPresent
      tolerations:
      - key: "node-type"
        operator: "Equal"        # Exists(污点存在就行不在乎值) and Equal(等值比较)
        value: "production"
        effect: "NoExecute"      # 即影响调度过程,也影响现有的Pod
        tolerationSeconds: 3600


4、注意事项


注意 kubernetes 调度策略添加,不要影响现有现有环境。

5、结果检查


分别使用节点选择器、节点亲和器和节点容忍进行测试。


相关文章

REPMGR-PG高可用搭建(三)

REPMGR-PG高可用搭建(三)

2.2.2repmgr安装兼容性3节点均安装repmgr1.安装依赖 # yum install flex 2.下载解压 # wget -c https://repmgr.org/downloa...

Flink sql 集成hive metastore-测试

Flink sql 集成hive metastore-测试

FQA1、如何不使用catalog命令,默认进入hive catalog在sql-client-defaults.yaml中添加以下配置2、如何使用mysql使用./bin/sql-client.sh...

Redis 运维规范_运维管理规范

三、运维管理规范1、密码认证 云上 Redis 的权限控制:账号管理+白名单设置+阿里云子账号权限。对于线下 Redis 可以通过设置密码和 bind 参数文件控制访问。2、合理设置备份策略 Redi...

MySQL运维实战(7.2) MySQL复制server_id相关问题

MySQL运维实战(7.2) MySQL复制server_id相关问题

主库server_id没有设置主库没有设置server_idGot fatal error 1236 from master when&nb...

Scylladb部署

Scylladb部署一、部署在centos 7.9上部署scylla-4.2下面步骤都需要root权限或者sudo权限1、添加scylladb 回购文件和yum源yum install epel-re...

解决grafana服务无法停止问题

解决grafana服务无法停止问题

背景:grafana服务无法停止,无论使用什么方式,哪怕使用kill -9 ,杀掉进程都会重新启动解决办法:1、将grafana加到系统服务里去systemctl enable grafana.ser...

发表评论    

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。