为何引入Pod

为何已经有了Docker技术还需要引入Pod技术呢?

假设容器中同时运行着多个不相关的进程,这些进程的持续运行,管理,以及输入输出日志会是容器的责任,如果这些不相关的进程同时都有标准的输出,那么此时我们很难确定每个进程具体输出了什么内容

另一方面,每个容器是一个逻辑的运行单位,有着自己的命名空间,IP以及端口和其他信息,假如非一个团队开发的不同进程监听了相同的端口号,必将发生资源的争夺冲突

虽然多个进程运行在同一个容器中无论是通过进程间通信还是通过存储文件进行共享文件都很容易,但是Docker和Kubernetes还是期望每个进程都运行在自己的容器中,除非是和自己相关的子进程,如果你的多个进程有着依赖关系(例如:一个进程的启动依赖于另外一个进程),这样的多个进程推荐运行在相同的容器中

由于不推荐将无关的进程运行在同一个容器中,但是特殊情况下还存在要求多个相关进程运行于同一个容器的需求,Kubernetes提供了一种更高级的结构来把容器捆绑在一起,并将这种结构作为调度部署的基本单元,这个结构就是Pod

使用Pod的三个好处有:

  • 借助CRI这个抽象层,使得Kubernetes不依赖于底层某一种具体的容器运行时实现技术,而是直接操作Pod,Pod内部再管理多个业务上紧密相关的用户业务容器,这种架构便于 Kubernetes做扩展
  • 每个Pod里都有一个Kubernetes系统自带的pause容器,通过引入pause这个与业务无关并且作用类似于Linux操作系统守护进程的Kubernetes系统标准容器,以pause容器的状态来代表整个容器组的状态(如果没有Pod,那么一组业务容器中的一个容器死亡了那应该定义为整体死亡还是个体死亡呢?)
  • Pod里所有的业务容器共享pause容器的IP地址,以及pause容器mount的Volume,通过这种设计,业务容器之间可以直接通信,文件也能够直接彼此共享

Pod资源清单

下面是Pod的yaml配置文件完整配置

  1. apiVersion: v1 # 必选,版本号,例如v1
  2. kind: Pod # 必选,Pod
  3. metadata: # 必选,元数据
  4. name: string # 必选,Pod名称
  5. namespace: string # 必选,Pod所属的命名空间
  6. labels: # 自定义标签
  7. - name: string # 自定义标签名字
  8. spec: # 必选,Pod中容器的详细定义
  9. '----------------------------------------------------------'
  10. containers: # 必选,Pod中容器列表
  11. - name: string # 必选,容器名称
  12. image: string # 必选,容器的镜像名称
  13. imagePullPolicy: [Always | Never | IfNotPresent] # 获取镜像的策略
  14. command: [string] # 容器的启动命令列表,如不指定,使用打包时使用的启动命令
  15. args: [string] # 容器的启动命令参数列表
  16. workingDir: string # 容器的工作目录
  17. '------------存储挂载------------'
  18. volumeMounts: # 挂载到容器内部的存储卷配置
  19.    - name: string # 引用pod定义的共享存储卷的名称,需用volumes[]部分定义的的卷名
  20. mountPath: string # 存储卷在容器内mount的绝对路径,应少于512字符
  21. readOnly: boolean # 是否为只读模式
  22. '------------端口暴露------------'
  23. ports: # 需要暴露的端口库号列表
  24.    - name: string # 端口号名称
  25. containerPort: int # 容器需要监听的端口号
  26.    hostPort: int # 容器所在主机需要监听的端口号,默认与container相同
  27.    protocol: string # 端口协议,支持TCP和UDP,默认TCP
  28. '------------环境变量------------'
  29. env: # 容器运行前需设置的环境变量列表
  30.    - name: string # 环境变量名称
  31.    value: string # 环境变量的值
  32. '------------资源设置------------'
  33. resources: # 资源限制和请求的设置
  34.    limits: # 资源限制的设置
  35.    cpu: string # cpu的限制,单位为core数,将用于docker run --cpu-shares参数
  36.    memory: string # 内存限制,单位可以为Mib/Gib,将用于docker run --memory参数
  37.    requests: # 资源请求的设置
  38.    cpu: string # cpu请求,容器启动的初始可用数量
  39.    memory: string # 内存请求,容器启动的初始可用数量
  40. '------------健康检查------------'
  41. livenessProbe: # 对Pod内个容器健康检查的设置,当探测无响应几次后将自动重启该容器,检查方法有exec、httpGet和tcpSocket,对一个容器只需设置其中一种方法即可
  42.    exec: # 对Pod容器内检查方式设置为exec方式
  43. command: string # exec方式需要制定的命令或脚本
  44.    httpGet: # 对Pod内个容器健康检查方法设置为HttpGet,需要制定Path、port
  45.    path: string
  46.    port: number  
  47.    host: string  
  48.    scheme: string  
  49.    HttpHeaders:  
  50.    - name: string  
  51.    value: string  
  52.    tcpSocket: # 对Pod内个容器健康检查方式设置为tcpSocket方式  
  53.    port: number
  54.     initialDelaySeconds: 0 # 容器启动完成后首次探测的时间,单位为秒
  55.     timeoutSeconds: 0 # 对容器健康检查探测等待响应的超时时间,单位秒,默认1秒 
  56.     periodSeconds: 0 # 对容器监控检查的定期探测时间设置,单位秒,默认10秒一次  
  57.     successThreshold: 0  
  58.     failureThreshold: 0  
  59.     securityContext:  
  60.      privileged: false
  61. '----------------------------------------------------------'
  62. restartPolicy: [Always | Never | OnFailure] # Pod的重启策略,Always表示一旦不管以何种方式终止运行,kubelet都将重启,OnFailure表示只有Pod以非0退出码退出才重启,Nerver表示不再重启该Pod  
  63. nodeSelector: obeject # 设置NodeSelector表示将该Pod调度到包含这个label的node上,以key:value的格式指定
  64. imagePullSecrets: # Pull镜像时使用的secret名称,以key:secretkey格式指定 
  65. - name: string  
  66. hostNetwork: false # 是否使用主机网络模式,默认为false,如果设置为true,表示使用宿主机网络
  67. '----------------------------------------------------------'
  68. volumes: # 在该pod上定义共享存储卷列表
  69. - name: string # 共享存储卷名称 (volumes类型有很多种)
  70. emptyDir: {} # 类型为emtyDir的存储卷,与Pod同生命周期的一个临时目录。为空值
  71. hostPath: string # 类型为hostPath的存储卷,表示挂载Pod所在宿主机的目录
  72. path: string # Pod所在宿主机的目录,将被用于同期中mount的目录
  73. secret: # 类型为secret的存储卷,挂载集群与定义的secre对象到容器内部
  74. scretname: string
  75. items:
  76. - key: string
  77. path: string
  78. configMap: # 类型为configMap的存储卷,挂载预定义的configMap对象到容器内部
  79. name: string
  80. items:
  81. - key: string
  82. path: string

