第三章 Pod

  • Pod 是什么
  • Pod 基本操作
  • Pod 生命周期
  • 容器特性
  • Pod 调度

1 Pod 是什么

1.1 简介

Pod 是可以在 Kubernetes 中创建和管理的、最小的可部署的计算单元

Pod(就像在鲸鱼荚或者豌豆荚中)是一组(一个或多个) 容器; 这些容器共享存储、网络、以及怎样运行这些容器的规约。 Pod 中的内容总是并置的并且一同调度,在共享的上下文中运行。

简单来说就是 Pod 是用来管理一组(一个或多个)容器的集合。其特点是Pod之间的容器共享网络,共享存储,共享上下文环境的

1.2 如何使用 Pod

通常你不需要直接创建 Pod,甚至单实例 Pod。相反,你会使用诸如 Deployment 或 Job 这类工作负载资源来创建 Pod。 如果 Pod 需要跟踪状态,可以考虑 StatefulSet 资源。这些将在下面章节展开赘述,本章节仍然采用直接创建进行学习演示。

Kubernetes 集群中的 Pod 主要有两种用法:

  • 运行单个容器的 Pod。“每个 Pod 一个容器” 模型是最常见的 Kubernetes 用例; 在这种情况下,可以将 Pod 看作单个容器的包装器。Kubernetes 直接管理 Pod,而不是容器。
  • 运行多个需要协同工作的容器的 Pod。 Pod 可以封装由多个紧密耦合且需要共享资源的并置容器组成的应用。 这些位于同一位置的容器可能形成单个内聚的服务单元 —— 一个容器将文件从共享卷提供给公众, 而另一个单独的边车容器则刷新或更新这些文件。 Pod 将这些容器和存储资源打包为一个可管理的实体。

先简单看一个 Pod 案例,具体配置后面会详细介绍

apiVersion: v1 # 版本号
kind: Pod  # 资源类型
metadata:  # 元数据
  name: nginx
spec:
  containers: # pod 容器数组列表
  - name: nginx  # 容器名称
    image: nginx:1.14.2 # 容器镜像以及对应的版本
    ports:
    - containerPort: 80 # 容器监听的端口号

2 Pod 基本操作

2.1 查看 Pod
# 查看命名空间的 Pod 不指定为默认命名空间default
$ kubectl get pods -n 命名空间名称

# 查看所有命名空间的Pod
$ kubectl get pods -A

# 查看命名空间的 Pod 的详细信息 不指定为默认命名空间default
$ kubectl get pods -o wide -n 命名空间名称 

2.2 创建 Pod

在讲创建 pod 之前,如果大家对 yaml 语法还不熟悉的话可以自行简单学习下语法。

如果有使用 idea 工具的话推荐安装下kubernetes 插件 ,可以帮助我们自动生成一些基本配置。我这边使用的idea工具的 vscode。
在这里插入图片描述
例如 我输入 pod 回车 就能自动填充一些基本配置,我们按需修改就行。
在这里插入图片描述

2.2.1 基本配置
# 定义一个最简单的 Pod 配置
# nginx 是一个轻量级的 web 容器
apiVersion: v1 # 版本号
kind: Pod # 资源类型
metadata: # 元数据
  name: base-pod # pod 名称
spec:
  containers: # 容器列表
  - name: nginx
    image: nginx:1.17.1
# 新建 base-pod.yml 文件 复制进去
$ vim base-pod.yml
# 创建 pod 
$ kubectl apply -f base-pod.yml
# 查看 pod
$ kubectl get pods -o  wide

在这里插入图片描述

2.2.2 标签

在 Kubernetes 中,Label 是用于标识和组织资源(如 Pods、Services 等)的键值对。Label 可以用于选择、分组和管理资源,使得管理复杂的集群变得更加容易。

标签可以在对象创建时附加,并且可以随时添加和修改。每个对象可以定义一组键(key)/值(value)标签,但对于每个对象,每个键必须是唯一的。

