《Kubernetes权威指南》学习笔记第九篇-Pod调度

1、控制器使用场景

一般Pod不会单独进行创建,而是通过控制器来进行创建,但是控制器有很多种,针对Pod需要调度的使用场景需要使用不同的控制器,下面总结了kubernetes中常见控制器

控制器 描述 场景
Deployment 管理ReplicaSet来创建无状态Pod副本 可以调度Pod副本到任一节点
StatefulSet 创建有状态的Pod副本 各类有数据变化的集群,如数据库集群、消息队列集群
DaemonSet 每个节点上调度有且仅有一个Pod副本 常用于系统监控的Pod
CronJob 创建多个Pod副本协同工作 批处理作业

通过控制器创建的Pod归属这些控制器,如果删除了这些控制器,默认情况下归属的相关Pod也会被删除,如果只想删除控制器而不想删除Pod请添加参数--cascade=false

2、Deployment全自动调度

原理:Deployment通过ReplicaSet创建Pod副本,自动部署一个容器应用的多份副本并持续监控副本数量并维持该副本数量

cat nginx-deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment   # Deployment控制器名称
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80

kubectl create -f nginx-deployment.yaml
kubectl get pods

nginx-deployment-7848d4b86f-m4dtx 1/1 Running 0 3m42s
nginx-deployment-7848d4b86f-mhffq 1/1 Running 0 3m42s
nginx-deployment-7848d4b86f-sb5vv 1/1 Running 0 3m42s

kubectl get deployments 查看Deployment

nginx-deployment 3/3 3 3 7m7s

kubectl get rs查看ReplicaSet

nginx-deployment-7848d4b86f 3 3 3 9m38s

从调度策略上讲,以上3个nginxPod副本又master上的Scheduler经过一系列算法来调度,人为无法干预,但如果用户想自定义调度就需要在yaml文件中进行一些字段的设置,下面一一进行介绍

3、自定义调度

3.1.NodeSelector

两步走

  • 给想要调度Pod的目标节点设置标签
  • 编写yaml文件时添加nodeSelector字段

kubectl label nodes 192.168.0.160 zone=north给节点192.168.0.160打上标签zone=north

cat redis-master-controller.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis-deployment
  labels:
    name: redis-master
spec:
  replicas: 1
  selector:
    matchLabels:
      name: redis-master
  template:
    metadata:
      labels:
        name: redis-master
    spec:
      containers:
      - name: master
        image: kubeguide/redis-master
        ports:
        - containerPort: 6379
      nodeSelector:
        zone: north

创建Deployment后可以看到Pod副本在192.168.0.160上创建了
kubectl get pods -o wide返回

NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
redis-deployment-565bd84bdb-5rhwj 1/1 Running 0 4m1s 172.17.0.4 192.168.0.160

可以看到Pod在192。168.0.160这个我们打了标签的节点创建

ps1:使用nodeselector调度策略可以将Pod调度到我们自定义了标签的节点上去,这个特性在实际生产环境中很有用,比如可以给计划部署数据库的节点打赏nodename=database之类的标签,然后将数据库Pod部署到该节点上

ps2:如果yaml文件里使用了字段nodeselector,但是节点上都没有打上相应的标签,即使此时集群中有可用的节点,Pod也无法被成功调度

ps3:除了自定义的节点标签,也可以使用kubernetes给节点预定义的标签,查看节点标签使用命令
kubectl get nodes --show-labels

3.2.NodeAffinity亲和性调度

NodeAffinity比NodeSelector在调度层面更为灵活强大,可以用来替代NodeSelector
节点亲和性有两个表达方式

表达方式 描述
RequiredDuringSchedulingIgnoredDuringExecution 必须满足指定规则Pod才会被调度,类似于nodeselector
PreferredDuringSchedulingIgnoredDuringExecution 优先满足指定规则就会尝试进行调度,但不强制

ps:IgnoreDuringExecution时表示如果某个Pod运行的节点标签有变动,但不会影响该Pod继续在此节点上运行

下面做个测试,给192.168.0.159这个节点打上标签disk-type=ssd
kubectl label node 192.168.0.159 disk-type=ssd

cat with-node-affinity.yaml

apiVersion: v1
kind: Pod
metadata:
  name: with-node-affinity
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: beta.kubernetes.io/arch
            operator: In
            values:
            - amd64
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1
        preference:
          matchExpressions:
          - key: disk-type
            operator: In
            values:
            - ssd
  containers:
  - name: with-node-affinity
    image: k8s.gcr.io/pause:3.2

创建Pod后查看
kubectl get pods with-node-affinity -o wide

with-node-affinity 1/1 Running 0 7m1s 172.17.0.6 192.168.0.159
与我们预期的一样,该Pod被调度到了节点192.168.0.159上

在使用过程中以下几点要注意

  • 如果同时定义了nodeselector和nodeaffinity,则必须同时满足这两个规则才能完成调度
  • 如果nodeaffinity指定了多个nodeSelectorTerms,则匹配其中一个即可完成调度
  • 如果nodeSelectorTerms有多个matchExpressions则必须有节点能同时满足所有matchExpressions规则才可以完成调度

3.3.PodAffinity调度

原理:根据已运行的pod标签而不是节点标签进行调度,通俗一点讲,如果在具有标签X的node上运行了一个或者多个符合条件Y的Pod,如果设置为互斥则不把将要创建Pod调度到这些节点,如果亲和则调度到这些节点