可以通过kubectl explain 资源类型来查看某种资源可以配置的一级属性

kubectl explain pod

可以通过kubectl explain 资源类型.属性来查看属性的子属性

kubectl explain pod.metadata

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

  1. apiVersion:版本,由kubernetes内部定义,某个资源的版本可以用kubectl api-resources查询到
  2. kind:资源类型,由kubernetes内部定义
  3. metadata:元数据,主要是资源标识和说明,常用的有name、namespace、labels
  4. spec:描述,这是配置中最重要的一部分,里面是对各种资源配置的详细描述(重点)
    • containers:容器列表,用于定义容器的详细信息
    • nodeName:根据nodeName的值将pod调度到指定的Node节点上
    • nodeSelector:根据NodeSelector中定义的信息选择将该Pod调度到包含这些label的 Node 上
    • hostNetwork:是否使用主机网络模式,默认为false,如果设置为true,表示使用宿主机网络
    • volumes:存储卷,用于定义Pod上面挂在的存储信息
    • restartPolicy:重启策略,表示Pod在遇到故障的时候的处理策略
  5. status:状态信息,里面的内容不需要定义,由kubernetes自动生成

Pod容器管理

镜像及其获取策略

kubernetes支持用户自定义镜像文件的获取策略,例如在网络资源较为紧张时可以禁止从仓库中获取镜像文件等,容器的imagePullPolicy字段用于为其指定镜像获取策略,它的值可以包括以下几种

  • Always:镜像标签为latest或镜像不存在时总是从指定的仓库中获取镜像
  • IfNotPresent:仅当本地镜像缺失时方才从目标仓库下载镜像
  • Never:禁止从仓库下载镜像,即仅使用本地镜像

默认值说明:

如果镜像tag为具体的版本号,那么默认策略为:IfNotPresent

如果镜像tag为:latest(最终版本),那么默认策略为:always

配置文件示例:

apiVersion: v1
kind: Pod
metadata:
  name: pod-example
spec:
  containers:
  - name: nginx
    image: nginx:latest
    imagePullPolicy: IfNotPresent

暴露端口

Docker的网络模型中,使用默认网络的容器化应用需要通过NAT将其暴露到外部网络中才能被其他节点之上的容器客户端所访问

然而kubernetes系统的网络模型中,各Pod的IP地址处于同一网络平面上,无论是否为容器指定了要暴露的端口,都不会影响集群中其他节点之上的Pod客户端对其进行访问,这就意味着,任何监听在非lo接口的端口都可以通过Pod网络直接被请求

  • 指定暴露端口为TCP的80,并将其命名为http
[root@k8s-master ~]# vim pod1.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-example
spec:
  containers:
  - name: nginx
    image: nginx:latest
    imagePullPolicy: IfNotPresent
    ports:
    - name: http
      containerPort: 80
      protocol: TCP
[root@k8s-master ~]# kubectl apply -f pod1.yaml 
[root@k8s-master ~]# kubectl get -f pod1.yaml -o wide
NAME          READY   STATUS    RESTARTS   AGE   IP            NODE        NOMINATED NODE   READINESS GATES
pod-example   1/1     Running   0          77s   10.244.1.12   k8s-node2   <none>           <none>
[root@k8s-master ~]# curl 10.244.1.12 -I

自定义运行的容器化应用

由docker镜像启动容器时运行的应用程序在相应的Dokcerfile中由ENTRYPOINT指令进行定义,传递给程序的参数则通过CMD指令指定,ENTRYPOINT指令不存在时,CMD可用于同时指定程序及其参数

容器的command字段能够指定不同于镜像默认运行的应用程序,并且可以同时使用args字段进行参数传递,他们将覆盖镜像中的默认定义

[root@k8s-master ~]#vim pod1.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-command
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  - name: busybox
    image: busybox:1.30
    command: ["/bin/bash","-c","touch /root/hello.txt;while true;do /bin/echo $(date +%T) >> /root/hello.txt;sleep 3;done;"]
[root@k8s-master ~]# kubectl apply -f pod1.yaml
[root@k8s-master ~]# kubectl get pods pod-command
NAME          READY   STATUS    RESTARTS   AGE
pod-command   2/2     Running   0          17s
# kubectl exec pod名称 -n 命名空间 -it -c 容器名称 /bin/sh 在容器内部执行命令
[root@k8s-master ~]# kubectl exec pod-command -it -c busybox -- /bin/sh
/ # cat /root/hello.txt 
15:07:45
15:07:48
15:07:51
15:07:54

资源限制

在kubernetes上,可由容器或Pod请求或消费的计算资源指的是CPU和内存,CPU属于可压缩型资源,即资源额度可按需收缩,而内存则是不可压缩型资源,对其执行收缩操作可能会导致某种程度的问题

资源隔离属于容器级别,CPU和内存资源的配置需要在Pod中的容器上进行,每种资源均可由requests属性定义其请求的确保可用值(可以理解为最小值),即容器运行可能用不到这些额度的资源,但用到时必须确保由如此多的资源可用,而limits属性则用于限制资源的可用的最大值

Pod和Container的资源请求和限制:

spec.containers[].resources.limits.cpu 
spec.containers[].resources.limits.memory 
spec.containers[].resources.requests.cpu 
spec.containers[].resources.requests.memory
  • 资源请求和限制示例配置文件
[root@k8s-master ~]# vim pod-stress-ng.yaml
apiVersion: v1
kind: Pod
metadata:
  name: frontend
spec:
  containers:
  - name: db
    image: mysql
    env:
    - name: MYSQL_ROOT_PASSWORD
      value: "password"
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"        # 也可以直接设置为数字,1为1核,2为2核
      limits:
        memory: "128Mi"
        cpu: "500m"
  - name: wp
    image: wordpress
    resources:
      requests:
        memory: "64Mi"
         cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"
[root@k8s-master ~]# vim pod-stress-ng.yaml
[root@k8s-master ~]# kubectl apply -f pod-stress-ng.yaml 
[root@k8s-master ~]# kubectl describe -f pod-stress-ng.yaml

Kubernetes之Pod资源 - 图1

环境变量

通过环境变量配置容器化应用时,需要在容器配置段中嵌套使用env字段

  • 配置环境变量示例
[root@k8s-master ~]# vim pod-env.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: pod-env
spec:
  containers:
  - name: busybox
    image: busybox
    env: # 设置环境变量列表
      - name: "username"
        value: "admin"
      - name: "password"
        value: "123456"
[root@k8s-master ~]# kubectl apply -f pod-env.yaml 
[root@k8s-master ~]# kubectl get -f pod-env.yaml 
NAME      READY   STATUS    RESTARTS   AGE
pod-env   1/1     Running   0          29s
[root@k8s-master ~]# kubectl exec pod-env -c busybox -it -- /bin/sh
/ # echo $username
admin
/ # echo $password
123456

共享节点的网络名称空间

同一个pod对象的各容器均运行于一个独立的、隔离的Network名称空间中,共享同一个网络协议栈及相关的网络设备,也有一些特殊的pod对象需要运行于所在节点的名称空间中,执行系统级的管理任务,例如查看和操作节点的网络资源甚至是网络设备等

