Kubernetes之deployment小家电维修

从一个简单的例子开始。有一组pod实例为其他pod或外部客户端提供服务。这些pod是由ReplicationController或ReplicaSet来创建和管理的。客户端(运行在其他pod或外部的客户端程序)通过该Service访问pod。这就是Kubernetes中一个典型应用程序的运行方式(如图9.1所示)。

假设pod—开始使用v1版本的镜像运行第一个版本的应用。然后开发了一个新版本的应用打包成镜像,并将其推送到镜像仓库,标记为v2,接下来想用这个新版本替换所有的pod。由于pod在创建之后,不允许直接修改镜像,只能通过删除原有pod并使用新的镜像创建新的pod替换。

有以下两种方法可以更新所有pod:

如何在Kubernetes中使用上述的两种更新方法呢?首先,来看看如何进行手动操作;一旦了解了手动流程中涉及的要点之后,将继续讨论如何让Kubernetes自动执行更新操作。

ReplicationController会将原有pod实例替换成新版本的pod并且ReplicationController内的pod模板一旦发生了更新之后,ReplicationController会使用更新后的pod模板来创建新的实例。

如果有一个ReplicationController来管理一组v1版本的pod,可以直接通过将pod模板修改为v2版本的镜像,然后删除旧的pod实例。ReplicationController会检测到当前没有pod匹配它的标签选择器,便会创建新的实例。整个过程如图9.2所示。

如果可以接受从删除旧的pod到启动新pod之间短暂的服务不可用,那这将是更新一组pod的最简单方式。

与前面的方法相比较,这个方法稍微复杂一点,可以结合ReplicationController和Service的概念进行使用。

从旧版本立即切换到新版本

pod通常通过Service来暴露。在运行新版本的pod之前,Service只将流量切换到初始版本的pod。一旦新版本的pod被创建并且正常运行之后,就可以修改服务的标签选择器并将Service的流量切换到新的pod。这就是所谓的蓝绿部署。在切换之后,一旦确定了新版本的功能运行正常,就可以通过删除旧的ReplicationController来删除旧版本的pod。

注意:可以使用kubectlsetselector命令来修改Service的pod选择器。

执行滚动升级操作

还可以执行滚动升级操作来逐步替代原有的pod,而不是同时创建所有新的pod并一并删除所有旧的pod。可以通过逐步对旧版本的ReplicationController进行缩容并对新版本的进行扩容,来实现上述操作。在这个过程中,希望服务的pod选择器同时包含新旧两个版本的pod,因此它将请求切换到这两组pod,如图9.4所示。

手动执行滚动升级操作非常烦琐,而且容易出错。根据副本数量的不同,需要以正确的顺序运行十几条甚至更多的命令来执行整个升级过程。但是实际上Kubernetes可以实现仅仅通过一个命令来执行滚动升级。在下一节会介绍如何执行这个操作。

不用手动地创建Replicationcontroller来执行滚动升级,可以直接使用kubectl来执行。使用kubectl执行升级会使整个升级过程更容易。虽然这是一种相对过时的升级方式,但是还是会先介绍一下,因为它是第一种实现自动滚动升级的方式,并且允许在不引入太多额外概念的情况下了解整个过程。

在更新一个应用之前,需要先部署好一个应用。使用一个NodeJS应用作为基础,并稍微修改版本作为应用的初始版本。它就是一个简单的web应用程序,在HTTP响应中返回pod的主机名。

创建v1版本的应用

你将修改这个应用,让它在响应中返回版本号,以便区分应用构建的不同版本。原作者已经构建并将应用镜像推送到DockerHub的luksa/kubia:v1中。下面的代码清单就是应用的源代码。

通过创建一个ReplicationController来运行应用程序,并创建LoadBalancer服务将应用程序对外暴露。接下来,不是分别创建这两个资源,而是为它们创建一个YAML文件,并使用一个kubectlcreate命令调用KubernetesAPI。YAMLmanifest可以使用包含三个横杠(---)的行来分隔多个对象,如下面的代码清单所示。

#单个YAML文件同时包含一个RC和一个Service:kubia-rc-and-service-v1.yamlapiVersion:v1kind:ReplicationControllermetadata:name:kubia-v1spec:replicas:3template:metadata:name:kubialabels:app:kubiaspec:containers:-image:luksa/kubia:v1#使用ReplicationController来创建pod并运行镜像name:nodejs---#YAML文件可以包含多种资源定义,并通过三个横杠(---)来分行apiVersion:v1kind:Servicemetadata:name:kubiaspec:type:LoadBalancerselector:app:kubiaports:-port:80targetPort:8080这个YAML定义了一个名为kubia-v1的ReplicationController和一个名为kubia的Service。将此YAML发布到Kubernetes之后,三个v1pod和负载均衡器都会开始工作。如下面的代码清单所示,可以通过查找服务的外部IP并使用curl命令来访问服务。

接下来创建V2版本的应用,修改之前的应用程序,使得其请求返回“ThisisV2”:

response.end("Thisisv2runninginpod"+os.hostname()+"\n");这个新的镜像己经推到了DockerHub的luksa/kubia:v2中,也可以用v1版本的DockerFile自己构建。

保持curl循环运行的状态下打开另一个终端,开始启动滚动升级。运行kubectlrolling-update命令来执行升级操作。指定需要替换的ReplicationController,以及为新的ReplicationController指定一个名称,并指定想要替换的新镜像。下面的代码清单显示了滚动升级的完整命令。