先创建一个参照Pod
cat pod-flag.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-flag
  labels:
    security: "S1"
    app: "nginx"
spec:
  containers:
  - name: nginx
    image: nginx

3.3.1.亲和性调度

针对参照Pod,创建一个例子来说明亲和性调度
cat pod-affinity.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-affinity
spec:
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: security
            operator: In
            values:
            - S1
        topologyKey: kubernetes.io/hostname
  containers:
  - name: with-pod-affinity
    image: k8s.gcr.io/pause:3.2

创建Pod后查看
kubectl get pods -o wide

NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod-affinity 1/1 Running 0 22m 172.17.0.3 192.168.0.160
pod-flag 1/1 Running 0 38m 172.17.0.2 192.168.0.160
with-node-affinity 1/1 Running 0 2d23h 172.17.0.4 192.168.0.159

从上面得结果说明创建得Pod与参照Pod是处于同一个节点上
注意这里的topologyKey值,为了验证其作用,做一个测试
先删除目前运行的Pod
kubectl delete -f pod-affinity.yaml
kubectl labels nodes 192.168.0.160 kubernetes.io/hostname-删除节点192.168.0.160上的标签kubernetes.io/hostname
kubectl create -f pod-affinity.yaml再次创建Pod
kubectl get pods返回

NAME READY STATUS RESTARTS AGE
pod-affinity 0/1 Pending 0 2m11s

从上面发现Pod一直处于Pending状态,这说明没有满足条件topologyKey值时Pod也是无法成功调度的

3.3.2.互斥性调度

将上面删掉标签kubernetes.io/hostname=192.168.0.160恢复
kubectl label node 192.168.0.160 kubernetes.io/hostname=192.168.0.160

cat anti-affinity.yaml

apiVersion: v1
kind: Pod
metadata:
  name: anti-affinity
spec:
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: security
            operator: In
            values:
            - S1
        topologyKey: beta.kubernetes.io/arch
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: app
            operator: In
            values:
            - nginx
        topologyKey: kubernetes.io/hostname
  containers:
  - name: anti-affinity
    image: k8s.gcr.io/pause:3.2

kubectl create -f anti-affinity.yaml创建Pod
kubectl get pods -o wide

NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
anti-affinity 1/1 Running 0 13m 172.17.0.2 192.168.0.159
pod-affinity 1/1 Running 0 70m 172.17.0.3 192.168.0.160
pod-flag 1/1 Running 0 121m 172.17.0.2 192.168.0.160

从上面的返回结果可以看出新创建的Pod基于Pod互斥性特性没有调度到与参照Pod同一节点上,这里虽然也有亲和性调度配置,匹配节点标签名beta.kubernetes.io/arch和pod标签security: S1,但是当存在互斥性的时候也需要满足互斥性配置,如果两个规则之间有所交集以互斥配置优先,比如本文就是

ps1:在Pod亲和性调度和RequiredDuringScheduling类型的互斥性定义中不允许使用空的topologyKey

ps2:在PreferredDuringScheduling类型的Pod互斥性定义中,空的topologyKey默认为kubernetes.io/hostname、failure-domain.beta.kubernetes.io/zone及failure-domain.beta.kubernetes.io/region的组合

ps3:除了设置labelSelector和topologyKey还可以设置Namespace来限制,这里默认使用了要创建的Pod所在的namespace,设置为空则表示所有的namespace

ps4:与节点亲和性类似,在requireDuringScheduling类型的matchExpressions要全部满足才能调度Pod到目标节点

3.4.污点与容忍调度

与nodeaffinity相反,污点与容忍是让pod不调度到某些节点
Taints需要与Toleration结合使用,Taint是节点属性,Toleration是pod属性,两者均可以设置多个

  • 给node设置Taint
    kubectl taint node nodename key=value:Noschedule
    该命令表示node上配置了一个taint,键值为key、value,效果是Noschedule,除非在pod声明可以容忍这个taint,否则pod不会被调度到该node上
  • pod上声明Tolerations
tolerations:
- key: "key"
  operator: "Equal"
  value: "value"
  effect: "NoSchedule"

或者写成

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

下面是针对效果的表,描述了三种效果

效果值 结果
NoSchedule 不会将pod调度到节点
PreferNoSchedule 尽量不把Pod调度到节点
NoExecute 不会将Pod调度到节点,如果某个节点已经运行该Pod,会进行驱逐

下面用一个例子来简单说明以下
对某个节点进行Taint设置
kubectl taint nodes node1 key1=value1:NoSchedule
kubectl taint nodes node1 key1=value1:NoExecute
kubectl taint nodes node1 key2=value2:NoSchedule

pod上设置Toleration

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

经过上面的设置后,pod还是不可能调度到节点上,这是因为还缺少一个toleration设置,但是若该pod已经运行在该节点,那么在运行时设置第3个Taint时该pod还是能够继续在节点上运行的,因为pod容忍前两个taint了,什么意思呢,这就是说Noschedule只对调度前的pod生效,但是NoExecute不一样,它会直接驱逐,但是这里kubernetes还有一种针对NoExecute的机制,可以在Tolerations中设置一个字段tolerationSeconds,它可以让正在节点运行的Pod在节点被设置taint后仍然可以继续运行的时间范围,在这个时间内Pod不会马上被驱逐,同时如果在这个时间范围取消了NoExecute的taint,那么不会触发驱逐事件

应用场景