深入掌握Pod(中)

深入掌握Pod

 

Pod调度

在Kubernetes平台上,我们很少会直接创建一个Pod,在大多数情况下会通过RC、Deployment、DaemonSet、Job等控制器完成对一组Pod副本的创建、调度及全生命周期的自动控制任务。

在最早的Kubernetes版本里是没有这么多Pod副本控制器的,只有一个Pod副本控制器RC(Replication Controller),这个控制器是这样设计实现的:RC独立于所控制的Pod,并通过Label标签这个松耦合关联关系控制目标Pod实例的创建和销毁,随着Kubernetes的发展,RC也出现了新的继任者——Deployment,用于更加自动地完成Pod副本的部署、版本更新、回滚等功能。

RC的继任者其实并不是Deployment,而是ReplicaSet,因为 ReplicaSet进一步增强了 RC标签选择器的灵活性。之前RC的标签选择器只能选择一个标签,而ReplicaSet拥有集合式的标签选择器,可以选择多个Pod标签,如下所示:

与RC不同,ReplicaSet被设计成能控制多个不同标签的Pod副本。一种常见的应用场景是,应用MyApp目前发布了v1与v2两个版本,用户希望MyApp的Pod副本数保持为3个,可以同时包含v1和v2版本的Pod,就可以用ReplicaSet来实现这种控制,写法如下:.

 

Deployment或RC

Deployment或RC:全自动调度

Deployment或RC的主要功能之一就是自动部署一个容器应用的多份副本,以及持续监控副本的数量,在集群内始终维持用户指定的副本数量。

下面是一个Deployment配置的例子,使用这个配置文件可以创建一个ReplicaSet,这个ReplicaSet会创建3个Nginx应用的Pod:

$ kubectl create deployment nginx-deployment --image=nginx -o yaml --dry-run > nginx-deployment.yaml

[root@master ~]# cat  nginx-deployment.yaml  
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx-deployment
  name: nginx-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx-deployment
  template:
    metadata:
      labels:
        app: nginx-deployment
    spec:
      containers:
      - image: nginx
        name: nginx
        imagePullPolicy: IfNotPresent 
        ports:
        - containerPort: 80 
        
[root@master ~]# kubectl create -f nginx-deployment.yaml 
deployment.apps/nginx-deployment created

"查看Deployment的状态"

[root@master ~]# kubectl get  deployment 
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   3/3     3            3           8s

[root@master ~]# kubectl get pods 
NAME                               READY   STATUS             RESTARTS   AGE
nginx-deployment-b5f4f78d8-dlc8h   1/1     Running            0          45s
nginx-deployment-b5f4f78d8-nq5zh   1/1     Running            0          45s
nginx-deployment-b5f4f78d8-tngkc   1/1     Running            0          45s

总结 

  • 该状态说明Deployment已创建好所有3个副本,并且所有副本都是最新的可用的。通过运行kubectl get rs和kubectl get pods可以查看已创建的ReplicaSet(RS)和Pod的信息。
  • 从调度策略上来说,这3个Nginx Pod由系统全自动完成调度。它们各自最终运行在哪个节点上,完全由Master的Scheduler经过一系列算法计算得出,用户无法干预调度过程和结果。

 

NodeSelector

NodeSelector:定向调度

Kubernetes Master上的Scheduler服务(kube-scheduler进程)负责实现Pod的调度,整个调度过程通过执行一系列复杂的算法,最终为每个Pod都计算出一个最佳的目标节点,这一过程是自动完成的,通常我们无法知道Pod最终会被调度到哪个节点上。在实际情况下,也可能需要将Pod调度到指定的一些Node上,可以通过Node的标签(Label)和Pod的nodeSelector属性相匹配,来达到上述目的。

首先通过kubectl label命令给目标Node打上一些标签:

$  kubectl label nodes <node-name> <label-key>=<label-key> 

这里,我们为k8s-node-1节点打上一个zone=north标签,表明它是“北方”的一个节点:

[root@master ~]# kubectl label nodes  master zone=north
node/master labeled

在Pod的定义中加上nodeSelector的设置,以redis-master-controller.yaml为例:

[root@master ~]# vi replicaset.yaml 
kind: ReplicationController
apiVersion: v1
metadata:
  name: nginx
  namespace: default
spec:
  replicas: 3
  selector:
      app: nginx
  template:
    metadata:
      name: nginx
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx
          ports:
            - containerPort: 80
      nodeSelector:
        zone: north
        
[root@master ~]# kubectl create -f replicaset.yaml 
replicationcontroller/nginx created