配置文件示例:

apiVersion: v1
kind: Pod
metadata:
  name: pod-example
spec:
  containers:
  ......
  hostNetwork: True

安全上下文

参考博客:https://blog.csdn.net/qq_34556414/article/details/118529692

标签与标签选择器

管理资源标签

标签是kubernetes极具特色的功能之一,它能够附加于kubernetes的任何资源对象之上

简单来说,标签就是键值类型的数据,他们可用于资源创建时直接指定,也可随时按需添加于活动对象中

  • 创建资源时,可以直接在其metadata中嵌套使用labels字段以定义要附加的标签项
[root@k8s-master ~]# vim pod1.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-example
  labels:
    env: qa
    tier: frontend
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
    ports:
    - name: http
      containerPort: 80
      protocol: TCP
[root@k8s-master ~]# kubectl apply -f pod1.yaml
  • 显示某个资源的所有标签--show-labels
[root@k8s-master ~]# kubectl get pods pod-example --show-labels
NAME          READY   STATUS    RESTARTS      AGE   LABELS
pod-example   1/1     Running   2 (20s ago)   17h   env=qa,tier=frontend
  • 指定显示特定的标签-L
[root@k8s-master ~]# kubectl get pods pod-example -L env,tier
NAME          READY   STATUS    RESTARTS      AGE   ENV   TIER
pod-example   1/1     Running   2 (94s ago)   17h   qa    frontend
  • 管理活动对象的标签kubectl label
[root@k8s-master ~]# kubectl label pods pod-example env2=production
pod/pod-example labeled
[root@k8s-master ~]# kubectl get pods pod-example --show-labels
NAME          READY   STATUS    RESTARTS        AGE   LABELS
pod-example   1/1     Running   2 (2m52s ago)   17h   env2=production,env=qa,tier=frontend
  • 覆盖已经存在的标签 --overwrite
[root@k8s-master ~]# kubectl label pods pod-example env=qq --overwrite
pod/pod-example labeled
[root@k8s-master ~]# kubectl get pods pod-example -L env
NAME          READY   STATUS    RESTARTS        AGE   ENV
pod-example   1/1     Running   2 (4m17s ago)   17h   qq
  • 删除标签key -
# 语法格式:kubectl label pods [pod名称] key- -n [namespace名称]
[root@k8s-master ~]# kubectl label pods pod-example env-

标签选择器

标签选择器用于表达标签的查询条件或选择标准,kubernetes api支持两个选择器,基于等值关系以及基于集合关系的标签选择器

  • 选择出env不等于qq的pod
[root@k8s-master ~]# kubectl get pods -l "env!=qq" --show-labels
  • 多个标签选择器,选择出env=qq并且tier=frontend的pod
[root@k8s-master ~]# kubectl get pods -l "env=qq,tier=frontend" --show-labels
  • 显示标签env等于qq或qa中的一个的所有pod
[root@k8s-master ~]# kubectl get pods -l "env in (qq,qa)" --show-labels
  • 显示所有存在env标签的pod
[root@k8s-master ~]# kubectl get pods -l "env" --show-labels
  • 显示所有不存在env标签,但有app标签的pod
[root@k8s-master ~]# kubectl get pods -l '!env,app' --show-labels

节点选择器

pod节点选择器是标签及标签选择器的一种应用,它能够让pod对象基于集群中工作节点的标签来挑选倾向运行的目标节点

  • 给Node对象添加标签
[root@k8s-master ~]# kubectl label nodes k8s-node1 disktype=ssd
node/k8s-node1 labeled
[root@k8s-master ~]# kubectl label nodes k8s-node2 disktype=hdd
node/k8s-node2 labeled
[root@k8s-master ~]# kubectl get nodes -L disktype
NAME         STATUS   ROLES                  AGE   VERSION   DISKTYPE
k8s-master   Ready    control-plane,master   10d   v1.23.5   
k8s-node1    Ready    <none>                 10d   v1.23.5   ssd
k8s-node2    Ready    <none>                 10d   v1.23.5   hdd
  • 指定pod资源调度至具有ssd的节点上,只需要为其使用spec.nodeSelector标签选择器即可
[root@k8s-master ~]# vim pod1.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: pod-example
  labels:
    env: qa
    tier: frontend
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
    ports:
    - name: http
      containerPort: 80
      protocol: TCP
  nodeSelector:
    disktype: ssd
[root@k8s-master ~]# kubectl apply -f pod1.yaml
  • 查看pod资源在哪个节点上
[root@k8s-master ~]# kubectl get pods pod-example -o wide
NAME          READY   STATUS    RESTARTS   AGE   IP            NODE        NOMINATED NODE   READINESS GATES
pod-example   1/1     Running   0          4s    10.244.2.31   k8s-node1   <none>           <none>

Pod对象生命周期

一般将pod对象从创建至终的这段时间范围称为pod的生命周期,它主要包含下面的过程:

  1. Pod创建
  2. 运行初始化容器(init container)
  3. 运行主容器(main container)
    • 容器启动后钩子(post start)、容器终止前钩子(pre stop)
    • 容器的存活性探测(liveness probe)、就绪性探测(readiness probe)
  4. Pod终止

Kubernetes之Pod资源 - 图2

Pod的相位

在整个生命周期中,Pod会出现5种状态(相位),分别如下:

  • 挂起(Pending):apiserver已经创建了Pod资源对象,但它尚未被调度完成或者仍处于下载镜像的过程中
  • 运行中(Running):Pod已经被调度至某节点,并且所有容器都已经被kubelet创建完成
  • 成功(Succeeded/Completed):Pod中的所有容器都已经成功终止并且不会被重启
  • 失败(Failed):所有容器都已经终止,但至少有一个容器终止失败,即容器返回了非0值的退出状态
  • 未知(Unknown):apiserver无法正常获取到pod对象的状态信息,通常由网络通信失败所导致

Pod的创建过程

  1. kubectl将YAML配置文件发送给API server
  2. kube-apiserver尝试着将Pod对象的相关信息存入etcd中,待写入操作执行完成,API server即会返回确认信息至客户端
  3. API server开始反应etcd中的状态变化
  4. 所有的kubernetes组件均使用watch机制来跟踪检查API server上的相关的变动
  5. kube-scheduler调度程序通过其watch觉察到API server创建了新的pod对象但尚未绑定至任何工作节点(此时Pod配置清单文件存储在etcd中,但是工作节点内并没有该 pod)
  6. kube-scheduler为Pod对象挑选一个工作节点并将其结果信息更新至API server
  7. 调度结果信息由API server更新至etcd存储系统中,API server也开始反应此Pod对象的调度结果
  8. pod被调度到的目标工作节点上的kubelet尝试在当前节点上调用docker启动容器,并将容器的结果状态回送至API server
    1. kubelet 将创建容器的委托委派给 CRI
    2. kubelet 代表将容器连接到 CNI 的网络
    3. CNI 分配一个 IP 地址
    4. 检查探针
    5. kubelet 将 IP 地址报告给控制平面
  9. API server将pod状态信息存入etcd中
  10. 在etcd确认写入操作成功完成之后,API server将确认信息发送至相关的kubelet事件

