k8s学习-工作负载2

网友投稿 260 2022-09-10

k8s学习-工作负载2

控制器-StatefuSet

StatefulSet 顾名思义,用于管理 Stateful(有状态)的应用程序。

StatefulSet 管理 Pod 时,确保其 Pod 有一个按顺序增长的 ID。

与 Deployment 相似,StatefulSet 基于一个 Pod 模板管理其 Pod。与 Deployment 最大的不同在于 StatefulSet 始终将一系列不变的名字分配给其 Pod。这些 Pod 从同一个模板创建,但是并不能相互替换:每个 Pod 都对应一个特有的持久化存储标识。

同其他所有控制器一样,StatefulSet 也使用相同的模式运作:用户在 StatefulSet 中定义自己期望的结果,StatefulSet 控制器执行需要的操作,以使得该结果被达成。

StatefulSet使用场景

稳定、唯一的网络标识(dnsname)每个Pod始终对应各自的存储路径(PersistantVolumeClaimTemplate)按顺序地增加副本、减少副本,并在减少副本时执行清理按顺序自动地执行滚动更新

如果一个应用程序不需要稳定的网络标识,或者不需要按顺序部署、删除、增加副本,应该考虑使用 Deployment 这类无状态(stateless)的控制器。

StatefulSet 的限制

Pod 的存储要么由 storage class 对应的PersistentVolume Provisioner 提供,要么由集群管理员事先创建删除或 scale down 一个 StatefulSet 将不会删除其对应的数据卷。这样做的考虑是数据安全删除 StatefulSet 时,将无法保证 Pod 的终止是正常的。如果要按顺序 gracefully 终止 StatefulSet 中的 Pod,可以在删除 StatefulSet 前将其 scale down 到 0当使用默认的Pod Management Policy (OrderedReady) 进行滚动更新时,可能进入一个错误状态,并需要人工介入才能修复

创建StatefulSet

下面是一个 StatefulSet 的例子,由如下内容组成:

一个名为 nginx 的Headless Service,用于控制网络域一个名为 web 的StatefulSet,副本数为 3volumeClaimTemplates 提供稳定的存储(每一个 Pod ID 对应自己的存储卷,且 Pod 重建后,仍然能找到对应的存储卷)

apiVersion: v1kind: Servicemetadata: name: nginx labels: app: nginxspec: ports: - port: 80 name: web clusterIP: None selector: app: nginx---apiVersion: apps/v1kind: StatefulSetmetadata: name: webspec: selector: matchLabels: app: nginx # has to match .spec.template.metadata.labels serviceName: "nginx" replicas: 3 # by default is 1 template: metadata: labels: app: nginx # has to match .spec.selector.matchLabels spec: terminationGracePeriodSeconds: 10 containers: - name: nginx image: nginx:1.7.9 ports: - containerPort: 80 name: web volumeMounts: - name: mountPath: /usr/share/nginx/html volumeClaimTemplates: - metadata: name: spec: accessModes: [ "ReadWriteOnce" ] storageClassName: "my-storage-class" resources: requests: storage: 1Gi

Pod的标识

StatefulSet 中的 Pod 具备一个唯一标识,该标识由以下几部分组成:

序号稳定的网络标识稳定的存储

该标识始终与 Pod 绑定,无论该 Pod 被调度(重新调度)到哪一个节点上。

序号

假设一个 StatefulSet 的副本数为 N,其中的每一个 Pod 都会被分配一个序号,序号的取值范围从 0 到 N - 1,并且该序号在 StatefulSet 内部是唯一的。

稳定的网络 ID

StatefulSet 中 Pod 的 hostname 格式为 $(StatefulSet name)-$(Pod 序号)。上面的例子将要创建三个 Pod,其名称分别为: web-0,web-1,web-2。StatefulSet 可以使用 Headless Service 来控制其 Pod 所在的域。该域(domain)的格式为 $(service name).$(namespace).svc.cluster.local,其中 “cluster.local” 是集群的域。StatefulSet 中每一个 Pod 将被分配一个 dnsName,格式为: $(podName).$(所在域名)

下表列出了不同的 集群域、Service name、StatefulSet name 的情况下,对应的 StatefulSet 中 Pod 的 DNS 名字:

字段名

组合一

组合二

组合三

集群域 Cluster Domain

cluster.local

cluster.local

kube.local

Service name

default/nginx

foo/nginx

foo/nginx

StatefulSet name

default/web

foo/web

foo/web

StatefulSet Domain

nginx.default.svc.cluster.local

nginx.foo.svc.cluster.local

nginx.foo.svc.kube.local

Pod DNS

web-{0..N-1}.nginx.default.svc.cluster.local

web-{0..N-1}.nginx.foo.svc.cluster.local

web-{0..N-1}.nginx.foo.svc.kube.local

Pod name

web-{0..N-1}

web-{0..N-1}

web-{0..N-1}

稳定的存储

