调度器概念

调度器作为单独的程序运行,启动后一直坚挺 APIServer,获取 PodSpec.NodeName 为空的 Pod,对每个 Pod 都会创建一个 Binding,表面该 Pod 应放在哪个节点上

自定义调度器

调度器可以自定义,通过:spec.schedulername 参数指定调度器名字,可以为 Pod 选择用哪个调度器

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  labels:
    app: myapp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      schedulerName: my-scheduler
      containers:
      - name: myapp
        image: docker.xuanyuan.run/nginx:alpine

运行后 pod 会是 pending 状态

因为调度器指定的my-scheduler,而我们没有这个调度器,所有没有调度器去调度这个 pod,所以就是pending 状态了

创建一个基于 shell 的自定义调度器

yum install -y epel-release jq
#!/bin/bash
SERVER='localhost:8001'

while true; do
    # 找到 schedulerName 为 my-scheduler 且未绑定 Node 的 Pod
    for PODNAME in $(kubectl --server $SERVER get pods -o json \
        | jq '.items[]
        | select(.spec.schedulerName == "my-scheduler")
        | select(.spec.nodeName == null)
        | .metadata.name' | tr -d '"')
    do
        # 获取所有 Node 列表
        NODES=($(kubectl --server $SERVER get nodes -o json \
            | jq '.items[].metadata.name' | tr -d '"'))

        NUMNODES=${#NODES[@]}
        CHOSEN=${NODES[$((RANDOM % NUMNODES))]}

        # 发送 Binding 对象
        curl -s \
            -H "Content-Type: application/json" \
            -X POST \
            -d '{
                "apiVersion": "v1",
                "kind": "Binding",
                "metadata": { "name": "'$PODNAME'" },
                "target": {
                    "apiVersion": "v1",
                    "kind": "Node",
                    "name": "'$CHOSEN'"
                }
            }' \
            http://$SERVER/api/v1/namespaces/default/pods/$PODNAME/binding/

        echo "Assigned $PODNAME to $CHOSEN"
    done

    sleep 1
done

调度过程

调度器是可以自定义的,但是一般不会这样做,因为官方的调度器很完善,通过多个阶段来匹配调度,所以直接用官方的就好了。

过滤阶段

可以理解为StatefulSet 的预选/优选

过滤不能运行该 Pod 的 Node

  • PodFitsResources: 节点资源大于 Pod 的请求的资源
  • PodFitsHost:Pod 如果指定了 Node,那就用指定的
  • PodFitsHostPorts:节点上已经使用的端口和 Pod 期望的 port 是否冲突
  • PodSelectorMatches:过滤和 pod 指定的 label 不匹配的节点
  • NoDiskConflict:已经挂载的卷和 Pod 指定的不能冲突
打分阶段
  • LeastRequestedPriority:倾向空闲资源多的节点
  • BalancedAllocation:cpu/内存都均衡的
  • ImageLocality:如果节点上有本地镜像的
绑定阶段

调度器执行

老版本

POST /api/v1/namespaces/{ns}/pods/{name}/binding

新版本

PATCH /api/v1/namespaces/{namespace}/pods/{name}

告诉 APiServer,这个 pod 应该绑定在哪个 Node 上

随后,APiServer 会把

Pod.Spec.NodeName = <NodeName>

进行更新,这是老版本的绑定阶段,大概在v1.15 前,新版本的会直接使用 PATCH 方式进行更新 NodeName,不单独创建 Binding 对象,这是一个优化,可以理解为,不用创建 Binding 对象到 etcd 数据库里,直接操作修改,操作更少,效率更高。

亲和性

节点亲和性,k8s 官方的调度器有自己的调度方案,通过过滤、打分,然后调度,但是有时候我就想这个 pod1 跑在 node1 上甚至 master 上,人的权限总归是大于机器的,所以就有了亲和性这个东西

pod.spec.nodeAffinity

- preferredDuringSchedulingIgnoredDuringExecution:软性策略
 你希望pod放在node1,但是一些其它原因导致不行,那就算了,调度器会进行预选优选选一个node运行pod
- requiredDuringSchedulingIgnoredDuringExecution:硬性策略
 你希望pod放在node1,但是一些其它原因导致不行,那也不行,必须放在node1上,不然就别运行了

node 亲和性

软策略

affinity:亲和性策略

preferredDuringSchedulingIgnoredDuringExecution:软策略

weight:权重,访问 1-100

preference:亲和类型,或者说偏好

matchExpressions:对应亲和类型,类型为匹配运算

然后是匹配 key 等于什么

operator:In 包含

除了 In 还有这些

matchLabels:只支持等值匹配(key=value)

matchExpressions:用来进行 更复杂的 label 匹配

- In:值在列表中
- NotIn:值不在列表中
- Exists:存在该 key
- DoesNotExist:不存在该 key

包含什么,包含values 是 test 的

这里匹配的是节点的标签

节点标签

kubectl get node --show-labels

apiVersion: v1
kind: Pod
metadata:
  name: node-affinity-preferred
  labels:
    app: node-affinity-preferred
spec:
  containers:
  - name: node-affinity-preferred-pod
    image: docker.xuanyuan.run/nginx:alpine
  affinity:
    nodeAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1
        preference:
          matchExpressions:
          - key: domain
            operator: In
            values:
            - test

指定了软策略,但是 node 没有符合条件的,那就算了,但也不是随便哪个 node 都行,都要进行调度的预选/优选,所以即使没有匹配,Pod 也 running 了

场景:

  1. 跨机房部署服务时,尽量用本地机房,减少延迟
  2. 一些 node 是机械硬盘,一些事固态,优先固态的
  3. 镜像本地化加速启动,优先本地
硬策略
apiVersion: v1
kind: Pod
metadata:
  name: node-affinity-required
  labels:
    app: node-affinity-required
spec:
  containers:
  - name: node-affinity-required-pod
    image: docker.xuanyuan.run/nginx:alpine
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/hostname
            operator: In
            values:
            - k8s-node02

因为是硬策略,选了 kubernetes.io/hostname,一定包含k8s-node02 的才运行,所以最后 pod 落在了k8s-node02 node 上

场景

  1. 一些业务可能依赖 GPU,但不是所有都有高性能显卡,有显卡的才能跑
  2. 安全性与合规,跨数据中心的业务场景,比如中国《——》新加坡的一个集群,国内产生的数据需要放置在本地,不允许出境。
添加 node 的标签
kubectl label node k8s-node02 domain=test

Pod 亲和性

其实跟 node 亲和性是一个概念,但是 pod 有反亲和性,node 没有

亲和性
pod.spec.podAffinity
反亲和性
pod.spec.podAntiAffinity


preferredDuringSchedulingIgnoredDuringExecution:软策略
requiredDuringSchedulinglgnoredDuringExecution:硬策略
topologykey

topologykey:拓扑域(指定 Pod 调度或分布约束的作用范围)

  • kubernetes.io/hostname:单节点
    • 按节点计算 Pod 的亲和/反亲和
    • 设置反亲和:
      • 比如有一个 redis 集群场景,运行 3 个副本,是反亲和性的 Pod 策略,topologykey:kubernetes.io/hostname,调度器打分时,会尽量选择不同节点部署 Pod 副本,同样如果是软的就是尽量,如果是硬的就是必须。
      • 意义:避免单节点故障导致的集群不可用
    • 设置亲和:
      • 有一个 Web 服务器与本地缓存服务的场景。Web 服务器 Pod 需要和本地的缓存服务 Pod 部署在同一节点上,以便通过本地通信(如 localhost 或共享内存)获得极低的访问延迟。同,软就是尽量,硬就是必须。
      • 意义:性能优化,业务延迟最低
  • topology.kubernetes.io/zone:区级别
    • 区级别,一般对应华东、华北这种区域
    • 设置反亲和
      • 一个 Mysql 主从集群,把主和从部署到不同区域,实现高可用。软尽量、硬必须。
      • 意义:跨区域实现高可用,避免单个区域故障导致不可用
    • 设置亲和
      • 一个 Web 应用和它的 Redis,分布式部署,配置亲和让它们离得最近,用户访问 web,因为没有配置亲和,redis 在成都,需要缓存时会从上海 web《——》成都 redis ,这样做肯定会提高延迟,设置区域级亲和,让上海的 web 调度到上海的 redis。
      • 意义:降低延迟
  • topology.kubernetes.io/region:域级别
    • 域级别,一般对应欧洲、亚洲这种区域
    • 同上。因为没有接触到这个级别,所有我也不知道:)

