Kubernetes 资源控制器使用指南_linux

Kubernetes 资源控制器配置文件的编写是学习 K8S 的重中之重!

资源配额控制器确保了指定的资源对象始终不会超过配置的资源,能够有效的降低整个系统宕机的机率,增强系统的鲁棒性,对整个集群的稳定性有非常重要的作用。

Kubernetes 资源控制器使用指南_python_02

Pod 控制器

需要我们重点掌握的知识点

什么是控制器呢?在 Kubernetes 中内建了很多控制器(controller),这些相当于一个状态机,用来控制 Pod 的具体状态和行为的,我们成为 Pod 控制器。咳咳咳,在 Kubernetes 中内建控制器有如下几种,它们的功能和特点各不相同。

  • ReplicationController

  • ReplicaSet

  • Deployment

  • DaemonSet

  • StateFulSet

  • Job

  • CronJob

  • Horizontal Pod Autoscaling

Kubernetes 资源控制器使用指南_linux_03

简单介绍

与所有其它 Kubernetes 配置一样,我们创建资源控制器的时候也是需要,如 apiVersion、kind 和 metadata 等字段的,通过使用这些字段组成配置文件,来完成服务部署的。

  • Pod 模板

    • .spec.template

书写配置文件的时候,.spec.template 是一个 pod 模板,它的模式与 pod 完全相同,只是它是嵌套的,没有 apiVersion 或 kind 属性。

  • 设置标签

    • 优先级高 => .metadata.labels

    • 优先级低 => .spec.template.metadata.labels

    • 没有指定 .metadata.labels 则默认为 .spec.template.metadata.labels

控制器本身是可以设置标签的,用于做分类或者控制选择时使用。

  • Pod 选择器

    • .spec.selector

    • .spec.template.metadata.labels

没有指定 .spec.selector 则默认为 .spec.template.metadata.labels 其中 .spec.selector 字段是一个标签选择器,所创建出来的标签用于选择则出匹配的 Pod。如果指定了 .spec.template.metadata.labels 字段,则必须和 .spec.selector 字段相同,否则在创建的时候将会被 API 拒绝。

  • 多个副本

    • .spec.replicas

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

资源类型

下表列出的是 kubernetes 中所有支持的类型和缩写的别名。

 

资源类型 缩写别名
clusters -
componentstatuses cs
configmaps cm
daemonsets ds
deployments deploy
endpoints ep
event ev
horizontalpodautoscalers hpa
ingresses ing
jobs -
limitranges limits
namespaces ns
networkpolicies -
nodes no
statefulsets -
persistentvolumeclaims pvc
persistentvolumes pv
pods po
podsecuritypolicies psp
podtemplates -
replicasets rs
replicationcontrollers rc
resourcequotas quota
cronjob -
secrets -
serviceaccount sa
services svc
storageclasses -
thirdpartyresources -

 

格式化输出

要以特定的格式向终端窗口输出详细信息,可以在 kubectl 命令中添加 -o 或者 -output 标志。

-o=custom-columns=<spec>   #使用逗号分隔的自定义列列表打印表格
-o=custom-columns-file=<filename> #使用文件中的自定义列模板打印表格
-o=json    #输出JSON 格式的API对象
-o=jsonpath=< template>       #打印jsonpath表达式中定义的字段
-o=jsonpath-file=<filename>   #打印由文件中的jsonpath表达式定义的字段
-o=name  #仅打印资源名称 
-o=wide  #以纯文本格式输出任何附加信息,对于Pod ,包含节点名称
-o=yaml  #输出 YAML 格式的 API 对象 

Kubectl 命令

Kubectl 详细输出和调试,使用 -v 或 --v 标志跟着一个整数来指定日志级别。

--v=0  #总是对操作人员可见。
--v=1  #合理的默认日志级别,如果您不需要详细输出。
--v=2  #可能与系统的重大变化相关的,有关稳定状态的信息和重要的日志信息。这是对大多数系统推荐的日志级别。
--v=3  #有关更改的扩展信息。
--v=4  #调试级别详细输出。
--v=6  #显示请求的资源。
--v=7  #显示 HTTP 请求的 header。
--v=8  #显示 HTTP 请求的内容。

ReplicationController

官方建议使用 RS 来代替 RC,完成部署服务的工作!

知识要点

与用户直接创建 pod 的情况不同,RC 能够替换因某些原因被删除或被终止的 pod,例如内核升级等情况。因此,建议使用 RC 来创建 pod,这样我们就可以不用人为的管理令人头痛的 pod 了。

  • 功能特点

