theboyaply
theboyaply
发布于 2020-03-27 / 656 阅读
0
0

k8s 控制器介绍

Pod 控制器在 这一章节 已经简单的介绍过了,那么本篇文章将继续讲解 Pod 的控制器,并提供一些简单的示例。

  • 自主式 Pod

    直接创建一个 Pod 而不使用控制器,这种方式创建的 Pod 就称为 自主式 Pod。

    这种 Pod 一旦停止或被删除,就无法自动重启或重新创建。

  • 控制器管理的 Pod

    通过 ReplicaSet、DaemonSet 等控制器创建的 Pod,称为控制器管理的 Pod。

    通过控制器来管理的 Pod,会有一个期望的副本数量,一旦 Pod 超出或者达不到这个数量,那么控制器就会通过重启、创建或者删除 Pod 的方式维持 Pod 的数量达到期望值。

什么是控制器

k8s 中内建了很多控制器(controller ),这些相当于一个状态机,用来控制 Pod 的具体状态和行为。

控制器类型

  • ReplicationController
  • ReplicaSet
  • DaemonSet
  • StatefulSet
  • Job/CronJob
  • Horizontal Pod Autoscaling

ReplicationController 和 ReplicaSet

ReplicationController(RC)用来确保容器应用的副本数始终保持在用户定义的副本数,即如果有容器异常退出,会自动创建新的 Pod 来替代,而如果异常多出来的容器也会自动回收。

在新版本的 k8s 中建议使用 ReplicaSet(RS)来取代 RC,RS 与 RC 没有本质的不同,只是名字不一样,并且RS 支持集合式的 selector,即通过标签(labels)来管理多个 Pod。

以下是 RS 示例:

apiVersion: extensions/v1beta1
kind: ReplicaSet
metadata:
  name: rs-1
spec:
  replicas: 3
  selector:
    matchLabels:
      tier: frontend
  template:
    metadata:
      labels:
        tier: frontend
    spec:
      containers:
      - name: nginx-container
        image: hub.xixihaha.com/library/mynginx:v1
        ports:
        - containerPort: 80

说明:

  • metadata.name:定义 rs 的名称。
  • spec.replicas:期望的 Pod 副本数。
  • spec.selector:selector 选择器。其中 matchLabels.tier 是需要匹配的 Pod 标签。
  • spec.template:template 中的信息可以理解为就是定义一个 Pod。
  • spec.template.metadata:Pod 的元数据信息,其中 labels.tier 是 Pod 的标签,且必须和上面的 matchLabels.tier 一致,这样才能被 RS 控制器选择到。
  • image:这里使用的镜像是我本地镜像,本质上就是一个 nginx 镜像。

执行以下操作:

[root@k8s-master01 ~]# kubectl create -f rs.yaml 
replicaset.extensions/rs-1 created

# 查看 rs 控制器
[root@k8s-master01 ~]# kubectl get rs
NAME   DESIRED   CURRENT   READY   AGE
rs-1   3         3         3       4m33s

# 查看Pod。--show-labels:列出LABELS字段;-l tier=frontend:查看标签是 tier=frontend 的Pod
[root@k8s-master01 ~]# kubectl get pod --show-labels -l tier=frontend
NAME         READY   STATUS    RESTARTS   AGE     LABELS
rs-1-d96fz   1/1     Running   0          94s     tier=frontend
rs-1-rxsls   1/1     Running   0          4m57s   tier=frontend
rs-1-zmckx   1/1     Running   0          4m57s   tier=frontend

# 将 rs-1-ppmnz 这个Pod的标签改为 frontend1
[root@k8s-master01 ~]# kubectl label pod rs-1-ppmnz tier=frontend1 --overwrite=true
pod/rs-1-ppmnz labeled

# 再次查看Pod。因为 frontend1 这个标签不属于 rs-1 控制器管理,控制器为了达到副本数3,就又创建了一个Pod
[root@k8s-master01 ~]# kubectl get pod --show-labels
NAME         READY   STATUS    RESTARTS   AGE     LABELS
rs-1-d96fz   1/1     Running   0          12s     tier=frontend
rs-1-ppmnz   1/1     Running   0          3m35s   tier=frontend1
rs-1-rxsls   1/1     Running   0          3m35s   tier=frontend
rs-1-zmckx   1/1     Running   0          3m35s   tier=frontend

