K8S-Deployment
介绍
Kubernetes
最核心的功能:编排Pod
本质上是对容器进一步的抽象和封装,容器就好像我们平时了解到的集装箱一样,集装箱虽然好用,但是四面光秃秃的,吊车怎么来吊集装箱并且把它摆好呢?Pod对象就好像是加装了吊环的集装箱,让吊车(Kubernetes)能够能够更好的操作它,Kubernetes
怎么操作它呢,就是由控制器(Controller),最常用的控制器Deployment。
下面是一个简单的名为nginx-deployment
的例子:
1 | apiVersion: apps/v1 |
上面这个Deployment的编排很简单,确保带有标签app=nginx
的Pod只有两个,等于spec.replicas
指定的个数。
集群中如果有携带标签app=nginx
的Pod被删除,就会有新的Pod创建起来,跟之前被删除的Pod属性一样。
这个动作能够执行全靠一个名为kube-controller-manager
的组件,controller有很多种,比如下面的
- deployment
- job
- statefulset
- replicaset
- cronjob
- service
- volume
- ···
这些控制器之所以都放在controller下面,是因为他们都遵循Kubernetes项目的一个通用编排模式,控制循环。
比如用下面的这样一段伪代码可以解释一下什么叫做控制循环,一个待编排的对象的X。带有一个控制器。
1 | for { |
实际状态往往来自于Kubernetes
集群本身,kubelet
通过心跳汇报容器状态和节点状态,或者是监控系统保存的应用监控数据,或者是控制器主动收集自己感兴趣的信息。期望状态一般就是提交的YAML了。
比如Deployment
对象的Replicas
字段的值就往往是保存在Etcd
中。
Deployment详解
控制器模型的实现
仍然以上面的名为nginx-deployment为例
Deployment
控制器从Etcd中获取所有携带app:nginx
标签的Pod,并且统计数量,这个就是实际状态。Deployment
对象的Replicas
字段就是期望值。Deployment
控制器将两个状态作比较,根据比较的状态,执行类似上文的伪代码,判断是要新增Pod还是删除Pod。
这个操作通常被叫做调谐(Reconcile
),这个调谐的过程被称作Reconcile Loop
(调谐循环)或者Sync Loop
(同步循环),就像增加Pod,删除Pod,更新Pod这些都算是Kubernetes中面向API对象编程的一个直观体现。
控制器本身负责定义被管理对象的期望状态,比如replicas
字段,被控制的对象则来自于一个模板,Deployment
的template
字段,这个template
字段的属性跟标准的Pod定义丝毫不差,Deployment
的template
字段在Kubernetes中有个专有的名字叫做PodTemplate
(Pod模板)
Deployment的水平扩展和收缩
ReplicaSet简介
如果你更新了Deployment的Pod模板,比如你修改了镜像的版本,那么Deployment就需要要遵循一种叫做滚动更新的策略还更新控制器,而这个能力的实现就是依靠ReplicaSet
详解
还是从上面的名为nginx-deployment
的例子说到ReplicaSet
先使用如下命令创建nginx-deployment
这个Deployment
1 | kubectl apply -f nginx-deployment.yml |
查看ReplicaSet状态
1 | 先使用如下命令等待Deployment都Running起来 |
获取到结果如下
从图中可以大概看出来ReplicaSet
对象就是由副本数目定义和一个Pod模板组成的,是Deployment
的一个子集,重要的是,Deployment
操作的是ReplicaSet
对象,而不是直接操作的Pod
分析ReplicaSet
还是从上面的Deployment说起
1 | apiVersion: apps/v1 |
在这个副本数量为3的控制器中,Deployment
,ReplicaSet
和Pod
的关系是怎么样的
从上图中我么就可以看到,其实Deployment
并不直接关系到Pod
,他是通过ReplicaSet
间接操控Pod
,其中ReplicaSet
通过控制器模式保证集群中Pod的数量是期望的值那样,这也正是Deployment
只允许容器的restartPolicy=Always
的主要原因,只有在容器保证自己始终是Running的情况下,调整Pod
的个数才有意义
水平扩展/收缩
在此基础上,Deployment
同样通过控制器模式来操控ReplicaSet
的个数和属性,进行”水平收缩/扩展“和“滚动更新”这两个编排动作。在Deployment
中水平收缩扩展比较容易实现,只需要修改ReplicaSet
的Pod
副本个数就可以了。
想要实现这个操作很简单,看下面的命令
1 | 第一种方式:使用scale命令进行扩展 |
滚动更新
我们如下命令对Deployment
进行删除,并且在重建的时候加上--record
参数,这个参数为了记录你每次操作执行的命令方便以后查看
1 | 先删除 |
从上面的输出中可以看到,这个Deployment
有四个状态字段:
- DESIRED:用户期望的Pod副本个数(spec.replicas)
- READY:当前处于Running状态的Pod数量
- UP-TO-DATE:当前处于最新版本Pod的个数,所谓最新版本是指Pod的Spec部分跟Deployment里模板定义的一样。(可以会存在正在进行滚动更新还没删除的老Pod)
- AVAILABLE:当前已经可用的Pod个数,既是Running状态,有事最新版本,而且已经处于Ready状态的Pod个数,这个属性才是我们最为期待成功的属性
我们再来尝试查看这个ReplicaSet
1 | [root@localhost home]# kubectl get rs |
如上所示用户提交Deployment
对象之后,Deployment Controller
为立即创建一个副本数为3的ReplicaSet
,这个ReplicaSet
的名字是Deployment
名字和一个随机字符串组成的,这个随机字符串叫做pod-template-hash
。同时Pod
也会加上这个字符串用于跟其他的Pod进行区分。通过这个列表可以看到,Deployment
只是比ReplicaSet
多了一个UP-TO-DATE
字段
1 | 可以使用如下命令看看Pod |
如果这个时候我修改了Deployment
的Pod
模板,就会触发滚动更新操作,比如使用kubectl edit
命令或者还是修改yaml文件再执行kubectl apply
这里我使用kubectl edit
命令,升级nginx
版本到1.9.1
1 | [root@localhost home]# kubectl edit deployment/nginx-deployment |
kubectl edit
编辑完保存退出之后,Kubernetes会马上触发滚动更新流程
通过如下命令可以看到滚动更新的流程
1 | [root@localhost home]# kubectl describe deployment nginx-deployment |
从Events中可以看出来,首先修改了Deployment的定义之后,Deployment Controller会根据模板的定义创建一个新的ReplicaSet,也就是这里的nginx-deployment-779fcd779f
,老的ReplicaSet是nginx-deployment-76bf4969df
- 2m48s,这个新的ReplicaSet控制的Pod副本数从0变成1,即水平扩展出一个副本
- 紧接着在2m26s时,将老的ReplicaSet控制的Pod数从3变成了2,水平收缩成两个副本
- 如此交替运行,一个扩,一个收
现在你在使用下面的命令查看可以看到从以前的2个ReplicaSet变成了3个ReplicaSet,旧的ReplicaSet每个属性的值都变成了0
1 | [root@localhost home]# kubectl get rs |
滚动更新的优势
比如在刚开始升级的时候,集群里只有1个新增的新版本Pod,这个新Pod启动不起来,滚动更新就停止了,从而开发和运维介入,这个介入结束之前都还是有两个旧的Pod在运行,所以服务并不会收到太多的影响。
这种情况也必须依赖于Pod使用Health Check检查应用的运行状态,而不是简单的依赖于容器的Running状态,要不然虽然容器可能已经Running了,但是服务可能还是没启动,那滚动更新的效果就达不到了。(就像我们简单的运行一个SpringBoot项目,万一它启动做了很多事,初始化一堆东西,需要几分钟,但是只要这条启动命令java -jar xxx.jar
运行起来没错Pod就是Running了,然后就启动其它Pod,最后发现新三个Pod全部启动但是都还在初始化,旧的就全被删了,这样服务就存在不可用的时间节点)
为了保证服务的连续性,Deployment Controller还会保证在任何时间内,只有指定比例的Pod处于离线状态,同事也确保任何时间内只有指定比例的新Pod创建出来。这两个比例都可配置,默认是DESIRED的25%
上面的例子中,最少有2个Pod处于可用状态,最多有4个同时在集群中,这个配置还是在Deployment中的配置中,字段名为RollingUpdateStrategy,如下:
1 | apiVersion: apps/v1 |
新增下面的strategy字段,maxSurge指定除了DESIRED之外在一次滚动中,最多还可以创建多少个新Pod,而maxUnavailable是指在一次滚动中最多删除多少个旧Pod,当然除了配置数值也能直接配置百分比像50%这种。滚动更新的时候示意图如下:
Deployment控制ReplicaSet的数量和ReplicaSet的属性
ReplicaSet中Pod的数量是由ReplicaSet Controller来控制的。
一次失败的滚动更新
这次我们做一个错误的操作,我们制定nginx镜像为一个错误镜像nginx:1.91
,这样就会出现升级失败
1 | 直接使用如下命令修改镜像 |
因为我们这里其实使用的是一个错误的镜像版本,DockerHub中并不存在的镜像,Deployment的滚动更新会报错停止。
现在我们来看看现在集群里面的ReplicaSet
1 | [root@localhost ~]# kubectl get rs |
能够看到名为nginx-deployment-79dccf98ff
的ReplicaSet是新创建出来的,因为我们之前指定了maxSurge属性可以让他新创建的Pod数为1,现在这个ReplicaSet的DESIRED
和CURRENT
都是1,但是READY确是0,因为拉取不到指定的镜像,我们也可以使用如下命令查看现在集群中的Pod,发现有一个Pod的状态是ImagePullBackOff
滚动更新失败的回滚
回滚Deployment版本其实很简单,只需要执行kubectl rollout undo
命令,就能把Deployment回滚到上个版本。
1 | [root@localhost ~]# kubectl rollout undo deployment/nginx-deployment |
执行完成之后我们再次查看ReplicaSet,发现之前升级镜像版本指定的1.91创建的ReplicaSet的所有值全部在此变回了0
如果想回退到更早的版本,可以使用kubectl rollout history
查看每次修改Deployment对应的版本,由于我们之前创建Deployment的时候加上了--record
参数,所以我们执行的kubectl命令都被会记录下来。
1 | [root@localhost ~]# kubectl rollout history deployment/nginx-deployment |
像这里2就是之前那个失败的操作,指定镜像版本1.91那个,使用如下命令可以看到2的细节
1 | [root@localhost ~]# kubectl rollout history deployment/nginx-deployment --revision=2 |
可以看到镜像版本1.91,有了这个我们同样也可以制定Deployment回滚到哪个版本,同时也是按照滚动更新的方式进行回滚,通过如下命令
1 | --to-revision指定回滚到哪个版本 |
更高级的更新
每次对Deployment进行一次更新操作都会触发创建一个新的ReplicaSet,这样弄下来是不是ReplicaSet太多了你觉得,Kubernetes提供了一种操作,让你多次更新只创建一个ReplicaSet,具体如下:
1 | 我们先查看这个nginx-deployment的Deployment有两个ReplicaSet,中间的两个 |
更新之前,首先执行下面的这条命令,目的是让Deployment先进入一个暂停状态:
1 | [root@localhost ~]# kubectl rollout pause deployment/nginx-deployment |
执行完这条命令之后,我们对Deployment的修改都不触发滚动更新,也不会创建新的ReplicaSet。
同时我们执行下面这三条命令,先把镜像版本改成1.92,第二步再改成1.93,发现ReplicaSet并没有任何改变。
1 | [root@localhost ~]# kubectl set image deployment/nginx-deployment nginx=nginx:1.92 |
有了上面的暂停状态,自然也能恢复回来,执行下面这条命令,就能恢复到正常状态,这两条命令之间执行的多个更新操作最后只会生成一个ReplicaSet。现在执行下面命令进行恢复状态:
1 | [root@localhost ~]# kubectl rollout resume deployment/nginx-deployment |
虽然上面的操作能够减少ReplicaSet的数量,但是多次操作还是很多,可能我们并不需要保留这么多的历史操作记录,Deployment对象有个字段spec.revisionHistoryLimit
,就是用来限制Kubernetes为Deployment保留的历史个数,如果直接设置为0,就不能做回滚操作
金丝雀发布(Canary Deployment)和蓝绿部署(Blue-Green Deployment)
蓝绿部署
蓝绿部署:蓝绿部署是不停老版本,部署新版本然后进行测试,确认OK,将流量切到新版本,然后老版本同时也升级到新版本。
灰度发布
灰度发布(金丝雀发布)是指在黑与白之间,能够平滑过渡的一种发布方式。AB test就是一种灰度发布方式,让一部分用户继续用A,一部分用户开始用B,如果用户对B没有什么反对意见,那么逐步扩大范围,把所有用户都迁移到B上面来。灰度发布可以保证整体系统的稳定,在初始灰度的时候就可以发现、调整问题,以保证其影响度,而我们平常所说的金丝雀部署也就是灰度发布的一种方式。
金丝雀发布名字的来源
注释:矿井中的金丝雀
17世纪,英国矿井工人发现,金丝雀对瓦斯这种气体十分敏感。空气中哪怕有极其微量的瓦斯,金丝雀也会停止歌唱;而当瓦斯含量超过一定限度时,虽然鲁钝的人类毫无察觉,金丝雀却早已毒发身亡。当时在采矿设备相对简陋的条件下,工人们每次下井都会带上一只金丝雀作为“瓦斯检测指标”,以便在危险状况下紧急撤离。
金丝雀发布
有了Deployment是不是这些都很简单,金丝雀发布先用金丝雀做探视,就好像我们的Deployment先部署新的ReplicaSet开始新建Pod做测试,成功就继续增加新的Pod,新增新的Pod减少老的Pod。
少量金丝雀接受流量,再全量发布。
优点:
- 用户体验小,金丝雀发布过程中只影响少量用户
流量发布图如下:
蓝绿部署
蓝绿部署其实很简单,就是先把新的部署好,再把流量全部切换过去。
可以先创建blue.yaml部署起来运行一段时间,现在复制一个green.yaml,修改需要改变的信息,然后让Service指向这个gree.yaml新建的Pod。