Kubernetes-Pod介绍(三)-Pod调度

620 阅读16分钟

前言

本篇是Kubernetes第六篇,大家一定要把环境搭建起来,看是解决不了问题的,必须实战。

Kubernetes系列文章:
  1. Kubernetes介绍
  2. Kubernetes环境搭建
  3. Kubernetes-kubectl介绍
  4. Kubernetes-Pod介绍(-)
  5. Kubernetes-Pod介绍(二)-生命周期

Pod调度

在Kubernetes中我们很少直接创建一个Pod,大多数情况下会通过Replication Controller、Deployment、Daemonset、Job等控制器完成一组Pod的创建、调度以及生命周期的管理。这是因为单个Pod不能满足我们提出的高可用、高并发的概念,除此之外在真实的生产环境下还有些行行色色的需求:

  1. 不同的Pod之间的亲和性问题,例如主从MySQL数据库不能够分配到同一个节点上或者两种Pod必须调度到同一个节点上,实现本地网路、文件共享等等;
  2. 有状态的集群,例如Zookeeper、Kafka等有状态的集群,每个节点看起来都是差不多,但是每个节点都必须明确主节点,而且节点启动有严格的顺序要求,此外集群中的数据也需要持久化存储,每个工作节点挂点的时候,如何按照持计划的信息进行恢复等等问题;
  3. 每个Node上调度仅仅创建一个Pod,例如对Node节点的监控,主机节点日志、性能采集节点只能部署一个节点;
  4. 批量调度的任务以及定时调度的任务,调用完成的时候要求Pod就销毁;
Deployment或者Replication Controller

Deployment和Replication Controller主要功能就是自动部署一个容器应用的多份副本以及控制副本的数量,在集群内部始终控制指定的副本的数量。

  1. 删除现有的资源信息;
#删除pod
kubectl delete -f nginx-deployment.yaml
  1. 编辑nginx-deployment.yaml文件;
#编辑nginx-deployment.yaml
apiVersion: apps/v1
kind: Deployment
#创建名为nginx-deployment的Deployment资源对象
metadata:
  name: nginx-deployment
spec:
  selector:
    #通过标签查找pod
    matchLabels:
      app: nginx
  #副本个数
  replicas: 3
  template:
    #pod打标签
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        resources:
          limits:
            memory: "128Mi"
            cpu: "128m"
        ports:
        - containerPort: 80
  1. 创建Deployment对象;
kubectl apply -f nginx-deployment.yaml
  1. 查看创建的资源对象信息;
#获取deployment对象信息
kubectl get deployment
#获取pod信息
kubectl get pod
#获取rs信息
kubectl get rs

image.png

注意:在定义Deployment资源的时候,matchLables和template.labels时必须成对出现的,名字也必须相同;

亲和性调度
NodeSelector

在Kubernetes中Pod的调度由kube-scheduler完成,最终实现Pod调度到最佳的节点上,这个过程都是自动完成的,我们是无法预计Pod最终被分配到那个节点上的,在实际的情况我们可能需要将Pod调度到指定的节点上,这个时候我们可以通过Node的Lable与Pod的NodeSelector的属性匹配完成节点的定向调度。

  1. 删除现有Pod,这里我又在阿里云买了一台临时的ECS来完成这个实验,目前我们是1主两从的状态,这里我遇到这样一个问题大家可以参考解决
#删除pod
kubectl delete -f nginx-deployment.yaml
#查看node节点
kubectl get nodes
  1. 给节点打上标签;
#查看节点详情
kubectl get nodes
#给节点打标签
kubectl label nodes demo-work-1 zone=hangzhou
#查看节点标签
kubectl get node --show-labels

image.png

  1. 编辑nginx-deployment.yaml文件;
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 3
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        resources:
          limits:
            memory: "128Mi"
            cpu: "128m"
        ports:
        - containerPort: 80
      #node选择器
      nodeSelector:
        zone: hangzhou
  1. 创建Deployment对象;
kubectl apply -f nginx-deployment.yaml
  1. 检查Pod节点分布,这里我们会发现Pod节点都被调度到了demo-work-1节点上;
#查看pod更多的节点信息
kubectl get pods -o wide

注意:如果我们指定Pod的NodeSelector条件的时候,如果集群上不存在包含相同标签Node,Pod是无法调度成功的,也包含正在运行的Pod。