Kubernetes之Pod资源 - 图3

Pod的终止过程

  1. 用户发送删除Pod对象的命令
  2. API server中的Pod对象会随着事件的推移而更新,在宽限期内(默认为30秒),Pod会被视为”dead”
  3. 将Pod标记为”Terminating”状态
  4. (与第三步同时进行)kubelet在监控到Pod对象转为”Terminating”状态的同时启动Pod关闭进程
  5. (与第三步同时进行)端点控制器监控到Pod对象的关闭行为时将其从所有匹配到此端点的service资源的端点列表中移除
  6. 如果当前Pod对象定义了PreStop钩子处理器,则在其标记为”Terminating”后即会以同步的方式启动执行;如若宽限期结束后,PreStop仍未执行结束,则第2步会被重新执行并额外获取一个时长为2秒的小宽限期(宽限期和PreStop同时进行,宽限期不会在PreStop执行后再开始计算)
  7. Pod对象中的容器进程收到SIGTERM信号,这个信号即通知容器它们很快将被进行关闭
  8. 宽限期结束后,若存在任何一个仍在运行的进程,那么Pod对象即会收到SIGKILL信号,即开始强制删除
  9. Kubelet请求API server将此Pod资源的宽限期设置为0从而完成删除操作,它变得对用户不再可见

Kubernetes之Pod资源 - 图4

删除宽限期默认是30秒。 **kubectl delete**命令支持 **—grace-period=<seconds>** 选项,允许用户设置自己的宽限期,如果设置为0将强制删除pod

在kubectl>=1.5版本的命令中,必须同时使用 --force--grace-period=0 来强制删除pod

Pod的强制删除是通过在集群和etcd中将其定义为删除状态,当执行强制删除命令时,API server不会等待该Pod所运行在节点上的kubelet确认,就会立即将该Pod从API server中移除,这时就可以创建跟原Pod同名的Pod了

钩子函数

钩子函数能够感知自身生命周期中的事件,并在相应的时刻到来时运行用户指定的程序代码

kubernetes在主容器的启动之后和停止之前提供了两个钩子函数:

  • post start容器创建之后执行,如果失败了会重启容器
  • pre stop容器终止之前执行,执行完成后容器将成功终止,在其完成之前会阻塞删除容器的操作

钩子处理器支持使用下面三种方式定义动作:

  • Exec命令:在容器内执行一次命令
……
  lifecycle:
    postStart:
      exec:
        command:
        - cat
        - /tmp/healthy
……
  • TCPSocket:在当前容器尝试访问指定的socket
……
  lifecycle:
    postStart:
      tcpSocket:
        port: 8080
……
  • HTTPGet:在当前容器中向url发起http请求
……
  lifecycle:
    postStart:
        httpGet:
        path: / # URI地址
        port: 80 # 端口号
        host: 192.168.31.100 # 主机地址
        scheme: HTTP # 支持的协议,http或者https
……

Exec动作示例

创建pod-hook-exec.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-hook-exec
spec:
  containers:
  - name: main-container
    image: nginx
    imagePullPolicy: IfNotPresent
    ports:
    - name: nginx-port
      containerPort: 80
    lifecycle:
      postStart:
        exec:
          command: ["/bin/sh","-c","echo postStart...> /usr/share/nginx/html/index.html"]
      preStop:
        exec:
          command: ["/usr/sbin/nginx","-s","quit"]

创建并查看Pod,访问Pod的IP地址

[root@k8s-master ~]# kubectl apply -f pod-hook-exec.yaml
[root@k8s-master ~]# kubectl get pods pod-hook-exec -o wide
NAME            READY   STATUS    RESTARTS   AGE   IP            NODE        NOMINATED NODE   READINESS GATES
pod-hook-exec   1/1     Running   0          27s   10.244.1.16   k8s-node2   <none>           <none>
[root@k8s-master ~]# curl 10.244.1.16
postStart...

存活性探测

有不少应用程序长时间持续运行后会逐渐转为不可用状态,并且能够通过重启操作恢复,kubernetes的容器存活性探测机制可发现诸如此类的问题,并依据探测结果结合重启策略出发后续的行为,存活性探测是隶属于容器级别的配置,kubelet可基于它判定何时需要重启一个容器

容器探测用于检测容器中的应用实例是否正常工作,是保障业务可用性的一种传统机制,如果经过探测,实例的状态不符合预期,那么kubernetes就会把该问题实例”摘除”,不承担业务流量

kubernetes提供了两种探针来实现容器探测,分别是:

  • liveness probes:存活性探针,用于检测应用实例当前是否处于正常运行状态,如果不是,k8s会重启容器
  • readiness probes:就绪性探针,用于检测应用实例当前是否可以接收请求,如果不能,k8s不会转发流量

livenessProbe决定是否重启容器,readinessProbe决定是否将请求转发给容器

上面两种探针目前均支持三种探测方式:

  • Exec:在容器内执行一次命令,如果命令执行的退出码为0,则认为程序正常,否则不正常
……
  livenessProbe:
    exec:
      command:
      - cat
      - /tmp/healthy
……
  • TCPSocket:将会尝试访问一个用户容器的端口,如果能够建立这条连接,则认为程序正常,否则不正常
……
  livenessProbe:
    tcpSocket:
      port: 8080
……
  • HTTPGet:调用容器内Web应用的URL,如果返回的状态码在200和399之间,则认为程序正常,否则不正常
……
  livenessProbe:
    httpGet:
      path: / #URI地址
      port: 80 #端口号
      host: 127.0.0.1 #主机地址
      scheme: HTTP #支持的协议,http或者https
……

Exec

exec类型的探针通过在目标容器中执行用户自定义的命令来判定容器的健康状态,它只有一个可用属性command,用于指定要执行的命令

  • 创建pod-liveness-exec.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-liveness-exec
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
    ports:
    - name: nginx-port
      containerPort: 80
    livenessProbe:
      exec:
        command: ["/bin/cat","/tmp/hello.txt"]    # 执行一个查看文件的命令
  • 创建pod,观察效果
[root@k8s-master ~]# kubectl apply -f pod-liveness-exec.yaml 
pod/pod-liveness-exec created
[root@k8s-master ~]# kubectl describe pods pod-liveness-exec
Events:
  Type     Reason     Age               From               Message
  ----     ------     ----              ----               -------
  Normal   Scheduled  20s               default-scheduler  Successfully assigned default/pod-liveness-exec to k8s-node1
  Normal   Pulled     19s               kubelet            Container image "nginx" already present on machine
  Normal   Created    19s               kubelet            Created container nginx
  Normal   Started    19s               kubelet            Started container nginx
  Warning  Unhealthy  0s (x2 over 10s)  kubelet            Liveness probe failed: /bin/cat: /tmp/hello.txt: No such file or directory