apiVersion: v1 
kind: Pod 
metadata: 
  name: label-pod
  labels:
    name: nginx # 创建时添加
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
# 创建pod
$ kubectl apply -f label-pod.yml
# 查看标签
$ kubectl get pods --show-labels

在这里插入图片描述

除了查看还有一些标签的基本操作

# 查看标签
$ kubectl get pods --show-labels
# 添加标签
$ kubectl label pod label-pod version="1.0"
# 覆盖标签 --overwrite
$ kubectl label --overwrite pod label-pod version="2.0"
# 删除标签 -号代表删除标签
$ kubectl label pod label-pod version-
# 根据标签筛选 env=test/env  > = < 
$ kubectl get pod -l name=nginx
$ kubectl get pod -l name # 包含这个key的pod
$ kubectl get pod -l '!name' # 不包含的 pod
2.3 删除 Pod
$ kubectl delete pod  pod名称
$ kubectl delete -f pod.yml # 推荐使用
2.4 查看 Pod 描述
$ kubectl describe pod pod名称 -n 命名空间名称(不指定-n 默认为default)
2.5 查看 Pod 日志
$ kubect logs -f pod名称 -c 容器名称(不指定-c 默认查看第一个容器日志)
2.6 进入 Pod 容器
$ kubectl exec -it pod名称 -c 容器名称 --(固定写死) bash(执行命令)

3 Pod 生命周期

Pod 遵循预定义的生命周期,起始于 Pending 阶段, 如果至少其中有一个主要容器正常启动,则进入 Running,之后取决于 Pod 中是否有容器以失败状态结束而进入 Succeeded 或者 Failed 阶段。与此同时Pod 在其生命周期中只会被调度一次,将 Pod 分配到特定节点的过程称为绑定,而选择使用哪个节点的过程称为调度。一旦 Pod 被调度并绑定到某个节点,Kubernetes 会尝试在该节点上运行 Pod。Pod 会在该节点运行,直到 Pod 停止或者被终止。

Pod 阶段
取值 描述
Pending(等待) 当 Pod 被创建时,它首先进入 Pending 状态。在这个阶段,Pod 的定义已经被接受,但其中的一个或多个容器尚未创建。这个状态包含以下两个子阶段:1.PodScheduled(表示 Pod 已被调度到某个节点)2.Initialized(表示所有 Init 容器已经成功完成)
Running(运行中) 在此阶段,Pod 已经被调度到节点上,并且所有指定的容器都已经被创建。至少有一个容器正在运行,或者正在启动或重启。依然存在两个子状态:1.PodReady(表示 Pod 已经准备好处理请求并且已经完成了启动流程)2.ContainersReady(表示 Pod 中的所有容器都已经准备就绪)
Succeeded(成功) 当 Pod 中的所有容器都成功终止,并且不会再重启时,Pod 进入 Succeeded 状态。通常用于一次性任务(例如批处理作业)完成后的状态。
Failed(失败) 如果 Pod 中的某个容器以非零状态终止,并且不会再重启,则 Pod 进入 Failed 状态。这个状态表示 Pod 中的一个或多个容器没有成功完成其任务
Unknown(未知) 在某些情况下,Pod 的状态可能无法确定(例如与节点失去联系时),此时 Pod 的状态为 Unknown

4 容器特性

3.1 容器状态

Kubernetes 会跟踪 Pod 中每个容器的状态,就像它跟踪 Pod 总体上的阶段一样。 你可以使用容器生命周期回调来在容器生命周期中的特定时间点触发事件。

一旦调度器将 Pod 分派给某个节点,kubelet 就通过容器运行时开始为 Pod 创建容器。容器的状态有三种:Waiting(等待)、Running(运行中)和 Terminated(已终止)。

每种状态都有特定的含义:

  • Waiting (等待)

如果容器并不处在 RunningTerminated 状态之一,它就处在 Waiting 状态。 处于 Waiting 状态的容器仍在运行它完成启动所需要的操作:例如, 从某个容器镜像仓库拉取容器镜像,或者向容器应用 Secret 数据等等。 当你使用 kubectl 来查询包含 Waiting 状态的容器的 Pod 时,你也会看到一个 Reason 字段,其中给出了容器处于等待状态的原因。

  • Running(运行中)

