容器作为一种新的资源类型,已经应用于各大公司,G行也不例外。容器的轻量化特性使得它能够在故障发生时快速进行重建,将对业务的影响降到最低,我们通常称它为自愈。在Kube.NETes中,自动重建的逻辑是什么?为什么我们经常说到Pod自愈而不是容器自愈?如何判定Pod状态,Pod都有什么状态?重启策略都有什么,是什么推动重启?对于不同的重启资源需求,G行如何实现?下文将一一讲述。
图1 Kubernetes架构
谈到容器自愈,为什么说是通过Pod自愈实现的呢?Pod是一组(一个或多个)容器,这些容器共享Pod的存储、网络、以及运行容器的声明。且Pod是K8s(即Kubernetes)的最小部署管理计算单元。K8s是CNCF(Cloud Native Computing Foundation)的毕业项目之一,是目前市场使用最广泛的自动化部署、扩展和管理成千上万容器化应用程序的开源系统,也是G行采用的容器管理工具。Pod运行在被称作是Node的节点中,节点既可以是物理机,也可以是虚拟机。当K8s判定Pod出现问题且需要被杀死的时候,会在运转正常且资源充足的Node节点重建完成,从而完成自愈。
图2 CNCF的毕业项目和孵化中的项目
应用程序可能因多种原因而变得不可靠,例如:暂时失联、配置错误、应用程序错误、硬件故障、资源紧张等。那么如何检测运行着应用程序的pod健康状态是好还是坏呢,K8s是通过探针来检查的,探针有三种:ReadinessProbe、StartupProbe及LivenessProbe。
ReadinessProbe(就绪)探针:用于判断容器服务是否可用(即是否为ready状态),只有Ready状态的Pod才可以接收服务请求。
StartupProbe(启动)探针:适用于应用程序启动缓慢、网络访问延迟等场景,相应造成容器启动缓慢的情况。
LivenessProbe(存活)探针:用于判断容器是否存活①(即是否为running状态),如果探针检测到容器不健康,则Kubelet将杀死这个容器,并根据容器的重启策略(重启策略后面会说)做相应的处理。
G行通过同时使用ReadinessProbe(就绪)探针和LivenessProbe(存活)探针来判定Pod的状态。
K8s提供了5个控制这些探针的选项:
图片
而这三种探针均可以通过以下三种方式②实现:
通过容器的IP地址、端口号及路径调用http get方法,如果相应的状态码大于等于200且小于400,则认定容器是健康状态。
通过容器的IP地址和端口号执行tcp检查,如果能够建立tcp连接,则表明容器健康。
在容器内部运行一个命令,如果返回码为0,则表示容器健康,否则表示不健康。
G行使用HTTPGetAction和TCPSocketAction两种方式查看连接情况。
运维人员可以使用Kubectl命令行客户端或Yaml部署模板来配置探针和IP地址、端口号、路径连接方式。下面是以LivenessProbe探针、HTTPGetAction连接方式为例,编写的Yaml文件:
图片
知道如何检查Pod的健康状态了,如果不健康,该如何进行重启呢?这里就需要说到Pod的重启策略(restartPolicy)了。
重启策略应用于Pod内的所有容器,由Pod所处的Node上的Kubelet进行判断和重启操作。当某个容器异常退出或者健康检查失败时,Kubelet会根据重启策略的设置进行相应的操作。
Pod的重启策略有Always、OnFAIlure、Never三种,Default为Always。
Always:当容器失效时,由Kubelet自动重启该容器
OnFailure:当容器终止运行且退出码不为0时,由Kubelet自动重启该容器。
Never:不论容器运行状态如何,Kubelet都不会重启该容器。
图3 示例Pod在G行Prometheus的可视化平台Grafana上的重启情况,红线为阈值
重启策略明确了,那么是靠谁来完成重启的呢?是靠控制器来实现的。
在讲述控制器之前,先考虑这样一个场景:Pod没有副本,如果这个Pod出现问题,上面跑的容器应用也就无法运转了,服务也就中断了。所以在K8s中,在创Pod之初,就是以多副本的形式创建的。这种负责Pod副本的创建、Pod重启、调度及全生命周期自动控制的组件叫做控制器(也叫工作负载,即workload)。
控制器根据不同的功能分为Replicaset与Deployment、 DaemonSet、StatefulSet、Job和Cronjob几种。这几种控制器G行都有使用,并且在G行自建的容器统一管理平台进行了部署。这个平台融合管理了云上所有的应用相关容器资源,提高了运维效率。
图4 G行容器统一管理平台登录首页
图片
图5 Deployment控制器下两种Pod滚动升级方式,分别是Recreate和Rolling update,各适用于大版本和小版本
ReplicaSet(简称rs)是Replication Controller的升级版,是副本集的意思,用于保证K8s集群中有指定数量的Pods副本在运行③。一般不单独使用,而是作为Deployment理想状态的参数使用。且拥有集合式的标签选择器,可以选择多个标签。
Deployment被称作是副本控制集(即rs)的控制器,通过为应用程序创建一个或多个rs以管理应用程序的多个版本。通过每个副本控制集(rs)的模板和副本保持的能力来管理无状态应用的工作负载(应用程序集群),比如web集群。
图6 展示了DaemonSet这种类型的控制器的Pods部署
DaemonSet是特殊类型的Deployment,在集群中的全部或者部分节点上,每个节点上有且仅有一份Pod的副本资源在运行。比如系统的监控、日志的收集、分布式存储、网络的代理等,都需要每个成员节点上有且只有一个Pod。例如:每个Node上只需要运行一个日志采集程序Logstach,或者只运行一个性能监控程序Prometheus Node Exporter。
而且后续加入集群的新的节点也会自动创建一个相同的Pod对象。管理员也可以使用Nodeselector(节点选择器)配合节点Label指定仅在部分具有特定特征的节点上运行指定的Pod对象。
图7 有状态集在一个Pod故障被杀死,并重建新Pod的情况
用来管理有状态应用的工作负载(应用程序集群),管理Pods集合的部署和伸缩,并为这些Pods提供持久存储和持久标识符。与Depolyment类似,Statefulset管理基于相同容器模板的一组Pods。但与Depolyment不同的是,Statefulset为每个Pod维护了一个有粘性的ID。这些Pods是基于相同的模板,但是不能相互替换,即无论怎么调度,每个Pod都有一个永久不变的ID。
图8 Job和Cronjob在完成工作后回收算力的场景展示
Job是用来定义并启动一个批处理任务,是单次性作业控制器。这任务通常并行或者串行启动多个计算进程去处理一批工作项,即work item,工作项处理完成后,整个批处理任务结束。比如Hadoop的离线数据处理、视频解码、或者HPC业务等,都需要很多个节点提供一个集中式的大算力。与传统算力相比,Job控制器能在保留结果的同时,快速回收算力,因为一个Job完成后,就会立即杀死使用的Pod。
Cronjob控制器用于周期性调度Job控制器。传统环境使用的一般用到的是7*24小时不间断的备份服务器,现在可以使用Cronjob控制器周期性的起一个Job作业,做完后,保留结果,杀死Pod,回收算力。Cronjob基本照搬了linux操作系统的周期性任务Crontab,用minutes、hours、dayofmonth、month、dayofweek来进行定义。
需要注意,一些控制器对Pod的重启策略要求,G行也是按如下执行的:
Replicaset或者DaemonSet:必须设置为always,需要保证该容器持续运行。
Job:onFailure或Never,确保容器执行完成后不再重启。
Pod对Node有特定要求的场景:
Node节点的配置是多种类型的,比如有的安装了SSD磁盘,有的没有。有的是AMD64的,有的是AMD32的。如果想着把新建的Pod运行在AMD64的Node上,该怎么做呢。下面是Pod的Yaml配置:
图片
Pod之间相互依存或者互斥的场景:
现在想新建这么两个Pod,有互相依存关系,需要放到同一个Region内,该如何实现呢。像这种互为亲和或者互斥关系的Pod是通过在Yaml文件中增加TopologyKey属性,来声明目标拓扑内的Pod是否在一起的。下面是一个亲和的例子,目标Pod的标签值是App:”Nginx”。亲和Pod的标签值也是一样。增加的TopologyKey值为topology.kubernetes.io/region,意味着这两个Pod给配置到了同一个Region下。
图片
这些相互依赖或者相互之间频繁调用的Pod,需要尽可能的部署在同一个Zone、机房、机架、Node节点。反之,就需要让这些Pod尽可能的互相远离。简而言之,就是Pod之间在同一个拓扑域中共存或互斥。拓扑域指由相同地理空间中的几个Node节点组成。
一些常规的拓扑域有:
kubernetes.io/hostname;topology.kubernetes.io/region;topology.kubernetes.io/zone。
在企业实际在使用容器这类资源的时候,除了技术本身,要考虑的其他问题也会很多。企业管理的容器有千千万万,出于效率考虑,对于有特殊需求的容器如何进行批量创建和管理呢,这就需要在统一管理平台按照相应的模板进行创建与维护。在Pod进行重建后,如何保证客户端应用屏蔽这些Pod IP地址的变化及数量的变化呢,这就需要通过定义Pod上层的Service进行保障。不同的应用系统有自身的架构特点,在进行控制器配置的时候如何进行考量呢,需要选择哪种控制器呢,这就需要进行交付前的资源配置梳理。等等问题。总之,在考虑到基本的技术特性的同时,再应用系统自身特点相结合,才能够将技术的最大价值加以发挥。
① 容器的状态一共有五个:created(已创建)、running(运行中)、paused(暂停)、exited(停止)、dead(死亡)、restarted(重启中)、removing(迁移中)。②每种探测方式,还需要额外设置initialDelaySeconds和timeoutSeconds这两个参数。initialDelaySeconds表示容器启动后进行首次健康检查的等待时间,单位是秒。timeoutSeconds表示健康检查请求发送后等待响应的超时时间,单位是秒。如果超时,则Kubelet认为容器无法提供服务,会重启该容器。③例如:Pod所在节点发生宕机,K8s就会第一时间观察到这个故障,并自动创建一个新Pod对象,将其调度到其他合适的节点上,K8s会实时监控集群中目标Pod的副本数量,并尽力与Deployment中声明的Replicas数量保持一致。