Kubernetes in Action—副本机制和其他控制器:部署托管的 pod

实际使用中,我们希望部署的 pod 能自动保持运行,并且保持健康,无须任何干预,那么几乎不会直接创建 pod,而是创建 ReplicationController 或 Deployment 资源来间接管理 pod。

保持 pod 健康

将 pod 调度到工作节点,节点上的 Kubelet 就会运行 pod 容器,从此只要 pod 存在,则会保持运行。如果容器主进程崩溃,则 Kubelet 将重启容器。但对于应用内部无限循环或死锁停止响应等情况,必须从外部检查应用程序的运行情况,而不是依赖于应用的检测。

介绍存活探针(liveness probe)

**Pod 所属工作节点上的 Kubelet ** 通过存活探针检查容器是否还在运行。Kubernetes 有以下三种探测容器的机制:

  • HTTP GET 探针对容器的 IP 地址(指定的端口和路径)执行 HTTP GET 请求,如果探测器收到响应,且响应状态码不代表错误(2xx 或 3xx),则认为探测成功。当服务器返回错误响应状态或没有响应时,则认为探测失败,容器被重新启动。
  • TCP 套接字探针尝试与容器指定端口建立 TCP 连接,如果连接成功建立,则探测成功,否则容器重新启动。
  • Exec 探针在容器内执行任意命令,并检查命令的退出状态码。如果状态码是 0,则探测成功,所有其他状态码则认为失败。

创建 HTTP 存活探针

由于前面创建的 kubia 镜像是一个 Web 应用程序,因此添加存活探针来检查其 Web 服务器是否能正常提供请求是有意义的。

创建新应用程序,第 5 次请求时返回 HTTP 状态码 500:

const http = require('http');
const os = require('os');

console.log("Kubia server starting...");

var requestCount = 0;

var handler = function(request, response) {
  console.log("Received request from " + request.connection.remoteAddress);
  requestCount++;
  if (requestCount > 5) {
    response.writeHead(500);
    response.end("I'm not well. Please restart me!");
    return;
  }
  response.writeHead(200);
  response.end("You've hit " + os.hostname() + "\n");
};

var www = http.createServer(handler);
www.listen(8080);

构建 docker 镜像 april5/kubia-unhealthy,创建 pod 定义文件 kubia-liveness-probe.yaml

apiVersion: v1
kind: Pod
metadata:
  name: kubia-liveness
spec:
  containers:
  - image: april5/kubia-unhealthy
    name: kubia
    livenessProbe:
      httpGet:
        path: /
        port: 8080

该 pod 描述文件定义了一个 httpGet 存活探针,探针访问 8080/ 确定容器是否健康。

当 5 次请求后, Kubernetes 由于应用程序返回的 500 状态码认为探测失败并重启容器。

使用存活探针

查看 pod 工作情况:

➜ kubectl get po kubia-liveness
NAME             READY   STATUS    RESTARTS   AGE
kubia-liveness   1/1     Running   1          3m51s

RESTARTS 列显示 pod 容器重启的次数。

获取崩溃容器的应用日志,添加 previous 查看之前崩溃的容器的日志:

kubectl logs mypod --previous

通过 kubectl describe 内容了解为什么必须重启容器:

➜ kubectl describe po kubia-liveness
Name:         kubia-liveness
...
Containers:
  kubia:
    Container ID:   docker://1e6b3bc678bdc4f221cb79324be91b269ca1710bbb49d9c06d3500ed6364f951
    Image:          april5/kubia-unhealthy
    Image ID:       docker-pullable://april5/kubia-unhealthy@sha256:d5947ab0d4b73d18436453c5bcd70d323178990c8f04ac3715b203267bbbb6aa
    Port:           <none>
    Host Port:      <none>
    State:          Running
      Started:      Fri, 20 Dec 2019 13:33:58 +0800
    Last State:     Terminated
      Reason:       Error
      Exit Code:    137
      Started:      Fri, 20 Dec 2019 13:32:10 +0800
      Finished:     Fri, 20 Dec 2019 13:33:53 +0800
    Ready:          True
    Restart Count:  1
    Liveness:       http-get http://:8080/ delay=0s timeout=1s period=10s #success=1 #failure=3
    ...
