【编者的话】业务的正常是第一优先保障,所以健康检查是一个重要能力。Kubernetes作为容器编排平台,对于容器以及容器所承载的业务,需要保证其健康,并且在异常情况下能够进行处理。这次分享将讨论Kubernetes的健康检查策略。
【3 天烧脑式基于Docker的CI/CD实战训练营 | 北京站】本次培训围绕基于Docker的CI/CD实战展开,具体内容包括:持续集成与持续交付(CI/CD)概览;持续集成系统介绍;客户端与服务端的 CI/CD 实践;开发流程中引入 CI、CD;Gitlab 和 CI、CD 工具;Gitlab CI、Drone 的使用以及实践经验分享等。
Kubernetes的监控检查支持
在Kubernetes中Pod是最核心的概论,Pod是一个或者多个容器的组合,Pod包含的容器运行在同一台宿主机上,这些容器使用相同的网络命名空间、IP地址和端口,相互之间能通过localhost来发现和通信。另外,这些容器还可共享一块存储卷空间。在Kubernetes中创建、调度和管理的最小单位是Pod,而不是容器,Pod通过提供更高层次的抽象,提供了更加灵活的管理模式,但是同时也增加了健康检查的复杂性。
Pod的生命周期管理
Kubernetes提供了一整套对于Pod的状态机制和生命周期管理,Pod的本质是一组容器,Pod的状态便是容器状态的体现和概括,同时容器的状态变化会影响Pod的状态变化,触发Pod的生命周期阶段转换。
在使用docker run运行容器的时候,首先会下载容器镜像。下载成功后运行容器,当容器运行结束退出后(包括正常和异常退出),容器终止,这是一个容器的生命周期过程。相应的,Kubernetes中对于Pod中的容器进行了状态的记录,其中每种状态下包含的信息如下所示。
- Waiting:容器正在等待创建,比如正在下载镜像。
- Running:容器已经创建,并且正在运行。
- Terminated:容器终止退出。
Pod的生命周期可以简单描述为:首先Pod被创建,紧接着Pod被调度到Node进行部署运行。Pod是非常忠诚的,一旦被分配到Node后,就不会离开这个Node,直到它被删除,生命周期完结。
Pod的生命周期被定义为以下几个阶段。
- Pending:Pod已经被创建,但是一个或者多个容器还未创建,这包括Pod调度阶段,以及容器镜像的下载过程。
- Running:Pod已经被调度到Node,所有容器已经创建,并且至少一个容器在运行或者正在重启。
- Succeeded:Pod中所有容器正常退出。
- Failed:Pod中所有容器退出,至少有一个容器是一次退出的。
Pod被创建成功后,首先会进入Pending阶段,然后被调度到Node后运行,进入Running阶段。如果Pod中的容器停止(正常或者异常退出),那么Pod根据重启策略的不同会进入不同的阶段,举例如下。
Pod是Running阶段,含有一个容器,容器正常退出:
如果重启策略是Always,那么会重启容器,Pod保持Running阶段。
如果重启策略是OnFailure,Pod进入Succeeded阶段。
如果重启策略是Never,Pod进入Succeeded阶段。
Pod是Running阶段,含有一个容器,容器异常退出:
如果重启策略是Always,那么会重启容器,Pod保持Running阶段。
如果重启策略是OnFailure,Pod保持Running阶段。
如果重启策略是Never,Pod进入Failed阶段。
Pod是Running阶段,含有两个容器,其中一个容器异常退出:
如果重启策略是Always,那么会重启容器,Pod保持Running阶段。
如果重启策略是OnFailure,Pod保持Running阶段。
如果重启策略是Never,Pod保持Running阶段。
Pod是Running阶段,含有两个容器,两个容器都异常退出:
如果重启策略是Always,那么会重启容器,Pod保持Running阶段。
如果重启策略是OnFailure,Pod保持Running阶段。
如果重启策略是Never,Pod进入Failed阶段。
一旦被分配到Node,Pod就不会离开这个Node,直到被删除。删除可能是人为地删除,或者被Replication Controller删除,也有可能是当Pod进入Succeeded 或者Failed 阶段过期,被Kubernetes清理掉。总之Pod被删除后,Pod的生命周期就算结束,即使被Replication Controller进行重建,那也是新的Pod,因为Pod的ID已经发生了变化,所以实际上Pod迁移,准确的说法是在新的Node上重建Pod。
健康检查Probe机制
对于Pod是否健康,即Pod中的容器是否健康,默认情况下只是检查容器是否正常运行。但有时候容器正常运行不代表应用健康,有可能应用的进程已经阻塞住无法正常处理请求,所以为了提供更加健壮的应用,往往需要定制化的健康检查。
除此之外,有的应用启动后需要进行一系列初始化处理,在初始化完成之前应用是无法正常处理请求的。如果应用初始化需要较长时间,而实际上容器创建的时间是可以忽略不计的。
默认情况下,Kubernetes发现容器创建成功并运行,就会认为其准备就绪,真实情况是容器里的应用可能还处于初始化阶段,无法正常接受请求。如果用户访问就会得到错误响应,这不是我们希望看到的情况。同样的,我们需要更加精确的检查机制来判断Pod和容器是否准备就绪,从而让Kubernetes判断是否分发请求给Pod。
针对这些需求,Kubernetes中提供了Probe机制,有以下两种类型的Probe。
- Liveness Probe:用于容器的自定义健康检查,如果Liveness Probe检查失败,Kubernetes将杀死容器,然后根据Pod的重启策略来决定是否重启容器。
- Readiness Probe:用于容器的自定义准备状况检查,如果Readiness
Probe检查失败,Kubernetes将会把Pod从服务代理的分发后端移除,即不会分发请求给该Pod。
Probe支持以下三种检查方法。
- ExecAction:在容器中执行指定的命令进行检查,当命令执行成功(返回码为0),检查成功。
示例:
` exec:
command:
- cat
- /tmp/health`复制代码
- TCPSocketAction:对于容器中的指定TCP端口进行检查,当TCP端口被占用,检查成功。
示例:
{{{ tcpSocket:
port: 8080}}}
- HTTPGetAction: 发生一个HTTP请求,当返回码介于200~400之间时,检查成功。
示例:httpGet: path: /healthz port: 8080
结合Supervisor优化问题排查
一般情况下我们是一个容器运行一个进程,即将应用进程的启动命令作为容器的CMD,这样一来当应用进程退出的时候,容器也就退出,可以第一时间发现异常,我们是统一设置 Pod中容器的重启策略为Always, Kubernetes中重启就是重新创建容器,毕竟新建一个容器的成本很低。所以当容器退出的时候,Kubernetes就会重建容器,这样一来就无法查看应用的第一事发现场,因为旧的容器已经被停止了,极端情况下,容器一直在重建,根本无法登陆到容器去排除;因此,我们希望对容器内应用的健康进行监控,并且根据监控情况,做出保留故障现场和故障恢复等策略,为此我们考虑结合Supervisor进行优化。
Supervisor 是由 Python 语言编写、基于 Linux 操作系统的一款服务器管理工具,用于监控服务器的运行,发现问题能立即自动预警及自动重启等。在Linux系统启动之后,第一个启动的用户态进程是/sbin/init ,它的PID是1,其余用户态的进程都是init进程的子进程。Supervisor在docker容器里面充当的就类似init进程的角色,其它的应用进程都是Supervisor进程的子进程。
Supervisor维护着进程的状态机,并且支持着丰富的重启策略。
- autostart=true; 如果是true的话,子进程将在supervisord启动后被自动启动默认就是true 。
- autorestart=unexpected; 这个是设置子进程挂掉后自动重启的情况,有三个选项,false、unexpected和true。如果为false的时候,无论什么情况下,都不会被重新启动,如果为unexpected,只有当进程的退出码不在下面exitcodes里面定义的退出码的时候,才会被自动重启。当为true的时候,只要子进程挂掉,将会被无条件的重启 startsecs=1;
这个选项是子进程启动多少秒之后,此时状态如果是running,则我们认为启动成功了。 - startretries=3; 当进程启动失败后,最大尝试启动的次数。。当超过次数后,supervisor将把此进程的状态置为FAIL
- exitcodes=0,2; 和autorestart=unexpected对应。exitcodes里面的定义的退出码是expected的。
这样一来当进程退出的时候,我们可以利用Supervisor去重启进程,并且当进程已经无法正常运行的时候,还可以保留容器现场,方便登录容器进行问题定位。要注意的是我们使用Supervisor的初衷并不是去支持一个容器运行多个进程,本质上我们要求一个容器只运行一个应用进程(比如Tomcat),然后可以运行几个附属进程(crontab、rsyslog)。
另外当应用异常的时候,更重要的是要能够快速恢复。这时候我们就要利用Kubernetes的Readiness Probe去对Pod进行健康检查,检查的维度最好是以上层业务的健康性为标准,比如业务提供健康状态的Restful API /health。
当Readiness Probe检查失败的时候,Pod就设置为NotReady状态,为了保证业务是不中断的,我们可以新建一个Pod出来顶替,当然如果新建的Pod仍是异常,就没必要一直新建了。
整体来说利用Supervisor和Kubernetes Readiness Probe来优化问题排查的支持,逻辑图:
Q&A
Q:请问Pod的状态是crashbackoff 除了下载镜像失败有哪些可能? 下载的镜像能否指定registry? pod如果有一个容器是exit 0, 那是否就是您之前提到的succeed? 使用livenessProbe检测失败的是failed还是crashbackoff?
A:Crashbackoff大部分情况下是容器的启动命令失败,比如tomcat启动失败了,初学者比较容器犯的错误是CMD的命令是一个非阻塞命令,这样容器一运行就马上退出了。下载的镜像可以指定registry,根据镜像的命令来的,比如 test.registry.com/image:version。容器exit 0了,得根据重启策略来判断。而livenessProbe检测是业务层面的检测。
Q:请问readinessProbe检测失败后,是手动Scale添加Pod确保业务稳定还是可以在ReplicationController的yaml里面定义?
A:这部分 Kubernetes并不支持,是我们自己准备开发的功能。就是发现Pod NotReady后,一来保留问题容器,二来新增一个Pod顶替。
Q:请问Supervisord是手动在Pod里面的容器里面添加么? 还是有专门的镜像已经自带?谢谢! 容器出错后收集的现场信息都保存在哪里?
A:Supervisord就安装到容器里面就行了,比如我们是CentOS基础镜像,然后yum install即可。当进程异常的时候,Supervisord可以重启进程并且保证容器不会退出,这样一来就可以登录到容器里面排查问题,信息的话根据组件的情况来定了。
Q:请问,如果一个deployment有三个副本,分别部署再三个Node上,当其中一个Node宕机了,这时候对应的service中的endpoint更新需要一定的时间,用户在这个时间段访问就会有1/3的错误可能,这种情况怎么办?
A:当Pod异常的时候,比如是NotReady,Service的endpoint了马上就会剔除这个Pod了。Kubernetes的实现都是实时watch的。
Q:保留容器现场如果造成多个僵尸容器怎么办?
A:当Pod NotReady的时候,新建Pod顶替,新建的Pod也异常的话,就不能一直重建。然后定位完成后,只能手动去清理Pod了。
Q:用Supervisor来启动服务,应用的日志是打到指定的目录的还是直接std输出然后再处理的啊?
A:应用的日志打印到文件处理。Stdout被Supervisor占用了
Q: 一个容器里面推荐只跑一个进程,对于遇到一个项目要跑多个服务的情况,是每个服务都单独生成一个容器吗?如果是这样的话代码是怎么管理的?每个服务一个分支吗?而且往往开发、测试、生产是不同的分支,这样服务多了的话对于代码管理很麻烦,如果一个容器里面用Supervisor来跑多个进程的话理论上可以,但是显然做法不好。
A:每个服务可以做成一个容器。那这个是管理的成本了,可以通过一些工具和脚本来自动化。至于要不要跑多进程,看实际场景,都是可以的,其实只要保证Pod提供的业务是正常的即可,所以我们用Probe来对业务检查,
Q:请问Kubernetes的应用的日志是怎么管理的,用的网络文件系统还是其他的方式?
A:我们是要求应用的日志全部输出到指定目录,比如/var/log。然后我们针对容器里面的日志目录统一挂载到Ceph存储。
Q:是否可以通过preStop元素在pod failed被移除前执行一些收集现场信息的命令?
A:也是可以的。但是大部分人定位问题还是希望能够登录到容器里面定位,这样是最快最方便的方式。
Q:Supervisor的方式打乱了Kubernetes原来的容器重启方式,可能会带来更大的问题。不如考虑在Kubernetes的基础上修改增强。
A:是个问题。可以说Supervisor就覆盖了Kubernetes的重启方式,但是一方面Supervisor的重启方式更加灵活,另一方面修改Kubernetes的话侵入性比较大。所以我们选择用Supervisor。
Q:你们的应用日志统一挂载存储的话,存储上是为每个容器新建一个目录吗?
A:这部门我们是修改了Kubernetes的代码,为每个pod在宿主机创建一个目录,然后宿主机的这个目录是挂载Cephfs的。
Q:假如APP不能正常进行业务处理(连不上数据等原因),而health check依然正常返回。怎么办?你们会强制要求开发对APP的状态进行管理,在health check里返回吗?
A:这是个好问题。我们会尽量要求应用提供的health接口尽量准确,但是这也很难保证100%的业务正常,所以目前就是需要权衡,这部门我们也在细化中。
以上内容根据2017年08月08日晚微信群分享内容整理。分享人吴龙辉,网宿科技云计算架构师,负责设计开发PaaS平台,《Kubernetes实战》作者。 DockOne每周都会组织定向的技术分享,欢迎感兴趣的同学加微信:liyingjiesa,进群参与,您有想听的话题或者想分享的话题都可以给我们留言。