Running 状态表明容器正在执行状态并且没有问题发生。 如果配置了 postStart 回调,那么该回调已经执行且已完成。 如果你使用 kubectl 来查询包含 Running 状态的容器的 Pod 时, 你也会看到关于容器进入 Running 状态的信息。

  • Terminated(已终止)

处于 Terminated 状态的容器已经开始执行并且或者正常结束或者因为某些原因失败。 如果你使用 kubectl 来查询包含 Terminated 状态的容器的 Pod 时, 你会看到容器进入此状态的原因、退出代码以及容器执行期间的起止时间。

如果容器配置了 preStop 回调,则该回调会在容器进入 Terminated 状态之前执行。

3.2 容器生命周期回调钩子

Pod 生命周期管理还可以通过配置生命周期钩子(Lifecycle Hooks)来控制容器的启动和终止行为。常用的钩子有:

  • PostStart:在容器启动后立即执行。
  • PreStop:在容器终止前执行。
apiVersion: v1
kind: Pod
metadata:
  name: lifecycle-pod
spec:
  containers:
    - name: nginx
      image: nginx:1.17.1
      lifecycle:
        postStart: #容器创建过程中执行
          exec:
            command: ["/bin/sh","-c","echo postStart >> /start.txt"]
        preStop:  #容器终止之前执行
          exec:
            command: ["/bin/sh","-c","echo postStop >> /stop.txt && sleep 30"]

# 创建pod
$ kubectl apply -f lifecycle-pod.yml
# 进入容器查看 目录可以看到start.txt
$ kubectl exec -it lifecycle-pod -- sh
# 另一个窗口删除容器 可以看到stop.txt
3.3 容器镜像拉取策略

imagePullPolicy 用于设置镜像拉取策略

  • Always 总是从远程仓库拉取镜像(一直远程下载)
  • Never 只使用本地镜像,从不去远程仓库拉取,本地没有就报错
  • IfNotPresent 本地有则使用本地镜像,本地没有则从远程仓库拉取镜像

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

如果镜像tag为:latest(最终版本) ,默认策略是Always

apiVersion: v1 
kind: Pod 
metadata: 
  name: image-pod 
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
    imagePullPolicy: IfNotPresent # 设置镜像拉取策略
3.4 容器启动命令
  • command: 是容器启动时运行的命令。如果不指定 command,将会使用容器镜像中的默认命令。

  • args: 可以用来给 command 传递参数。与 command 一起使用时,args 列表中的每个元素都会作为单独的参数传递给命令。

# 就以 busybox 为例 linux命令集合
apiVersion: v1 
kind: Pod 
metadata: 
  name: command-pod
spec:
  containers: 
  - name: busybox
    image: busybox:1.30
    imagePullPolicy: IfNotPresent
# 创建pod
$ kubectl apply -f command-pod.yml
# 创建报错 报错原因大概是busybox并不是一个程序,而是类似于一个工具类的集合,kubernetes集群启动管理后,它会自动关闭。解决方法就是让其一直在运行,这就用到了command配置。
command-pod   0/1     CrashLoopBackOff   4 (30s ago)   2m
apiVersion: v1 
kind: Pod 
metadata: 
  name: command-pod
spec:
  containers: 
  - name: busybox
    image: busybox:1.30
    command: ["sh", "-c"] # 容器启动命令
    args: ["echo Hello, Kubernetes! && sleep 3600"] # 容器启动参数
    imagePullPolicy: IfNotPresent
# 先把之前的删除
$ kubectl delete -f command-pod.yml
# 创建pod
$ kubectl apply -f command-pod.yml
# 查看日志 成功打印 Hello, Kubernetes!
$ kubectl logs -f command-pod 

在这里插入图片描述

3.5 容器重启策略

Pod 的 spec 中包含一个 restartPolicy 字段,其可能取值包括 Always(只要容器终止就自动重启容器)、OnFailure(只有在容器错误退出(退出状态非零)时才重新启动容器) 和 Never(不会自动重启已终止的容器)。默认值是 Always