ReplicationController 简称为 RC,同时也是 kubectl 命令的快捷方式简写使用方式,是用来确保容器应用的副本数始终保持在用户定义的副本数。如果有容器异常退出的话,其会自动创建新的 Pod 来替代,而如果有异常多出来的容器也会被自动回收掉的。

  • 工作方式

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

示例运行

下面示例中,我们配置了运行 nginx web 服务的三个副本,可以通过运行以下命令来运行示例任务。

# [ReplicationController]
# .spec.template是.spec的唯一必需字段
# 只允许.spec.template.spec.restartPolicy等于Always

apiVersion: v1
kind: ReplicationController
metadata:
  name: nginx
  namespace: prod
spec:
  replicas: 3
  selector:
    app: nginx
  spec:
    metadata:
      name: nginx
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx
          ports:
            - containerPort: 80

奇怪的是,我们创建了三个 Pod,但发现此时并没有一个 Pod 正在运行,这是因为可以正在拉取镜像。

# 部署服务
$ kubectl apply -f ./replication.yaml
replicationcontroller/nginx created

# 检查RC的状态
$ kubectl describe rc/nginx
Pods Status:    3 Running / 0 Waiting / 0 Succeeded / 0 Failed

ReplicaSet

官方建议使用 RS 来代替 RC,完成部署服务的工作!

知识要点

  • 功能特点

官方在新版本的 Kubernetes 中建议使用 RS 来取代 RC,而且 RS 跟 RC 的唯一区别是在选择器的支持上。其中 RS 支持集集合(selector)的选择器,其就意味着其支持通过标签进行 Pod 的选择,而 RC 仅支持基于相等选择器。

  • 工作方式

RS 确保任何时间都有指定数量的 Pod 副本在运行,而且大多数支持 RC 的 kubectl 命令也支持 RS,但是 rolling-update 这个命令是个例外。如果你需要使用滚动更新功能,请考虑使用 Deployment 来创建 Pod。

虽然 RS 可以独立的使用和创建, 然而 Deployment 是一个更高级的概念,它管理 RS 并向 Pod 提供声明式的更新以及许多其他有用的功能,用作协调 Pod 创建、删除和更新的机制。当你使用 Deployment 的时候,不必担心还要管理它们创建的 RS 控制器。因此,建议使用 Deployment 而不是直接使用 ReplicaSet,除非你需要自定义更新业务流程或根本不需要更新。

示例运行

下面示例中,我们配置了运行 php-redis 服务的三个副本,可以通过运行以下命令来运行示例任务。

# [ReplicaSet]
# .spec.template是.spec的唯一必需字段
# 只允许.spec.template.spec.restartPolicy等于Always

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: frontend
  namespace: prod
  labels:
    app: guestbook
    tier: frontend
spec:
  replicas: 3
  selector:
    matchLabels:
      tier: frontend
  template:
    metadata:
      labels:
        tier: frontend
    spec:
      containers:
      - name: php-redis
        image: gcr.io/google_samples/gb-frontend:v3
        - name: GET_HOSTS_FROM
          value: dns
        ports:
          containerPort: 80
        env:

将此清单保存到 frontend.yaml 中,并将其提交到 Kubernetes 集群,应该就能创建 yaml 文件所定义的 ReplicaSet 及其管理的 Pod。

# 部署服务
$ kubectl create -f ./frontend.yaml
replicaset.apps/frontend created

# 检查RC的状态
$ kubectl describe rs/frontend

Deployment

官方推荐的 Pod 部署方式,所以以后创建 Pod 的话,优先选择!

知识要点

  • 功能特点

Deployment 为 Pod 和 ReplicaSet 提供了一个声明式定义的方法,用来替代以前的 RC 来方便的管理应用。而定义方式分为,命令式(RS)和声明式(Deployment)两种,前者侧重于考虑如何实现,而后者侧重于定义想要什么。

  • 应用场景

    • 定义 Deployment 来创建 Pod 和 RS

    • 滚动升级和回滚应用

    • 扩容和缩容

    • 暂停和继续 Deployment

  • RS 和 Deployment 的关联

Kubernetes 资源控制器使用指南_linux_04

示例运行 - 创建

下面示例中,我们配置了运行 nginx web 服务的三个副本,可以通过运行以下命令来运行示例任务。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  namespace: prod
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:1.7.9
          ports:
            - containerPort: 80

通过运行以下命令创建 Deployment。

# 部署服务
$ kubectl apply -f ./nginx-deployment.yaml