# 删除 rs-1 控制器
[root@k8s-master01 ~]# kubectl delete rs rs-1
replicaset.extensions "rs-1" deleted

# 查看Pod,发现被控制器管理的Pod全部不存在了
[root@k8s-master01 ~]# kubectl get pod --show-labels
NAME         READY   STATUS    RESTARTS   AGE     LABELS
rs-1-ppmnz   1/1     Running   0          9m42s   tier=frontend1

Deployment

Deployment 为 Pod 和 RS 提供了一个声明式定义(declarative)方法,用来替代以前的 RC 来方便的管理应用。典型的应用场景包括:

  • 定义 Deployment 来创建 RS 和 Pod,实际上 Deployment 是通过管理 RS 去管理 Pod的。

  • 滚动升级和回滚应用

  • 扩容和缩容

  • 暂停和继续 Deployment

声明式编程:侧重于定义期望目标,然后告诉程序或者软件,让它来帮我们完成。声明式一般使用 apply 来创建对应的服务。如 Deployment。

命令式编程:侧重于如何实现程序,通常需要一步一步定义程序执行步骤,以达到预期目的。命令式一般使用 create 来创建对应的服务。如 ReplicaSet。

虽然声明式编程和命令式编程建议区别 apllycreate ,但是两者混淆使用也不会出问题。

创建 Deployment

以下是 Deployment 示例:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: deployment-1
spec:
  replicas: 3
  template:
    metadata:
      labels:
        app: nginx-app
    spec:
      containers:
      - name: nginx-container
        image: hub.xixihaha.com/library/mynginx:v1
        ports:
        - containerPort: 80

说明:

  • metadata.name:deployment 的名称。
  • spec.replicas:期望的 Pod 副本数。
  • spec.template:template 中的信息可以理解为就是定义一个 Pod。
  • spec.template.metadata:Pod 的元数据信息。其 labels.app 为 Pod 的标签。
  • image:这里使用的镜像是我本地镜像,本质上就是一个 nginx 镜像。

执行以下操作:

kubectl 的 --record 的 flag 设置为 true 可以在 annotation 中记录当前命令创建或者升级了该资源。这在未来会很有用,例如,查看在每个 Deployment revision 中执行了哪些命令。

# 创建 Deployment,
[root@k8s-master01 ~]# kubectl apply -f demployment.yaml --record
deployment.extensions/deployment-1 created

# 查看 Deployment
[root@k8s-master01 ~]# kubectl get deployment
NAME           READY   UP-TO-DATE   AVAILABLE   AGE
deployment-1   3/3     3            3           31s

# 查看 rs
[root@k8s-master01 ~]# kubectl get rs
NAME                      DESIRED   CURRENT   READY   AGE
deployment-1-7796b9c74d   3         3         3       37s

# 查看 Pod,--show-labels:显示Pod的标签
[root@k8s-master01 ~]# kubectl get pod --show-labels
NAME                            READY   STATUS    RESTARTS   AGE   LABELS
deployment-1-7796b9c74d-96psh   1/1     Running   0          44s   app=nginx-app,pod-template-hash=7796b9c74d
deployment-1-7796b9c74d-gm9z5   1/1     Running   0          44s   app=nginx-app,pod-template-hash=7796b9c74d
deployment-1-7796b9c74d-kvtj2   1/1     Running   0          44s   app=nginx-app,pod-template-hash=7796b9c74d

扩容

# 将Pod的期望副本数扩容到5个
[root@k8s-master01 ~]# kubectl scale deployment deployment-1 --replicas 5
deployment.extensions/deployment-1 scaled

# 查看 rs,rs没有改变,仅仅是期望的副本数变为5个了
[root@k8s-master01 ~]# kubectl get rs
NAME                      DESIRED   CURRENT   READY   AGE
deployment-1-7796b9c74d   5         5         5       8m39s