NodeSelector通过标签的方式,完成将节点的定向调度,这种亲和性的调度机制极大提升的Pod调度能力,帮助Kubernetes更好的去完成我们需求,但是NodeSelector调度方式还是过于简单,因此Kubernetes还提供NodeAffinity和PodAffinity两种维度亲和调度功能。

NodeAffinity

NodeAffinity翻译过来是Node亲和性调度,目的是为了替换NodeSelector,NodeAffinity目前有两种亲和性的表达方式:

  1. requiredDuringSchedulingIgnoredDuringExecution:表示 pod 必须部署到满足条件的节点上,如果没有满足条件的节点,就不断重试;
  2. preferredDuringSchedulingIgnoredDuringExecution:表示优先部署在满足条件的节点上,如果没有满足条件的节点,就忽略这些条件,按照正常逻辑部署,多个优先级级别的规则还可以设置权重;

IgnoredDuringExecution的意思是,Pod所在节点在运行期间发生变更,不在符合Pod节点的亲和性规则,系统不会影响已经在节点上运行的Pod。

  1. 删除现有Pod,给新节点打上zone=shanghai标签,查看Node列表;
#删除pod
kubectl delete -f nginx-deployment.yaml
#打上海标签
kubectl label nodes demo-work-2 zone=shanghai
#看看Node标签
kubectl get node --show-labels

image.png

  1. 上个实战已经给Node节点打好标签,所以这里我们直接编辑nginx-deployment.yaml文件;
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 10
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        resources:
          limits:
            memory: "128Mi"
            cpu: "128m"
        ports:
        - containerPort: 80
      affinity:
        nodeAffinity:
          #优先匹配hangzhou的节点 其次匹配shanghai 
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 80
              preference:
                matchExpressions:
                - key: zone
                  operator: In
                  values:
                    - hangzhou
            - weight: 20
              preference:
                matchExpressions:
                - key: zone
                  operator: In
                  values:
                    - shanghai
  1. 创建Deployment对象;
kubectl apply -f nginx-deployment.yaml
  1. 查看Pod节点的分布;
#查看pod更多的节点信息
kubectl get pods -o wide

image.png

在配置中我们看到可以使用操作符,操作符类型有以下几种:

  1. In:表示所有信息都要在value的列表里面;

  2. NotIn: 标签的值不在某个列表里面;

  3. Exists: 存在某个标签;

  4. DoesNotExist: 某个标签不存在;

  5. Gt: 标签的值大于某个值;

  6. Lt: 标签的值小于某个值;

注意点:

  1. 如果同时设置nodeSelector和nodeAffinity则必须同时满足两个条件,Pod才会运行到最终的节点上:

  2. 如果同时指定多个nodeSelectorTerms,只要满足其中一个就可以匹配成功;

  3. 如果nodeSelectorTerms有多个matchExpressions,则必须满足所有matchExpressions才可以运行Pod;

PodAffinity And PodAntiAffinity

在生产环境中存在一类Pod,他们Pod之间相互依赖,要求尽可能被部署到相同的Node节点上,比如将应用的前端和后端部署在一起,减少访问延迟,或者为了避免Pod之间相互竞争,要求某些Pod相互远离,这就是Pod之间的亲和性或者互斥性。Pod亲和同样有requiredDuringSchedulingIgnoredDuringExecution和preferredDuringSchedulingIgnoredDuringExecution两种规则。

  1. 删除Pod;
#删除pod
kubectl delete -f nginx-deployment.yaml
  1. 编辑nginx-deployment.yaml文件,修改Pod名称为backend,并且将节点的个数调整为3个;
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: backend
  replicas: 3
  template:
    metadata:
      labels:
        app: backend
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        resources:
          limits:
            memory: "128Mi"
            cpu: "128m"
        ports:
        - containerPort: 80
      affinity:
        nodeAffinity:
          #优先匹配hangzhou的节点 其次匹配shanghai
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 80
              preference:
                matchExpressions:
                - key: zone
                  operator: In
                  values:
                    - hangzhou
            - weight: 20
              preference:
                matchExpressions:
                - key: zone
                  operator: In
                  values:
                    - shanghai 
  1. 新建文件podAffinity-deployment.yaml, 选择标签为zone,并且app=backend的Pod;
apiVersion: apps/v1
kind: Deployment
metadata:
  name: podaffinitydemo