# 检查Deployments的状态
$ kubectl get deployments
NAME               DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   3         3         3            3           18s

#上述输出字段含义解释
NAME: 列出了集群中Deployments的名称
DESIRED: 显示应用程序的期望状态的所需副本数
CURRENT: 显示当前正在运行的副本数
UP-TO-DATE: 显示已更新以实现期望状态的副本数
AVAILABLE: 显示应用程序可供用户使用的副本数
AGE: 显示应用程序运行的时间量

# 查看Deployment展开状态
$ kubectl rollout status deployment/nginx-deployment

# 检查RC的状态
$ kubectl describe rs
NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-75675f5897   3         3         3       18s

# 查看每个Pod自动生成的标签
$ kubectl get pods --show-labels
NAME                                READY   STATUS   LABELS
nginx-deployment-75675f5897-7ci7o   1/1     Running  app=nginx,pod-template-hash=3123191453
nginx-deployment-75675f5897-kzszj   1/1     Running  app=nginx,pod-template-hash=3123191453
nginx-deployment-75675f5897-qqcnn   1/1     Running  app=nginx,pod-template-hash=3123191453

示例运行 - 更新

让我们更新 nginx 的 Pods,使用 nginx:1.9.1 镜像来代替之前的旧镜像。

# 更新nginx服务
$ kubectl set image deployment/nginx-deployment nginx=nginx:1.9.1
deployment/nginx-deployment image updated

当然,我们也可以使用 edit 命令来编辑 Deployment 配置文件,达到同样的效果。

# 编辑Deployment文件
$ kubectl edit deployment/nginx-deployment
deployment/nginx-deployment edited

使用 rollout 命令来,查看其展开的状态。

# 查看展开状态
$ kubectl rollout status deployment/nginx-deployment
Waiting for rollout to finish: 2 out of 3 new replicas have been updated...
deployment/nginx-deployment successfully rolled out

# 检查RC的状态
$ kubectl get rs
NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-1564180365   3         3         3       6s
nginx-deployment-2035384211   0         0         0       36s

# 查看Pod的状态(生成了新的Pods)
$ kubectl get pods
NAME                                READY     STATUS    RESTARTS   AGE
nginx-deployment-1564180365-khku8   1/1       Running   0          14s
nginx-deployment-1564180365-nacti   1/1       Running   0          14s
nginx-deployment-1564180365-z9gth   1/1       Running   0          14s
  • Deployment 可确保在更新时仅关闭一定数量的 Pods,默认情况下,它确保至少 75% 所需 Pods 是运行的,即有 25% 的最大不可用。Deployment 还确保仅创建一定数量的 Pods 高于期望的 Pods 数,默认情况下,它可确保最多增加 25% 期望 Pods 数。出现上述情况的时候,多是在进行扩容、缩容、滚动升级和回滚应用时出现。

  • 可以看到,当第一次创建 Deployment 的时候,它创建了一个 ReplicaSet 并将其直接扩展至 3 个副本。更新 Deployment 时,它创建了一个新的 ReplicaSet ,并将其扩展为 1,然后将旧 ReplicaSet 缩小到 2,以便至少有 2 个 Pod 可用,并且最多创建 4 个 Pod。然后,它继续向上和向下扩展新的和旧的 ReplicaSet ,具有相同的滚动更新策略。最后,将有 3 个可用的副本在新的 ReplicaSet 中,旧 ReplicaSet 将缩小到 0。

# 获取Deployment的更多信息
$ kubectl describe deployments

示例运行 - 回滚

  • 当 Deployment 不稳定或遇到故障的的时候,例如循环崩溃,这时可能就需要回滚 Deployment 了。在默认情况下,所有 Deployment 历史记录都保留在系统中,以便可以随时回滚。当然,可以通过修改历史记录限制来更改该限制。当回滚到较早的修改版时,只有 Deployment Pod 模板部分会回滚。这是因为当 Deployment Pod 模板(.spec.template)发生更改时,才会创建新修改版本。例如,如果更新模板的标签或容器镜像,其他更新,如扩展 Deployment。

  • 假设在更新 Deployment 时犯了一个拼写错误,将镜像名称命名为 nginx:1.91 而不是 nginx:1.9.1,这样我们更新 Pod 的时候就会出现错误。

# 设置镜像来更新服务
$ kubectl set image deployment/nginx-deployment nginx=nginx:1.91
deployment.apps/nginx-deployment image updated

