K8s in Action 阅读笔记——【4】Replication and other controllers: deploying managed Pods

Pod 是可部署的基本单元。实际应用中,部署需要自动运行和保持健康状态。为实现自动运行和健康状态保持,不建议直接创建Pod,而是创建 ReplicationControllers 或 Deployments来管理Pod。当创建未经管理的Pod时,Kubernetes会监视容器并在容器失败时自动重新启动。如果整个节点失败,则节点上的Pod将丢失,除非这些Pod受到 ReplicationControllers 或类似的机制进行管理。

4.1 Keeping pods healthy

使用Kubernetes的一个主要好处——它可以在集群中保持容器的运行。创建一个Pod资源并让Kubernetes选择一个工作节点来运行Pod的容器,让kubelet(节点上的Kubernetes代理)运行它们,只要Pod存在即可使容器一直运行。应用程序出现错误时(如进程崩溃)Kubernetes将自动重新启动容器。然而,有些应用程序不会导致进程崩溃,例如出现内存泄漏的Java应用程序,这会导致应用程序停止作用但进程仍在运行。如果应用程序停止响应而未崩溃,则必须从应用程序外部检查健康状况并通知Kubernetes重新启动它

4.1.1 Introducing liveness probes

Kubernetes可以通过活性探针(liveness probes)检查容器是否仍然活着。你可以在Pod的spec中为每个容器指定一个活性探针。Kubernetes会定期执行探测,如果探测失败,则重启容器。

k8s可以通过三种机制探测容器:

  • HTTP GET探针在你指定的容器IP地址、端口和路径上执行HTTP GET请求。如果探针接收到响应并且响应代码不表示错误(换句话说,如果HTTP响应代码是2xx或3xx),则该探针被认为是成功的。如果服务器返回错误响应代码或根本没有响应,则探针将被视为失败,容器将被重新启动。
  • TCP Socket 探针尝试打开容器指定端口的 TCP 连接。如果连接成功建立,探针就算成功。否则,容器将会被重启。
  • Exec 探针在容器内部执行任意命令,并检查该命令的退出状态码。如果状态码为 0,探针就算成功。其他所有状态码都被视为失败。

4.1.2 Creating an HTTP-based liveness probe

针对 Web 应用,添加存活探针来检查其 Web 服务器是否可提供请求非常必要。但由于该 Node.js 应用过于简单且无法失败,需要通过人为操作让其出现失败情况。

为了演示存活探针,需要对应用程序进行微调,第五个请求后每个请求返回 500 内部服务器错误的 HTTP 状态代码。前五个客户端请求正确处理后,应用程序将在随后的每次请求中返回错误。当应用程序出现问题时,可以通过存活探针来重新启动应用程序,以便能够再次正确处理客户端请求。

需要创建一个包含 HTTP GET 存活探针的新 Pod 。以下 YAML 显示了 Pod 的配置:

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

Pod 描述符定义了一个httpGet活动探测,它告诉Kubernetes定期在路径/端口8080上执行HTTP GET请求,以确定容器是否仍然健康。一旦容器运行,这些请求就会启动。

在五个这样的请求(或实际的客户端请求)之后,你的应用程序开始返回HTTP状态码500,Kubernetes将把它视为探测失败,并因此重新启动容器。

4.1.3 Seeing a liveness probe in action

要查看活动探针的功能,现在尝试创建 Pod 。大约一分半钟后,容器将重新启动。你可以通过运行kubectl get看到:

$ kubectl get    pod   
NAME             READY   STATUS    RESTARTS   AGE
kubia-liveness   1/1     Running   1          3m37s

restart列显示 Pod 的容器已经重新启动了一次(如果再等待一分半钟,它将再次重新启动,然后无限地继续这个循环)。

使用kubectl logs my Pod --previous可以查看重启前的容器的日志。

通过kubectl describe命令可以查看Pod的具体信息:

Events:
  Type     Reason     Age                  From               Message
  ----     ------     ----                 ----               -------
  Normal   Scheduled  4m26s                default-scheduler  Successfully assigned default/kubia-liveness to yjq-k8s2
  Normal   Pulled     4m11s                kubelet            Successfully pulled image "luksa/kubia-unhealthy" in 15.432956294s
  Normal   Pulled     2m27s                kubelet            Successfully pulled image "luksa/kubia-unhealthy" in 411.705578ms
  Warning  Unhealthy  67s (x6 over 3m17s)  kubelet            Liveness probe failed: HTTP probe failed with statuscode: 500
  Normal   Killing    67s (x2 over 2m57s)  kubelet            Container kubia failed liveness probe, will be restarte

