经过前面不少文章的铺垫,终于可以写这个大家都感兴趣的话题了,在前面两篇文章,我们讲了Kubernetes里的 Pod副本集ReplicaSet (RS) 这两个API对象。知道了PodKubernetes里的最小调度单元,ReplicaSet则是控制Pod副本数的一个基础控制器。文章最后留下了一个话题:

Kubernetes里一般使用Deployment控制器而不是直接使用ReplicaSet,Deployment是一个管理ReplicaSet并提供水平扩展/收缩、Pod声明式更新、应用的版本管理以及许多其他功能的更高级的控制器。

所以部署到Kubernetes集群里的Go项目就是通过Deployment这个控制器实现应用的水平扩展/收缩更应用新管理的,它通过自己的控制循环确保集群里当前的状态始终等于Deployment对象定义的期望状态。

我会使用《Kubernetes入门实践--部署运行Go项目》文章里用过的项目作为演示项目,演示Kubernetes怎么对应用服务进行水平扩容、发版更新、版本回滚等操作,在演示的过程中一起探讨下面几个话题:

  • 什么是Deployment控制器

  • Deployment的工作原理。

  • 怎么创建Deployment

  • 如何使用Deployment滚动更新应用。

  • 如何使用Deployment进行应用的版本回滚。

什么是Deployment

Kubernetes中,建议使用Deployment来部署PodRS,因为它具有很多方便管理集群的内置功能,比如:

  • 轻松部署RS(副本集)

  • 清理不再需要的旧版RS

  • 扩展/缩小RS里的Pod数量

  • 动态更新Pod(根据Pod模板定义的更新用新Pod替换旧Pod)

  • 回滚到以前的Deployment版本

  • 保证服务的连续性

以下面这个Deployment对象的定义为例,第一部分是自己的元信息(name, labels)的定义,第二部分是ReplicaSet对象的定义(spec.replica=3....),ReplicaSet定义里又包含了Pod的定义(spec.template):

apiVersion: apps/v1
kind: Deployment
metadata:name: nginx-deploymentlabels:app: nginx
spec:replicas: 3selector:matchLabels:app: nginxtemplate:metadata:labels:app: nginxspec:containers:- name: nginximage: nginx:1.7.9ports:- containerPort: 80

在具体的实现上,这个Deployment,与ReplicaSet,以及Pod 的关系和管理层级我们可以用一张图把它描述出来:

Deployment、RS和Pod的关系

Deployment的工作原理

Kubernetes里有很多种控制器,每一个控制器,都以独有的方式负责某种编排功能。Deployment,正是这些控制器中的一种。它们都遵循 Kubernetes 项目中的一个通用编排模式,即:控制循环(control loop),每种控制器负责的编排功能就是它们自己在控制循环里实现的逻辑。

接下来,还是以上面定义的Deployment 为例,我和你简单描述一下的工作原理:

  • Deployment 控制器从 Etcd 中获取到所有携带了"app: nginx"标签的 Pod,然后统计它们的数量,这就是实际状态;

  • Deployment 对象的 Replicas 字段的值就是期望状态,Deployment 控制器将两个状态做比较;

  • 根据比较结果,Deployment确定是创建 Pod,还是删除已有的 Pod,还是什么不干;

这是针对Pod副本数的编排,至于Pod的动态更新和Deployment对象版本的回滚文章下面再说。总而言之,控制器的核心思想就是通过控制循环不断地将实际状态调谐成定义的期望状态,一旦期望状态有更新就会触发控制循环里的调谐逻辑。

怎么创建Deployment

创建Deployment前需要先声明它的对象定义,我们拿以前文章《Kubernetes入门实践--部署运行Go项目》里用到过的Deployment定义简单解释下每部分的含义:

apiVersion: apps/v1
kind: Deployment
metadata: # Deployment的元数据name: my-go-app
spec:replicas: 1 # ReplicaSet部分的定义selector:matchLabels:app: go-apptemplate: # Pod 模板的定义metadata:labels:app: go-appspec: # Pod里容器相关的定义containers:- name: go-app-containerimage: kevinyan001/kube-go-appresources:limits:memory: "128Mi"cpu: "100m"ports:- containerPort: 3000
  • apiVersion 声明了对象的API版本,Kubernetes会去对应的包里加载库文件。

  • kind声明对象的种类,其实就是告诉Kubernetes去加载什么对象。

  • metadata就是我们这个对象的元数据。

  • spec.replicas 定义副本集有多少个Pod副本,而spec.selectors则是副本集匹配Pod的规则。

  • spec.template是Pod模板的定义,其中的内容就是一个完整的Pod对象的定义。

  • spec.template.spec是关于Pod里容器相关的定义。