# 遇到问题展开状态来验证
# 通过如下命令来查看Deployment是否完成
$ kubectl rollout status deployment/nginx-deployment
Waiting for rollout to finish: 1 out of 3 new replicas have been updated...

查看旧 ReplicaSets 和 Pod 的时候,会发现由新 ReplicaSet 创建的 1 个 Pod 卡在镜像拉取循环中,要解决此问题,需要回滚到以前稳定的 Deployment 版本。

# 查看RS状态
$ kubectl get rs
NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-1564180365   3         3         3       25s
nginx-deployment-2035384211   0         0         0       36s
nginx-deployment-3066724191   1         1         0       6s

# 查看Pod状态
$ kubectl get pods
NAME                                READY     STATUS             RESTARTS   AGE
nginx-deployment-1564180365-70iae   1/1       Running            0          25s
nginx-deployment-1564180365-jbqqo   1/1       Running            0          25s
nginx-deployment-1564180365-hysrc   1/1       Running            0          25s
nginx-deployment-3066724191-08mng   0/1       ImagePullBackOff   0          6s

按照如下步骤,检查 Deployment 回滚历史。

# 检查Deployment修改历史
$ kubectl rollout history deployment/nginx-deployment
deployments "nginx-deployment"
REVISION    CHANGE-CAUSE
1           kubectl apply --filename=https://k8s.io/examples/controllers/nginx-deployment.yaml --record=true
2           kubectl set image deployment.v1.apps/nginx-deployment nginx=nginx:1.9.1 --record=true
3           kubectl set image deployment.v1.apps/nginx-deployment nginx=nginx:1.91 --record=true

# 查看修改历史的详细信息
$ kubectl rollout history deployment/nginx-deployment --revision=2
deployments "nginx-deployment" revision 2
  Labels:       app=nginx
          pod-template-hash=1159050644
......

按照下面给出的步骤将 Deployment 从当前版本回滚到以前的版本,即版本 2。现在,Deployment 将回滚到以前的稳定版本。如所见,Deployment 回滚事件回滚到修改版 2 是从 Deployment 控制器生成的。

# 现在已决定撤消当前展开并回滚到以前的版本
$ kubectl rollout undo deployment/nginx-deployment
deployment/nginx-deployment

# 通过下面命令来回滚到特定修改版本
$ kubectl rollout undo deployment/nginx-deployment --to-revision=2
deployment/nginx-deployment

检查回滚是否成功或者 Deployment 是否正在运行,可以通过下面命令查看。

# 查看deployment状态
$ kubectl get deployment nginx-deployment
NAME               DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   3         3         3            3           30m

# 获取Deployment描述信息
$ kubectl describe deployment nginx-deployment

示例运行 - 缩放

我们可以通过使用如下指令,缩放 Deployment。假设启用水平自动缩放 Pod 在集群中,可以为 Deployment 设置自动缩放器,并选择最小和最大 要基于现有 Pods 的 CPU 利用率运行的 Pods。

# 扩容(3->10)
$ kubectl scale deployment/nginx-deployment --replicas=10
deployment.apps/nginx-deployment scaled

# 水平缩放
$ kubectl autoscale deployment/nginx-deployment --min=10 --max=15 --cpu-percent=80
deployment/nginx-deployment scaled

DaemonSet

介绍 DaemonSet 的基本概念和使用方法

知识要点

  • 基本概念

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

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

  • 运行集群存储 daemon,例如在每个 Node 上运行

    • glusterd、ceph

  • 在每个 Node 上运行日志收集 daemon

    • fluentd、logstash

  • 在每个 Node 上运行监控 daemon

    • Prometheus Node Exporter、collectd、Datadog

Kubernetes 资源控制器使用指南_python_05

  • 实现原理

Kubernetes 资源控制器使用指南_docker_06

  • 调度逻辑

仅在某些节点上运行 Pod:如果指定了 .spec.template.spec.nodeSelector,DaemonSet Controller 将在能够与 Node Selector 匹配的节点上创建 Pod。类似这种情况,可以指定 .spec.template.spec.affinity,然后 DaemonSet Controller 将在能够与 node Affinity 匹配的节点上创建 Pod。如果根本就没有指定,则 DaemonSet Controller 将在所有节点上创建 Pod。

示例运行

每个 Node 节点都运行一个 nginx 服务。

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: deamonset-example
  labels:
    app: daemonset
spec:
  selector:
    matchLabels:
    name: deamonset-example
  template:
    metadata:
      labels:
        name: deamonset-example
    spec:
      containers:
        - name: daemonset-example
          image: hub.escapelife/nginx:v1
