K8s-----pod详解

目录

一、Pod介绍

1、Pod的基础概念

2、pod定义(资源清单)

二、Pod相关实例

1、初始化容器(init容器)

2、镜像拉取策略(image PullPolicy)

3、pod容器重启策略(restartPolicy)

三、Pod进阶

1、资源限制

2、健康检查:又称为探针(Probe)

2.1 livenessProbe (存活探针

2.2 readinessProbe(就绪探针)

2.3 启动、退出动作

3、pod的状态

四、Pod调度

1、自动调度

1.1 list-watch监听机制

1.2 调度过程

2、定向调度

2.1 NodeName(节点名称)

2.2 NodeSeletor(node标签)

3、亲和性调度

3.1 NodeAffinity(节点亲和性)

3.2 PodAffinity(Pod亲和性)

3.3 PodAntiAffinity(Pod反亲和性)

4、Pod污点(Taints)

4.1 NoExecute实例

4.2 NoSchedule实例

5、Pod容忍(Toleration)

实例1:设置 NoSchedule 污点

实例2:设置NoExecute污点


一、Pod介绍

1、Pod的基础概念

Pod是kubernetes中最小的资源管理组件,Pod也是最小化运行容器化应用的资源对象,一个pod代表着集群中运行的一个进程。kubernetes中其它大多数组件都是围绕着pod来进行支持和扩展pod功能的。

例如,用于管理pod运行的statefulset和deployment 等控制器对象,用于暴露应用的service和ingress对象,为pod提供存储的persistentVolumes存储资源对象。

在kubernetes集群中Pod有如下两种使用方式

  • 一个Pod中运行一个容器, “ 每个Pod中一个容器” 的模式是最常见的用法,在这种使用方式中,你可以把Pod想像成单个容器的封装,kubernetes管理的是Pod而不是直接管理容器。
  • 在一个Pod中同时运行多个容器 ,一个Pod中也可以同时封装几个需要紧密耦合互相协作的容器,他们之间共享资源,这些在同一个Pod中的容器可以互相协作称为一个service单位,比如一个容器共享文件,另一个sidecar 容器来更新这些文件。pod将这些容器的存储资源作为一个实体来管理。

一个Pod下的容器必须运行在同一个节点上,现代容器技术建议一个容器只运行一个进程,该进程在容器中PID命名空间中的进程号为1,可直接接受并处理信号,进程终止时容器生命周期也就结束了。

若想在容器内运行多个进程,需要有一个类似linux操作系统init进程的管控类进程,以树状结构完成多进程的生命周期管理,运行于各自容器内的 进程无法直接完成网络通信,这是由于容器间的隔离机制导致,k8s中的Pod资源抽象正式解决此类问题,Pod对象是一组容器的集合,这些容器共享network、UTS及IPC命名空间,因此具有相同的域名,主句名和网络接口,并可通过IPC直接通信。

namespace功能
mnt(mount)提供磁盘挂载点和文件系统的隔离能力
ipc(inter-Process Communication)提供进程间通信的隔离能力
net(network)提供网络隔离能力
uts(Unix Time Sharing)提供主机名隔离能力
pid提供进程号隔离能力
user提供用户隔离能力

Pod中的pause容器

Pod资源中针对各容器提供网络命名空间等共享机制的是底层基础容器pause,pause就是为了管理Pod容器间的共享操作,这个副容器需要能够准确的知道如何去创建共享运行环境的容器,还能管理这些容器的生命周期,为了实现这个副容器的构想,kubernetes中用pause容器来作为一个Pod中所有容器的副容器, 这个pause容器有两个核心的功能,一个是它提供整个Pod的Linux命名空间的基础,二是启动PID命名空间,它在每个Pod中都作为PID为1的进程(init进程),并回收僵尸进程。


pause容器使得Pod中的所有容器可以共享两种资源:网络和存储

网络

  • 每个Pod都会被分配一个唯一的IP地址,Pod中的所有容器共享网络空间,包括IP地址和端口,Pod内部的容器可以使用localhost互相通信,Pod中的容器与外界通信时,必须分配共享网络资源(例如使用宿主机的端口映射)

存储

  • Pod可以指定多个共享的Volume,Pod中的所有容器都可以访问共享Volume。Volume也可以用来持久化Pod中的存储资源,以防止容器后文件丢失。

总结:
每个Pod都有一个特殊的被称为“基础容器”的pause容器,pause容器对应的镜像属于kubernetes平台的一部分,除了pause容器,每个Pod号包含一个或多个紧密相关的用户应用容器。

kubernetes中pause容器主要为每个容器提供一下功能:

  • 在pod中担任linux命名空间(如网络命名空间)共享的基础
  • 启用PID命名空间,开启init进程

kubernetes涉及这样的Pod概念和特殊组成结构有什么用意?

  • 原因一:在一组容器作为一个单元的情况下,难以对整体的容器简单的进行判断及有效进行行动。比如一个容器死亡了,此时是算整体挂了吗?那么引入与业务无关的pause容器作为Pod的基础容器,以它的状态代表牌整个容器组的状态,这样就可以解决该问题。
  • 原因二:Pod里的多个应用容器共享pause容器的IP。共享pause容器挂载的Volume,这样简化了应用容器之间的通信问题,也解决了容器之间的文件共享问题。

就是产生pause容器的两个原因是,1、通过pause容器判断整个pod中容器是否正常,2、通过pause容器共享网络和挂载。

Pod的分类

自主式Pod

  • 这种Pod本身是不能自我修复的,当Pod被创建后(不论是由你直接创建还是被其它controller),都会被kubernetes调度到集群的Node上,直到Pod的进程终止,被删掉,因为缺少资源而被驱逐,或者Node故障之前这个Pod都会一直保持在那个Node上。
  • Pod不会自愈。如果Pod运行的Node故障,或者是调度器本身故障,这个Pod就会被删除,同样的,如果所在Node缺少资源或者pod处理维护状态,Pod也会被驱逐。

控制器管理的Pod

  • kubernetes使用更高级的称为controller的抽象层,来管理Pod实例。controller可以创建和管理多个Pod,提供副本管理,滚动升级和集群级别的自愈能力。
  • 例如,如果一个Node故障,controller就能自动将该节点上Pod调度到其它健康的Node上,虽然可以直接使用Pod,但是kubernetes中通常是使用controller来管理Pod的。

Pod的容器分类

基础容器(infrastructure container)

  • 维护整个Pod网络和存储空间
  • node节点中操作
  • 启动一个容器时,k8s会自动启动一个基础容器

初始化容器(init container)

Init 容器必须在应用程序容器启动之前运行完成,而应用程序容器是并行运行的,所以 Init 容器能够提供了一种简单的阻塞或延迟应用容器的启动的方法。Init 容器与普通的容器非常像,除了以下两点

  • Init 容器总是运行到成功完成为止
  • 每个 Init 容器都必须在下一个 Init 容器启动之前成功完成启动和退出

如果 Pod 的 Init 容器失败,k8s 会不断地重启该 Pod,直到 Init 容器成功为止。然而,如果 Pod 对应的重启策略(restartPolicy)为 Never,它不会重新启动。

#init容器的作用
因为 init 容器具有与应用容器分离的单独镜像,其启动相关代码具有如下优势

1、Init 容器可以包含一些安装过程中应用容器中不存在的实用工具或个性化代码。例如,没有必要仅为了在安装过程中使用类似 sed、 awk、python 或 dig 这样的工具而去 FROM 一个镜像来生成一个新的镜像。

2、Init 容器可以安全地运行这些工具,避免这些工具导致应用镜像的安全性降低。

3、应用镜像的创建者和部署者可以各自独立工作,而没有必要联合构建一个单独的应用镜像。

4、Init 容器能以不同于 Pod 内应用容器的文件系统视图运行。因此,Init 容器可具有访问 Secrets的权限,而应用容器不能够访问。

5、由于 Init 容器必须在应用容器启动之前运行完成,因此 Init容器提供了一种机制来阻塞或延迟应用容器的启动,直到满足了一组先决条件。一旦前置条件满足,Pod 内的所有的应用容器会并行启动。

应用容器( main container)

  • 在init容器完成并退出后并行启动

2、pod定义(资源清单)

apiVersion: v1     #必选,版本号,例如v1
kind: Pod         #必选,资源类型,例如 Pod
metadata:         #必选,元数据
  name: string     #必选,Pod名称
  namespace: string  #Pod所属的命名空间,默认为"default"
  labels:           #自定义标签列表
    - name: string                 
spec:  #必选,Pod中容器的详细定义
  containers:  #必选,Pod中容器列表
  - name: string   #必选,容器名称
    image: string  #必选,容器的镜像名称
    imagePullPolicy: [ Always|Never|IfNotPresent ]  #获取镜像的策略 
    command: [string]   #容器的启动命令列表,如不指定,使用打包时使用的启动命令
    args: [string]      #容器的启动命令参数列表
    workingDir: string  #容器的工作目录
    volumeMounts:       #挂载到容器内部的存储卷配置
    - name: string      #引用pod定义的共享存储卷的名称,需用volumes[]部分定义的的卷名
      mountPath: string #存储卷在容器内mount的绝对路径,应少于512字符
      readOnly: boolean #是否为只读模式
    ports: #需要暴露的端口库号列表
    - name: string        #端口的名称
      containerPort: int  #容器需要监听的端口号
      hostPort: int       #容器所在主机需要监听的端口号,默认与Container相同
      protocol: string    #端口协议,支持TCP和UDP,默认TCP
    env:   #容器运行前需设置的环境变量列表
    - name: string  #环境变量名称
      value: string #环境变量的值
    resources: #资源限制和请求的设置
      limits:  #资源限制的设置
        cpu: string     #Cpu的限制,单位为core数,将用于docker run --cpu-shares参数
        memory: string  #内存限制,单位可以为Mib/Gib,将用于docker run --memory参数
      requests: #资源请求的设置
        cpu: string    #Cpu请求,容器启动的初始可用数量
        memory: string #内存请求,容器启动的初始可用数量
    lifecycle: #生命周期钩子
        postStart: #容器启动后立即执行此钩子,如果执行失败,会根据重启策略进行重启
        preStop: #容器终止前执行此钩子,无论结果如何,容器都会终止
    livenessProbe:  #对Pod内各容器健康检查的设置,当探测无响应几次后将自动重启该容器
      exec:         #对Pod容器内检查方式设置为exec方式
        command: [string]  #exec方式需要制定的命令或脚本
      httpGet:       #对Pod内个容器健康检查方法设置为HttpGet,需要制定Path、port
        path: string
        port: number
        host: string
        scheme: string
        HttpHeaders:
        - name: string
          value: string
      tcpSocket:     #对Pod内个容器健康检查方式设置为tcpSocket方式
         port: number
       initialDelaySeconds: 0       #容器启动完成后首次探测的时间,单位为秒
       timeoutSeconds: 0          #对容器健康检查探测等待响应的超时时间,单位秒,默认1秒
       periodSeconds: 0           #对容器监控检查的定期探测时间设置,单位秒,默认10秒一次
       successThreshold: 0
       failureThreshold: 0
       securityContext:
         privileged: false
  restartPolicy: [Always | Never | OnFailure]  #Pod的重启策略
  nodeName: <string> #设置NodeName表示将该Pod调度到指定到名称的node节点上
  nodeSelector: obeject #设置NodeSelector表示将该Pod调度到包含这个label的node上
  imagePullSecrets: #Pull镜像时使用的secret名称,以key:secretkey格式指定
  - name: string
  hostNetwork: false   #是否使用主机网络模式,默认为false,如果设置为true,表示使用宿主机网络
  volumes:   #在该pod上定义共享存储卷列表
  - name: string    #共享存储卷名称 (volumes类型有很多种)
    emptyDir: {}       #类型为emtyDir的存储卷,与Pod同生命周期的一个临时目录。为空值
    hostPath: string   #类型为hostPath的存储卷,表示挂载Pod所在宿主机的目录
      path: string                #Pod所在宿主机的目录,将被用于同期中mount的目录
    secret:          #类型为secret的存储卷,挂载集群与定义的secret对象到容器内部
      scretname: string  
      items:     
      - key: string
        path: string
    configMap:         #类型为configMap的存储卷,挂载预定义的configMap对象到容器内部
      name: string
      items:
      - key: string
        path: string

在kubernetes中所有资源的一级属性都是一样的,主要包含5部分

  • apiVersion 版本: 由kubernetes内部定义,版本号必须可以用kubectl api-versions 查询到
  • kind 类型: 由kubernetes 内部定义,版本号必须可以用kubectl api-resources 查询到
  • metadata 元数据: 主要是资源标识和说明,常用的有name、namespace、labels等
  • spec 描述: 这是配置中最重要的一部分,里面是对各种资源配置的详细描述。
  • status 状态信息: 里面的内容不需要定义,由kubernetes自动生成。

在上面的属性中,spec 是接下来研究的重点,继续看它的常见子属性

  • containers <[]Object> 容器列表: 用于定于容器的详细信息
  • nodeName : 根据nodeName的值将pod调度到指定的Node节点上
  • nodeSelector <map[]> : 根据NodeSelrctor中定义的信息选择将该pod调度到包含这些label的Node上
  • hostNetwork 是否使用主机网络模式,默认为false,如果设置为true,表示使用宿主机网络
  • volumes <[]Object> 存储卷: 用于定义Pod上面挂在的存储信息
  • restartPolicy 重启策略: 表示Pod在遇到故障的时候的处理策略

二、Pod相关实例

官网示例:https://kubernetes.io/zh-cn/docs/concepts/workloads/pods/init-containers/

1、初始化容器(init容器)

下述例子是定义了一个具有 2 个 Init 容器的简单 Pod。 第一个等待 myservice 启动, 第二个等待 mydb 启动。 一旦这两个 Init容器都启动完成,Pod 将启动 spec 中的应用容器。

apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod
  labels:
    app: myapp
spec:
  containers:
  - name: myapp-container
    image: busybox:1.28
    command: ['sh', '-c', 'echo The app is running! && sleep 3600']
  initContainers:
  - name: init-myservice
    image: busybox:1.28
    command: ['sh', '-c', 'until nslookup myservice; do echo waiting for myservice; sleep 2; done;']
  - name: init-mydb
    image: busybox:1.28
    command: ['sh', '-c', 'until nslookup mydb; do echo waiting for mydb; sleep 2; done;']

#查看pod的详细信息
kubectl describe pod myapp-pod

#查看具体的日志信息
kubectl logs myapp-pod -c init-myservice

#配置myservice的yaml文件
vim myservice.yaml
apiVersion: v1
kind: Service
metadata:
  name: myservice
spec:
  ports:
  - protocol: TCP
    port: 80
    targetPort: 9376
	
kubectl create -f myservice.yaml

kubectl get svc

kubectl get pods -n kube-system

kubectl get pods

特别说明:

  • 在Pod启动过程中,Init容器会按顺序在网络和数据卷初始化之后启动。每个容器必须在下一个容器启动之前成功退出。
  • 如果由于运行时或失败退出,将导致容器启动失败,它会根据Pod的restartPolicy指定的策略进行重试。然而,如果Pod的restartPolicy设置为Always,Init容器失败时会使用RestartPolicy策略。
  • 在所有的Init容器没有成功之前,Pod将不会变成Ready状态。Init容器的端口将不会在Service中进行聚集。正在初始化中的Pod处于Pending状态,但应该会将Initializing状态设置为true。
  • 如果Pod重启,所有Init容器必须重新执行。
  • 对Init容器spec的修改被限制在容器image字段,修改其他字段都不会生效。更改Init容器的image字段,等价于重启该Pod。
  • Init容器具有应用容器的所有字段。除了readinessProbe,因为Init容器无法定义不同于完成(completion)的就绪(readiness)之外的其他状态。这会在验证过程中强制执行。
  • 在Pod中的每个app和Init容器的名称必须唯一;与任何其它容器共享同一个名称,会在验证时抛出错误。

2、镜像拉取策略(image PullPolicy)

Pod 的核心是运行容器,必须指定容器引擎,比如 Docker,启动容器时,需要拉取镜像,k8s 的镜像拉取策略可以由用户指定:

  • IfNotPresent(非latest):优先使用本地已存在的仓库,如本地没有,则从仓库镜像拉取
  • Always(latest默认):总是从仓库去拉取镜像,无论本地是否已存在镜像
  • Never:总是不从仓库去拉取镜像,仅使用本地镜像

注意:对于标签为“:latest”的镜像文件,其默认的镜像获取策略即为“Always”;而对于其他标签的镜像,其默认策略则为“IfNotPresent”。

创建Pod资源时,因为设置镜像的版本资源为:lastest,默认的镜像拉取策略为Always

设置镜像的版本资源为:1.14

重新创建一个Pod资源,修改镜像版本为latest,然后再设置镜像策略为IfNotPresent,会按设置的顺序进行。

3、pod容器重启策略(restartPolicy)

当 Pod 中的容器退出时通过节点上的 kubelet 重启容器。适用于 Pod 中的所有容器。

1、Always

当容器终止退出后,总是重启容器,默认策略

容器退出总是重启容器,不管返回状态码如何,默认的Pod容器重启策略

2、OnFailure

当容器异常退出(退出状态码非0)时,重启容器;正常退出则不重启容器

仅在容器异常退出时,返回码为非0时,会重启容器。

3、Never

当容器终止退出,从不重启容器。

容器退出时从不重启容器,不管返回状态码如何

#注意:K8S 中不支持重启 Pod 资源,只有删除重建

创建的Pod资源,默认的重启策略都为:Always

重新创建Pod资源,并且添加默认的重启策略为:Never 

三、Pod进阶

1、资源限制

当定义 Pod 时可以选择性地为每个容器设定所需要的资源数量。 最常见的可设定资源是 CPU 和内存大小,以及其他类型的资源。

这种机制主要通过resources选项实现,他有两个子选项:

  • requests :用于设置预留资源,指定了 request 资源时,调度器就使用该信息来决定将 Pod 调度到哪个节点上。
  • limits: 用于限制运行时容器的最大占用资源,当容器占用资源超过limits时会被终止,并进行重启

如果 Pod 运行所在的节点具有足够的可用资源,容器可以使用超出所设置的 request 资源量。不过,容器不可以使用超出所设置的 limit 资源量。

如果给容器设置了内存的 limit 值,但未设置内存的 request 值,Kubernetes 会自动为其设置与内存 limit 相匹配的 request 值。 

官网示例:
https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/

CPU 资源单位:

  • CPU 资源的 request 和 limit 以 cpu 为单位。Kubernetes 中的一个 cpu 相当于1个 vCPU(1个超线程)。
  • Kubernetes 也支持带小数 CPU 的请求。spec.containers[].resources.requests.cpu 为 0.5 的容器能够获得一个 cpu 的一半 CPU 资源(类似于Cgroup对CPU资源的时间分片)。表达式 0.1 等价于表达式 100m(毫核),表示每 1000 毫秒内容器可以使用的 CPU 时间总量为 0.1*1000 毫秒。
  • Kubernetes 不允许设置精度小于 1m 的 CPU 资源。 

内存 资源单位 :

  • 内存的 request 和 limit 以字节为单位。可以以整数表示,或者以10为底数的指数的单位(E、P、T、G、M、K)来表示, 或者以2为底数的指数的单位(Ei、Pi、Ti、Gi、Mi、Ki)来表示。
  • 如:1KB=10^3=1000,1MB=10^6=1000000=1000KB,1GB=10^9=1000000000=1000MB,1KiB=2^10=1024,1MiB=2^20=1048576=1024KiB

案例:

vim pod1.yaml
apiVersion: v1
kind: Pod
metadata:
  name: frontend
spec:
  containers:
  - name: web
    image: nginx
    env:
    - name: WEB_ROOT_PASSWORD
      value: "password"
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"
  - name: db
    image: mysql
    env:
    - name: MYSQL_ROOT_PASSWORD
      value: "abc123"
    resources:
      requests:
        memory: "512Mi"  
        cpu: "0.5"
      limits:
        memory: "1Gi"    
        cpu: "1"

kubectl apply -f pod-resources.yaml

kubectl describe pod frontend
#查看详细信息

kubectl get pods -o wide

kubectl describe nodes node02				
#由于当前虚拟机有2个CPU,所以Pod的CPU Limits一共占用了75%

2、健康检查:又称为探针(Probe)

探针是由kubelet对容器执行的定期诊断。

探针的三种规则:

  • livenessProbe (存活探针):判断容器是否正在运行。如果探测失败,则kubelet会杀死容器,并且容器将根据 restartPolicy 来设置 Pod 状态。 如果容器不提供存活探针,则默认状态为Success。
  • readinessProbe(就绪探针) :判断容器是否准备好接受请求。如果探测失败,端点控制器将从与 Pod 匹配的所有 service 址endpoints 中剔除删除该Pod的IP地。 初始延迟之前的就绪状态默认为Failure。如果容器不提供就绪探针,则默认状态为Success。
  • startupProbe(启动探针):判断容器内的应用程序是否已启动,主要针对于不能确定具体启动时间的应用。如果配置了 startupProbe 探测,则在 startupProbe 状态为 Success 之前,其他所有探针都处于无效状态,直到它成功后其他探针才起作用。 如果 startupProbe 失败,kubelet 将杀死容器,容器将根据 restartPolicy 来重启。如果容器没有配置 startupProbe, 则默认状态为 Success。

Probe支持三种检查方法:

  • exec :在容器内执行指定命令。如果命令退出时返回码为0则认为诊断成功。
  • tcpSocket :对指定端口上的容器的IP地址进行TCP检查(三次握手)。如果端口打开,则诊断被认为是成功的。
  • httpGet :对指定的端口和路径上的容器的IP地址执行HTTPGet请求。如果响应的状态码大于等于200且小于400,则诊断被认为是成功的

每次探测都将获得以下三种结果之一:

●成功:容器通过了诊断。
●失败:容器未通过诊断。
●未知:诊断失败,因此不会采取任何行动

官网示例:
https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/

2.1 livenessProbe (存活探针

实例1:exec方式

vim exec.yaml
apiVersion: v1
kind: Pod
metadata:
  name: liveness-exec
  namespace: default
spec:
  containers:
  - name: liveness-exec-container
    image: busybox
    imagePullPolicy: IfNotPresent
    command: ["/bin/sh","-c","touch /tmp/live ; sleep 30; rm -rf /tmp/live; sleep 3600"]
    livenessProbe:
      exec:
        command: ["test","-e","/tmp/live"]
      initialDelaySeconds: 1
      periodSeconds: 3

可以看到 Pod 中只有一个容器。kubelet 在执行第一次探测前需要等待 2 秒,kubelet 会每 3 秒执行一次存活探测。kubelet 在容器内执行命令 cat /tmp/healthy 来进行探测。如果命令执行成功并且返回值为 0,kubelet 就会认为这个容器是健康存活的。 当到达第 31 秒时,这个命令返回非 0 值,kubelet 会杀死这个容器并重新启动它。

探测策略:

  • initialDelaySeconds:指定 kubelet 在执行第一次探测前应该等待1秒,即第一次探测是在容器启动后的第2秒才开始执行。默认是 0 秒,最小值是 0。
  • periodSeconds:指定了 kubelet 应该每3 秒执行一次存活探测。默认是 10 秒。最小值是 1。
  • failureThreshold: 当探测失败时,Kubernetes 将在放弃之前重试的次数。 存活探测情况下的放弃就意味着重新启动容器。就绪探测情况下的放弃 Pod 会被打上未就绪的标签。默认值是 3。最小值是 1。
  • timeoutSeconds:探测的超时后等待多少秒。默认值是 1 秒。最小值是 1。(在 Kubernetes 1.20 版本之前,exec 探针会忽略 timeoutSeconds 探针会无限期地 持续运行,甚至可能超过所配置的限期,直到返回结果为止。)
#创建资源
kubectl create -f exec.yaml
#查看资源的详细信息
kubectl describe pods liveness-exec
#查看资源实时状态
kubectl get pods -w

实例2:httpGet方式

vim httpget.yaml
apiVersion: v1
kind: Pod
metadata:
  name: liveness-httpget
  namespace: default
spec:
  containers:
  - name: liveness-httpget-container
    image: nginx
    imagePullPolicy: IfNotPresent
    ports:
    - name: http
      containerPort: 80
    livenessProbe:
      httpGet:
        port: http
        path: /index.html
      initialDelaySeconds: 1
      periodSeconds: 3
      timeoutSeconds: 10

在这个配置文件中,可以看到 Pod 也只有一个容器。initialDelaySeconds 字段告诉 kubelet 在执行第一次探测前应该等待 1 秒。periodSeconds 字段指定了 kubelet 每隔 3 秒执行一次存活探测。kubelet 会向容器内运行的服务(服务会监听 80 端口)发送一个 HTTP GET 请求来执行探测。如果服务器上 /index.html 路径下的处理程序返回成功代码,则 kubelet 认为容器是健康存活的。如果处理程序返回失败代码,则 kubelet 会杀死这个容器并且重新启动它。

任何大于或等于 200 并且小于 400 的返回代码标示成功,其它返回代码都标示失败。

#创建pod资源
kubectl create -f httpget.yaml

#测试:将容器内检测的文件删除
kubectl exec -it liveness-httpget -- rm -rf /usr/share/nginx/html/index.html

实例3:tcpSocket方式

apiVersion: v1
kind: Pod
metadata:
  name: goproxy
  labels:
    app: goproxy
spec:
  containers:
  - name: goproxy
    image: k8s.gcr.io/goproxy:0.1
    ports:
    - containerPort: 8080
    readinessProbe:
      tcpSocket:
        port: 8080
      initialDelaySeconds: 5
      periodSeconds: 10
    livenessProbe:
      tcpSocket:
        port: 8080
      initialDelaySeconds: 15
      periodSeconds: 20

上述例子同时使用 readinessProbe 和 livenessProbe 探测。kubelet 会在容器启动 5 秒后发送第一个 readinessProbe 探测。这会尝试连接 goproxy 容器的 8080 端口。如果探测成功,kubelet 将继续每隔 10 秒运行一次检测。除了 readinessProbe 探测,这个配置包括了一个 livenessProbe 探测。kubelet 会在容器启动 15 秒后进行第一次 livenessProbe 探测。就像 readinessProbe 探测一样,会尝试连接 goproxy 容器的 8080 端口。如果 livenessProbe 探测失败,这个容器会被重新启动。

vim tcpsocket.yaml
apiVersion: v1
kind: Pod
metadata:
  name: probe-tcp
spec:
  containers:
  - name: nginx
    image: nginx:1.13
    livenessProbe:
      initialDelaySeconds: 5
      timeoutSeconds: 1
      tcpSocket:
        port: 8080
      periodSeconds: 10
      failureThreshold: 2

2.2 readinessProbe(就绪探针)

实例1:

vim readiness-httpget.yaml
apiVersion: v1
kind: Pod
metadata:
  name: readiness-httpget
  namespace: default
spec:
  containers:
  - name: readiness-httpget-container
    image: soscscs/myapp:v1
    imagePullPolicy: IfNotPresent
    ports:
    - name: http
      containerPort: 80
    readinessProbe:
      httpGet:
        port: 80
        path: /index1.html
      initialDelaySeconds: 1
      periodSeconds: 3
    livenessProbe:
      httpGet:
        port: http
        path: /index.html
      initialDelaySeconds: 1
      periodSeconds: 3
      timeoutSeconds: 10

#创建pod资源
kubectl create -f readiness-httpget.yaml

kubectl get pods 

实例2:验证readiness探测失败,Pod 无法进入READY状态,且端点控制器将从 endpoints 中剔除删除该 Pod 的 IP 地址

vim readiness-myapp.yaml
apiVersion: v1
kind: Pod
metadata:
  name: myapp1
  labels:
     app: myapp
spec:
  containers:
  - name: myapp
    image: soscscs/myapp:v1
    ports:
    - name: http
      containerPort: 80
    readinessProbe:
      httpGet:
        port: 80
        path: /index.html
      initialDelaySeconds: 5
      periodSeconds: 5
      timeoutSeconds: 10 
---
apiVersion: v1
kind: Pod
metadata:
  name: myapp2
  labels:
     app: myapp
spec:
  containers:
  - name: myapp
    image: soscscs/myapp:v1
    ports:
    - name: http
      containerPort: 80
    readinessProbe:
      httpGet:
        port: 80
        path: /index.html
      initialDelaySeconds: 5
      periodSeconds: 5
      timeoutSeconds: 10 
---
apiVersion: v1
kind: Pod
metadata:
  name: myapp3
  labels:
     app: myapp
spec:
  containers:
  - name: myapp
    image: soscscs/myapp:v1
    ports:
    - name: http
      containerPort: 80
    readinessProbe:
      httpGet:
        port: 80
        path: /index.html
      initialDelaySeconds: 5
      periodSeconds: 5
      timeoutSeconds: 10 
---
apiVersion: v1
kind: Service
metadata:
  name: myapp
spec:
  selector:
    app: myapp
  type: ClusterIP
  ports:
  - name: http
    port: 80
    targetPort: 80

2.3 启动、退出动作

vim post.yaml
apiVersion: v1
kind: Pod
metadata:
  name: lifecycle-demo
spec:
  containers:
  - name: lifecycle-demo-container
    image: soscscs/myapp:v1
    lifecycle:   #此为关键字段
      postStart:
        exec:
          command: ["/bin/sh", "-c", "echo Hello from the postStart handler >> /var/log/nginx/message"]      
      preStop:
        exec:
          command: ["/bin/sh", "-c", "echo Hello from the poststop handler >> /var/log/nginx/message"]
    volumeMounts:
    - name: message-log
      mountPath: /var/log/nginx/
      readOnly: false
  initContainers:
  - name: init-myservice
    image: soscscs/myapp:v1
    command: ["/bin/sh", "-c", "echo 'Hello initContainers'   >> /var/log/nginx/message"]
    volumeMounts:
    - name: message-log
      mountPath: /var/log/nginx/
      readOnly: false
  volumes:
  - name: message-log
    hostPath:
      path: /data/volumes/nginx/log/
      type: DirectoryOrCreate

由上可知,init Container先执行,然后当一个主容器启动后,Kubernetes 将立即发送 postStart 事件。

由上可知,当在容器被终结之前, Kubernetes 将发送一个 preStop 事件。

3、pod的状态

  • pending:pod已经被系统认可了,但是内部的container还没有创建出来。这里包含调度到node上的时间以及下载镜像的时间,会持续一小段时间。
  • Running:pod已经与node绑定了(调度成功),而且pod中所有的container已经创建出来,至少有一个容器在运行中,或者容器的进程正在启动或者重启状态。--这里需要注意pod虽然已经Running了,但是内部的container不一定完全可用。因此需要进一步检测container的状态。
  • Succeeded:这个状态很少出现,表明pod中的所有container已经成功的terminated了,而且不会再被拉起了。
  • Failed:pod中的所有容器都被terminated,至少一个container是非正常终止的。(退出的时候返回了一个非0的值或者是被系统直接终止)
  • unknown:由于某些原因pod的状态获取不到,有可能是由于通信问题。 一般情况下pod最常见的就是前两种状态。而且当Running的时候,需要进一步关注container的状态
     

四、Pod调度

在默认情况下,一个Pod在哪个Node节点上运行,是由Scheduler组件采用相应的算法计算出来的,这个过程是不受人工控制的。但是在实际使用中,这并不满足的需求,因为很多情况下,我们想控制某些Pod到达某些节点上,那么应该怎么做呢?这就要求了解kubernetes对Pod的调度规则,kubernetes提供了四大类调度方式:

  • 自动调度: 运行在哪个节点上完全由Scheduler经过一系列的算法计算得出
  • 定向调度: NodeName(节点名称)、NodeSelector(标签选择器)
  • 亲和性调度: NodeAffinity(节点亲和)、PodAffinity(pod亲和)、PodAntiAffinity(Pod非亲和)
  • 污点(容忍)调度: Taints、Toleration

1、自动调度

1.1 list-watch监听机制

各组件协作介绍

kubernets是通过List-Watch的机制进行每个组件的协作,保持数据同步的,每个组件之间的设计实现了解耦。

  • 用户是通过kubelet 根据配置文件,向APIServer发送命名,在Node节点上建立Pod和container。

  • APIServer经过API 调用,权限控制,调用资源和存储资源的过程,实际上还没有真正开始部署应用,这里需要controller Manager、Scheduler 和 kubelet 的协助才能完成整个部署过程。

  • 在kubernetes中,所有部署的信息都会写到etcd中保存,实际上etcd在存储部署信息的时候,会发送Create 事件给APIServer,而APIServer会通过监听(watch)etcd。

Pod 启动典型创建过程

(1)这里有三个 List-Watch,分别是 Controller Manager(运行在 Master),Scheduler(运行在 Master),kubelet(运行在 Node)。 他们在进程已启动就会监听(Watch)APIServer 发出来的事件。

(2)用户通过 kubectl 或其他 API 客户端提交请求给 APIServer 来建立一个 Pod 对象副本。

(3)APIServer 尝试着将 Pod 对象的相关元信息存入 etcd 中,待写入操作执行完成,APIServer 即会返回确认信息至客户端。

(4)当 etcd 接受创建 Pod 信息以后,会发送一个 Create 事件给 APIServer。

(5)由于 Controller Manager 一直在监听(Watch,通过https的6443端口)APIServer 中的事件。此时 APIServer 接受到了 Create 事件,又会发送给 Controller Manager。

(6)Controller Manager 在接到 Create 事件以后,调用其中的 Replication Controller 来保证 Node 上面需要创建的副本数量。一旦副本数量少于 RC 中定义的数量,RC 会自动创建副本。总之它是保证副本数量的 Controller(PS:扩容缩容的担当)。

(7)在 Controller Manager 创建 Pod 副本以后,APIServer 会在 etcd 中记录这个 Pod 的详细信息。例如 Pod 的副本数,Container 的内容是什么。

(8)同样的 etcd 会将创建 Pod 的信息通过事件发送给 APIServer。

(9)由于 Scheduler 在监听(Watch)APIServer,并且它在系统中起到了 “承上启下” 的作用,“承上”是指它负责接收创建的 Pod 事件,为其安排 Node;“启下”是指安置工作完成后,Node 上的 kubelet 进程会接管后继工作,负责 Pod 生命周期中的“下半生”。 换句话说, Scheduler 的作用是将待调度的 Pod 按照调度算法和策略绑定到集群中 Node 上。

(10)Scheduler 调度完毕以后会更新 Pod 的信息,此时的信息更加丰富了。除了知道 Pod 的副本数量,副本内容。还知道部署到哪个 Node 上面了。并将上面的 Pod 信息更新至 API Server,由 APIServer 更新至 etcd 中,保存起来。

(11)etcd 将更新成功的事件发送给 APIServer,APIServer 也开始反映此 Pod 对象的调度结果。

(12)kubelet 是在 Node 上面运行的进程,它也通过 List-Watch 的方式监听(Watch,通过https的6443端口)APIServer 发送的 Pod 更新的事件。kubelet 会尝试在当前节点上调用 Docker 启动容器,并将 Pod 以及容器的结果状态回送至 APIServer。

(13)APIServer 将 Pod 状态信息存入 etcd 中。在 etcd 确认写入操作成功完成后,APIServer将确认信息发送至相关的 kubelet,事件将通过它被接受。
 

#注意:在创建 Pod 的工作就已经完成了后,

#为什么 kubelet 还要一直监听呢?

原因很简单,假设这个时候 kubectl 发命令,要扩充 Pod 副本数量,那么上面的流程又会触发一遍,kubelet 会根据最新的 Pod 的部署情况调整 Node 的资源。又或者 Pod 副本数量没有发生变化,但是其中的镜像文件升级了,kubelet 也会自动获取最新的镜像文件并且加载。

1.2 调度过程

scheduler是kubernetes 的调度器,主要的任务是把定义的Pod分配到集群的节点上,其主要考虑的问题如下。

  • 公平: 如何保证每个节点都被分配资源
  • 资源高效利用: 集群所有资源最大化被使用
  • 效率:调度的性能要好,能够尽快地对大批量的 pod 完成调度工作
  • 灵活: 允许用户根据自己的需求控制调度的逻辑

调度分为几个部分: 首先是过滤掉不满足条件的节点,这个过程称为预算策略(predicate);然后对通过的节点按照优先级排序,这个是优选策略(priorities);最后从中选择优先级最高的节点。如果中间任何一步骤有错误,就直接返回错误。

Predicate 有一系列的常见的算法可以使用:

  • PodFitsResources:节点上剩余的资源是否大于 pod 请求的资源。
  • PodFitsHost:如果 pod 指定了 NodeName,检查节点名称是否和 NodeName 匹
  • PodFitsHostPorts:节点上已经使用的 port 是否和 pod 申请的 port 冲突。
  • PodSelectorMatches:过滤掉和 pod 指定的 label 不匹配的节点。
  • NoDiskConflict:已经 mount 的 volume 和 pod 指定的 volume 不冲突,除非它们都是只读。
如果在 predicate 过程中没有合适的节点,pod 会一直在 pending 状态,不断重试调度,直到有节点满足条件。

经过这个步骤,如果有多个节点满足条件,就继续 priorities 过程:按照优先级大小对节点排序。

priorities一系列键值对组成,键是该优先级项的名称,值是它的权重(该项的重要性)。有一系列的常见的优先级选项包括:

  • LeastRequestedPriority:通过计算CPU和Memory的使用率来决定权重,使用率越低权重越高。也就是说,这个优先级指标倾向于资源使用比例更低的节点。
  • BalancedResourceAllocation:节点上 CPU 和 Memory 使用率越接近,权重越高。这个一般和上面的一起使用,不单独使用。比如 node01 的 CPU 和 Memory 使用率 20:60,node02 的 CPU 和 Memory 使用率 50:50,虽然 node01 的总使用率比 node02 低,但 node02 的 CPU 和 Memory 使用率更接近,从而调度时会优选 node02。
  • ImageLocalityPriority:倾向于已经有要使用镜像的节点,镜像总大小值越大,权重越高。

2、定向调度

定向调度、指的是利用在Pod上声明nodeName或nodeSelector,以此将Pod调度到期望的node节点上。注意,这里的调度是强制的,这就意味着即使调度的目标node不存在,也会向上面进行调度,只不过pod运行失败而已。

2.1 NodeName(节点名称)

NodeName用于强制约束将Pod调度到指定的Name的Node节点上,这种方式,起始就是直接跳过Scheduler的调度逻辑,直接将Pod调度到指定名称的节点。

创建一个pod-nodename.yaml文件

apiVersion: v1
kind: Pod
metadata:
  name: pod-nodename
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  nodeName: node01        # 指定调度到node01节点上

kubectl apply -f myapp.yaml

kubectl get pods -o wide

##查看详细事件(发现未经过 scheduler 调度分配)
kubectl describe pod pod-nodename 

2.2 NodeSeletor(node标签)

NodeSelector 用于将pod调度到添加了指定标签的node节点上,它是通过kubernetes的label-selector机制实现的,也就是说,在pod创建之前,会由scheduler使用MatchNodeSelector调度策略进行label匹配,找出目标node,然后pod调度到目标节点,该匹配规则是强制约束。

给节点添加标签

创建pod-nodeselector.yaml文件

apiVersion: apps/v1
kind: Deployment  
metadata:
  name: myapp1
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp1
  template:
    metadata:
      labels:
        app: myapp1
    spec:
      nodeSelector:
	    kfc: a
      containers:
      - name: myapp1
        image: soscscs/myapp:v1
        ports:
        - containerPort: 80

kubectl apply -f pod-nodeselector.yaml

kubectl get pods pod-nodeselector -o wide

##此时调度到指定标签的节点上,如果在yaml文件中,将标签修改,它将会调度到标签所在的node,即使node不存在。

标签相关设置:

#修改一个 label 的值,需要加上 --overwrite 参数
kubectl label nodes node02 kgc=a --overwrite

#删除一个 label,只需在命令行最后指定 label 的 key 名并与一个减号相连即可:
kubectl label nodes node02 kgc-

#指定标签查询 node 节点
kubectl get node -l kgc=a

3、亲和性调度

官方文档:https://kubernetes.io/zh/docs/concepts/scheduling-eviction/assign-pod-node/

定向调度使用起来非常方便,但是也存在一定的问题,那就是没有满足条件的node,那么Pod将不会被运行,即使在集群中还有可用的Node列表也不行,这就限制了它的使用场景

基于上面的问题,kubernetes还提供了一种亲和性调度(Affinity),它在NodeSelector的基础之上进行了扩展,可用通过配置的形式,实现优先选择满足条件的Node进行调度,如果没有,也可以调度到不满足的节点上,使调度更加灵活。

Affinity主要分为三类

  • nodeAffinity(node亲和性): 以node为目标,解决pod可以调度哪些node问题。
  • podAffinity(pod亲和性): 以pod为目标,解决pod可以和哪些已存在的pod部署在同一个拓扑域中的问题
  • podAntiAffinity(pod反亲和性): 以pod为目标,解决pod不能和哪些已存在pod部署在同一个拓扑域中的问题。
#关于亲和性(反亲和性)使用场景的说明
//亲和性:
如果两个应用频繁交互,那就有必要利用亲和性让两个应用的竟可能靠近,这样可以减少因网络通信而带来的性能损耗。

//反亲和性:
 当应用的采用多副本部署时,有必要采用反亲和性让各个应用实例打散分布在各个node上,这样可以提高服务的高可用性。

3.1 NodeAffinity(节点亲和性)

kubectl explain pod.spec.affinity.nodeAffinity  #查看解释资源

----------------------------------硬限制----------------------------------------------------
  requiredDuringSchedulingIgnoredDuringExecution  #Node节点必须满足指定的所有规则才可以,相当于硬限制
    nodeSelectorTerms  #节点选择列表
      matchFields   #按节点字段列出的节点选择器要求列表
      matchExpressions   #按节点标签列出的节点选择器要求列表(推荐)
        key      #键
        values   #值
        operator  #关系符:支持Exists, DoesNotExist, In, NotIn, Gt, Lt

----------------------------------软限制----------------------------------------------------        
  preferredDuringSchedulingIgnoredDuringExecution #优先调度到满足指定的规则的Node,相当于软限制 (倾向)
    preference   #一个节点选择器项,与相应的权重相关联
      matchFields   #按节点字段列出的节点选择器要求列表
      matchExpressions  #按节点标签列出的节点选择器要求列表(推荐)
        key    #键
        values #值
        operator #关系符:支持In, NotIn, Exists, DoesNotExist, Gt, Lt
	weight    #倾向权重,在范围1-100。
#键值运算关系

●In:label 的值在某个列表中  pending

●NotIn:label 的值不在某个列表中

●Gt:label 的值大于某个值

●Lt:label 的值小于某个值

●Exists:某个 label 存在

●DoesNotExist:某个 label 不存在

关系符的使用说明:

- matchExpressions:
  - key: nodeenv              # 匹配存在标签的key为nodeenv的节点
    operator: Exists
    
  - key: nodeenv              # 匹配标签的key为nodeenv,且value是"xxx"或"yyy"的节点
    operator: In
    values: ["xxx","yyy"]
   
  - key: nodeenv              # 匹配标签的key为nodeenv,且value大于"xxx"的节点(大于小于的是资源的大小。)
    operator: Gt
    values: "xxx"

硬限制案例

  • 这种和定向调度类似,都是必须满足,不然调度失败,显示Pending状态
apiVersion: v1
kind: Pod
metadata:
  name: affinity
  labels:
    app: node-affinity-pod
spec:
  containers:
  - name: with-node-affinity
    image: soscscs/myapp:v1
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/hostname   #指定node的标签
            operator: NotIn     #设置Pod安装到kubernetes.io/hostname的标签值不在values列表中的node上
            values::
            - node02

软限制案例

  • 软限制,会进行按照设置的条件进行匹配,匹配不到,它会自己调度一个node节点
apiVersion: v1
kind: Pod
metadata:
  name: affinity
  labels:
    app: node-affinity-pod
spec:
  containers:
  - name: with-node-affinity
    image: soscscs/myapp:v1
  affinity:
    nodeAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1   #如果有多个软策略选项的话,权重越大,优先级越高
        preference:
          matchExpressions:
          - key: kubernetes.io/hostname
            operator: In
            values:
            - node03

硬策略和软策略一起使用

如果把硬策略和软策略合在一起使用,则要先满足硬策略之后才会满足软策略

nodeAffinity规则设置的注意事项

1、如果同时定义了nodeSelector和nodeAffinity,那么必须两个条件都得到满足,Pod才能运行在指定的Node上。

2、如果nodeAffinity指定了多个nodeSelectorTerms,那么只需要其中一个能够匹配成功即可

3、如果一个nodeSelectorTerms中有多个matchExpressions ,则一个节点必须满足所有的才能匹配成功

4、如果一个pod所在的Node在Pod运行期间其标签发生了改变,不再符合该Pod的节点亲和性需求,则系统将忽略此变化

3.2 PodAffinity(Pod亲和性)

---------------------------------------硬限制-----------------------------------------------

kubectl explain pod.spec.affinity.podAffinity  #查看pod亲和度的解释

  requiredDuringSchedulingIgnoredDuringExecution  #硬限制
    namespaces       #指定参照pod的namespace
    topologyKey      #指定调度作用域
    labelSelector    #标签选择器
      matchExpressions  #按节点标签列出的节点选择器要求列表(推荐)
        key    #键
        values #值
        operator 关系符 #支持In, NotIn, Exists, DoesNotExist.
      matchLabels    #指多个matchExpressions映射的内容
---------------------------------------软限制----------------------------------------------- 
     
  preferredDuringSchedulingIgnoredDuringExecution #软限制
    podAffinityTerm  #选项
      namespaces      
      topologyKey
      labelSelector
        matchExpressions  
          key    键
          values 值
          operator
        matchLabels 
    weight 倾向权重,在范围1-100
#topologyKey 是节点标签的键

1、如果两个节点使用此键标记并且具有相同的标签值,则调度器会将这两个节点视为处于同一拓扑域中;
2、调度器试图在每个拓扑域中放置数量均衡的 Pod;
3、如果 kgc 对应的值不一样就是不同的拓扑域。
#例如:
比如 Pod1 在 kgc=a 的 Node 上,Pod2 在 kgc=b 的 Node 上,Pod3 在 kgc=a 的 Node 上,则 Pod2 和 Pod1、Pod3 不在同一个拓扑域,而Pod1 和 Pod3在同一个拓扑域。

硬限制案例

创建一个标签为 app=myapp01 的 Pod

apiVersion: v1
kind: Pod
metadata:
  name: myapp01
  labels:
    app: myapp01
spec:
  containers:
  - name: with-node-affinity
    image: soscscs/myapp:v1

创建pod02.yaml

apiVersion: v1
kind: Pod
metadata:
  name: mypod02
  labels:
    app: myapp02
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  affinity:  #亲和性设置
    podAffinity: #设置pod亲和性
      requiredDuringSchedulingIgnoredDuringExecution: # 硬限制
      - labelSelector:
          matchExpressions: 
          - key: app
            operator: In
            values: 
            - myapp001
        topologyKey: kubernetes.io/hostname

----------------------------------------------------------------
# 上面配置表达的意思是: 新Pod必须要和拥有标签app: myapp001 的pod在同一node上,显然现在没有这样的pod。

可将亲和度的标签修改如下:
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: app
            operator: In
            values:
            - myapp01    ##更改为存在的node节点的标签
        topologyKey: kubernetes.io/hostname

软限制案例

  • 软限制案例跟硬限制类似,

3.3 PodAntiAffinity(Pod反亲和性)

PodAntiAffinity主要实现以运行的Pod为参照,让新创建的Pod跟参照pod不在一个区域中的功能。

实例1:

创建pod3.yaml,

apiVersion: v1
kind: Pod
metadata:
  name: myapp03
  labels:
    app: myapp03
spec:
  containers:
  - name: myapp03
    image: soscscs/myapp:v1
  affinity:
    podAntiAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: app
              operator: In
              values:
              - myapp01
          topologyKey: kubernetes.io/hostname

实例2:

创建pod4.yaml,

apiVersion: v1
kind: Pod
metadata:
  name: myapp04
  labels:
    app: myapp04
spec:
  containers:
  - name: myapp04
    image: soscscs/myapp:v1
  affinity:
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: app
            operator: In
            values:
            - myapp01
        topologyKey: kfc

上述问题: 由于指定 Pod 所在的 node01 节点上具有带有键 kfc 和标签值 a 的标签,node02 也有这个kfc=a的标签,所以 node01 和 node02 是在一个拓扑域中,反亲和要求新 Pod 与指定 Pod 不在同一拓扑域,所以新 Pod 没有可用的 node 节点,即为 Pending 状态。

4、Pod污点(Taints)

前面的调度方式都是站在Pod的角度上,通过在Pod上添加属性,来确定Pod是否要调度到指定的Node上,其实我们也可以站在Node的角度上,通过在Node上添加 污点 属性,来决定是否允许Pod调度过来。

Node被设置上污点之后就和Pod之间存在了一种相斥的关系,进而拒绝Pod调度进来,甚至可以将已经存在的Pod驱逐出去。

污点的格式为:key=value:effect ,key和 value是污点的标签,effect描述污点的作用,支持下面三个选项

  • PreferNoSchedule: kubernetes将尽量避免把Pod调度到具有该污点Node上,除非没有其它节点可调度。
  • NoSchedule : kubernetes将不会把Pod调度到具有该污点的Node上,但不会影响当前Node上已经存在的Pod
  • NoExecute: kubernetes将不会把Pod调度到具有该污点的Node上,同时也会将Node上已存在的Pod驱离。
#注意!!!
//在我们kubernetes集群中,目前有三个节点,两个node一个master,那为什么schedule不把pod调度到master节点上呢?

//因为master节点就设置了最高级的污点,也就是NoEcxecute。pod无法运行。

使用kubectl设置和去除污点的命令

kubectl taint nodes node01 key=value:NoExecute
#设置污点

kubectl taint nodes node01 key:NoExecute-
#去除污点

kubectl describe node node-name  
#节点说明中,查找 Taints 字段

kubectl taint node node02 check=mycheck:NoExecute

#查看 Pod 状态,会发现 node02 上的 Pod 已经被全部驱逐(注:如果是 Deployment 或者 StatefulSet 资源类型,为了维持副本数量则会在别的 Node 上再创建新的 Pod)
kubectl get pods -o wide

4.1 NoExecute实例

kubectl taint node node02 check=mycheck:NoExecute

注:如果是 Deployment 或者 StatefulSet 资源类型,为了维持副本数量则会在别的 Node 上再创建新的 Pod

4.2 NoSchedule实例

##清除污点命令
kubectl uncordon <node name>

5、Pod容忍(Toleration)

上面介绍了污点的作用,我们可以在node上添加污点用于拒绝pod调度上来,但是如果就是想将一个pod调度到一个有污点的node上去,这时候应该怎么做呢?这就要使用到容忍

#污点就是拒绝,容忍就是忽略,Node通过污点拒绝Pod调度上去,Pod通过容忍忽略拒绝

#查看容忍的详细配置
kubectl explain pod.spec.tolerations
....
FIELDS:
   key       //对应着要容忍的污点的键,空意味着匹配所有的键
   value     //对应着要容忍的污点的值
   operator  //key-value的运算符,支持Equal和Exists(默认)
   effect    //对应污点的effect,空意味着匹配所有影响
   tolerationSeconds   //容忍时间, 当effect为NoExecute时生效,表示pod在Node上的停留时间

实例1:设置 NoSchedule 污点

在2个node上都设置 NoSchedule 污点

创建Pod使用Pod容忍

apiVersion: v1
kind: Pod
metadata:
  name: myapp01
  labels:
    app: myapp01
spec:
  containers:
  - name: with-node-affinity
    image: soscscs/myapp:v1
  tolerations:
  - key: "ket2"
    operator: "Equal"
    value: "check"
    effect: "NoSchedule"

#其中的 key、vaule、effect 都要与 Node 上设置的 taint 保持一致
#operator 的值为 Exists 将会忽略 value 值,即存在即可
#tolerationSeconds 用于描述当 Pod 需要被驱逐时可以在 Node 上继续保留运行的时间

    tolerationSeconds: 3600

实例2:设置NoExecute污点

 创建Pod使用Pod容忍

apiVersion: v1
kind: Pod
metadata:
  name: myapp02
  labels:
    app: myapp02
spec:
  containers:
  - name: with-node-affinity
    image: soscscs/myapp:v1
  tolerations:
  - key: "key1"
    operator: "Equal"
    value: "check"
    effect: "NoExecute"
    tolerationSeconds: 180

#其中的 key、vaule、effect 都要与 Node 上设置的 taint 保持一致
#operator 的值为 Exists 将会忽略 value 值,即存在即可
#tolerationSeconds 用于描述当 Pod 需要被驱逐时可以在 Node 上继续保留运行的时间

#其它注意事项
(1)当不指定 key 值时,表示容忍所有的污点 key
  tolerations:
  - operator: "Exists"
  
(2)当不指定 effect 值时,表示容忍所有的污点作用
  tolerations:
  - key: "key"
    operator: "Exists"

(3)有多个 Master 存在时,防止资源浪费,可以如下设置
kubectl taint node Master-Name node-role.kubernetes.io/master=:PreferNoSchedule
1、如果某个 Node 更新升级系统组件,为了防止业务长时间中断,可以先在该 Node 设置 NoExecute 污点,把该 Node 上的 Pod 都驱逐出去
kubectl taint node node01 check=mycheck:NoExecute

2、此时如果别的 Node 资源不够用,可临时给 Master 设置 PreferNoSchedule 污点,让 Pod 可在 Master 上临时创建
kubectl taint node master node-role.kubernetes.io/master=:PreferNoSchedule

3、待所有 Node 的更新操作都完成后,再去除污点
kubectl taint node node01 check=mycheck:NoExecute-
维护操作
#cordon 和 drain
##对节点执行维护操作:
kubectl get nodes

#将 Node 标记为不可调度的状态,这样就不会让新创建的 Pod 在此 Node 上运行
kubectl cordon <NODE_NAME> 		 #该node将会变为SchedulingDisabled状态

#kubectl drain 可以让 Node 节点开始释放所有 pod,并且不接收新的 pod 进程。drain 本意排水,意思是将出问题的 Node 下的 Pod 转移到其它 Node 下运行
kubectl drain <NODE_NAME> --ignore-daemonsets --delete-local-data --force

--ignore-daemonsets:无视 DaemonSet 管理下的 Pod。
--delete-local-data:如果有 mount local volume 的 pod,会强制杀掉该 pod。
--force:强制释放不是控制器管理的 Pod,例如 kube-proxy。

注:执行 drain 命令,会自动做了两件事情:
(1)设定此 node 为不可调度状态(cordon)
(2)evict(驱逐)了 Pod

#kubectl uncordon 将 Node 标记为可调度的状态
kubectl uncordon <NODE_NAME>
#Pod启动阶段(相位 phase)
Pod 创建完之后,一直到持久运行起来,中间有很多步骤,也就有很多出错的可能,因此会有很多不同的状态。
一般来说,pod 这个过程包含以下几个步骤:
(1)调度到某台 node 上。kubernetes 根据一定的优先级算法选择一台 node 节点将其作为 Pod 运行的 node
(2)拉取镜像
(3)挂载存储配置等
(4)运行起来。如果有健康检查,会根据检查的结果来设置其状态。

phase 的可能状态有:
●Pending:表示APIServer创建了Pod资源对象并已经存入了etcd中,但是它并未被调度完成(比如还没有调度到某台node上),或者仍然处于从仓库下载镜像的过程中。

●Running:Pod已经被调度到某节点之上,并且Pod中所有容器都已经被kubelet创建。至少有一个容器正在运行,或者正处于启动或者重启状态(也就是说Running状态下的Pod不一定能被正常访问)。

●Succeeded:有些pod不是长久运行的,比如job、cronjob,一段时间后Pod中的所有容器都被成功终止,并且不会再重启。需要反馈任务执行的结果。

●Failed:Pod中的所有容器都已终止了,并且至少有一个容器是因为失败终止。也就是说,容器以非0状态退出或者被系统终止,比如 command 写的有问题。

●Unknown:表示无法读取 Pod 状态,通常是 kube-controller-manager 无法与 Pod 通信。

##故障排除步骤:
//查看Pod事件
kubectl describe TYPE NAME_PREFIX  

//查看Pod日志(Failed状态下)
kubectl logs <POD_NAME> [-c Container_NAME]

//进入Pod(状态为running,但是服务没有提供)
kubectl exec –it <POD_NAME> bash

//查看集群信息
kubectl get nodes

//发现集群状态正常
kubectl cluster-info

//查看kubelet日志发现
journalctl -xefu kubelet
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值