[root@master ~]# kubectl get pods -o wide 
NAME                               READY   STATUS             RESTARTS   AGE   IP            NODE     NOMINATED NODE   READINESS GATES
nginx-deployment-b5f4f78d8-dlc8h   1/1     Running            0          16m   10.244.0.9    master   <none>           <none>
nginx-deployment-b5f4f78d8-nq5zh   1/1     Running            0          16m   10.244.0.11   master   <none>           <none>
nginx-deployment-b5f4f78d8-tngkc   1/1     Running            0          16m   10.244.0.10   master   <none>           <none>

 如果我们给多个Node都定义了相同的标签(例如zone=north),则scheduler会根据调度算法从这组Node中挑选一个可用的Node进行Pod调度。

通过基于Node标签的调度方式,我们可以把集群中具有不同特点的Node都贴上不同的标签,例如“role=frontend”“role=backend”“role=database”等标签,在部署应用时就可以根据应用的需求设置NodeSelector来进行指定Node范围的调度。

NodeSelector通过标签的方式,简单实现了限制Pod所在节点的方法。亲和性调度机制则极大扩展了Pod的调度能力,主要的增强功能如下。

  • 更具表达力(不仅仅是“符合全部”的简单情况)。
  • 可以使用软限制、优先采用等限制方式,代替之前的硬限制,这样调度器在无法满足优先需求的情况下,会退而求其次,继续运行该Pod。
  • 可以依据节点上正在运行的其他Pod的标签来进行限制,而非节点本身的标签。这样就可以定义一种规则来描述Pod之间的亲和或互斥关系。
  • 亲和性调度功能包括节点亲和性(NodeAffinity)和Pod亲和性(PodAffinity)两个维度的设置。节点亲和性与NodeSelector类似,增强了上述前两点优势;Pod的亲和与互斥限制则通过Pod标签而不是节点标签来实现,也就是上面第4点内容所陈述的方式,同时具有前两点提到的优点。

 

NodeAffinity

NodeAffinity:Node亲和性调度

NodeAffinity意为Node亲和性的调度策略,是用于替换NodeSelector的全新调度策略。目前有两种节点亲和性表达。

    • RequiredDuringSchedulingIgnoredDuringExecution:必须满足指定的规则才可以调度Pod到Node上(功能与nodeSelector很像,但是使用的是不同的语法),相当于硬限制。
    • PreferredDuringSchedulingIgnoredDuringExecution:强调优先满足指定规则,调度器会尝试调度Pod到Node上,但并不强求,相当于软限制。多个优先级规则还可以设置权重(weight)值,以定义执行的先后顺序.
    • IgnoredDuringExecution的意思是:如果一个Pod所在的节点在Pod运行期间标签发生了变更,不再符合该Pod的节点亲和性需求,则系统将忽略Node上Label的变化,该Pod能继续在该节点运行。

下面的例子设置了NodeAffinity调度的如下规则。

    • requiredDuringSchedulingIgnoredDuringExecution要求只运行在amd64的节点上(beta.kubernetes.io/arch In amd64)。
    • preferredDuringSchedulingIgnoredDuringExecution的要求是尽量运行在磁盘类型为ssd(disk-type In ssd)的节点上。

从上面的配置中可以看到In操作符,NodeAffinity语法支持的操作符包括In、NotIn、Exists、DoesNotExist、Gt、Lt。虽然没有节点排斥功能,但是用NotIn和DoesNotExist就可以实现排斥的功能了。

NodeAffinity规则设置的注意事项如下。

  • 如果同时定义了nodeSelector和nodeAffinity,那么必须两个条件都得到满足,Pod才能最终运行在指定的Node上。
  • 如果nodeAffinity指定了多个nodeSelectorTerms,那么其中一个能够匹配成功即可。
  • 如果在nodeSelectorTerms中有多个matchExpressions,则一个节点必须满足所有matchExpressions才能运行该Pod。

 

PodAffinity

PodAffinity:Pod亲和与互斥调度策略

 

Pod间的亲和与互斥从Kubernetes 1.4版本开始引入。这一功能让用户从另一个角度来限制Pod所能运行的节点:根据在节点上正在运行的Pod的标签而不是节点的标签进行判断和调度,要求对节点和Pod两个条件进行匹配。这种规则可以描述为:如果在具有标签X的Node上运行了一个或者多个符合条件Y的Pod,那么Pod应该(如果是互斥的情况,那么就变成拒绝)运行在这个Node上。

这里X指的是一个集群中的节点、机架、区域等概念,通过Kubernetes内置节点标签中的key来进行声明。这个key的名字为topologyKey,意为表达节点所属的topology范围。

    • kubernetes.io/hostname
    • failure-domain.beta.kubernetes.io/zone
    • failure-domain.beta.kubernetes.io/region