kubelet 根据配置的重启策略处理容器重启时,仅适用于同一 Pod 内替换容器并在同一节点上运行的重启。当 Pod 中的容器退出时,kubelet 会以指数级回退延迟机制(10 秒、20 秒、40 秒…)重启容器, 上限为 300 秒(5 分钟)。一旦容器顺利执行了 10 分钟, kubelet 就会重置该容器的重启延迟计时器。

apiVersion: v1
kind: Pod
metadata:
  name: restart-pod
spec:
  containers:
    - name: nginx
      image: nginx:1.17.1
      imagePullPolicy: IfNotPresent
  restartPolicy: Always # 设置容器重启策略 Always OnFailure Never
3.6 容器环境变量

这种方式其实不是很推荐,配置的话后面会有专门章节讲configMap和Secret。

这里就简单了解下有这么一种方式

apiVersion: v1 
kind: Pod 
metadata: 
  name: env-pod
spec:
  containers: 
  - name: busybox
    image: busybox:1.30
    command: ["sh", "-c"] 
    args: ["echo Hello, Kubernetes! && sleep 3600"]
    imagePullPolicy: IfNotPresent
    env: # 设置环境变量列表
    - name: "username"
      value: "admin"
    - name: "password"
      value: "123456"
# 创建 env-pod
$ kubeclt apply -f env-pod.yml
# 查看环境变量 先进入容器 echo
$ kubectl exec -it env-pod -- sh

在这里插入图片描述

3.7 资源配额

在k8s中对于容器资源限制主要分为以下两类:

  • 内存资源限制: 内存请求(request)和内存限制(limit)分配给一个容器。 我们保障容器拥有它请求数量的内存,但不允许使用超过限制数量的内存。
  • CPU 资源限制: 为容器设置 CPU request(请求) 和 CPU limit(限制)。 容器使用的 CPU 不能超过所配置的限制。 如果系统有空闲的 CPU 时间,则可以保证给容器分配其所请求数量的 CPU 资源。
apiVersion: v1 
kind: Pod 
metadata: 
  name: resource-pod
spec:
  containers: 
  - name: busybox
    image: busybox:1.30
    command: ["sh", "-c"]
    args: ["echo Hello, Kubernetes! && sleep 3600"]
    imagePullPolicy: IfNotPresent
    resources: # 资源配额
      limits:  # 限制资源(上限)
        cpu: "2" # CPU限制,单位是core数
        memory: "10Gi" # 内存限制
      requests: # 请求资源(下限)
        cpu: "1"  # CPU限制,单位是core数
        memory: "10Mi"  # 内存限制
# 查看容器内存使用情况
$ kubectl get pod resource-pod --output=yaml
# 查看容器正在使用内存情况
$ kubectl top pod resource-pod
3.8 容器探针

probe 是由 kubelet对容器执行的定期诊断。 要执行诊断,kubelet 既可以在容器内执行代码,也可以发出一个网络请求。探针是用来定期检查容器的健康状态。

3.8.1 探测类型

针对运行中的容器,kubelet 可以选择是否执行以下三种探针,以及如何针对探测结果作出反应:

  • livenessProbe 指示容器是否正在运行。如果存活态探测失败,则 kubelet 会杀死容器, 并且容器将根据其重启策略决定未来。如果容器不提供存活探针, 则默认状态为 Success
  • readinessProbe指示容器是否准备好为请求提供服务。如果就绪态探测失败, 端点控制器将从与 Pod 匹配的所有服务的端点列表中删除该 Pod 的 IP 地址。 初始延迟之前的就绪态的状态值默认为 Failure。 如果容器不提供就绪态探针,则默认状态为 Success
  • startupProbe 1.7+指示容器中的应用是否已经启动。如果提供了启动探针,则所有其他探针都会被 禁用,直到此探针成功为止。如果启动探测失败,kubelet 将杀死容器, 而容器依其重启策略进行重启。 如果容器没有提供启动探测,则默认状态为 Success
3.8.2 探针机制