# 检查失败是因为没有/tmp/hello.txt文件,如果有这个文件就会成功

可以看出Nginx容器启动之后就进行了健康检查,检查失败后,容器就会被Kill掉,然后就会开始反复重启,可以查看Pod的状态信息,看到RESTARTS不是0,而是一直在增长

[root@k8s-master ~]# kubectl get pods pod-liveness-exec
NAME                READY   STATUS             RESTARTS        AGE
pod-liveness-exec   0/1     CrashLoopBackOff   6 (2m35s ago)   6m55s

TCPSocket

基于TCP的存活性检测用于向容器的特定端口发起TCP请求并尝试建立连接进行结果判定,连接建立成功即为通过检测,相比较来说,它比基于HTTP的探测要更高效、更节约资源,但精准度略低,毕竟连接建 立成功未必意味着资源可用,它主要包含以下两个可用的属性:

  • host :请求连接的目标ip地址,默认为Pod IP
  • port :请求连接的目标端口,必选字段

创建pod-liveness-tcpsocket.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-liveness-tcpsocket
spec:
  containers:
  - name: nginx
    image: nginx:latest
    ports:
    - name: nginx-port
      containerPort: 80
    livenessProbe:
      tcpSocket:
        port: 8080 # 尝试访问8080端口

创建Pod,观察效果

[root@k8s-master ~]# kubectl apply -f pod-liveness-tcpsocket.yaml
[root@k8s-master ~]# kubectl describe -f pod-liveness-tcpsocket.yaml
.......
Events:
.......
  Normal   Killing    2m2s (x3 over 3m2s)    kubelet            Container nginx failed liveness probe, will be restarted
  Warning  Unhealthy  112s (x10 over 3m22s)  kubelet            Liveness probe failed: dial tcp 10.244.1.18:8080: connect: connection refused
# 可以发现尝试访问8080端口,但是失败了,如果把端口改成80端口,那么就会正常

然后就会开始反复重启,可以查看Pod的状态信息,看到RESTARTS不是0,而是一直在增长

[root@k8s-master ~]# kubectl get -f pod-liveness-tcpsocket.yaml 
NAME                     READY   STATUS             RESTARTS      AGE
pod-liveness-tcpsocket   0/1     CrashLoopBackOff   6 (27s ago)   4m47s

HTTPGet

基于HTTP的探测向目标容器发起一个HTTP请求,根据其响应码进行结果判定,响应码形如为2xx或3xx时表示检测通过,它的可用配置字段如下:

  • host:请求的主机地址,默认为Pod IP;也可以在httpHeaders中使用“HOST:”来定义
  • port:请求的端口,必选字段
  • httpHeaders <[Object]>:自定义的请求报文首部
  • path:请求的HTTP资源路径
  • scheme:连接使用的协议,仅为HTTP或为HTTPS,默认为HTTP

创建pod-liveness-httpget.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-liveness-httpget
spec:
  containers:
  - name: nginx
    image: nginx:latest
    imagePullPolicy: IfNotPresent
    ports:
    - name: nginx-port
      containerPort: 80
    livenessProbe:
      httpGet:            # 其实就是访问http://127.0.0.1:80/hello
        scheme: HTTP    # 支持的协议,http或者https
        port: 80
        path: /hello    # URI地址

创建Pod,观察效果

[root@k8s-master ~]# kubectl apply -f pod-liveness-httpget.yaml
[root@k8s-master ~]# kubectl describe -f pod-liveness-httpget.yaml
Events:
  Type     Reason     Age               From               Message
  ----     ------     ----              ----               -------
  Normal   Scheduled  38s               default-scheduler  Successfully assigned default/pod-liveness-httpget to k8s-node2
  Normal   Pulled     8s (x2 over 37s)  kubelet            Container image "nginx:latest" already present on machine
  Normal   Created    8s (x2 over 37s)  kubelet            Created container nginx
  Normal   Started    8s (x2 over 37s)  kubelet            Started container nginx
  Warning  Unhealthy  8s (x3 over 28s)  kubelet            Liveness probe failed: HTTP probe failed with statuscode: 404
  Normal   Killing    8s                kubelet            Container nginx failed liveness probe, will be restarted
# 显示404错误,因为无法到达这个访问路径

然后就会开始反复重启,可以查看Pod的状态信息,看到RESTARTS不是0,而是一直在增长

[root@k8s-master ~]# kubectl get -f pod-liveness-httpget.yaml
NAME                   READY   STATUS    RESTARTS     AGE
pod-liveness-httpget   1/1     Running   4 (3s ago)   2m3s

查看livenessProbe的子属性,会发现除了上面的三种方式,还有一些其他的配置:

[root@k8s-master ~]# kubectl explain pod.spec.containers.livenessProbe
FIELDS:
    exec <Object>
    tcpSocket <Object>
    httpGet <Object>
    initialDelaySeconds <integer>     # 容器启动后等待多少秒执行第一次探测
    timeoutSeconds <integer>         # 探测超时时间,默认1秒,最小1秒
    periodSeconds <integer>         # 执行探测的频率,默认是10秒,最小1秒
    failureThreshold <integer>         # 连续探测失败多少次才被认定为失败,默认是3,最小值是1
    successThreshold <integer>         # 连续探测成功多少次才被认定为成功,默认是1

配置清单示例:

apiVersion: v1
kind: Pod
metadata:
  name: pod-liveness-httpget
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
    ports:
    - name: nginx-port
      containerPort: 80
    livenessProbe:
      httpGet:
        scheme: HTTP
        port: 80
        path: /
      initialDelaySeconds: 30     # 容器启动后30s开始探测
      timeoutSeconds: 5         # 探测超时时间为5s

容器重启策略

一旦容器探测出现了问题,kubernetes就会对容器所在的Pod进行重启,其实这是由Pod的重启策略决定的,Pod的重启策略有3种,分别如下:

  • Always:容器失效时,自动重启该容器,这也是默认值
  • OnFailure:容器终止运行且退出码不为0时重启
  • Never:不论状态为何,都不重启该容器

重启策略适用于Pod对象中的所有容器,首次需要重启的容器,将在其需要时立即进行重启,随后再次需要重启的操作将由kubelet延迟一段时间后进行,且反复的重启操作的延迟时长以此为10s、20s、 40s、80s、160s和300s,300s是最大延迟时长

示例

创建pod-restartpolicy.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-liveness-httpget
spec:
  containers:
  - name: nginx
    image: nginx:latest
    imagePullPolicy: IfNotPresent
    ports:
    - name: nginx-port
      containerPort: 80
    livenessProbe:
      httpGet:
        scheme: HTTP
        port: 80
        path: /hello
  restartPolicy: Never

创建Pod,查看状态,可以发现重启次数为0,健康检测失败就停止了容器

[root@k8s-master ~]# kubectl apply -f pod-restartpolicy.yaml
[root@k8s-master ~]# kubectl get -f pod-restartpolicy.yaml 
NAME                   READY   STATUS      RESTARTS   AGE
pod-liveness-httpget   0/1     Completed   0          60s