spec:
  selector:
    matchLabels:
      app: frontend
  replicas: 3
  template:
    metadata:
      labels:
        app: frontend
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        resources:
          limits:
            memory: "128Mi"
            cpu: "128m"
        ports:
        - containerPort: 80
      affinity:
        #亲和
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            #选择标签键为zone
            - topologyKey: zone
              labelSelector:
                #必须匹配到zone=hangzhou app=backend
                matchExpressions:
                - key: app
                  operator: In             
                  values:
                    - backend
  1. 创建Deployment资源;
kubectl apply -f nginx-deployment.yaml
kubectl apply -f podAffinity-deployment.yaml
  1. 查看Pod节点的分布;
#查看pod更多的节点信息
kubectl get pods -o wide

image.png

PodAntiAffinity就是反亲和性,可以和PodAffinity一起出现在配置文件中,与节点亲和类似,在Pod亲和中也可以使用操作符,相比于节点亲和多了一个必须的值topologyKey,对于topologyKey使用有以下注意:

  1. 对于requiredDuringSchedulingIgnoredDuringExecution的Pod的反亲和性和Pod的亲和性必须出现topologyKey;

  2. 对于requiredDuringSchedulingIgnoredDuringExecution的Pod反亲和性,引入LimitPodHardAntiAffinityTopology准入控制器来限制 topologyKey 只能是 kubernetes.io/hostname。如果要使用自定义拓扑域,则可以修改准入控制器,或者直接禁用它;

  3. 对于 preferredDuringSchedulingIgnoredDuringExecution 的 Pod 反亲和性,空的 topologyKey 表示所有拓扑域。所有拓扑域还只能是 kubernetes.io/hostname、failure-domain.beta.kubernetes.io/zone 和 failure-domain.beta.kubernetes.io/region 的组合;

  4. 除上述情况外,topologyKey 可以是任何合法的标签 key;

除了 labelSelector 和 topologyKey,也可以指定命名空间,labelSelector 也可以匹配它。 如果忽略或者为空,则默认为 Pod 亲和性/反亲和性的定义所在的命名空间。

Taints and Tolerations

亲和性可以帮助Pod调度到指定的节点,但是在复杂的生产环境中,当你某个节点出现问题的时候,我们不希望再有Pod被调度到该节点,这里的问题并不是节点挂掉了,而是磁盘爆满、CPU、内存不足等等情况,这时候我们可以将节点标记Taint,Pod就不会调度到该节点上。还有一种特殊情况有时候我们仍然需要将Pod调度到被标记Taint的节点上,这个时候我们为Pod设置Toleration属性来满足Taint节点。

  1. 给节点标记Taint;
#给demo-work-1标记污点 键名是 notRam,键值是 true,效果是NoSchedule
kubectl taint nodes demo-work-1 notRam=true:NoSchedule
#给demo-work-2标记污点 键名是 haha,键值是 true,效果是NoSchedule
kubectl taint nodes demo-work-2 haha=true:NoSchedule
#移除污点
kubectl taint nodes demo-work-1 notRam=true:NoSchedule-
  1. 编辑toleration-pod.yaml,设置Toleration属性,保证节点被调度到对应污点上;
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx:latest
    imagePullPolicy: IfNotPresent
  #设置Toleration
  tolerations:
  - key: "notRam"
    operator: "Equal"
    value: "true"
    effect: "NoSchedule"
  1. 创建Pod资源;
kubectl apply -f toleration-pod.yaml
  1. 查看Pod节点的分布;
#查看pod更多的节点信息
kubectl get pods -o wide

image.png

对于operator取值有两种:

  1. 当operator 是 Exists不需要设置value的值;
  2. 当operator 是 Equal ,则它们的 value 必须相等;
调度策略

系统允许同一个Node设置多个Taint标签,也允许Pod设置设置多个Toleration属性。Kubernetes 处理多个Taint和Toleration的过程就像一个过滤器:首先列出所有Taint,忽略掉 Pod 中匹配的Taint。剩下的Taint有以下三种情况:

  1. 如果剩余的Taint中存在effect=NoSchedule, 则调度器不会将 Pod 分配到该节点;

  2. 如果剩余的Taint中不存在NoSchedule,但是存在PreferNoSchedule的污点, 则调度器会尝试不将 Pod 分配到该节点;

  3. 如果剩余的Taint中存在NoExecute,如果有Pod运行在该节点上,则会被驱逐;如果没有在该节点运行,则也不会调度到该节点;

驱逐策略