[root@k8s-master01 ~]# kubectl get pod --show-labels
NAME                            READY   STATUS    RESTARTS   AGE     LABELS
deployment-1-7796b9c74d-8rvgk   1/1     Running   0          10s     app=nginx-app,pod-template-hash=7796b9c74d
deployment-1-7796b9c74d-96psh   1/1     Running   0          7m22s   app=nginx-app,pod-template-hash=7796b9c74d
deployment-1-7796b9c74d-gm9z5   1/1     Running   0          7m22s   app=nginx-app,pod-template-hash=7796b9c74d
deployment-1-7796b9c74d-jkwc8   1/1     Running   0          10s     app=nginx-app,pod-template-hash=7796b9c74d
deployment-1-7796b9c74d-kvtj2   1/1     Running   0          7m22s   app=nginx-app,pod-template-hash=7796b9c74d

滚动更新

滚动更新过程可参考这里:点我直达

Deployment 的 rollout(滚动更新) 当且仅当 Deployment 的 pod template(例如.spec.template)中的 label更新或者镜像更改时被触发。其他更新,例如扩容 Deployment 不会触发 rollout。

假如我们现在想要让 Pod 中的 nginx-container 容器使用 hub.xixihaha.com/library/mynginx:v2 的镜像来代替原来的 hub.xixihaha.com/library/mynginx:v1 的镜像。

[root@k8s-master01 ~]# kubectl set image deployment/deployment-1 nginx-container=hub.xixihaha.com/library/mynginx:v2
deployment.extensions/deployment-1 image updated

# 查看 deployment,发现没什么变化
[root@k8s-master01 ~]# kubectl get deployment
NAME           READY   UP-TO-DATE   AVAILABLE   AGE
deployment-1   5/5     5            5           24m

# 查看 rs,出现了一个新的rs,pod数量维持在5个,并且原先的rs的pod数量变为0了
# deployment-1-6fd64dcb5 就是deployment升级后新创建的rs,并且Pod里的容器使用的是新的镜像 
[root@k8s-master01 ~]# kubectl get rs
NAME                      DESIRED   CURRENT   READY   AGE
deployment-1-6fd64dcb5    5         5         5       22s
deployment-1-7796b9c74d   0         0         0       22m

查看更新状态

[root@k8s-master01 ~]# kubectl rollout status deployment/deployment-1
deployment "deployment-1" successfully rolled out

查看升级记录

[root@k8s-master01 ~]# kubectl rollout history deployment/deployment-1
deployment.extensions/deployment-1 
REVISION  CHANGE-CAUSE
1         kubectl apply --filename=demployment.yaml --record=true
2         kubectl apply --filename=demployment.yaml --record=true

因为我们创建 Deployment 的时候使用了 --recored 参数可以记录命令,我们可以很方便的查看每次 revision 的变化。

查看单个revision 的详细信息:

[root@k8s-master01 ~]# kubectl rollout history deployment/deployment-1 --revision=2
deployment.extensions/deployment-1 with revision #2
Pod Template:
  Labels:	app=nginx-app
	pod-template-hash=6fd64dcb5
  Annotations:	kubernetes.io/change-cause: kubectl apply --filename=demployment.yaml --record=true
  Containers:
   nginx-container:
    Image:	hub.xixihaha.com/library/mynginx:v2
    Port:	80/TCP
    Host Port:	0/TCP
    Environment:	<none>
    Mounts:	<none>
  Volumes:	<none>

清理 Policy

我们可以设置 Deployment 中的 .spec.revisionHistoryLimit 项来指定保留多少旧的 ReplicaSet。 余下的将在后台被当作垃圾收集。默认的,所有的 revision 历史就都会被保留。在未来的版本中,将会更改为2。

注意: 将该值设置为0,将导致所有的 Deployment 历史记录都会被清除,该 Deployment 就无法再回退了。

回滚更新

假设我们上面升级的镜像出现了问题,现在需要回退一个版本,可以使用以下命令:

kubectl rollout undo deployment/deployment-1

也可以使用 --revision 参数指定某个历史版本:

kubectl rollout undo deployment/deployment-1 --to-revision=2

