K8S系列 -- Pod控制器

        我们之前通过资源配置清单,自己创建了一个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。