大家好,我是腾意。

自主式Pod对象由调度器绑定至目标工作节点后即由相应节点上的kubelet负责监控其容器的存活性,容器主进程崩溃后,kubelet能够自动重启相应的容器。不过,kubelet对非主进程崩溃类的容器错误却无从感知,这依赖于用户为Pod资源对象自定义的存活性探测(liveness probe)机制,以便kubelet能够探知到此类故障。然而,在Pod对象遭到意外删除,或者工作节点自身发生故障时,又该如何处理呢?

kubelet是Kubernetes集群节点代理程序,它在每个工作节点上都运行着一个实例。因而,集群中的某工作节点发生故障时,其kubelet也必将不再可用,于是,节点上的Pod资源的健康状态将无从得到保证,也无法再由kubelet重启。此种场景中的Pod存活性一般要由工作节点之外的Pod控制器来保证。事实上,遭到意外删除的Pod资源的恢复也依赖于其控制器。

Pod控制器由master的kube-controller-manager组件提供,常见的此类控制器有ReplicationController、ReplicaSet、Deployment、DaemonSet、StatefulSet、Job和CronJob等,它们分别以不同的方式管理Pod资源对象。实践中,对Pod对象的管理通常都是由某种控制器的特定对象来实现的,包括其创建、删除及重新调度等操作。本篇将逐一讲解常用的Pod控制器资源。

1.1 关于Pod控制器

我们可以把API Server类比成一个存储对象的数据库系统,它向客户端提供了API,并负责存储由用户创建的各种资源对象,至于各对象的当前状态如何才能符合用户期望的状态,则需要交由另一类称为控制器的组件来负责完成。Kubernetes提供了众多的控制器来管理各种类型的资源,如Node Lifecycle Controller、Namespace Controller、Service Controller和Deployment Controller等,它们的功用几乎可以做到见名知义。创建完成后,每一个控制器对象都可以通过内部的和解循环(reconciliation loop),不间断地监控着由其负责的所有资源并确保其处于或不断地逼近用户定义的目标状态。

尽管能够由kubelet为其提供自愈能力,但在节点宕机时,自主式Pod对象的重建式自愈机制则需要由Pod控制器对象负责提供,并且由它来负责实现生命周期中的各类自动管理行为,如创建及删除等。

1.1.1 Pod控制器概述

Master的各组件中,API Server仅负责将资源存储于etcd中,并将其变动通知给各相关的客户端程序,如kubelet、kube-scheduler、kube-proxy和kube-controller-manager等,kube-scheduler监控到处于未绑定状态的Pod对象出现时遂启动调度器为其挑选适配的工作节点,然而,Kubernetes的核心功能之一还在于要确保各资源对象的当前状态(status)以匹配用户期望的状态(spec),使当前状态不断地向期望状态“和解”(reconciliation)来完成容器应用管理,而这些则是kube-controller-manager的任务。kube-controller-manager是一个独立的单体守护进程,然而它包含了众多功能不同的控制器类型分别用于各类和解任务,如图5-1所示。

img

图5-1 kube-controller-manager及其控制器

img 提示 Kubernetes可用的控制器有attachdetach、bootstrapsigner、clusterrole-aggregation、cronjob、csrapproving、csrcleaner、csrsigning、daemonset、deployment、disruption、endpoint、garbagecollector、horizontalpodautoscaling、job、namespace、node、persistentvolume-binder、persistentvolume-expander、podgc、pvc-protection、replicaset、replication- controller、resourcequota、route、service、serviceaccount、serviceaccount-token、statefulset、tokencleaner和ttl等数十种。

创建为具体的控制器对象之后,每个控制器均通过API Server提供的接口持续监控相关资源对象的当前状态,并在因故障、更新或其他原因导致系统状态发生变化时,尝试让资源的当前状态向期望状态迁移和逼近。简单来说,每个控制器对象运行一个和解循环负责状态和解,并将目标资源对象的当前状态写入到其status字段中。控制器的“和解”循环如图5-2所示。

img

图5-2 控制器的“和解”循环

List-Watch是Kubernetes实现的核心机制之一,在资源对象的状态发生变动时,由API Server负责写入etcd并通过水平触发(level-triggered)机制主动通知给相关的客户端程序以确保其不会错过任何一个事件。控制器通过API Server的watch接口实时监控目标资源对象的变动并执行和解操作,但并不会与其他控制器进行任何交互,甚至彼此之间根本就意识不到对方的存在。

工作负载(workload)一类的控制器资源类型包括ReplicationController、ReplicaSet、Deployment、DaemonSet、StatefulSet、Job和CronJob等,它们分别代表了一种类型的Pod控制器资源,各类型的功用在3.1.1节中已经给出过说明。本章后面的篇幅主要介绍各控制器的特性及其应用,不过StatefulSet控制器依赖于存储卷资源,因此它将单独在存储卷之后的章节中给予介绍。

1.1.2 控制器与Pod对象

Pod控制器资源通过持续性地监控集群中运行着的Pod资源对象来确保受其管控的资源严格符合用户期望的状态,例如资源副本的数量要精确符合期望等。通常,一个Pod控制器资源至少应该包含三个基本的组成部分。

·标签选择器:匹配并关联Pod资源对象,并据此完成受其管控的Pod资源计数。

·期望的副本数:期望在集群中精确运行着的Pod资源的对象数量。

·Pod模板:用于新建Pod资源对象的Pod模板资源。

注意 DaemonSet用于确保集群中的每个工作节点或符合条件的每个节点上都运行着一个Pod副本,而不是某个精确的数量值,因此不具有上面组成部分中的第二项。

例如,一个如图5-3所示的Deployment控制器资源使用的标签选择器为“role=be-eshop”,它期望相关的Pod资源副本数量精确为3个,少于此数量的缺失部分将由控制器通过Pod模板予以创建,而多出的副本也将由控制器负责终止及删除。

img

图5-3 Deployment控制器示例

1.1.3 Pod模板资源

PodTemplate是Kubernetes API的常用资源类型,常用于为控制器指定自动创建Pod资源对象时所需的配置信息。因为要内嵌于控制器中使用,所以Pod模板的配置信息中不需要apiVersion和kind字段,但除此之外的其他内容与定义自主式Pod对象所支持的字段几乎完全相同,这包括metadata和spec及其内嵌的其他各个字段。Pod控制器类资源的spec字段通常都要内嵌replicas、selector和template字段,其中template即为Pod模板的定义。下面是一个定义在ReplicaSet资源中的模板资源示例:


apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: rs-example
spec:
  replicas: 2
  selector:
     matchLabels:
       app: rs-demo
  template:
    metadata:
      labels:
        app: rs-demo
    spec:
      containers:
      - name: myapp
        image: ikubernetes/myapp:v1
        ports:
        - name: http
          containerPort: 80

如上示例中,spec.template字段在定义时仅给出了metadata和spec两个字段,它的使用方法与自主式Pod资源完全相同。后面讲到控制器的章节时会反复用到Pod模板资源。

1.2 ReplicaSet控制器

Kubernetes较早期的版本中仅有ReplicationController一种类型的Pod控制器,后来的版本中陆续引入了更多的控制器实现,这其中就包括用来取代ReplicationController的新一代实现ReplicaSet。事实上,除了额外支持基于集合(set-based)的标签选择器,以及它的滚动更新(Rolling-Update)机制要基于更高级的控制Deployment实现之外,目前的ReplicaSet的其余功能基本上与ReplicationController相同。考虑到Kubernetes强烈推荐使用ReplicaSet控制器,且表示ReplicationController不久后即将废弃,这里就重点介绍ReplicaSet控制器。

1.2.1 ReplicaSet概述

ReplicaSet(简称RS)是Pod控制器类型的一种实现,用于确保由其管控的Pod对象副本数在任一时刻都能精确满足期望的数量。如图5-4所示,ReplicaSet控制器资源启动后会查找集群中匹配其标签选择器的Pod资源对象,当前活动对象的数量与期望的数量不吻合时,多则删除,少则通过Pod模板创建以补足,等Pod资源副本数量符合期望值后即进入下一轮和解循环。

img

图5-4 ReplicaSet的控制循环(图片来源:《Kubernetes in action》)