具体里面每个字段的意思和用途我就不多说了,前面的文章里都讲过,重点强调一下容器配置里limits.memory128Mi代表的是内存分配给容器128兆,而limits.cpu的1000m = 1核心。100m就是分配给容器0.1核,这个在自己电脑上实践的时候尽量别分配太大,不然根本启动不起来。

写好声明文件后,使用kubectl create命令创建Deployment对象,Kubernetes里所有的API对象都是这么创建的。

➜  kubectl create -f deployment.yaml --record
deployment.apps/my-go-app created
➜

对于在笔记本上实践的同学,需要先安装Minikube,具体的安装步骤可以参考:Minikube-运行在笔记本上的Kubernetes集群。

在继续使用Deployment进行更高级的编排工作前,我们先用下面两个命令确保一下Deployment的运行状态:

  • kubectl rollout status deployment 告诉我们Deployment对象的状态变化。

    ➜ kubectl rollout status deployment my-go-app
    deployment "my-go-app" successfully rolled out
    
  • kubectl get deployment 显示期望的副本数和正在更新的副本数,以及当前可提供服务的Pod数量。因为我们在定义里只指定了一个副本,所以当前只有一个Pod

    kubectl get deployment my-go-app
    NAME        READY   UP-TO-DATE   AVAILABLE   AGE
    my-go-app   1/1     1            1           13m
    
  • kubectl get replicaset 查看Deployment为Pod创建的ReplicaSet的状态。

    kubectl get replicaset
    NAME                   DESIRED   CURRENT   READY   AGE
    my-go-app-864496b67b   1         1         1       19m
    

    默认情况下,Deployment会将pod-template-hash添加到它创建的ReplicaSet的名称中。比如这里的my-go-app-864496b67b

  • 最后 kubectl get pod 命令可以查看ReplicaSet创建出来的Pod副本的状态。

    NAME                         READY   STATUS             RESTARTS   AGE
    my-go-app-864496b67b-ctkf9   1/1     Running            0          25m
    

使用Deployment滚动更新应用

Deployment 通过"控制器模式",来操作ReplicaSet 的个数和属性,进而实现"水平扩展 / 收缩" "滚动更新" 这两个编排动作。

水平扩展/收缩

"水平扩展 / 收缩"非常容易实现,Deployment 只需要修改它所控制的ReplicaSetPod 副本个数就可以了。比如,把这个值从 1 改成 3,那么 Deployment 所对应的 ReplicaSet,就会根据修改后的值自动创建两个新的Pod,"水平收缩"则反之。这个操作的指令也非常简单,就是 kubectl scale,比如:

➜ kubectl scale --replicas=3 deployment my-go-app --record
deployment.apps/my-go-app scaled

如果你手快点还能通过上面说的命令 kubectl rollout status deployment my-go-app 看到扩展过程中Deployment对象的状态变化:

kubectl rollout status deployment my-go-app
Waiting for deployment "my-go-app" rollout to finish: 1 of 3 updated replicas are available...
Waiting for deployment "my-go-app" rollout to finish: 2 of 3 updated replicas are available...
deployment "my-go-app" successfully rolled out

可以通过下面的命令观察到ReplicaSet的Name没有发生变化:

➜   kubectl get replicaset
NAME                   DESIRED   CURRENT   READY   AGE
my-go-app-864496b67b   3         3         3       53m

这证明了 Deployment水平扩展和收缩副本集是不会创建新的ReplicaSet的,但是涉及到Pod模板的更新后,比如更改容器的镜像,那么Deployment会用创建一个新版本的ReplicaSet用来替换旧版本。

滚动更新

在上面的Deployment定义里,Pod模板里的容器镜像设置的是kevinyan001/kube-go-app,接下来比如我们的Go项目代码更新了,用最新的代码打包了镜像 kevinyan001/kube-go-app:v0.1,部署Go项目的新镜像的过程就会触发Deployment的滚动更新。

有两种方式更新镜像,一种是更新deployment.yaml里的镜像名称,然后执行 kubectl apply -f deployment.yaml。一般公司里的Jenkins等持续继承工具用的就是这种方式。还有一种就是使用kubectl set image 命令,为了方便演示我们这里就是用第二种方式进行Pod的滚动更新。

