kubernetes学习笔记六(控制器)

kubernetes学习笔记六(控制器)

Scroll Down
1、什么是控制器

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

自主式Pod:如果Pod意外退出,这个类型的Pod不会被重新拉起,就相当于没有管理者

控制器管理的Pod:在控制器管理的生命周期下,Pod的副本数始终会被控制器所维持着

控制器类型

# ReplicationController 和 ReplicaSet
# Deployment
# DaemonSet
# StateFulSet
# Job/CronJob
# Horizontal Pod Autoscaling

ReplicationController(退役选手) 和 ReplicaSet

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

# 在新版本的 Kubernetes 中建议使用 ReplicaSet 来取代 ReplicationController 。
# ReplicaSet 跟ReplicationController 没有本质的不同,只是 ReplicaSet 支持集合式的 selector

Depoyment:

Deployment 为 Pod 和 ReplicaSet 提供了一个声明式定义 (declarative) 方法,用来替代以前的 RC 来方便的管理应用。

# 简单提一嘴先
# 声明式编程
声明式编程最经典的代表就是SQL语句,我们不需要知道计算机内部是怎么实现的,我们只用定义语句,比如创建数据库create table ishells; 只用告诉它我要创建一个数据库,DBMS就会理解我们的请求,作为用户来说,我们根本不用管内部是怎么实现的。

# 命令式编程:
它侧重于如何实现程序,强调一步一步按照逻辑实现目的

Deployment典型的应用场景包括
# 定义 Deployment 来创建 Pod 和 ReplicaSet
# 滚动升级和回滚
# 应用扩容和缩容
# 暂停和继续 Deploymen

Deployment是通过管理Rs来管理Pod

当实现滚动更新的时候,用户将新的代码封装到镜像中并修改Deployment的image,提交了更新请求后,Deployment会删除一个旧版本的Pod,创建一个新版本的Pod,旧版本的RS并不会删除,如果需要回滚还需要用到旧版本的RS。而且,滚动更新和回退的时候,每次替换Pod的数目是可以自定义的,比如一次更新百分之多少的Pod、多少个Pod

DaemonSet:

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


使用 DaemonSet 的一些典型用法:
# 运行集群存储 daemon,例如在每个 Node 上运行glusterd、ceph
# 在每个 Node 上运行日志收集 daemon,例如fluentd、logstash
# 在每个 Node 上运行监控 daemon,例如Prometheus Node Exporter、collectd、Datadog 代理、New Relic # 代理,或 Ganglia gmond

Job:

Job负责批量处理短暂的一次性任务 (short lived one-off tasks)
即仅执行一次的任务,它保证批处理任务的一个或多个Pod成功结束。

Kubernetes支持以下几种Job:
# 非并行Job:通常创建仅一个Pod直至其成功结束
# 固定结束次数的Job:设置.spec.completions,创建多个Pod,直到.spec.completions个Pod成功结束
# 带有工作队列的并行Job:设置.spec.Parallelism但不设置.spec.completions,当所有Pod结束并且至少一个成功时,Job就认为是成功

(简单来说,.spec.completions设定成功执行退出的次数,.spec.Parallelism指定并行运行的Pod个数,根据这俩参数会出现下面的几种情况)

根据.spec.completions和.spec.Parallelism的设置,可以将Job划分为以下几种pattern:

Job类型使用示例行为completionsParallelism
一次性Job数据库迁移创建一个Pod直至其成功结束11
固定结束次数的Job处理工作队列的Pod依次创建仅一个Pod运行直至completions次数成功结束2+1
固定结束次数的并行Job多个Pod同时处理工作队列依次创建多个Pod运行直至completions次数成功结束2+2+
并行Job多个Pod同时处理工作队列创建一个或多个Pod直至有一个成功结束12+

CronJob:

Cron Job管理基于时间的 Job,即:
# 在给定时间点只运行一次
# 周期性地在给定时间点运行