Pod调度

在默认情况下,一个Pod在哪个Node节点上运行,是由kube-scheduler组件采用相应的算法计算出来的,这个过程是不受人工控制的,但是在实际使用中,这并不满足的需求,因为很多情况下,我们想控制某些Pod分配到某些指定的节点上,因此Kubernetes也提供了四类调度方式

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

定向调度

定向调度指的是利用在Pod上声明nodeName或者nodeSelector,将Pod调度到期望的node节点上,如果要调度的目标Node不存在,也会向这个Node进行调度,只不过Pod会运行失败

NodeName

NodeName用于强制约束将Pod调度到指定的Name的Node节点上,这种方式,其实是直接跳过kube-scheduler的调度逻辑,直接将Pod调度到指定名称的节点,也会越过taints污点进行调度

  • 创建一个pod-nodename.yaml文件
[root@k8s-master pod]# vim pod-nodename.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-nodename
spec:  
  containers:
  - name: nginx
    image: nginx
  nodeName: k8s-node1 # 指定调度到node1节点上
  • 创建Pod,查看Pod的状态
[root@k8s-master pod]# kubectl apply -f pod-nodename.yaml 
pod/pod-nodename created
# 发现Pod确实调度到了k8s-node1节点上
[root@k8s-master pod]# kubectl get pods -o wide
NAME           READY   STATUS    RESTARTS   AGE   IP           NODE        NOMINATED NODE   READINESS GATES
pod-nodename   1/1     Running   0          32s   10.244.2.9   k8s-node1   <none>           <none>
  • 删除刚刚创建的Pod,修改nodeName的值为node1(但是并没有node1节点)
[root@k8s-master pod]# kubectl delete -f pod-nodename.yaml 
pod "pod-nodename" deleted
[root@k8s-master pod]# vim pod-nodename.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-nodename
spec:  
  containers:
  - name: nginx
    image: nginx
  nodeName: node1
  • 可以发现已经向node1节点进行了调度,但是由于不存在node1节点,所以pod一直无法正常运行
[root@k8s-master pod]# kubectl apply -f pod-nodename.yaml 
pod/pod-nodename created
[root@k8s-master pod]# kubectl get -f pod-nodename.yaml -o wide
NAME           READY   STATUS    RESTARTS   AGE   IP       NODE    NOMINATED NODE   READINESS GATES
pod-nodename   0/1     Pending   0          10s   <none>   node1   <none>           <none>

Kubernetes之Pod资源 - 图5

NodeSelector

NodeSelector用于将Pod调度到添加了指定标签的Node节点上

它是通过Kubernetes的label-selector机制实现的,也就是说,在Pod创建之前,会由kube-scheduler使用MatchNodeSelector调度策略进行label匹配,找出目标Node,然后将Pod调度到目标节点,该匹配规则是强制约束

  • 首先分别为node节点添加标签
[root@k8s-master pod]# kubectl label nodes k8s-node1 nodeenv=pro
node/k8s-node1 labeled
[root@k8s-master pod]# kubectl label nodes k8s-node2 nodeenv=test
node/k8s-node2 labeled
  • 创建一个pod-nodeselector.yaml文件,并使用它创建Pod
[root@k8s-master pod]# vim pod-nodeselector.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-nodeselector
spec:
  containers:
  - name: nginx
    image: nginx
  nodeSelector:
    nodeenv: pro # 指定调度到具有nodeenv=pro标签的节点上
  • 创建Pod,查看Pod的状态
[root@k8s-master pod]# kubectl apply -f pod-nodeselector.yaml 
pod/pod-nodeselector created

# 发现Pod确实调度到了有nodeenv:pro标签的k8s-node1节点上
[root@k8s-master pod]# kubectl get -f pod-nodeselector.yaml -o wide
NAME               READY   STATUS    RESTARTS   AGE     IP            NODE        NOMINATED NODE   READINESS GATES
pod-nodeselector   1/1     Running   0          2m49s   10.244.2.10   k8s-node1   <none>           <none>
  • 删除刚刚创建的Pod,修改nodeSelector的值为nodeenv:xxx(即不存在此标签的节点)
[root@k8s-master pod]# kubectl delete -f pod-nodeselector.yaml 
pod "pod-nodeselector" deleted
[root@k8s-master pod]# vim pod-nodeselector.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-nodeselector
spec:
  containers:
  - name: nginx
    image: nginx
  nodeSelector:
    nodeenv: xxx
  • 创建Pod,由于不存在xxx节点,所以Pod一直无法正常运行,NODE一栏为none
[root@k8s-master pod]# kubectl apply -f pod-nodeselector.yaml 
pod/pod-nodeselector created
[root@k8s-master pod]# kubectl get -f pod-nodeselector.yaml -o wide
NAME               READY   STATUS    RESTARTS   AGE   IP       NODE     NOMINATED NODE   READINESS GATES
pod-nodeselector   0/1     Pending   0          44s   <none>   <none>   <none>           <none>
  • 查看pod详情,可以看到错误原因,即没有匹配的节点
[root@k8s-master pod]# kubectl describe pods pod-nodeselector
......
Events:
  Type     Reason            Age                 From               Message
  ----     ------            ----                ----               -------
  Warning  FailedScheduling  8s (x3 over 2m15s)  default-scheduler  0/3 nodes are available: 1 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate, 2 node(s) didn't match Pod's node affinity/selector.

亲和性调度

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

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

Affinity主要分为三类:

  • nodeAffinity(node亲和性): 以node为参照目标,解决pod可以调度到哪些node的问题
  • podAffinity(pod亲和性) : 以pod为参照目标,解决pod可以和哪些已存在的pod部署在同一个拓扑域中的问题
  • podAntiAffinity(pod反亲和性) : 以pod为参照目标,解决pod不能和哪些已存在pod部署在同一个拓扑域中的问题

关于亲和性(反亲和性)使用场景的说明:

亲和性:如果两个应用频繁交互,那就有必要利用亲和性让两个应用的尽可能的靠近,这样可以减少因网络通信而带来的性能损耗

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

NodeAffinity

可以通过kubectl explain查看帮助文档,可配置项有:

requiredDuringSchedulingIgnoredDuringExecution    # Node节点必须满足指定的所有规则才可以,相当于硬限制
    nodeSelectorTerms        # 节点选择列表
        matchFields            # 按节点字段列出的节点选择器要求列表
        matchExpressions    # 按节点标签列出的节点选择器要求列表(推荐)
            key                # 键
            values            # 值
            operator        # 关系符
preferredDuringSchedulingIgnoredDuringExecution # 优先调度到满足指定的规则的Node,相当于软限制 (倾向)
    preference                # 一个节点选择器项,与相应的权重相关联
        matchFields            # 按节点字段列出的节点选择器要求列表
        matchExpressions    # 按节点标签列出的节点选择器要求列表(推荐)
            key                # 键
            values            # 值
            operator        # 关系符
    weight                    # 倾向权重