➜  kubectl set image deployment my-go-app go-app-container=kevinyan001/kube-go-app:v0.1 --record
deployment.apps/my-go-app image updated

执行滚动更新后通过命令行查看ReplicaSet的状态会发现Deployment用新版本的ReplicaSet对象替换旧版本对象的过程。

➜  kubectl get replicaset
NAME                   DESIRED   CURRENT   READY   AGE
my-go-app-6749dbc697   3         3         2       19s
my-go-app-864496b67b   1         1         1       72m
➜  kubectl get replicaset
NAME                   DESIRED   CURRENT   READY   AGE
my-go-app-6749dbc697   3         3         3       24s
my-go-app-864496b67b   0         0         0       72m

通过这个Deployment的Events可以查看到这次滚动更新的详细过程:

➜  kubectl describe deployment my-go-app
Name:                   my-go-app
Namespace:              default
CreationTimestamp:      Sat, 29 Aug 2020 00:31:56 +0800Events:
.....Normal  ScalingReplicaSet  37h                deployment-controller  Scaled up replica set my-go-app-6749dbc697 to 1Normal  ScalingReplicaSet  37h                deployment-controller  Scaled down replica set my-go-app-864496b67b to 2Normal  ScalingReplicaSet  37h                deployment-controller  Scaled up replica set my-go-app-6749dbc697 to 2Normal  ScalingReplicaSet  37h (x2 over 37h)  deployment-controller  Scaled down replica set my-go-app-864496b67b to 1Normal  ScalingReplicaSet  37h                deployment-controller  Scaled up replica set my-go-app-6749dbc697 to 3Normal  ScalingReplicaSet  37h                deployment-controller  Scaled down replica set my-go-app-864496b67b to 0

当你修改了Deployment里的Pod定义之后,Deployment 会使用这个修改后的 Pod 模板,创建一个新的 ReplicaSet(hash=6749dbc697),这个新的ReplicaSet 的初始Pod副本数是:0。然后Deployment 开始将这个新的ReplicaSet所控制的Pod 副本数从 0 个变成 1 个,即:"水平扩展"出一个副本。紧接着Deployment又将旧的 ReplicaSet(hash=864496b67b)所控制的旧 Pod 副本数减少一个,即:"水平收缩"成两个副本。如此交替进行就完成了这一组Pod 的版本升级过程。像这样,将一个集群中正在运行的多个 Pod 版本,交替地逐一升级的过程,就是 "滚动更新"

用示意图描述这个过程的话就像下图这样

Deployment滚动更新的过程

为了保证服务的连续性,Deployment 还会确保,在任何时间窗口内,只有指定比例的Pod 处于离线状态。同时,它也会确保,在任何时间窗口内,只有指定比例的新 Pod 被创建出来。这两个比例的值都是可以配置的,默认都是期望状态里spec.relicas值的 25%。所以,在上面这个 Deployment 的例子中,它有 3 个 Pod 副本,那么控制器在“滚动更新”的过程中永远都会确保至少有 2 个Pod 处于可用状态,至多只有 4 个 Pod 同时存在于集群中。这个策略可以通过Deployment 对象的一个字段,RollingUpdateStrategy来设置:


apiVersion: apps/v1
kind: Deployment
...
spec:
...strategy:type: RollingUpdaterollingUpdate:maxSurge: 1maxUnavailable: 1

回滚Deployment版本

上面执行变更命令的时候都使用了--record 参数,这个参数能让Kubernetes在这个Deployment的变更记录里记录上产生变更当时执行的命令。

执行kubectl rollout history deployment my-go-app 就能看到这个Deployment的更新记录:

➜  kubectl rollout history deployment my-go-app
deployment.apps/my-go-app
REVISION  CHANGE-CAUSE
1         kubectl scale deployment my-go-app --replicas=3 --record=true
2         kubectl set image deployment my-go-app go-app-container=kevinyan001/kube-go-app:v0.1 --record=true

假如刚才那个滚动更新的Go项目镜像有问题,我们想回退到以前的版本。借助--record参数帮我们记录的执行命令和更新记录里的修订号就可以找到想要回滚的版本修订号。

一旦确定了修订号后我们kubectl rollout undo命令就能完成Deployment对象的版本回滚。

kubectl rollout undo  deployment my-go-app --to-revision=1
deployment.apps "my-go-app"

执行完后我们会发现一个非常有意思的事情,以前那个版本的ReplicaSet(hash=864496b67b)的Pod的数又变回了3,新ReplicaSet(hash=6749dbc697)的Pod数变成了0。