Events:
	...
  Normal   Killing    17s (x2 over 2m7s)   kubelet, minikube  Container kubia failed liveness probe, will be restarted

Exit Code 为 137 表示该进程由外部信号终止。137 是 128+x,其中 x 是终止进程的信号编号,即 9,其代表 SIGKILL 信号,意味着进程是被强行终止的。

配置存活探针的附加属性

Liveness:       http-get http://:8080/ delay=0s timeout=1s period=10s #success=1 #failure=3

在 describe 中有关于存活探针的附加信息,除了存活探针选项,还有其他属性,如 delay(超时)、period(周期)等。delay=0s 表示容器启动后立即开始探测,每 10s 探测一次容器,并在连续三次失败后重启容器。

定义探针可以自定义参数,如设置初始延迟的属性为 initialDelaySeconds ,如果没有设置初始延迟,会导致容器内应用程序还没准备好接收请求,失败次数就超过阈值,容器就重启了。

    livenessProbe:
      httpGet:
        path: /
        port: 8080
      initialDelaySeconds: 15

创建有效的存活探针

存活探针应该检查什么

简单的存活探针仅仅检查服务器是否响应,虽然看起来过于简单,但却能在 web 服务器停止响应后重启容器,这在大多数情况下已经足够了。

通常为了更好地存活检查,探针会配置为特定的 URL 路径(如 health),这样可以让应用从内部对内部的所有重要组件状态执行检查,确保自身没有终止或停止响应。

还有一点要注意,存活探针一定不能有外部因素影响,如当服务器无法连接后端数据库时,重启 Web 服务器容器是没有意义的。

保持探针轻量

存活探针不应该消耗太多计算资源,并且不应该花太长时间。

默认情况下探测器的执行频率都相对较高,必须在一秒之内完成。因为探针的 CPU 时间将计入容器的 CPU 时间,因此过于重量的探针将减少程序的可用 CPU 时间。

无须在探针中实现重试循环

即使将失败阈值设置为 1,Kubernetes 为了确认一次探测的失败,也会尝试若干次,因此在探测中自己实现循环是浪费精力。

了解 ReplicationController

ReplicationController 属于 Kubernetes 的一种资源,其确保属于它的 pod 始终保持运行状态,当 pod 消失时,ReplicationController 会注意到缺少的 pod 并创建替代的 pod。

image-20191221181541117

ReplicationController 的操作

ReplicationController 是根据 pod 是否匹配某个标签选择器来执行操作的。

介绍控制器的协调流程

下图展示了 ReplicationController 的工作协调流程:

image-20191221181917058

了解 ReplicationController 的三部分

  • label selector(标签选择器):用于确定 ReplicationController 作用域有哪些 pod
  • replica count(副本个数):指定应运行的 pod 数量
  • pod template(pod 模板):用于创建新的 pod 副本

image-20191221182147184

更改控制器的标签选择器或 pod 模板的效果

更改标签选择器或 pod 模板对现有 pod 没有影响。其只会使现有的 pod 脱离 ReplicationController 的管控范围。pod 模板并不会影响创建后的 pod,可以将其视为创建新 pod 时的 cookie cutter。

使用 ReplicationController 的好处

  • 确保一个 pod 或多个 pod 副本持续运行
  • 集群节点发生故障时,将为故障节点上运行的所有 pod 创建替代副本
  • 轻松实现 pod 的水平伸缩

创建一个新的 ReplicationController

通过 yaml 文件创建 ReplicationController,创建文件 kubia-rc.yaml:

apiVersion: v1
kind: ReplicationController
metadata:
  name: kubia
spec:
  replicas: 3
  selector: 
    app: kubia
  template:
    metadata:
      labels:
        app: kubia
    spec:
      containers:
      - name: kubia
        image: april5/kubia
        ports:
        - containerPort: 8080

该 yaml 描述创建名为 kubia 的 ReplicationController,其确保符合标签 app=kubia 的实例始终是 3 个,当没有足够 pod 时,则根据 pod 模板创建新 pod。当不指定选择器时,ReplicationController 会根据 pod 模板中的标签自动配置。

创建 ReplicationController:

➜ kubectl create -f kubia-rc.yaml
replicationcontroller/kubia created

使用 ReplicationController

列出 ReplicationController 创建的 3 个新 pod:

➜ kubectl get pods
NAME          READY   STATUS    RESTARTS   AGE
kubia-4nlgt   1/1     Running   0          61s
kubia-6ck54   1/1     Running   0          61s
kubia-6wxm6   1/1     Running   0          61s

查看 ReplicationController 对已删除的 pod 的响应

手动删除 pod:

➜ kubectl delete pod kubia-4nlgt
pod "kubia-4nlgt" deleted

➜ kubectl get pods
NAME          READY   STATUS    RESTARTS   AGE
kubia-2klt5   1/1     Running   0          39s
kubia-6ck54   1/1     Running   0          3m35s
kubia-6wxm6   1/1     Running   0          3m35s

可以发现,ReplicationController 立即创建了新的容器,而旧的容器删除成功。

获取有关 ReplicationController 的信息

➜ kubectl get rc
NAME    DESIRED   CURRENT   READY   AGE
kubia   3         3         3       5m5s

通过 kubectl describe 获取详细信息:

➜ kubectl describe rc kubia
Name:         kubia
Namespace:    default
Selector:     app=kubia
Labels:       app=kubia
Annotations:  <none>
Replicas:     3 current / 3 desired
Pods Status:  3 Running / 0 Waiting / 0 Succeeded / 0 Failed
Pod Template:
  Labels:  app=kubia
  Containers:
   kubia:
    Image:        april5/kubia
    Port:         8080/TCP
    Host Port:    0/TCP
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Events:
  Type    Reason            Age    From                    Message
  ----    ------            ----   ----                    -------
  Normal  SuccessfulCreate  5m57s  replication-controller  Created pod: kubia-4nlgt
  Normal  SuccessfulCreate  5m57s  replication-controller  Created pod: kubia-6wxm6
  Normal  SuccessfulCreate  5m57s  replication-controller  Created pod: kubia-6ck54
  Normal  SuccessfulCreate  3m1s   replication-controller  Created pod: kubia-2klt5

控制器如何创建新的 pod

ReplicationController 并没有对删除本身做出反应,而是针对由此产生的状态 — pod 数量不足做出反应,删除 pod 的通知触发了其检查实际的 pod 数量并采取措施。

应对节点故障

选择一个启动了至少一个 pod 的节点,使用 GKE 模拟网络节点故障:

gcloud compute ssh gke-kubia-default-pool-xxxxxxx-xxxx
sudo ifconfig eth0 down

通过 kubectl 检查工作节点,可以看到 Kubernetes 检测到节点下线状态 NotReady:

kubectl get node

此时,ReplicationController 将在另一个节点立即启动新的 pod:

kubectl get pods

此时人为干预就没必要了,pod 将自动修复,而当工作节点重新启动后,运行在上面的 Unknown 的 pod 将被删除:

gcloud compute instances rese七 gke-kubia-default-pool-xxxxxxx-xxxxx

将 pod 移入或移出 ReplicationController 的作用域

通过 ReplicationController 创建的 pod 并不是绑定到 pod,而是 ReplicationController 通过 标签选择器匹配 pod。修改标签选择器可以控制 pod 从 ReplicationController 作用域增加或删除。

注意:pod 的 metadata . ownerReferences 字段引用 ReplicationController ,可以轻松找到其所属的 ReplicationController。

给 ReplicationController 管理的 pod 加标签

➜ kubectl label pod kubia-6wxm6 type=special
pod/kubia-6wxm6 labeled
➜ kubectl get pods --show-labels
NAME          READY   STATUS    RESTARTS   AGE   LABELS
kubia-2klt5   1/1     Running   0          19m   app=kubia
kubia-6ck54   1/1     Running   0          22m   app=kubia
kubia-6wxm6   1/1     Running   0          22m   app=kubia,type=special

更改已托管的 pod 标签

➜ kubectl label pod kubia-6wxm6 app=foo --overwrite
pod/kubia-6wxm6 labeled

—overwrite 用于覆盖已存在的标签。

➜ kubectl get pods -L app
NAME          READY   STATUS    RESTARTS   AGE   APP
kubia-2klt5   1/1     Running   0          22m   kubia
kubia-6ck54   1/1     Running   0          25m   kubia
kubia-6wxm6   1/1     Running   0          25m   foo
kubia-jmg98   1/1     Running   0          51s   kubia