使用探针来检查容器有四种不同的方法。 每个探针都必须准确定义为这四种机制中的一种:

  • exec

    在容器内执行指定命令。如果命令退出时返回码为 0 则认为诊断成功。

  • grpc

    使用 gRPC 执行一个远程过程调用。 目标应该实现 gRPC健康检查。 如果响应的状态是 “SERVING”,则认为诊断成功。

  • httpGet

    对容器的 IP 地址上指定端口和路径执行 HTTP GET 请求。如果响应的状态码大于等于 200 且小于 400,则诊断被认为是成功的。

  • tcpSocket

    对容器的 IP 地址上的指定端口执行 TCP 检查。如果端口打开,则诊断被认为是成功的。 如果远程系统(容器)在打开连接后立即将其关闭,这算作是健康的。

3.8.3 探测结果

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

  • Success(成功)容器通过了诊断。
  • Failure(失败)容器未通过诊断。
  • Unknown(未知)诊断失败,因此不会采取任何行动。
3.8.4 探针参数
  • initialDelaySeconds 初始化时间
  • periodSeconds 检测间隔时间
  • timeoutSeconds 检测超时时间
  • failureThreshold 失败次数,达到失败次数重启
  • successThreshold 成功次数,达到次数则成功
3.8 容器探针

probe 是由 kubelet对容器执行的定期诊断。 要执行诊断,kubelet 既可以在容器内执行代码,也可以发出一个网络请求。探针是用来定期检查容器的健康状态。

3.8.1 探测类型

针对运行中的容器,kubelet 可以选择是否执行以下三种探针,以及如何针对探测结果作出反应:

  • livenessProbe 指示容器是否正在运行。如果存活态探测失败,则 kubelet 会杀死容器, 并且容器将根据其重启策略决定未来。如果容器不提供存活探针, 则默认状态为 Success
  • readinessProbe指示容器是否准备好为请求提供服务。如果就绪态探测失败, 端点控制器将从与 Pod 匹配的所有服务的端点列表中删除该 Pod 的 IP 地址。 初始延迟之前的就绪态的状态值默认为 Failure。 如果容器不提供就绪态探针,则默认状态为 Success
  • startupProbe 1.7+指示容器中的应用是否已经启动。如果提供了启动探针,则所有其他探针都会被 禁用,直到此探针成功为止。如果启动探测失败,kubelet 将杀死容器, 而容器依其重启策略进行重启。 如果容器没有提供启动探测,则默认状态为 Success
3.8.2 探针机制

使用探针来检查容器有四种不同的方法。 每个探针都必须准确定义为这四种机制中的一种:

  • exec

    在容器内执行指定命令。如果命令退出时返回码为 0 则认为诊断成功。

  • grpc

    使用 gRPC 执行一个远程过程调用。 目标应该实现 gRPC健康检查。 如果响应的状态是 “SERVING”,则认为诊断成功。

  • httpGet

    对容器的 IP 地址上指定端口和路径执行 HTTP GET 请求。如果响应的状态码大于等于 200 且小于 400,则诊断被认为是成功的。

  • tcpSocket

    对容器的 IP 地址上的指定端口执行 TCP 检查。如果端口打开,则诊断被认为是成功的。 如果远程系统(容器)在打开连接后立即将其关闭,这算作是健康的。

3.8.3 探测结果

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

  • Success(成功)容器通过了诊断。
  • Failure(失败)容器未通过诊断。
  • Unknown(未知)诊断失败,因此不会采取任何行动。
3.8.4 探针参数
  • initialDelaySeconds 初始化时间
  • periodSeconds 检测间隔时间
  • timeoutSeconds 检测超时时间
  • failureThreshold 失败次数,达到失败次数重启
  • successThreshold 成功次数,达到次数则成功
3.8.5 使用探针

exec

apiVersion: v1
kind: Pod
metadata:
  labels:
    test: liveness
  name: liveness-exec
