[K8s]Kubernetes-工作负载(下)

Posted 若水三千

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[K8s]Kubernetes-工作负载(下)相关的知识,希望对你有一定的参考价值。

2.4 - DaemonSet

DaemonSet 确保全部(或者某些)节点上运行一个 Pod 的副本。当有节点加入集群时,也会为他们新增一个 Pod。当有节点从集群移除时,这些 Pod 也会被回收。删除 DaemonSet 将会删除它创建的所有 Pod。

DaemonSet 的一些典型用法:

  • 在每个节点上运行集群守护进程
  • 在每个节点上运行日志收集守护进程
  • 在每个节点上运行监控守护进程

一种简单的用法是为每种类型的守护进程在所有的节点上都启动一个 DaemonSet。一个稍微复杂的用法是为同一种守护进程部署多个 DaemonSet;每个具有不同的标志,并且对不同硬件类型具有不同的内存、CPU 要求。

编写 DaemonSet Spec

创建 DaemonSet

你可以在 YAML 文件中描述 DaemonSet。例如,下面的 daemonset.yaml 文件描述了一个运行 fluentd-elasticsearch Docker 镜像的 DaemonSet:

kind: DaemonSet
metadata:
  name: fluentd-elasticsearch
  namespace: kube-system
  labels:
    k8s-app: fluentd-logging
spec:
  selector:
    matchLabels:
      name: fluentd-elasticsearch
  template:
    metadata:
      labels:
        name: fluentd-elasticsearch
    spec:
      tolerations:
      # this toleration is to have the daemonset runnable on master nodes
      # remove it if your masters can\'t run pods
      - key: node-role.kubernetes.io/master
        operator: Exists
        effect: NoSchedule
      containers:
      - name: fluentd-elasticsearch
        image: quay.io/fluentd_elasticsearch/fluentd:v2.5.2
        resources:
          limits:
            memory: 200Mi
          requests:
            cpu: 100m
            memory: 200Mi
        volumeMounts:
        - name: varlog
          mountPath: /var/log
        - name: varlibdockercontainers
          mountPath: /var/lib/docker/containers
          readOnly: true
      terminationGracePeriodSeconds: 30
      volumes:
      - name: varlog
        hostPath:
          path: /var/log
      - name: varlibdockercontainers
        hostPath:
          path: /var/lib/docker/containers

基于 YAML 文件创建 DaemonSet:

kubectl apply -f https://k8s.io/examples/controllers/daemonset.yaml

必需字段

和所有其他 Kubernetes 配置一样,DaemonSet 需要 apiVersion、kind 和 metadata 字段。 有关配置文件的基本信息。

DaemonSet 对象的名称必须是一个合法的 DNS 子域名。

DaemonSet 也需要一个 .spec 配置段。

Pod 模板

.spec 中唯一必需的字段是 .spec.template。

.spec.template 是一个 Pod 模板。除了它是嵌套的,因而不具有 apiVersion 或 kind 字段之外,它与 Pod 具有相同的 schema。

除了 Pod 必需字段外,在 DaemonSet 中的 Pod 模板必须指定合理的标签(查看 Pod 选择算符)。

在 DaemonSet 中的 Pod 模板必须具有一个值为 Always 的 RestartPolicy。当该值未指定时,默认是 Always。

Pod 选择算符

.spec.selector 字段表示 Pod 选择算符,它与 Job 的 .spec.selector 的作用是相同的。

从 Kubernetes 1.8 开始,您必须指定与 .spec.template 的标签匹配的 Pod 选择算符。用户不指定 Pod 选择算符时,该字段不再有默认值。选择算符的默认值生成结果与 kubectl apply 不兼容。此外,一旦创建了 DaemonSet,它的 .spec.selector 就不能修改。修改 Pod 选择算符可能导致 Pod 意外悬浮,并且这对用户来说是费解的。

spec.selector 是一个对象,如下两个字段组成:

  • matchLabels - 与 ReplicationController 的 .spec.selector 的作用相同。
  • matchExpressions - 允许构建更加复杂的选择器,可以通过指定 key、value 列表以及将 key 和 value 列表关联起来的 operator。

当上述两个字段都指定时,结果会按逻辑与(AND)操作处理。

如果指定了 .spec.selector,必须与 .spec.template.metadata.labels 相匹配。如果与后者不匹配,则 DeamonSet 会被 API 拒绝。

仅在某些节点上运行 Pod

如果指定了 .spec.template.spec.nodeSelector,DaemonSet 控制器将在能够与 Node 选择算符匹配的节点上创建 Pod。类似这种情况,可以指定 .spec.template.spec.affinity,之后 DaemonSet 控制器将在能够与节点亲和性匹配的节点上创建 Pod。如果根本就没有指定,则 DaemonSet Controller 将在所有节点上创建 Pod。

Daemon Pods 是如何被调度的

通过默认调度器调度

FEATURE STATE: Kubernetes v1.23 [stable]

DaemonSet 确保所有符合条件的节点都运行该 Pod 的一个副本。通常,运行 Pod 的节点由 Kubernetes 调度器选择。不过,DaemonSet Pods 由 DaemonSet 控制器创建和调度。这就带来了以下问题:

  • Pod 行为的不一致性:正常 Pod 在被创建后等待调度时处于 Pending 状态,DaemonSet Pods 创建后不会处于 Pending 状态下。这使用户感到困惑。
  • Pod 抢占由默认调度器处理。启用抢占后,DaemonSet 控制器将在不考虑 Pod 优先级和抢占的情况下制定调度决策。

ScheduleDaemonSetPods 允许您使用默认调度器而不是 DaemonSet 控制器来调度 DaemonSets,方法是将 NodeAffinity 条件而不是 .spec.nodeName 条件添加到 DaemonSet Pods。默认调度器接下来将 Pod 绑定到目标主机。如果 DaemonSet Pod 的节点亲和性配置已存在,则被替换(原始的节点亲和性配置在选择目标主机之前被考虑)。DaemonSet 控制器仅在创建或修改 DaemonSet Pod 时执行这些操作,并且不会更改 DaemonSet 的 spec.template。

nodeAffinity:
  requiredDuringSchedulingIgnoredDuringExecution:
    nodeSelectorTerms:
    - matchFields:
      - key: metadata.name
        operator: In
        values:
        - target-host-name

此外,系统会自动添加 node.kubernetes.io/unschedulable:NoSchedule 容忍度到 DaemonSet Pods。在调度 DaemonSet Pod 时,默认调度器会忽略 unschedulable 节点。

污点和容忍度

尽管 Daemon Pods 遵循污点和容忍度规则,根据相关特性,控制器会自动将以下容忍度添加到 DaemonSet Pod:

容忍度键名
效果
版本
描述
node.kubernetes.io/not-ready
NoExecute
1.13+
当出现类似网络断开的情况导致节点问题时,DaemonSet Pod 不会被逐出。
node.kubernetes.io/unreachable
NoExecute
1.13+
当出现类似于网络断开的情况导致节点问题时,DaemonSet Pod 不会被逐出。
node.kubernetes.io/disk-pressure
NoSchedule
1.8+
DaemonSet Pod 被默认调度器调度时能够容忍磁盘压力属性。
node.kubernetes.io/memory-pressure
NoSchedule
1.8+
DaemonSet Pod 被默认调度器调度时能够容忍内存压力属性。
node.kubernetes.io/unschedulable
NoSchedule
1.12+
DaemonSet Pod 能够容忍默认调度器所设置的 unschedulable 属性.
node.kubernetes.io/network-unavailable
NoSchedule
1.12+
DaemonSet 在使用宿主网络时,能够容忍默认调度器所设置的 network-unavailable 属性。