与节点不同的是,Pod是属于某个命名空间的,所以条件Y表达的是一个或者全部命名空间中的一个Label Selector。

和节点亲和相同,Pod亲和与互斥的条件设置也是requiredDuringSchedulingIgnoredDuringExecution和preferredDuringSchedulingIgnoredDuringExecution。Pod的亲和性被定义于PodSpec的affinity字段下的podAffinity子字段中。Pod间的互斥性则被定义于同一层次的podAntiAffinity子字段中。

 

参照目标Pod

首先,创建一个名为pod-flag的Pod,带有标签security=S1和app=nginx,后面的例子将使用pod-flag作为Pod亲和与互斥的目标Pod:

 

Pod的亲和性调度

下面创建第2个Pod来说明Pod的亲和性调度,这里定义的亲和标签是security=S1,对应上面的Pod“pod-flag”,topologyKey的值被设置为“kubernetes.io/hostname”:

创建Pod之后,使用kubectl get pods -o wide命令可以看到,这两个Pod在同一个Node上运行。

 

Pod的互斥性调度

创建第3个Pod,我们希望它不与目标Pod运行在同一个Node上:

这里要求这个新Pod与security=S1的Pod为同一个zone,但是不与app=nginx的Pod为同一个Node。创建Pod之后,同样用kubectl get pods -o wide来查看,会看到新的Pod被调度到了同一Zone内的不同Node上。

 

Taints和Tolerations

Taints和Tolerations(污点和容忍)

Taint需要和Toleration配合使用,让Pod避开那些不合适的Node。在Node上设置一个或多个Taint之后,除非Pod明确声明能够容忍这些污点,否则无法在这些Node上运行。Toleration是Pod的属性,让Pod能够(注意,只是能够,而非必须)运行在标注了Taint的Node上。

Pod的Toleration声明中的key和effect需要与Taint的设置保持一致,并且满足以下条件之一。

  • operator的值是Exists(无须指定value)。
  • operator的值是Equal并且value相等。

如果不指定operator,则默认值为Equal。

另外,有如下两个特例。

  • 空的key配合Exists操作符能够匹配所有的键和值。
  • 空的effect匹配所有的effect。

系统允许在同一个Node上设置多个Taint,也可以在Pod上设置多个Toleration。Kubernetes调度器处理多个Taint和Toleration的逻辑顺序为:首先列出节点中所有的Taint,然后忽略Pod的Toleration能够匹配的部分,剩下的没有忽略的Taint就是对Pod的效果了。下面是几种特殊情况。

  • 如果在剩余的Taint中存在effect=NoSchedule,则调度器不会把该Pod调度到这一节点上。
  • 如果在剩余的Taint中没有NoSchedule效果,但是有PreferNoSchedule效果,则调度器会尝试不把这个Pod指派给这个节点。
  • 如果在剩余的Taint中有NoExecute效果,并且这个Pod已经在该节点上运行,则会被驱逐;如果没有在该节点上运行,则也不会再被调度到该节点上。

查看当前存在的节点

[root@master flannel]# kubectl get  nodes 
NAME     STATUS   ROLES    AGE     VERSION
master   Ready    master   5h54m   v1.18.1

增加一个污点

$ kubectl  taint   node k8s-node2  node-role.kubernetes.io=k8s-node2:NoSchedule

若要移除上述命令所添加的污点,你可以执行:

kubectl taint node k8s-node2  node-role.kubernetes.io:NoSchedule-

创建可以容忍k8s-node2的污点的Pod

$ kubectl  create   deployment  noschedule --image=nginx:latest  -o yaml --dry-run=client  > noschedule.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: noschedule
  name: noschedule
spec:
  replicas: 1
  selector:
    matchLabels:
      app: noschedule
  template:
    metadata:
      labels:
        app: noschedule
    spec:
      affinity:
        nodeAffinity:    #这里强制将pod调度到k8s-node2节点上进行测试
          requiredDuringSchedulingIgnoredDuringExecution:  # 定义硬亲和性
            nodeSelectorTerms:
            - matchExpressions:    #集合选择器
              - key: kubernetes.io/hostname
                operator: In
                values:
                - k8s-node2
      containers:
      - image: nginx:latest
        name: nginx

创建并且查看Pod调度情况

$ kubectl  apply -f noschedule.yaml   &&  kubectl   get  pod   -o wide

 

为Pod添加容忍点

生成Pod文件

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: noschedule
  name: noschedule