spec:
  containers:
  - name: liveness
    image: registry.k8s.io/busybox
    args: # 这个容器生命的前 30 秒,/tmp/healthy 文件是存在的。 所以在这最开始的 30 秒内,执行命令 cat /tmp/healthy 会返回成功代码。 30 秒之后,执行命令 cat /tmp/healthy 就会返回失败代码。
    - /bin/sh
    - -c
    - touch /tmp/healthy; sleep 30; rm -f /tmp/healthy; sleep 600
    livenessProbe:
      exec:  # 容器内执行命令 cat /tmp/healthy 来进行探测。 如果命令执行成功并且返回值为 0,kubelet 就会认为这个容器是健康存活的。 如果这个命令返回非 0 值,kubelet 会杀死这个容器并重新启动它。
        command:
        - cat
        - /tmp/healthy
      initialDelaySeconds: 5 # 执行第一次探测等待5秒
      periodSeconds: 5 # 每5秒执行一次探测

在这里插入图片描述
在输出结果的最下面,有信息显示存活探针失败了,这个失败的容器被杀死并且被重建了。

httpGet

apiVersion: v1
kind: Pod
metadata:
  labels:
    test: liveness
  name: liveness-http
spec:
  containers:
  - name: liveness
    image: registry.k8s.io/e2e-test-images/agnhost:2.40
    args:
    - liveness
    # kubelet 会向容器内运行的服务(服务在监听 8080 端口)发送一个 HTTP GET 请求来执行探测。 如果服务器上 /healthz 路径下的处理程序返回成功代码,则 kubelet 认为容器是健康存活的。 如果处理程序返回失败代码,则 kubelet 会杀死这个容器并将其重启。
    livenessProbe:
      httpGet:
        path: /healthz
        port: 8080
        httpHeaders:
        - name: Custom-Header
          value: Awesome
      initialDelaySeconds: 3
      periodSeconds: 3

你可以访问 server.go 阅读服务的源码。 容器存活期间的最开始 10 秒中,/healthz 处理程序返回 200 的状态码。 之后处理程序返回 500 的状态码。

http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
    duration := time.Now().Sub(started)
    if duration.Seconds() > 10 {
        w.WriteHeader(500)
        w.Write([]byte(fmt.Sprintf("error: %v", duration.Seconds())))
    } else {
        w.WriteHeader(200)
        w.Write([]byte("ok"))
    }
})

kubelet 在容器启动之后 3 秒开始执行健康检测。所以前几次健康检查都是成功的。 但是 10 秒之后,健康检查会失败,并且 kubelet 会杀死容器再重新启动容器。
在这里插入图片描述
tcpSocket

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

这个例子同时使用就绪和存活探针。kubelet 会在容器启动 15 秒后发送第一个就绪探针。 探针会尝试连接 goproxy 容器的 8080 端口。 如果探测成功,这个 Pod 会被标记为就绪状态,kubelet 将继续每隔 10 秒运行一次探测。

除了就绪探针,这个配置包括了一个存活探针。 kubelet 会在容器启动 15 秒后进行第一次存活探测。 与就绪探针类似,存活探针会尝试连接 goproxy 容器的 8080 端口。 如果存活探测失败,容器会被重新启动。

5 Pod 调度

讲调度之前,先了解下怎么给节点设置标签

5.1 节点添加标签
# 查看标签
$ kubectl get nodes --show-labels
# 添加标签
$ kubectl label nodes node1(节点名称) disktype=ssd
5.2 定向调度

定向调度,指的是利用在pod上声明nodeName或者nodeSelector,以此将Pod调度到期望的node节点上。定向调度是强制性的。

nodeName

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  nodeName: node1 # 调度 Pod 到特定的节点 不存在的话则失败
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent

nodeSelector

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  nodeSelector:
    disktype: ssd # 将被调度到有 disktype=ssd 标签的节点
5.3 亲和性调度
5.3.1 节点亲和性