此时我们查看 rs:

[root@k8s-master01 ~]# kubectl get rs
NAME                      DESIRED   CURRENT   READY   AGE
deployment-1-6fd64dcb5    0         0         0       4m41s
deployment-1-7796b9c74d   5         5         5       5m13s

版本回退时 Deployment 并没有创建新的 RS,而是将原来的 RS 重新激活了。

我们再次查看版本更新记录:

[root@k8s-master01 ~]# kubectl rollout history deployment/deployment-1
deployment.extensions/deployment-1 
REVISION  CHANGE-CAUSE
2         kubectl apply --filename=demployment.yaml --record=true
3         kubectl apply --filename=demployment.yaml --record=true

[root@k8s-master01 ~]# kubectl rollout history deployment/deployment-1 --revision=3
deployment.extensions/deployment-1 with revision #3
Pod Template:
  Labels:	app=nginx-app
	pod-template-hash=7796b9c74d
  Annotations:	kubernetes.io/change-cause: kubectl apply --filename=demployment.yaml --record=true
  Containers:
   nginx-container:
    Image:	hub.xixihaha.com/library/mynginx:v1
    Port:	80/TCP
    Host Port:	0/TCP
    Environment:	<none>
    Mounts:	<none>
  Volumes:	<none>

可以发现,回退版本的操作也会被记录下来。

更新策略

Deployment 可以保证在升级时只有一定数量的 Pod 是 down 的。默认的,它会确保至少有比期望的Pod数量少一个是up状态(最多一个不可用)。

Deployment 同时也可以确保只创建出超过期望数量的一定数量的 Pod。默认的,它会确保最多比期望的Pod数量多一个的 Pod 是 up 的(最多1个 surge )。

在未来的 Kuberentes 版本中,将从1-1变成25%-25%。

Rollover(多个rollout并行)

每当 Deployment controller 观测到有新的 deployment 被创建时,如果没有已存在的 ReplicaSet 来创建期望个数的 Pod 的话,就会创建出一个新的 ReplicaSet 来做这件事。已存在的 ReplicaSet 控制 label 与 .spec.selector 匹配但是 template 跟 .spec.template 不匹配的 Pod 缩容。最终,新的 ReplicaSet 将会扩容出 .spec.replicas 指定数目的 Pod,旧的 ReplicaSet 会缩容到0。

如果您更新了一个的已存在并正在进行中的 Deployment,每次更新 Deployment 都会创建一个新的 ReplicaSet 并扩容它,同时回滚之前扩容的 ReplicaSet ----将它添加到旧的 ReplicaSet 列表中,开始缩容。

例如,假如您创建了一个有5个 niginx:1.7.9 replica 的 Deployment,但是当还只有3个 nginx:1.7.9 的 replica 创建出来的时候您就开始更新含有5个 nginx:1.9.1 replica 的 Deployment。在这种情况下,Deployment 会立即杀掉已创建的3个 nginx:1.7.9 的 Pod,并开始创建 nginx:1.9.1 的 Pod。它不会等到所有的5个 nginx:1.7.9 的 Pod 都创建完成后才开始改变航道。

DaemonSet

DaemonSet 确保全部(或者一些)node 上运行一个 Pod 的副本。当有新的 node 加入集群时,会自动为他们添加一个这样的 Pod。当有集群移除时,这些 Pod 也会被回收。删除 DaemonSet 将会删除它所创建的所有 Pod。

常见场景:

  • 运行集群存储 daemon,例如在每个 node 上运行 glusterd、ceph。
  • 在每个 node 上运行日期收集 daemon,例如 fluentd、logstash。
  • 在每个 node 上运行监控 daemon,例如 Prometheus node Exporter、collectd、Datadog 代理或New Relic 代理。

以下是 DaemonSet 示例:

apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
  name: daemonset-1
  labels:
    app: daemonset-app
spec:
  selector:
    matchLabels:
      name: daemonset-label
  template:
    metadata:
      labels:
        name: daemonset-label
    spec:
      containers:
      - name: nginx-container
        image: hub.xixihaha.com/library/mynginx:v1
        ports:
        - containerPort: 80