# 创建DaemonSet的Pod
kubectl apply -f ./daemonset-test.yaml

# 查看服务状态
kubectl get daemonsets
kubectl get pods --namespace default

每个 Node 节点都运行一个 fluentd 日志监控服务。

apiVersion: apps/v1
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:
        - key: node-role.kubernetes.io/master
          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
# 创建DaemonSet的Pod
kubectl apply -f https://k8s.io/examples/controllers/daemonset.yaml

# 查看服务状态
kubectl get daemonsets.apps fluentd-elasticsearch --namespace kube-system
kubectl get pods --namespace kube-system --label name=fluentd-elasticsearch

StatefulSet

StatefulSet 是用来管理有状态应用的工作负载 API 对象。

在 k8s 中,ReplicaSet 和 Deployment 主要是用于处理无状态的服务,无状态服务的需求往往非常简单并且轻量,每一个无状态节点存储的数据在重启之后就会被删除。但是如果我们需要保留,那该怎么办呢?所以为了满足有状态的服务这一特殊需求,StatefulSet 就是 Kubernetes 为了运行有状态服务引入的资源,例如 MySQL 等。

知识要点

  • 基本概念

产生 StatefulSet 的用途主要是用于管理有状态应用的工作负载对象,与 ReplicaSet 和 Deployment 这两个对象不同,StatefulSet 不仅能管理 Pod 的对象,还它能够保证这些 Pod 的顺序性和唯一性。以及,其会为每个 Pod 设置一个单独的持久标识 ID 号,这些用于标识序列的标识符在发生调度时也不会丢失,即无论怎么调度,每个 Pod 都有一个永久不变的 ID。

Kubernetes 资源控制器使用指南_数据库_07

  • 功能特点

StatefulSets 最为重要的功能就是稳定,稳定意味着 Pod 调度或重调度的整个过程是有持久性的。如果应用程序不需要任何稳定的标识符或有序的部署、删除或伸缩,则应该使用由一组无状态的副本控制器提供的工作负载来部署应用程序,比如 Deployment 或者 ReplicaSet 可能更适用于您的无状态应用部署需要。

  • 稳定的、唯一的网络标识符

  • 稳定的、持久的存储

  • 有序的、优雅的部署和缩放

  • 有序的、自动的滚动更新

需要额外注意的是,删除或者收缩 StatefulSet 并不会删除它关联的存储卷,这样做是为了保证数据安全。而且,StatefulSet 当前需要无头服务来负责 Pod 的网络标识,所以需要事先创建好。

Kubernetes 资源控制器使用指南_kubernetes_08

  • 访问方式

我们在下面的示例中是使用 StatefulSet 和对应的无头服务来做演示的,当 StatefulSet Pod 创建之后其具有唯一的标识,该标识包括顺序标识、稳定的网络标识和稳定的存储。该标识和 Pod 是绑定的,不管它被调度在哪个节点上。

  • 有序索引

对于具有 N 个副本的 StatefulSet,StatefulSet 中的每个 Pod 将被分配一个整数序号,从 0 到 N-1,该序号在 StatefulSet 上是唯一的。

  • 稳定的网络 ID

在 StatefulSet 中的每个 Pod 根据 StatefulSet 的名称和 Pod 的序号派生出它的主机名。组合主机名的格式为 名称 (序号)。上例将会创建三个名称分别为 web-0、web-1、web-2 的 Pod。StatefulSet 可以使用 headless 服务 控制它的 Pod 的网络域。管理域的这个服务的格式为: 服务名称 (命名空间).svc.cluster.local,其中 cluster.local 是集群域。一旦每个 Pod 创建成功,就会得到一个匹配的 DNS 子域,格式为: 名称 (所属服务的 DNS 域名),其中所属服务由 StatefulSet 的 serviceName 域来设定。

  • 稳定的存储

在 Kubernetes 中 StatefulSet 模式会为每个 VolumeClaimTemplate 创建一个 PersistentVolumes。请注意,当 Pod 或者 StatefulSet 被删除时,与 PersistentVolumeClaims 相关联的 PersistentVolume 并不会被删除。要删除它必须通过手动方式来完成。

Kubernetes 资源控制器使用指南_数据库_09

  • 管理策略

对于某些分布式系统来说,StatefulSet 的顺序性保证是不必要和/或者不应该的。这些系统仅仅要求唯一性和身份标志。为了解决这个问题,在 Kubernetes 1.7 中引入了 .spec.podManagementPolicy。