关于区级标签和域级标签,比较抽象

一般如果是云上,云供应商会打上区域标签,如果是本地自建,需要自己打标签,以代表它们的区域。

比如这样,看了这个图应该好理解些了

软策略

podAffinityTerm 在 pod 里

topologyKey: kubernetes.io/hostname 为单节点

apiVersion: v1
kind: Pod
metadata:
  name: pod-aff-prefer
  labels:
    app: pod-aff
spec:
  containers:
  - name: myapp
    image: docker.xuanyuan.run/nginx:alpine
  affinity:
    podAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: app
              operator: In
              values:
              - pod-1
          topologyKey: kubernetes.io/hostname

硬策略
apiVersion: v1
kind: Pod
metadata:
  name: pod-aff-req
  labels:
    app: pod-aff-req
spec:
  containers:
  - name: pod-aff-req-c
    image: docker.xuanyuan.run/nginx:alpine
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: app
            operator: In
            values:
            - pod-1
        topologyKey: kubernetes.io/hostname

不满足 Pending

Pod 反亲和性

反亲和软策略

如果有 Pod 有 app=pod-aff 这些标签,那就不在那一台 Node 上运行,但又同时,我这个 pod 又有pod-aff 这个标签

apiVersion: v1
kind: Pod
metadata:
  name: pod-antiaff-prefer
  labels:
    app: pod-aff