#代码9.4使用kubectl开始ReplicationController的滚动升级$kubectlrolling-updatekubia-v1kubia-v2--image=luksa/kubia:v2Createdkubia-v2Scalingupkubia-v2from0to3,scalingdownkubia-v1from3to0(keep3podsavailable,don'texceed4pods)...使用kubiav2版本应用来替换运行着kubia-v1的ReplicationController,将新的复制控制器命名为kubia-v2,并使用luksa/kubia:v2作为容器镜像。

当运行该命令时,一个名为kubia-v2的新ReplicationController会立即被创建。此时系统的状态如图9.5所示。

新的ReplicationController的pod模板引用了luksa/kubia:v2镜像,并且其初始期望的副本数被设置为0,如下面的代码清单所示。

#代码9.5滚动升级过程中创建的新的ReplicationController描述$kubectldescriberckubia-v2Name:kubia-v2Namespace:defaultImage(s):luksa/kubia:v2Selector:app=kubia,deployment=757d16a0f02f6a5c387f2b5edb62b155Labels:app=kubiaReplicas:0current/0desired...了解滚动升级前kubectl所执行的操作

kubectl通过复制kubia-v1的ReplicationController并在其pod模板中改变镜像版本。如果仔细观察控制器的标签选择器,会发现它也被做了修改。它不仅包含一个简单的app=kubia标签,而且还包含一个额外的deployment标签,为了由这个ReplicationController管理,pod必须具备这个标签。

尽可能避免使用新的和旧的ReplicadonController来管理同一组pod。但是即使新创建的pod添加了除额外的deployment标签以外,还有app=kubia标签,这是否也意味着它们会被第一个ReplicationController的选择器选中?因为它的标签内也含有app=kubia。

其实在滚动升级过程中,第一个ReplicationController的选择器也会被修改:

$kubectldescriberckubia-v1Name:kubia-v1Namespace:defaultImage(s):luksa/kubia:v1Selector:app=kubia,deployment=3ddd307978b502a5b975ed4045ae4964-orig但这是不是意味着第一个控制器现在选择不到pod呢?因为之前由它创建的三个pod只包含app=kubia标签。事实并不是这样,因为在修改ReplicationController的选择器之前,kubectl修改了当前pod的标签:

$kubectlgetpo--show-labelsNAMEREADYSTATUSRESTARTSAGELABELSkubia-v1-m33mv1/1Running02mapp=kubia,deployment=3ddd...kubia-v1-nmzw91/1Running02mapp=kubia,deployment=3ddd...kubia-v1-cdtey1/1Running02mapp=kubia,deployment=3ddd...如果觉得上述内容描述得太过复杂,看图9.6,其中显示了pod、标签、两个Replicationcontroller,以及它们的pod标签选择器。

kubectl在开始伸缩服务前,都会这么做。设想手动执行滚动升级,并且在升级过程中出现了问题,这可能使ReplicationController删除所有正在为生产级别的用户提供服务的pod!

通过伸缩两个ReplicationController将旧pod替换成新的pod

设置完所有这些后,kubectl开始替换pod。首先将新的Controller扩展为1,新的Controller因此创建第一个v2pod,kubectl将旧的ReplicationController缩小1.以下两行代码是kubectl输出的:

Scalingkubia-v2upto1Scalingkubia-v1downto2由于Service针对的只是所有带有app=kubia标签的pod,所以应该可以看到一一每进行几次循环访问,就将curl请求的流量切换到新的v2pod:

随着kubectl继续滚动升级,开始看到越来越多的请求被切换到v2Pod。因为在升级过程中,v1pod不断被删除,并被替换为运行新镜像的pod。最终,最初的ReplicationController被伸缩到0,导致最后一个v1pod被删除,也意味着服务现在只通过v2pod提供。此时,kubectl将删除原始的ReplicationController完成升级过程,如下面的代码清单所示。

#代码9.6kubectl执行滚动升级的最终步骤Scalingkubia-v2upto2Scalingkubia-v1downto1Scalingkubia-v2upto3Scalingkubia-v1downto0Updatesucceeded.Deletingkubia-v1replicationcontroller"kubia-v1"rollingupdatedto"kubia-v2"现在只剩下kubia-v2的ReplicationController和三个v2pod。在整个升级过程中,每次发出的请求都有相应的响应,通过一次滚动升级,服务一直保持可用状态。

在本节的开头,提到了一种比通过kubectlrolling-update更好的方式进行升级。那这个过程有什么不合理的地方呢?

首先,这个过程会直接修改创建的对象。直接更新pod和ReplicationController的标签并不符合之前创建时的预期。还有更重要的一点是,kubectl只是执行滚动升级过程中所有这些步骤的客户端。

当触发滚动更新时,可以使用--V选项打开详细的日志并能看到这一点:

$kubectlrolling-updatekubia-v1kubia-v2--image=luksa/kubia:v2--v6提示:使用--v6选项会提高日志级别使得所有kubectl发起的到API服务器的请求都会被输出。

使用这个选项,kubectl会输出所有发送至KubernetesAPI服务器的HTTP请求。会看到一个PUT请求:

/api/v1/namespaces/default/replicationcontrollers/kubia-v1它是表示kubia-v1ReplicationController资源的RESTfulURL。这些请求减少了ReplicationController的副本数,这表明伸缩的请求是由kubectl客户端执行的,而不是由Kubernetesmaster执行的。

提示:使用详细日志模式运行其他kubectl命令,将会看到kubectl和API服务器之前的更多通信细节。