'------------------------------------------------------'
# operator操作符说明
In:label 的值在某个列表中
NotIn:label 的值不在某个列表中
Gt:label 的值大于某个值
Lt:label 的值小于某个值
Exists:某个 label 存在
DoesNotExist:某个 label 不存在

**requiredDuringSchedulingIgnoredDuringExecution**硬限制

  • 创建pod-nodeaffinity-required.yaml
[root@k8s-master pod]# vim pod-nodeaffinity-required.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-nodeaffinity-required
spec:
  containers:
  - name: nginx
    image: nginx
  affinity:         # 亲和性设置
    nodeAffinity:     # 设置node亲和性
      requiredDuringSchedulingIgnoredDuringExecution: # 硬限制
        nodeSelectorTerms:
        - matchExpressions: # 匹配nodeenv的值在values中的标签
          - key: nodeenv
            operator: In
            values: ["test","xxx"]
  • 创建Pod,查看Pod的状态,发现已经成功调度到了k8s-node2上
[root@k8s-master pod]# kubectl apply -f pod-nodeaffinity-required.yaml 
pod/pod-nodeaffinity-required created
[root@k8s-master pod]# kubectl get -f pod-nodeaffinity-required.yaml -o wide
NAME                        READY   STATUS    RESTARTS   AGE   IP           NODE        NOMINATED NODE   READINESS GATES
pod-nodeaffinity-required   1/1     Running   0          29s   10.244.1.6   k8s-node2   <none>           <none>
  • 删除刚刚创建的Pod,修改values的值为[“yyy”,”xxx”](即不存在此标签的节点)
[root@k8s-master pod]# kubectl delete pod/pod-nodeaffinity-required
pod "pod-nodeaffinity-required" deleted
[root@k8s-master pod]# vim pod-nodeaffinity-required.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-nodeaffinity-required
spec:
  containers:
  - name: nginx
    image: nginx
  affinity:          # 亲和性设置
    nodeAffinity:     # 设置node亲和性
      requiredDuringSchedulingIgnoredDuringExecution: # 软限制
        nodeSelectorTerms:
        - matchExpressions: # 匹配nodeenv的值在values中的标签(当前环境没有)
          - key: nodeenv
            operator: In
            values: ["yyy","xxx"]
  • 创建Pod,查看Pod信息,可以发现由于没有与之匹配的标签,因此一直无法正常运行
[root@k8s-master pod]# kubectl apply -f pod-nodeaffinity-required.yaml 
pod/pod-nodeaffinity-required created
[root@k8s-master pod]# kubectl get -f pod-nodeaffinity-required.yaml -o wide
NAME                        READY   STATUS    RESTARTS   AGE   IP       NODE     NOMINATED NODE   READINESS GATES
pod-nodeaffinity-required   0/1     Pending   0          3s    <none>   <none>   <none>           <none>

**preferredDuringSchedulingIgnoredDuringExecution**软限制

  • 创建pod-nodeaffinity-preferred.yaml
[root@k8s-master pod]# vim pod-nodeaffinity-preferred.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-nodeaffinity-preferred
spec:
  containers:
  - name: nginx
    image: nginx
  affinity:              # 亲和性设置
    nodeAffinity:    # 设置node亲和性
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1
        preference:
          matchExpressions:
          - key: nodeenv
            operator: In
            values: ["xxx","yyy"]
  • 创建Pod,查看Pod状态,发现是可以运行成功的,因为这不是硬限制
[root@k8s-master pod]# kubectl apply -f pod-nodeaffinity-preferred.yaml 
pod/pod-nodeaffinity-preferred created
[root@k8s-master pod]# kubectl get -f pod-nodeaffinity-preferred.yaml
NAME                         READY   STATUS    RESTARTS   AGE
pod-nodeaffinity-preferred   1/1     Running   0          2m18s

NodeAffinity规则设置的注意事项:

  1. 如果同时定义了nodeSelector和nodeAffinity,那么必须两个条件都得到满足,Pod才能运 行在指定的Node上
  2. 如果nodeAffinity指定了多个nodeSelectorTerms,那么只需要其中一个能够匹配成功即可
  3. 如果一个nodeSelectorTerms中有多个matchExpressions ,则一个节点必须满足所有的才能匹配成功
  4. 如果一个Pod所在的Node在Pod运行期间其标签发生了改变,不再符合该Pod的节点亲和性需求, 则系统将忽略此变化

PodAffinity

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

可以通过kubectl explain查看帮助文档,可配置项有:

requiredDuringSchedulingIgnoredDuringExecution         # 硬限制
    namespaces         # 指定参照pod的namespace
    topologyKey     # 指定调度作用域
    labelSelector     # 标签选择器
        matchExpressions     # 按节点标签列出的节点选择器要求列表(推荐)
            key             # 键
            values            # 值
            operator        # 关系符
        matchLabels         # 指多个matchExpressions映射的内容
preferredDuringSchedulingIgnoredDuringExecution     # 软限制
    podAffinityTerm
    namespaces
    topologyKey        # 指定调度时作用域
    labelSelector
        matchExpressions
            key 键
            values 值
            operator
        matchLabels
    weight     # 倾向权重
'------------------------------------------------------'
topologyKey用于指定调度时作用域,例如:
如果指定为kubernetes.io/hostname,那就是以Node节点为区分范围
如果指定为beta.kubernetes.io/os,则以Node节点的操作系统类型来区分

**requiredDuringSchedulingIgnoredDuringExecution**硬限制

  • 创建pod-podaffinity-target.yaml
[root@k8s-master pod]# vim pod-podaffinity-target.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-podaffinity-target
  labels:
    podenv: pro        # 设置标签
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  nodeName: k8s-node1   # 将目标pod明确指定到k8s-node1上
  • 创建参照Pod,查看Pod的状态
[root@k8s-master pod]# kubectl apply -f pod-podaffinity-target.yaml 
pod/pod-podaffinity-target created
[root@k8s-master pod]# kubectl get pods -o wide
NAME                     READY   STATUS    RESTARTS   AGE   IP            NODE        NOMINATED NODE   READINESS GATES
pod-podaffinity-target   1/1     Running   0          12s   10.244.2.16   k8s-node1   <none>           <none>
  • 创建pod-podaffinity-required.yaml
[root@k8s-master pod]# vim pod-podaffinity-required.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-podaffinity-required
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  affinity:          # 亲和性设置
    podAffinity:     # 设置pod亲和性
      requiredDuringSchedulingIgnoredDuringExecution: # 硬限制
      - labelSelector:
         matchExpressions: # 匹配podenv的值在values中的标签
         - key: podenv
           operator: In
           values: ["pro","xxx"]
        topologyKey: kubernetes.io/hostname
  • 创建新的Pod,查看Pod的状态,可以看到目标Pod和新的Pod都在一个节点上
[root@k8s-master pod]# kubectl apply -f pod-podaffinity-required.yaml
[root@k8s-master pod]# kubectl get pods -o wide
NAME                       READY   STATUS    RESTARTS   AGE   IP            NODE        NOMINATED NODE   READINESS GATES
pod-podaffinity-required   1/1     Running   0          2s    10.244.2.17   k8s-node1   <none>           <none>
pod-podaffinity-target     1/1     Running   0          46s   10.244.2.16   k8s-node1   <none>           <none>