# 使用CronJob的k8s集群版本需要>=1.8

典型的用法如下所示:
# 在给定的时间点调度 Job 运行
# 创建周期性运行的 Job,例如:数据库备份、发送邮件

StatefulSet:

StatefulSet 作为 Controller 为 Pod 提供唯一的标识。它可以保证部署和 scale 的顺序
StatefulSet是为了解决有状态服务的问题(对应Deployments和ReplicaSets是为无状态服务而设计),其应用场景包括:
稳定的持久化存储,即Pod重新调度后还是能访问到相同的持久化数据,基于PVC来实现
稳定的持久化存储就是,被StatefulSet管理的Pod,可以使用相同或不同的存储卷,当pod因故死亡后,为了维持副本数,会重新创建新的Pod,并且会连接到之前使用的存储卷,也就是持久化数据不会丢失
稳定的网络标志,即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个数自动调整呢?HPA就是实现这种水平伸缩的控制器, 它能在当POD中业务负载上升的时候,创建新的POD来保证业务系统稳定运行,当POD中业务负载下降的时候,可以销毁POD来提高资源利用率。

HPA其实更像是控制器的附属品,也就是说HPA是通过管理控制器而达到控制pod的目的,HPA是根据一些指标来实现自动扩容缩放的,比如CPU、内存啊这些信息(比方说cpu使用到达到80了,那就扩容到10个节点,cpu小于40了,那就缩放到4个节点),而k8s本身并不支持这些数据收集的功能,所以需要借助一些其他的资源收集方案,它来向HPA提供这些性能指标,然后HPA根据这些指标来做调整。

问题:

为什么k8s没有提供收集指标的方案?

什么场景使用什么控制器呢?

也就意味着遇倒批处理级别的任务时,可以选择`Job`,`Cronjob`

# 1、遇到需要在多节点运行守护进程的任务时,我们可以选择DaemonSet

# 2、遇到需要滚动更新、回滚的任务时,可以选择Rs和Deployment

# 3、那我们也知道,容器最适合的处理场景还是无状态服务,那么遇到无状态服务任务的时候,Docker中使用了存储卷的形式来应对无状态服务的应用场景,那在k8s中应该怎么做呢?

# 3.1 那就是StatefulSet,由StatefulSet所管理的Pod都会连接到同一个或多个数据卷,当Pod因为某些原因down掉,重启了,新起来的pod仍然会连接到原来的数据卷,这样其实就实现了数据的持久化存储方案,

# 3.2 很多服务其实都是基于pod name或Host name进行连接的,而Pod因故重启之后name发生改变了,就需要根据新的pod name或host name重新进行建立连接关系,那这对后续的自动化解决方案肯定是很不友好的。而StatefulSet就可以确保它所管理的Pod在其生命周期中pod name 和 host name 完全不变

# 4、如果我们有一些类似于脚本的功能需要去实现的时候,就可以用到Job、CronJob了,当我们在linux中使用crontab去周期性的执行1个脚本的时候,我们是不是会有这样的忧虑,万一脚本哪里出了错误怎么办?脚本本身如果不具有纠错能力,crontab更不具有这样的能力。

# 4.1 而我们使用job的时候就完全不必有这样的忧虑,job更倾向于`脚本运行管理机制`这样的一个管理方案去管理Pod,`Job`会检查脚本运行是否正常退出了(即返回状态码是否为0),如果没有正常退出就重新执行,job管理Pod时可以`指定一个成功退出的次数`,比如指定Pod执行脚本成功退出次数为2,那么当Pod成功执行一次脚本,次数就从0开始加1,当Pod的脚本成功执行2次,job就会结束退出,也就意味着`job的生命周期就是指定的成功退出次数`


# 4.2 而当需要定时、周期性的执行Job的时候,就需要用到另一个控制管理器了,`Crontab`,并且它的工作原理其实就是在特定的时间循环创建`Job`