ReplicaSet的副本数量、标签选择器甚至是Pod模板都可以随时按需进行修改,不过仅改动期望的副本数量会对现存的Pod副本产生直接影响。修改标签选择器可能会使得现有的Pod副本的标签变得不再匹配,此时ReplicaSet控制器要做的不过是不再计入它们而已。另外,在创建完成后,ReplicaSet也不会再关注Pod对象中的实际内容,因此Pod模板的改动也只会对后来新建的Pod副本产生影响。

相比较于手动创建和管理Pod资源来说,ReplicaSet能够实现以下功能。

·确保Pod资源对象的数量精确反映期望值:ReplicaSet需要确保由其控制运行的Pod副本数量精确吻合配置中定义的期望值,否则就会自动补足所缺或终止所余。

·确保Pod健康运行:探测到由其管控的Pod对象因其所在的工作节点故障而不可用时,自动请求由调度器于其他工作节点创建缺失的Pod副本。

·弹性伸缩:业务规模因各种原因时常存在明显波动,在波峰或波谷期间,可以通过ReplicaSet控制器动态调整相关Pod资源对象的数量。此外,在必要时还可以通过HPA(HroizontalPodAutoscaler)控制器实现Pod资源规模的自动伸缩。

1.2.2 创建ReplicaSet

类似于Pod资源,创建ReplicaSet控制器对象同样可以使用YAML或JSON格式的清单文件定义其配置,而后使用相关的创建命令来完成资源创建。前面1.1.3节中给出的示例清单就是一个简单的ReplicaSet的定义。它也由kind、apiVersion、metadata、spec和status这5个一级字段组成,其中status为只读字段,因此需要在清单文件中配置的仅为前4个字段。它的spec字段一般嵌套使用以下几个属性字段。

·replicas:期望的Pod对象副本数。

·selector"Object":当前控制器匹配Pod对象副本的标签选择器,支持matchLabels和matchExpressions两种匹配机制。

·template"Object":用于补足Pod副本数量时使用的Pod模板资源。

·minReadySeconds:新建的Pod对象,在启动后的多长时间内如果其容器未发生崩溃等异常情况即被视为“就绪”;默认为0秒,表示一旦就绪性探测成功,即被视作可用。

将1.1.3节中的示例保存于资源清单文件中,例如rs-example.yaml,而后即可使用如下命令将其创建:


~]$ kubectl apply -f rs-example.yaml
replicaset.apps "rs-example" created

集群中当前没有标签为“app:rs-demo”的Pod资源存在,因此rs-example需要按照replicas字段的定义创建它们,名称以其所属的控制器名称为前缀。这两个Pod资源目前都处于ContainerCreating状态,即处于容器创建过程中,待创建过程完成后,其状态即转为Running,Pod也将转变为“READY”:


~]# kubectl get pods -l app=rs-demo
NAME               READY     STATUS              RESTARTS   AGE
rs-example-p66nv   0/1       ContainerCreating   0          8s
rs-example-rdm7q   0/1       ContainerCreating   0          8s

接下来可以使用“kubectl get replicaset”命令查看ReplicaSet控制器资源的相关状态。下面的命令结果显示出它已经根据清单中配置的Pod模板创建了2个Pod资源,不过这时它们尚未创建完成,因此仍为“READY”:


~]$ kubectl get replicaset rs-example -o wide
NAME  DESIRED  CURRENT   READY  AGE  CONTAINERS  IMAGES  SELECTOR
rs-example   2      2      0     2m    nginx  ikubernetes/myapp:v1 app=rs-demo

经由控制器创建与用户自主创建的Pod对象的功能并无二致,但其自动和解的功能在很大程度上能为用户省去不少的管理精力,这也是使得Kubernetes系统之上的应用程序变得拥有自愈能力的主要保障。

1.2.3 ReplicaSet管控下的Pod对象

1.2.2节中创建的rc-example通过标签选择器将拥有“app=rs-demo”标签的Pod资源收归于麾下,并确保其数量精确符合所期望的数目,使用标签选择器显示出的Pod资源列表也能验证这一点。然而,实际中存在着不少可能导致Pod对象数目与期望值不符合的可能性,如Pod对象的意外删除、Pod对象标签的变动(已有的Pod资源变得不匹配控制器的标签选择器,或者外部的Pod资源标签变得匹配到了控制器的标签选择器)、控制器的标签选择器变动,甚至是工作节点故障等。ReplicaSet控制器的和解循环过程能够实时监控到这类异常,并及时启动和解操作。

1.缺少Pod副本

任何原因导致的相关Pod对象丢失,都会由ReplicaSet控制器自动补足。例如,手动删除上面列出的一个Pod对象,命令如下:


~]$ kubectl delete pods rs-example-rdm7q
pod "rs-example-rdm7q" deleted

再次列出相关Pod对象的信息,可以看到被删除的rs-example-rdm7q进入了终止过程,而新的Pod对象rs-example-l4gkp正在被rs-example控制器创建:


~]$ kubectl get pods -l app=rs-demo -o wide
NAME               READY     STATUS              RESTARTS   AGE
rs-example-l4gkp   0/1       ContainerCreating   0          1s
rs-example-p66nv   1/1       Running             0          39m
rs-example-rdm7q   0/1       Terminating         0          39m

另外,强行修改隶属于控制器rs-example的某Pod资源(匹配于标签控制器)的标签,会导致它不再被控制器作为副本计数,这也将触发控制器的Pod对象副本缺失补足机制。例如,将rs-example-p66nv的标签app的值置空:


~]$ kubectl label pods rs-example-p66nv app= --overwrite
pod "rs-example-p66nv" labeled

列出rs-example相关Pod对象的信息,发现rs-example-p66nv已经消失不见,并且正在创建新的对象副本。


~]$ kubectl get pods -l app=rs-demo
NAME               READY     STATUS               RESTARTS    AGE
rs-example-4bqzv   0/1       ContainerCreating    0           2s
rs-example-l4gkp   1/1       Running           0        5m

由此可见,修改Pod资源的标签即可将其从控制器的管控之下移出,当然,修改后的标签如果又能被其他控制器资源的标签选择器所命中,则此时它又成了隶属于另一控制器的副本。如果修改其标签后的Pod对象不再隶属于任何控制器,那么它就将成为自主式Pod,与此前手动直接创建的Pod对象的特性相同,即误删除或所在的工作节点故障都会造成其永久性的消失。

2.多出Pod副本

一旦被标签选择器匹配到的Pod资源数量因任何原因超出期望值,多余的部分都将被控制器自动删除。例如,为pod-example手动为其添加“app:rs-demo”标签:


~]$ kubectl label pods pod-example app=rs-demo
pod "pod-example" labeled

再次列出相关的Pod资源,可以看到rs-example控制器启动了删除多余Pod的操作,pod-example正处于终止过程中:


~]$  kubectl get pods -l app=rs-demo
NAME               READY    STATUS        RESTARTS   AGE
pod-example        1/1      Terminating   0          2m
rs-example-4bqzv   1/1      Running       0          17m
rs-example-l4gkp   1/1      Running       0          22m

这就意味着,任何自主式的或本隶属于其他控制器的Pod资源其标签变动的结果一旦匹配到了其他的副本数足额的控制器,就会导致这类Pod资源被删除。

3.查看Pod资源变动的相关事件

“kubectl describe replicasets”命令可打印出ReplicaSet控制器的详细状态,从下面命令结果中Events一段也可以看出,rs-example执行了Pod资源的创建和删除操作,为的就是确保其数量的精确性。


~]$ kubectl describe replicasets/rs-example
Name:         rs-example
Namespace:    default
Selector:     app=rs-demo
Labels:       app=rs-demo
……
Events:
  Type    Reason            Age   From                   Message
  ----    ------            ----  ----                   -------
  Normal  SuccessfulCreate  21m   replicaset-controller  Created pod: rs-
      example-l4gkp
  Normal  SuccessfulDelete  4m    replicaset-controller  Deleted pod: pod-
      example
  Normal  SuccessfulCreate  26m   replicaset-controller  Created pod: rs-
      example-4bqzv

事实上,ReplicaSet控制器能对Pod对象数目的异常及时做出响应,是因为它向API Server注册监听(watch)了相关资源及其列表的变动信息,于是API Server会在变动发生时立即通知给相关的监听客户端。