Pod name标签

当 StatefulSet 控制器创建一个 Pod 时,会为 Pod 添加一个标签(label) statefulset.kubernetes.io/pod-name 且该标签的值为 Pod 的名字。您可以利用此名字,为 StatefulSet 中的某一个特定的 Pod 关联一个 Service。

StatefulSet 的部署和伸缩

部署和伸缩 StatefulSet 时的执行顺序

在创建一个副本数为 N 的 StatefulSet 时,其 Pod 将被按 {0 ... N-1} 的顺序逐个创建在删除一个副本数为 N 的 StatefulSet (或其中所有的 Pod)时,其 Pod 将按照相反的顺序(即 {N-1 ... 0})终止和删除在对 StatefulSet 执行扩容(scale up)操作时,新增 Pod 所有的前序 Pod 必须处于 Running(运行)和 Ready(就绪)的状态终止和删除 StatefulSet 中的某一个 Pod 时,该 Pod 所有的后序 Pod 必须全部已终止

创建 StatefulSet 例子中的 nginx StatefulSet 被创建时:

Pod web-0、web-1、web-2 将被按顺序部署web-0 处于 Running 和 Ready 状态之前,web-1 不会创建;web-1 处于 Running 和 Ready 状态之前,web-2 不会创建如果 web-1 已处于 Running 和 Ready 的状态,web-2 尚未创建,此时 web-0 发生了故障,则在 web-0 成功重启并达到 Running 和 Ready 的状态之前,web-2 不会创建如果用户对这个 StatefulSet 执行缩容(scale down)操作,将其副本数调整为 1,则:

web-2 将被首先终止;在 web-2 已终止并删除之后,才开始终止 web-1假设在 web-2 终止并删除之后,web-1 终止之前,此时 web-0 出现故障,则,在 web-0 重新回到 Running 和 Ready 的状态之前,kubernetes 将不会终止 web-1

StatefulSet 的更新策略

在 Kubernetes 1.7 及之后的版本中,可以为 StatefulSet 设定 .spec.updateStrategy 字段,以便您可以在改变 StatefulSet 中 Pod 的某些字段时(container/labels/resource request/resource limit/annotation等)禁用滚动更新。

On Delete

OnDelete 策略实现了 StatefulSet 的遗留版本(kuberentes 1.6及以前的版本)的行为。如果 StatefulSet 的 .spec.updateStrategy.type 字段被设置为 OnDelete,当您修改 .spec.template 的内容时,StatefulSet Controller 将不会自动更新其 Pod。您必须手工删除 Pod,此时 StatefulSet Controller 在重新创建 Pod 时,使用修改过的 .spec.template 的内容创建新 Pod。

Rolling Updates

.spec.updateStrategy.type​ 字段的默认值是 RollingUpdate,该策略为 StatefulSet 实现了 Pod 的自动滚动更新。在用户更新 StatefulSet 的 .spec.tempalte 字段时,StatefulSet Controller 将自动地删除并重建 StatefulSet 中的每一个 Pod。处理顺序如下:

从序号最大的 Pod 开始,逐个删除和更新每一个 Pod,直到序号最小的 Pod 被更新当正在更新的 Pod 达到了 Running 和 Ready 的状态之后,才继续更新其前序 PodPartitions

通过指定.spec.updateStrategy.rollingUpdate.partition 字段,可以分片(partitioned)执行RollingUpdate 更新策略。当更新 StatefulSet 的 .spec.template 时:

序号大于或等于.spec.updateStrategy.rollingUpdate.partition 的 Pod 将被删除重建序号小于.spec.updateStrategy.rollingUpdate.partition​ 的 Pod 将不会更新,及时手工删除该 Pod,kubernetes 也会使用前一个版本的.spec.template 重建该 Pod如果.spec.updateStrategy.rollingUpdate.partition​ 大于.spec.replicas​,更新.spec.tempalte 将不会影响到任何 Pod

Forced Rollback

​当使用默认的 Pod 管理策略时(OrderedReady),很有可能会进入到一种卡住的状态,需要人工干预才能修复。

如果您更新 Pod template 后,该 Pod 始终不能进入 Running 和 Ready 的状态(例如,镜像错误或应用程序配置错误),StatefulSet 将停止滚动更新并一直等待。

此时,如果您仅仅将 Pod template 回退到一个正确的配置仍然是不够的。由于一个已知的问题,StatefulSet 将继续等待出错的 Pod 进入就绪状态(该状态将永远无法出现),才尝试将该 Pod 回退到正确的配置。

在修复 Pod template 以后,您还必须删除掉所有已经尝试使用有问题的 Pod template 的 Pod。StatefulSet此时才会开始使用修复了的 Pod template 重建 Pod。

控制器-DaemonSet

DaemonSet 控制器确保所有(或一部分)的节点都运行了一个指定的 Pod 副本。

