自学kubernetes能学会吗 (kubernetes详细技术教程)

在之前的文章中, 介绍了搭建好的#minikube#环境,如果你现在还没有一个可用的minikube环境, 那么可以去该篇文章中直接*载下**;

在之前的文章中, 先后介绍了如何从源代码开始构建一个Node.js应用和Spring Boot 应用, 并且部署到Kubernetes 中(这个Kubernetes 环境主要是之前建好的#minikube#) , 现在我们开始进一步深入的学习Kubernetes, 用一个个可以实际运行的例子的形式, 深入理解#Kubernetes#的概念以及原理.

在#动手动脑学Kubernetes#系列教程中, 我们展示了Kubernetes的基本用法

在第一篇里, 学习了#Pod#的基本知识;

在第二篇里, 学习了标签(#Label#)的语法, 使用Label来选择和过滤Kubernetes 资源;

在第三篇里, 介绍了#Deployment#的使用, 介绍了Deployment与Replica Set、Pod的关系, 并展示了如何进行应用的版本回滚;

在第四篇里, 介绍了#Service#的使用,使用Replication Controller创建Pod, 并创建Service, 展示了从Service 调用应用的方法; 随后又展示了扩展 Pod的数量为2, 比较了Service和之前的不同, 基本展示了Cluster IP 类型的Service的基本用法.

在第五篇里, 介绍了#Namespace#的使用, 除了创建,列出系统的Namespace之外, 还说明Namespace 如何对资源进行隔离, 建立Development, Staging, Production等环境的方法.

在第六篇里, 介绍了#Service Discovery#的使用, 讲解了如何检查Kube-dns, 如何检查和使用Service的FQDN等知识, 对Kubernetes的DNS 系统有整体的理解.

在第七篇里, 介绍了#Port Forwards#, #端口转发#的用法, 在本地程序开发的时候, 使用端口转发可以简化本地测试的工作, 同时介绍了其他几种本地端口转发的用法.

在第八篇里, 介绍了#Probe#(#探针#)的知识, 介绍了livenessProbe 和readinessProbe的用法,同时介绍了Pod 容器中的几个状态变化, 以及2个容器生命周期回调接口.

在第九篇里, 介绍了#环境变量#的用法, 使用环境变量可以把Pod 定义的信息传递给运行其中的镜像.

在第十篇里, 我们介绍了#Volume#, 卷的用法, 主要展示了emptyDir卷的使用, emptyDir卷的生命周期是和Pod 生命周期同步的.

在第十一篇里, 我们介绍了#Persistent Volume#, 也就是持久卷的用法,展示了当数据存入到PV 之后,数据超乎Pod 生命周期之外的情况.

在第十二篇里, 介绍了Secret, 也就是机密信息的用法, 机密是绑定在命名空间里的, 在使用时候和Volume的用法一样, 可以被Pod 访问, 本篇展示了Opaque类型的Secret的用法.

在第十三篇里, 介绍了日志(logging)的使用, 介绍了Kubernetes中基本日志记录的查看和常用的命令行参数,在理论部分展示了其他几种logging的使用.

在本篇里, 我们将学习Kubernetes Job的使用, 继续学习吧!

kubernetesoperator快速入门,kubernetes学完找哪类工作

在应用程序开发中, 批处理作业(batch processing) 是必不可少的, 批处理作业一般是定时的, 运行一段时间后结束的作业,这些运营作业包括:

  • 无需用户交互即可最有效地处理大量信息的自动化,复杂处理。 这些操作通常包括基于时间的事件(例如月末计算,通知或通信)。
  • 在非常大的数据集中重复处理复杂业务规则的定期应用(例如,保险利益确定或费率调整)。
  • 集成从内部和外部系统接收的信息,这些信息通常需要以事务方式格式化,验证和处理到记录系统中。 批处理用于每天为企业处理数十亿的交易。

这种作业和我们之前见到的一些Web应用,在生命周期方面并不相同, Web应用一般是始终处于等待用户请求和处理用户请求的过程中, 而批处理作业则多为定时任务, 任务完成之后自动销毁, 然后等待下次调度. 常用的框架有Spring Batch, Quartz等.

而Kubernetes也提供了对批处理作业的支持, 专门提供了Job 资源对象;在Kubernetes中, 和Service/Deployment 一样, 具体的执行是由Pod 来完成的, 而Job则负责对Pod的监管(supervisor), 由Pod来执行批处理, 运行一定时间才能完成的过程,例如计算或备份操作。

先来看一个具体的Job吧.

从镜像开始

我们来创建一个自动计数的Pod, 这个Pod 会从9开始递减, 减到1之后停止,

Github 地址: https://raw.githubusercontent.com/hintcnuie/kbe/main/specs/jobs/job.yaml

文件内容:

apiVersion: batch/v1
kind: Job
metadata:
  name: countdown
spec:
  template:
    metadata:
      name: countdown
    spec:
      containers:
      - name: counter
        image: centos:7
        command:
         - "bin/bash"
         - "-c"
         - "for i in 9 8 7 6 5 4 3 2 1 ; do echo $i ; done"
      restartPolicy: Never

从上面的文件中可以看出, Job的定义里面, apiVersion 和我们之前的不一样, 使用的是batch/v1, 类别是Job, 剩下的Container部分内容很简单, 就是包含了一个countdown的容器, 在其中进行一个for 循环,然后退出.

运行Job

来创建这个Pod:

$ kubectl apply -f https://raw.githubusercontent.com/hintcnuie/kbe/main/specs/jobs/job.yaml
job.batch/countdown created

可以检查下Job 的状态:

$ kubectl get jobs
NAME    COMPLETIONS  DURATION  AGE
countdown  1/1      3s     111s

可以看出Job 执行了3秒钟后结束, 现在的状态是COMPLETE.

来看看对应的Pod的情况:

$ kubectl get pods
NAME                  READY  STATUS   RESTARTS  AGE
countdown-mfsb7             0/1   Completed  0     4m5s
$ kubectl logs countdown-mfsb7
9
8
7
6
5
4
3
2
1

可以看到, Pod 的状态也已经结束, 而且从上面的log 中看出, Pod 中的容器也打印出了循环中的数值.

来仔细看看这个Job 的情况:

$ kubectl describe jobs/countdown
Name:      countdown
Namespace:   default
Selector:    controller-uid=1bc5db99-232d-433e-a2cf-7d15cfdafd49
Labels:     controller-uid=1bc5db99-232d-433e-a2cf-7d15cfdafd49
        job-name=countdown
Annotations:  <none>
Parallelism:  1
Completions:  1
Start Time:   Fri, 19 Mar 2021 14:14:47 +0000
Completed At:  Fri, 19 Mar 2021 14:14:50 +0000
Duration:    3s
Pods Statuses: 0 Running / 1 Succeeded / 0 Failed
Pod Template:
 Labels: controller-uid=1bc5db99-232d-433e-a2cf-7d15cfdafd49
     job-name=countdown
 Containers:
 counter:
  Image:   centos:7
  Port:    <none>
  Host Port: <none>
  Command:
   bin/bash
   -c
   for i in 9 8 7 6 5 4 3 2 1 ; do echo $i ; done
  Environment: <none>
  Mounts:    <none>
 Volumes:    <none>
Events:
 Type  Reason      Age  From      Message
 ----  ------      ----  ----      -------
 Normal SuccessfulCreate 7m47s job-controller Created pod: countdown-mfsb7
 Normal Completed     7m44s job-controller Job completed

从上面的状态中我们可以更加详细地看出, Job 已经运行完毕, 而且相应的Pod状态也会变成结束.

这个Job 非常简单, 运行的时间也比较短, 我们再来看一个稍微长一点的Job:

Github地址:https://raw.githubusercontent.com/hintcnuie/kbe/main/specs/jobs/pi-job.yaml

文件内容:

apiVersion: batch/v1
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://raw.githubusercontent.com/hintcnuie/kbe/main/specs/jobs/pi-job.yaml
job.batch/pi created
$ kubectl get jobs pi
NAME   COMPLETIONS   DURATION   AGE
pi     0/1           3m46s      3m46s

看一下这个Job 的情况:

$ kubectl describe job pi
Name:      pi
Namespace:   default
Selector:    controller-uid=70eb8e44-3579-4517-8a74-3db6983f77d6
Labels:     controller-uid=70eb8e44-3579-4517-8a74-3db6983f77d6
        job-name=pi
Annotations:  <none>
Parallelism:  1
Completions:  1
Start Time:   Fri, 19 Mar 2021 14:55:50 +0000
Pods Statuses: 1 Running / 0 Succeeded / 0 Failed
Pod Template:
 Labels: controller-uid=70eb8e44-3579-4517-8a74-3db6983f77d6
     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 4m  job-controller Created pod: pi-h54hw

可以看到, 这个Job 已经创建, 而且在写稿之时, 其中Pod的容器还处于运行状态, 查看一下情况:

$ kubectl get pods pi-h54hw
NAME    READY  STATUS   RESTARTS  AGE
pi-h54hw  0/1   Completed  0     14m

看一下这个容器中的Log:

kubernetesoperator快速入门,kubernetes学完找哪类工作

log 里面完整地打印出了π的前2000位的数值.

下面我们来学习一下Job相关的理论知识.

Job 模式

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

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

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

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

Job 与Pod的关系

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

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

处理 Pod 和容器失效

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

整个 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 的容器 会在 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 的知识我们暂时介绍到这里, 今天讲解的Job的例子, 是非并行性的一次性运行的作业, 在以后的篇幅里面,我们会介绍定时Job(CronJob), 继续学习吧!

kubernetesoperator快速入门,kubernetes学完找哪类工作

kubernetesoperator快速入门,kubernetes学完找哪类工作

kubernetesoperator快速入门,kubernetes学完找哪类工作

学而时习之,不亦说乎