而因节点自身故障而导致的Pod对象丢失,ReplicaSet控制器一样会使用补足资源的方式进行处理,这里不再详细说明其过程。有兴趣的读者可通过直接关掉类似上面Pod对象运行所在的某一个节点来检验其处理过程。

1.2.4 更新ReplicaSet控制器

ReplicaSet控制器的核心组成部分是标签选择器、副本数量及Pod模板,但更新操作一般是围绕replicas和template两个字段值进行的,毕竟改变标签选择器的需求几乎不存在。改动Pod模板的定义对已经创建完成的活动对象无效,但在用户逐个手动关闭其旧版本的Pod资源后就能以新代旧,实现控制器下应用版本的滚动升级。另外,修改副本的数量也就意味着应用规模的扩展(提升期望的副本数量)或收缩(降低期望的副本数量)。这两种操作也是系统运维人员日常维护工作的重要组成部分。

1.更改Pod模板:升级应用

ReplicaSet控制器的Pod模板可随时按需修改,但它仅影响这之后由其新建的Pod对象,对已有的副本不会产生作用。大多数情况下,用户需要改变的通常是模板中的容器镜像文件及其相关的配置以实现应用的版本升级。下面的示例清单文件片断(rs-example-v2.yaml)中的内容与之前版本(rs-example.yaml)的唯一不同之处也仅在于镜像文件的改动:


containers:
- name: nginx
  image: ikubernetes/myapp:v2
  ports:
  - name: http
    containerPort: 80

对新版本的清单文件执行“kubectl apply”或“kubectl replace”命令即可完成rs-example控制器资源的修改操作:


~]$ kubectl replace -f rs-example-v2.yaml
replicaset.apps "rs-example" replaced

不过,控制器rs-example管控的现存Pod对象使用的仍然是原来版本中定义的镜像:


~]$ kubectl get pods -l app=rs-demo -o \
     custom-columns=Name:metadata.name,Image:spec.containers[0].image
Name               Image
rs-example-4bqzv   ikubernetes/myapp:v1
rs-example-l4gkp   ikubernetes/myapp:v1

此时,手动删除控制器现有的Pod对象(或修改与其匹配的控制器标签选择器的标签),并由控制器基于新的Pod模板自动创建出足额的Pod副本,即可完成一次应用的升级。新旧更替的过程支持如下两类操作方式。

·一次性删除控制器相关的所有Pod副本或更改相关的标签:剧烈更替,可能会导致Pod中的应用短时间不可访问(如图5-5所示);生产实践中,此种做法不可取。

img

图5-5 直接更替所有Pod资源

·分批次删除旧有的Pod副本或更改其标签(待控制器补足后再删除另一批):滚动更替,更替期间新旧版本共存(如图5-6所示)。

img

图5-6 滚动更替

例如,这里采用第一种方式进行操作,一次性删除rs-example相关的所有Pod副本:


~]$ kubectl delete pods -l app=rs-demo
pod "rs-example-4bqzv" deleted
pod "rs-example-l4gkp" deleted

再次列出rc-example控制器相关的Pod及其容器镜像版本时可以发现,使用新版本镜像的Pod已经创建完成:


~]$ kubectl get pods -l app=rs-demo -o \
custom-columns=Name:metadata.name,Image:spec.containers[0].image
Name               Image
rs-example-4bwqq   ikubernetes/myapp:v2
rs-example-hqgnh   ikubernetes/myapp:v2

必要时,用户还可以将Pod模板改回旧的版本进行应用的“降级”或“回滚”,它的操作过程与上述过程基本类似。事实上,修改Pod模板时,不仅仅能替换镜像文件的版本,甚至还可以将其替换成其他正在运行着的、完全不同应用的镜像,只不过此类需求并不多见。若同时改动的还有Pod模板中的其他字段,那么在新旧更替的过程中,它们也将随之被应用。

以上操作只为说明应用部署的方式,实际使用时还需要更为完善的机制。即便是仅执行了一到多次删除操作,手动执行更替操作也并非一项轻松的任务,幸运的是,更高级别的Pod控制器Deployment能够自动实现更完善的滚动更新和回滚,并为用户提供自定义更新策略的接口。而且,经过精心组织的更新操作还可以实现诸如蓝绿部署(Blue/Green Deployment)、金丝雀部署(Canary Deployment)和灰度部署等,这些内容将在后面章节中详细展开说明。

2.扩容和缩容

改动ReplicaSet控制器对象配置中期望的Pod副本数量(replicas字段)会由控制器实时做出响应,从而实现应用规模的水平伸缩。replicas的修改及应用方式同Pod模板,不过,kubectl还提供了一个专用的子命令scale用于实现应用规模的伸缩,它支持从资源清单文件中获取新的目标副本数量,也可以直接在命令行通过“--replicas”选项进行读取,例如将rs-example控制器的Pod副本数量提升至5个:


~]$ kubectl scale replicasets rs-example --replicas=5
replicaset.extensions "rs-example" scaled

由下面显示的rs-example资源的状态可以看出,将其Pod资源副本数量扩展至5个的操作已经成功完成:


~]$ kubectl get replicasets rs-example
NAME    DESIRED   CURRENT  READY   AGE
rs-example   5       5         5     12h

收缩规模的方式与扩展相同,只需要明确指定目标副本数量即可。例如:


~]$ kubectl scale replicasets rs-example --replicas=3
replicaset.extensions "rs-example" scaled

另外,kubectl scale命令还支持在现有Pod副本数量符合指定的值时才执行扩展操作,这仅需要为命令使用“--current-replicas”选项即可。例如,下面的命令表示如果rs-example目前的Pod副本数量为2,就将其扩展至4个:


~]$ kubectl scale replicasets rs-example --current-replicas=2 --replicas=4
error: Expected replicas to be 2, was 3

但由于rs-example控制器现存的副本数量是3个,因此上面的扩展操作未执行并返回了错误提示。

注意 如果ReplicaSet控制器管控的是有状态的应用,例如主从架构的Redis集群,那么上述这些升级、降级、扩展和收缩的操作都需要精心编排和参与才能进行,不过,这也在一定程度上降低了Kubernetes容器编排的价值和意义。好在,它提供了StatefulSet资源来应对这种需求,因此,ReplicaSet通常仅用于管理无状态的应用,如HTTP服务程序等。

1.2.5 删除ReplicaSet控制器资源

使用kubectl delete命令删除ReplicaSet对象时默认会一并删除其管控的各Pod对象。有时,考虑到这些Pod资源未必由其创建,或者即便由其创建却也并非其自身的组成部分,故而可以为命令使用“--cascade=false”选项,取消级联,删除相关的Pod对象,这在Pod资源后续可能会再次用到时尤为有用。例如,删除rs控制器rs-example:


~]$ kubectl delete replicasets rs-example --cascade=false
replicaset.extensions "rs-example" deleted

删除操作完成后,此前由rs-example控制器管控的各Pod对象仍处于活动状态,但它们变成了自主式Pod资源,用户需要自行组织和维护好它们。

提示 后续讲到的各Pod控制器的删除方式都与ReplicaSet类似,这里就不再分别进行说明了。

尽管ReplicaSet控制器功能强大,但在实践中,它却并非是用户直接使用的控制器,而是要由比其更高一级抽象的Deployment控制器对象来调用。

1.3 Deployment控制器

Deployment(简写为deploy)是Kubernetes控制器的又一种实现,它构建于ReplicaSet控制器之上,可为Pod和ReplicaSet资源提供声明式更新。相比较而言,Pod和ReplicaSet是较低级别的资源,它们很少被直接使用。Deployment、ReplicaSet和Pod的关系如图5-7所示。

img

图5-7 Deployment、ReplicaSets和Pods

Deployment控制器资源的主要职责同样是为了保证Pod资源的健康运行,其大部分功能均可通过调用ReplicaSet控制器来实现,同时还增添了部分特性。

·事件和状态查看:必要时可以查看Deployment对象升级的详细进度和状态。

·回滚:升级操作完成后发现问题时,支持使用回滚机制将应用返回到前一个或由用户指定的历史记录中的版本上。

·版本记录:对Deployment对象的每一次操作都予以保存,以供后续可能执行的回滚操作使用。

·暂停和启动:对于每一次升级,都能够随时暂停和启动。