其实也就意味着遇到批处理级别的任务时,可以选择`Job`,`Cronjob`

为什么有序部署不是基于start、stop命令,而是init容器?

以为你无论是start还是stop命令,都是需要去修改镜像的,而init容器只用在主容器之前启动一个容器就行了,并会对原有的pod结构发生改变

2、部分控制器演示:

资源清单中,控制器所管理的Pod的template.spec部分都是可以根据个人需求来定义所需的功能,可简单可复杂,例如简单进定义镜像、端口,复杂可定义liveness、startupness、readiness、start命令、stop命令等

① RS的创建

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: frontend
spec:
  replicas: 3
  selector:
    matchLabels:
      tier: frontend
  template:
    metadata:    
      labels:      
        tier: frontend   
    spec:     
      containers:   
      - name: nginx-1     
        image: nginx 
        env:        
        - name: GET_HOSTS_FROM   
          value: dns      
        ports:      
        - containerPort: 80
 
 # 这个yaml定义rs的时候也就相当于在template下嵌套了一个pod的配置字段
 
 # 注意RS的matchLabels和Pod的metadata.labels下所定义的标签值要是一致的,这样pod才能被RS所管理

create_rs.jpg

image.png

在此案例中,由rs根据标签tier:frontend所管理3个pod的副本,如果中途强行将标签值修改了,那么修改过标签的pod将不再属于RS所管理,因为RS是通过集合式的标签进行管理Pod的,一旦Pod标签值发生改变,那么RS就会认为这个Pod不在属于它,它为了维持原有的副本书目不变就会重新创建一个原有标签的pod

1、 获取pod的标签值

# kubectl get pods --show-labels

show_pod_labels.jpg

2、根据指定的标签值筛选pod

   # kubectl get pods --selector 标签名=标签值
或 # kubectl get pods -l 标签名=标签值 

get_pods_from_label.jpg

3、删除pod的标签值

# kubectl label pod pod名称 标签名-

label_pod.jpg

5、为pod添加标签

# kubectl label  pods pod名称 标签名=标签值
# 
# 如果pod原有标签,加上--overwrite参数可覆盖原参数,不加--overwrite参数就会添加一个新的标签
# kubectl label --overwrite pod pod名称 标签名=标签值

label_pod_overwrite.jpg

删除RS,RS所维持的副本也会被一并删除
delete_rs.jpg

② Deployment部署一个简单的nginx应用

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:  
    metadata:      
      labels:    
        app: nginx   
    spec:   
      containers:  
      - name: nginx 
        image: nginx:1.7.9  
        ports:    
        - containerPort: 80

创建Deployment

# kubectl apply -f yaml文件  --record
--record参数可以记录命令,可以很方便的查看每次 revision(修正) 的变化

create_deployment.jpg

副本数目的收缩、扩容

# kubectl scale deployment deployment名称 --replicas=新副本数

# 如果集群支持Horizontal pod autoscaling 的话,还可以为Deployment设置自动扩展
# kubectl autoscale deployment nginx-deployment --min=10 --max=15 --cpu-percent=80

如果通过 scale 命令进行更换副本数,它仅仅是更新了pod的副本数目,而不涉及到版本的回退、滚动等,因为创建/删除的pod副本都是原来pod的模板生成的,RS本身并没有发生改变,pod的滚动更新、回滚都是涉及到RS的更新与回退的
scale_deployment.jpg

autoscale_deployment.jpg

删除自动扩缩容HPA

通过HPA参数autoscale自动扩缩容的pod并不会随着HPA的删除而改变,如果需要更改pod的数目需要个人重新指定pod的数目
get_delete_hpa.jpg

更新镜像/滚动更新

# kubectl set image deployment/deployment名称  容器名=本地或远程镜像:标签

# 实际操作得时候,你会发现镜像的更新会触发新的RS的创建,也就是之前提到的滚动更新的过程:创建新的RS,更新一个新的Pod、删除一个旧的Pod