但是为什么由客户端执行升级过程,而不是服务端执行是不好的呢?在上述的例子中,升级过程看起来很顺利,但是如果在kubectl执行升级时失去了网络连接,升级进程将会中断。pod和ReplicationController最终会处于中间状态。

这样的升级不符合预期的原因还有一个。强调过Kubernetes是如何通过不断地收敛达到期望的系统状态的。这就是pod的部署方式,以及pod的伸缩方式。直接使用期望副本数来伸缩pod而不是通过手动地删除一个pod或者增加一个pod。

同样,只需要在pod定义中更改所期望的镜像tag,并让Kubernetes用运行新镜像的pod替换旧的pod。正是这一点推动了一种称为Deployment的新资源的引入,这种资源正是现在Kubernetes中部署应用程序的首选方式。

当创建一个Deployment时,ReplicaSet资源也会随之创建(最终会有更多的资源被创建)。ReplicaSet是新一代的ReplicationController,并推荐使用它替代ReplicationController来复制和管理pod。在使用Deployment时,实际的pod是由Deployment的Replicaset创建和管理的,而不是由Deployment直接创建和管理的(如图9.8所示)。

你可能想知道为什么要在ReplicationController或ReplicaSet上引入另一个对象来使整个过程变得更复杂,因为它们己经足够保证一组pod的实例正常运行了。如上面滚动升级示例所示,在升级应用程序时,需要引入一个额外的ReplicationController,并协调两个Controller,使它们再根据彼此不断修改,而不会造成干扰。所以需要另一个资源用来协调。Deployment资源就是用来负责处理这个问题的(不是Deployment资源本身,而是在Kubernetes控制层上运行的控制器进程)。

使用Deployment可以更容易地更新应用程序,因为可以直接定义单个Deployment资源所需达到的状态,并让Kubernetes处理中间的状态,接下来将会介绍整个过程。

创建Deployment与创建ReplicationController并没有任何区别。Deployment也是由标签选择器、期望副数和pod模板组成的。此外,它还包含另一个字段,指定一个部署策略,该策略定义在修改Deployment资源时应该如何执行更新。

创建一个DeploymentManifest

看一下如何使用本章前面的kubia-v1ReplicationController示例,并对其稍作修改,使其描述一个Deployment,而不是一个ReplicationController。这只需要三个简单的更改,下面的代码清单显示了修改后的YAML。

#代码9.7Deployment定义:kubia-deployment-v1.yamlapiVersion:apps/v1beta1#Deployment属于appsAPI组,版本为V1beta1kind:Deployment#需要将原有kind从ReplicationController修改为Deploymentmetadata:name:kubia#Deployment的名称中不再需要包含版本号spec:replicas:3template:metadata:name:kubialabels:app:kubiaspec:containers:-image:luksa/kubia:v1name:nodejs因为之前的ReplicationController只维护和管理了一个特定版本的pod,并需要命名为kubia-v1。另一方面,一个Deployment资源高于版本本身。Deployment可以同时管理多个版本的pod,所以在命名时不需要指定应用的版本号。

创建Deployment资源

在创建这个Deployment之前,请确保删除仍在运行的任何ReplicationController和pod,但是暂时保留kubiaService。可以使用--all选项来删除所有的ReplicationController:

$kubectldeleterc--all此时己经可以创建一个Deployment了:

$kubectlcreate-fkubia-deployment-v1.yaml--recorddeploymentkubia"created注意:确保在创建时使用了--record选项。这个选项会记录历史版本号,在之后的操作中非常有用。

展示Deployment滚动过程中的状态

可以直接使用kubectlgetdeployment和kubectldescribedeployment命令来查看Deployment的详细信息,但是还有另外一个命令,专门用于查看部署状态:

$kubectlrolloutstatusdeploymentkubiadeploymentkubiasuccessfullyrolledout通过上述命令查看到,Deployment己经完成了滚动升级,可以看到三个pod副本己经正常创建和运行了:

$kubectlgetpoNAMEREADYSTATUSRESTARTSAGEkubia-1506449474-otnnh1/1Running014skubia-1506449474-vmn7s1/1Running014skubia-1506449474-xis6m1/1Running014s了解Deployment如何创建Replicaset以及pod

注意这些pod的命名,之前当使用ReplicationController创建pod时,它们的名称是由Controller的名称加上一个运行时生成的随机字符串(例如kubia-v1-m33mv)组成的。现在由Deployment创建的三个pod名称中均包含一个额外的数字。那是什么呢?

这个数字实际上对应Deployment和ReplicaSet中的pod模板的哈希值。如前所述,Deployment不能直接管理pod。相反,它创建了ReplicaSet来管理pod。所以看看Deployment创建的ReplicaSet是什么样子的。

$kubectlgetreplicasetsNAMEDESIREDCURRENTAGEkubia-15064494743310sReplicaSet的名称中也包含了其pod模板的哈希值。之后的篇幅也会介绍,Deployment会创建多个ReplicaSet,用来对应和管理一个版本的pod模板。像这样使用pod模板的哈希值,可以让Deployment始终对给定版本的pod模板创建相同的(或使用己有的)ReplicaSet。

通过Service访问pod

ReplicaSet创建了二个副本并成功运行以后,因为新的pod的标签和Service的标签选择器相匹配,因此可以直接通过之前创建的Service来访问它们。