从控制器删除 pod

当想要操作特定 pod 时,从 ReplicationController 管理范围移除 pod 操作就十分有用。比如当遇到一个 bug 导致 pod 在特定时间或特定事件后开始出现问题,将其从 ReplicationController 管理范围移除,让控制器启动新的 pod 替代它,而我们就可以使用该 pod 排除 bug 而不会影响到正常的使用了。

更改 ReplicationController 的标签选择器

之前创建的 pod 将完全脱离 ReplicationController 的管理, 而 ReplicationController 将重新创建 3 个新的 pod。

修改 pod 模板

更改 pod 模板只会影响后面创建的新 pod,而要修改旧的 pod,最好是删除它们,由 ReplicationController 根据新模板创建新 pod。

image-20191221193559949

编辑 pod 模板:

➜ kubectl edit rc kubia
replicationcontroller/kubia edited

默认编辑器(环境变量 KUBE_EDITOR 决定)将打开 ReplicationController 的 YAML 配置,修改保存后,kubectl 将更新 ReplicationController 。

水平缩放 pod

ReplicationController 扩容

kubectl scale rc kubia --replicas=10

通过编辑定义来缩放 ReplicationController

➜ kubectl edit rc kubia
...
spec:
  replicas: 5
  selector:
    app: kubia
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: kubia
        ...

编辑保存后 ,pod 将被扩容到 5 个。

将副本数目缩小到 3:

➜ kubectl scale rc kubia --replicas=3
replicationcontroller/kubia scaled
➜ kubectl get pod
NAME          READY   STATUS        RESTARTS   AGE
kubia-2klt5   1/1     Running       0          35m
kubia-6ck54   1/1     Running       0          38m
kubia-6wxm6   1/1     Running       0          38m
kubia-jmg98   1/1     Running       0          14m
kubia-mlr4j   1/1     Terminating   0          91s
kubia-pd2qw   1/1     Terminating   0          91s

伸缩集群的声明式方法

所有水平伸缩 pod 的方式都是陈述式的,因此使得和 Kubernetes 集群交互变得十分容易。

删除一个 ReplicationController

当通过 kubectl delete 删除 ReplicationController 时,pod 也会被删除。由于 ReplicationController 创建的 pod 不是 ReplicationController 的组成部分,因此可以只删除 ReplicationController 并保持 pod 运行,当决定用 ReplicaSet 替换 ReplicationController 时,就可以在不影响 pod 的情况下执行此操作。

使用 —cascade=false 删除 ReplicationController 使托管的 pod 不被删除。

➜ kubectl delete rc kubia —cascade=false
replicationcontroller "kubia" deleted
➜ kubectl get pod
NAME          READY   STATUS    RESTARTS   AGE
kubia-2klt5   1/1     Running   0          59m
kubia-6ck54   1/1     Running   0          62m
kubia-6wxm6   1/1     Running   0          62m
kubia-jmg98   1/1     Running   0          37m
➜ kubectl get rc
No resources found in default namespace.

使用 ReplicaSet 而不是 ReplicationController

ReplicaSet 通常在创建更高层级的资源 Deployment 时被创建,但还是应该了解 ReplicaSet,其将完全替代 ReplicationController。

比较 ReplicaSet 和 ReplicationController

两者的行为完全相同,但 pod 选择器的表达能力 ReplicaSet 更强,ReplicaController 的标签选择器只允许包含某个标签匹配的 pod,但 ReplicaSet 还允许匹配缺少某个标签的 pod,或包含特定标签名的 pod,不管其值如何。

例如,单个 ReplicationController 无法将 env=production 和 env=devel 同时匹配,而 ReplicaSet 可以匹配两组并将它们视为一个大组。

同样, ReplicaSe 还可以只匹配标签名(可以理解为 env=*)。

定义 ReplicaSet

通过 ReplicaSet 接管无主 pod,创建 kubia-replicaset.yaml 文件:

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: kubia
spec:
  replicas: 3
  selector:
    matchLabels:
      app: kubia
  template:
    metadata:
      labels:
        app: kubia
    spec:
      containers:
      - name: kubia
        image: april5/kubia

注意 ReplicaSet 不属于 v1 版本的 API,因此要指定正确的 apiVersion。