spec:
  replicas: 1
  selector:
    matchLabels:
      app: noschedule
  template:
    metadata:
      labels:
        app: noschedule
    spec:
      affinity:
        nodeAffinity:    #这里强制将pod调度到k8s-node2节点上进行测试
          requiredDuringSchedulingIgnoredDuringExecution:  # 定义硬亲和性
            nodeSelectorTerms:
            - matchExpressions:    #集合选择器
              - key: kubernetes.io/hostname
                operator: In
                values:
                - k8s-node2
      containers:
      - image: nginx:latest
        name: nginx
      tolerations:
      - key: "node-role.kubernetes.io" #污点的key
        operator: "Equal"
        value: "k8s-node2"    #污点的键
        effect: "NoSchedule"  #污点策略

更新Pod文件

$ kubectl apply  -f noschedule.yaml && kubectl  get   po

使用污点NoExecute策略进行测试

k8s-node2添加NoExecute污点

$ kubectl  taint  node k8s-node2  node-role.kubernetes.io=k8s-node2:NoExecute

更新yaml文件并且更新Pod

$ vim noschedule.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: noschedule
  name: noschedule
spec:
  replicas: 1
  selector:
    matchLabels:
      app: noschedule
  template:
    metadata:
      labels:
        app: noschedule
    spec:
      affinity:
        nodeAffinity:    #这里强制将pod调度到k8s-node2节点上进行测试
          requiredDuringSchedulingIgnoredDuringExecution:  # 定义硬亲和性
            nodeSelectorTerms:
            - matchExpressions:    #集合选择器
              - key: kubernetes.io/hostname
                operator: In
                values:
                - k8s-node2
      containers:
      - image: nginx:latest
        name: nginx
      tolerations:
      - key: "node-role.kubernetes.io" #污点的key
        operator: "Equal"
        value: "k8s-node2"    #污点的键
        effect: "NoSchedule"  #污点策略
      - key: "node-role.kubernetes.io" #污点的key
        operator: "Equal"
        value: "k8s-node2"     #污点的键
        effect: "NoExecute"    #污点策略
        tolerationSeconds: 60  #Pod在节点上运行60秒

$ kubectl  apply  -f noschedule.yaml  && kubectl  get  pod -w

 

Pod优先级调度

Pod Priority Preemption:Pod优先级调度

在用户提交新的Pod创建请求后,该Pod会一直处于Pending状态,即使这个Pod是一个很重要(很有身份)的Pod,也只能被动等待其他Pod被删除并释放资源,才能有机会被调度成功。Kubernetes 1.8版本引入了基于Pod优先级抢占(Pod Priority Preemption)的调度策略,此时Kubernetes会尝试释放目标节点上低优先级的Pod,以腾出空间(资源)安置高优先级的Pod,这种调度方式被称为“抢占式调度”。

我们可以通过以下几个维度来定义:

  • Priority,优先级;
  • QoS,服务质量等级;
  • 系统定义的其他度量指标。

Pod优先级调度示例如下。

上述YAML文件定义了一个名为high-priority的优先级类别,优先级为100000,数字越大,优先级越高,超过一亿的数字被系统保留,用于指派给系统组件。

我们可以在任意Pod中引用上述Pod优先级类别:

 

DaemonSet

DaemonSet:在每个Node上都调度一个Pod

DaemonSet是Kubernetes 1.2版本新增的一种资源对象,用于管理在集群中每个Node上仅运行一份Pod的副本实例.

图3.3 DaemonSet示例

这种用法适合有这种需求的应用。

  • 💨  在每个Node上都运行一个GlusterFS存储或者Ceph存储的Daemon进程。
  • 💨  在每个Node上都运行一个日志采集程序,例如Fluentd或者Logstach。
  • 💨  在每个Node上都运行一个性能监控程序,采集该Node的运行性能数据,例如Prometheus Node Exporter、collectd、New Relic agent或者Ganglia gmond等。

DaemonSet的Pod调度策略与RC类似,除了使用系统内置的算法在每个Node上进行调度,也可以在Pod的定义中使用NodeSelector或NodeAffinity来指定满足条件的Node范围进行调度。

[root@master ~]# vi daemonset.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentd
  namespace: default
spec:
  selector:
    matchLabels:
      name: fluentd
  template:
    metadata:
      labels:
        name: fluentd
    spec:
      containers:
        - name: fluentd-container01
          image: quay.io/fluentd_elasticsearch/fluentd:v2.5.2
          
[root@master ~]# kubectl create -f daemonset.yaml 
daemonset.apps/fluentd created

 

Job

Job:批处理调度

