k8s之Deployment 声明式地升级应用(五)
Deployment 声明式地升级应用
现在你已经知道如何将应用程序组件打包进容器,将他们分组到pod中,并为它们提供临时或者持久存储,将密钥或配置文件注入,并可以使pod之间互相通信。这就是微服务化:如何将一个大规模系统拆分成各个独立运行的组件。之后,你将会需要升级你的应用程序。如何升级再kubernetes集群中运行的程序,以及kubernetes如何帮助你实现真正的零停机升级过程。升级操作可以通过使用replicationController 或者 replicaSet实现,但Kubernetes提供了另一种基于ReplicaSet的资源Deployment,并支持声明式地更新应用程序。
看一个简单的例子:
现有一个应用 ,版本为V1 ,运行在Kubernetes的pod中,现在应用的镜像有更新,标记为v2,那么如何将新版本运行的pod替换掉V1版本的pod.
有以下两种方法更新pod:
1. 直接删除所有现有的pod,然后创建新pod
2. 新创建一组pod,等待运行后删除旧pod. 可以一次性创建,一次性删除,也可以一部分一部分操作。
这两种各有优缺点:第一种方法将会导致应用程序在一定的时间内不可用。第二种方法会导致新旧版本同时在线,如果对统一个数据的处理方式不一样,将会给系统带来数据损坏。
暂且不管优缺点,先来看看如何实现。
我们用replicationController来托管pod v1,可以直接通过将pod模版修改为v2,然后再删除旧pod,这时候rc就会按照新模版创建pod.
如果你可以接受短暂的服务不可用,那这是最简单更新pod的方法。
接下来我们用第二种方法。首先要保持两个版本同时在线,所以要双倍的硬件资源。
前提我们是用service来暴露pod的。保持rc v1不变,然后创建 rc v2 ,rc v2的pod模版选择新镜像标签V2. 创建rc v2 ,rc v2将运行pod v2 。等待pod v2运行正常后,我们使用 kubectl set selector 命令来修改service的pod选择器。
这种一次性从v1切到v2的方法即为蓝绿部署,主要问题就是硬件资源要两倍。如果我们不是一次切换,而是一点点切,资源将不需要两倍。比如你创建rc v2,成功后,缩容 rc v1 ,更改service的pod选择器。然后再扩容rc v2,在缩容 v1 这就是滚动更新。那么这个就要求你的应用允许存在两个不同版本,具体要根据实际需求来操作了。
以上第二种方法的两种情况,我们大概知道是怎么回事了。但我们在实际操作中,要创建两个rc,并且要修改service的pod selector. 这要是用滚动更新,pod副本越多,我们手动操作的次数就越多,手动操作越多越容易出现问题。那么问题来了,难道kubernetes没有提供自动完成这些操作的方法吗?答案是提供了。k8s中使用kubectl rolling-update kubia-v1 kubia-v2 --image=luksa/kubia:v2
我们来通过一个例子了解下:
用rc创建一个应用v1 ,并使用service的 loadbalancer将服务对外暴露。
apiVersion: v1
kind: ReplicationController
metadata:
name: kubia-v1
spec:
replicas: 3
template:
metadata:
name: kubia
labels:
app: kubia
spec:
containers:
- image: luksa/kubia:v1
name: nodejs
---
apiVersion: v1
kind: Service
metadata:
name: kubia
spec:
type: LoadBalancer
selector:
app: kubia
ports:
- port: 80
targetPort: 8080
查看loaderbalancer的IP
#kubectl get svc kubia
访问测试
# while true; do curl http://IP; done
this is v1 running in pod 主机名
接下来用v2版本升级,this is v2 running in pod 主机名
kubectl rolling-update kubia-v1 kubia-v2 --image=luksa/kubia:v2
使用 kubia v2版本应用替换运行着kubia-v1 的replicationController,将新的复制控制器命名为kubia-v2,并使用luksa/kubia:v2作为容器镜像。
kubectl 通过复制kubia-v1 的replicationController并在其pod模版中改变镜像版本。如果仔细观察控制器的标签选择器,会阿贤它也被做了修改。它不仅包含一个简单的app=kubia标签,而且还包含一个额外的deployment标签,为了由这个ReplicationController管理,pod必须具备这个标签。这也是为了避免使用新的和旧的RC来管理同一组Pod.但是即使新创建的pod添加了deployment标签,pod中还是有app=kubia标签,所以不仅新的RC要加deployment标签,旧的RC同样也要加上deployment标签,标签的值为 一个镜像hash(先这样理解)。要保证旧的RC添加deployment标签后依然可以管理之前创建的pod,因此也要修改旧pod,进行添加标签,而实际上正是在修改旧RC之前,kubectl修改了旧pod的标签:
kubectl get po --show-labels 进行查看标签
设置完成后,就开始执行更新操作了,过程就是我们上面描述的滚动更新过程。
为什么 kubectl rolling-update已经过时
我们可以在使用rolling-update命令的时候添加 --v 6 来提供日志级别,使得所有kubectl 发起的到API服务器的请求都会被输出。
你会看到一个put请求:
/api/v1/namespace/default/replicationcontrollers/kubia-v1
它是表示kubia-v1 ReplicationController资源的RESTful URL.这些请求减少了RC的副本数,这表明伸缩的请求是由kubectl 客户端执行的,而不是kubernetes master执行的。那么当kubectl执行升级时失去了网络连接,升级过程就会中断。对于中断后的结果处理起来将很麻烦。所以我们想尽量把这个过程让master负责。这就引出了DeployMent资源。
Deployment是一个高阶资源,replicationController和replicaSet都被认为是底层的概念。当创建一个Deployment时,ReplicaSet资源就会被创建,实际的pod是由Deployment的Replicaset创建和管理的,而不是由Deployment直接创建和管理的。
接下来我们创建一个简单的deployment,将上面的replicationController稍作修改:
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: kubia
spec:
replicas: 3
template:
metadata:
name: kubia
labels:
app: kubia
spec:
containers:
- image: luksa/kubia:v1
name: nodejs
因为之前RC只维护和管理了一个特定的版本的pod,并需要命名为kubia-v1,而一个deployment资源高于版本本身。可以同时管理多个版本的pod,所以在命名时不需要指定应用的版本号。
kubectl create -f kubia-deployment-v1.yaml --record
使用 --record选项会记录历史版本号,在之后操作中非常有用。
kubectl get deployment kubia
kubectl describe deployment kubia
还有一个命令,专门用于查看部署状态:
kubectl rollout status deployment kubia
查看pod,会发现使用deployment资源部署的pod名称有一定的规律,
deployment的名字 + replicaset pod模版的hash + 随机数,名称我曾怀疑是template模版的名字 + 模版的hash+随机数。经过验证发现不是,甚至可以不给模版定义name.
如:
kubia-15098375-otnsd
kubia-15098375-djc6s
而rc创建出来的名称是 : rc名称 + 随机数
kubia-v1-kgysg
可以通过查看replicaSet来确认pod模版hash
kubectl get replicasets
kubia-15098375 ...
deployment正是根据pod模版的hash值,对给定版本的pod模版创建相同的(或使用已有的)ReplicaSet.
Deployment的高明之处
有不同的更新策略
Recreate 策略在删除旧的Pod之后才开始创建新的Pod。如果不允许多个版本共存,使用这个,但会有短暂的不可用。
RollingUpdate 策略会渐进地删除旧的pod,于此同时创建新的pod.这是deployment默认使用的策略。如果支持多个版本共存,推荐使用这个。
我们来演示下deployment滚动升级的过程。
在演示之前我们先减慢滚动升级的速度,以方便我们观察
kubectl path deployment kubia -p '{"spec": {"minReadySeconds": 10} }'
使用path对于修改单个或少量资源属性非常有用,不需要在通过编辑器编辑,使用minReadySeconds 配置正常检查后多少时间才属于正常。
注:使用patch命令更改Deployment的自有属性,并不会导致pod的任何更新,因为pod模版并没有被修改。更改其他Deployment的属性,比如所需要的副本数或部署策略,也不会触发滚动升级,现有运行中的pod也不会受影响。
触发滚动升级
kubectl set image deployment kubia nodejs=luksa/kubia:v2
执行完成后pod模版的镜像会被更改为luksa/kubia:v2
deployment的优点
使用控制器接管整个升级过程,不用担心网络中断。
仅仅更改pod模版即可。
注:如果Deployment中的pod模版引用了一个ConfigMap(或secret),那么更改ConfigMap字眼本身将不会触发升级操作。如果真的需要修改应用程序的配置并想触发更新的话,可以通过创建一个新的ConfigMap并修改pod模版引用新的ConfigMap.
Deployment背后完成的升级过程和kubectl rolling-update命令相似。
一个新的ReplicaSet会被创建然后慢慢扩容,同时之前版本的Replicaset会慢慢缩容至0
deployment的另外一个好处是可以回滚
kubectl rollout undo deployment kubia
deployment会被回滚到上一个版本
undo命令也可以在滚动升级过程中运行,并直接停止滚动升级。在升级过程中一创建的pod会被删除并被老版本的pod替代。
显示deployment的滚动升级历史
kubectl rollout history deployment kubia
reversion change-cause
2 kubectl set image deployment kubia nodejs=luksa/kubia:v2
3 kubectl set image deployment kubia nodejs=luksa/kubia:v3
还记得创建Deployment的时候--record 参数吗?如果不给这个参数,版本历史中的Change-cause这一栏会空。这也会使用户很难辨别每次的版本做了哪些修改。
回滚到一个特定的Deployment版本
kubectl rollout undo deployment kubia --to-reversion=1
这些历史版本的replicaset都用特定的版本号保存Deployment的完整的信息,所以不应该手动删除ReplicaSet。如果删除会丢失Deployment的历史版本记录而导致无法回滚。
ReplicaSet默认保留最近的两个版本,可以通过制定Deployment的reversionHistoryLimit属性来限制历史版本数量。apps/v1beta2 版本的Deployment默认值为10.
控制滚动更新的速率
maxSurge 最多超过期望副本数多少个pod,默认为25%,可以设置为整数
maxUanavailable 最多允许期望副本数内多少个pod为不可用状态。
看图比较容易理解
暂停滚动更新:
kubectl rollout pause deployment kubia
恢复滚动更新:
kubectl rollout resume deployment kubia
阻止出错版本的滚动升级
使用minReadySeconds属性指定至少要成功运行多久之后,才能将其视为可用。在pod可用之前,滚动升级的过程不会继续。是由maxSurge属性和maxUanavailable属性决定。
例子:
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: kubia
spec:
replicas:3
minReadySeconds: 10
strategy:
rollingUpdate:
maxSurge: 1
maxUnavilable: 0
type: RollingUpdate
template:
metadata:
name: kubia
labels:
app: kubia
spec:
containers:
- image: luksa/kubia:v3
name: nodejs
readinessProbe:
periodSeconds: 1 1秒探测一次
httpGet:
path: /
port: 8080
kubectl apply -f kubia-deployment-v3-with-readinesscheck.yaml
kubectl rollout status deployment kubia
就绪探针是如何阻止出错版本的滚动升级的
首先luksa/kubia:v3镜像应用前5个应用是正常反馈,后面会500状态返回。因此pod会从service的endpoint中移除。当执行curl时,pod已经被标记为未就绪。这就解释了为什么请求不会到新的pod上了。
使用kubectl rollout status deployment kubia查看状体,发现只显示一个新副本启动,之后滚动升级便没有进行下去,因为新的pod一直处于不可用状态。即使变为就绪状态后,也至少需要保持10秒,才是真正可用。在这之前滚动升级过程将不再创建任何新的pod,因为当前maxUnavailable属性设置为0,所以不会删除任何原始的pod。如果没有定义minReadySeconds,一旦有一次就绪探针调用成功,便会认为新的pod已经处于可用状态。因此最好适当的设置minReadySeconds.另外就绪探针默认间隔为1秒。
deployment资源介绍完结。
码农小明