说明:

  • metadata.name:daemonSet 的名称。
  • metadata.labels.app:daemonSet 自己的标签。
  • spec.selector:标签选择。matchLabels.name 指定了要选择的 Pod 标签。
  • spec.template:template 中的信息可以理解为就是定义一个 Pod。
  • spec.template.metadata:Pod 的元数据信息,其中 labels.name 是 Pod 的标签,且必须和上面的 matchLabels.name 一致,这样才能被 DS 控制器选择到。

执行以下命令:

[root@k8s-master01 ~]# kubectl create -f daemonset.yaml 
daemonset.extensions/daemonset-1 created

# 查看daemonSet
[root@k8s-master01 ~]# kubectl get ds
NAME          DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
daemonset-1   2         2         2       2            2           <none>          113s

# 查看Pod标签
[root@k8s-master01 ~]# kubectl get pod --show-labels
NAME                READY   STATUS    RESTARTS   AGE   LABELS
daemonset-1-2ftjc   1/1     Running   0          52s   controller-revision-hash=6445f7997f,name=daemonset-label,pod-template-generation=1
daemonset-1-w9cwc   1/1     Running   0          52s   controller-revision-hash=6445f7997f,name=daemonset-label,pod-template-generation=1

# 查看Pod运行在哪个节点
[root@k8s-master01 ~]# kubectl get pod -o wide
NAME                READY   STATUS    RESTARTS   AGE   IP            NODE         NOMINATED NODE   READINESS GATES
daemonset-1-2ftjc   1/1     Running   0          8s    10.244.2.45   k8s-node02   <none>           <none>
daemonset-1-w9cwc   1/1     Running   0          8s    10.244.1.14   k8s-node01   <none>           <none>

结果显示一共创建了两个 Pod,且分别运行在 k8s-node01 和 k8s-node02 两个节点上。

我们说过,DaemonSet 确保了集群中每一个节点都会运行一个 Pod(master 节点没有运行是因为 污点 的设置)。就算我们删除某个节点的 Pod,DaemonSet 也会马上为这个节点重新创建一个 Pod。

此时如果有新的节点加入到集群中,那么 DaemonSet 也会为这个新节点自动创建一个这样的 Pod。

Job

Job 负责批处理任务,即仅执行一次的任务,它能够确保批处理任务的一个或多个 Pod 运行成功。意思就是,运行一个 Job 来创建 Pod,让里面的容器成功运行了指定的次数,才认为这个 Job 是成功的,那么这个 Job 才算执行完成。

特殊说明:

  • spec.template 格式同 Pod
  • restartPolicy 策略仅支持 Never 或 OnFailure。
  • 单个 Pod 时,默认 Pod 成功运行后 Job 即结束。
  • .spec.completions 标志 Job 结束需要成功运行的 Pod 个数,默认为 1。
  • .spec.parallelism 标志并行运行的 Pod 个数,默认为 1。
  • spec.activeDeadlineSeconds 标志失败的重试最大时间,超过这个时间不会继续重试。

以下是 Job 示例:

apiVersion: batch/v1
kind: Job
metadata:
  name: job-1
spec:
  template:
    metadata:
      name: job-app
    spec:
      containers:
      - name: busybox-container
        image: busybox
        command: ['sh', '-c', 'echo hello world']
      restartPolicy: Never

执行以下命令:

# 查看 job
[root@k8s-master01 ~]# kubectl get job
NAME    COMPLETIONS   DURATION   AGE
job-1   1/1           57s        2m10s

# 查看 pod
[root@k8s-master01 ~]# kubectl get pod
NAME          READY   STATUS      RESTARTS   AGE
job-1-znjg4   0/1     Completed   0          79s

# 查看 pod 日志
[root@k8s-master01 ~]# kubectl log job-1-znjg4
log is DEPRECATED and will be removed in a future version. Use logs instead.
hello world

CronJob

CronJob 管理基于时间的 Job,即:

  • 在给定的时间点运行一次
  • 周期性的在给定时间点运行
  • 其本质就是在特定的时间循环创建 Job 来执行任务
  • 其表达式为:分、时、日、月、周