当一个容器被终止时,将创建一个全新的容器——它不是重新启动的同一个容器。

4.1.4 Configuring additional properties of the liveness probe

通过kubectl describe命令还看到了探测指针的相关信息:

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

除了你明确指定的活动探针选项外,还可以看到其他属性,例如延迟、超时、周期等等。 delay=0s 部分显示探测会在容器启动后立即开始。超时设置为只有1秒,因此容器必须在1秒内返回响应,否则探测将被视为失败。容器每10秒被探测一次(period=10s),如果探测连续失败三次(#failure=3),容器将被重启。

apiVersion: v1
kind: Pod
metadata:
  name: kubia-liveness
spec:
  containers:
  - name: kubia
    image: luksa/kubia-unhealthy
    livenessProbe:
      httpGet:
        path: /
        port: 8080
      initialDelaySeconds: 15 # 会在执行第一次探测前等待15秒

如果你没有设置初始延迟,探测器将在容器启动后立即开始探测,这通常会导致探测失败,因为应用程序还没有准备好开始接收请求。如果失败数量超过失败阈值,容器甚至在能够开始正确响应请求之前就会重新启动

4.1.5 Creating effective liveness probes

对于在生产环境中运行的 Pod ,你应该始终定义一个活动探针。没有它,Kubernetes就无法知道你的应用是否还活着。

要进行更好的活动检查,需要配置探针在特定的 URL 路径上执行请求(例如 /health),并要求应用程序对运行在应用程序内部的所有重要组件执行内部状态检查,以确保它们都没有死亡或无响应

确保/health HTTP端点不需要身份验证,否则探测将总是失败,导致容器无限期地重新启动。

一定要检查应用程序内部的内容,不要受到外部因素的影响。例如,前端 Web 服务器的活动探针在无法连接到后端数据库时不应返回失败。如果底层问题在于数据库本身,那么重启 Web 服务器容器将无法解决问题。

活动探针不应使用过多的计算资源,而且完成速度也不应太慢。默认情况下,探针的执行频率相对较高,并且只允许1秒钟的完成时间。如果使用重量级的探针,可能会严重拖慢容器的运行速度。

如果你在容器中运行Java应用程序,确保使用HTTP GET活动探针而不是Exec探针。使用Exec探针需要启动一个全新的JVM来获取活动状态信息,这会占用大量的计算资源。

Kubernetes通过在容器崩溃或其活动探针失败时重新启动它们来保持容器的运行。这项工作由托管 Pod 的节点上的 Kubelet 完成,运行在主节点上的 Kubernetes 控制平面组件不参与此过程。但是,如果节点本身崩溃,控制平面必须为所有与该节点一起崩溃的 Pod 创建替代品。为了确保你的应用程序在另一个节点上重新启动,你需要将 Pod 由 ReplicationController 或类似机制管理。

4.2 Introducing ReplicationControllers

ReplicationController是Kubernetes的一种资源,确保其 Pod 始终在运行。如果 Pod 因任何原因消失,比如节点从集群中消失或因为 Pod 被从节点中驱逐,ReplicationController会注意到缺失的 Pod 并创建一个替代的 Pod

图4.1展示了一个节点崩溃并带走了两个 Pod 的情况。Pod A是直接创建的,因此是未受管理的 Pod ,而Pod B由ReplicationController管理。节点崩溃后,ReplicationController创建一个新的 Pod ( Pod B2)来替换缺失的 Pod B,而 Pod A完全丢失且永远不会重新创建它。

k8s获取pod的deployment_kubernetes

图中的ReplicationController仅管理单个 Pod ,但通常来说,ReplicationController旨在创建和管理多个 Pod 的副本。这就是ReplicationController得名的由来。

4.2.1 The operation of a ReplicationController

一个“复制控制器”(ReplicationController)不断监视当前运行的Pod列表,确保某一类 pod 的实际Pod数始终与期望数量匹配。如果运行的Pod数量太少,它将从Pod模板中创建新的复制品。如果运行的Pod数量太多,它将删除多余的复制品。

会有超过期望数量的副本,可能有以下几个原因:

  • 有人手动创建了相同类型的 pod 。
  • 有人更改了已有的 pod 的“类型”。
  • 有人减少了期望的 pod 数量,等等。

ReplicationControllers并不操作 pod 类型,而是操作与某个标签选择器匹配的 pod 集合。

控制器的调节回路

一个复制控制器的工作是确保一定数量的pod始终与其标签选择器匹配。如果不匹配,复制控制器将采取相应的行动来协调实际数量与期望数量。复制控制器的操作如图4.2所示。

k8s获取pod的deployment_kubernetes_02

ReplicationController的三个部分

ReplicationControllers由三个重要部分组成:

  • label selector 决定哪些pod在ReplicationController的作用域中。
  • replica count指定需要运行的pod的数量。
  • pod template在创建新的pod副本时使用。

k8s获取pod的deployment_docker_03

这些都可以随时修改,但只有副本数的更改会影响现有的pod。更改标签选择器和pod模板对现有pod没有影响。更改标签选择器会使现有的pod超出复制控制器的范围,因此控制器停止关注它们。在创建了pod之后,复制控制器也不会关心其pod的实际“内容”(容器镜像、环境变量等)。因此,模板仅影响由此复制控制器创建的新pod。

使用ReplicationController的好处

与Kubernetes中的许多内容一样,虽然ReplicationController是一个非常简单的概念,但它提供或实现了以下功能:

  • 通过在现有pod不可用时启动新的pod,确保一个或多个pod副本始终运行。
  • 当集群节点失败时,为所有在失败节点上运行的pod(即那些受复制控制器控制的pod)创建替代副本。
  • 它支持方便的pod水平扩展,包括手动扩展和自动扩展

4.2.2 Creating a ReplicationController

可以使用如下的YAML文件创建一个ReplicationController:

# kubia-rc.yaml
apiVersion: v1
kind: ReplicationController
metadata:
  name: kubia
spec:
  replicas: 3
  selector:
    app: kubia
  template:
    metadata:
      name: kubia
      labels:
        app: kubia
    spec:
      containers:
        - name: kubia
          image: yijunquan/kubia
          ports:
            - containerPort: 8080

当你向API服务器发布文件时,Kubernetes将创建一个名为kubia的新复制控制器,它确保三个pod实例始终与标签选择器app=kubia匹配。当pod数量不足时,将从提供的pod模板创建新的pod。模板的内容几乎与你在上一章中创建的pod定义相同。

模板中的pod标签显然必须与复制控制器的标签选择器相匹配; 否则,控制器会无限地创建新的pod,因为启动新的pod不会使实际副本数更接近所需副本数。为了防止这种情况发生,API服务器会验证复制控制器定义,如果配置错误,则不会接受它。

也可以选择根本不指定选择器。 在这种情况下,它将从pod模板中的标签自动配置。

应用上述文件:

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

4.2.3 Seeing the ReplicationController in action

因为不存在带有app=kubia标签的pod,所以ReplicationController应该从pod模板中启动三个新的pod。列出pod:

$ kubectl get pod
NAME            READY   STATUS    RESTARTS   AGE
kubia-nsb4x     1/1     Running   0          4m34s
kubia-pr8dq     1/1     Running   0          4m34s
kubia-w4rn8     1/1     Running   0          4m34s

试图删除其中的一个pod,ReplicationController会自动再生成一个新的pod:

$ kubectl delete pod kubia-nsb4x
pod "kubia-nsb4x" deleted
$ kubectl get pod
NAME            READY   STATUS    RESTARTS   AGE
kubia-dvxbp     1/1     Running   0          40s
kubia-pr8dq     1/1     Running   0          8m56s
kubia-w4rn8     1/1     Running   0          8m56s

也可以通过如下命令来看ReplicationController的信息:

$ kubectl get rc
NAME    DESIRED   CURRENT   READY   AGE
kubia   3         3         3       10m

也可以使用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:        luksa/kubia
    Port:         8080/TCP
    Host Port:    0/TCP
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Events:
  Type    Reason            Age    From                    Message
  ----    ------            ----   ----                    -------
  Normal  SuccessfulCreate  18m    replication-controller  Created pod: kubia-w4rn8
  Normal  SuccessfulCreate  18m    replication-controller  Created pod: kubia-pr8dq
  Normal  SuccessfulCreate  18m    replication-controller  Created pod: kubia-nsb4x
  Normal  SuccessfulCreate  9m54s  replication-controller  Created pod: kubia-dvxbp
准确理解导致控制器创建新pod的原因

控制器正在响应删除 Pod 的操作,并创建一个新的替换 Pod(见图4.4)。技术上来说,它并不是直接响应删除操作本身,而是响应其结果——Pod 数量不足。

k8s获取pod的deployment_笔记_04

尽管 ReplicationController 可以立即收到有关删除 Pod 的通知(API 服务器允许客户端监视资源和资源列表的更改),但这并不会导致它创建一个替换 Pod。通知会触发控制器检查实际 Pod 数量并采取适当的措施

4.2.4 Moving pods in and out of the scope of a ReplicationController

ReplicationController 创建的 Pod 不随 ReplicationController 绑定。ReplicationController 时刻管理与其标签选择器匹配的 Pod。通过更改 Pod 的标签,可以将其从 ReplicationController 的范围中移除或添加到其中,甚至可以将其从一个 ReplicationController 移动到另一个

如果更改 Pod 的标签,使其与 ReplicationController 的标签选择器不再匹配,则该 Pod 将变成像手动创建的其他任何 Pod 一样,不再受任何管理。如果运行该 Pod 的节点失败,则该 Pod 不会被重新调度。但请记住,当更改 Pod 的标签时,ReplicationController 注意到一个 Pod 不存在,并启动一个新 Pod 来代替它

现在我们来试试操作你的 Pod。因为你的 ReplicationController 管理的 Pod 具有 app=kubia 标签,所以需要删除此标签或更改其值,将该 Pod 移出 ReplicationController 的范围。添加其他标签没有作用,因为 ReplicationController 只关心 Pod 是否具有标签选择器中引用的所有标签,而不在乎其他标签的存在。

让我们确认一下ReplicationController并不在乎你是否给它管理的pod添加了额外的标签:

$ kubectl label pod kubia-dvxbp type=special
pod/kubia-dvxbp labeled
$ kubectl get pod
NAME            READY   STATUS    RESTARTS   AGE
kubia-dvxbp     1/1     Running   0          20m
kubia-pr8dq     1/1     Running   0          29m
kubia-w4rn8     1/1     Running   0          29m

可以看到,额外增加标签并不会对现有的Pod造成影响。

再看看将现有的标签改变会发生什么:

$ kubectl label pod kubia-dvxbp app=foo --overwrite
pod/kubia-dvxbp labeled
$ kubectl get pod
NAME            READY   STATUS              RESTARTS   AGE
kubia-djg2v     0/1     ContainerCreating   0          2s
kubia-dvxbp     1/1     Running             0          23m
kubia-pr8dq     1/1     Running             0          31m
kubia-w4rn8     1/1     Running             0          31m
$ kubectl get pods -L app
NAME            READY   STATUS    RESTARTS   AGE    APP
kubia-djg2v     1/1     Running   0          100s   kubia
kubia-dvxbp     1/1     Running   0          24m    foo
kubia-pr8dq     1/1     Running   0          33m    kubia
kubia-w4rn8     1/1     Running   0          33m    kubia

可以看到新的Pod创建了,所以现在有3个Pod受控制,一个Pod不受控制。

4.2.5 Changing the pod template

ReplicationController 的 Pod 模板可以随时修改。更改 Pod 模板就像用另一个饼干模具替换一个模具一样。它只会影响你之后切出的饼干,而对已经切出的饼干没有影响(见图4.6)。如果要修改旧的 Pod,你需要删除它们,让 ReplicationController 根据新的模板替换它们。

k8s获取pod的deployment_kubernetes_05

可以尝试编辑ReplicationController并在pod模板中添加标签。可以使用如下命令编辑ReplicationController:

$ kubectl edit rc kubia

4.2.6 Horizontally scaling pods

你已经看到了 ReplicationController 如何确保始终运行特定数量的 pod 实例。因为更改所需副本数非常简单,这也意味着水平扩展容器非常容易。

增加或减少 pod 的数量只需要更改 ReplicationController 资源中副本数字段的值。更改后,ReplicationController 将看到存在太多的 pod(当缩小时)并删除其中的一部分,或者看到太少的 pod(当扩大时)并创建额外的 pod。

可以通过如下命令将副本数增加到4:

$ kubectl scale rc kubia --replicas=4
replicationcontroller/kubia scaled

也可以恢复到3

$ kubectl scale rc kubia --replicas=3
replicationcontroller/kubia scaled

在 Kubernetes 中水平扩展 pod 的关键是表达你的意愿:“我希望运行 x 个实例。”你没有告诉 Kubernetes 如何执行或处理,你只是明确指定了所需的状态。这种声明性方法使得与 Kubernetes 集群交互变得非常容易。想象一下,如果你必须手动确定当前运行的实例数量,然后显式地告诉 Kubernetes 运行多少个额外实例,那将是多么繁琐且容易出错。

4.2.7 Deleting a ReplicationController

当你通过 kubectl delete删除一个 ReplicationController 时,pod 也会被删除。但是因为 ReplicationController 创建的 pod 不是 ReplicationController 的组成部分,只是由它来管理,所以你可以只删除 ReplicationController,让 pod 继续运行,可以通过在命令中加入--cascade=false选项来让其 pod 继续运行。

$ kubectl delete rc kubia --cascade=false
replicationcontroller "kubia" deleted

4.3 Using ReplicaSets instead of ReplicationControllers

最初,ReplicationControllers是Kubernetes中用于复制Pod并在节点故障后重新调度它们的唯一组件。后来,引入了一种类似的资源称为ReplicaSet。它是ReplicationController的新一代,并完全取代了它(ReplicationController最终将被弃用)。

4.3.1 Comparing a ReplicaSet to a ReplicationController

ReplicaSet与ReplicationController的行为完全相同,但ReplicaSet的Pod选择器更为丰富。ReplicationController的标签选择器只能匹配包含某个标签的Pod,而ReplicaSet的选择器还能匹配不包含某个标签或者包含某个标签键而无论其值为何的Pod。

例如,单个ReplicationController不能同时匹配具有标签env=production和env=devel的Pod,只能匹配具有env=production或者env=devel标签的Pod之一。相比之下,单个ReplicaSet可以将它们视为同一组并匹配。

同样地,ReplicationController不能仅根据标签键的存在(而不考虑其值)匹配Pod,而ReplicaSet可以。比如,ReplicaSet可以匹配所有包含键为env的标签的Pod,无论其实际值是什么(可视为env=*)。

4.3.2 Defining a ReplicaSet

下面是一个使用ReplicaSet的案例:

# kubia-replicaset.yaml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: kubia
spec:
  replicas: 3
  selector:
    # 这里使用的是更简单的matchLabels选择器,
    # 它很像ReplicationController的选择器。
    matchLabels: 
      app: kubia
  template:
  # 模板和ReplicationController一样
    metadata:
      labels:
        app: kubia
    spec:
      containers:
      - name: kubia
        image: luksa/kubia

因为你仍然有三个与之前运行的app=kubia选择器匹配的pod,所以创建这个ReplicaSet不会导致创建任何新的pod。

4.3.3 Creating and examining a ReplicaSet

使用kubectl create命令创建好ReplicaSet后,可以通过如下命令查看信息:

$ kubectl get rs
NAME    DESIRED   CURRENT   READY   AGE
kubia   3         3         3       68s
$ 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:
   kubia:
    Image:        luksa/kubia
    Port:         <none>
    Host Port:    <none>
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Events:           <none>

4.3.4 Using the ReplicaSet’s more expressive label selectors

ReplicaSet相对于ReplicationController的主要改进在于其更丰富的标签选择器。在第一个ReplicaSet示例中,你故意使用了较简单的matchLabels选择器,以便查看ReplicaSet与ReplicationController没有什么不同。现在,你将重写选择器以使用更强大的matchExpressions属性,如下面所示。

# kubia-replicaset-matchexpressions.yaml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: kubia
spec:
  replicas: 3
  selector:
    matchExpressions:
    # 标签必须有"kubia"
      - key: app
        operator: In
        values:
          - kubia
  template:
  # 模板和ReplicationController一样
    metadata:
      labels:
        app: kubia
    spec:
      containers:
      - name: kubia
        image: luksa/kubia

你可以向选择器添加其他表达式。就像示例中一样,每个表达式都必须包含keyoperator和(根据运算符而定)一个值列表。可以使用四种有效的运算符:

  1. In:标签的值必须与指定的值之一匹配
  2. NotIn:标签的值不能匹配任何指定的值
  3. Exists:Pod必须包含具有指定键的标签(值不重要)。在使用此运算符时,不应指定values字段
  4. DoesNotExist:Pod不应包含具有指定键的标签。不应指定values属性。

如果指定多个表达式,则所有这些表达式必须为true,才能使选择器匹配Pod。如果同时指定matchLabels和matchExpressions,则所有标签都必须匹配,并且所有表达式都必须为true,才能使Pod与选择器匹配。

最后,删除一下ReplicaSet:

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

4.4 Running exactly one pod on each node with DaemonSets

ReplicationController和ReplicaSet都用于在Kubernetes集群中的任何位置运行特定数量的Pod。但是,在某些情况下,你希望Pod在集群中的每个节点上运行(每个节点需要运行一个Pod实例,如图4.8所示)。

此类情况包括执行系统级操作的基础设施相关Pod,例如你需要在每个节点上运行日志收集器和资源监视器。另一个很好的例子是Kubernetes自己的kube-proxy进程,它需要在所有节点上运行以使服务正常工作。

k8s获取pod的deployment_Pod_06

4.4.1 Using a DaemonSet to run a pod on every node

为了在所有集群节点上运行Pod,需创建DaemonSet对象。与ReplicationController或ReplicaSet类似,但由DaemonSet创建的Pod已指定目标节点,不通过Kubernetes Scheduler分散部署。

DaemonSet确保创建与节点数相同的Pod,将每个Pod部署各个节点上。ReplicaSet(或ReplicationController)确保集群中维护所需Pod副本数量,但DaemonSet无所需副本数概念。因其任务为确保每个Node上都运行匹配其Pod选择器的pod,所以无需副本计数。若节点宕机,DaemonSet不会在其他地方创建Pod,但向集群添加新节点时,DaemonSet会立即向其部署Pod实例

如果有意外删除某个Pod,DaemonSet会执行同样操作以确保节点上有其Pod。DaemonSet从配置的Pod模板创建Pod,与ReplicaSet相似。

4.4.2 Using a DaemonSet to run pods only on certain nodes

DaemonSet将pods部署到集群中的所有节点,除非你指定pods只能在所有节点的一个子集上运行。这是通过在pod模板中指定nodeSelector属性来完成的,该属性是DaemonSet定义的一部分(类似于ReplicaSet或ReplicationController中的pod模板)。

假设有一个名为ssd-monitor的守护进程需要在包含固态驱动器(SSD)的所有节点上运行。你将创建一个守护进程,在所有标记为具有SSD的节点上运行这个守护进程。集群管理员已经为所有这样的节点添加了disk=ssd标签,因此你将创建带有节点选择器的DaemonSet,该节点选择器只选择带有该标签的节点,如图4.9所示。

k8s获取pod的deployment_kubernetes_07

你将创建一个 DaemonSet,它运行模拟的 SSD 监控进程。每隔五秒钟,该进程会向标准输出打印“SSD OK”。示例如下:

# ssd-monitor-daemonset.yaml
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

下面是应用以及查看

$ 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        6s

由于没有给node打上标签,所以还没有Pod创建,接下来给我们的yjq-k8s3打上disk=ssd的标签。

$ kubectl get nodes
NAME       STATUS   ROLES                  AGE   VERSION
yjq-k8s1   Ready    control-plane,master   10d   v1.21.3
yjq-k8s2   Ready    worker                 10d   v1.21.3
yjq-k8s3   Ready    worker                 10d   v1.21.3
yjq-k8s4   Ready    worker                 10d   v1.21.3

$ kubectl label node yjq-k8s3 disk=ssd
node/yjq-k8s3 labeled

$ kubectl get pod
NAME                READY   STATUS    RESTARTS   AGE
ssd-monitor-lgj5q   1/1     Running   0          27s

可以看到,ssd-monitor成功创建了。

如果更改节点的标签会发生什么?

$ kubectl label node yjq-k8s3 disk=hdd --overwrite
node/yjq-k8s3 labeled
$ kubectl get pod
NAME                READY   STATUS        RESTARTS   AGE
ssd-monitor-lgj5q   1/1     Terminating   0          115s

可以发现刚刚创建的Pod被终止了,这是显而易见的。

4.5 Running pods that perform a single completable task

到目前为止,我们只讨论了需要持续运行的 Pod。然而有些情况下,你只需要运行一项任务,当任务完成后就会终止。ReplicationControllers、ReplicaSets和DaemonSets以永不视为已完成的连续任务运行,这些 Pod 中的进程会在退出后重新启动。但对于可完成的任务,任务进程终止后就不应再次启动。

4.5.1 Introducing the Job resource

Kubernetes 通过 Job 资源来实现此功能。 Job 资源与本章中讨论的其它资源类似,但允许你运行一个 Pod,其中的容器在运行内部过程成功完成后不会被重启。 一旦成功后,Pod 就被认为是完成的。

如果发生节点故障,由 Job 管理的该节点上的 Pod 将被重新安排到其他节点上,就像 ReplicaSet Pod 一样。如果进程本身失败(即进程返回错误退出代码),则可以配置 Job 要么重新启动容器,要么不重新启动。

图4.10显示了由 Job 创建的 Pod 如果最初调度的节点失败,将如何被重新调度到新的节点。

k8s获取pod的deployment_Pod_08

Job 对于临时任务非常有用,举例来说,如果你有存储在某处的数据,并且需要将其转换并导出到某个地方,就可以利用Job。你将通过运行基于 busybox 镜像构建的容器镜像来模拟此过程,该容器镜像调用 sleep 命令等待两分钟。

4.5.2 Defining a Job resource

定义的YAML文件如下:

# exporter.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: batch-job
  namespace: default
  labels:
    app: batch-job
spec:
  template:
    metadata:
      labels:
        app: batch-job
    spec:
      # 失败时才重启容器
      restartPolicy: OnFailure 
      containers:
      - name: main
        image: luksa/batch-job

Job是批处理API组和v1 API版本的一部分。YAML文件定义了一个类型为Job的资源,该资源将运行luksa/batch-job镜像,该镜像调用一个进程,运行时间正好为120秒,然后退出。

在Pod的规范中,你可以指定在容器中运行的进程完成后 Kubernetes 应该做什么。这是通过 restartPolicy 设置项完成的,其默认值为 Always。但是,Job Pod 不能使用默认策略,因为它们不是永久运行的。因此,需要将重启策略显式设置为 OnFailure 或 Never。这是防止容器在完成任务后被重新启动的设置(不是因为 Pod 受 Job 资源管理)。

4.5.3 Seeing a Job run a pod

通过kubectl create命令创建以后,可以查看Job:

$ kubectl get job
NAME        COMPLETIONS   DURATION   AGE
batch-job   0/1           28s        28s

$ kkubectl get pod
NAME              READY   STATUS    RESTARTS   AGE
batch-job-dtzqf   1/1     Running   0          9s

,两分钟后,pod将不再显示在pod列表中,并且Job将被标记为已完成。默认情况下,在列出pod时不会显示完成的pod,除非你使用-A

$ kubectl get job
NAME        COMPLETIONS   DURATION   AGE
batch-job   1/1           2m4s       2m42s

$ kubectl get pod -A
NAMESPACE    NAME            READY   STATUS      RESTARTS   AGE
default   batch-job-dtzqf    0/1     Completed   0          3m15s

可以查看已完成的Pod的日志:

$ kubectl logs  batch-job-dtzqf
Sat May 27 07:46:57 UTC 2023 Batch job starting
Sat May 27 07:48:57 UTC 2023 Finished succesfully

删除Job以后就会删除该Pod:

$ kubectl delete job batch-job
job.batch "batch-job" deleted

4.5.4 Running multiple pod instances in a Job

Job可以被配置为创建多个 Pod 实例并并行或按顺序运行它们。这是通过在Job spec中设置完成次数和并行度属性来实现的。

按顺序运行Job Pod

如果想让作业运行超过一次,可以设置完成次数(completions)来指定Job的 Pod 运行次数。以下代码示例:

# multi-completion-batch-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: multi-completion-batch-job
spec:
  completions: 5
  template:
    metadata:
      name: batch-job
      labels:
        app: batch-job
    spec:
      # 失败时才重启容器
      restartPolicy: OnFailure 
      containers:
      - name: main
        image: luksa/batch-job

该作业会顺序运行五个 Pod。开始时创建一个 Pod,当该 Pod 的容器运行完成后,创建第二个 Pod,依此类推,直到成功运行五个 Pod。如果其中一个 Pod 失败了,作业会创建一个新的 Pod,因此作业可能会生成超过五个 Pod。

并行运行Job Pod

与其一个接一个地运行单个Job pod,还可以让Job并行运行多个pod。可以使用parallelism属性指定允许并行运行的pod数量,如下面所示。

multi-completion-parallel-batch-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: multi-completion-batch-job
spec:
  completions: 5
  parallelism: 2
  template:
    metadata:
      name: batch-job
      labels:
        app: batch-job
    spec:
      # 失败时才重启容器
      restartPolicy: OnFailure 
      containers:
      - name: main
        image: luksa/batch-job

通过将parallelism设置为2,Job将创建两个pod并并行运行它们:

$ kubectl create -f multi-completion-parallel-batch-job.yaml
job.batch/multi-completion-batch-job created

$ kubectl get pod
NAME                               READY   STATUS    RESTARTS   AGE
multi-completion-batch-job-kgxns   1/1     Running   0          28s
multi-completion-batch-job-txj72   1/1     Running   0          28s

一旦其中一个完成,Job将运行下一个pod,直到五个pod成功完成。

4.6 Scheduling Jobs to run periodically or once in the future

创建作业资源时,作业资源会立即运行其 Pod,但许多批处理作业需要在将来的特定时间或指定的时间间隔内运行。在类Unix操作系统中,这些作业更为人所知的是“定时任务”。Kubernetes也支持这些类型的作业。

通过创建CronJob资源,你可以在Kubernetes中配置定时任务。在指定的时间,Kubernetes将根据CronJob对象中配置的作业模板创建Job资源。创建Job资源时,会根据作业的pod模板创建一个或多个Pod副本。

4.6.1 Creating a CronJob

需要每15分钟运行前面示例中的批处理作业。创建具有以下CronJob资源:

# cronjob.yaml
apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: hello
  namespace: default
spec:
  # 这项工作应该在每天每小时的0、15、30和45分钟运行。
  schedule: "0,15,30,45 * * * *"
  jobTemplate:
    spec:
      template:
        metadata:
          labels:
            app: periodic-batch-job
        spec:
          containers:
          - name: main
            image: luksa/batch-job
          restartPolicy: OnFailure

shedule中从左到右包含五个条目:

  • 分钟
  • 小时
  • 一个月中的某一天
  • 月份
  • 一周中的某一天

在此示例中,你想每15分钟运行一次作业,因此shedule需要是“0,15,30,45 * * * *”,这意味着在每小时的0、15、30和45分钟(第一个星号),每月的每一天(第二个星号),每个月(第三个星号)和每周的每一天(第四个星号)

如果你想使作业每30分钟运行一次,但仅在每月的第一天运行,那么计划应设置为“0,30 * 1 * *”,如果希望它在每个星期天的上午3点运行,则将其设置为“0 3 * * 0”(最后一个零表示星期天)。

CronJob将从CronJob规范中配置的jobTemplate属性创建Job资源。

4.6.2 Understanding how scheduled jobs are run

作业资源将在大约预定的时间从CronJob资源中创建。然后,Job会创建Pod。

可能出现作业或Pod相对较晚被创建和运行的情况。你可能对于该作业不想启动得太迟有硬性要求。在这种情况下,你可以通过在CronJob规范中指定startingDeadlineSeconds字段来指定截止期限,如下所示:

apiVersion: batch/v1beta1
kind: CronJob
spec:
	schedule: "0,15,30,45 * * * *"
	startingDeadlineSeconds: 15
	# pod必须最迟在预定时间过后15秒开始运行。

在上面的实例中,作业应该运行的时间之一是10:30:00。如果由于任何原因在10:30:15之前未启动,则作业将不会运行,并且将显示为已失败。