每当向集群中添加一个节点时,指定的 Pod 副本也将添加到该节点上当节点从集群中移除时,Pod 也就被垃圾回收了删除一个 DaemonSet 可以清理所有由其创建的 Pod

DaemonSet 的典型使用场景有:

在每个节点上运行集群的存储守护进程,例如 glusterd、ceph在每个节点上运行日志收集守护进程,例如 fluentd、logstash在每个节点上运行监控守护进程,例如Prometheus Node Exporter (opens new window)、Sysdig Agent (opens new window)、collectd、Dynatrace OneAgent (opens new window)、APPDynamics Agent (opens new window)、Datadog agent (opens new window)、New Relic agent (opens new window)、Ganglia gmond、Instana Agent (opens new window)等

通常情况下,一个 DaemonSet 将覆盖所有的节点。复杂一点儿的用法,可能会为某一类守护进程设置多个 DaemonSets,每一个 DaemonSet 针对不同类硬件类型设定不同的内存、cpu请求。

创建 DaemonSet

下面是 DaemonSet 的 YAML 文件示例 daemonset.yaml。该例子中的 DaemonSet 运行了一个 fluentd-elasticsearch 的 docker 镜像:

apiVersion: apps/v1kind: DaemonSetmetadata: name: fluentd-elasticsearch namespace: kube-system labels: k8s-app: fluentd-loggingspec: selector: matchLabels: name: fluentd-elasticsearch template: metadata: labels: name: fluentd-elasticsearch spec: tolerations: - key: node-role.kubernetes.io/master effect: NoSchedule containers: - name: fluentd-elasticsearch image: fluent/fluentd-kubernetes-daemonset:v1.7.1-debian-syslog-1.0 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

必填字段

与其他所有 Kubernetes API 对象相同,DaemonSet 需要如下字段:

apiVersionkindmetadata

除此之外,DaemonSet 还需要 .spec 字段

Pod Template

.spec.template 是必填字段,定义了 Pod 的模板,与定义 Pod 的 yaml 格式完全相同(除了内嵌在 DaemonSet 中以外,没有 kind、APIVersion 字段以外)。

在 DaemonSet 中,您必须指定 .spec.template.metadata.labels 字段和 .spec.tempalte.spec 字段。

DaemonSet 的 .spec.template.spec.restartPolicy 字段必须为 Always,或者不填(默认值为 Always)

Pod Selector

.spec.selector 字段定义了 DaemonSet 的 pod selector,DaemonSet 认为符合该选择器的 Pod 由其管理。

自 Kubernets v1.8 以后,.spec.selector 是必填字段,且您指定该字段时,必须与 .spec.template.metata.labels 字段匹配(不匹配的情况下创建 DaemonSet 将失败)。DaemonSet 创建以后,.spec.selector 字段就不可再修改。如果修改,可能导致不可预见的结果。

spec.selector 由两个字段组成:

matchLabelsmatchExpressions通过指定 key、value列表以及运算符,可以构造更复杂的选择器

如果两个字段同时存在,则必须同时满足两个条件的 Pod 才被选中。

任何情况下,您不能以任何方式创建符合 DaemonSet 的 .spec.selector 选择器的 Pod。否则 DaemonSet Controller 会认为这些 Pod 是由它创建的。这将导致不可预期的行为出现。

DaemonSet如何调度

由DaemonSet控制器调度

v1.12以后默认禁用

通常,Kubernetes Scheduler(调度器)决定了 Pod 在哪个节点上运行。然而 DaemonSet Controller 创建的 Pod 已经指定了 .spec.nodeName 字段,因此:

Node 节点的unschedulable (opens new window)字段将被 DaemonSet Controller 忽略DaemonSet Controller 可以在 kubernetes scheduler 启动之前创建 Pod,这个特性在引导集群启动的时候非常有用(集群使用者无需关心)

由默认调度器调度

v1.12以后默认启用

DaemonSet 确保所以符合条件的节点运行了一个指定的 Pod。通常,Kubernetes Scheduler 决定 Pod 在哪个节点上运行。然而如果 DaemonSet 的 Pod 由 DaemonSet Controller 创建和调度,会引发如下问题:

Pod 的行为不一致:普通的 Pod 在创建后处于Pending状态,并等待被调度,但是 DaemonSet Pod 创建后,初始状态不是Pending。这会使用户感到困惑Pod 的优先权(preemption) (opens new window)由 kubernetes 调度器处理。如果 Pod 优先权被启用,DaemonSet Controller 在创建和调度 Pod 时,不会考虑 Pod 的优先权

Kubernetes v1.12 版本以后,默认通过 kubernetes 调度器来调度 DaemonSet 的 Pod。DaemonSet Controller 将会向 DaemonSet 的 Pod 添加 .spec.nodeAffinity​ 字段,而不是 .spec.nodeName​ 字段,并进一步由 kubernetes 调度器将 Pod 绑定到目标节点。如果 DaemonSet 的 Pod 已经存在了 nodeAffinity 字段,该字段的值将被替换

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