常用场景:

  • 在给定的时间点调度 Job 运行
  • 创建周期性的运行的 Job,例如:数据库备份,发送邮件。

CronJob 使用时需要 k8s 版本 >= 1.8。

以下是 CronJob 的示例:

apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: cronjob-1
spec:
  schedule: "*/1 * * * *"
  jobTemplate:
    spec:
      template:
        metadata:
          name: cronjob-app
        spec:
          containers:
          - name: busybox-container
            image: busybox
            command: ['sh', '-c', 'date && echo hello world']
          restartPolicy: Never

说明:

  • spec.schedule:调度,必须字段,指定任务运行周期,格式为分、时、日、月、周。

  • spec.jobTemplate:Job 模板,必须字段,指定需要运行的任务,格式同 Job。

  • spec.startingDeadlineSeconds:启动 Job 的期限(秒级别),该字段是可选的,如果因为任何原因而错过了被调度的时间,那么错过指定时间的 Job 将被认为的失败的。默认无期限。

  • spec.concurrencyPolicy:并发策略,可选。它指定了 CronJob 创建的 Job 的并发执行。只允许指定下面策略中的一种。

    • Allow:允许兵法执行,默认。
    • Forbid:禁止并发运行,如果前一个还没有完成,则直接跳过下一个。
    • Replace:取消当前中正在执行的 Job,用一个新的来替换。

    这个策略只作用于同一个 CronJob 所创建的 Job。如果存在多个 CronJob,它们创建的 Job 之间总是允许并行执行的。

执行以下操作:

# 创建cronjob
[root@k8s-master01 ~]# kubectl apply -f cronjob.yaml 
cronjob.batch/cronjob-1 created

# 查看 cronjob
[root@k8s-master01 ~]# kubectl get cj
NAME        SCHEDULE      SUSPEND   ACTIVE   LAST SCHEDULE   AGE
cronjob-1   */1 * * * *   False     0        <none>          12s

# 查看 job,每隔一分钟创建一个job
[root@k8s-master01 ~]# kubectl get job
NAME                   COMPLETIONS   DURATION   AGE
cronjob-1-1585241460   1/1           65s        73s
cronjob-1-1585241520   0/1           13s        13s

# 查看Pod,job一旦被创建出来,那么job会创建一个对应的Pod
[root@k8s-master01 ~]# kubectl get pod
NAME                         READY   STATUS              RESTARTS   AGE
cronjob-1-1585241460-hmhr6   0/1     Completed           0          91s
cronjob-1-1585241520-5mjr4   0/1     ContainerCreating   0          31s

# 查看Pod日志
[root@k8s-master01 ~]# kubectl log cronjob-1-1585241460-hmhr6
log is DEPRECATED and will be removed in a future version. Use logs instead.
Thu Mar 26 16:52:09 UTC 2020
hello world

StatefulSet

StatefulSet 为 Pod 提供唯一的标识,它可以保证部署和 scale 的顺序。StatefulSet 是为了解决有状态服努的问题,对应 Deployment 和 ReplicaSet 是为无状态服务而设计。

StatefulSet 有以下特点:

  • 稳定的持久化存储,即 Pod 重新调度后还是能访问到相同的持久化数据,基于PVC来实现。
  • 稳定的网络标志,即 Pod 重新调度后,其 PodName 和 HostName 不变,基于 Headless Service(即没有Cluster IP的 Service)来实现。
  • 有序部署,有序扩展。即 Pod 是有顺序的,在部署或扩展的时候要依据定义的顺序依次进行(即从 0 到 N-1,在下一个 Pod 运行之前,之前所有的 Pod 必须都是 Running 和 Ready 状态),基于 init containers 来实现。
  • 有序收缩,有序删除(即从 N- 1 到 0)。

Horizontal Pod Autoscaling

应用的资源使用率通常都有高峰和低谷的时候,如何削峰埋谷,提高集群的整体资源利用率,让 service 中的 Pod 个数自动调整昵?这就有赖于 Horizontal Pod Autoscaling 了,顾名思义,使 Pod 水平自动缩放。

-- end --


评论