·多种自动更新方案:一是Recreate,即重建更新机制,全面停止、删除旧有的Pod后用新版本替代;另一个是RollingUpdate,即滚动升级机制,逐步替换旧有的Pod至新的版本。

1.3.1 创建Deployment

Deployment是标准的Kubernetes API资源,它建构于ReplicaSet资源之上,于是其spec字段中嵌套使用的字段包含了ReplicaSet控制器支持的replicas、selector、template和minReadySeconds,它也正是利用这些信息完成了其二级资源ReplicaSet对象的创建。下面是一个Deployment控制器资源的配置清单示例:


apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-deploy
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: myapp
        image: ikubernetes/myapp:v1
        ports:
        - containerPort: 80
          name: http

上面的内容显示出,除了控制器类型和名称之外,它与前面ReplicaSet控制器示例中的内容几乎没有什么不同。下面在集群中创建以了解它的工作方式:


~]$ kubectl apply -f myapp-deploy.yaml --record
deployment.apps "myapp-deploy" created

“kubectl get deployments”命令可以列出创建的Deployment对象myapp-deploy及其相关的信息。下面显示的字段中,UP-TO-DATE表示已经达到期望状态的Pod副本数量,AVAILABLE则表示当前处于可用状态的应用程序的数量:


~]$ kubectl get deployments myapp-deploy
NAME           DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
myapp-deploy   3         3         3            3           13s

Deployment控制器会自动创建相关的ReplicaSet控制器资源,并以“[DEPLOYMENT-NAME]-[POD-TEMPLATE-HASH-VALUE]”格式为其命名,其中的hash值由Deployment控制器自动生成。由Deployment创建的ReplicaSet对象会自动使用相同的标签选择器,因此,可使用类似如下的命令查看其相关的信息:


~]$ kubectl get replicasets -l app=myapp
NAME                      DESIRED   CURRENT   READY     AGE
myapp-deploy-86b4b8c75d   3         3         3         37s

相关的Pod对象的信息可以用相似的命令进行获取。下面的命令结果中,Pod对象的名称遵循ReplicaSet控制器的命名格式,它以ReplicaSet控制器的名称为前缀,后跟5位随机字符:


~]$ kubectl get pods -l app=myapp
NAME                            READY     STATUS    RESTARTS   AGE
myapp-deploy-86b4b8c75d-7dtxn   1/1       Running   0          46s
myapp-deploy-86b4b8c75d-hdw9z   1/1       Running   0          46s
myapp-deploy-86b4b8c75d-w4svj   1/1       Running   0          46s

由此印证了Deployment借助于ReplicaSet管理Pod资源的机制,于是可以得知,其大部分管理操作与ReplicaSet相同。不过,Deployment也有ReplicaSet所不具有的部分高级功能,这其中最著名的当数其自动滚动更新的机制。

1.3.2 更新策略

如前所述,ReplicaSet控制器的应用更新需要手动分成多步并以特定的次序进行,过程繁杂且容易出错,而Deployment却只需要由用户指定在Pod模板中要改动的内容,例如容器镜像文件的版本,余下的步骤可交由其自动完成。同样,更新应用程序的规模也只需要修改期望的副本数量,余下的事情交给Deployment控制器即可。

Deployment控制器详细信息中包含了其更新策略的相关配置信息,如myapp-deploy控制器资源“kubectl describe”命令中输出的StrategyType、RollingUpdateStrategy字段等:


~]$ kubectl describe deployments myapp-deploy
Name:                   myapp-deploy
Namespace:              default
CreationTimestamp:      Fri, 02 Mar 2018 09:57:06 +0800
Labels:                 app=deploy-demo
Annotations:            deployment.kubernetes.io/revision=1
Selector:               app=myapp
Replicas:               3 desired | 3 updated | 3 total | 3 available | 0 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  25% max unavailable, 25% max surge
……
OldReplicaSets:  <none>
NewReplicaSet:   myapp-deploy-86b4b8c75d (3/3 replicas created)
……

Deployment控制器支持两种更新策略:滚动更新(rolling update)和重新创建(recreate),默认为滚动更新。重新创建更新类似于前文中ReplicaSet的第一种更新方式,即首先删除现有的Pod对象,而后由控制器基于新模板重新创建出新版本资源对象。通常,只应该在应用的新旧版本不兼容(如依赖的后端数据库的schema不同且无法兼容)时运行时才会使用recreate策略,因为它会导致应用替换期间暂时不可用,好处在于它不存在中间状态,用户访问到的要么是应用的新版本,要么是旧版本。

滚动升级是默认的更新策略,它在删除一部分旧版本Pod资源的同时,补充创建一部分新版本的Pod对象进行应用升级,其优势是升级期间,容器中应用提供的服务不会中断,但要求应用程序能够应对新旧版本同时工作的情形,例如新旧版本兼容同一个数据库方案等。不过,更新操作期间,不同客户端得到的响应内容可能会来自不同版本的应用。

Deployment控制器的滚动更新操作并非在同一个ReplicaSet控制器对象下删除并创建Pod资源,而是将它们分置于两个不同的控制器之下:旧控制器的Pod对象数量不断减少的同时,新控制器的Pod对象数量不断增加,直到旧控制器不再拥有Pod对象,而新控制器的副本数量变得完全符合期望值为止,如图5-8所示。

img

图5-8 Deployment的滚动更新

滚动更新时,应用升级期间还要确保可用的Pod对象数量不低于某阈值以确保可以持续处理客户端的服务请求,变动的方式和Pod对象的数量范围将通过spec.strategy.rollingUpdate.maxSurge和spec.strategy.rollingUpdate.maxUnavailable两个属性协同进行定义,它们的功用如图5-9所示。

·maxSurge:指定升级期间存在的总Pod对象数量最多可超出期望值的个数,其值可以是0或正整数,也可以是一个期望值的百分比;例如,如果期望值为3,当前的属性值为1,则表示Pod对象的总数不能超过4个。

·maxUnavailable:升级期间正常可用的Pod副本数(包括新旧版本)最多不能低于期望数值的个数,其值可以是0或正整数,也可以是一个期望值的百分比;默认值为1,该值意味着如果期望值是3,则升级期间至少要有两个Pod对象处于正常提供服务的状态。

img

图5-9 maxSurge和maxUnavailable的作用方式(图片来源:《Kubernetes in action》)

注意 maxSurge和maxUnavailable属性的值不可同时为0,否则Pod对象的副本数量在符合用户期望的数量后无法做出合理变动以进行滚动更新操作。

配置时,用户还可以使用Deplpoyment控制器的spec.minReadySeconds属性来控制应用升级的速度。新旧更替过程中,新创建的Pod对象一旦成功响应就绪探测即被视作可用,而后即可立即开始下一轮的替换操作。而spec.minReadySeconds能够定义在新的Pod对象创建后至少要等待多久才会将其视作就绪,在此期间,更新操作会被阻塞。因此,它可以用来让Kubernetes在每次创建出Pod资源后都要等上一段时长后再开始下一轮的更替,这个时间长度的理想值是等到Pod对象中的应用已经可以接受并处理请求流量。事实上,一个精心设计的等待时长和就绪性探测能让Kubernetes系统规避一部分因程序Bug而导致的升级故障。

Deployment控制器也支持用户保留其滚动更新历史中的旧ReplicaSet对象版本,如图5-10所示,这赋予了控制器进行应用回滚的能力:用户可按需回滚到指定的历史版本。控制器可保存的历史版本数量由“spec.revisionHistoryLimit”属性进行定义。当然,也只有保存于revision历史中的ReplicaSet版本可用于回滚,因此,用户要习惯性地在更新操作时指定保留旧版本。

img

图5-10 Deployment的版本历史记录

注意 为了保存版本升级的历史,需要在创建Deployment对象时于命令中使用“--record”选项。

尽管滚动更新以节约系统资源著称,但它也存在一些劣势。直接改动现有环境,会使系统引入不确定性风险,而且升级过程出现问题后,执行回滚操作也会较为缓慢。有鉴于此,金丝雀部署可能是较为理想的实现方式,当然,如果不考虑系统资源的可用性,那么传统的蓝绿部署也是不错的选择。

1.3.3 升级Deployment

