我们之前通过资源配置清单,自己创建了一个Pod资源,如果此时这个Pod被删除了,K8S是不会帮我们重新创建的。通过这种方式创建的Pod称之为自主式Pod资源,如果线上所有的服务都需要我们来手动管理Pod,那将是一个巨大的运维开销,那K8S就失去了其存在的意义,所以,K8S为我们提供了Pod控制器资源,专门用于对Pod的管理。Pod控制器可以帮我们自动保持Pod状态处于我们期望的状态,例如Pod的副本数,Pod中使用的容器镜像版本,Pod的更新策略等等。
当我们在创建Pod时,会给Pod打上标签,而我们的Pod控制器正是通过标签选择器来选中指定标签的Pod,从而实现对Pod的管理。
一、Pod控制器类型
常见的Pod控制器有如下类型:
ReplicationController:简称RC,旧版本K8S中使用的Pod控制器,ReplicaSet的前身,仅支持等式的标签选择器,官方不建议使用。
ReplicaSet:简称RS,新版本中用于顶替ReplicationController的Pod控制器,其支持集合式的标签选择器。ReplicaSet可以单独使用,但是单独使用时其并不支持rolling-update,官方建议通过Deployment来自动管理ReplicaSet。其由三部分组成:用户期望的副本数,标签选择器,Pod资源模板。用户期望的副本数表示启动的Pod的数量,标签选择器用来筛选那些被控制的Pod,Pod资源模板则定义了Pod的名称,容器,镜像等等,其本质上就是Pod资源的metadata和spec字段。
Deployment:简称deploy,最常用的Pod控制器,需要注意的是,其并不直接控制Pod,而是直接控制ReplicaSet,然后通过ReplicaSet来控制Pod,Deployment支持rolling-update,版本记录,回滚,暂停更新等操作。其每次更新时都会自动创建一个ReplicaSet,每一个ReplicaSet就是表示一个版本,这就表明在同一个Deployment下会有多个ReplicaSet,但是同一时间只有一个ReplicaSet在工作。
DaemonSet:简称DS,DaemonSet控制器会在K8S集群中所有Node节点上都启动且仅启动一个Pod,一般这类Pod都是用来运行集群中的公共服务,例如监控、日志收集等等。当有新的Node节点加入集群后,DaemonSet会立即在新的节点上创建Pod。
Job:负责一次性任务的处理,其控制下的Pod仅执行一次并成功退出。
CronJob:定时任务,负责在某个时间点或者以一定时间规律运行的任务。
StatefulSet:用于管理有状态应用的Pod控制器。
1、ReplicaSet
了解了Pod控制器的类型后,我们通过资源配置清单来创建一个ReplicaSet类型的Pod控制器。
[root@k8s7-200 ReplicaSet]# pwd
/data/k8s-yaml/ReplicaSet
[root@k8s7-200 ReplicaSet]# cat myapp-rs.yaml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: myapp-rs
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: myapp
release: test-v1
template:
metadata:
name: myapp
labels:
app: myapp
release: test-v1
spec:
containers:
- name: myapp-container
image: harbor.od.com/public/myapp:v1
ReplicaSet.spec
字段有如下几个二级字段:
replicas
:副本数,即用户期望的Pod数量
selector
:标签选择器,用来指定筛选Pod的标签,其下级字段有matchLabels用于匹配键值,matchExpressions用于匹配集合类型标签
template
:定义Pod资源模板,其下就是Pod资源的metada和spec字段,此处需要注意,Pod资源的标签一定要能被selector中定义的标签选择器所匹配到。此字段下的metadata和spec字段可以参见《K8S系列 -- K8S资源配置清单》,此处不再赘述
定义好资源配置清单后,我们就可以来创建Pod控制器了
[root@k8s7-22 ~]# kubectl create -f http://k8s-yaml.od.com/ReplicaSet/myapp-rs.yaml # 创建控制器
replicaset.apps/myapp-rs created
[root@k8s7-22 ~]# kubectl get rs # 查看rs资源
NAME DESIRED CURRENT READY AGE
myapp-rs 2 2 2 33m
[root@k8s7-22 ~]# kubectl get pods -l app=myapp,release=test-v1 --show-labels # 查看pod
NAME READY STATUS RESTARTS AGE LABELS
myapp-rs-v26gm 1/1 Running 0 34m app=myapp,release=test-v1
myapp-rs-xvp5z 1/1 Running 0 34m app=myapp,release=test-v1
我们将资源配置清单中的副本数量改成5,然后就可以对当前pod进行扩容了:
[root@k8s7-22 ~]# kubectl apply -f http://k8s-yaml.od.com/ReplicaSet/myapp-rs.yaml # 应用新的配置清单
replicaset.apps/myapp-rs configured
[root@k8s7-22 ~]# kubectl get pods -l app=myapp,release=test-v1 --show-labels
NAME READY STATUS RESTARTS AGE LABELS
myapp-rs-5vk9s 1/1 Running 0 9s app=myapp,release=test-v1
myapp-rs-6sbvz 1/1 Running 0 9s app=myapp,release=test-v1
myapp-rs-fqwdc 1/1 Running 0 9s app=myapp,release=test-v1
myapp-rs-v26gm 1/1 Running 0 73m app=myapp,release=test-v1
myapp-rs-xvp5z 1/1 Running 0 73m app=myapp,release=test-v1
[root@k8s7-22 ~]# kubectl edit rs myapp-rs # 动态修改资源配置
此时我们可以看到,我们集群中的Pod数量变成了5个,同理,我们也可以将集群Pod数量改回2,以此完成对集群的扩缩容。
除了对集群Pod的扩缩容外,我们还可以发布更新。比如我们要将我们的Pod镜像版本由V1升级为V2,我们可以直接编辑rs的配置清单:
[root@k8s7-22 ~]# kubectl edit rs myapp-rs
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: extensions/v1beta1
kind: ReplicaSet
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"apps/v1","kind":"ReplicaSet","metadata":{"annotations":{},"name":"myapp-rs","namespace":"default"},"spec":{"replicas":5,"selector":{"matchLabels":{"app":"myapp","release":"test-v1"}},"template":{"metadata":{"labels":{"app":"myapp","release":"test-v1"},"name":"myapp"},"spec":{"containers":[{"image":"harbor.od.com/public/myapp:v1","name":"myapp-container"}]}}}}
creationTimestamp: "2020-01-15T08:33:05Z"
generation: 5
labels:
app: myapp
release: test-v1
name: myapp-rs
namespace: default
resourceVersion: "3349727"
selfLink: /apis/extensions/v1beta1/namespaces/default/replicasets/myapp-rs
uid: f161c8ee-7506-4461-a7a1-579f3a661e78
spec:
replicas: 3
selector:
matchLabels:
app: myapp
release: test-v1
template:
metadata:
creationTimestamp: null
labels:
app: myapp
release: test-v1
name: myapp
spec:
containers:
- image: harbor.od.com/public/myapp:v2 # 修改镜像版本为V2
imagePullPolicy: IfNotPresent
此时,我们再来看下rs的详细信息以及pod内的版本:
[root@k8s7-22 ~]# kubectl get rs -o wide
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
myapp-rs 3 3 3 19h myapp-container harbor.od.com/public/myapp:v2 app=myapp,release=test-v1
[root@k8s7-22 ~]# kubectl get pods -o wide -l app=myapp,release=test-v1
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
myapp-rs-5vk9s 1/1 Running 0 18h 172.17.21.5 k8s7-21.host.com <none> <none>
myapp-rs-v26gm 1/1 Running 0 19h 172.17.22.4 k8s7-22.host.com <none> <none>
myapp-rs-xvp5z 1/1 Running 0 19h 172.17.21.3 k8s7-21.host.com <none> <none>
[root@k8s7-22 ~]# curl 172.17.21.5
abc
[root@k8s7-22 ~]# curl 172.17.22.4
abc
[root@k8s7-22 ~]# curl 172.17.21.3
abc
此时我们可以看到,我们的rs中镜像的版本已经变成了v2,但是我们的pod却没有被更新,依然是v1版本的内容。这是因为,我们更改了镜像的版本后,我们的ReplicaSet并不会去更新我们的Pod,而是当我们的Pod退出后,才会使用新版的镜像来重新创建,所以,我们来删掉其中一个Pod,让ReplicaSet为我们重建一个Pod,此时我们再来看新Pod中的应用版本:
[root@k8s7-22 ~]# kubectl delete pods myapp-rs-5vk9s
pod "myapp-rs-5vk9s" deleted
[root@k8s7-22 ~]# kubectl get pods -o wide -l app=myapp,release=test-v1
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
myapp-rs-8gmt4 1/1 Running 0 12s 172.17.21.4 k8s7-21.host.com <none> <none>
myapp-rs-v26gm 1/1 Running 0 19h 172.17.22.4 k8s7-22.host.com <none> <none>
myapp-rs-xvp5z 1/1 Running 0 19h 172.17.21.3 k8s7-21.host.com <none> <none>
[root@k8s7-22 ~]# curl 172.17.21.4
myapp | v2
[root@k8s7-22 ~]# curl 172.17.22.4
abc
此时我们就可以看到,当我们删除一个Pod后,我的ReplicaSet为我们自动重建了一个新的Pod,而此时新的Pod中就变成了V2版本,此时集群中新旧版本的Pod都存在,如果我们依次删除旧的Pod,就能实现灰度发布的效果,如果我们只删除部分Pod,就能实现金丝雀发布的效果。但是这些操作都是我们手动进行的,虽然便于控制更新频率,但是如果此时如果新版本有问题,我们想要快速回滚的话,依然需要改回旧版本,然后再手动删掉新Pod,这样就不是很方便了。
2、Deployment
Deployment类型的Pod控制器,其实并不会直接控制Pod,而是直接控制ReplicaSet,然后再通过ReplicaSet来控制Pod,这样,不同的ReplicaSet就会有不同的版本,如果需要回滚时,直接启用旧的ReplicaSet即可,这样就可以实现快速回滚的操作。接下来我们就创建一个Deployment类型的Pod控制器,其资源配置清单如下:
[root@k8s7-200 Deployment]# cat myapp-dp.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-dp
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: myapp-dp
release: v1
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 10
maxUnavailable: 0
revisionHistoryLimit: 3
paused: True
template:
metadata:
name: myapp-dp
labels:
app: myapp-dp
release: v1
spec:
containers:
- name: myapp-dp-container
image: harbor.od.com/public/myapp:v1
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
livenessProbe:
httpGet:
port: http
path: "/index.html"
failureThreshold: 4
periodSeconds: 20
timeoutSeconds: 10
initialDelaySeconds: 10
readinessProbe:
httpGet:
port: http
path: "/index.html"
failureThreshold: 4
periodSeconds: 20
timeoutSeconds: 10
initialDelaySeconds: 10
我们可以看到,在Deployment的资源配置清单中,spec字段下除了有和ReplicaSet相同的replicas、selector、template字段外,还多了如下字段:
strategy:用于指定更新策略,其下级字段如下:
type: 更新的类型,支持 Recreate及RollingUpdate,默认值是RollingUpdate
rollingUpdate:滚动更新策略,当type值是RollingUpdate时此字段才生效,其有如下下级字段:
maxSurge: 最多可以多创建Pod数量,可以是数字,也可以是百分比。
maxUnavailable: 最多不可用的Pod数量,可以是数字,也可以是百分比。
注意:maxSurge和maxUnavailable的值不能同时为0。
revisionHistoryLimit:保留的历史版本数,默认是10,及保留10个旧版本
paused: 暂停设置,当设置此项后,在K8S集群中新建Deployment时,只会先创建Deployment,其下并不会创建ReplicaSet以及Pod,此时可以用来编辑我们的Deployment,默认是False。如果创建Deployment完成后,再将paused改为True,则此时再更改其配置清单后不会自动更新Pod。
至于template的内容,与ReplicaSet是一样的,也不再赘述。现在,我们创建一下Deployment。
[root@k8s7-22 ~]# kubectl create -f http://k8s-yaml.od.com/Deployment/myapp-dp.yaml
deployment.apps/myapp-dp created
[root@k8s7-22 ~]# kubectl get rs
No resources found.
[root@k8s7-22 ~]# kubectl get pods
No resources found.
[root@k8s7-22 ~]# kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
myapp-dp 0/2 0 0 17s
此时我们可以看到,我们创建了Deployment,但是ReplicaSet和Pod都没有被创建,就是因为我们在资源配置清单中指定了paused为True,接下来,我们将此项配置删掉,然后再看下我们的Pod资源:
[root@k8s7-22 ~]# kubectl edit deploy myapp-dp
deployment.extensions/myapp-dp edited
[root@k8s7-22 ~]# kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
myapp-dp 0/2 2 0 39s
[root@k8s7-22 ~]# kubectl get rs
NAME DESIRED CURRENT READY AGE
myapp-dp-7f46b784cc 2 2 0 7s
[root@k8s7-22 ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
myapp-dp-7f46b784cc-jtv52 0/1 Running 0 11s
myapp-dp-7f46b784cc-lbnwf 0/1 Running 0 11s
[root@k8s7-22 ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
myapp-dp-7f46b784cc-jtv52 0/1 Running 0 23s
myapp-dp-7f46b784cc-lbnwf 1/1 Running 0 23s
[root@k8s7-22 ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
myapp-dp-7f46b784cc-jtv52 0/1 Running 0 25s
myapp-dp-7f46b784cc-lbnwf 1/1 Running 0 25s
[root@k8s7-22 ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
myapp-dp-7f46b784cc-jtv52 1/1 Running 0 29s
myapp-dp-7f46b784cc-lbnwf 1/1 Running 0 29s
当我们将myapp-dp的资源配置清单编辑完成后,删除了paused配置项,我们可以看到,Deployment立即继续执行了剩下的操作,创建了ReplicaSet,创建了Pod,因为我们的Pod设置了readinessProbe,所以直到可用性检测完成后,我们的Pod才标记为READY状态,此时,我们的Pod才可以对外提供服务,如果其前端有service资源的话,此时才能被加入service之中。
接下来,我们将我们的Pod升级到v2版本,我们来看下Deployment更新的过程,我们定义了更新策略是滚动更新,且最多可以多创建10个临时Pod用于升级,所以,我们直接用kubectl edit命令实时编辑其资源配置清单,此时就会立即触发更新操作:
[root@k8s7-22 ~]# kubectl edit deploy myapp-dp
deployment.extensions/myapp-dp edited
[root@k8s7-22 ~]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
myapp-dp-7f46b784cc-jtv52 1/1 Running 0 8m59s 172.17.22.2 k8s7-22.host.com <none> <none>
myapp-dp-7f46b784cc-lbnwf 1/1 Running 0 8m59s 172.17.21.3 k8s7-21.host.com <none> <none>
myapp-dp-b99666748-d8rhh 0/1 Running 0 12s 172.17.21.4 k8s7-21.host.com <none> <none>
myapp-dp-b99666748-kvt6w 0/1 Running 0 12s 172.17.22.3 k8s7-22.host.com <none> <none>
[root@k8s7-22 ~]# kubectl get rs -o wide
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
myapp-dp-7f46b784cc 0 0 0 9m12s myapp-dp-container harbor.od.com/public/myapp:v1 app=myapp-dp,pod-template-hash=7f46b784cc,release=v1
myapp-dp-b99666748 2 2 2 25s myapp-dp-container harbor.od.com/public/myapp:v2 app=myapp-dp,pod-template-hash=b99666748,release=v1
[root@k8s7-22 ~]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
myapp-dp-b99666748-d8rhh 1/1 Running 0 30s 172.17.21.4 k8s7-21.host.com <none> <none>
myapp-dp-b99666748-kvt6w 1/1 Running 0 30s 172.17.22.3 k8s7-22.host.com <none> <none>
[root@k8s7-22 ~]# curl 172.17.22.3
myapp | v2
[root@k8s7-22 ~]# curl 172.17.21.4
myapp | v2
我们看到,当我们编辑完成后,Deployment立即新建了两个新的Pod,而且这两个新的Pod是通过新的ReplicaSet
创建的,而旧的ReplicaSet
的期望副本数变成了0,此时我们还可以查看一下Deployment资源的更新版本号:
[root@k8s7-22 ~]# kubectl rollout history deploy myapp-dp
deployment.extensions/myapp-dp
REVISION CHANGE-CAUSE
1 <none>
2 <none>
此时,已经有两个版本了,通过kubectl rollout undo命令可以来回滚版本,例如,回退到上一个版本:
[root@k8s7-22 ~]# kubectl rollout undo deploy myapp-dp
deployment.extensions/myapp-dp rolled back
[root@k8s7-22 ~]# kubectl rollout history deploy myapp-dp
deployment.extensions/myapp-dp
REVISION CHANGE-CAUSE
2 <none>
3 <none>
[root@k8s7-22 ~]# kubectl get rs -o wide
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
myapp-dp-7f46b784cc 2 2 2 20m myapp-dp-container harbor.od.com/public/myapp:v1 app=myapp-dp,pod-template-hash=7f46b784cc,release=v1
myapp-dp-b99666748 0 0 0 11m myapp-dp-container harbor.od.com/public/myapp:v2 app=myapp-dp,pod-template-hash=b99666748,release=v1
[root@k8s7-22 ~]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
myapp-dp-7f46b784cc-jnq65 1/1 Running 0 54s 172.17.22.2 k8s7-22.host.com <none> <none>
myapp-dp-7f46b784cc-p4q6l 1/1 Running 0 54s 172.17.21.3 k8s7-21.host.com <none> <none>
[root@k8s7-22 ~]# curl 172.17.22.2
abc
[root@k8s7-22 ~]# curl 172.17.21.3
abc
除了能回退到上一个版本外,我们还可以回退到某个指定版本,只需要在回滚操作时加上参数 --to-revision=版本号 即可回滚到指定版本:
[root@k8s7-22 ~]# kubectl rollout undo deploy myapp-dp --to-revision=2
deployment.extensions/myapp-dp rolled back
[root@k8s7-22 ~]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
myapp-dp-b99666748-kxlxv 1/1 Running 0 63s 172.17.22.4 k8s7-22.host.com <none> <none>
myapp-dp-b99666748-nlp2z 1/1 Running 0 112s 172.17.22.2 k8s7-22.host.com <none> <none>
[root@k8s7-22 ~]# curl 172.17.22.4
myapp | v2
[root@k8s7-22 ~]# curl 172.17.22.2
myapp | v2
3、DeamonSet
DaemonSet型的Pod控制器,会在集群中所有的Node节点上启动且仅启动一个Pod,DaemonSet一般用于部署集群基础服务,例如监控、日志收集等等。我们来看一个DaemonSet的资源配置清单:
[root@k8s7-200 DaemonSet]# cat myapp-ds.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: myapp-ds
namespace: default
spec:
selector:
matchLabels:
app: myapp-ds
release: ds
revisionHistoryLimit: 3
updateStrategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
template:
metadata:
labels:
app: myapp-ds
release: ds
spec:
hostNetwork: True # 使用Node节点的网络,此时Pod的IP地址会使用Node的地址,从集群中其他Node会直接访问到Pod的服务
containers:
- name: myapp-ds
image: harbor.od.com/public/myapp:v1
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
livenessProbe:
httpGet:
port: http
path: "/index.html"
failureThreshold: 4
periodSeconds: 20
timeoutSeconds: 10
initialDelaySeconds: 10
readinessProbe:
httpGet:
port: http
path: "/index.html"
failureThreshold: 4
periodSeconds: 20
timeoutSeconds: 10
initialDelaySeconds: 10
其实DaemonSet的资源配置清单和Deployment的资源配置清单字段差不多,只是DaemonSet需要在每个Node节点上部署一个Pod,所以不用再指定replicas字段了。DaemonSet也支持滚动更新,也可以定义滚动更新的策略,updateStrategy字段就是来定义更新策略的,其下级字段type可指定更新类型,支持 RollingUpdate 及 OnDelete,默认是RollingUpdate,当type选择RollingUpdate时,还可以设置rollingUpdate字段,其下级字段只有maxUnavailable可选,且这个字段不能为0,默认值是1。