如何检测Kubernetes健康
2017-08-14 18:53:59
Angela
  • 访问次数: 261
  • 注册日期: 2017-03-15
  • 最后登录: 2017-12-05
  • 当前积分: 1309

原创 2017-08-14 吴龙辉 Docker

业务的正常是第一优先保障,所以健康检查是一个重要能力。Kubernetes作为容器编排平台,对于容器以及容器所承载的业务,需要保证其健康,并且在异常情况下能够进行处理。这次分享将讨论Kubernetes的健康检查策略。


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支持以下三种检查方法。


1. ExecAction:在容器中执行指定的命令进行检查,当命令执行成功(返回码为0),检查成功。


示例:


exec:
        command:
        - cat
        - /tmp/health


2. TCPSocketAction:对于容器中的指定TCP端口进行检查,当TCP端口被占用,检查成功。


示例:

tcpSocket:
        port: 8080


3. 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:请问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%的业务正常,所以目前就是需要权衡,这部门我们也在细化中。

Angela 最后编辑, 2017-08-14 18:58:04