➜ kubectl get rs
NAME                   DESIRED   CURRENT   READY   AGE
my-go-app-6749dbc697   0         0         0       3m33s
my-go-app-864496b67b   3         3         3       4m30s

证明Deployment在上次滚动更新后并不会把旧版本的ReplicaSet删掉,而是留着回滚的时候用,所以ReplicaSet相当于一个基础设施层面的应用的版本管理。

回滚后在看变更记录,发现已经没有修订号1的内容了,而是多了修订号为3的内容,这个版本的变更内容其实就是回滚前修订号1里的变更内容。

➜ kubectl rollout history deployment my-go-app
deployment.apps/my-go-app
REVISION  CHANGE-CAUSE
2         kubectl set image deployment my-go-app go-app-container=kevinyan001/kube-go-app:v0.1 --record=true
3         kubectl scale deployment my-go-app --replicas=3 --record=true

控制ReplicaSet的版本数量

你可能已经想到了一个问题:我们对Deployment 进行的每一次更新操作,都会生成一个新的ReplicaSet 对象,是不是有些多余,甚至浪费资源?所以,Kubernetes 项目还提供了一个指令,使得我们对 Deployment 的多次更新操作,最后只生成一个ReplicaSet对象。具体的做法是,在更新Deployment前,你要先执行一条 kubectl rollout pause 指令。它的用法如下所示:

➜ kubectl rollout pause deployment my-go-app
deployment.apps/my-go-app paused

这个命令的作用,是让这个Deployment进入了一个"暂停"状态。由于此时Deployment正处于“暂停”状态,所以我们对Deployment的所有修改,都不会触发新的“滚动更新”,也不会创建新的ReplicaSet。而等到我们对 Deployment 修改操作都完成之后,只需要再执行一条 kubectl rollout resume 指令,就可以把这个 它恢复回来,如下所示:

➜ kubectl rollout resume deployment my-go-app
deployment.apps/my-go-app resumed

随着应用版本的不断增加,Kubernetes会为同一个Deployment保存很多不同的ReplicaSetDeployment 对象有一个字段,叫作 spec.revisionHistoryLimit,就是 KubernetesDeployment 保留的"历史版本"个数。如果把它设置为 0,就再也不能做回滚操作了。

总结

Kubernetes 项目对 Deployment 的设计,代替我们完成了对应用的抽象,让我们可以用一个Deployment 对象来描述应用,使用 kubectl rollout 命令控制应用的版本。

Deployment 还会保证服务的连续性,确保滚动更新时在任何时间窗口内,只有指定比例的Pod 处于离线状态,同时也只有指定比例的新 Pod 被创建出来,这样就保证了服务能平滑更新。用Go写的HTTP服务举例子来说,我们不需要再在代码里自己实现HTTP Server平滑重启的功能,因为这些功能都由Deployment在应用抽象层面替我们实现了。

希望大家都能跟着今天文章里的演示,掌握Deployment的提供的各种功能的用法。文章里我用的镜像已经上传到DockerHub上了,创建Deployment对象时会自动去DockerHub上拉取。如果网络受限,拉取不了镜像,可以在文章下面留言或者公众号私信我获取项目的源码和构建镜像用的Dockerfile

MySQL读锁的区别和应用场景分析

Go内存管理之代码的逃逸分析

如何避免用动态语言的思维写Go代码

看到这里了就点个在看支持下吧,你的「在看」是我创作的动力。

关注公众号网管叨bi叨,「每周为您分享原创技术文章」!

“在看转发”是最大的支持