创建和检查 ReplicaSet

创建 ReplicaSet,然后使用 kubectl get 和 kubectl describe 检查 ReplicaSet:

➜ kubectl create -f kubia-replicaset.yaml
replicaset.apps/kubia created
➜ kubectl get rs
NAME    DESIRED   CURRENT   READY   AGE
kubia   3         3         3       6s
➜ kubectl describe rs
Name:         kubia
Namespace:    default
Selector:     app=kubia
Labels:       <none>
Annotations:  <none>
Replicas:     3 current / 3 desired
Pods Status:  3 Running / 0 Waiting / 0 Succeeded / 0 Failed
Pod Template:
  Labels:  app=kubia
  Containers: ...
  Volumes:        <none>
Events:           <none>

ReplicaSet 接管了之前 ReplicationController 创建的 3 个 pod。

使用 ReplicaSet 的更富表达力的标签选择器

ReplicaSet 中的 matchLabels 选择器和 ReplicationController 没有区别,下面是通过更强大的 matchExpression 属性来重写选择器的示例,创建 kubia-replica-matchexpressions.yaml:

spec:
  replicas: 3
  selector:
    matchExpressions:
      - key: app
        operator: In
        values:
          - kubia

表达式都包含一个 key、一个 operator,并且可能还要一个 values 列表,以下是 4 个有效的运算符:

  • In:Label 的值必须与其中一个指定的 values 匹配
  • NotIn:Label 的值与任何指定的 values 不匹配
  • Exists:pod 必须包含一个指定名称的标签(值不重要),使用此运算符时,不应指定 values 字段
  • DoesNotExist:pod 不得包含有指定名称的标签,values 属性不得指定

当使用多个表达式时,则需要所有表达式均为 true 才能使选择器与 pod 匹配,matchLabels 和 matchExpressions 同时指定时也一样。

删除 ReplicaSet

➜ kubectl delete rs kubia
replicaset.apps "kubia" deleted

使用 DaemonSet 在每一个节点上运行一个 pod

当希望 pod 在集群的每个节点上运行时(且每个节点都需要正好一个运行的 pod 实例),就可以使用 DaemonSet。这种情况一般包括 pod 执行系统级别的与基础机构相关的操作,如希望每个节点都运行日志收集器和资源监控器,或是 Kubernetes 自身的 kube-proxy 进程。

image-20191221224802074

使用 DaemonSet 在每个节点上运行一个 pod

DaemonSet 没有期望副本数的概念,其确保一个满足其选择器的 pod 在每个节点上运行

使用 DaemonSet 只在特定的节点上运行 pod

DaemonSet 将 pod 部署到集群的节点上,通过 pod 模板中的 nodeSelector 属性指定节点。虽然节点可以被设置为不可调度的,但 DaemonSet 可以完全绕过调度器部署 pod,因为其目的是运行系统服务,即使在不可调度节点,系统服务通常也需要运行。

用一个例子来解释 DaemonSet

假设存在 ssd-monitor 的守护进程需要在包含 SSD 的所有节点上运行,创建 DaemonSet,使其在具有 SSD 标签的节点上运行 pod。

image-20191221234821861

创建 DaemonSet YAML 定义文件

创建 ssd-monitor-daemonset.yaml,其将创建一个 ssd-monitor 监控进程,该进程每 5 秒钟输出 “SSD OK” 并打印到标准输出:

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: ssd-monitor
spec:
  selector:
    matchLabels:
      app: ssd-monitor
  template:
    metadata:
      labels:
        app: ssd-monitor
    spec:
      nodeSelector:
        disk: ssd
      containers:
      - name: main
        image: luksa/ssd-monitor

创建 DaemonSet

➜ kubectl create -f ssd-monitor-daemonset.yaml
daemonset.apps/ssd-monitor created
➜ kubectl get ds
NAME          DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
ssd-monitor   0         0         0       0            0           disk=ssd        58s
➜ kubectl get pod
No resources found in default namespace.

由于节点并未打上 disk=ssd 的标签,因此 pod 还不会被创建。

向节点上添加所需的标签

➜ kubectl get node
NAME       STATUS   ROLES    AGE    VERSION
minikube   Ready    master   4d8h   v1.17.0
➜ kubectl label node minikube disk=ssd
node/minikube labeled