PodAffinity的**preferredDuringSchedulingIgnoredDuringExecution**软限制和NodeAffinity的意思一样

PodAntiAffinity

PodAntiAffinity主要实现以运行的Pod为参照,让新创建的Pod跟参照Pod不在一个区域中的功能,配置方式和选项跟PodAffinty一样

  • 创建pod-podantiaffinity-target.yaml
[root@k8s-master pod]# vim pod-podantiaffinity-target.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-podantiaffinity-target
  labels:
    podenv: pro
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  nodeName: k8s-node1
  • 创建参照Pod,查看Pod的状态
[root@k8s-master pod]# kubectl apply -f pod-podantiaffinity-target.yaml
  • 创建pod-podantiaffinity-required.yaml
[root@k8s-master pod]# vim pod-podantiaffinity-require.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-podantiaffinity-required
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  affinity:             # 亲和性设置
    podAntiAffinity:      # 设置pod反亲和性
      requiredDuringSchedulingIgnoredDuringExecution: # 硬限制
      - labelSelector:
         matchExpressions:  # 匹配podenv的值在values中的标签
         - key: podenv
           operator: In
           values: ["pro"]
        topologyKey: kubernetes.io/hostname
  • 创建新的Pod,查看两个Pod的状态,可以看到两个Pod在不同的Node上,因为这是Pod反亲和性配置
[root@k8s-master pod]# kubectl apply -f pod-podantiaffinity-require.yaml
pod/pod-podantiaffinity-required created
[root@k8s-master pod]# kubectl get pods -o wide

Kubernetes之Pod资源 - 图6

污点和容忍

污点(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之Pod资源 - 图7

使用kubectl设置和去除污点的命令示例如下:

# 设置污点
kubectl taint nodes k8s-node1 key=value:effect
# 去除污点
kubectl taint nodes k8s-node1 key:effect-
# 去除所有污点
kubectl taint nodes k8s-node1 key-

示例

  1. 准备节点k8s-node1(为了演示效果更加明显,暂时停止k8s-node2节点),为k8s-node1设置污点(PreferNoSchedule),创建Pod:taint1
[root@k8s-master ~]# kubectl taint nodes k8s-node1 tag=moodye:PreferNoSchedule
[root@k8s-master ~]# kubectl create ns dev
[root@k8s-master ~]# vim taint1.yaml
apiVersion: v1
kind: Pod
metadata:
  name: taint1
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
[root@k8s-master ~]# kubectl apply -f taint1.yaml 
[root@k8s-master ~]# kubectl get pods -n dev -o wide
NAME     READY   STATUS    RESTARTS   AGE   IP            NODE        NOMINATED NODE   READINESS GATES
taint1   1/1     Running   0          11s   10.244.2.36   k8s-node1   <none>           <none>
# taint1正常创建在了k8s-node1上
  1. 修改k8s-node1节点的污点设置(NoSchedule),创建Pod:taint2
[root@k8s-master ~]# kubectl taint nodes k8s-node1 tag:PreferNoSchedule-
node/k8s-node1 untainted
[root@k8s-master ~]# kubectl taint nodes k8s-node1 tag=moodye:NoSchedule
node/k8s-node1 tainted
[root@k8s-master ~]# vim taint2.yaml
apiVersion: v1
kind: Pod
metadata:
  name: taint2
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
[root@k8s-master ~]# kubectl apply -f taint2.yaml 
[root@k8s-master ~]# kubectl get pods -n dev -o wide
NAME     READY   STATUS    RESTARTS   AGE     IP            NODE        NOMINATED NODE   READINESS GATES
taint1   1/1     Running   0          4m19s   10.244.2.36   k8s-node1   <none>           <none>
taint2   0/1     Pending   0          11s     <none>        <none>      <none>           <none>
# taint2创建失败,处于挂起状态,因为污点的设置为NoSchedule
  1. 修改k8s-node1节点的污点设置(NoExecute),创建Pod:taint3
[root@k8s-master ~]# kubectl taint nodes k8s-node1 tag:NoSchedule-
node/k8s-node1 untainted
[root@k8s-master ~]# kubectl taint nodes k8s-node1 tag=moodye:NoExecute
node/k8s-node1 tainted
[root@k8s-master ~]# vim taint3.yaml
apiVersion: v1
kind: Pod
metadata:
  name: taint3
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
[root@k8s-master ~]# kubectl apply -f taint3.yaml 
[root@k8s-master ~]# kubectl get pods -n dev -o wide
NAME     READY   STATUS    RESTARTS   AGE   IP       NODE     NOMINATED NODE   READINESS GATES
taint3   0/1     Pending   0          14s   <none>   <none>   <none>           <none>
# 发现只剩下了一个taint3,因为污点的设置为NoExecute

PS:使用kubeadm搭建的集群,默认就会给master节点添加一个污点标记,所以pod就不会调度到master节点上

容忍(Toleration)

可以在node上添加污点用于拒绝Pod调度上来,但是如果就是想将一个Pod调度到一个有污点的node上去,这时候就需要用到容忍

Kubernetes之Pod资源 - 图8

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

在上面的污点案例中,已经给k8s-node1打上了NoExecute的污点,此时pod是调度不上去的,因此在这里可以通过给pod添加容忍的配置,然后就可以将其强行调度上去

容忍配置文件格式

......
  tolerations:            # 添加容忍
  - key:                # 对应着要容忍的污点的键,空意味着匹配所有的键
    value:                 # 对应着要容忍的污点的值
    operator:             # key-value的运算符,支持Equal和Exists(默认)
    effect:                # 对应污点的effect,空则意味着匹配所有影响
    tolerationSeconds:     # 容忍时间, 当effect为NoExecute时生效,表示pod在Node上的停留时间
......
  • 创建pod-toleration.yaml
apiVersion: v1
kind: Pod
metadata:
  name: taint4
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  tolerations:            # 添加容忍
  - key: "tag"            # 要容忍的污点的key值
      operator: "Equal"     # 操作符
      value: "moodye"    # 要容忍的污点的value值
      effect: "NoExecute"    # 容忍规则,必须和节点上的污点的规则一致
  • 创建这个Pod,并查看新创建的Pod状态信息
[root@k8s-master ~]# kubectl apply -f pod-toleration.yaml 
pod/taint4 created
[root@k8s-master ~]# kubectl get pods -o wide -n dev
NAME                    READY   STATUS    RESTARTS   AGE    IP            NODE        NOMINATED NODE   READINESS GATES
taint4                  1/1     Running   0          8s     10.244.2.38   k8s-node1   <none>           <none>
taint3                  0/1     Pending   0          9m5s   <none>        <none>      <none>           <none>
  • 可以发现taint3依旧是无法创建在k8s-node1上的,而taint4可以创建在k8s-node1上,因为在配置文件中添加了容忍规则