本文是《深入剖析k8s》学习笔记的第三篇,主要对k8s中的控制器进行分析和讲解。
pod是k8s操作的最小单元,操作pod的工作都是由控制器controller完成的,对应k8s中的组件就是kube-controller-manager,其下常见的控制器类型有:
- Deployment,无状态容器控制器,通过控制ReplicaSet来控制pod;应用场景web应用;
- StatefulSet,有状态容器控制器,保证pod的有序和唯一,pod的网络标识和存储在pod重建前后一致;应用场景主从架构;
- DeamonSet,守护容器控制器,确保所有节点上有且仅有一个pod;应用场景监控;
- Job,普通任务容器控制器,只会执行一次;应用场景离线数据处理;
- CronJob,定时任务容器控制器,定时执行;应用场景通知、备份;
控制器的yaml文件一般分为两个部分,上半部分是关于控制器的定义,下半部分是关于被控制对象,也就是pod的定义:
## 上半部分,定义控制器
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
# 管理标签为app=nginx的pod
selector:
matchLabels:
app: nginx
# 确保被管理的对象数量始终为3
replicas: 3
## 下半部分,定义被控制的对象,也就是pod
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
Deployment、ReplicaSet、Pod三者之间是一种层层控制的关系,ReplicaSet通过控制器模式来保证pod的数量永远等于指定的个数,而Deployment同样也通过控制器的模式来操作ReplicaSet。如此,Deployment可以很方便地实现如下两个特性(并不只是Deployment控制器的功能):
- 水平收缩,通过修改replicas副本数量,Deployment将指令传递给ReplicaSet,由ReplicaSet来实现pod的水平收缩;
- 滚动更新,Deployment会创建一个新的ReplicaSet来逐一新建新版本的pod,旧的ReplicaSet也逐渐将旧版本的pod副本数量降低到0;好处就是如果新版本的pod创建过程中出现问题,滚动更新会中断,旧版本的pod也会停止销毁,此时还能对外提供旧版本的服务,不会导致业务中断;当然,我们还可以通过如下的更新策略字段来控制新版本滚动更新的范围,实现金丝雀发布或者灰度发布的目的:
- partition,StatefulSet控制器拥有该属性,表示编号大于等于该值的pod才会更新为新的版本;
- maxSurge,Deployment拥有该属性,更新时同一时间新建pod的数量占总数量的百分比,默认25%;
- MaxUnavailable,Deployment拥有该属性,更新时同一时间可以删除pod的数量占总数量的百分比,默认25%;
- pods,Deployment拥有该属性,表示可以一次性更新的pod数量;
StatefulSet主要是为了满足需要维持应用之间拓扑状态、稳定应用各自的网络状态、保持应用各自存储状态的情形而设计的。
- 拓扑状态,即多个pod之间存在启动依赖关系,比如主从架构中,从节点肯定要依赖并晚于主节点才能启动,且写请求一定只能转发给主节点,这种情况下,Deployment就无法解决。StatefulSet之所以能满足该场景,是因为其给所有pod进行了编号,编号以StatefulSet Name-index命名,比如mystatefulset-0、mystatefulset-1,每个pod按照编号递增,永不重复。这些pod的创建是顺序同步进行的,前面的pod没有进入到running状态,后面的pod就会一直pending。
- 网络状态,所有pod都进入running之后,各自都会有固定不变的网络身份,即他们的hostname,和它们的pod编号名称一样,固定不变是指无论重新创建多少次,各个pod的网络身份始终一致。这样通过headless service方式访问每个pod的地址就始终固定不变,比如mystatefulset-0.servicename、mystatefulset-1.servicename,如此StatefulSet就保证了pod网络标识的稳定性。
- 存储状态,每个pod挂载pvc时,也会给pvc命名,命名规则为pvc-StatefulSet Name-index,比如mypvc-mystatefulset-0、mypvc-mystatefulset-1,无论pod重新创建与否,每个pod与其对应的pvc始终都会处于绑定状态。如此StatefulSet就保证了存储状态的稳定性。
DaemonSet控制器确保k8s集群中的每个节点有且仅有一个定义的pod运行。那么它是如何实现这个功能的呢?
- 从etcd中获取所有node的信息,循环遍历每个node,没有守护容器的就新建,超过1个的就删除,正好就1个的就表示正常;
- 通过nodeAffinity节点亲和性来保证每个守护pod只会在和其亲和的节点上被调度运行;
- 通过tolerations声明,允许守护pod可以在被标记为污点的Node上调度运行;
建议在设置DaemonSet控制器时,限制守护pod的资源占用上线,避免守护容器过度占用宿主机的资源,影响用户容器的正常运行。
前面所说的Deployment、StatefulSet、DaemonSet都是一种Long Running Task,即长在线业务,这些应用一旦运行起来,除非出错或者停止,它的容器进程会一直保持在running的状态。然而现实中有种业务是执行完成就结束,不需要一直在线运行的,这种业务被称为离线业务,Batch Job,就是我们接下来要介绍的Job和CronJob。
如下是一个Job的yaml定义代码,它也是直接管理pod,其中的部分字段含义如下:
- restartPolicy,指pod中的任务执行失败时的重启策略;
- Never,表示pod不要重启,但是会创建新的pod来替换旧的pod执行任务;
- OnFailure,pod不会变化,但是会尝试重启pod中的容器;
- spec.backoffLimit,任务失败的最大重试次数,每次重试的时间间隔是按照指数级增加的,比如10s、20s、40s......
- spec.activeDeadlineSeconds,任务最大可运行时间,到了这个时间,Job中还未结束的pod都会被强制停止;
apiVersion: batch/v1
kind: Job
metadata:
name: test-job
spec:
# 定义pod模板
template:
spec:
# 定义pod中容器
containers:
- name: test01
image: resouer/ubuntu-bc
# 需要在容器中执行的任务,执行完毕就退出
command: ["sh", "-c", "echo 'scale=10000; 4*a(1)' | bc -l "]
# pod的重启策略,通常有Never和OnFailure
restartPolicy: Never
# 任务失败重试次数,默认为6
backoffLimit: 4
Job还支持任务的并行执行,主要通过如下两个参数来控制并行的策略:
- spec.parallelism,任务并行度,定义一个Job同时最多可以启动pod的数量;
- spec.completions,任务数,定义一个Job总共需要完成多少个pod的数量;
CronJob是Job的控制器,通过控制Job来控制pod,只是比Job多了调度cron表达式的功能。它所用的cron表达式是unix cron风格的,支持5位,分别代表分、时、日、月、星期,比如*/1 * * * *
就表示每隔1分钟就创建一个Job,但是并不限制已经创建Job的结束时间,所以可能就会出现多个Job同时在运行的情况,此时可以通过如下的字段来进行约束:
- spec.concurrencyPolicy,定义Job的调度策略;
- Allow,允许多个Job同时运行;
- Forbid,不允许多个Job同时运行,该创建周期被跳过;
- Replace,新周期创建的Job会替换旧的还没有执行完成的Job;
- spec.startingDeadlineSeconds,指定若干秒内,某个Job如果执行100次都失败,就会停止再次创建这个Job;