此外, node.kubernetes.io/unschedulable:NoSchedule​ 容忍(toleration)将被自动添加到 DaemonSet 的 Pod 中。由此,默认调度器在调度 DaemonSet 的 Pod 时可以忽略节点的 unschedulable 属性

污点和容忍

在调度 DaemonSet 的 Pod 时,污点和容忍(taints and tolerations)会被考量到,同时,以下容忍(toleration)将被自动添加到 DaemonSet 的 Pod 中:

Toleration Key

Effect

Version

描述

node.kubernetes.io/not-ready

NoExecute

1.13+

节点出现问题时(例如网络故障),DaemonSet 容器组将不会从节点上驱逐

node.kubernetes.io/unreachable

NoExecute

1.13+

节点出现问题时(例如网络故障),DaemonSet 容器组将不会从节点上驱逐

node.kubernetes.io/disk-pressure

NoSchedule

1.8+

node.kubernetes.io/memory-pressure

NoSchedule

1.8+

node.kubernetes.io/unschedulable

NoSchedule

1.12+

默认调度器针对 DaemonSet 容器组,容忍节点的 unschedulable属性

node.kubernetes.io/network-unavailable

NoSchedule

1.12+

默认调度器针对 DaemonSet 容器组,在其使用 host network 时,容忍节点的 network-unavailable 属性

DaemonSet通信

与 DaemonSet 容器组通信的模式有:

Push:DaemonSet 容器组用来向另一个服务推送信息,例如数据库的统计信息。这种情况下 DaemonSet 容器组没有客户端NodeIP + Port:DaemonSet 容器组可以使用hostPort,此时可通过节点的 IP 地址直接访问该容器组。客户端需要知道节点的 IP 地址,以及 DaemonSet 容器组的 端口号DNS:创建一个headless service (opens new window),且该 Service 与 DaemonSet 有相同的 Pod Selector。此时,客户端可通过该 Service 的 DNS 解析到 DaemonSet 的 IP 地址Service:创建一个 Service,且该 Service 与 DaemonSet 有相同的 Pod Selector,客户端通过该 Service,可随机访问到某一个节点上的 DaemonSet 容器组

更新DaemonSet

在改变节点的标签时:

如果该节点匹配了 DaemonSet 的.spec.template.spec.nodeSelector,DaemonSet 将会在该节点上创建一个 Pod如果该节点原来匹配 DaemonSet 的.spec.template.spec.nodeSelector,现在不匹配了,则,DaemonSet 将会删除该节点上对应的 Pod

您可以修改 DaemonSet 的 Pod 的部分字段,但是,DaemonSet 控制器在创建新的 Pod 时,仍然会使用原有的 Template 进行 Pod 创建。您可以删除 DaemonSet。如果在kubectl 命令中指定 --cascade=false 选项,DaemonSet 容器组将不会被删除。紧接着,如果您创建一个新的 DaemonSet,与之前删除的 DaemonSet 有相同的 .spec.selector,新建 DaemonSet 将直接把这些未删除的 Pod 纳入管理。DaemonSet 根据其 updateStrategy 决定是否更新这些 Pod

Deamon的替代选项

Init Scripts

您可以通过脚本(例如,​​init​​、​​upstartd​​、​​systemd​​)直接在节点上启动一个守护进程。相对而言,DaemonSet 在处理守护进程时,有如下优势:

使用与应用程序相同的方式处理守护进程的日志和监控使用与应用程序相同的配置语言和工具(例如:Pod template、kubectl)处理守护进程在容器中运行守护进程,可为守护进程增加 resource limits 等限定

Pods

您可以直接创建 Pod,并指定其在某一个节点上运行。相对而言,使用 DaemonSet 可获得如下优势:

Pod 终止后,DaemonSet 可以立刻新建 Pod 以顶替已终止的 Pod。Pod 终止的原因可能是:

节点故障节点停机维护

Deployment

DaemonSet 和 Deployment 一样,他们都创建长时间运行的 Pod(例如 web server、storage server 等)

Deployment 适用于无状态服务(例如前端程序),对于这些程序而言,扩容(scale up)/ 缩容(scale down)、滚动更新等特性比精确控制 Pod 所运行的节点更重要。DaemonSet 更适合如下情况:

Pod 的副本总是在所有(或者部分指定的)节点上运行需要在其他 Pod 启动之前运行

控制器job

Kubernetes中的 Job 对象将创建一个或多个 Pod,并确保指定数量的 Pod 可以成功执行到进程正常结束:

当 Job 创建的 Pod 执行成功并正常结束时,Job 将记录成功结束的 Pod 数量当成功结束的 Pod 达到指定的数量时,Job 将完成执行删除 Job 对象时,将清理掉由 Job 创建的 Pod