对于是设置NoExecute的Taint,会对正在运行的Pod有驱逐策略:

  1. 没有设置Toleration的Pod会马上被驱逐;

  2. 配置Toleration的Pod,如果没有设置tolerationSeconds,则一种运行在节点上;

  3. 配置Toleration的Pod并且设置tolerationSeconds,则会在指定的时间后被驱逐,注意节点故障的情况下,系统会按照限速的模式逐步给Node添加Taint,避免特定情况下大量的Pod被驱逐;

自动添加的Toleration

Kubernetes默然情况下会给Pod添加以下两种Toleration:

  1. key为node.kubernetes.io/not-ready,并设置tolerationSeconds=300;
  2. key为node.kubernetes.io/unreachable,并设置tolerationSeconds=300;

自动添加的容忍度意味着在其中一种问题被检测到时 Pod 默认能够继续停留在当前节点运行 5 分钟,而不是立即被驱逐,避免系统产生波动。

按照条件添加Taint

Kubernetes从1.6版本开始引入两个Taint相关的新特性TaintNodesByCondition以及TaintBasedEvictions,用来改进Pod调度和驱逐问题,改造以后流程如下:

  1. 不断检查所有Node状态,设置对应的Condition;

  2. 不断的根据Node Condition设置对应的Taint;

  3. 不断的更加Taint驱逐Node上的Pod;

其中,检查Node的状态并且设置Node的Taint就是TaintNodesByCondition的特性,在满足以下情况的时候会自动给Node添加Taint:

  1. node.kubernetes.io/unreachable: 节点不可触达,对应NodeCondition Ready为Unknown情况;

  2. node.kubernetes.io/not-ready: 节点未就绪,对应NodeCondition Ready为False情况;

  3. node.kubernetes.io/disk-pressure: 节点磁盘已满;

  4. node.kubernetes.io/network-unavailable:节点网络不可用;

  5. node.kubernetes.io/unschedulable(1.10 或更高版本): 节点不可调度;

Kubernetes从1.13以上两个特性会默认开启,TaintNodesByCondition只会为节点设置NoSchedule的添加Taint;TaintBasedEvictions只会为节点添加NoExecute添加Taint,该特性被开启之后,调度器会在有资源压力的时候给对应的Node添加NoExecute的Taint,如果没有设置对应的Toleration,则Pod会立即被驱逐,用此来保证Node不会崩溃。

优先调度

当集群资源不足的时候,当我们需要创建Pod的时候,这个时候Pod会一致处于Pending状态,即使该Pod是一个特别重要的Pod,我们也需要等待调度器释放掉其他资源,才可以调用成功。针对这种情况,Kubernetes在1.8引入优先级调度的Pod,当资源不足的时候有优先级比较高的Pod需要调度的时候,会尝试释放一些优先级比较低的资源,来满足优先级高的资源的调度,在1.14的版本中正式Release。

  1. 定义PriorityClass,命名为prioritydemo.yaml;
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high-priority
value: 1000000
globalDefault: false
description: "用于优先级调用"
  1. 定义任意Pod,使用优先调度;
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx:latest
    imagePullPolicy: IfNotPresent
  priorityClassName: high-priority
  1. 创建资源;
#创建优先级调度资源
kubectl apply -f prioritydemo.yaml
#创建pod
kubectl apply -f priority-pod.yaml
PriorityClass

PriorityClass 是一个无名称空间对象,它定义了从优先级类名称到优先级整数值的映射,值越大,优先级越高。 PriorityClass 对象的名称必须是有效的 DNS 子域名, 并且它不能以 system- 为前缀。

关于使用PriorityClass注意:

  1. 如果你升级一个已经存在的但尚未使用优先级调度的集群,该集群中已经存在的 Pod 的优先级等效于0;

  2. 添加一个将 globalDefault 设置为 true 的 PriorityClass 不会改变现有 Pod 的优先级。 此类 PriorityClass 的值仅用于添加 PriorityClass 后创建的 Pod;

  3. 如果你删除了某个 PriorityClass 对象,则使用被删除的 PriorityClass 名称的现有 Pod 保持不变, 但是不能再创建使用已删除的 PriorityClass 名称的 Pod;

注意点,使用优先级抢占式的调度策略会导致某些Pod永远无法被调度。优先级调度不仅增加系统复杂性,还会带来很多不稳定的因素,建议在资源紧张的时候优先采用扩容手段。

DeamonSet