至此可能还没有从根本上解释,为什么更推荐使用Deployment而不是直接使用ReplicationController。另一方面看,创建一个Deployment的难度和成本也并没有比ReplicationController更高。后面将针对这个Deployment做一些操作,并从根本上了解Deployment的优点和强大之处。接下来会介绍如何通过Deployment资源升级应用,并对比通过ReplicationController升级应用的区别,你就会明白这一点。

前面提到,当使用ReplicationController部署应用时,必须通过运行ku-bectlrolling-update显式地告诉Kubernetes来执行更新,甚至必须为新的ReplicationController指定名称来替换旧的资源。Kubernetes会将所有原来的pod替换为新的pod,并在结束后删除原有的ReplicationController。在整个过程中必须保持终端处于打幵状态,让kubectl完成滚动升级。

接下来更新Deployment的方式和上述的流程相比,只需修改Deployment资源中定义的pod模板,Kubernetes会自动将实际的系统状态收敛为资源中定义的状态。类似于将ReplicationController或ReplicaSet扩容或者缩容,升级需要做的就是在部署的pod模板中修改镜像的tag,Kubernetes会收敛系统,匹配期望的状态。

不同的Deployment升级策略

实际上,如何达到新的系统状态的过程是由Deployment的升级策略决定的,默认策略是执行滚动更新(策略名为RollingUpdate)。另一种策略为Recreate,它会一次性删除所有旧版本的pod,然后创建新的pod,整个行为类似于修改ReplicationController的pod模板,然后删除所有的pod。

Recreate策略在删除旧的pod之后才幵始创建新的pod。如果你的应用程序不支持多个版本同时对外提供服务,需要在启动新版本之前完全停用旧版本,那么需要使用这种策略。但是使用这种策略的话,会导致应用程序出现短暂的不可用。

RollingUpdate策略会渐进地删除旧的pod,与此同时创建新的pod,使应用程序在整个升级过程中都处于可用状态,并确保其处理请求的能力没有因为升级而有所影响。这就是Deployment默认使用的升级策略。升级过程中pod数量可以在期望副本数的一定区间内浮动,并且其上限和下限是可配置的。如果应用能够支持多个版本同时对外提供服务,则推荐使用这个策略来升级应用。

演示如何减慢滚动升级速度

在接下来的练习中,将使用Rollingupate策略,但是需要略微减慢滚动升级的速度,以便观察升级过程确实是以滚动的方式执行的。可以通过在Deployment上设置minReatySconds属性来实现。将在本章末尾解释这个属性的作用。现在,使用kubectlpateh命令将其设置为10秒。

$kubectlpatchdeploymentkubia-p'{"spec":{"minReadySeconds":10}}'"kubia"patched提示:kubectlpatch对于修改单个或者少量资源属性非常有用,不需要再通过编辑器编辑。

使用patch命令更改Deployment的自有属性,并不会导致pod的任何更新,因为pod模板并没有被修改。更改其他Deployment的属性,比如所需的副本数或部署策略,也不会触发滚动升级,现有运行中的pod也不会受其影响。

触发滚动升级

如果想要跟踪更新过程中应用的运行状况,需要先在另一个终端中再次运行curl循环,以查看请求的返回情况(需要将IP替换为Service实际暴露IP)

$kubectlsetimagedeploymentkubianodejs=luksa/kubia:v2deployment"kubia"imageupdated当执行完这个命令,kubiaDeployment的pod模板内的镜像会被更改为luksa/kubia:v2(从:v1更改而来)。如9.9所示。

如果循环执行了curl命令,将看到一开始请求只是切换到v1pod;然后越来越多的请求切换到v2pod中,最后所有的v1pod都被删除,请求全部切换到v2pod。这和kubectl的滚动更新过程非常相似。

Deployment的优点

回顾一下刚才的过程,通过更改Deployment资源中的pod模板,应用程序己经被升级为一个更新的版本——仅仅通过更改一个字段而己!

这个升级过程是由运行在Kubernetes上的一个控制器处理和完成的,而不再是运行kubectlrolling-update命令,它的升级是由kubectl客户端执行的。让Kubernetes的控制器接管使得整个升级过程变得更加简单可靠。

注意:如果Deployment中的pod模板引用了一个ConfigMap(或Secret),那么更改ConfigMap资源本身将不会触发升级操作。如果真的需要修改应用程序的配置并想触发更新的话,可以通过创建一个新的ConfigMap并修改pod模板引用新的ConfigMap。

Deployment背后完成的整个升级过程和执行kubectlrolling-update命令非常相似。一个新的ReplicaSet会被创建然后慢慢扩容,同时之前版本的Replicaset会慢慢缩容至0(初始状态和最终状态如图9.10所示)

可以通过下面的命令列出所有新旧ReplicaSet

$kubectlgetrsNAMEDESIREDCURRENTAGEkubia-15064494740024mkubia-15813571233323m与ReplicationController类似,所有新的pod现在都由新的ReplicaSet管理。与以前不同的是,旧的ReplicaSet仍然会被保留,而旧的ReplicationController会在滚动升级过程结束后被删除。之后马上会介绍这个被保留的旧ReplicaSet的用处。

因为并没有直接创建ReplicaSet,所以这里的ReplicaSet本身并不需要用户去关心和维护。所有操作都是在Deployment资源上完成的,底层的ReplicaSet只是实现的细节。和处理与维护多个ReplicationController相比,管理单个Deployment对象要容易得多。

尽管这种差异在滚动升级中可能不太明显,但是如果在滚动升级过程中出错或遇到问题,就可以明显看出两种方案的差异。下面来模拟一个错误。