一个简单的例子是:创建一个 Job 对象用来确保一个 Pod 的成功执行并结束。在第一个 Pod 执行失败或者被删除(例如,节点硬件故障或机器重启)的情况下,该 Job 对象将创建一个新的 Pod 以重新执行。

当然,您也可以使用 Job 对象并行执行多个 Pod。

编写job的定义

与所有的 Kubernetes 对象一样,Job 对象的 YAML 文件中,都需要包括如下三个字段:

.apiVersion.kind.metadata

Job 对象的 YAML 文件,还需要一个 .spec 字段。

Pod Template

.spec.template 是必填字段:

用于定义pod template与 Pod 有相同的字段内容,但由于是内嵌元素,pod template 不包括阿apiVersion 字段和kind 字段除了 Pod 所需的必填字段之外,Job 中的 pod template 必须指定

合适的标签.spec.template.spec.labels,参考Pod Selector指定合适的重启策略 restartPolicy.spec.template.spec.restartPolicy,此处只允许使用Never 和OnFailure 两个取值

Pod Selector

.spec.selector 字段是可选的。绝大部分情况下,您不需要指定该字段。只有在少数情况下,您才需要这样做,请参考 Job 的特殊操作

Parallel Jobs

有三种主要的任务类型适合使用 Job 运行:

Non-parallel Jobs

通常,只启动一个 Pod,除非该 Pod 执行失败Pod 执行成功并结束以后,Job 也立刻进入完成 completed 状态

Parallel Jobs with a fixed completion count

.spec.completions 为一个非零正整数Job 将创建至少.spec.completions​ 个 Pod,编号为 1 -.spec.completionsJob 记录了任务的整体执行情况,当 1 -.spec.completions 中每一个编号都有一个对应的 Pod 执行成功时,Job 进入完成状态

Parallel Jobs with a work queue

不指定.spec.completions,使用.spec.parallelismPod 之间必须相互之间自行协调并发,或者使用一个外部服务决定每个 Pod 各自执行哪些任务。例如,某个Pod可能从带工作队列(work queue)中取出最多N个条目的批次数据每个 Pod 都可以独立判断其他同僚(peers)是否完成,并确定整个Job是否完成当 Job 中任何一个 Pod 成功结束,将不再为其创建新的 Pod当所有的 Pod 都结束了,且至少有一个 Pod 执行成功后才结束,则 Job 判定为成功结束一旦任何一个 Pod 执行成功并退出,Job 中的任何其他 Pod 都应停止工作和输出信息,并开始终止该 Pod 的进程

completions 和 parallelism

对于 non-parallel Job,.spec.completions 和.spec.parallelism 可以不填写,默认值都为 1对于 fixed completion count Job,需要设置.spec.completions 为您期望的个数;同时不设置.spec.parallelism 字段(默认值为 1)对于 work queue Job,不能设置.spec.completions 字段,且必须设置.spec.parallelism 为0或任何正整数

Controlling Parallelism 并发控制

并发数 .spec.parallelism 可以被设置为0或者任何正整数,如果不设置,默认为1,如果设置为 0,则 Job 被暂停,直到该数字被调整为一个正整数。

实际的并发数(同一时刻正在运行的 Pod 数量)可能比设定的并发数 .spec.parallelism 要大一些或小一些,不一定严格相等,主要的原因有:

对于 fixed completion count Job,实际并发运行的 Pod 数量不会超过剩余未完成的数量。如果.spec.parallelism 比这个数字更大,将被忽略对于 work queue Job,任何一个 Pod 成功执行后,将不再创建新的 Pod (剩余的 Pod 将继续执行)Job 控制器可能没有足够的时间处理并发控制如果 Job 控制器创建 Pod 失败(例如,ResourceQuota 不够用,没有足够的权限等)同一个Job中,在已创建的 Pod 出现大量失败的情况下,Job 控制器可能限制 Pod 的创建当 Pod 被优雅地关闭时(gracefully shut down),需要等候一段时间才能结束

处理Pod和容器失败

Pod 中的容器可能会因为多种原因执行失败,例如:

容器中的进程退出了,且退出码(exit code)不为 0容器因为超出内存限制而被 Kill其他原因

如果 Pod 中的容器执行失败,且 .spec.template.spec.restartPolicy = "OnFailure"​,则 Pod 将停留在该节点上,但是容器将被重新执行。此时,您的应用程序需要处理在原节点(失败之前的节点)上重启的情况。或者,您也可以设置为 .spec.template.spec.restartPolicy = "Never"。

整个 Pod 也可能因为多种原因执行失败,例如:

Pod 从节点上被驱逐(节点升级、重启、被删除等)Pod 的容器执行失败,且.spec.template.spec.restartPolicy = "Never"

当 Pod 执行失败时,Job 控制器将创建一个新的 Pod。此时,您的应用程序需要处理在一个新 Pod 中重新启动的情况。具体来说,需要处理临时文件、锁、未完成的输出信息以及前一次执行可能遗留下来的其他东西