DeamonSet确保节点上运行一个 Pod 的副本。 当有节点加入集群时, 也会为他们新增一个 Pod 。 当有节点从集群移除时,Pod也会被回收。删除 DaemonSet 将会删除它创建的所有 Pod。DeamonSet调度策略与RC类似,除了内置算法保证在Node上进行调度,也可以在Pod定义NodeSelector和NodeAffinity来满足指定条件的节点进行调度。

  1. 删除Pod;
kubectl delete -f priority-pod.yaml
  1. 新建fluentd-deamonset.yaml文件,挂载物理机的/var/log和/var/lib/docker/containers目录;
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentd-elasticsearch
spec:
  selector:
    matchLabels:
      name: fluentd-elasticsearch
  template:
    metadata:
      labels:
        name: fluentd-elasticsearch
    spec:
      containers:
      - name: fluentd-elasticsearch
        image: fluentd:latest
        resources:
          limits:
            memory: 200Mi
          requests:
            cpu: 100m
            memory: 200Mi
        volumeMounts:
        - name: varlog
          mountPath: /var/log
        - name: varlibdockercontainers
          mountPath: /var/lib/docker/containers
          readOnly: true
      terminationGracePeriodSeconds: 30
      volumes:
      - name: varlog
        hostPath:
          path: /var/log
      - name: varlibdockercontainers
        hostPath:
          path: /var/lib/docker/containers
  1. 创建DeamonSet资源;
kubectl apply -f fluentd-deamonset.yaml
  1. 查看Pod节点的分布;
#查看pod更多的节点信息
kubectl get pods -o wide

image.png

DaemonSet场景
  1. 每个节点上运行集群守护进程;

  2. 每个节点上运行日志收集守护进程;

  3. 每个节点上运行监控守护进程;

Taints and Tolerations

image.png

批量调度

经常会遇到这样的场景,有一批大量的数据需要计算,这个时候就需要我们批任务去处理,Kubernetes可以通过Job来定义启动批处理任务,批处理任务通常并行多个计算节点进行处理一个工作项,处理完成以后,整个批处理任务结束,按照实现方式不同可分为以下几种情况:

  1. Job Template Expansion模式: 一个Job对象对应一个待处理的工作项,有几个工作项就对应几个Job,通常适合工作项数量少,每个工作项处理数据量比较大的场景;
  2. Queue with Pod Per Work Item模式: 采用一个任务队列存放工作项, 一个Job对象作为消费者去消费这些工作项,这种模式下每个Pod都对应一个工作项,一个工作项处理完成,Pod就结束了;

image.png

  1. Queue with Variable Pod Count模式: 同样采用一个任务队列存放任务项, 一个Job对象作为消费者去消费这些任务项,每个 Pod 不断的去队列拉取任务项,完成后继续去队列里去任务项,直到队列里没有任务,Pod 才退出。这种情况下,只要有一个 Pod 成功退出,就意味着整个 Job 结束;

image.png

  1. 新建busybox-job.yaml文件;
apiVersion: batch/v1
kind: Job
metadata:
  name: jobdemo
  labels:
    jobgroup: jobexample
spec:
  template:
    metadata:
      name: jobexample
      labels:
        jobgroup: jobexample
    spec:
      containers:
      - name: c
        image: busybox
        command: ["sh", "-c", "echo job demo && sleep 5"]
      restartPolicy: Never
  1. 创建Job资源;
kubectl apply  -f busybox-job.yaml
  1. 查看Job资源;
kubectl get jobs -l jobgroup=jobexample
  1. 检查输出内容;
kubectl logs -f -l jobgroup=jobexample

image.png

定时调度

在我们的日常需求中还有一种周期性任务,Kubernetes可以通过CronJobs 来创建周期性的任务,例如定期执行数据库备份,此外还可以使用CronJobs用来在指定时间来执行的独立任务,例如在集群状态空闲的时候执行某个Job。

  1. 创建文件hello-cronjob.yaml文件,实现每分钟打印Hello Word;
apiVersion: batch/v1
kind: CronJob
metadata:
  name: hello
spec:
  schedule: "*/1 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: hello
            image: busybox
            imagePullPolicy: IfNotPresent
            command:
            - /bin/sh
            - -c
            - date; echo Hello Word 
          restartPolicy: OnFailure
  1. 创建CronJob资源;
kubectl apply -f hello-cronjob.yaml
  1. 查看CronJob资源;
kubectl get cronJob hello
  1. 检查输出内容,我们会发现每隔1分钟就调度一个Pod;

image.png

结束

欢迎大家点点关注,点点赞!