现阶段,应用使用v2版本的镜像运行,接下来会先准备v3版本的镜像。

创建v3版本的应用程序

在v3版本中,将引入一个bug,使你的应用程序只能正确地处理前四个请求。第五个请求之后的所有请求将返回一个内部服务器错误(HTTP状态代码500)。你将通过在处理程序函数的开头添加if语句来模拟这个bug。下面的代码清单显示了修改后的代码,所有需要更改的地方都用粗体显示。

部署v3版本

$kubectlsetimagedeploymentkubianodejs=luksa/kubia:v3deployment"kubia"imageupdated可以通过运行kubectlrolloutstatus来观察整个升级过程:

$kubectlrolloutstatusdeploymentkubiaWaitingforrollouttofinish:1outof3newreplicashavebeenupdated...Waitingforrollouttofinish:2outof3newreplicashavebeenupdated...Waitingforrollouttofinish:1oldreplicasarependingtermination...deployment"kubia"successfullyrolledout新的版本己经开始运行。接下来的代码清单会显示,在发送几个请求之后,客户端开始收到服务端返回的错误。

不能让用户感知到升级导致的内部服务器错误,因此需要快速处理。在后面,将看到如何自动停止出错版本的滚动升级,但是现在先看一下如何手动停止。比较好的是,Deployment可以非常容易地回滚到先前部署的版本,它可以让Kubernetes取消最后一次部署的Deployment:

$kubectlrolloutundodeploymentkubiadeployment"kubia"rolledbackDeployment会被回滚到上一个版本。

提示:undo命令也可以在滚动升级过程中运行,并直接停止滚动升级。在升级过程中已创建的pod会被删除并被老版本的pod替代。

显示Deployment的滚动升级历史

回滚升级之所以可以这么快地完成,是因为Deployment始终保持着升级的版本历史记录。之后也会看到,历史版本号会被保存在ReplicaSet中。滚动升级成功后,老版本的ReplicaSet也不会被删掉,这也使得回滚操作可以回滚到任何一个历史版本,而不仅仅是上一个版本。可以使用kubectlrollouthistory来显示升级的版本:

$kubectlrollouthistorydeploymentkubiadeployments"kubia":REVISIONCHANGE-CAUSE2kubectlsetimagedeploymentkubianodejs=luksa/kubia:v23kubectlsetimagedeploymentkubianodejs=luksa/kubia:v3还记得创建Deployment时的--record参数吗?如果不给定这个参数,版本历史中的CHANGE-CAUSE这一栏会为空。这也会使用户很难辨别每次的版本做了哪些修改。

回滚到一个特定的Deployment版本

通过在undo命令中指定一个特定的版本号,便可以回滚到那个特定的版本。例如,如果想回滚到第一个版本,可以执行下述命令:

$kubectlrolloutundodeploymentkubia--to-revision=1还记得第一次修改Deployment时留下的ReplicaSet吗?这个ReplicaSet便表示Deployment的第一次修改版本。由Deployment创建的所有ReplicaSet表不完整的修改版本历史,如图9.11所示。每个ReplicaSet都用特定的版本号来保存Deployment的完整信息,所以不应该手动删除ReplicaSet。如果这么做便会丢失Deployment的历史版本记录而导致无法回滚。

旧版本的ReplicaSet过多会导致ReplicaSet列表过于混乱,可以通过指定Deployment的revisionHistoryLimit属性来限制历史版本数量。默认值是2,所以正常情况下在版本列表里只有当前版本和上一个版本(以及只保留了当前和上一个ReplicaSet),所有再早之前的ReplicaSet都会被删除。

注意:extensions/v1betal版本Deployment的revisionHisto-ryLimit没有值,在apps/v1beta2版本中,这个默认值是10

当执行如kubectlrolloutstatus命令来观察升级到v3的过程时,会看到第一个pod被新创建,等到它运行时,一个旧的pod会被删除,然后又一个新的pod被创建,直到再没有新的pod可以更新。创建新pod和删除旧pod的方式可以通过配置滚动更新策略内的两个属性。

介绍滚动升级策略的maxSurge和maxUnavailable属性

在Deployment的滚动升级期间,有两个属性会决定一次替换多少个pod:maxSurge和maxUnavailable。可以通过Deployment的strategy字段下rollingUpdate的子属性来配置,如下面的代码清单所示。

#代码9.10为rollingUpdate策略指定参数spec:strategy:rollingUpdate:maxSurge:1maxUnavailable:0type:RollingUpdate表9.2控制滚动升级速率的属性属性含义maxSurge决定了Deployment配置中期望的副本数之外,最多允许超出的pod实例的数量。默认值为25%,所以pod实例最多可以比期望数量多25%。

如果期望副本数被设置为4,那么在滚动升级期间,不会运行超过5个pod实例。当把百分数转换成绝对值时,会将数字四舍五入。这个值

也可以不是百分数而是绝对值(例如,可以允许最多多出一个或两个pod)。

决定了在滚动升级期间,相对于期望副本数能够允许有多少pod实例处于不可用状态。默认值也是25%,所以可用pod实例的数量不能低于

期望副本数的75%。百分数转换成绝对值时这个数字也会四舍五入。如果期望副本数设置为4,并且百分比为25%,那么只能有一个pod处于不

可用状态。在整个发布过程中,总是保持至少有三个pod实例处于可用状态来提供服务。与maxSurge—样,也可以指定绝对值而不是百分比。

