最近在项目中发现一些容器虽然活着但已经不具备提供对外服务能力,导致业务出现异常,而且运维人员也不能准确的定位问题。在分布式系统中,利用健康检查机制来检查服务的可用性,防止其他服务调用时出现异常特别重要,可以帮助运维人员快速的定位问题,而且结合一些容器运维工具可以让容器不健康时自动恢复,增加平台的稳定性和减少宕机时间。下面的部分将列举一些不同运维场景下的常用的容器健康检测机制,最后结合自己的项目如何实践容器的健康检测和自动恢复。
容器健康检测
进程级的健康检测
对于容器而言,最简单的健康检查是进程级的健康检查,即检验进程是否存活。Docker Daemon会自动监控容器中的PID1进程,如果docker run命令中指明了restart policy,可以根据策略自动重启已结束的容器。在很多实际场景下,仅使用进程级健康检查机制还远远不够。比如,容器进程虽然依旧运行却由于网络连接的原因无法连接业务数据库等导致业务异常,这样的问题是无法通过进程监控发现的。因此,进程级别的健康检测应该是最低级别的容器健康检测。
Docker原生健康检测机制
Docker提供了原生的健康检测机制,可以在Dockerfile中声明应用自身的健康检测配置或者在Docker run中指定健康检测命令。
HEALTHCHECK
在dockerfile中的HEALTHCHECK 指令声明了健康检测命令,用这个命令来判断容器主进程的服务状态是否正常,从而比较真实的反应容器实际状态。HEALTHCHECK命令格式及参数如下:
HEALTHCHECK [选项] CMD [命令]
//示例:HEALTHCHECK CMD curl 'http://localhost'
HEALTHCHECK NONE
//说明:如果基础镜像中有HEALTHCHECK指令,可以通过此方式屏蔽基础镜像中的健康检测
下面是HEALTHCHECK选项列表:
选项 | 作用 | 示例 | 默认值 |
---|---|---|---|
interval | 检测间隔时间 | 5s | 30s |
timeout | 健康检查命令运行 超时时间,如果超 过这个时间,本次 健康检查就被视为 失败 |
30s | 30s |
retries | 重试指定次数仍失败,容器将被认为 unhealthy |
3 | 3 |
start-period | 应用启动期间的健康 检测不计入统计次数, 但仍会发生检测 |
10s | 0s |
HEALTHCHECK的CMD命令和其他CMD命令一样,支持SHELL和EXEC两种格式。命令的返回值有以下几种:
返回值 | 意义 |
---|---|
0 | healthy |
1 | unhealthy |
2 | 重试指定次数仍失败,容器将被认为unhealthy |
假设我们有个镜像是基于Nginx部署的一个网站,我们可以使用比较简单的方法检测Nginx的健康状态,在dockerfile中增加HEALTHCHECK如下:
HEALTHCHECK --interval=30s --timeout=2s --start-period=30s --retries=3 CMD curl --silent --fail http://localhost/ver.txt || exit 1
除了在dockerfile中使用HEALTHCHECK来检测容器的健康状态外,我们也可以在docker run中实现容器的健康检测。参数同dockerfile中的完全一致,参入如下:
health-cmd
health-interval
health-retries
health-start-period
health-timeout
no-healthcheck
K8S容器健康检测
Pod是K8S的最小管理单元,因此,K8S提供了对Pod的健康状态检测,没有对Pod的单个容器进行健康检测机制,kubernetes提供了两类探针(Probe)来执行对Pod的健康状态检测:
LivenessProbe探针
用于判断容器是否存活,即Pod是否为running状态,如果LivenessProbe探针探测到容器不健康,则kubelet将kill掉容器,并根据容器的重启策略是否重启,如果一个容器不包含LivenessProbe探针,则Kubelet认为容器的LivenessProbe探针的返回值永远成功。
ReadinessProbe探针:
用于判断容器是否启动完成,即容器的Ready是否为True,可以接收请求,如果ReadinessProbe探测失败,则容器的Ready将为False,控制器将此Pod的Endpoint从对应的service的Endpoint列表中移除,从此不再将任何请求调度此Pod上,直到下次探测成功。
每类探针都支持三种探测方法:
exec:通过执行命令来检查服务是否正常,针对复杂检测或无HTTP接口的服务,命令返回值为0则表示容器健康。
httpGet:通过发送http请求检查服务是否正常,返回200-399状态码则表明容器健康。tcpSocket:通过容器的IP和Port执行TCP检查,如果能够建立TCP连接,则表明容器健康。
探针探测的结果有以下三者之一:
Success:Container通过了检查。
Failure:Container未通过检查。Unknown:未能执行检查,因此不采取任何措施。
下面是一个以tcpSocket的Pod yaml文件,包含readinessProbe和livenessProbe两个探针:
apiVersion: v1
kind: Pod
metadata:
name: goproxy
labels:
app: goproxy
spec:
containers:
- name: goproxy
image: k8s.gcr.io/goproxy:0.1
ports:
- containerPort: 8080
readinessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 15
periodSeconds: 20
探针相关的属性如下:
initialDelaySeconds:Pod启动后延迟多久才进行检查,单位:秒。
periodSeconds:检查的间隔时间,默认为10,单位:秒。
timeoutSeconds:探测的超时时间,默认为1,单位:秒。
successThreshold:探测失败后认为成功的最小连接成功次数,默认为1,在Liveness探针中必须为1,最小值为1。
failureThreshold:探测失败的重试次数,重试一定次数后将认为失败,在readiness探针中,Pod会被标记为未就绪,默认为3,最小值为1。
容器自动恢复
Docker提供的restart policy可以自动重启退出的容器,K8S提供了自动杀死不健康的Pod并重新创建新的Pod,但是对于不健康的容器Docker没有提供自动重启机制,GitHub上提供了一个Auto Heal容器可以保证自动重启本机上的unhealthy容器。Autoheal容器是一个独立的容器,它可以监控本机上的某一个容器或者所有容器,根据容器的健康状态来自动重启不健康的容器。注意两个参数,AUTOHEAL_INTERVAL和AUTOHEAL_START_PERIOD,5分钟后开始检测容器的健康,并每次间隔5秒。安装方法如下:
docker run -d \
--name autoheal \
--restart=always \
-e AUTOHEAL_CONTAINER_LABEL=all \
-e AUTOHEAL_INTERVAL=5 \
-e AUTOHEAL_START_PERIOD=300 \
-v /var/run/docker.sock:/var/run/docker.sock \
willfarrell/autoheal
由于我们的项目是私有云部署而且目前没有容器化编排工具,比如K8S和SWARM等,因此我们利用auto heal容器的自动恢复unhealthy容器的功能,设计了如下的部署策略以最经济的方式最大限度的保证我们的程序能更加稳定的运行。下面是我们的部署架构图
平台健康检测
平台健康检测尤为重要,一般它与具体的业务相关。首先需要分析与平台相关的健康有哪些因素,在我们的项目中,平台相关的因素有Redis,Cassandra,PostgreSQL,ActiveMQ等,因此,我们开发了一个健康检测service,来逐个的分析是否能正常的连接这些关键组件。Health service需要考虑安全,因为每个Service都有认证授权,但是在docker容器的HEALTHCHECK不能提供额外的机制来进行认证授权,所以需要为此service屏蔽认证授权,由于此service是只提供给Docker的HEALTHCHECK指令,不允许不会对外提供服务。
动态资源服务
我们使用Nodejs提供的动态资源服务,因此在项目中直接curl http://localhost来检测,如果成功,说明nodejs正常启动并可以提供服务
静态资源服务
前端程序我们使用的是Nginx部署,因此在项目中直接curl http://localhost/ver.txt,如果成功,说明静态资源服务器的nginx正常工作
Redis的健康检测
Redis的健康检测,Redis的健康检测是执行healthcheck命令,命令如下:
#!/bin/bash
set -eo pipefail
if ping="$(redis-cli -a "10d9a99851a411cdae8c3fa09d7290df192441a9" ping)" && [ "$ping" = 'PONG' ]; then
exit 0
fi
exit 1
Cassandra的健康检测
Cassandra的健康检测,Cassandra的健康检测是执行healthcheck命令,命令如下:
#!/bin/bash
set -eo pipefail
host="$(hostname --ip-address || echo '127.0.0.1')"
if cqlsh -u cassandra -p cassandra "$host" < /dev/null; then
exit 0
fi
exit 1
写在最后
对于已经在项目中使用K8S或者Swarm来进行容器化编排的可以利用K8S提供探针来自动重启不在运行状态或者不具备对外提供服务的POD机制保证系统的高可用,减少系统的宕机时间,但是K8S只能检测Pod,不能提供对原生态的Docker容器检测,现在很多云服务提供商已经具备此能力。对于那些对系统宕机时间要求不是十分苛刻,并且也还没有用使用容器编排工具部署自己的系统的用户,使用auto heal也是一种经济方案,部署和运维都比较简单,但不具备自动扩容的能力,可以比较简单的保证单个容器不健康后自动恢复,减少系统的宕机时间,降低运维成本。