spec:
  containers:
  - name: myapp
    image: docker.xuanyuan.run/nginx:alpine
  affinity:
    podAntiAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: app
              operator: In
              values:
              - pod-aff
          topologyKey: kubernetes.io/hostname

总结

调度策略 匹配对象 操作符 是否依赖拓扑域(topologyKey) 调度目标
nodeAffinity Node(节点标签) In、NotIn、Exists、DoesNotExist、Gt、Lt 将 Pod 调度到符合特定 Node 标签的节点(或避免某些节点)
podAffinity Pod(Pod 标签) In、NotIn、Exists、DoesNotExist 是(必须指定 topologyKey) 将 Pod 调度到与目标 Pod 同一拓扑域 的节点上
podAntiAffinity Pod(Pod 标签) In、NotIn、Exists、DoesNotExist 是(必须指定 topologyKey) 将 Pod 调度到与目标 Pod 不同拓扑域 的节点上

污点和容忍

污点 Taint

容忍 Toleration

亲和性是 正向选择,打上标签后,调度器根据标签调度

污点和容忍是 反向隔离",需要时打标签,默认无污点

对比项 亲和/反亲和 污点/容忍
调度机制 正向筛选 反向隔离
是否必须打标签 Pod 或 Node 都需要标签 按需使用,默认无污点
控制方式 “我想去哪” “我不能去哪(除非容忍)”
默认行为 不指定就都能调度 不加污点 → 所有 Pod 都能去
应用场景 定位节点、性能优化、副本分布 保护节点、专用节点、隔离节点

污点(Taint)和容忍(Toleration)是成对使用的调度控制机制:

  • 污点加在 Node 上,用于“排斥”没有相应容忍的 Pod。一个节点可以有多个污点,每个污点都会阻止未容忍该污点的 Pod 被调度到该节点。
  • 容忍加在 Pod 上,用于“允许”该 Pod 调度到带有特定污点的节点。

污点

key=value:effect

每个污点有一个 key 和一个 value 作为污点标签,value 可为空