OrderedReady 的 Pod 管理策略:是 StatefulSets 的默认选项,它告诉 StatefulSet 控制器遵循上文展示的顺序性保证。

Parallel 的 Pod 管理策略:是告诉 StatefulSet 控制器并行的终止所有 Pod,在启动或终止另一个 Pod 前,不必等待这些 Pod 变成 Running 和 Ready 或者完全终止状态。

apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  clusterIP: None
  selector:
    app: nginx
  ports:
  - port: 80
    name: web

---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  serviceName: "nginx"
  podManagementPolicy: "Parallel"
  replicas: 2
  ......

示例运行

名为 nginx 的 Headless Service 用来控制网络域名。

名为 web 的 StatefulSet 有一个 Spec,它表明将在独立的 2 个 Pod 副本中启动 nginx 容器。

apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
    - port: 80
      name: web
  clusterIP: None
  selector:
    app: nginx

---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  serviceName: "nginx"
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: k8s.gcr.io/nginx-slim:0.8
          ports:
            - containerPort: 80
              name: web
          volumeMounts:
            - name: www
              mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
    - metadata:
        name: www
      spec:
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 1Gi

创建和查看 - StatefulSet

# 创建定义在web.yaml中的Headless Service和StatefulSet
$ sudo kubectl apply -f web.yaml
service/nginx created
statefulset.apps/web created

# 验证是否成功创建
$ sudo kubectl get service nginx
NAME      TYPE         CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
nginx     ClusterIP    None         <none>        80/TCP    12s

$ sudo kubectl get statefulset web
NAME      DESIRED   CURRENT   AGE
web       2         1         20s
# 动态查看创建状态,发现是顺序创建Pod的
# 我们可以动态的看到web-0的Pod先创建完毕之后,web-1才会被启动
$ sudo kubectl get pods -w -l app=nginx
NAME      READY     STATUS              RESTARTS   AGE
web-0     0/1       Pending             0          0s
web-0     0/1       Pending             0          0s
web-0     0/1       ContainerCreating   0          0s
web-0     1/1       Running             0          19s
web-1     0/1       Pending             0          0s
web-1     0/1       Pending             0          0s
web-1     0/1       ContainerCreating   0          0s
web-1     1/1       Running             0          18s

# 获取StatefulSet的Pod
$ sudo kubectl get pods -l app=nginx
NAME      READY     STATUS    RESTARTS   AGE
web-0     1/1       Running   0          1m
web-1     1/1       Running   0          1m

查看绑定的特点 - StatefulSet

# 使用稳定的网络身份标识
$ sudo kubectl exec web-0 -- bash -c \
    "for i in 0 1; do kubectl exec web-$i -- sh -c 'hostname'; done"
web-0

$ sudo kubectl exec web-1 -- bash -c \
    "for i in 0 1; do kubectl exec web-$i -- sh -c 'hostname'; done"
web-1
# 稳定的网络身份标识
$ sudo kubectl run -i --tty --image busybox:1.28 \
    dns-test --restart=Never --rm nslookup web-0.nginx
Server:    10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

Name:      web-0.nginx
Address 1: 10.244.1.6

nslookup web-1.nginx
Server:    10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

Name:      web-1.nginx
Address 1: 10.244.2.6
# 写入稳定的存储
$ sudo kubectl get pvc -l app=nginx
NAME        STATUS    VOLUME                                     CAPACITY   ACCESSMODES   AGE
www-web-0   Bound     pvc-15c268c7-b507-11e6-932f-42010a800002   1Gi        RWO           48s
www-web-1   Bound     pvc-15c79307-b507-11e6-932f-42010a800002   1Gi        RWO           48s

删除 StatefulSet 中所有的 Pod 之后,我们使用上述查看网络身份标识的命令再次查看,发现 Pod 的序号、主机名、SRV 条目和记录名称没有改变,但和 Pod 相关联的 IP 地址可能发生了改变。因为我们使用的是 StatefulSet 的模式,其他模式不会有这个问题。这就是为什么不要在其他应用中使用 StatefulSet 中的 Pod 的 IP 地址进行连接,这点很重要。

$ sudo kubectl delete pod -l app=nginx
pod "web-0" deleted
pod "web-1" deleted

# 对应的服务访问地址,我们可以间接访问
web-0.nginx.default.svc.cluster.local
web-1.nginx.default.svc.cluster.local
扩容/缩容 - StatefulSet
# 扩容
$ sudo kubectl scale sts web --replicas=5
statefulset.apps/web scaled