此时 DaemonSet 将创建 pod:

➜ kubectl get pod
NAME                READY   STATUS    RESTARTS   AGE
ssd-monitor-44m6z   1/1     Running   0          42s

从节点上删除所需的标签

修改标签,查看是否能影响运行在节点上的 pod:

➜ kubectl label node minikube disk=hdd --overwrite
node/minikube labeled
➜ kubectl get pod
NAME                READY   STATUS        RESTARTS   AGE
ssd-monitor-44m6z   1/1     Terminating   0          2m51s

pod 如预期被终止了,当删除 DaemonSet 时,如果还有其他的 pod 在运行,也将被一并删除,其与 pod 的生命周期是相关联的。

运行执行单个任务的 pod

ReplicationController、ReplicaSet 和 DaemonSet 会持续执行任务,永远达不到完成态。但通常会有需要执行任务后就终止的情况,因此需要 Job 资源。

介绍 Job 资源

Job 运行一种 pod,该 pod 在内部进程成功结束时,不重启容器,而是将 pod 标志为完成状态。节点发生故障时,该节点上由 Job 管理的 pod 将按照 ReplicaSet 的 pod 方式重新安排到其他节点,如果节点本身异常退出,则可以将 Job 配置为重新启动容器。

image-20191222100223887

定义 Job 资源

创建 exporter.yaml 文件定义 Job 资源:

apiVersion: batch/v1
kind: Job
metadata:
  name: batch-job
spec:
  template:
    metadata:
      labels:
        app: batch-job
    spec:
      restartPolicy: OnFailure
      containers:
      - name: main
        image: luksa/batch-job

Job 是 API 组 batch/v1 版本的一部分,该 YAML 定义了一个 Job 类型的资源,其调用一个运行 120s 的进程,之后退出。restartPolicy 是指在容器运行的进程结束时, Kubernetes 会做的事情,默认为 Always,但 Job pod 不能使用默认策略,因此其并不是要无限期运行的。因此,restartPolicy 一般在 Job 被设置为 OnFailure 或 Never。

看 Job 运行一个 pod

➜ kubectl create -f exporter.yaml
job.batch/batch-job created
➜ kubectl get jobs
NAME        COMPLETIONS   DURATION   AGE
batch-job   0/1           4s         4s
➜ kubectl get po
NAME              READY   STATUS    RESTARTS   AGE
batch-job-fnnpb   1/1     Running   0          8s

两分钟后,进程结束,查看已完成的 pod,还可以通过 logs 查看日志:

➜ kubectl get po
NAME              READY   STATUS      RESTARTS   AGE
batch-job-fnnpb   0/1     Completed   0          2m23s
➜ kubectl logs batch-job-fnnpb
Sat Dec 21 17:03:33 UTC 2019 Batch job starting
Sat Dec 21 17:05:33 UTC 2019 Finished succesfully
➜ kubectl get job
NAME        COMPLETIONS   DURATION   AGE
batch-job   1/1           2m6s       3m41s

在 Job 中运行多个 pod 实例

在 Job 运行的多个 pod 实例,可以配置为并行(parallelism)或串行(completions)的方式运行。

顺序运行 Job pod

示例,当需要一个 Job 运行多次时,将 completions 设为希望作业的 pod 运行多少次,创建 multi-completion-batch-job.yaml:

apiVersion: batch/v1
kind: Job
metadata:
  name: mulit-completion-batch-job
spec:
  completions: 5
  template:
    metadata:
      labels:
        app: batch-job
    spec:
      restartPolicy: OnFailure
      containers:
      - name: main
        image: luksa/batch-job

Job 将一个接一个地运行 5 个 pod,直到 5 个 pod 成功完成为止,当有一个 pod 发生故障后,工作会创建一个新 pod,因此 Job 会创建 5 个或 5 个以上的 pod。

➜ kubectl create -f multi-completion-batch-job.yaml
job.batch/mulit-completion-batch-job created
➜ kubectl get jobs
NAME                         COMPLETIONS   DURATION   AGE
batch-job                    1/1           2m6s       10m
mulit-completion-batch-job   0/5           20s        20s
➜ kubectl get pod
NAME                               READY   STATUS      RESTARTS   AGE
batch-job-fnnpb                    0/1     Completed   0          12m
mulit-completion-batch-job-6rj4q   1/1     Running     0          18s
mulit-completion-batch-job-x62lr   0/1     Completed   0          2m23s