失败重试

Pod backoff failure policy

某些情况下(例如,配置错误),您可能期望在 Job 多次重试仍然失败的情况下停止该 Job。此时,可通过 .spec.backoffLimit 来设定 Job 最大的重试次数。该字段的默认值为 6.

Job 中的 Pod 执行失败之后,Job 控制器将按照一个指数增大的时间延迟(10s,20s,40s ... 最大为 6 分钟)来多次重新创建 Pod。如果没有新的 Pod 执行失败,则重试次数的计数将被重置。

job的终止和清理

当 Job 完成后:

将不会创建新的 Pod已经创建的 Pod 也不会被清理掉。此时,您仍然可以继续查看已结束 Pod 的日志,以检查 errors/warnings 或者其他诊断用的日志输出Job 对象也仍然保留着,以便您可以查看该 Job 的状态由用户决定是否删除已完成的 Job 及其 Pod

可通过kubectl 命令删除 Job,例如:kubectl delete jobs/pi 或者kubectl delete -f Job 对象时,由该 Job 创建的 Pod 也将一并被删除

Job 通常会顺利的执行下去,但是在如下情况可能会非正常终止:

某一个 Pod 执行失败(且restartPolicy=Never)或者某个容器执行出错(且restartPolicy=OnFailure)

此时,Job 按照处理Pod和容器的失败 中.spec.bakcoffLimit 描述的方式进行处理一旦重试次数达到了.spec.backoffLimit 中的值,Job 将被标记为失败,且尤其创建的所有 Pod 将被终止

Job 中设置了.spec.activeDeadlineSeconds。该字段限定了 Job 对象在集群中的存活时长,一旦达到.spec.activeDeadlineSeconds 指定的时长,该 Job 创建的所有的 Pod 都将被终止,Job 的 Status 将变为type:Failed 、reason: DeadlineExceeded

Job的自动清理

系统中已经完成的 Job 通常是不在需要里的,长期在系统中保留这些对象,将给 apiserver 带来很大的压力。如果通过更高级别的控制器(例如 CronJobs)来管理 Job,则 CronJob 可以根据其中定义的基于容量的清理策略(capacity-based cleanup policy)自动清理Job

TTL 机制

除了 CronJob 之外,TTL 机制是另外一种自动清理已结束Job(Completed​ 或 Finished)的方式:

TTL 机制由TTL 控制器 提供在 Job 对象中指定.spec.ttlSecondsAfterFinished 字段可激活该特性

当 TTL 控制器清理 Job 时,TTL 控制器将删除 Job 对象,以及由该 Job 创建的所有 Pod 对象。

例子

apiVersion: batch/v1kind: Jobmetadata: name: pi-with-ttlspec: ttlSecondsAfterFinished: 100 template: spec: containers: - name: pi image: perl command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"] restartPolicy: Never

​字段解释 ttlSecondsAfterFinished:

Jobpi-with-ttl​ 的ttlSecondsAfterFinished​ 值为 100,则,在其结束100 秒之后,将可以被自动删除如果ttlSecondsAfterFinished​ 被设置为0,则 TTL 控制器在 Job 执行结束后,立刻就可以清理该 Job 及其 Pod如果ttlSecondsAfterFinished 值未设置,则 TTL 控制器不会清理该 Job

Job的模式

Kubernetes Job 对象可以用来支持 Pod 的并发执行,但是:

Job 对象并非设计为支持需要紧密相互通信的Pod的并发执行,例如科学计算Job 对象支持并发处理一系列相互独立但是又相互关联的工作任务,例如:

发送邮件渲染页面转码文件扫描 NoSQL 数据库中的主键其他

在一个复杂的系统中,可能存在多种类型的工作任务,本文只考虑批处理任务(batch job)。

对于批处理任务的并行计算,存在着几种模式,它们各自有自己的优缺点:

每个工作任务一个 Job 对象 v.s. 一个 Job 对象负责所有的工作任务

当工作任务特别多时,第二种选择(一个 Job 对象负责所有的工作任务)更合适一些第一种选择(每个工作任务一个 Job 对象)将为管理员和系统带来很大的额外开销,因为要管理很多数量的 Job 对象

Pod的数量与工作任务的数量相等 v.s. 每个Pod可以处理多个工作任务

第一种选择(Pod的数量与工作任务的数量相等)通常只需要对现有的代码或容器做少量的修改第二种选择(每个Pod可以处理多个工作任务)更适合工作任务的数量特别多的情况,相较于第一种选择可以降低系统开销

使用工作队列,此时:

需要运行一个队列服务需要对已有的程序或者容器做修改,以便其可以配合队列工作如果是一个已有的程序,改造时可能存在难度

Job的特殊操作

在创建 Job 时,系统默认将为其指定一个 .spec.selector 的取值,并确保其不会与任何其他 Job 重叠。