# 更新镜像,实际上它会触发滚动更新( 但请确保资源清单中imagePolicy: Always )。在实际环境中,涉及到容器化的更新都是将更新好的代码、环境封装到镜像中,更新容器版本其实更新镜像就可

set_image_deployment.png

set_image_update.jpg

回滚

# 回滚
# kubectl rollout   undo deployment/deployment名称 

# 查看回滚的状态
# kubectl rollout status deployment/deployment名称
# 使用status查看rollout的状态,如果显示回滚已成功的话,status那条命令的返回状态码会是0                    

# 回滚到特定历史版本
# kubectl rollout undo deployment/deployment名称 --to-revision=指定历史版本

# 查看回滚历史操作
# kubectl rollout history deployment/deployment名称

# kubectl rollout history 看到的历史版本数目与 # kubectl get rs 所看到的数目应该是一致的

# 暂停deployment的更新
# kubectl rollout pause deployent/deployment名称

rollout_undo.jpg

rollout_history.jpg

after_rollout_get_rs.jpg

清理Policy

用户可以通过设置资源清单的.spec.revisionHistoryLimit项来指定deployment最多保留多少version历史记录。默认会保留所有的version,如果将该选项设置为0,Deployment就不允许回退了。因为Deployment的滚动更新、回滚都是基于RS的版本来完成的,如果没有了老旧版本的RS,那也就不存在回滚的操作了。

默认情况下,滚动更新(Rolling Updates)允许通过使用新的实例逐步更新Pod实例,零停机进行Deployment更新。新的Pod将在具有可用资源的节点上进行调度。默认情况下,更新期间不可用的Pod的最大值和可以创建的新的Pod数目都是1这两个选项都可以配置为(pod)数字或百分比。在kubernetes中,更新是经过版本控制的,任何Deployment更新都可以恢复到以前的(稳定)版本。

Rollover(多个rollout并行)

当正在创建10个副本的nginx:v1的deployment时,当replica副本数才创建到了3个,此时镜像被更新为nginx:v2版本了且开始更新这个Deployment了,那么这个Deployment权限会立即杀掉已经创建了的3个v1版本的Pod,并开始创建v2版本的Pod,它不会等到v1版本的10个副本都创建完成后才开始改变航道

疑问:

1、kubectl apply 与 kubectl create 的区别

序号kubectl applykubectl create
1根据yaml文件中包含的字段(yaml文件可以只写需要改动的字段),直接升级集群中的现有资源对象首先删除集群中现有的所有资源,然后重新根据yaml文件(必须是完整的配置信息)生成新的资源对象
2yaml文件可以不完整,只写需要的字段yaml文件必须是完整的配置字段内容
3kubectl apply只工作在yaml文件中的某些改动过的字段kubectl create工作在yaml文件中的所有字段
4在只改动了yaml文件中的某些声明时,而不是全部改动,你可以使用kubectl apply在没有改动yaml文件时,使用同一个yaml文件执行命令kubectl replace,将不会成功(fail掉),因为缺少相关改动信息

2、如果资源清单中,使用kind定义为deployment,然后template下面直接是pod的信息,在创建deployment时,是不是默认会自动创建rs?

答:deployment默认就是通过RS来管理pod的,所以通过资源清单创建deployment的时候默认就会创建RS来管理Pod。

③ DaemonSet 操作

apiVersion: apps/v1
kind: DaemonSet
metadata:  
  name: deamonset-example  # 与下面的 matchLabels 必须一致
  labels:   
    app: daemonset
spec:  
  selector:   
    matchLabels:    
      name: deamonset-example 
  template:   
    metadata:    
      labels:     
        name: deamonset-example    
    spec:    
      containers:     
      - name: daemonset-example     
        image: wangyanglinux/myapp:v1

apply_daemonset.jpg