由于在之前场景中,设置的期望副本数为3,上述的两个属性都设置为25%,maxSurge允许最多pod数量达到4,同时maxUnavailable不允许出现任何不可用的pod(也就是说三个pod必须一直处于可运行状态),如图9.12所示。

了解maxUnavailable属性

extensions/v1beta1版本的Deployment使用不一样的默认值,maxSurge和maxUnavailable会被设置为1,而不是25%。对于三个副本的情况,maxSurge还是和之前一样,但是maxUnavailable是不一样的(是1而不是0),这使得滚动升级的过程稍有不同,如图9.13所示。

在这种情况下,一个副本处于不可用状态,如果期望副本数为3,则只需要两个副本处于可用状态。这就是为什么上述滚动升级过程中会立即删除一个pod并创建两个新的pod。这确保了两个pod是可用的并且不超过pod允许的最大数量〔在这种情况下,最大数量是4,三个加上maxSurges的一个。一旦两个新的pod处于可用状态,剩下的两个旧的pod就会被删除了。

这里相对有点难以理解,主要是maxUnavailable这个属性,它表示最大不可用pod数量。但是如果仔细查看前一个图的第二列,即使maxUnavailable被设置为1,可以看到两个pod处于不可用的状态。

重要的是要知道maxUnavailable是相对于期望副本数而言的。如果replica的数量设置为3,maxUnavailable设置为1,则更新过程中必须保持至少两个(3-1)pod始终处于可用状态,而不可用pod数量可以超过一个。

在经历了v3版本应用的糟糕体验之后,假设你现在已经修复了这个错误,并推送了第四个版本的镜像。但是你还是有点担心像之前那样将其滚动升级到所有的pod上。你需要的是在现有的v2pod之外运行一个v4pod,并查看一小部分用户请求的处理情况。如果一旦确定符合预期,就可以用新的pod替换所有旧的pod。

可以通过直接运行一个额外的pod或通过一个额外的Deployment、ReplicationController或ReplicaSet来实现上述的需求。但是通过Deployment自身的一个选项,便可以让部署过程暂停,方便用户在继续完成滚动升级之前来验证新的版本的行为是否都符合预期。

暂停滚动升级

可以直接将镜像修改为luksa/kubia:v4(镜像已准备好)并触发滚动更新,然后立马〔在几秒之内)暂停滚动更新:

$kubectlsetimagedeploymentkubianodejs=luksa/kubia:v4deployment"kubia"imageupdated$kubectlrolloutpausedeploymentkubiadeployment"kubia"paused一个新的pod会被创建,与此同时所有旧的pod还在运行。一旦新的pod成功运行,服务的一部分请求将被切换到新的pod。这样相当于运行了一个金丝雀版本。金丝雀发布是一种可以将应用程序的出错版本和其影响到的用户的风险化为最小的技术。与其直接向每个用户发布新版本,不如用新版本替换一个或一小部分的pod。通过这种方式,在升级的初期只有少数用户会访问新版本。验证新版本是否正常工作之后,可以将剩余的pod继续升级或者回滚到上一个的版本。

恢复滚动升级

在上述例子中,通过暂停滚动升级过程,只有一小部分客户端请求会切换到V4pod,而大多数请求依然仍只会切换到v3pod。一旦确认新版本能够正常工作,就可以恢复滚动升级,用新版本pod替换所有旧版本的pod:

$kubectlrolloutresumedeploymentkubiadeployment"kubia"resumed在滚动升级过程中,想要在一个确切的位置暂停滚动升级目前还无法做到,以后可能会有一种新的升级策略来自动完成上面的需求。但目前想要进行金丝雀发布的正确方式是,使用两个不同的Deployment并同时调整它们对应的pod数量。

使用暂停功能来停止滚动升级

暂停部署还可以用于阻止更新Deployment而自动触发的滚动升级过程,用户可以对Deployment进行多次更改,并在完成所有更改后才恢复滚动升级。一旦更改完毕,则恢复并启动滚动更新过程。

注意:如果部署被暂停,那么在恢复部署之前,撤销命令不会撤销它。

现在讨论Deployment资源的另一个属性。在<升级Deployment>节开始时在Deployment中设置的minReadySeconds属性吗?使用它来减慢滚动升级速率,使用这个参数之后确实执行了滚动更新,并且没有一次性替换所有的pod。minReadySeconds的主要功能是避免部署出错版本的应用,而不只是单纯地减慢部署的速度。

了解minReadySeconds的用处

使用这个属性可以通过让Kubernetes在pod就绪之后继续等待10秒,然后继续执行滚动升级,来减缓滚动升级的过程。通常情况下需要将minReadySeconds设置为更高的值,以确保pod在它们真正开始接收实际流量之后可以持续保持就绪状态。

当然在将pod部署到生产环境之前,需要在测试和预发布环境中对pod进行测试。但使用minReadySeconds就像一个安全气囊,保证了即使不小心将bug发布到生产环境的情况下,也不会导致更大规模的问题。

使用正确配置的就绪探针和适当的minReadySeconds值,Kubernetes将预先阻止发布部署带有bug的v3版本。下面会展示如何实现。

配置就绪探针来阻止全部v3版本的滚动部署

再一次部署v3版本,但这1次会为pod配置正确的就绪探针。由于当前部署的是v4版本,所以在开始之前再次回滚到v2版本,来模拟假设是第一次升级到v3。当然也可以直接从v4升级到v3,但是后续假设都是先回滚到了v2版本。