K8s上的Go服务怎么扩容、发版更新、回滚、平滑重启?教你用Deployment全搞定!相关推荐

  1. 在K8S上的Web服务该怎么做域名解析呢?

    在K8S上的Web服务该怎么做域名解析呢? 我们这个系列的文章一直都在学习和掌握K8S各种组成部分在集群里的角色.作用和使用场景,那么针对今天这个主题任务「给K8S上的Web服务做域名解析」你觉得应该 ...

  2. Istio最佳实践:在K8s上通过Istio服务网格进行灰度发布

    Istio是什么? Istio是Google继Kubernetes之后的又一开源力作,主要参与的公司包括Google,IBM,Lyft等公司.它提供了完整的非侵入式的微服务治理解决方案,包含微服务的管 ...

  3. 一部手机全搞定,抖音发工资了,一共2千多,方法人人可以用

    发工资了抖音给我发了2000多,昨天一共做了8个作品,花了两个多小时赚了2000多. 而且是不需要真人出镜的,全程复制粘贴,一部手机就能搞定,很多人问我是怎么做到的,今天我就把这个方法分享给大家. 如 ...

  4. python搭建邮件服务器_手把手教你使用Python轻松搞定发邮件

    前言 现在生活节奏加快,人们之间交流方式也有了天差地别,为了更加便捷的交流沟通,电子邮件产生了,众所周知,电子邮件其实就是客户端和服务器端发送接受数据一样,他有一个发信和一个收信的功能,电子邮件的通信 ...

  5. 发哥莫慌!这56亿让区块链帮你搞定

    近日,爆出周润发打算把毕生所赚全部捐出来,范冰冰8亿多的罚款对比周润发56亿港币的裸捐,也许人格本身没有魅力,但是不妨碍它散发光芒. 虽然小编没有办法想象56亿港币需要多少个麻袋来装,但是这些钱如果分 ...

  6. 手把手教你使用Python轻松搞定发邮件

    击上方"Python爬虫与数据挖掘",进行关注 回复"书籍"即可获赠Python从入门到进阶共10本电子书 今 日 鸡 汤 自言本是京城女,家在虾蟆陵下住. 前 ...

  7. spring cloud gateway 网关_微服务网关Spring Cloud Gateway全搞定

    一.微服务网关Spring Cloud Gateway 1.1 导引 文中内容包含:微服务网关限流10万QPS.跨域.过滤器.令牌桶算法. 在构建微服务系统中,必不可少的技术就是网关了,从早期的Zuu ...

  8. “线上+包作图+线下”培训,精通AI、PS等软件,高分论文插图轻松搞定!

    学术论文是科研成果最重要的呈现和传播方式.对于科研人员来说,可能90%的时间在做实验,花在写论文上的时间不到10%.但对审稿人来说,他们无法看到论文作者在实验室的日夜付出,只能通过送审论文的质量来评估 ...

  9. mfc让图片与按钮一起_微信朋友圈发图片还能添加语音,简单两步就能搞定!今天学到了...

    大家好,我是分享科技小达人~ 今天跟大家探讨的问题是:[微信朋友圈发图片添加语音的方法]. 日常生活中,我们都喜欢发朋友圈,今天就来教你如何在微信朋友圈,发送带语音的图片,方法非常简单,一起来学习一下 ...

最新文章

  1. 架构设计开发方式汇总
  2. git clone 代码下载速度慢的解决方法
  3. java.sql.SQLSyntaxErrorException: Unknown database ‘spring‘
  4. java lambda 调用函数_Java lambda函数将如何编译?
  5. 关于windows2008重新启动需要重新设置分辨率的问题
  6. shell 脚本里面的数组和遍历
  7. java进销存管理系统设计_java进销存管理系统的设计与实现-springboot源码
  8. Windows 恢复使用老版的图片查看器
  9. cmd如何返回上一级目录,如何进入其他文件目录
  10. python转义是什么意思_什么是python转义字符?看看人士如何理解它.
  11. 元素偏移offset的常用属性
  12. gazebo publish pose
  13. excel删除重复的行_如何在Excel中删除重复的行
  14. 联想天逸 510S 2022怎么样
  15. html把保留图片改为提交按钮,如何制作图片按钮,并为图片按钮添加提交表单和重置表单功能...
  16. 【哈希】关于哈希表和哈希函数的理解与应用
  17. python项目实战(二):选课系统(采用面向对象思想开发)
  18. 雄牛PVC地板革新胶地板行业成环保绿色新选择
  19. 纯属好玩:我做的“截图续弈”
  20. 使用redis就可以获得root权限,怎么做的?

热门文章

  1. ConcurrentHashMap源码跟踪记录
  2. NTT Docomo研究主管Kazuaki OBANA:NTT DOCOMO NFV案例解析
  3. 修改Windows 2003/2008/2012远程桌面服务端口号
  4. 20150318知识小结
  5. SQL Server Audit(审核)配置方法--数据库级别的审核
  6. 关于垂直切分Vertical Sharding的粒度
  7. 精通Windows Sockets 网络开发-基于Visual C++实现
  8. Android系统为例解读智能手机如何防盗
  9. PostgreSQL 10.1 手册_部分 II. SQL 语言_第 12 章 全文搜索_12.4. 额外特性
  10. ActiveMQ Topic发布订阅消息