Deployment 是 k8s 中一种重要的pod控制器,主要用于实现控制 Pod 的副本数量和水平伸缩以及版本更新。起到一个实现应用程序级别的资源模拟的作用。
如下图所示,Deployment 其实并不是直接控制 Pod 的,而是借助 ReplicaSet 来控制 Pod 的,为什么要这么做呢?先来了解一下 ReplicaSet 吧。
ReplicaSet
ReplicaSet 是 kubernetes 中的一种副本控制器资源,主要用于控制由其管理的Pod,使Pod副本的数量始终维持在预设的个数。
通过一个简单的 ReplicaSet 定义 yaml 文件来看看:
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: rs-nginx
namespace: wuvikr
labels:
app: nginx
spec:
replicas: 3 # 副本数
selector: # 标签选择器,必须和Pod模板中的Label一致
matchLabels:
app: nginx
template: # Pod模板
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.18.0-alpine
可以看出,一个 ReplicaSet 对象,其实就是由副本数目的定义(replicas
),标签选择器定义(selector
),以及一个能被标签选择器所匹配的Pod 模板(template
)所组成的。
运行上面的 yaml 文件:
[root@center-188 deployment]# kubectl apply -f rs.yaml
replicaset.apps/rs-nginx created
[root@center-188 deployment]# kubectl get rs -n wuvikr
NAME DESIRED CURRENT READY AGE
rs-nginx 3 3 3 77s
可以看到 ReplicaSet 有三个状态字段:
- DESIRED:期望的副本数(spec.replicas 的值)
- CURRENT:处于运行状态的副本数
- READY:可用的副本数
再查看一下当前的 Pod:
[root@center-188 deployment]# kubectl get pod -n wuvikr
NAME READY STATUS RESTARTS AGE
rs-nginx-6vvjq 1/1 Running 0 9s
rs-nginx-hd2jr 1/1 Running 0 9s
rs-nginx-x4n59 1/1 Running 0 9s
确实是有三个 Pod ,而且每个 Pod 的命名均为 ReplicaSet 名加上一个随机字符串以作区分。尝试删除一个Pod试试:
[root@center-188 deployment]# kubectl delete pod rs-nginx-6vvjq -n wuvikr
pod "rs-nginx-6vvjq" deleted
[root@center-188 deployment]# kubectl get pod -n wuvikr
NAME READY STATUS RESTARTS AGE
rs-nginx-hd2jr 1/1 Running 0 6m15s
rs-nginx-vsdlk 1/1 Running 0 5s
rs-nginx-x4n59 1/1 Running 0 6m15s
在删除 pod "rs-nginx-6vvjq" 后,马上查看一下会发现又自动启动了一个新的 pod "rs-nginx-vsdlk",从 AGE 上可以知道,这个 Pod 确实是刚刚启动的。这就是 ReplicaSet 最重要的作用,即始终保持 Pod 副本的实际数量与 spec 的replicas
字段所定义的期望数量一致。
而这一功能则是由 K8S 中的 kube-controller-manager 组件所实现的,这个组件并不只是单一的一个组件,而是一系列控制器的集合。这些控制器都具有一种名为控制循环(control loop)的机制,这个机制的功能简单来讲就是负责保持集群中对象的实际状态与期望状态保持一致。
实现上为一段死循环的代码,不断的来比较对象的实际状态与期望状态,如果状态一致则什么都不用做,如果不一致则执行一系列操作,将实际状态调整为期望状态。
期望状态,一般都是来自于用户定义的YAML文件,而实际状态则是由 Kubernetes 集群来负责,一般由 kubelet 收集汇报给 apiServer 的容器和节点状态信息,或者监控系统中的监控数据,以及一些控制器信息。
Deployment
将我们上面ReplicaSet的yaml文件稍作修改,即得到如下所示Deployment的定义yaml文件:
# 仅将`kind`字段修改为了Deployment。
apiVersion: apps/v1
kind: Deployment
metadata:
name: deploy-nginx
namespace: wuvikr
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.18.0-alpine
运行起来看一下:
[root@center-188 deployment]# kubectl apply -f deployment.yaml --record
deployment.apps/deploy-nginx created
[root@center-188 deployment]# kubectl get deploy -n wuvikr
NAME READY UP-TO-DATE AVAILABLE AGE
deploy-nginx 3/3 3 3 26s
查看一下Deployment,所显示的状态字段有:
- READY:可用的副本数
- UP-TO-DATE:达到期望状态的副本数。
- AVAILABLE:可供用户使用的副本数。
然后再来查看一下ReplicaSet 和 Pod:
[root@center-188 deployment]# kubectl get rs -n wuvikr
NAME DESIRED CURRENT READY AGE
deploy-nginx-d54b84765 3 3 3 7s
[root@center-188 deployment]# kubectl get pod --show-labels -n wuvikr
NAME READY STATUS RESTARTS AGE LABELS
deploy-nginx-d54b84765-j6g94 1/1 Running 0 97s app=nginx,pod-template-hash=d54b84765
deploy-nginx-d54b84765-mdgcd 1/1 Running 0 97s app=nginx,pod-template-hash=d54b84765
deploy-nginx-d54b84765-pmwhh 1/1 Running 0 97s app=nginx,pod-template-hash=d54b84765
ReplicaSet 的名称为[Deployment名称]-[随机字符串]
。 其中的随机字符串是使用 pod-template-hash
作为种子随机生成的。
Deployment 控制器会为他的每一个ReplicaSet生成一个pod-template-hash
标签,并添加到 ReplicaSet 中,包括selector选择器,Pod模板标签,以确保 Deployment 的子 ReplicaSets 不会重叠 。
查看一下生成的其中一个Pod:
[root@center-188 deployment]# kubectl get pod deploy-nginx-d54b84765-j6g94 -n wuvikr -o yaml
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: "2021-03-01T05:21:19Z"
generateName: deploy-nginx-d54b84765-
labels:
app: nginx
pod-template-hash: d54b84765
name: deploy-nginx-d54b84765-j6g94
namespace: wuvikr
ownerReferences:
- apiVersion: apps/v1
blockOwnerDeletion: true
controller: true
kind: ReplicaSet
name: deploy-nginx-d54b84765
uid: 8674028e-b70f-41e5-b571-13f96c666f33
resourceVersion: "47622525"
...
...
可以在metadata中看到,name
和labels
字段确实被注入了pod-template-hash
值。并且和单独定义一个Pod所不一样的是,这里多出了一个ownerReferences
字段,指明了该Pod属于哪个 ReplicaSet 。
Deployment的两大功能
通过上面简单的介绍,我们已经大概了解了Deployment, ReplicaSet,以及 Pod 的关系,他们实际上是一种“层层控制”的关系。
那么,Deployment的作用到底是什么,又为什么需要 ReplicaSet 来间接控制 Pod 呢,答案就是为了实现水平扩展 / 收缩和滚动更新功能。
水平扩展 / 收缩
“水平扩展 / 收缩”非常容易实现,Deployment Controller 只需要修改它所控制的 ReplicaSet 的 Pod 副本个数就可以了。
比如,把这个值从 3 改成 4,那么 Deployment 所对应的 ReplicaSet,就会根据修改后的值自动创建一个新的 Pod。这就是“水平扩展”了;“水平收缩”则反之。
想要执行这个操作的指令也非常简单,就是 kubectl scale命令,这里先查看下帮助:
[root@center-188 deployment]# kubectl scale -h
Set a new size for a Deployment, ReplicaSet, Replication Controller, or
StatefulSet.
Scale also allows users to specify one or more preconditions for the scale
action.
If --current-replicas or --resource-version is specified, it is validated
before the scale is attempted, and it is guaranteed that the precondition holds
true when the scale is sent to the server.
Examples:
# Scale a replicaset named 'foo' to 3.
kubectl scale --replicas=3 rs/foo
# Scale a resource identified by type and name specified in "foo.yaml" to 3.
kubectl scale --replicas=3 -f foo.yaml
# If the deployment named mysql's current size is 2, scale mysql to 3.
kubectl scale --current-replicas=2 --replicas=3 deployment/mysql
# Scale multiple replication controllers.
kubectl scale --replicas=5 rc/foo rc/bar rc/baz
# Scale statefulset named 'web' to 3.
kubectl scale --replicas=3 statefulset/web
Options:
--all=false: Select all resources in the namespace of the specified
resource types
--allow-missing-template-keys=true: If true, ignore any errors in
templates when a field or map key is missing in the template. Only applies to
golang and jsonpath output formats.
--current-replicas=-1: Precondition for current size. Requires that the
current size of the resource match this value in order to scale.
-f, --filename=[]: Filename, directory, or URL to files identifying the
resource to set a new size
-k, --kustomize='': Process the kustomization directory. This flag can't be
used together with -f or -R.
-o, --output='': Output format. One of:
json|yaml|name|go-template|go-template-file|template|templatefile|jsonpath|jsonpath-file.
--record=false: Record current kubectl command in the resource annotation.
If set to false, do not record the command. If set to true, record the command.
If not set, default to updating the existing annotation value only if one
already exists.
-R, --recursive=false: Process the directory used in -f, --filename
recursively. Useful when you want to manage related manifests organized within
the same directory.
--replicas=0: The new desired number of replicas. Required.
--resource-version='': Precondition for resource version. Requires that
the current resource version match this value in order to scale.
-l, --selector='': Selector (label query) to filter on, supports '=', '==',
and '!='.(e.g. -l key1=value1,key2=value2)
--template='': Template string or path to template file to use when
-o=go-template, -o=go-template-file. The template format is golang templates
[http://golang.org/pkg/text/template/#pkg-overview].
--timeout=0s: The length of time to wait before giving up on a scale
operation, zero means don't wait. Any other values should contain a
corresponding time unit (e.g. 1s, 2m, 3h).
Usage:
kubectl scale [--resource-version=version] [--current-replicas=count]
--replicas=COUNT (-f FILENAME | TYPE NAME) [options]
Use "kubectl options" for a list of global command-line options (applies to all
commands).
接下来直接指定 deploy-nginx 的副本数为4:
[root@center-188 deployment]# kubectl scale deploy deploy-nginx -n wuvikr --replicas=4
deployment.apps/deploy-nginx scaled
# 可以看到Pod数量多了一个,并且是刚刚被创建出来的。
[root@center-188 deployment]# kubectl get pod -n wuvikr
NAME READY STATUS RESTARTS AGE
deploy-nginx-d54b84765-hbprn 1/1 Running 0 17s
deploy-nginx-d54b84765-j6g94 1/1 Running 0 31m
deploy-nginx-d54b84765-mdgcd 1/1 Running 0 31m
deploy-nginx-d54b84765-pmwhh 1/1 Running 0 31m
滚动更新
应用的发布和升级是运维最重要的核心职能之一,那么部署在K8S中的应用是怎么发布升级的呢?其实,你只要编辑修改下 Deployment 的 yaml 文件中镜像版本号,再 apply 一下,就更新成功了,但是具体的实现过程又是怎么样的呢?
在 K8S 中可以使用 kubectl rollout命令来查看 Deployment 对象的状态变化。
先来查看一下该命令的帮助信息:
[root@center-188 deployment]# kubectl rollout -h
Manage the rollout of a resource.
Valid resource types include:
* deployments
* daemonsets
* statefulsets
Examples:
# Rollback to the previous deployment
kubectl rollout undo deployment/abc
# Check the rollout status of a daemonset
kubectl rollout status daemonset/foo
Available Commands:
history View rollout history
pause Mark the provided resource as paused
restart Restart a resource
resume Resume a paused resource
status Show the status of the rollout
undo Undo a previous rollout
Usage:
kubectl rollout SUBCOMMAND [options]
Use "kubectl <command> --help" for more information about a given command.
Use "kubectl options" for a list of global command-line options (applies to all commands).
可以看到,kubectl rollout支持操作三种资源类型,分别是deployments,daemonsets 和 statefulsets。
子命令分别有:
- history:滚动发布历史版本信息,可以利用里面的版本信息做版本回退。
- pause:暂停滚动发布,多用于金丝雀发布。
- restart:重启资源下所有的Pod。
- resume:恢复被pause的资源,继续滚动更新发布流程。
- status:查看资源对象的状态信息。
- undo:版本回退。
可以跟上子命令,查看更详细的帮助信息:
[root@center-188 deployment]# kubectl rollout status -h
Show the status of the rollout.
By default 'rollout status' will watch the status of the latest rollout until
it's done. If you don't want to wait for the rollout to finish then you can use
--watch=false. Note that if a new rollout starts in-between, then 'rollout
status' will continue watching the latest revision. If you want to pin to a
specific revision and abort if it is rolled over by another revision, use
--revision=N where N is the revision you need to watch for.
Examples:
# Watch the rollout status of a deployment
kubectl rollout status deployment/nginx
Options:
-f, --filename=[]: Filename, directory, or URL to files identifying the
resource to get from a server.
-k, --kustomize='': Process the kustomization directory. This flag can't be
used together with -f or -R.
-R, --recursive=false: Process the directory used in -f, --filename
recursively. Useful when you want to manage related manifests organized within
the same directory.
--revision=0: Pin to a specific revision for showing its status. Defaults
to 0 (last revision).
--timeout=0s: The length of time to wait before ending watch, zero means
never. Any other values should contain a corresponding time unit (e.g. 1s, 2m,
3h).
-w, --watch=true: Watch the status of the rollout until it's done.
Usage:
kubectl rollout status (TYPE NAME | TYPE/NAME) [flags] [options]
Use "kubectl options" for a list of global command-line options (applies to all
commands).
这里来更新一下nginx的版本,查看下版本更新的过程:
# 仅仅将nginx image版本由1.18改为了1.19
[root@center-188 deployment]# cat deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: deploy-nginx
namespace: wuvikr
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.19-alpine
[root@center-188 deployment]# kubectl apply -f deployment.yaml
deployment.apps/deploy-nginx configured
可以看到已经成功 configured 了,这时候马上使用 kubectl rollout status命令,就可以查看到 Deployment 的整个滚动更新的过程了:
[root@center-188 deployment]# kubectl get pod -n wuvikr
NAME READY STATUS RESTARTS AGE
deploy-nginx-696649f6f9-nwvhz 1/1 Running 0 2m49s
deploy-nginx-696649f6f9-qgqsp 1/1 Running 0 2m49s
deploy-nginx-696649f6f9-qj85t 1/1 Running 0 2m49s
deploy-nginx-7c99c6c766-qq86r 0/1 ContainerCreating 0 4s
[root@center-188 deployment]# kubectl rollout status deployment/deploy-nginx -n wuvikr
Waiting for deployment "deploy-nginx" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "deploy-nginx" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "deploy-nginx" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "deploy-nginx" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "deploy-nginx" rollout to finish: 1 old replicas are pending termination...
deployment "deploy-nginx" successfully rolled out
上面重新查看 Pod 可以发现,明明定义的只有 3 个副本,但是这里新建多出来了一个 Pod ,这是为什么呢?从下面的更新过程中我们可以看出一点端倪,先是三分之二的 replicas 被更新,然后是1个旧的 replicas 被删除,最后 rolled out 成功。
使用 kubectl describe 命令查看一下 Deployment 的详细信息:
[root@center-188 deployment]# kubectl describe deploy deploy-nginx -n wuvikr
Name: deploy-nginx
Namespace: wuvikr
CreationTimestamp: Thu, 18 Mar 2021 09:51:39 +0800
Labels: app=nginx
Annotations: deployment.kubernetes.io/revision: 3
kubectl.kubernetes.io/last-applied-configuration:
{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{},"labels":{"app":"nginx"},"name":"deploy-nginx","namespace":"wuvik...
Selector: app=nginx
Replicas: 3 desired | 3 updated | 3 total | 3 available | 0 unavailable
StrategyType: RollingUpdate
MinReadySeconds: 0
RollingUpdateStrategy: 25% max unavailable, 25% max surge
Pod Template:
Labels: app=nginx
Containers:
nginx:
Image: nginx:1.19-alpine
...
...
可以发现,有两个我们并没有定义过的字段:StrategyType
和RollingUpdateStrategy
,这是K8S自动为我们添加进去的。StrategyType 就是用来定义版本更新的方式的,这里是 RollingUpdate,这是 K8S 中的默认的更新策略,即创建一个正常的 Pod ,同时删除一个旧 Pod ,然后再创建一个新 Pod ,再删除一个旧 Pod....直到所有的 Pod 更新完成。
当然,一次性创建多少个新Pod和删除多少旧Pod?先创建 Pod 还是先删除 Pod 呢?这些都是可以在 RollingUpdateStrategy 字段中自己定义的。而RollingUpdateStrategy 只有当 StrategyType 为 RollingUpdate 的时候才可以使用,这个字段中包含两个字段,max unavailable
和max surge
,默认都是25%。max unavailable的意思是最大不可用 Pod 数量,即当版本更新过程中,最多只有该数量值的 Pod 可以为不可用状态;max surge 的意思的最大多出的 Pod 数量,即一次性可以新创建的Pod数量。
除了设置百分比之外,也可以设置为具体数值,例如max surge: 3
。默认情况下,K8S是创建新 Pod 和删除旧 Pod 同时进行的,如果你的服务访问量很大,需要确保服务非常稳定,你可以将 max unavailable 设置为0,这样只有当新建的Pod也就处于就绪状态后,才会删除旧的 Pod ;或者是你的资源十分紧张,服务访问量也不大,可以将 max surge 设置为0,这样就变成了先删除,再新建的方式了。
需要注意的是:max unavailable和max surge不可以同时设置为0。
另外,当使用百分比的时候,max surge向上取整而max unavailable向下取整,小于1,则为1。
以上面的例子再说明一下:
replicas = 3
25% max unavailable, 25% max surge
则:
max surge = 3 + 3 * 25% = 4
max unavailable = 3 * 25% = 1
replicas - unavailable + x = surge
所以 x = 2
这下你知道刚刚更新过程中为什么是三个中的两个已经被更新,一个被删除了吧。
至于创建Deployment的时候怎么定义yaml中的这些内容,可以使用 kubectl explain Deployment 命令进行查看。文章最后有一个Deployment常用字段值供参考。
滚动更新还有一个好处在于,如果这次发布的版本有问题,新建的第一批Pod无法处于就绪状态,那么这次的发布就会被暂停在这,而剩下的Pod并不会受到影响。所以,在定义yaml文件的时候,一定要使用Health Check机制来检查应用的运行状态,不能只是Pod 处于Running状态就完事了。
这里我们故意写错镜像版本然后在发布一次:
# 将image故意修改为错误的镜像
image: nginx:1.28-alpine
[root@center-188 deployment]# kubectl get pod -n wuvikr
NAME READY STATUS RESTARTS AGE
deploy-nginx-696649f6f9-t8jsc 1/1 Running 0 18m
deploy-nginx-9c749fd58-44d8t 0/1 ImagePullBackOff 0 42s
deploy-nginx-9c749fd58-45r2w 0/1 ErrImagePull 0 42s
可以看到,当发现镜像错误后,滚动更新的过程就被自动停止了,剩下的一个Pod仍旧可以正常提供服务。
版本回退
发布出现了错误,肯定要进行版本的回退。通过之前的介绍,可以知道,Deployment 是通过控制 ReplicaSet 来控制 Pod 的,这下你应该或许能猜到是为什么了吧。
刚刚我们一共更新了两次版本,我们来查看一下ReplicaSet:
[root@center-188 deployment]# kubectl get rs -n wuvikr
NAME DESIRED CURRENT READY AGE
deploy-nginx-6876bcfdd 0 0 0 24m
deploy-nginx-696649f6f9 3 1 1 66m
deploy-nginx-7446b5ff49 0 0 0 9m11s
可以看到一共有3个,正好对应上了我们发布的三个版本,所谓的版本回退或者说版本切换,在Deployment 中的实现其实就是它所控制的ReplicaSet的切换。
在上面介绍的 kubectl rollout命令中有个 history 和 undo 子命令,就是用来实现版本回退和切换的,首先要使用 kubectl rollout history 命令来查看发布的版本:
[root@center-188 deployment]# kubectl rollout history deployment/deploy-nginx -n wuvikr
deployment.apps/deploy-nginx
REVISION CHANGE-CAUSE
1 <none>
2 <none>
3 <none>
返回值里有两个字段,REVISION
:版本号;CHANGE-CAUSE
:在创建 Deployment 的时候可以加上 --record 参数,这样后续每次版本变更都会记录下造成版本更新的命令,可以用来识别版本发布的一些信息:
[root@center-188 deployment]# vim deployment.yaml
# 使用--record参数
[root@center-188 deployment]# kubectl apply -f deployment.yaml --record
deployment.apps/deploy-nginx configured
# 再次查看 history
[root@center-188 deployment]# kubectl rollout history deployment/deploy-nginx -n wuvikr
deployment.apps/deploy-nginx
REVISION CHANGE-CAUSE
1 <none>
2 <none>
3 <none>
4 kubectl apply --filename=deployment.yaml --record=true
然后就可以使用 undo 子命令回退到指定的版本了:
[root@center-188 deployment]# kubectl rollout undo deployment/deploy-nginx -n wuvikr --to-revision=2
deployment.apps/deploy-nginx rolled back
[root@center-188 deployment]# kubectl get rs -n wuvikr
NAME DESIRED CURRENT READY AGE
deploy-nginx-6876bcfdd 3 3 3 30m
deploy-nginx-696649f6f9 0 0 0 72m
deploy-nginx-7446b5ff49 0 0 0 15m17s
可以看到已经成功回退到 2 版本去了。
需要注意的是:如果没有使用 --to-revision 指定版本号的话,默认是回到上一个版本,而即使是版本回退,在 history 中也会被记录成一个新的版本:
[root@center-188 deployment]# kubectl rollout history deployment/deploy-nginx -n wuvikr
deployment.apps/deploy-nginx
REVISION CHANGE-CAUSE
1 <none>
3 <none>
4 kubectl apply --filename=deployment.yaml --record=
5 <none>
发现了么?2 版本已经没有了,对应的是现在的 5 版本了。
另外,Deployment 的默认保留的 History 数量,即 ReplicaSets 的数量默认是10,这个也是允许我们自己定义的。
Deployment常用字段解释
apiVersion: apps/v1 # API群组和版本
kind: Deployment # 资源类型
metadata:
name <string> # 资源名称,名称空间中要唯一
namespace <string> # 名称空间
spec:
minReadySeconds <integer> # Pod就绪后多少秒后没有容器crash才视为“就绪”
replicas <integer> # Pod副本数,默认为1
selector <object> # 标签选择器,必须和template字段中Pod的标签一致
template <object> # 定义Pod模板
revisionHistoryLimit <integer> # 滚动更新历史记录数量,默认为10
strategy <Object> # 滚动更新策略
type <string> # 滚动更新类型,默认为RollingUpdate
rollingUpdate <Object> # 滚动更新参数,只能用于RollingUpdate类型
maxSurge <string> # 更新期间可以比replicas定义的数量多出的数量或比例
maxUnavailable <string> # 更新期间可以比replicas定义的数少的数量或比例
progressDeadlineSeconds <integer> # 滚动更新故障超时时长,默认为600秒
paused <boolean> # 是否暂停部署