# 缩容
$ sudo kubectl patch sts web -p '{"spec":{"replicas":3}}'
statefulset.apps/web patched
# 五个存储仍然存在
$ sudo kubectl get pvc -l app=nginx
NAME        STATUS    VOLUME                                     CAPACITY   ACCESSMODES   AGE
www-web-0   Bound     pvc-15c268c7-b507-11e6-932f-42010a800002   1Gi        RWO           13h
www-web-1   Bound     pvc-15c79307-b507-11e6-932f-42010a800002   1Gi        RWO           13h
www-web-2   Bound     pvc-e1125b27-b508-11e6-932f-42010a800002   1Gi        RWO           13h
www-web-3   Bound     pvc-e1176df6-b508-11e6-932f-42010a800002   1Gi        RWO           13h
www-web-4   Bound     pvc-e11bb5f8-b508-11e6-932f-42010a800002   1Gi        RWO           13h

更新 - StatefulSet

StatefulSet 里的 Pod 采用和序号相反的顺序更新。在更新下一个 Pod 前,StatefulSet 控制器终止每个 Pod 并等待它们变成 Running 和 Ready。请注意,虽然在顺序后继者变成 unning 和 Ready 之前 StatefulSet 控制器不会更新下一个 Pod,但它仍然会重建任何在更新过程中发生故障的 Pod,使用的是它们当前的版本。已经接收到更新请求的 Pod 将会被恢复为更新的版本,没有收到请求的 Pod 则会被恢复为之前的版本。像这样,控制器尝试继续使应用保持健康并在出现间歇性故障时保持更新的一致性。

# Patch web StatefulSet 来执行 RollingUpdate 更新策略
$ sudo kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate"}}}'
statefulset.apps/web patched

# 在一个终端窗口中 patch web StatefulSet 来再次的改变容器镜像
$ sudo kubectl patch statefulset web --type='json' \
    -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/image", \
    "value":"gcr.io/google_containers/nginx-slim:0.8"}]'
statefulset.apps/web patched

删除 - StatefulSet

# 删除StatefulSet
kubectl delete statefulset web
statefulset.apps "web" deleted

# 删除Pod
kubectl delete pod web-0
pod "web-0" deleted

区别选择 - sts/deploy

  • 访问方式区别

Kubernetes 资源控制器使用指南_kubernetes_10Kubernetes 资源控制器使用指南_python_11

  • 综合对比区别

Kubernetes 资源控制器使用指南_数据库_12

  • 综上选择总结

    • 如果是不需额外数据依赖或者状态维护的部署,或者 replicas 是 1,优先考虑使用 Deployment;

    • 如果单纯的要做数据持久化,防止 pod 宕掉重启数据丢失,那么使用 pv/pvc 就可以了;

    • 如果要打通 app 之间的通信,而又不需要对外暴露,使用 headlessService 即可;

    • 如果需要使用 service 的负载均衡,不要使用 StatefulSet,尽量使用 clusterIP 类型,用 serviceName 做转发;

    • 如果是有多 replicas,且需要挂载多个 pv 且每个 pv 的数据是不同的,因为 pod 和 pv 之间是一一对应的,如果某个 pod 挂掉再重启,还需要连接之前的 pv,不能连到别的 pv 上,考虑使用 StatefulSet;

    • 能不用 StatefulSet,就不要用;

Job/CronJob

任务计划/周期性任务计划

知识要点

  • 基本概念

Job 负责批处理任务,即仅执行一次的任务,它保证批处理任务的一个或多个 Pod 成功结束。

Kubernetes 资源控制器使用指南_数据库_13

Cron Job 管理基于时间的 Job,即在给定时间点只运行一次或周期性地在给定时间点运行。

Kubernetes 资源控制器使用指南_docker_14

  • 执行特点

Job 会创建一个或者多个 Pods,并确保指定数量的 Pods 成功终止。随着 Pods 成功结束,Job 跟踪记录成功完成的 Pods 个数。当数量达到指定的成功个数阈值时,任务结束。删除 Job 的操作会清除所创建的全部 Pods。当第一个 Pod 失败或者被删除(比如因为节点硬件失效或者重启)时,Job 对象会启动一个新的 Pod。

CronJob 对于创建周期性的、反复重复的任务很有用,例如执行数据备份或者发送邮件。CronJobs 也可以用来计划在指定时间来执行的独立任务,例如计划当集群看起来很空闲时 执行某个 Job。