在少数情况下,您仍然可能需要覆盖这个自动设置 .spec.selector 的取值。在做此项操作时,您必须十分小心:

如果指定的.spec.selector 不能确定性的标识出该 Job 的 Pod,并可能选中无关的 Pod (例如,selector 可能选中其他控制器创建的 Pod),则:

与该 Job 不相关的 Pod 可能被删除该 Job 可能将不相关的 Pod 纳入到.spec.completions 的计数一个或多个 Job 可能不能够创建 Pod,或者不能够正常结束

如果指定的.spec.selector 不是唯一的,则其他控制器(例如,Deployment、StatefulSet 等)及其 Pod 的行为也将难以预测

在 Kubernetes 中,系统并不能帮助你避免此类型的错误,您需要自己关注所指定的 .spec.selector 的取值是否合理。

让我们来看一个实际使用 .spec.selector 的例子:假设 Job old 已经运行,您希望已经创建的 Pod 继续运行,但是您又想要修改该 Job 的 名字,同时想要使该 Job 新建的 Pod 使用新的 template。此时您不能够修改已有的 Job 对象,因为这些字段都是不可修改的。此时,您可以执行命令 kubectl delete jobs/old --cascade=false,以删除 Job old 但是保留其创建的 Pod。

CronJob

CronJob 按照预定的时间计划(schedule)创建 Job​。一个 CronJob 对象类似于 crontab (cron table) 文件中的一行记录。该对象根据 Cron (opens new window)格式定义的时间计划,周期性地创建 Job 对象。

一个 CronJob 在时间计划中的每次执行时刻,都创建 大约 一个 Job 对象。这里用到了 大约 ,是因为在少数情况下会创建两个 Job 对象,或者不创建 Job 对象。尽管 K8S 尽最大的可能性避免这种情况的出现,但是并不能完全杜绝此现象的发生。因此,Job 程序必须是 幂等的。

当以下两个条件都满足时,Job 将至少运行一次:

startingDeadlineSeconds 被设置为一个较大的值,或者不设置该值(默认值将被采纳)concurrencyPolicy​ 被设置为Allow

对于每一个 CronJob,CronJob 控制器将检查自上一次执行的时间点到现在为止有多少次执行被错过了。如果错过的执行次数超过了 100,则 CronJob 控制器将不再创建 Job 对象,并记录如下错误:

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

非常重要的一点是,如果设置了 startingDeadlineSeconds​ (非空 nil​),控制器将按照从 startingDeadlineSeconds​ 秒之前到现在为止的时间段计算被错过的执行次数,而不是按照从上一次执行的时间点到现在为止的时间段来计算被错过的执行次数。例如,如果 startingDeadlineSeconds​ 被设置为 200,则,控制器将计算过去 200 秒内,被错过的执行次数。

当 CronJob 在其计划的时间点应该创建 Job 时却创建失败,此时 CronJob 被认为错过了一次执行。例如,如果 concurrencyPolicy 被设置为 Forbid 且 CronJob 上一次创建的 Job 仍然在运行,此时 CronJob 再次遇到一个新的计划执行的时间点并尝试创建一个 Job,该此创建尝试将失败,并被认为错过了一次执行。

又例如,假设某个 CronJob 被设置为:自 08:30:00 开始,每分钟创建一个新的 Job,且 CronJob 的 startingDeadlineSeconds 字段未被设置。如果 CronJob 控制器恰好在 08:29:00 到 10:21:00 这个时间段出现故障(例如 Crash),则该 CronJob 将不会再次执行,因为其错过的执行次数已经超过了 100。

为了进一步解释这个概念,我们再列举一个例子,假设某个 CronJob 被设置为:自 08:30:00 开始,每分钟创建一个新的 Job,且 CronJob 的 startingDeadlineSeconds 字段被设置为 200 秒。同样,如果 CronJob 控制器恰好在 08:29:00 到 10:21:00 这个时间段出现故障(时间段与上个例子相同),此时 CronJob 控制器将在 10:22:00 为该 CronJob 创建一个 Job。这是因为,在这个例子中,控制器将只计算过去 200 秒中错过的执行次数(大约 3 次),而不是从上一次执行的时间点开始计算错过的执行次数。

CronJob 只负责按照时间计划的规定创建 Job 对象,由 Job 来负责管理具体 Pod 的创建和执行。

使用CronJob执行自动任务

CronJob​ 可以用来执行基于时间计划的定时任务,类似于Linux/Unix系统中的 crontable (opens new window)。

CronJob 执行周期性的重复任务时非常有用,例如备份数据、发送邮件等。CronJob 也可以用来指定将来某个时间点执行单个任务,例如将某项任务定时到系统负载比较低的时候执行。

CronJob 也存在某些限制,例如,在某些情况下,一个 CronJob 可能会创建多个 Job。因此,Job 必须是 幂等 的

创建