并行运行 Job pod

通过 parallelism Job 配置属性,指定允许多少个 pod 并行执行,创建 multi-completion-parallel-batch-job.yaml:

apiVersion: batch/v1
kind: Job
metadata:
  name: mulit-completion-batch-job
spec:
  completions: 5
  parallelism: 2
  template:
    metadata:
      labels:
        app: batch-job
        ...

其中 completions 指定必须确保 5 个 pod 成功完成,而 parallelism 指定最多可以并行运行两个 pod。

➜ kubectl create -f multi-completion-parallel-batch-job.yaml
job.batch/mulit-completion-batch-job created
➜ kubectl get pod
NAME                               READY   STATUS              RESTARTS   AGE
mulit-completion-batch-job-kbwzk   1/1     Running     0          64s
mulit-completion-batch-job-pnpm8   1/1     Running     0          64s

当一个任务完成后,下一个任务将被运行,知道 5 个 pod 都完成了任务。

Job 的缩放

使用 kubectl patch(kubectl scale 已废弃) 命令完成 Job 资源的缩放:

➜  ch04 kubectl patch job mulit-completion-batch-job -p '{"spec":{"parallelism": 3}}'
job.batch/mulit-completion-batch-job patched
➜  ch04 kubectl get pod
NAME                               READY   STATUS              RESTARTS   AGE
mulit-completion-batch-job-fmb5k   1/1     Running             0          27s
mulit-completion-batch-job-rvcqm   1/1     Running             0          27s
mulit-completion-batch-job-xpmzc   0/1     ContainerCreating   0          3s

此时会修改 parallelism 的数目,从 2 增加到 3,即会有 3 个 pod 同时运行,另一个 pod 将启动。

限制 Job pod 完成任务的时间

Job 要等待一个 pod 多久来完成任务?如果 pod 卡住并且根本无法完成(或者无法足够快速完成),该怎么办?

通过在 pod 配置中设置 activeDeadlineSeconds 属性,可以限制 pod 的时间。如果 pod 运行时间超过此时间,系统将尝试终止 pod,并将 Job 标记为失败。

注意:Job manifest 中的 spec.backoffLimit 字段可配置 Job 被标记为失败前重试的次数,默认为 6。

安装 Job 定期运行或在将来运行一次

Kubernetes 支持 cron 任务,指定任务在指定时间间隔内重复运行。其通过创建 CronJob 资源进行配置 cron 任务。

创建一个 CronJob

创建 CronJob 资源,规定每 15 分钟运行一次批处理任务,为此创建 cronjob.yaml 文件:

apiVersion: batch/v1
kind: CronJob
metadata:
  name: batch-job-every-fifteen-minutes
spec:
  schedule: "0,15,30,45 * * * *"
  jobTemplate:
    spec:
      template:
        metadata:
          labels:
            app: periodic-batch-job
        spec:
          restartPolicy: OnFailure
          containers:
          - name: main
            image: luksa/batch-job

schedule 配置表示该工作每天 0、15、30 和 45 分钟运行一次。

配置时间表安排

时间表从左到右包含以下五个条目:

  • 分钟
  • 小时
  • 每月中的第几天
  • 星期几

示例:

“0, 30 * 1 * *” 表示每隔 30 分钟运行一次,但仅在每月的第一天运行

“0 3 * * 0” 表示每个星期天的 3AM 运行

配置 Job 模板

CrobJob 通过 jobTemplate 属性创建任务资源。

了解计划任务的运行方式

在计划时间内,CronJob 资源会创建 Job 资源,然后 Job 创建 pod。

为了避免 Job 或 pod 创建得比预计情况晚的情况,可以通过指定 CrobJob 规范额 startingDeadlineSeconds 来指定截止日期,如 startingDeadlineSeconds: 15 表示 pod 最迟在预定时间后 15s 开始运行。

正常情况下, CronJob 总是为计划配置的每次执行创建一个 Job,但有时可能同时创建两个 Job 或者根本没有创建。因此,要使任务是幂等的(多次运行能得到希望的结果),而且确保下一个任务运行完成本应该上一次运行完成的任何工作。