Kubernetes 资源控制器使用指南_docker_15Kubernetes 资源控制器使用指南_linux_16

  • Job 的并行执行

    • 不设置 .spec.completions,默认值为 .spec.parallelism

    • 多个 Pod 之间必须相互协调,或者借助外部服务确定每个 Pod 要处理哪个工作条目

    • 每个 Pod 都可以独立确定是否其它 Pod 都已完成,进而确定 Job 是否完成

    • 一旦至少 1 个 Pod 成功完成,并且所有 Pod 都已终止,即可宣告 Job 成功完成

    • .spec.completions 字段设置为非 0 的正数值

    • 当 .spec.completions 等于 1 时一个成功的 Pod 就被视为完成

    • 通常只启动一个 Pod,除非该 Pod 失败

    • 当 Pod 成功终止时,立即视 Job 为完成状态

    • 非并行 Job

    • 具有确定完成计数的并行 Job

    • 带工作队列的并行 Job

Kubernetes 资源控制器使用指南_kubernetes_17

示例运行

  • Job

    • .spec.completions 标志 Job 结束需要成功运行的 Pod 个数,默认为 1

    • .spec.parallelism 标志并行运行的 Pod 的个数,默认为 1

    • .spec.activeDeadlineSeconds 标志失败 Pod 的重试最大时间,超过这个时间不会继续重试

apiVersion: batch/v1
kind: Job
metadata:
  name: pi
spec:
  restartPolicy: OnFailure
  backoffLimit: 5 # timeout
  activeDeadlineSeconds: 100 # timeout
  ttlSecondsAfterFinished: 100 # ttl
  template:
    spec:
      containers:
        - name: pi
          image: perl
          command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"]
      restartPolicy: Never
  backoffLimit: 4
# 使用下面的命令来运行此示例
$ sduo kubectl apply -f https://kubernetes.io/examples/controllers/job.yaml
job.batch/pi created

# 来检查Job的状态
$ sudo kubectl describe jobs/pi

# 查看Job对应的已完成的Pods
$ sudo kubectl get pods

# 查看PI输出结果
$ pods=$(suod kubectl get pods --selector=job-name=pi --output=jsonpath={.items..metadata.name})
$ sduo kubectl logs $pods
3.141592653589793238462......
  • CronJob

    • .spec.schedule:指定任务运行周期(调度)

    • .spec.jobTemplate:指定需要运行的任务(Job 模板)

    • .spec.startingDeadlineSeconds:启动 Job 的期限(秒级别)

    • .spec.concurrencyPolicy:并发策略(Allow/Forbid/Replace)

    • .spec.suspend:挂起

    • .spec.successfulJobsHistoryLimit:指定了可以保留多少完成的 Job(历史限制;默认 3)

    • .spec.failedJobsHistoryLimit:指定了可以保留多少失败的 Job(历史限制;默认 1)

apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: hello
spec:
  schedule: "*/1 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
            - name: hello
              image: busybox
              args:
                - /bin/sh
                - -c
                - date; echo Hello from the Kubernetes cluster
          restartPolicy: OnFailure
# 创建
$ sudo kubectl create -f cronjob.yaml
cronjob "hello" created

# 查看
$ sudo kubectl get jobs
NAME               DESIRED   SUCCESSFUL   AGE
hello-1202039034   1         1            49s

# 查看
$ sudo kubectl get cronjob
NAME      SCHEDULE      SUSPEND   ACTIVE    LAST-SCHEDULE
hello     */1 * * * *   False     0         <none>

# 输出
$ pods=$(sudo kubectl get pods --selector=job-name=hello-1202039034 --output=jsonpath={.items..metadata.name})
$ sudo kubectl logs $pods
Mon Aug 29 21:34:09 UTC 2020
Hello from the Kubernetes cluster

# 删除
$ sudo kubectl delete cronjob hello
cronjob "hello" deleted

一旦不再需要 Cron Job,简单地可以使用 kubectl 命令删除它。这将会终止正在创建的 Job。然而,运行中的 Job 将不会被终止,不会删除 Job 或它们的 Pod。为了清理那些 Job 和 Pod,需要列出该 Cron Job 创建的全部 Job,然后删除它们。

# 逐个删除
$ kubectl get jobs
NAME               DESIRED   SUCCESSFUL   AGE
hello-1201907962   1         1            11m
hello-1202039034   1         1            8m
...

$ kubectl delete jobs hello-1201907962 hello-1202039034 ...
job "hello-1201907962" deleted
job "hello-1202039034" deleted
...

# 强制删除
$ sudo kubectl delete jobs --all