修改Pod模板相关的配置参数便能完成Deployment控制器资源的更新。由于是声明式配置,因此对Deployment控制器资源的修改尤其适合使用apply和patch命令来进行;当然,如果仅是修改容器镜像,“set image”命令更为易用。

接下来通过更新此前创建的Deployment控制器deploy-example来了解更新操作过程的执行细节,为了使得升级过程更易于观测,这里先使用“kubectl patch”命令为其spec.minReadySeconds字段定义一个等待时长,例如5s:


~]$ kubectl patch deployments myapp-deploy -p '{"spec": {"minReadySeconds": 5}}'
deployment.extensions "myapp-deploy" patched

patch命令的补丁形式为JSON格式,以-p选项指定,上面命令中的'{"spec":{"minReadySeconds":5}}'表示设置spec.minReadySeconds属性的值。若要改变myapp-deploy中myapp容器的镜像,也可使用patch命令,如'{"spec":{"containers":["name":"myapp","image""ikubernetes/myapp:v2"}}',不过,修改容器镜像有更为简单的专用命令“set image”。

注意 修改Deployment控制器的minReadySeconds、replicas和strategy等字段的值并不会触发Pod资源的更新操作,因为它们不属于模板的内嵌字段,对现存的Pod对象不产生任何影响。

接着,使用“ikubernetes/myapp:v2”镜像文件修改Pod模板中的myapp容器,启动Deployment控制器的滚动更新过程:


~]$ kubectl set image deployments myapp-deploy myapp=ikubernetes/myapp:v2
deployment.apps "myapp-deploy" image updated

“kubectl rollout status”命令可用于打印滚动更新过程中的状态信息:


~]$ kubectl rollout status deployments myapp-deploy

另外,还可以使用“kubectl get deployments--watch”命令监控其更新过程中Pod对象的变动过程:


~]$ kubectl get deployments myapp-deploy --watch

滚动更新时,myapp-deploy控制器会创建一个新的ReplicaSet控制器对象来管控新版本的Pod对象,升级完成后,旧版本的ReplicaSet会保留在历史记录中,但其此前的管控Pod对象将会被删除。


~]$ kubectl get replicasets -l app=myapp
NAME                      DESIRED   CURRENT   READY     AGE
myapp-deploy-79859f456c   3         3         3         1m
myapp-deploy-86b4b8c75d   0         0         0         7m

myapp-deploy控制器管控的Pod资源对象也将随之更新为以新版本ReplicaSet名称“myapp-deploy-79859f456c”为前缀的Pod副本,命令结果如下所示:


~]$ kubectl get pods -l app=myapp
NAME                            READY     STATUS    RESTARTS   AGE
myapp-deploy-79859f456c-29rqw   1/1       Running   0          16m
myapp-deploy-79859f456c-fhhwf   1/1       Running   0          15m
myapp-deploy-79859f456c-h4n9d   1/1       Running   0          15m

由于已经处于READY状态,因此上面命令列出的任一Pod资源均可正常向用户提供相关服务,例如,在集群内任一能使用kubectl的节点访问myapp-deploy-79859f456c-29rqw中的Web服务,命令如下:


~]$ curl $(kubectl get pods myapp-deploy-79859f456c-29rqw -o go-template={{.
    status.podIP}})
Hello MyApp | Version: v2 | <a href="hostname.html">Pod Name</a>

1.3.4 金丝雀发布

Deployment控制器还支持自定义控制更新过程中的滚动节奏,如“暂停”(pause)或“继续”(resume)更新操作,尤其是借助于前文讲到的maxSurge和maxUnavailable属性还能实现更为精巧的过程控制。比如,待第一批新的Pod资源创建完成后立即暂停更新过程,此时,仅存在一小部分新版本的应用,主体部分还是旧的版本。然后,再根据用户特征精心筛选出小部分用户的请求路由至新版本的Pod应用,并持续观察其是否能稳定地按期望的方式运行。确定没有问题后再继续完成余下Pod资源的滚动更新,否则立即回滚更新操作。这便是所谓的金丝雀发布(Canary Release),如图5-11所示。

img

图5-11 金丝雀发布(图片来源:http://blog.christianposta.com )

拓展知识:矿井中的金丝雀

17世纪,英国矿井工人发现,金丝雀对瓦斯这种气体十分敏感。空气中哪怕有极其微量的瓦斯气体,金丝雀也会停止歌唱;当瓦斯含量超过一定限度时,人类依旧毫无察觉,而金丝雀却早已毒发身亡。当时在采矿设备相对简陋的条件下,工人们每次下井都会带上一只金丝雀作为瓦斯检测工具,以便在危险状况下紧急撤离。

直接发布新应用版本的在线发布形式中,金丝雀发布是一种较为妥当的方式。不过,这里只涉及其部署操作的相关步骤,发布方式则通常依赖于具体的环境设置。接下来说明如何在Kubernetes上使用Deployment控制器实现金丝雀部署。

为了尽可能地降低对现有系统及其容量的影响,金丝雀发布过程通常建议采用“先添加、再删除,且可用Pod资源对象总数不低于期望值”的方式进行。首次添加的Pod对象数量取决于其接入的第一批请求的规则及单个Pod的承载能力,视具体需求而定,为了能够更简单地说明问题,接下来采用首批添加1个Pod资源的方式。将Deployment控制器的maxSurge属性的值设置为1,并将maxUnavailable属性的值设置为0:


~]$ kubectl patch deployments myapp-deploy  \
-p '{"spec": {"strategy":{"rollingUpdate": {"maxSurge": 1, "maxUnavailable": 
    0}}}}'
deployment.extensions "myapp-deploy" patched

接下来,启动myapp-deploy控制器的更新过程,在修改相应容器的镜像版本后立即暂停更新进度,它会在启动第一批新版本Pod对象的创建操作之后转为暂停状态。需要注意的是,这里之所以能够在第一批更新启动后就暂停,有赖于此前为maxReadySeconds属性设置的时长,因此用户要在更新命令启动后的此时长指定的时间范围内启动暂停操作,其执行过程如图5-12所示。当然,对kubectl命令来说,也可以直接以“&&”符号在Shell中连接两个命令:


~]$ kubectl set image deployments myapp-deploy myapp=ikubernetes/myapp:v3 \
&& kubectl rollout pause deployments myapp-deploy
deployment.apps "myapp-deploy" image updated
deployment.apps "myapp-deploy" paused

img

图5-12 暂停Deployment滚动更新

通过其状态查看命令可以看到,在创建完一个新版本的Pod资源后滚动更新操作“暂停”:


~]$ kubectl rollout status deployments myapp-deploy
Waiting for rollout to finish: 1 out of 3 new replicas have been updated...

相关的Pod列表也可能显示旧版本的ReplicaSet的所有Pod副本仍在正常运行,新版本的ReplicaSet也包含一个Pod副本,但最多不超过期望值1个,myapp-deploy原有的期望值为3,因此总数不超过4个。此时,通过Service或Ingress资源及相关路由策略等设定,即可将一部分用户的流量引入到这些Pod之上进行发布验证。运行一段时间后,如果确认没有问题,即可使用“kubectl rollout resume”命令继续此前的滚动更新过程:


~]$ kubectl rollout resume deployments myapp-deploy
deployment.apps "myapp-deploy" resumed

“kubectl rollout status”命令监控到滚动更新过程完成后,即可通过myapp-deploy控制器及其ReplicaSet和Pod对象的相关信息来了解其结果状态。

然而,如果“金丝雀”遇险甚至遭遇不幸,那么回滚操作便成了接下来的当紧任务。

1.3.5 回滚Deployment控制器下的应用发布

若因各种原因导致滚动更新无法正常进行,如镜像文件获取失败、“金丝雀”遇险等,则应该将应用回滚到之前的版本,或者回滚到由用户指定的历史记录中的版本。Deployment控制器的回滚操作可使用“kubectl rollout undo”命令完成,例如,下面的命令可将myapp-deploy回滚至此前的版本:


~]$ kubectl rollout undo deployments myapp-deploy
deployment.apps "myapp-deploy"

等回滚完成后,验证myapp-deploy的ReplicaSet控制器对象是否已恢复到指定的历史版本以确保其回滚正常完成。在“kubectl rollout undo”命令上使用“--to-revision”选项指定revision号码即可回滚到历史特定版本,例如,假设myapp-deploy包含如下的revision历史记录:


~]$ kubectl rollout history deployments myapp-deploy
deployments "myapp-deploy"
REVISION  CHANGE-CAUSE
1         kubectl patch deployments myapp-deploy --patch={"spec": {"minReady-
    Seconds": 5}}
2         kubectl patch deployments myapp-deploy --patch={"spec": {"strategy":
    {"rollingUpdate": {"maxSurge": 1, "maxUnavailable": 0}}}}
3         kubectl set image deployments myapp-deploy myapp=ikubernetes/myapp:v3
4         kubectl set image deployments myapp-deploy myapp=ikubernetes/myapp:v4

若要回滚到号码为2的revision记录,则使用如下命令即可完成:


~]$ kubectl rollout undo deployments myapp-deploy --to-revision=2
deployment.apps "myapp-deploy"

回滚操作中,其revision记录中的信息会发生变动,回滚操作会被当作一次滚动更新追加进历史记录中,而被回滚的条目则会被删除。需要注意的是,如果此前的滚动更新过程处于“暂停”状态,那么回滚操作就需要先将Pod模板的版本改回到之前的版本,然后“继续”更新,否则,其将一直处于暂停状态而无法回滚。

1.3.6 扩容和缩容

通过修改.spec.replicas即可修改Deployment控制器中Pod资源的副本数量,它将实时作用于控制器并直接生效。Deployment控制器是声明式配置,replicas属性的值可直接修改资源配置文件,然后使用“kubectl apply”进行应用,也可以使用“kubectl edit”对其进行实时修改。而前一种方式能够将修改结果予以长期留存。

另外,“kubectl scale”是专用于扩展某些控制器类型的应用规模的命令,包括Deployment和Job等。而Deployment通过ReplicaSet控制其Pod资源,因此扩缩容的方式是相同的,除了命令直接作用的资源对象有所不同之外,这里不再对其进行展开说明。

1.4 DaemonSet控制器

DaemonSet是Pod控制器的又一种实现,用于在集群中的全部节点上同时运行一份指定的Pod资源副本,后续新加入集群的工作节点也会自动创建一个相关的Pod对象,当从集群移除节点时,此类Pod对象也将被自动回收而无须重建。管理员也可以使用节点选择器及节点标签指定仅在部分具有特定特征的节点上运行指定的Pod对象。

DaemonSet是一种特殊的控制器,它有特定的应用场景,通常运行那些执行系统级操作任务的应用,其应用场景具体如下。

·运行集群存储的守护进程,如在各个节点上运行glusterd或ceph。

·在各个节点上运行日志收集守护进程,如fluentd和logstash。

·在各个节点上运行监控系统的代理守护进程,如Prometheus Node Exporter、collectd、Datadog agent、New Relic agent或Ganglia gmond等。

当然,既然是需要运行于集群内的每个节点或部分节点,于是很多场景中也可以把应用直接运行为工作节点上的系统级守护进程,不过,这样一来就失去了运用Kubernetes管理所带来的便捷性。另外,也只有必须将Pod对象运行于固定的几个节点并且需要先于其他Pod启动时,才有必要使用DaemonSet控制器,否则就应该使用Deployment控制器。

1.4.1 创建DaemonSet资源对象

DaemonSet控制器的spec字段中嵌套使用的字段同样主要包了前面讲到的Pod控制器资源支持的selector、template和minReadySeconds,并且功能和用法基本相同,但它不支持使用replicas,毕竟DaemonSet并不是基于期望的副本数来控制Pod资源数量,而是基于节点数量,但template是必选字段。

下面的资源清单文件(filebeat-ds.yaml)示例中定义了一个名为filebeat-ds的DaemonSet控制器,它将在每个节点上运行一个filebeat进程以收集容器相关的日志数据:


apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: filebeat-ds
  labels:
    app: filebeat
spec:
  selector:
    matchLabels:
      app: filebeat
  template:
    metadata:
      labels:
        app: filebeat
      name: filebeat
    spec:
      containers:
      - name: filebeat
        image: ikubernetes/filebeat:1.6.5-alpine
        env:
        - name: REDIS_HOST
          value: db.ilinux.io:6379
        - name: LOG_LEVEL
          value: info

通过清单文件创建DaemonSet资源的命令与其他资源的创建并无二致:


~]$ kubectl apply -f filebeat-ds.yaml
daemonset.apps "filebeat-ds" created

注意 自Kubernetes 1.8版本起,DaemonSet也必须使用selector来匹配Pod模板中指定的标签,而且它也支持matchLabels和matchExpressions两种标签选择器。

与其他资源对象相同,用户也可以使用“kubectl describe”命令查看DaemonSet对象的详细信息。下面命令的结果信息中,Node-Selector字段的值为空,表示它需要运行于集群中的每个节点之上。而当前集群的节点数量为3,因此,其期望的Pod副本数(Desired Number of Nodes Scheduled)为3,而当前也已经成功创建了3个相关的Pod对象:


~]$ kubectl describe daemonsets filebeat-ds
Name:           filebeat-ds
Selector:       app=filebeat
Node-Selector:  <none>
……
Desired Number of Nodes Scheduled: 3
Current Number of Nodes Scheduled: 3
Number of Nodes Scheduled with Up-to-date Pods: 3
Number of Nodes Scheduled with Available Pods: 3
Number of Nodes Misscheduled: 0
Pods Status:  3 Running / 0 Waiting / 0 Succeeded / 0 Failed
……

根据DaemonSet资源本身的意义,filebeat-ds控制器成功创建的3个Pod对象应该分别运行于集群中的每个节点之上,这一点可以通过如下命令进行验证:


~]$ kubectl get pods -l app=filebeat \
-o custom-columns=NAME:metadata.name,NODE:spec.nodeName
NAME                NODE
filebeat-ds-sjrvb   node01.ilinux.io
filebeat-ds-swd47   node03.ilinux.io
filebeat-ds-z7r97   node02.ilinux.io

集群中的部分工作节点偶尔也存在需要将Pod对象以单一实例形式运行的情况,例如对于拥有特殊硬件的节点来说,可能会需要为其运行特定的监控代理(agent)程序,等等。其实现方式与前面讲到的Pod资源的节点绑定机制类似,只需要在Pod模板的spec字段中嵌套使用nodeSelector字段,并确保其值定义的标签选择器与部分特定工作节点的标签匹配即可。

1.4.2 更新DaemonSet对象

DaemonSet自Kubernetes 1.6版本起也开始支持更新机制,相关配置定义在spec.update-Strategy嵌套字段中。目前,它支持RollingUpdate(滚动更新)和OnDelete(删除时更新)两种更新策略,滚动更新为默认的更新策略,工作逻辑类似于Deployment控制,不过仅支持使用maxUnavailabe属性定义最大不可用Pod资源副本数(默认值为1),而删除时更新的方式则是在删除相应节点的Pod资源后重建并更新为新版本。

例如,将此前创建的filebeat-ds中Pod模板中的容器镜像升级为“ikubernetes/filebeat:1.6.6-alpine”,使用“kubectl set image”命令即可实现:


~]$ kubectl set image daemonsets filebeat-ds filebeat=ikubernetes/filebeat:1.6.6-
    alpine
daemonset.apps "filebeat-ds" image updated

由下面命令的返回结果可以看出,filebeat-ds控制器Pod模板中的容器镜像文件已经完成更新,对滚动更新策略来说,它会自动触发更新操作。用户也可以通过filebeat-ds控制器的详细信息中的Events字段等来了解滚动更新的操作过程。由下面的命令结果可以看出,默认的滚动更新策略是一次删除一个工作节点上的Pod资源,待其新版本Pod资源重建完成后再开始操作另一个工作节点上的Pod资源:


~]$  kubectl describe daemonsets filebeat-ds
……
Events:
  Type    Reason            Age   From                  Message
  ----    ------            ----  ----                  -------
  Normal  SuccessfulDelete  3m    daemonset-controller  Deleted pod: filebeat-
      ds-swd47
  Normal  SuccessfulCreate  3m    daemonset-controller  Created pod: filebeat-
      ds-pnhvl
  Normal  SuccessfulDelete  3m    daemonset-controller  Deleted pod: filebeat-
      ds-z7r97
  Normal  SuccessfulCreate  2m    daemonset-controller  Created pod: filebeat-
      ds-z2wdv
  Normal  SuccessfulDelete  2m    daemonset-controller  Deleted pod: filebeat-
      ds-sjrvb
  Normal  SuccessfulCreate  2m    daemonset-controller  Created pod: filebeat-
      ds-6pvdb