nodeSelector 提供了一种最简单的方法来将 Pod 约束到具有特定标签的节点上。 亲和性和反亲和性扩展了你可以定义的约束类型。使用亲和性与反亲和性的一些好处有:

  • 亲和性、反亲和性语言的表达能力更强。nodeSelector 只能选择拥有所有指定标签的节点。 亲和性、反亲和性为你提供对选择逻辑的更强控制能力。
  • 你可以标明某规则是“软需求”或者“偏好”,这样调度器在无法找到匹配节点时仍然调度该 Pod。
  • 你可以使用节点上(或其他拓扑域中)运行的其他 Pod 的标签来实施调度约束, 而不是只能使用节点本身的标签。这个能力让你能够定义规则允许哪些 Pod 可以被放置在一起。

亲和性功能由两种类型的亲和性组成:

  • 节点亲和性功能类似于 nodeSelector 字段,但它的表达能力更强,并且允许你指定软规则。
  • Pod 间亲和性/反亲和性允许你根据其他 Pod 的标签来约束 Pod。

节点亲和性概念上类似于 nodeSelector, 它使你可以根据节点上的标签来约束 Pod 可以调度到哪些节点上。 节点亲和性有两种:

  • requiredDuringSchedulingIgnoredDuringExecution: 调度器只有在规则被满足的时候才能执行调度。此功能类似于 nodeSelector, 但其语法表达能力更强。
  • preferredDuringSchedulingIgnoredDuringExecution: 调度器会尝试寻找满足对应规则的节点。如果找不到匹配的节点,调度器仍然会调度该 Pod。
apiVersion: v1
kind: Pod
metadata:
  name: with-node-affinity
spec:
  affinity:
    nodeAffinity:
    # 节点必须包含一个键名为 disktype 的标签, 并且该标签的取值必须为 ssd 
      requiredDuringSchedulingIgnoredDuringExecution: 
        nodeSelectorTerms:
        - matchExpressions:
          - key: disktype
            operator: In  
            values:
            - ssd
  containers:
  - name: nginx
    image: nginx:1.17.1

使用 operator 字段来为 Kubernetes 设置在解释规则时要使用的逻辑操作符。

可以使用 InNotInExistsDoesNotExistGtLt 之一作为操作符。NotInDoesNotExist 可用来实现节点反亲和性行为。

5.3.2 节点亲和性权重
apiVersion: v1
kind: Pod
metadata:
  name: with-node-affinity
spec:
  affinity:
    nodeAffinity:
      preferredDuringSchedulingIgnoredDuringExecution: 
      - weight: 50 #取值范围是 1 到 100  
        preference:
          matchExpressions:
          - key: disktype
            operator: In  
            values:
            - ssd
      - weight: 50 #取值范围是 1 到 100  
        preference:
          matchExpressions:
          - key: env
            operator: In  
            values:
            - test
  containers:
  - name: nginx
    image: nginx:1.17.1
5.3.3 Pod 间亲和性和反亲和性

与节点亲和性类似,Pod 的亲和性与反亲和性也有两种类型:

  • requiredDuringSchedulingIgnoredDuringExecution
  • preferredDuringSchedulingIgnoredDuringExecution

例如,你可以使用 requiredDuringSchedulingIgnoredDuringExecution 亲和性来告诉调度器, 将两个服务的 Pod 放到同一个云提供商可用区内,因为它们彼此之间通信非常频繁。 类似地,你可以使用 preferredDuringSchedulingIgnoredDuringExecution 反亲和性来将同一服务的多个 Pod 分布到多个云提供商可用区中。

要使用 Pod 间亲和性,可以使用 Pod 规约中的 spec.affinity.podAffinity 字段。 对于 Pod 间反亲和性,可以使用 Pod 规约中的 spec.affinity.podAntiAffinity 字段。

apiVersion: v1
kind: Pod
metadata:
  name: with-pod-affinity
spec:
  affinity:
    podAffinity:
    # 必须将pod调度到具有env标签的节点上,并且运行着带有app=nginx标签的pod
      requiredDuringSchedulingIgnoredDuringExecution:
        - topologyKey: env
          labelSelector:
            matchExpressions:
              - key: app
                operator: In
                values:
                  - nginx
  containers:
    - name: redis
      image: redis
      imagePullPolicy: IfNotPresent
  restartPolicy: Always
5.3.4 pod 间亲和性权重
apiVersion: v1
kind: Pod
metadata:
  name: with-pod-affinity-weight