与之前只更新pod模板中的镜像不同的是,还将同时为容器添加就绪探针。之前因为就绪探针一直未被定义,所以容器和pod都处于就绪状态,即使应用程序本身并没有真正就绪甚至是正在返回错误。Kubernetes是无法知道应用本身是否出现了故障,也不会将未就绪信息暴露给客户端。

同时更改镜像并添加就绪探针,则可以使用kubectlapply命令。使用下面的YAML来更新Deployment(将它另存为kubian-deployment-v3-with-readinesscheck.yaml),如下面的代码清单所示。

可以使用如下方式直接使用kubectlapply来升级Deployment:

提示:使用kubectlapply更新Deployment时如果不期望副本数被更改,则不用在YAML文件中添加repLicas这个字段。

运行apply命令会自动开始滚动升级过程,可以再一次运行rolloutstatus命令来查看升级过程:

$kubectlrolloutstatusdeploymentkubiaWaitingforrollouttofinish:1outof3newreplicashavebeenupdated...因为升级状态显示一个新的pod己经创建,一小部分流量应该也会切换到这个pod。可以通过下面的命令看到:

$kubectlgetpoNAMEREADYSTATUSRESTARTSAGEkubia-1163142519-7ws0i0/1Running030skubia-1765119474-jvslk1/1Running09mkubia-1765119474-pmb261/1Running09mkubia-1765119474-xk5g31/1Running08m可以看到,有一个pod并没有处于就绪状态

就绪探针如何阻止出错版本的滚动升级

当新的pod启动时,就绪探针会每隔一秒发起请求(在podspec中,就绪探针的间隔被设置为1秒)。在就绪探针发起第五个请求的时候会出现失败,因为应用从第五个请求开始一直返回HTTP状态码500。

因此,pod会从Service的endpoint中移除(参见图9.14)。当执行curl循环请求服务时,pod已经被标记为未就绪。这就解释了为什么curl发出的请求不会切换到新的pod。这正是符合预期的,因为你不希望客户端流量会切换到一个无法正常工作的pod。

rolloutstatus命令显示只有一个新副本启动,之后滚动升级过程没有再继续下去,因为新的pod—直处于不可用状态。即使变为就绪状态之后,也至少需要保持10秒,才是真正可用的。在这之前滚动升级过程将不再创建任何新的pod,因为当前maxUnavailable属性设置为O,所以也不会删除任何原始的pod。

实际上部署过程自动被阻止是一件好事。如果继续用新的pod替换旧的pod,那么最终服务将处于完全不能工作的状态,就和当时没有使用就绪探针的情况下滚动升级v3版本时出现的结果一样。但是添加了就绪探针之后,升级出错程序而对用户造成的影响面不会过大,对比之前替换所有pod的方式,只有一小部分用户受到了影响。

提示:如果只定义就绪探针没有正确设置minReadySeconds,—旦有一次就绪探针调用成功,便会认为新的pod已经处于可用状态。因此最好适当地设置minReadySeconds的值。

为滚动升级配置deadline

默认情况下,在10分钟内不能完成滚动升级的话,将被视为失败。如果运行kubectldescribedeployment命令,将会显示一条ProgressDeadline-Exceeded的记录,如下面的代码清单所示。

注意:extensions/v1betal版本不会设置默认的deadline。

取消出错版本的滚动升级

因为滚动升级过程不再继续,所以只能通过rolloutundo命令来取消滚动升级:

$kubectlrolloutundodeploymentkubiadeployment"kubia"rolledback

使用默认编辑器打开资源配置。修改保存并退出编辑器,资源对象会被更新

例子:kubectleditdeploymentkubia

修改单个资源属性

例子:kubectlpatchdeploymentkubia-p'{"spec":{"template":{"spec":{"containers":

[{"name":"nodejs","image":"luksa/kubia:v2"}]}}}}’

通过一个完整的YAML或JSON文件,应用其中新的值来修改对象。如果YAML/JSON中指定的对象不存在,

则会被创建。该文件需要包含资源的完整定义(不能像kubectlpatch那样只包含想要更新的字段)

例子:kubectlapply-fkubia-deployment-v2.yaml

将原有对象替换为YAML/JSON文件中定义的新对象。与apply命令相反,

运行这个命令前要求对象必须存在,否则会打印错误

例子:kubectlreplace-fkubia-deployment-v2.yaml。

修改Pod、ReplicationController、Deployment、DemonSet、Job或ReplicaSet内的镜像

例子:kubectlsetimagedeploymentkubianodejs=luksa/kubia:v2