apiVersion: batch/v1beta1kind: CronJobmetadata: name: hellospec: schedule: "*/1 * * * *" jobTemplate: spec: template: spec: containers: - name: hello image: busybox args: - /bin/sh - -c - date; echo Hello from the Kubernetes cluster restartPolicy: OnFailure

#创建kubectl create -f created#查看kubectl get cronjob hello##################输出结果################NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGEhello */1 * * * * False 0 10s#等待后再次查看kubectl get jobs --watch#################输出结果################NAME COMPLETIONS DURATION AGEhello-4111706356 0/1 0shello-4111706356 0/1 0s 0shello-4111706356 1/1 5s 5s#删除kubectl delete cronjob hello

编写yaml

​与其他所有 Kubernetes 对象一样,CronJob 对象需要 apiVersion、kind、metadata​ 这几个字段。CronJob 还需要 .spec 字段。

所有对 CronJob 对象作出的修改,尤其是 .spec 的修改,都只对修改之后新建的 Job 有效,已经创建的 Job 不会受到影响

Schedule

.spec.schedule 是一个必填字段。类型为 Cron (opens new window)格式的字符串,例如 0 * * * * 或者 @hourly,该字段定义了 CronJob 应该何时创建和执行 Job。

该字段同样支持 vixie cron step 值(step values),参考 FreeBSD manual (opens new window)。例如,指定 CronJob 每隔两个小时执行一次,可以有如下三种写法:vixie cron step 值(step values),参考 FreeBSD manual (opens new window)。例如,指定 CronJob 每隔两个小时执行一次,可以有如下三种写法:

0 0,2,4,5,6,8,12,14,16,17,20,22 * * *)使用 范围值 + Step 值的写法:0 0-23/2 * * *Step 也可以跟在一个星号后面,如0 */2 * * *

Job Template

.spec.jobTemplate 字段是必填字段。该字段的结构与 Job 相同,只是不需要 apiVersion 和 kind。

Starting Deadline

.spec.startingDeadlineSeconds 为可选字段,代表着从计划的时间点开始,最迟多少秒之内必须启动 Job。如果超过了这个时间点,CronJob 就不会为其创建 Job,并将其记录为一次错过的执行次数。如果该字段未指定,则 Job 必须在指定的时间点执行。

CronJob 控制器将为每一个 CronJob 记录错过了多少次执行次数,如果错过的执行次数超过 100,则控制器将不会再为该 CronJob 创建新的 Job。如果 .spec.startingDeadlineSeconds 未指定,CronJob 控制器计算从 .status.lastScheduleTime 开始到现在为止总共错过的执行次数。

例如,某一个 CronJob 应该每分钟执行一次,.status.lastScheduleTime 的值是 上午5:00,假设现在已经是上午7:00。这意味着已经有 120 次执行时间点被错过,因此该 CronJob 将不再执行了。

如果 .spec.startingDeadlineSeconds 字段被设置为一个非空的值,则 CronJob 控制器计算将从 .spec.startingDeadlineSeconds 秒以前到现在这个时间段内错过的执行次数。

例如,假设该字段被设置为 200,控制器将只计算过去 200 秒内错过的执行次数。如果在过去 200 秒之内,有超过 100 次错过的执行次数,则 CronJob 将不再执行。

Concurrency Policy

.spec.concurrencyPolicy 是选填字段,指定了如何控制该 CronJob 创建的 Job 的并发性,可选的值有:

Allow: 默认值,允许并发运行 JobForbid: 不允许并发运行 Job;如果新的执行时间点到了,而上一个 Job 还未执行完,则 CronJob 将跳过新的执行时间点,保留仍在运行的 Job,且不会在此刻创建新的 JobReplace: 如果新的执行时间点到了,而上一个 Job 还未执行完,则 CronJob 将创建一个新的 Job 以替代正在执行的 Job

Suspend

.spec.suspend 是选填字段。如果该字段设置为 true,所有的后续执行都将挂起,该字段不会影响到已经创建的 Job。默认值为 false。

挂起(suspend)的时间段内,如果恰好存在有计划的执行时间点,则这些执行时间计划都被记录下来。如果不指定 .spec.startingDeadlineSeconds​,并将 .spec.suspend​ 字段从 true​ 修改为 false,则挂起这段时间内的执行计划都将被立刻执行。

Job History Limits

.spec.successfulJobsHistoryLimit 和 .spec.failedJobsHistoryLimit 字段是可选的。这些字段指定了 CronJob 应该保留多少个 completed 和 failed 的 Job 记录。

.spec.successfulJobsHistoryLimit 的默认值为 3.spec.failedJobsHistoryLimit 的默认值为 1

如果将其设置为 0,则 CronJob 不会保留已经结束的 Job 的记录。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:电影“欺诈式”营销:骗的是观众 害的是行业!
下一篇:k8s 1.24.x kubeadm 安装部署
相关文章

 发表评论

暂时没有评论,来抢沙发吧~