spec:
  containers:
    - name: redis
      image: redis
      imagePullPolicy: IfNotPresent
  restartPolicy: Always
  affinity:
    podAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
        - podAffinityTerm:
            topologyKey: env
            labelSelector:
              matchExpressions:
                - key: app
                  operator: In
                  values:
                    - nginx
          weight: 50 # 范围1-100
        - podAffinityTerm:
            topologyKey: env
            labelSelector:
              matchExpressions:
                - key: app
                  operator: In
                  values:
                    - mysql
          weight: 50 # 范围1-100
5.4 污点和容忍

节点亲和性 是 Pod 的一种属性,它使 Pod 被吸引到一类特定的节点 (这可能出于一种偏好,也可能是硬性要求)。 污点(Taint) 则相反——它使节点能够排斥一类特定的 Pod。

容忍度(Toleration) 是应用于 Pod 上的。容忍度允许调度器调度带有对应污点的 Pod。 容忍度允许调度但并不保证调度:作为其功能的一部分, 调度器也会评估其他参数。

污点和容忍度(Toleration)相互配合,可以用来避免 Pod 被分配到不合适的节点上。 每个节点上都可以应用一个或多个污点,这表示对于那些不能容忍这些污点的 Pod, 是不会被该节点接受的。

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

污点的格式为:key=value:effect, key和value是污点的标签,effect描述污点的作用,支持如下三个选项:
在这里插入图片描述

5.4.1 污点
# 设置污点
$ kubectl taint nodes node1 key1=value1:NoSchedule
# 删除污点
$ kubectl taint nodes node1 key1=value1:NoSchedule-
# 示例
# 创建pod1
$ kubectl run taint1 --image=nginx:1.17.1
$ kubectl get pods  -o wide
NAME     READY   STATUS    RESTARTS   AGE   IP            NODE     NOMINATED NODE   READINESS GATES
taint1   1/1     Running   0          16s   192.168.1.4   node01   <none>           <none>
# 为node01设置污点NoSchedule
$ kubectl taint nodes node01 tag=test:NoSchedule
# 创建pod2
$ kubectl run taint2 --image=nginx:1.17.1 
$ kubectl get pods -o wide
NAME     READY   STATUS    RESTARTS   AGE     IP            NODE     NOMINATED NODE   READINESS GATES
taint1   1/1     Running   0          5m50s   192.168.1.4   node01   <none>           <none>
taint2   0/1     Pending   0          9s      <none>        <none>   <none>           <none>
# 为node1设置污点NoExecute
$ kubectl taint nodes node01 tag=test:NoExecute
# 查看pod 发现之前运行的taint1 被驱逐了看不到了
$ kubectl get pods -o wide
NAME     READY   STATUS    RESTARTS   AGE    IP       NODE     NOMINATED NODE   READINESS GATES
taint2   0/1     Pending   0          119s   <none>   <none>   <none>           <none>
5.4.2 容忍度

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

可以在 Pod 规约中为 Pod 设置容忍度。

tolerations:
- key: "key1"
  operator: "Equal"
  value: "value1"
  effect: "NoSchedule"
apiVersion: v1
kind: Pod
metadata:
  name: pod-toleration
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  tolerations:      # 添加容忍
  - key: "tag"        # 要容忍的污点的key
    operator: "Equal" # 操作符
    value: "test"    # 容忍的污点的value
    effect: "NoExecute"   # 添加容忍的规则,这里必须和标记的污点规则相同
# 发现从之前的peeding 状态变成running了
$ kubectl get pods -o wide
NAME             READY   STATUS    RESTARTS   AGE     IP            NODE           NOMINATED NODE   READINESS GATES
pod-toleration   1/1     Running   0          4m38s   192.168.0.7   controlplane   <none>           <none>

关于pod的描述就暂时这些,还有一些其他的比如pod的开销pod的拓扑分布约束pod的优先级和抢占等有兴趣的可以看看官网。

本章节部分案例和描述借鉴duansamve大佬的博客,感谢大佬,有兴趣大家也可以阅读此博客。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