策略

  • NoSchedule:表示不会调度 Pod 到具有污点的 Node 上
  • PreferNoSchedule:尽量避免调度 Pod 到有污点 Node 上
  • NoExecute:不会调度 Pod 到具有污点的 Node,且将已存在的 Pod 驱赶出去

master 上默认会有一个污点,所以我们的 Pod 从来没有部署到 master 上

master 其实是一个概念,指定的一台主机为 master,并且将主机名命为 master(方便管理,主机名并不能决定它就是 master),在这台叫 master 的主机上部署 k8s,他就成了真的 master 了,上面运行了kube-apiserver、etcd 等容器,所以他就是 master,如果我们在另一台 node 上也运行 apiserver、etcd 等容器,那这台叫 node 的节点也可以成为 master,并且在配置了 kube config 配置文件,有 kubectl 工具的话也可以调用下面的其它 node,所以有 kubectl get node 这种命令,没有 get master 这种命令

污点的 key 叫:node-role.kubernetes.io/control-plane

value 为空,所以这里没写

策略为NoSchedule表示不会调度 Pod 到具有污点的 Node 上,所以删除它,就会有 pod 被调度到 master 这台主机上了

查询污点可以通过查询 node 的详细信息看到

kubectl describe node  k8s-master01

设置污点与移除污点
kubectl taint node k8s-master01 key=value:NoSchedule

移除污点

在设置的命令上加一个减号就是移除

kubectl taint node k8s-master01 key=value:NoSchedule-

注意,如果有些为空的污点,不会显示,但是在删除的时候需要加上等于

比如污点查询出来是这个

node-role.kubernetes.io/control-plane:NoSchedule

删除的时候需要加上=

node-role.kubernetes.io/control-plane=:NoSchedule-

容忍

master 默认上不运行 pod 的,但是这些因为是 master 的核心组件,它们配置了容忍,所以可以在 master 上运行

比如 etcd

设置了污点的 node 将根据污点的策略产生互斥,而通过配置 Pod 的容忍,那就可以调度了

配置容忍

operator 匹配方式

  • Equal:精确匹配 key 和 value
  • Exists:只要存在 key 就容忍,所以可以不写 value,如果都不写,代表容忍所有污点

精确匹配,key:value

tolerations:
- key: "key1"
  operator: "Equal"
  value: "value1"
  effect: "NoSchedule"

只匹配 key

- key: "key2"
  operator: "Exists"
  effect: "NoSchedule"

都不匹配,全部容忍

tolerations:
  - operator: "Exists"

如果有多 master 场景,或者资源很紧张,但是也没办法加 node, 在资源利用和稳定性中取个中间值比较合理,比如

将 master01配置为PreferNoSchedule(尽可能不调度 Pod )

kubectl taint node k8s-master01 node-role.kubernetes.io/control-plane=:PreferNoSchedule

固定节点调度

跳过调度器的预选/优选、亲和、污点这种策略,根据配置文件在指定的 pod 上运行 pod,场景:node2 上有高性能 GPU 用于 AI 训练,其它 node 上没有,通过直接指定就可以运行在 node2 上了,即使是 master 也行。

nodename

nodeName: k8s-node01 指定运行的 node

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nodename-test
spec:
  replicas: 7
  selector:
    matchLabels:
      app: nodename
  template:
    metadata:
      labels:
        app: nodename
    spec:
      nodeName: k8s-node01
      containers:
        - name: myweb
          image: docker.xuanyuan.run/nginx:alpine
          ports:
            - containerPort: 80

nodeselect

指定运行 node,通过标签来指定

设置了一个 Pod 的标签:key=app,value=nodeselect,跟调度没关系,只是标识这个 pod 的标签

nodeSelector 节点选择器

  • type: nodeselect

代表只会调度到有type: nodeselect 这个标签的节点上运行

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nodeselect-test
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nodeselect
  template:
    metadata:
      labels:
        app: nodeselect
    spec:
      nodeSelector:
        type: nodeselect
      containers:
        - name: myweb
          image: docker.xuanyuan.run/nginx:alpine
          ports:
            - containerPort: 80

添加 node 的标签
kubectl label node k8s-node01 type=nodeselect
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