Kubernetes从1.2版本开始支持批处理类型的应用,我们可以通过Kubernetes Job资源对象来定义并启动一个批处理任务。批处理任务通常并行(或者串行)启动多个计算进程去处理一批工作项(work item),处理完成后,整个批处理任务结束。

  • Job Template Expansion模式:一个Job对象对应一个待处理的Work item,有几个Work item就产生几个独立的Job,通常适合Work item数量少、每个Work item要处理的数据量比较大的场景,比如有一个100GB的文件作为一个Work item,总共有10个文件需要处理。
  • Queue with Pod Per Work Item模式:采用一个任务队列存放Work item,一个Job对象作为消费者去完成这些Work item,在这种模式下,Job会启动N个Pod,每个Pod都对应一个Work item。
  • Queue with Variable Pod Count模式:也是采用一个任务队列存放Work item,一个Job对象作为消费者去完成这些Work item,但与上面的模式不同,Job启动的Pod数量是可变的。

 

考虑到批处理的并行问题,Kubernetes将Job分以下三种类型。

1.Non-parallel Jobs

通常一个Job只启动一个Pod,除非Pod异常,才会重启该Pod,一旦此Pod正常结束,Job将结束。

2.Parallel Jobs with a fixed completion count

并行Job会启动多个Pod,此时需要设定Job的.spec.completions参数为一个正数,当正常结束的Pod数量达至此参数设定的值后,Job结束。此外,Job的.spec.parallelism参数用来控制并行度,即同时启动几个Job来处理Work Item。

3.Parallel Jobs with a work queue

任务队列方式的并行Job需要一个独立的Queue,Work item都在一个Queue中存放,不能设置Job的.spec.completions参数,此时Job有以下特性。

  • 🧀 每个Pod都能独立判断和决定是否还有任务项需要处理。
  • 🧀 如果某个Pod正常结束,则Job不会再启动新的Pod。
  • 🧀 如果一个Pod成功结束,则此时应该不存在其他Pod还在工作的情况,它们应该都处于即将结束、退出的状态。
  • 🧀 如果所有Pod都结束了,且至少有一个Pod成功结束,则整个Job成功结束。

利用job的粗并发管理输出圆周率小数点后2000位。Pods运行成功次数:10;Pod最大失败次数:4次;并行运行的Pod的个数:2。

kubectl create job myjob --image=perl -o yaml --dry-run > perl-job.yaml
[root@master ~]# vi perl-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: myjob
spec:
  completions: 10
  parallelism: 2
  template:
    spec:
      containers:
      - name: myjob
        image: perl
        imagePullPolicy: IfNotPresent
        command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
      restartPolicy: Never
  backoffLimit: 4
  
[root@master ~]# kubectl create -f perl-job.yaml 
job.batch/myjob created
[root@master ~]# kubectl get job
NAME    COMPLETIONS   DURATION   AGE
myjob   0/10          5m29s      5m29s

 

Cronjob:定时任务

Cronjob:定时任务

Kubernetes从1.5版本开始增加了一种新类型的Job,即类似Linux Cron的定时任务Cron Job,下面看看如何定义和使用这种类型的Job。

首先,确保Kubernetes的版本为1.8及以上。

其次,需要掌握Cron Job的定时表达式,它基本上照搬了Linux Cron的表达式。

是第1位是分钟而不是秒,格式如下:

其中每个域都可出现的字符如下。

  1. Minutes:可出现“,”“-”“*”“/”这4个字符,有效范围为0~59的整数。
  2. Hours:可出现“,”“-”“*”“/”这4个字符,有效范围为0~23的整数。
  3. DayofMonth:可出现“,”“-”“*”“/”“?”“L”“W”“C”这8个字符,有效范围为0~31的整数。
  4. Month:可出现“,”“-”“*”“/”这4个字符,有效范围为1~12的整数或JAN~DEC。
  5. DayofWeek:可出现“,”“-”“*”“/”“?”“L”“C”“#”这8个字符,有效范围为1~7的整数或SUN~SAT。1表示星期天,2表示星期一,以此类推。
  6. 表达式中的特殊字符“*”与“/”的含义如下。
  7. *:表示匹配该域的任意值,假如在Minutes域使用“*”,则表示每分钟都会触发事件。
  8. /:表示从起始时间开始触发,然后每隔固定时间触发一次,例如在Minutes域设置为5/20,则意味着第1次触发在第5min时,接下来每20min触发一次,将在第25min、第45min等时刻分别触发。
posted @ 2021-07-27 14:31  isicman  阅读(119)  评论(0编辑  收藏  举报