因为我的k8s集群只有三个节点,分别是master1、node1、node2,因此创建DaemonSet控制器的话,默认会在三个节点各创建一个DaemonSet 指定的 Pod

daemonset_pods.jpg

④ Job

Job spec特殊说明
# spec.template格式同Pod的格式一致,可以根据个人需求加上liveiness、startupness等

# RestartPolicy仅支持Never或OnFailure (Job的机制就是成功执行就退出,如果成功执行退出又重新拉起了那就重复执行了)

# 单个Pod时,默认Pod成功运行后Job即结束

# .spec.completions指定任务成功执行的次数,默认为1(即Pod需要成功运行几次所设定的命令)

# .spec.parallelism标志并行运行的Pod的个数,默认为1
( 如果.completions指定为4,parallelism指定为2,那么意思就是可以并行运行2个Pod,这2个Pod总共成功执行4次即可 )

# .spec.activeDeadlineSeconds标志失败Pod的重试最大时间,超过这个时间不会继续重试
apiVersion: batch/v1
kind: Job
metadata:
  name: pi
spec:
  template: 
  metadata:  
    name: pi   
  spec:    
    containers: 
    - name: pi      
      image: perl      
      command: ["perl","-Mbignum=bpi","-wle","print bpi(2000)"]    
    restartPolicy: Never
# kubectl describe pod

⑤ CronJob

CronJob spec特殊说明
# .spec.schedule:调度,必需字段,指定任务运行周期,格式同 Cron

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

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

# .spec.concurrencyPolicy:并发策略,该字段也是可选的。它指定了如何处理被 Cron Job 创建的 Job 的并发执行。只允许指定下面策略中的一种:
    # Allow(默认):允许并发运行 Job
    # Forbid:禁止并发运行,如果前一个还没有完成,则直接跳过下一个
    # Replace:取消当前正在运行的 Job,用一个新的来替换
 ( 
     意思就是,假设定义每1分钟创建1个Job,第1分钟创建了1个Pod,到了第2分钟的时候又要创建Pod了,此时第一个Pod还没有退出,
         如果.spec.concurrencyPolicy为Allow的话,创建第2个Pod与第1个Pod并行运行;
         如果.spec.concurrencyPolicy为Forbid的话;
         如果.spec.concurrencyPolicy为Replace的话,干掉老的,以新的来替换它
   )
注意,当前策略只能应用于同一个 Cron Job 创建的 Job。如果存在多个 Cron Job,它们创建的 Job 之间总是允许并发运行。

# .spec.suspend:挂起,该字段也是可选的。如果设置为true,后续所有执行都会被挂起。它对已经开始执行的 Job 不起作用。默认值为false。

# .spec.successfulJobsHistoryLimit和.spec.failedJobsHistoryLimit:历史限制,是可选的字段。它们指定了可以保留多少完成和失败的 Job。默认情况下,它们分别设置为3和1(即成功保留3份副本,失败保存1份副本)。设置限制的值为0,相关类型的 Job 完成后将不会被保留
apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: hello
spec:  
  schedule: "*/1 * * * *" 
  jobTemplate:  
    spec:    
      template:    
        spec:
          containers:   
          - name: hello
            image: busybox
            args:
            - /bin/sh
            - -c 
            - date; echo Hello from the Kubernetes cluster
          restartPolicy: OnFailure
# kubectl get cronjob

# kubectl get jobs

# pods=$(kubectl get pods --seletor=job-name=job名称 --output=jsonpath={.items..metadata.name})
# kubectl logs  $pods

# 注意,删除 cronjob 的时候不会自动删除 job,这些 job 可以用 kubectl delete job 来删除
# kubectl delete cronjob cronjob名称

CrondJob 本身的一些限制:

a、cronjob是否成功并不容易判断,原因是cronjob连接到一个job任务,job任务的执行状态可以被判断,但是cronjob无法链接到job的成功状态( 即cronjob仅会定期的创建job而已 )

b、创建 Job 操作应该是幂等的