与 Daemon Pods 通信

与 DaemonSet 中的 Pod 进行通信的几种可能模式如下:

  • 推送(Push):配置 DaemonSet 中的 Pod,将更新发送到另一个服务,例如统计数据库。 这些服务没有客户端。

  • NodeIP 和已知端口:DaemonSet 中的 Pod 可以使用 hostPort,从而可以通过节点 IP 访问到 Pod。客户端能通过某种方法获取节点 IP 列表,并且基于此也可以获取到相应的端口。

  • DNS:创建具有相同 Pod 选择算符的无头服务,通过使用 endpoints 资源或从 DNS 中检索到多个 A 记录来发现 DaemonSet。

  • Service:创建具有相同 Pod 选择算符的服务,并使用该服务随机访问到某个节点上的守护进程(没有办法访问到特定节点)。

更新 DaemonSet

如果节点的标签被修改,DaemonSet 将立刻向新匹配上的节点添加 Pod,同时删除不匹配的节点上的 Pod。

你可以修改 DaemonSet 创建的 Pod。不过并非 Pod 的所有字段都可更新。下次当某节点(即使具有相同的名称)被创建时,DaemonSet 控制器还会使用最初的模板。

您可以删除一个 DaemonSet。如果使用 kubectl 并指定 --cascade=orphan 选项,则 Pod 将被保留在节点上。接下来如果创建使用相同选择算符的新 DaemonSet,新的 DaemonSet 会收养已有的 Pod。如果有 Pod 需要被替换,DaemonSet 会根据其 updateStrategy 来替换。

你可以对 DaemonSet 执行滚动更新操作。

DaemonSet 的替代方案

init 脚本

直接在节点上启动守护进程(例如使用 init、upstartd 或 systemd)的做法当然是可行的。不过,基于 DaemonSet 来运行这些进程有如下一些好处:

  • 像所运行的其他应用一样,DaemonSet 具备为守护进程提供监控和日志管理的能力。

  • 为守护进程和应用所使用的配置语言和工具(如 Pod 模板、kubectl)是相同的。

  • 在资源受限的容器中运行守护进程能够增加守护进程和应用容器的隔离性。然而,这一点也可以通过在容器中运行守护进程但却不在 Pod 中运行之来实现。例如,直接基于 Docker 启动。

裸 Pod

直接创建 Pod并指定其运行在特定的节点上也是可以的。然而,DaemonSet 能够替换由于任何原因(例如节点失败、例行节点维护、内核升级)而被删除或终止的 Pod。由于这个原因,你应该使用 DaemonSet 而不是单独创建 Pod。

静态 Pod

通过在一个指定的、受 kubelet 监视的目录下编写文件来创建 Pod 也是可行的。这类 Pod 被称为静态 Pod。不像 DaemonSet,静态 Pod 不受 kubectl 和其它 Kubernetes API 客户端管理。静态 Pod 不依赖于 API 服务器,这使得它们在启动引导新集群的情况下非常有用。此外,静态 Pod 在将来可能会被废弃。

Deployments

DaemonSet 与 Deployments 非常类似,它们都能创建 Pod,并且 Pod 中的进程都不希望被终止(例如,Web 服务器、存储服务器)。

建议为无状态的服务使用 Deployments,比如前端服务。对这些服务而言,对副本的数量进行扩缩容、平滑升级,比精确控制 Pod 运行在某个主机上要重要得多。当需要 Pod 副本总是运行在全部或特定主机上,并且当该 DaemonSet 提供了节点级别的功能(允许其他 Pod 在该特定节点上正确运行)时,应该使用 DaemonSet。

例如,网络插件通常包含一个以 DaemonSet 运行的组件。这个 DaemonSet 组件确保它所在的节点的集群网络正常工作。

2.5 - Jobs

Job 会创建一个或者多个 Pods,并将继续重试 Pods 的执行,直到指定数量的 Pods 成功终止。随着 Pods 成功结束,Job 跟踪记录成功完成的 Pods 个数。当数量达到指定的成功个数阈值时,任务(即 Job)结束。删除 Job 的操作会清除所创建的全部 Pods。挂起 Job 的操作会删除 Job 的所有活跃 Pod,直到 Job 被再次恢复执行。

一种简单的使用场景下,你会创建一个 Job 对象以便以一种可靠的方式运行某 Pod 直到完成。当第一个 Pod 失败或者被删除(比如因为节点硬件失效或者重启)时,Job 对象会启动一个新的 Pod。

你也可以使用 Job 以并行的方式运行多个 Pod。

运行示例 Job

下面是一个 Job 配置示例。它负责计算 π 到小数点后 2000 位,并将结果打印出来。此计算大约需要 10 秒钟完成。

kind: Job
metadata:
  name: pi
spec:
  template:
    spec:
      containers:
      - name: pi
        image: perl
        command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
      restartPolicy: Never
  backoffLimit: 4

你可以使用下面的命令来运行此示例:

kubectl apply -f https://kubernetes.io/examples/controllers/job.yaml

输出类似于:

job.batch/pi created

使用 kubectl 来检查 Job 的状态:

kubectl describe jobs/pi

输出类似于:

Name:           pi
Namespace:      default
Selector:       controller-uid=c9948307-e56d-4b5d-8302-ae2d7b7da67c
Labels:         controller-uid=c9948307-e56d-4b5d-8302-ae2d7b7da67c
                job-name=pi
Annotations:    kubectl.kubernetes.io/last-applied-configuration:
                  "apiVersion":"batch/v1","kind":"Job","metadata":"annotations":,"name":"pi","namespace":"default","spec":"backoffLimit":4,"template":...
Parallelism:    1
Completions:    1
Start Time:     Mon, 02 Dec 2019 15:20:11 +0200
Completed At:   Mon, 02 Dec 2019 15:21:16 +0200
Duration:       65s
Pods Statuses:  0 Running / 1 Succeeded / 0 Failed
Pod Template:
  Labels:  controller-uid=c9948307-e56d-4b5d-8302-ae2d7b7da67c
           job-name=pi
  Containers:
   pi:
    Image:      perl
    Port:       <none>
    Host Port:  <none>
    Command:
      perl
      -Mbignum=bpi
      -wle
      print bpi(2000)
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Events:
  Type    Reason            Age   From            Message
  ----    ------            ----  ----            -------
  Normal  SuccessfulCreate  14m   job-controller  Created pod: pi-5rwd7

要查看 Job 对应的已完成的 Pods,可以执行 kubectl get pods

要以机器可读的方式列举隶属于某 Job 的全部 Pods,你可以使用类似下面这条命令:

pods=$(kubectl get pods --selector=job-name=pi --output=jsonpath=\'.items[*].metadata.name\')
echo $pods

输出类似于:

pi-5rwd7

这里,选择算符与 Job 的选择算符相同。--output=jsonpath 选项给出了一个表达式,用来从返回的列表中提取每个 Pod 的 name 字段。

查看其中一个 Pod 的标准输出:

kubectl logs $pods

输出类似于:

3.1415926535897.........................

编写 Job 规约

与 Kubernetes 中其他资源的配置类似,Job 也需要 apiVersion、kind 和 metadata 字段。Job 的名字必须是合法的 DNS 子域名。

Job 配置还需要一个.spec 节。

Pod 模版

Job 的 .spec 中只有 .spec.template 是必需的字段。

字段 .spec.template 的值是一个 Pod 模版。其定义规范与 Pod 完全相同,只是其中不再需要 apiVersion 或 kind 字段。

除了作为 Pod 所必需的字段之外,Job 中的 Pod 模版必需设置合适的标签(参见Pod 选择算符)和合适的重启策略。

Job 中 Pod 的 RestartPolicy 只能设置为 Never 或 OnFailure 之一。

Pod 选择算符

字段 .spec.selector 是可选的。在绝大多数场合,你都不需要为其赋值。参阅设置自己的 Pod 选择算符。

Job 的并行执行

适合以 Job 形式来运行的任务主要有三种:

  1. 非并行 Job:
    • 通常只启动一个 Pod,除非该 Pod 失败。
    • 当 Pod 成功终止时,立即视 Job 为完成状态。
  2. 具有确定完成计数的并行 Job:
    • .spec.completions 字段设置为非 0 的正数值。
    • Job 用来代表整个任务,当成功的 Pod 个数达到 .spec.completions 时,Job 被视为完成。
    • 当使用 .spec.completionMode="Indexed" 时,每个 Pod 都会获得一个不同的索引值,介于 0 和 .spec.completions-1 之间。
  3. 带工作队列的并行 Job:
    • 不设置 spec.completions,默认值为 .spec.parallelism。
    • 多个 Pod 之间必须相互协调,或者借助外部服务确定每个 Pod 要处理哪个工作条目。例如,任一 Pod 都可以从工作队列中取走最多 N 个工作条目。
    • 每个 Pod 都可以独立确定是否其它 Pod 都已完成,进而确定 Job 是否完成。
    • 当 Job 中 任何 Pod 成功终止,不再创建新 Pod。
    • 一旦至少 1 个 Pod 成功完成,并且所有 Pod 都已终止,即可宣告 Job 成功完成。
    • 一旦任何 Pod 成功退出,任何其它 Pod 都不应再对此任务执行任何操作或生成任何输出。 所有 Pod 都应启动退出过程。

对于非并行的 Job,你可以不设置 spec.completions 和 spec.parallelism。这两个属性都不设置时,均取默认值 1。

对于确定完成计数类型的 Job,你应该设置 .spec.completions 为所需要的完成个数。你可以设置 .spec.parallelism,也可以不设置。其默认值为 1。

对于一个工作队列 Job,你不可以设置 .spec.completions,但要将.spec.parallelism 设置为一个非负整数。

关于如何利用不同类型的 Job 的更多信息,请参见 Job 模式一节。

控制并行性

并行性请求(.spec.parallelism)可以设置为任何非负整数。如果未设置,则默认为 1。如果设置为 0,则 Job 相当于启动之后便被暂停,直到此值被增加。

实际并行性(在任意时刻运行状态的 Pods 个数)可能比并行性请求略大或略小,原因如下:

  • 对于确定完成计数 Job,实际上并行执行的 Pods 个数不会超出剩余的完成数。如果 .spec.parallelism 值较高,会被忽略。
  • 对于工作队列 Job,有任何 Job 成功结束之后,不会有新的 Pod 启动。不过,剩下的 Pods 允许执行完毕。
  • 如果 Job 控制器没有来得及作出响应。
  • 如果 Job 控制器因为任何原因(例如,缺少 ResourceQuota 或者没有权限)无法创建 Pods。Pods 个数可能比请求的数目小。
  • Job 控制器可能会因为之前同一 Job 中 Pod 失效次数过多而压制新 Pod 的创建。
  • 当 Pod 处于体面终止进程中,需要一定时间才能停止。

完成模式

FEATURE STATE: Kubernetes v1.22 [beta]

带有确定完成计数的 Job,即 .spec.completions 不为 null 的 Job,都可以在其 .spec.completionMode 中设置完成模式:

  • NonIndexed(默认值):当成功完成的 Pod 个数达到 .spec.completions 所设值时认为 Job 已经完成。换言之,每个 Job 完成事件都是独立无关且同质的。要注意的是,当 .spec.completions 取值为 null 时,Job 被隐式处理为 NonIndexed。

  • Indexed:Job 的 Pod 会获得对应的完成索引,取值为 0 到 .spec.completions-1。该索引可以通过三种方式获取:

    • Pod 注解 batch.kubernetes.io/job-completion-index。
    • 作为 Pod 主机名的一部分,遵循模式 $(job-name)-$(index)。当你同时使用带索引的 Job(Indexed Job)与 服务(Service),Job 中的 Pods 可以通过 DNS 使用确切的主机名互相寻址。
    • 对于容器化的任务,在环境变量 JOB_COMPLETION_INDEX 中。

当每个索引都对应一个完成完成的 Pod 时,Job 被认为是已完成的。关于如何使用这种模式的更多信息,可参阅用带索引的 Job 执行基于静态任务分配的并行处理。需要注意的是,对同一索引值可能被启动的 Pod 不止一个,尽管这种情况很少发生。这时,只有一个会被记入完成计数中。

处理 Pod 和容器失效

Pod 中的容器可能因为多种不同原因失效,例如因为其中的进程退出时返回值非零,或者容器因为超出内存约束而被杀死等等。如果发生这类事件,并且 .spec.template.spec.restartPolicy = "OnFailure",Pod 则继续留在当前节点,但容器会被重新运行。因此,你的程序需要能够处理在本地被重启的情况,或者要设置 .spec.template.spec.restartPolicy = "Never"。关于 restartPolicy 的更多信息,可参阅 Pod 生命周期。

整个 Pod 也可能会失败,且原因各不相同。例如,当 Pod 启动时,节点失效(被升级、被重启、被删除等)或者其中的容器失败而 .spec.template.spec.restartPolicy = "Never"。当 Pod 失败时,Job 控制器会启动一个新的 Pod。这意味着,你的应用需要处理在一个新 Pod 中被重启的情况。尤其是应用需要处理之前运行所产生的临时文件、锁、不完整的输出等问题。

注意,即使你将 .spec.parallelism 设置为 1,且将 .spec.completions 设置为 1,并且 .spec.template.spec.restartPolicy 设置为 "Never",同一程序仍然有可能被启动两次。

如果你确实将 .spec.parallelism 和 .spec.completions 都设置为比 1 大的值,那就有可能同时出现多个 Pod 运行的情况。为此,你的 Pod 也必须能够处理并发性问题。

Pod 回退失效策略

在有些情形下,你可能希望 Job 在经历若干次重试之后直接进入失败状态,因为这很可能意味着遇到了配置错误。为了实现这点,可以将 .spec.backoffLimit 设置为视 Job 为失败之前的重试次数。失效回退的限制值默认为 6。与 Job 相关的失效的 Pod 会被 Job 控制器重建,回退重试时间将会按指数增长(从 10 秒、20 秒到 40 秒)最多至 6 分钟。当 Job 的 Pod 被删除时,或者 Pod 成功时没有其它 Pod 处于失败状态,失效回退的次数也会被重置(为 0)。

说明: 如果你的 Job 的 restartPolicy 被设置为 "OnFailure",就要注意运行该 Job 的 Pod 会在 Job 到达失效回退次数上限时自动被终止。这会使得调试 Job 中可执行文件的工作变得非常棘手。我们建议在调试 Job 时将 restartPolicy 设置为 "Never",或者使用日志系统来确保失效 Jobs 的输出不会意外遗失。

Job 终止与清理

Job 完成时不会再创建新的 Pod,不过已有的 Pod 通常也不会被删除。保留这些 Pod 使得你可以查看已完成的 Pod 的日志输出,以便检查错误、警告 或者其它诊断性输出。Job 完成时 Job 对象也一样被保留下来,这样你就可以查看它的状态。在查看了 Job 状态之后删除老的 Job 的操作留给了用户自己。你可以使用 kubectl 来删除 Job(例如,kubectl delete jobs/pi 或者 kubectl delete -f ./job.yaml)。当使用 kubectl 来删除 Job 时,该 Job 所创建的 Pods 也会被删除。

默认情况下,Job 会持续运行,除非某个 Pod 失败(restartPolicy=Never)或者某个容器出错退出(restartPolicy=OnFailure)。这时,Job 基于前述的 spec.backoffLimit 来决定是否以及如何重试。一旦重试次数到达 .spec.backoffLimit 所设的上限,Job 会被标记为失败,其中运行的 Pods 都会被终止。

终止 Job 的另一种方式是设置一个活跃期限。你可以为 Job 的 .spec.activeDeadlineSeconds 设置一个秒数值。该值适用于 Job 的整个生命期,无论 Job 创建了多少个 Pod。一旦 Job 运行时间达到 activeDeadlineSeconds 秒,其所有运行中的 Pod 都会被终止,并且 Job 的状态更新为 type: Failed 及 reason: DeadlineExceeded。

注意 Job 的 .spec.activeDeadlineSeconds 优先级高于其 .spec.backoffLimit 设置。因此,如果一个 Job 正在重试一个或多个失效的 Pod,该 Job 一旦到达 activeDeadlineSeconds 所设的时限即不再部署额外的 Pod,即使其重试次数还未达到 backoffLimit 所设的限制。

例如:

apiVersion: batch/v1
kind: Job
metadata:
  name: pi-with-timeout
spec:
  backoffLimit: 5
  activeDeadlineSeconds: 100
  template:
    spec:
      containers:
      - name: pi
        image: perl
        command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
      restartPolicy: Never

注意 Job 规约和 Job 中的 Pod 模版规约都有 activeDeadlineSeconds 字段。请确保你在合适的层次设置正确的字段。

还要注意的是,restartPolicy 对应的是 Pod,而不是 Job 本身:一旦 Job 状态变为 type: Failed,就不会再发生 Job 重启的动作。换言之,由 .spec.activeDeadlineSeconds 和 .spec.backoffLimit 所触发的 Job 终结机制都会导致 Job 永久性的失败,而这类状态都需要手工干预才能解决。

自动清理完成的 Job

完成的 Job 通常不需要留存在系统中。在系统中一直保留它们会给 API 服务器带来额外的压力。如果 Job 由某种更高级别的控制器来管理,例如 CronJobs,则 Job 可以被 CronJob 基于特定的根据容量裁定的清理策略清理掉。

已完成 Job 的 TTL 机制

FEATURE STATE: Kubernetes v1.21 [beta]

自动清理已完成 Job(状态为 Complete 或 Failed)的另一种方式是使用由 TTL 控制器所提供的 TTL 机制。通过设置 Job 的 .spec.ttlSecondsAfterFinished 字段,可以让该控制器清理掉已结束的资源。

TTL 控制器清理 Job 时,会级联式地删除 Job 对象。换言之,它会删除所有依赖的对象,包括 Pod 及 Job 本身。注意,当 Job 被删除时,系统会考虑其生命周期保障,例如其 Finalizers。

例如:

apiVersion: batch/v1
kind: Job
metadata:
  name: pi-with-ttl
spec:
  ttlSecondsAfterFinished: 100
  template:
    spec:
      containers:
      - name: pi
        image: perl
        command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
      restartPolicy: Never

Job pi-with-ttl 在结束 100 秒之后,可以成为被自动删除的对象。

如果该字段设置为 0,Job 在结束之后立即成为可被自动删除的对象。如果该字段没有设置,Job 不会在结束之后被 TTL 控制器自动清除。

Job 模式

Job 对象可以用来支持多个 Pod 的可靠的并发执行。Job 对象不是设计用来支持相互通信的并行进程的,后者一般在科学计算中应用较多。Job 的确能够支持对一组相互独立而又有所关联的工作条目 的并行处理。这类工作条目可能是要发送的电子邮件、要渲染的视频帧、要编解码的文件、NoSQL 数据库中要扫描的主键范围等等。

在一个复杂系统中,可能存在多个不同的工作条目集合。这里我们仅考虑用户希望一起管理的工作条目集合之一 — 批处理作业

并行计算的模式有好多种,每种都有自己的强项和弱点。这里要权衡的因素有:

  • 每个工作条目对应一个 Job 或者所有工作条目对应同一 Job 对象。后者更适合处理大量工作条目的场景;前者会给用户带来一些额外的负担,而且需要系统管理大量的 Job 对象。
  • 创建与工作条目相等的 Pod 或者令每个 Pod 可以处理多个工作条目。前者通常不需要对现有代码和容器做较大改动;后者则更适合工作条目数量较大的场合,原因同上。
  • 有几种技术都会用到工作队列。这意味着需要运行一个队列服务,并修改现有程序或容器使之能够利用该工作队列。与之比较,其他方案在修改现有容器化应用以适应需求方面可能更容易一些。

下面是对这些权衡的汇总,列 2 到 4 对应上面的权衡比较。模式的名称对应了相关示例和更详细描述的链接。

模式
单个 Job 对象
Pods 数少于工作条目数?
直接使用应用无需修改?
每工作条目一 Pod 的队列
 
有时
Pod 数量可变的队列
 
静态任务分派的带索引的 Job
 
Job 模版扩展
 
 

当你使用 .spec.completions 来设置完成数时,Job 控制器所创建的每个 Pod 使用完全相同的 spec。这意味着任务的所有 Pod 都有相同的命令行,都使用相同的镜像和数据卷,甚至连环境变量都(几乎)相同。这些模式是让每个 Pod 执行不同工作的几种不同形式。

下表显示的是每种模式下 .spec.parallelism 和 .spec.completions 所需要的设置。其中,W 表示的是工作条目的个数。

模式
.spec.completions
.spec.parallelism
每工作条目一 Pod 的队列
W
任意值
Pod 个数可变的队列
1
任意值
静态任务分派的带索引的 Job
W
 
Job 模版扩展
1
应该为 1

高级用法

挂起 Job

FEATURE STATE: Kubernetes v1.21 [alpha]

说明:
该特性在 Kubernetes 1.21 版本中是 Alpha 阶段,启用该特性需要额外的步骤;请确保你正在阅读与集群版本一致的文档。

Job 被创建时,Job 控制器会马上开始执行 Pod 创建操作以满足 Job 的需求,并持续执行此操作直到 Job 完成为止。不过你可能想要暂时挂起 Job 执行,之后再恢复其执行。要挂起一个 Job,你可以将 Job 的 .spec.suspend 字段更新为 true。之后,当你希望恢复其执行时,将其更新为 false。创建一个 .spec.suspend 被设置为 true 的 Job 本质上会将其创建为被挂起状态。

当 Job 被从挂起状态恢复执行时,其 .status.startTime 字段会被重置为当前的时间。这意味着 .spec.activeDeadlineSeconds 计时器会在 Job 挂起时被停止,并在 Job 恢复执行时复位。

要记住的是,挂起 Job 会删除其所有活跃的 Pod。当 Job 被挂起时,你的 Pod 会收到 SIGTERM 信号而被终止。Pod 的体面终止期限会被考虑,不过 Pod 自身也必须在此期限之内处理完信号。处理逻辑可能包括保存进度以便将来恢复,或者取消已经做出的变更等等。Pod 以这种形式终止时,不会被记入 Job 的 completions 计数。

处于被挂起状态的 Job 的定义示例可能是这样子:

kubectl get job myjob -o yaml

apiVersion: batch/v1
kind: Job
metadata:
  name: myjob
spec:
  suspend: true
  parallelism: 1
  completions: 5
  template:
    spec:
      ...

Job 的 status 可以用来确定 Job 是否被挂起,或者曾经被挂起。

kubectl get jobs/myjob -o yaml

apiVersion: batch/v1
kind: Job
# .metadata and .spec omitted
status:
  conditions:
  - lastProbeTime: "2021-02-05T13:14:33Z"
    lastTransitionTime: "2021-02-05T13:14:33Z"
    status: "True"
    type: Suspended
  startTime: "2021-02-05T13:13:48Z"

Job 的 "Suspended" 类型的状况在状态值为 "True" 时意味着 Job 正被 挂起;lastTransitionTime 字段可被用来确定 Job 被挂起的时长。如果此状况字段的取值为 "False",则 Job 之前被挂起且现在在运行。如果 "Suspended" 状况在 status 字段中不存在,则意味着 Job 从未 被停止执行。

当 Job 被挂起和恢复执行时,也会生成事件:

kubectl describe jobs/myjob

Name:           myjob
...
Events:
  Type    Reason            Age   From            Message
  ----    ------            ----  ----            -------
  Normal  SuccessfulCreate  12m   job-controller  Created pod: myjob-hlrpl
  Normal  SuccessfulDelete  11m   job-controller  Deleted pod: myjob-hlrpl
  Normal  Suspended         11m   job-controller  Job suspended
  Normal  SuccessfulCreate  3s    job-controller  Created pod: myjob-jvb44
  Normal  Resumed           3s    job-controller  Job resumed

最后四个事件,特别是 "Suspended" 和 "Resumed" 事件,都是因为 .spec.suspend 字段值被改来改去造成的。在这两个事件之间,我们看到没有 Pod 被创建,不过当 Job 被恢复执行时,Pod 创建操作立即被重启执行。

指定你自己的 Pod 选择算符

通常,当你创建一个 Job 对象时,你不会设置 .spec.selector。系统的默认值填充逻辑会在创建 Job 时添加此字段。它会选择一个不会与任何其他 Job 重叠的选择算符设置。

不过,有些场合下,你可能需要重载这个自动设置的选择算符。为了实现这点,你可以手动设置 Job 的 spec.selector 字段。

做这个操作时请务必小心。如果你所设定的标签选择算符并不唯一针对 Job 对应的 Pod 集合,甚或该算符还能匹配其他无关的 Pod,这些无关的 Job 的 Pod 可能会被删除。或者当前 Job 会将另外一些 Pod 当作是完成自身工作的 Pods,又或者两个 Job 之一或者二者同时都拒绝创建 Pod,无法运行至完成状态。如果所设置的算符不具有唯一性,其他控制器(如 RC 副本控制器)及其所管理的 Pod 集合可能会变得行为不可预测。Kubernetes 不会在你设置 .spec.selector 时尝试阻止你犯这类错误。

下面是一个示例场景,在这种场景下你可能会使用刚刚讲述的特性。

假定名为 old 的 Job 已经处于运行状态。你希望已有的 Pod 继续运行,但你希望 Job 接下来要创建的其他 Pod 使用一个不同的 Pod 模版,甚至希望 Job 的名字也发生变化。你无法更新现有的 Job,因为这些字段都是不可更新的。因此,你会删除 old Job,但 允许该 Job 的 Pod 集合继续运行。这是通过 kubectl delete jobs/old --cascade=orphan 实现的。在删除之前,我们先记下该 Job 所使用的选择算符。

kubectl get job old -o yaml

输出类似于:

kind: Job
metadata:
  name: old
  ...
spec:
  selector:
    matchLabels:
      controller-uid: a8f3d00d-c6d2-11e5-9f87-42010af00002
  ...

接下来你会创建名为 new 的新 Job,并显式地为其设置相同的选择算符。由于现有 Pod 都具有标签 controller-uid=a8f3d00d-c6d2-11e5-9f87-42010af00002,它们也会被名为 new 的 Job 所控制。

你需要在新 Job 中设置 manualSelector: true,因为你并未使用系统通常自动为你生成的选择算符。

kind: Job
metadata:
  name: new
  ...
spec:
  manualSelector: true
  selector:
    matchLabels:
      controller-uid: a8f3d00d-c6d2-11e5-9f87-42010af00002
  ...

新的 Job 自身会有一个不同于 a8f3d00d-c6d2-11e5-9f87-42010af00002 的唯一 ID。设置 manualSelector: true 是在告诉系统你知道自己在干什么并要求系统允许这种不匹配的存在。

使用 Finalizer 追踪 Job

FEATURE STATE: Kubernetes v1.22 [alpha]

说明:
要使用该行为,你必须为 API 服务器和控制器管理器启用 JobTrackingWithFinalizers 特性门控。默认是禁用的。
启用后,控制面基于下述行为追踪新的 Job。现有 Job 不受影响。作为用户,你会看到的唯一区别是控制面对 Job 完成情况的跟踪更加准确。

该功能未启用时,Job 控制器(Controller)依靠计算集群中存在的 Pod 来跟踪作业状态。也就是说,维持一个统计 succeeded 和 failed 的 Pod 的计数器。然而,Pod 可以因为一些原因被移除,包括:

  • 当一个节点宕机时,垃圾收集器会删除孤立(Orphan)Pod。
  • 垃圾收集器在某个阈值后删除已完成的 Pod(处于 Succeeded 或 Failed 阶段)。
  • 人工干预删除 Job 的 Pod。
  • 一个外部控制器(不包含于 Kubernetes)来删除或取代 Pod。

如果你为你的集群启用了 JobTrackingWithFinalizers 特性,控制面会跟踪属于任何 Job 的 Pod。并注意是否有任何这样的 Pod 被从 API 服务器上删除。为了实现这一点,Job 控制器创建的 Pod 带有 Finalizer batch.kubernetes.io/job-tracking。控制器只有在 Pod 被记入 Job 状态后才会移除 Finalizer,允许 Pod 可以被其他控制器或用户删除。

Job 控制器只对新的 Job 使用新的算法。在启用该特性之前创建的 Job 不受影响。你可以根据检查 Job 是否含有 batch.kubernetes.io/job-tracking 注解,来确定 Job 控制器是否正在使用 Pod Finalizer 追踪 Job。你不应该给 Job 手动添加或删除该注解。

替代方案

裸 Pod

当 Pod 运行所在的节点重启或者失败,Pod 会被终止并且不会被重启。Job 会重新创建新的 Pod 来替代已终止的 Pod。因为这个原因,我们建议你使用 Job 而不是独立的裸 Pod,即使你的应用仅需要一个 Pod。

副本控制器

Job 与副本控制器是彼此互补的。副本控制器管理的是那些不希望被终止的 Pod(例如,Web 服务器),Job 管理的是那些希望被终止的 Pod(例如,批处理作业)。

正如在 Pod 生命期中讨论的,Job 仅适合于 restartPolicy 设置为 OnFailure 或 Never 的 Pod。注意:如果 restartPolicy 未设置,其默认值是 Always。

单个 Job 启动控制器 Pod

另一种模式是用唯一的 Job 来创建 Pod,而该 Pod 负责启动其他 Pod,因此扮演了一种后启动 Pod 的控制器的角色。这种模式的灵活性更高,但是有时候可能会把事情搞得很复杂,很难入门,并且与 Kubernetes 的集成度很低。

这种模式的实例之一是用 Job 来启动一个运行脚本的 Pod,脚本负责启动 Spark 主控制器(参见 Spark 示例),运行 Spark 驱动,之后完成清理工作。

这种方法的优点之一是整个过程得到了 Job 对象的完成保障,同时维持了对创建哪些 Pod、如何向其分派工作的完全控制能力。

2.6 - 已完成资源的 TTL 控制器

FEATURE STATE: Kubernetes v1.21 [beta]

TTL 控制器提供了一种 TTL 机制来限制已完成执行的资源对象的生命周期。TTL 控制器目前只处理 Job,可能以后会扩展以处理将完成执行的其他资源,例如 Pod 和自定义资源。

此功能目前是 Beta 版而自动启用,并且可以通过 kube-apiserver 和 kube-controller-manager 上的特性门控 TTLAfterFinished 禁用。

TTL 控制器

TTL 控制器现在只支持 Job。集群操作员可以通过指定 Job 的 .spec.ttlSecondsAfterFinished 字段来自动清理已结束的作业(Complete 或 Failed)。

TTL 控制器假设资源能在执行完成后的 TTL 秒内被清理,也就是当 TTL 过期后。当 TTL 控制器清理资源时,它将做级联删除操作,即删除资源对象的同时也删除其依赖对象。注意,当资源被删除时,由该资源的生命周期保证其终结器(Finalizers)等被执行。

可以随时设置 TTL 秒。以下是设置 Job 的 .spec.ttlSecondsAfterFinished 字段的一些示例:

  • 在资源清单(manifest)中指定此字段,以便 Job 在完成后的某个时间被自动清除。
  • 将此字段设置为现有的、已完成的资源,以采用此新功能。
  • 在创建资源时使用 mutating admission webhook 动态设置该字段。集群管理员可以使用它对完成的资源强制执行 TTL 策略。

使用 mutating admission webhook 在资源完成后动态设置该字段,并根据资源状态、标签等选择不同的 TTL 值。

警告

更新 TTL 秒

请注意,在创建资源或已经执行结束后,仍可以修改其 TTL 周期,例如 Job 的 .spec.ttlSecondsAfterFinished 字段。但是一旦 Job 变为可被删除状态(当其 TTL 已过期时),即使您通过 API 增加其 TTL 时长得到了成功的响应,系统也不保证 Job 将被保留。

时间偏差

由于 TTL 控制器使用存储在 Kubernetes 资源中的时间戳来确定 TTL 是否已过期,因此该功能对集群中的时间偏差很敏感,这可能导致 TTL 控制器在错误的时间清理资源对象。

在 Kubernetes 中,需要在所有节点上运行 NTP 以避免时间偏差。时钟并不总是如此正确,但差异应该很小。设置非零 TTL 时请注意避免这种风险。

2.7 - CronJob

FEATURE STATE: Kubernetes v1.21 [stable]

CronJob 创建基于时隔重复调度的 Jobs。

一个 CronJob 对象就像 crontab (cron table) 文件中的一行。它用 Cron 格式进行编写,并周期性地在给定的调度时间执行 Job。

注意:
所有 CronJob 的 schedule: 时间都是基于 kube-controller-manager. 的时区。
如果你的控制平面在 Pod 或是裸容器中运行了 kube-controller-manager,那么为该容器所设置的时区将会决定 Cron Job 的控制器所使用的时区。

注意:
如 v1 CronJob API 所述,官方并不支持设置时区。
Kubernetes 项目官方并不支持设置如 CRON_TZ 或者 TZ 等变量。CRON_TZ 或者 TZ 是用于解析和计算下一个 Job 创建时间所使用的内部库中一个实现细节。不建议在生产集群中使用它。

为 CronJob 资源创建清单时,请确保所提供的名称是一个合法的 DNS 子域名。名称不能超过 52 个字符。这是因为 CronJob 控制器将自动在提供的 Job 名称后附加 11 个字符,并且存在一个限制,即 Job 名称的最大长度不能超过 63 个字符。

CronJob

CronJob 用于执行周期性的动作,例如备份、报告生成等。这些任务中的每一个都应该配置为周期性重复的(例如:每天/每周/每月一次);你可以定义任务开始执行的时间间隔。

示例

下面的 CronJob 示例清单会在每分钟打印出当前时间和问候消息:

apiVersion: batch/v1
kind: CronJob
metadata:
  name: hello
spec:
  schedule: "*/1 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: hello
            image: busybox
            imagePullPolicy: IfNotPresent
            command:
            - /bin/sh
            - -c
            - date; echo Hello from the Kubernetes cluster
          restartPolicy: OnFailure

Cron 时间表语法

# ┌───────────── 分钟 (0 - 59)
# │ ┌───────────── 小时 (0 - 23)
# │ │ ┌───────────── 月的某天 (1 - 31)
# │ │ │ ┌───────────── 月份 (1 - 12)
# │ │ │ │ ┌───────────── 周的某天 (0 - 6) (周日到周一;在某些系统上,7 也是星期日)
# │ │ │ │ │
# │ │ │ │ │
# │ │ │ │ │
# * * * * *
输入
描述
相当于
@yearly (or @annually)
每年 1 月 1 日的午夜运行一次
0 0 1 1 *
@monthly
每月第一天的午夜运行一次
0 0 1 * *
@weekly
每周的周日午夜运行一次
0 0 * * 0
@daily (or @midnight)
每天午夜运行一次
0 0 * * *
@hourly
每小时的开始一次
0 * * * *

例如,下面这行指出必须在每个星期五的午夜以及每个月 13 号的午夜开始任务:

0 0 13 * 5

要生成 CronJob 时间表表达式,你还可以使用 crontab.guru 之类的 Web 工具。

CronJob 限制

CronJob 根据其计划编排,在每次该执行任务的时候大约会创建一个 Job。我们之所以说 "大约",是因为在某些情况下,可能会创建两个 Job,或者不会创建任何 Job。我们试图使这些情况尽量少发生,但不能完全杜绝。因此,Job 应该是幂等的。

如果 startingDeadlineSeconds 设置为很大的数值或未设置(默认),并且 concurrencyPolicy 设置为 Allow,则作业将始终至少运行一次。

注意:
如果 startingDeadlineSeconds 的设置值低于 10 秒钟,CronJob 可能无法被调度。这是因为 CronJob 控制器每 10 秒钟执行一次检查。

对于每个 CronJob,CronJob 控制器(Controller)检查从上一次调度的时间点到现在所错过了调度次数。如果错过的调度次数超过 100 次,那么它就不会启动这个任务,并记录这个错误:

Cannot determine if job needs to be started. Too many missed start time (> 100). Set or decrease .spec.startingDeadlineSeconds or check clock skew.

需要注意的是,如果 startingDeadlineSeconds 字段非空,则控制器会统计从 startingDeadlineSeconds 设置的值到现在而不是从上一个计划时间到现在错过了多少次 Job。例如,如果 startingDeadlineSeconds 是 200,则控制器会统计在过去 200 秒中错过了多少次 Job。

如果未能在调度时间内创建 CronJob,则计为错过。例如,如果 concurrencyPolicy 被设置为 Forbid,并且当前有一个调度仍在运行的情况下,试图调度的 CronJob 将被计算为错过。

例如,假设一个 CronJob 被设置为从 08:30:00 开始每隔一分钟创建一个新的 Job,并且它的 startingDeadlineSeconds 字段未被设置。如果 CronJob 控制器从 08:29:00 到 10:21:00 终止运行,则该 Job 将不会启动,因为其错过的调度次数超过了 100。

为了进一步阐述这个概念,假设将 CronJob 设置为从 08:30:00 开始每隔一分钟创建一个新的 Job,并将其 startingDeadlineSeconds 字段设置为 200 秒。如果 CronJob 控制器恰好在与上一个示例相同的时间段(08:29:00 到 10:21:00)终止运行,则 Job 仍将从 10:22:00 开始。造成这种情况的原因是控制器现在检查在最近 200 秒(即 3 个错过的调度)中发生了多少次错过的 Job 调度,而不是从现在为止的最后一个调度时间开始。

CronJob 仅负责创建与其调度时间相匹配的 Job,而 Job 又负责管理其代表的 Pod。

控制器版本

从 Kubernetes v1.21 版本开始,CronJob 控制器的第二个版本被用作默认实现。要禁用此默认 CronJob 控制器而使用原来的 CronJob 控制器,请在 kube-controller-manager 中设置特性门控 CronJobControllerV2,将此标志设置为 false。例如:

--feature-gates="CronJobControllerV2=false"

2.8 - ReplicationController

说明: 现在推荐使用配置 ReplicaSet 的 Deployment 来建立副本管理机制。

ReplicationController 确保在任何时候都有特定数量的 Pod 副本处于运行状态。换句话说,ReplicationController 确保一个 Pod 或一组同类的 Pod 总是可用的。

ReplicationController 如何工作

当 Pod 数量过多时,ReplicationController 会终止多余的 Pod。当 Pod 数量太少时,ReplicationController 将会启动新的 Pod。与手动创建的 Pod 不同,由 ReplicationController 创建的 Pod 在失败、被删除或被终止时会被自动替换。例如,在中断性维护(如内核升级)之后,你的 Pod 会在节点上重新创建。因此,即使你的应用程序只需要一个 Pod,你也应该使用 ReplicationController 创建 Pod。ReplicationController 类似于进程管理器,但是 ReplicationController 不是监控单个节点上的单个进程,而是监控跨多个节点的多个 Pod。

在讨论中,ReplicationController 通常缩写为 "rc",并作为 kubectl 命令的快捷方式。

一个简单的示例是创建一个 ReplicationController 对象来可靠地无限期地运行 Pod 的一个实例。更复杂的用例是运行一个多副本服务(如 web 服务器)的若干相同副本。

运行一个示例 ReplicationController

这个示例 ReplicationController 配置运行 nginx Web 服务器的三个副本。

kind: ReplicationController
metadata:
  name: nginx
spec:
  replicas: 3
  selector:
    app: nginx
  template:
    metadata:
      name: nginx
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80

通过下载示例文件并运行以下命令来运行示例任务:

kubectl apply -f https://k8s.io/examples/controllers/replication.yaml

输出类似于:

replicationcontroller/nginx created

使用以下命令检查 ReplicationController 的状态:

kubectl describe replicationcontrollers/nginx

输出类似于:

Name:        nginx
Namespace:   default
Selector:    app=nginx
Labels:      app=nginx
Annotations:    <none>
Replicas:    3 current / 3 desired
Pods Status: 0 Running / 3 Waiting / 0 Succeeded / 0 Failed
Pod Template:
  Labels:       app=nginx
  Containers:
   nginx:
    Image:              nginx
    Port:               80/TCP
    Environment:        <none>
    Mounts:             <none>
  Volumes:              <none>
Events:
  FirstSeen       LastSeen     Count    From                        SubobjectPath    Type      Reason              Message
  ---------       --------     -----    ----                        -------------    ----      ------              -------
  20s             20s          1        replication-controller                     Normal    SuccessfulCreate    Created pod: nginx-qrm3m
  20s             20s          1        replication-controller                     Normal    SuccessfulCreate    Created pod: nginx-3ntk0
  20s             20s          1        replication-controller                     Normal    SuccessfulCreate    Created pod: nginx-4ok8v

在这里,创建了三个 Pod,但没有一个 Pod 正在运行,这可能是因为正在拉取镜像。稍后,相同的命令可能会显示:

Pods Status: 3 Running / 0 Waiting / 0 Succeeded / 0 Failed

要以机器可读的形式列出属于 ReplicationController 的所有 Pod,可以使用如下命令:

pods=$(kubectl get pods --selector=app=nginx --output=jsonpath=.items..metadata.name)
echo $pods

输出类似于:

nginx-3ntk0 nginx-4ok8v nginx-qrm3m

这里,选择算符与 ReplicationController 的选择算符相同(参见 kubectl describe 输出),并以不同的形式出现在 replication.yaml 中。--output=jsonpath 选项指定了一个表达式,仅从返回列表中的每个 Pod 中获取名称。

编写一个 ReplicationController 规约

与所有其它 Kubernetes 配置一样,ReplicationController 需要 apiVersion、 kind 和 metadata 字段。有关使用配置文件的常规信息,参考对象管理。

ReplicationController 也需要一个 .spec 部分。

Pod 模板

.spec.template 是 .spec 的唯一必需字段。

.spec.template 是一个 Pod 模板。它的模式与 Pod 完全相同,只是它是嵌套的,没有 apiVersion 或 kind 属性。

除了 Pod 所需的字段外,ReplicationController 中的 Pod 模板必须指定适当的标签和适当的重新启动策略。对于标签,请确保不与其他控制器重叠。参考 Pod 选择算符。

只允许 .spec.template.spec.restartPolicy 等于 Always,如果没有指定,这是默认值。

对于本地容器重启,ReplicationController 委托给节点上的代理,例如 Kubelet 或 Docker。

ReplicationController 上的标签

ReplicationController 本身可以有标签(.metadata.labels)。通常,你可以将这些设置为 .spec.template.metadata.labels;如果没有指定 .metadata.labels 那么它默认为 .spec.template.metadata.labels。

但是,Kubernetes 允许它们是不同的,.metadata.labels 不会影响 ReplicationController 的行为。

Pod 选择算符

.spec.selector 字段是一个标签选择算符。ReplicationController 管理标签与选择算符匹配的所有 Pod。它不区分它创建或删除的 Pod 和其他人或进程创建或删除的 Pod。这允许在不影响正在运行的 Pod 的情况下替换 ReplicationController。

如果指定了 .spec.template.metadata.labels,它必须和 .spec.selector 相同,否则它将被 API 拒绝。如果没有指定 .spec.selector,它将默认为 .spec.template.metadata.labels。

另外,通常不应直接使用另一个 ReplicationController 或另一个控制器(例如 Job)来创建其标签与该选择算符匹配的任何 Pod。如果这样做,ReplicationController 会认为它创建了这些 Pod。Kubernetes 并没有阻止你这样做。

如果你的确创建了多个控制器并且其选择算符之间存在重叠,那么你将不得不自己管理删除操作。

多个副本

你可以通过设置 .spec.replicas 来指定应该同时运行多少个 Pod。在任何时候,处于运行状态的 Pod 个数都可能高于或者低于设定值。例如,副本个数刚刚被增加或减少时,或者一个 Pod 处于优雅终止过程中而其替代副本已经提前开始创建时。

如果你没有指定 .spec.replicas,那么它默认是 1。

使用 ReplicationController

删除一个 ReplicationController 以及它的 Pod

要删除一个 ReplicationController 以及它的 Pod,使用 kubectl delete。kubectl 将 ReplicationController 缩放为 0 并等待以便在删除 ReplicationController 本身之前删除每个 Pod。如果这个 kubectl 命令被中断,可以重新启动它。

当使用 REST API 或 Go 客户端库时,你需要明确地执行这些步骤(缩放副本为 0、等待 Pod 删除,之后删除 ReplicationController 资源)。

只删除 ReplicationController

你可以删除一个 ReplicationController 而不影响它的任何 Pod。

使用 kubectl,为 kubectl delete 指定 --cascade=false 选项。

当使用 REST API 或 Go 客户端库时,只需删除 ReplicationController 对象。

一旦原始对象被删除,你可以创建一个新的 ReplicationController 来替换它。只要新的和旧的 .spec.selector 相同,那么新的控制器将领养旧的 Pod。但是,它不会做出任何努力使现有的 Pod 匹配新的、不同的 Pod 模板。如果希望以受控方式更新 Pod 以使用新的 spec,请执行滚动更新操作。

从 ReplicationController 中隔离 Pod

通过更改 Pod 的标签,可以从 ReplicationController 的目标中删除 Pod。此技术可用于从服务中删除 Pod 以进行调试、数据恢复等。以这种方式删除的 Pod 将被自动替换(假设复制副本的数量也没有更改)。

常见的使用模式

重新调度

如上所述,无论你想要继续运行 1 个 Pod 还是 1000 个 Pod,一个 ReplicationController 都将确保存在指定数量的 Pod,即使在节点故障或 Pod 终止(例如,由于另一个控制代理的操作)的情况下也是如此。

扩缩容

通过设置 replicas 字段,ReplicationController 可以允许扩容或缩容副本的数量。你可以手动或通过自动缩放控制代理来控制 ReplicationController 执行此操作。

滚动更新

ReplicationController 的设计目的是通过逐个替换 Pod 以方便滚动更新服务。

如 #1353 PR 中所述,建议的方法是使用 1 个副本创建一个新的 ReplicationController,逐个扩容新的(+1)和缩容旧的(-1)控制器,然后在旧的控制器达到 0 个副本后将其删除。这一方法能够实现可控的 Pod 集合更新,即使存在意外失效的状况。

理想情况下,滚动更新控制器将考虑应用程序的就绪情况,并确保在任何给定时间都有足够数量的 Pod 有效地提供服务。

这两个 ReplicationController 将需要创建至少具有一个不同标签的 Pod,比如 Pod 主要容器的镜像标签,因为通常是镜像更新触发滚动更新。

滚动更新是在客户端工具 kubectl rolling-update 中实现的。访问 kubectl rolling-update 任务以获得更多的具体示例。

多个版本跟踪

除了在滚动更新过程中运行应用程序的多个版本之外,通常还会使用多个版本跟踪来长时间,甚至持续运行多个版本。这些跟踪将根据标签加以区分。

例如,一个服务可能把具有 tier in (frontend), environment in (prod) 的所有 Pod 作为目标。现在假设你有 10 个副本的 Pod 组成了这个层。但是你希望能够 canary (金丝雀)发布这个组件的新版本。你可以为大部分副本设置一个 ReplicationController,其中 replicas 设置为 9,标签为 tier=frontend, environment=prod, track=stable 而为 canary 设置另一个 ReplicationController,其中 replicas 设置为 1,标签为 tier=frontend, environment=prod, track=canary。现在这个服务覆盖了 canary 和非 canary Pod。但你可以单独处理 ReplicationController,以测试、监控结果等。

和服务一起使用 ReplicationController

多个 ReplicationController 可以位于一个服务的后面,例如,一部分流量流向旧版本,一部分流量流向新版本。

一个 ReplicationController 永远不会自行终止,但它不会像服务那样长时间存活。服务可以由多个 ReplicationController 控制的 Pod 组成,并且在服务的生命周期内(例如,为了执行 Pod 更新而运行服务),可以创建和销毁许多 ReplicationController。服务本身和它们的客户端都应该忽略负责维护服务 Pod 的 ReplicationController 的存在。

编写多副本的应用

由 ReplicationController 创建的 Pod 是可替换的,语义上是相同的,尽管随着时间的推移,它们的配置可能会变得异构。这显然适合于多副本的无状态服务器,但是 ReplicationController 也可以用于维护主选、分片和工作池应用程序的可用性。这样的应用程序应该使用动态的工作分配机制,例如 RabbitMQ 工作队列,而不是静态的或者一次性定制每个 Pod 的配置,这被认为是一种反模式。执行的任何 Pod 定制,例如资源的垂直自动调整大小(例如,CPU 或内存),都应该由另一个在线控制器进程执行,这与 ReplicationController 本身没什么不同。

ReplicationController 的职责

ReplicationController 仅确保所需的 Pod 数量与其标签选择算符匹配,并且是可操作的。目前,它的计数中只排除终止的 Pod。未来,可能会考虑系统提供的就绪状态和其他信息,我们可能会对替换策略添加更多控制,我们计划发出事件,这些事件可以被外部客户端用来实现任意复杂的替换或缩减策略。

ReplicationController 永远被限制在这个狭隘的职责范围内。它本身既不执行就绪态探测,也不执行活跃性探测。它不负责执行自动缩放,而是由外部自动缩放器控制,后者负责更改其 replicas 字段值。我们不会向 ReplicationController 添加调度策略(例如, spreading)。它也不应该验证所控制的 Pod 是否与当前指定的模板匹配,因为这会阻碍自动调整大小和其他自动化过程。类似地,完成期限、整理依赖关系、配置扩展和其他特性也属于其他地方。我们甚至计划考虑批量创建 Pod 的机制。

ReplicationController 旨在成为可组合的构建基元。我们希望在它和其他补充原语的基础上构建更高级别的 API 或者工具,以便于将来的用户使用。kubectl 目前支持的 "macro" 操作(运行、缩放、滚动更新)就是这方面的概念示例。例如,我们可以想象类似于 Asgard 的东西管理 ReplicationController、自动定标器、服务、调度策略、金丝雀发布等。

API 对象

在 Kubernetes REST API 中 Replication controller 是顶级资源。更多关于 API 对象的详细信息可以在 ReplicationController API 对象找到。

ReplicationController 的替代方案

ReplicaSet

ReplicaSet 是下一代 ReplicationController,支持新的基于集合的标签选择算符。它主要被 Deployment 用来作为一种编排 Pod 创建、删除及更新的机制。请注意,我们推荐使用 Deployment 而不是直接使用 ReplicaSet,除非你需要自定义更新编排或根本不需要更新。

Deployment(推荐)

Deployment 是一种更高级别的 API 对象,它以类似于 kubectl rolling-update 的方式更新其底层 ReplicaSet 及其 Pod。如果你想要这种滚动更新功能,那么推荐使用 Deployment,因为与 kubectl rolling-update 不同,它们是声明式的、服务端的,并且具有其它特性。

裸 Pod

与用户直接创建 Pod 的情况不同,ReplicationController 能够替换因某些原因被删除或被终止的 Pod ,例如在节点故障或中断节点维护的情况下,例如内核升级。因此,我们建议你使用 ReplicationController,即使你的应用程序只需要一个 Pod。可以将其看作类似于进程管理器,它只管理跨多个节点的多个 Pod ,而不是单个节点上的单个进程。ReplicationController 将本地容器重启委托给节点上的某个代理(例如,Kubelet 或 Docker)。

Job

对于预期会自行终止的 Pod (即批处理任务),使用 Job 而不是 ReplicationController。

以上是关于[K8s]Kubernetes-工作负载(下)的主要内容,如果未能解决你的问题,请参考以下文章

云原生 | Kubernetes篇Kubernetes(k8s)工作负载

[K8s]Kubernetes-工作负载(中)

K8S实战系列:2-Pod工作负载与服务

云原生之kubernetes实战在k8s集群下部署ingress对外访问服务

如何跨不同版本K8S,为有状态工作负载做蓝绿部署

Kubernetes(k8s)之k8s高可用负载均衡集群(haproxy+pacemaker实现负载均衡+高可用)