DaemonSet控制器的滚动更新机制也可以借助于minReadySeconds字段控制滚动节奏,必要时可以执行暂停和继续操作,因此它也能够设计为金丝雀发布机制。另外,故障的更新操作也可以进行回滚,包括回滚至revision历史记录中的任何一个指定的版本。鉴于篇幅,这里不再给出其详细过程,感兴趣的读者可参考Deployment控制器的步骤测试其实现。

1.5 Job控制器

与Deployment及DaemonSet控制器管理的守护进程类的服务应用不同的是,Job控制器用于调配Pod对象运行一次性任务,容器中的进程在正常运行结束后不会对其进行重启,而是将Pod对象置于“Completed”(完成)状态。若容器中的进程因错误而终止,则需要依配置确定重启与否,未运行完成的Pod对象因其所在的节点故障而意外终止后会被重新调度。Job控制器的Pod对象的状态转换如图5-13所示。

img

图5-13 Job管理下Pod资源的运行方式

实践中,有的作业任务可能需要运行不止一次,用户可以配置它们以串行或并行的方式运行。总结起来,这种类型的Job控制器对象有两种,具体如下。

·单工作队列(work queue)的串行式Job:即以多个一次性的作业方式串行执行多次作业,直至满足期望的次数,如图5-14所示;这次Job也可以理解为并行度为1的作业执行方式,在某个时刻仅存在一个Pod资源对象。

img

图5-14 串行式多任务

·多工作队列的并行式Job:这种方式可以设置工作队列数,即作业数,每个队列仅负责运行一个作业,如图5-15a所示;也可以用有限的工作队列运行较多的作业,即工作队列数少于总作业数,相当于运行多个串行作业队列。如图5-15b所示,工作队列数即为同时可运行的Pod资源数。

img

图5-15 多队列并行式多任务

Job控制器常用于管理那些运行一段时间便可“完成”的任务,例如计算或备份操作。

1.5.1 创建Job对象

Job控制器的spec字段内嵌的必要字段仅为template,它的使用方式与Deployment等控制器并无不同。Job会为其Pod对象自动添加“job-name=JOB_NAME”和“controller-uid=UID”标签,并使用标签选择器完成对controller-uid标签的关联。需要注意的是,Job位于API群组“batch/v1”之内。下面的资源清单文件(job-example.yaml)中定义了一个Job控制器:


apiVersion: batch/v1
kind: Job
metadata:
  name: job-example
spec:
  template:
    spec:
      containers:
      - name: myjob
        image: alpine
        command: ["/bin/sh",  "-c", "sleep 120"]
      restartPolicy: Never

注意 Pod模板中的spec.restartPolicy默认为“Always”,这对Job控制器来说并不适用,因此必须在Pod模板中显式设定restartPolicy属性的值为“Never”或“OnFailure”。

使用“kubectl create”或“kubectl apply”命令完成创建后即可查看相关的任务状态,DESIRED字段表示期望并行运行的Pod资源数量,而SUCCESSFUL则表示成功完成的Job数:


~]$ kubectl get jobs job-example
NAME          DESIRED   SUCCESSFUL   AGE
job-example   1         0            7s

相关的Pod资源能够以Job控制器名称为标签进行匹配:


~]$ kubectl get pods -l job-name=job-example
NAME                READY     STATUS    RESTARTS   AGE
job-example-6c5g8   1/1       Running   0          20s

其详细信息中可显示所使用的标签选择器及匹配的Pod资源的标签,具体如下:


~]$ kubectl describe jobs job-example
Name:           job-example
Namespace:      default
Selector:       controller-uid=48ae496f-1e81-11e8-9267-000c29ab0f5b
Labels:        controller-uid=48ae496f-1e81-11e8-9267-000c29ab0f5b
                job-name=job-example
Annotations:    <none>
Parallelism:    1
Completions:    1
……

两分钟后,待sleep命令执行完成并成功退出后,Pod资源即转换为Completed状态,并且不会再于“kubectl get pods”命令中出现,除非为其使用选项“--show-all”或简单格式的“-a”:


~]$ kubectl get pods -l job-name=job-example -a
NAME                READY     STATUS      RESTARTS   AGE
job-example-6c5g8   0/1       Completed   0          3m

此时,如果使用“kubectl get jobs”显示job-example的相关信息,那么其SUCCESSFUL字段的数字就不再为“0”。

1.5.2 并行式Job

将并行度属性.spec.parallelism的值设置为1,并设置总任务数.spec.completion属性便能够让Job控制器以串行方式运行多任务。下面是一个串行运行5次任务的Job控制器示例:


apiVersion: batch/v1
kind: Job
metadata:
  name: job-multi
spec:
     completions: 5
  template:
    spec:
      containers:
      - name: myjob
        image: alpine
        command: ["/bin/sh",  "-c", "sleep 20"]
      restartPolicy: OnFailure

在创建之后或者创建之前,可以于另一终端启动Pod资源的列出命令“kubectl get pods-l job-name=job-multi--watch”来监控其变动,以了解其执行过程。

.spec.parallelism能够定义作业执行的并行度,将其设置为2或者以上的值即可实现并行多队列作业运行。同时,如果.spec.completions使用的是默认值1,则表示并行度即作业总数,如图5-15a所示;而如果将.spec.completions属性值设置为大于.spec.parallelism的属性值,则表示使用多队列串行任务作业模式,如图5-15b所示。例如,某Job控制器配置中的spec字段嵌套了如下属性,表示以2个队列并行的方式,总共运行5次的作业:


spec:
     completions: 5
     parallelism: 2

1.5.3 Job扩容

Job控制器的.spec.parallelism定义的并行度表示同时运行的Pod对象数,此属性值支持运行时调整从而改变其队列总数,实现扩容和缩容。使用的命令与此前的Deployment对象相同,即“kubectl scale--replicas”命令,例如在其运行过程中(未完成之前)将job-multi的并行度扩展为两路:


~]$ kubectl scale jobs job-multi --replicas=2
job.apps "job-multi" scaled

执行命令后可以看到,其同时运行的Pod对象副本数量立即扩展到了两个:


~]$ kubectl get pods -l job-name=job-multi
NAME              READY     STATUS              RESTARTS   AGE
job-multi-c26rh   0/1       ContainerCreating   0          2s
job-multi-tfj9k   1/1       Running             0          26s

根据工作节点及其资源可用量,适度提高Job的并行度,能够大大提升其完成效率,缩短运行时间。

1.5.4 删除Job

Job控制器待其Pod资源运行完成后,将不再占用系统资源。用户可按需保留或使用资源删除命令将其删除。不过,如果某Job控制器的容器应用总是无法正常结束运行,而其restartPolicy又定为了重启,则它可能会一直处于不停地重启和错误的循环当中。所幸的是,Job控制器提供了两个属性用于抑制这种情况的发生,具体如下。

·.spec.activeDeadlineSeconds :Job的deadline,用于为其指定最大活动时间长度,超出此时长的作业将被终止。

·.spec.backoffLimit :将作业标记为失败状态之前的重试次数,默认值为6。

例如,下面的配置片断表示其失败重试的次数为5,并且如果超出100秒的时间仍未运行完成,那么其将被终止:


spec:
  backoffLimit: 5
  activeDeadlineSeconds: 100

1.6 CronJob控制器

CronJob控制器用于管理Job控制器资源的运行时间。Job控制器定义的作业任务在其控制器资源创建之后便会立即执行,但CronJob可以以类似于Linux操作系统的周期性任务作业计划(crontab)的方式控制其运行的时间点及重复运行的方式,具体如下。

·在未来某时间点运行作业一次。

·在指定的时间点重复运行作业。

CronJob对象支持使用的时间格式类似于Crontab,略有不同的是,CronJob控制器在指定的时间点时,“”和“*”的意义相同,都表示任何可用的有效值。

1.6.1 创建CronJob对象

CronJob控制器的spec字段可嵌套使用以下字段。

·jobTemplate"Object":Job控制器模板,用于为CronJob控制器生成Job对象;必选字段。