THE END
1.家庭维修市场需求广阔,第三方维修平台仍具增长潜力啄木鸟随着科技的飞速发展和人们生活水平的日益提高,家庭维修服务的需求呈现出爆发式增长。然而,国内外在家庭维修行业的发展状况却存在显著差异。本文将从经济发展阶段、劳动力价值认可以及标准化建设三个维度,结合具体数据和分析,对比国内外维修业的异同,并探讨国内市场中具有价值和前景的一些平台的发展方向。 http://finance.sina.com.cn/tech/roll/2024-12-09/doc-incyvxpv2684270.shtml
2.家电维修行业现状3. 家电维修行业的竞争状况: 随着家电维修行业的迅速发展,竞争也变得日益激烈。越来越多的维修公司和个人加入这个行业,提供各种维修服务。维修人员需要具备更高的专业素质和技术能力,才能在竞争中脱颖而出。提供优质的服务和建立良好的信誉也成为维修人员吸引客户的重要因素。 http://www.skywxnj.com/article/314332.html
3.2023年中国家电维修行业现状及趋势(附市场规模产业链及重点企业整体而言,在人口增长、城市化及生活水平提高的推动下,中国市场上的大型家电销售额出现了大幅增长,随着家电产品的普及和消费者对家电维修服务需求的增加,我国家电维修行业的市场规模持续增长。2022年,中国家电维修行业市场规模为2206亿元。https://m.huaon.com/detail/988517.html
4.家电维修前景怎么样家电维修的生意为什么不好做了→MAIGOO知识摘要:过去,家电维修是一个比较好做的生意,毕竟那时候的家电是家里的大件耐用消费品,如果出现了故障,人们会愿意去维修。可是随着家电市场的饱和,价格的降低,人们的消费观念发生了变化,家电坏了不是去维修而是会买新的,所以现在家电维修的生意不好做了。 https://www.maigoo.com/goomai/270261.html
5.家电维修个人简历(通用16篇)家电维修个人简历(通用16篇) 三下乡总结大学生社会实践活动是大学生广泛接触社会、增长见识、锻炼成材的有效途径,是学生了解国情、服务社会的必由之路。对加强大学生思想道德修养、培养大学生创新精神和实践能力、推广素质拓展起着重要作用。为进一步贯彻和实施大学生“https://www.360wenmi.com/f/filev3tx1zax.html
6.现在学家电维修有前途吗有很多人问现在学家电维修到底有没有前途、就业前景到底好不好?近几年这个空调维修、洗衣机维修、冰箱维修、电视维修等大型家电的市场很广,市场需求量也很大的,所以说学家电维修这个职业还是很不错的也有很大得提升空间,就业前景也是很不错的。 首先呢咱们先来对家电维修的市场进行一种理性的分析,现在每家每户几乎家https://www.hyww.com/new/4621
7.[分享]电动自行车常识进入【家电维修技术】查看完整内容麻辣点评 7、如何识别电动自行车?1. 什么是电动自行车? 电动自行车是指以蓄电池作为辅助能源,具有两个车轮,能实现人力骑行、电动或电助动功能的特种自行车。根据国家标准对设计最高时速、空车质量、外形尺寸的相关规定,电动自行车归属非机动车管理范畴,应同时具备以下5个特征:必须具备http://bbs.chinadz.com/news__401249
8.售后述职报告通用8篇根据国家要求,上门从事家电安装、维修等业务人员都必须有相应的资格认证和岗位证书才行。但厂家把售后服务交给代理商,代理商又转包给维修网点,因为这种售后服务只是一种促销手段,大都流于形式,服务人员就很难规范。 毫无疑问,只有具备好的素质才能提供优质的服务。现在提供售后服务的人员分两大类:一是厂家和代理商聘用https://www.liuxue86.com/a/5034655.html
9.家用电器维修总结6篇(全文)在设备维修方面因为现在是刚刚毕业实习,在机械设备维修上几乎没什么经历,但是在平常生活中有时候经常对一般的家用电器方面做过一些相应的简单维修,也见过不少大型家电的维修实例,在这里做一个总结。 家电维修维修一般是拿到商家指定的维修点进行维修的,但是大型家如果不在保修期进行维修时的各种收费一般不会很便宜,所以https://www.99xueshu.com/w/filed92cn73l.html
10.家电维修加盟家电维修加盟费多少加盟电话家电维修加盟总部根据市场销售状况及规模等因素,选择样板市场,重点提供支持,倾力打造。 家电维修加盟流程 1、投资咨询:投资者以电话、传真、网上留言等方式向总部专业投资顾问咨询相关合作事项,索取相关资料。 2、实地考察:投资者到总部所在地进行实地考察,并与总部工作人员进行业务交流。 http://www.1637.com/jiadianweixiu/
11.租房电器坏了谁出钱维修专家导读 如租赁合同中未明确列出房屋内电器的权责划分,且租户未故意造成家电损坏,维修费用由房东承担。承租人按照约定的方法或者租赁物的性质使用租赁物,造成租赁物损失的,不承担损害赔偿责任。 租房电器坏了谁出钱维修 一、租房电器坏了谁出钱维修 如租赁合同中未明确列出房屋内电器的权责划分,且租户未故意造成家电https://m.64365.com/zs/1828604.aspx
12.家电维修高峰将至申城家电维修“正规军”公布跟殷阿姨一样,不少市民家里空调、冰箱都出过状况,却苦于找不到正规军上门维修。有的市民表示,打过10000,上过百度,甚至也打过报纸上推荐的电话,但都是大兴的。 来自上海市商务委的消息,上海16个行政区域目前拥有509家正规家电维修服务企业,各生产厂商的售后服务点有153家,这些都能够为市民提供正规家电维修服务。https://m.kankanews.com/detail/LMwVe7ZE1Qz?utm_source=Else
13.2024年家电维修行业分析报告及未来五到十年行业发展趋势报告.docx本市场行业分析报告致力于为您提供一个全面、深入且实用的家电维修行业市场分析,以助您更好地了解家电维修市场现状、竞争格局和未来发展趋势。我们对各种权威数据来源进行了深入挖掘和分析,以确保您在做出关键商业决策时拥有可靠的信息依据。报告中,我们将重点关注家电维修行业市场规模、增长趋势、竞争状况以及影响行业发展https://max.book118.com/html/2023/1209/8025017041006014.shtm