·schedule:Cron格式的作业调度运行时间点;必选字段。

·concurrencyPolicy:并发执行策略,可用值有“Allow”(允许)、“Forbid”(禁止)和“Replace”(替换),用于定义前一次作业运行尚未完成时是否以及如何运行后一次的作业。

·failedJobHistoryLimit:为失败的任务执行保留的历史记录数,默认为1。

·successfulJobsHistoryLimit:为成功的任务执行保留的历史记录数,默认为3。

·startingDeadlineSeconds:因各种原因缺乏执行作业的时间点所导致的启动作业错误的超时时长,会被记入错误历史记录。

·suspend:是否挂起后续的任务执行,默认为false,对运行中的作业不会产生影响。

下面是一个定义在资源清单文件(cronjob-example.yaml)中的CronJob资源对象示例,它每隔2分钟运行一次由jobTemplate定义的简单任务:


apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: cronjob-example
  labels:
    app: mycronjob
spec:
  schedule: "*/2 * * * *"
  jobTemplate:
    metadata:
      labels:
        app: mycronjob-jobs
    spec:
      parallelism: 2
      template:
        spec:
          containers:
          - name: myjob
            image: alpine
            command:
            - /bin/sh
            - -c
            - date; echo Hello from the Kubernetes cluster; sleep 10
          restartPolicy: OnFailure

运行资源创建命令创建上述CronJob资源对象,而后再通过资源对象的相关信息了解运行状态。下面命令结果中的SCHEDULE是指其调度时间点,SUSPEND表示后续任务是否处于挂起状态,即暂停任务的调度和运行,ACTIVE表示活动状态的Job对象的数量,而LAST SCHEDULE则表示上次调度运行至此刻的时长:


~]$ kubectl get cronjobs cronjob-example
NAME              SCHEDULE      SUSPEND     ACTIVE    LAST SCHEDULE    AGE
cronjob-example   */2 * * * *   False       1         37s              1m

提示 自Kubernetes 1.8起,CronJob资源所在的API资源组从batch/v2alpha1移至batch/v1beta1中,并且查看其资源格式时也要使用--api-version选项指定其所在的资源组,即“kubectl explain cronjob--api-version='batch/v1beta1'”。

1.6.2 CronJob的控制机制

CronJob控制器是一个更高级别的资源,它以Job控制器资源为其管控对象,并借助它管理Pod资源对象。因此,要使用类似如下命令来查看某CronJob控制器创建的Job资源对象,其中的标签“mycronjob-jobs”是在创建cronjob-example时为其指定。不过,只有相关的Job对象被调度执行时,此命令才能将其正常列出。可列出的Job对象的数量取决于CronJob资源的.spec.successfulJobsHistoryLimit的属性值,默认为3。


~]$ kubectl get jobs -l app=mycronjob-jobs
NAME                         DESIRED   SUCCESSFUL   AGE
cronjob-example-1520057880   <none>    2            5m
cronjob-example-1520058000   <none>    2            3m
cronjob-example-1520058120   <none>    2            1m

如果作业重复执行时指定的时间点较近,而作业执行时长(普遍或偶尔)跨过了其两次执行的时间长度,则会出现两个Job对象同时存在的情形。有些Job对象可能会存在无法或不能同时运行的情况,这个时候就要通过.spec.concurrencyPolicy属性控制作业并存的机制,其默认值为“Allow”,即允许前后Job,甚至属于同一个CronJob的更多Job同时运行。其他两个可用值中,“Forbid”用于禁止前后两个Job同时运行,如果前一个尚未结束,后一个则不予启动(跳过),“Replace”用于让后一个Job取代前一个,即终止前一个并启动后一个。

1.7 ReplicationController

ReplicationController(简称rc或RC)是Kubernetes较早实现的Pod控制器,用于确保Pod资源的不间断运行。不过,Kubernetes后来设计了ReplicaSet及其更高一级的控制器Deployment来取ReplicationController,并表示在后来的版本中可能会废弃RC。因此,这里不再对ReplicationController做过多的介绍。事实上,它的使用方式与ReplicaSet相同,一旦用到时,绝大多数操作都可以迁移使用,感兴趣的读者可以自行测试。

1.8 Pod中断预算

尽管Deployment或ReplicaSet一类的控制器能够确保相应Pod对象的副本数量不断逼近期望的数量,但它却无法保证在某一时刻一定会存在指定数量或比例的Pod对象,然而这种需求在某些强调服务可用性的场景中却是必备的。于是,Kubernetes自1.4版本起开始引入Pod中断预算(PodDisruptionBudget,简称PDB)类型的资源,用于为那些自愿的(Voluntary)中断做好预算方案(Budget),限制可自愿中断的最大Pod副本数或确保最少可用的Pod副本数,以确保服务的高可用性。

Pod对象会一直存在,除非有意将其销毁,或者出现了不可避免的硬件或系统软件错误。非自愿中断 是指那些由不可控外界因素导致的Pod中断退出操作,例如,硬件或系统内核故障、网络故障以及节点资源不足导致Pod对象被驱逐等;而那些由用户特地执行的管理操作导致的Pod中断则称为“自愿中断” ,例如排空节点、人为删除Pod对象、由更新操作触发的Pod对象重建等。部署在Kubernetes的每个应用程序都可以创建一个对应的PDB对象以限制自愿中断时最大可以中断的副本数或者最少应该保持可用的副本数,从而保证应用自身的高可用性。

PDB资源可以用来保护由控制器管理的应用,此时几乎必然意味着PDB使用等同于相关控制器对象的标签选择器以精确关联至目标Pod对象,支持的控制器类型包括Deployment、ReplicaSet和StatefulSet等。同时,PDB对象也可以用来保护那些纯粹是由定制的标签选择器自由选择的Pod对象。

定义PDB资源时,其spec字段主要嵌套使用以下三个字段。

·selector:当前PDB对象使用的标签选择器,一般是与相关的Pod控制器使用同一个选择器。

·minAvailable:Pod自愿中断的场景中,至少要保证可用的Pod对象数量或比例,要阻止任何Pod对象发生自愿中断,可将其设置为100%。

·maxUnavailable:Pod自愿中断的场景中,最多可转换为不可用状态的Pod对象数量或比例,0值意味着不允许Pod对象进行自愿中断;此字段与minAvailable互斥。

下面的示例定义了一个PDB对象,它对1.3.1节中由Deployment控制器myapp-deploy创建的Pod对象设置了Pod中断预算,要求其最少可用的Pod对象数量为2个:


apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
  name: myapp-pdb
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: myapp

PDB资源对象创建完成后,在它的简要信息输出中也标明了最少可用的Pod对象个数,以及允许中断的Pod对象个数:


~]$ kubectl get pdb
NAME        MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS  AGE
myapp-pdb   2               N/A               1                    24s

接下来可通过命令手动删除myapp-deploy控制器下的所有Pod对象模拟自愿中断过程,并监控各Pod对象被终止的过程来验证PDB资源对象的控制功效。

1.9 小结

本篇主要讲解了Kubernetes的Pod控制器,它们是“工作负载”类资源的核心组成部分,是基于Kubernetes运行应用的最重要的资源类型之一,具体如下。

·工作负载类型的控制器根据业务需求管控Pod资源的生命周期。

·ReplicaSet可以确保守护进程型的Pod资源始终具有精确的、处于运行状态的副本数量,并支持Pod规模的伸缩机制;它是新一代的ReplicationController控制器,不过用户通常不应该直接使用ReplicaSet,而是要使用Deployment。

·Deployment是建构在ReplicaSet上的更加抽象的工作负载型控制器,支持多种更新策略及发布机制。

·Job控制器能够控制相应的作业任务得以正常完成并退出,支持并行式多任务。

·CronJob控制器用于控制周期性作业任务,其功能类似于Linux操作系统上的Crontab。

·PodDisruptionBudget资源对象为Kubernetes系统上的容器化应用提供了高可用能力。

版权声明:如无特殊说明,文章均为本站原创,版权所有,转载需注明本文链接

本文链接:http://www.bianchengvip.com/article/Kubernetes-Pod-Controller/

您尚未登录,请 登录注册 后评论
    0 人参与 | 0 条评论
    暂时没有评论,欢迎来抢沙发!