health check#

当我们基于镜像启动一个容器的时候,容器在什么时候退出,又在什么时候运行?事实上只要这个容器的主进程不是运行在后台,也没有停止,那么这个容器就不会停止。

20190114

容器在判断容器正常与否,并不会根据容器是否能够正常服务,而仅仅是看容器主进程是否运行着,因此这种判断机制并不能说明容器就是健康的。

那么我们就可以使用curl,或者wget,向主页发起请求,如果主页内容状态是对的,或者内容是我们期望的来判断是否运行正常,这样的精度才是我们想要的

health check并不是检测一次就是ok的,他是一个周期的持续性的检测

当容器指定了运行状况检查时,除了正常状态外,它还具有运行状况。这个状态最初是starting。每当健康检查通过时,它就会变成healthy(以前的状态)。经过一定数量的连续失败后,它就变成了unhealthy

start period:容器在启动的时候,可能需要一定的准备的初始化时间,如果容器被run就开始检测,那如果容器也没有准备妥当,那此时检测肯定是失败的。那么就需要给容器一个启动的准备时间来做初始化。而这个start-period就是。

检测发出后返回状态值:

例如,要检查每五分钟左右网络服务器能够在三秒钟内为网站的主页面提供服务:

HEALTHCHECK --interval=5m --timeout=3s \
  CMD curl -f http://localhost/ || exit 1

这样以来就不依赖进程是否运行来判断,而是判断访问是否正常来判断

接着上面的Dockerfile,添加一行,时间均是3s,方便测试:

HEALTHCHECK --interval=3s --timeout=3s --start-period=3s CMD wget -O - -q http://${IP:-0.0.0.0}:${NGPORT:-80}
[marksugar@www.linuxea.com /data/linuxea3]$ cat Dockerfile 
FROM nginx:1.14.2-alpine
LABEL maintainer="linuxea.com"
ENV NGINX_ROOT="/data/wwwroot"

ADD entrypoint.sh /bin/entrypoint.sh
ADD index.html ${NGINX_ROOT}/

EXPOSE 8080/tcp 80/tcp

HEALTHCHECK --interval=3s --timeout=3s --start-period=3s CMD wget -O - -q http://${IP:-0.0.0.0}:${NGPORT:-80} || exit 1
#HEALTHCHECK --start-period=3s CMD wget -O - -q http://${IP:-0.0.0.0}:${NGPORT} || exit 1

CMD ["/usr/sbin/nginx","-g","daemon off;"]
ENTRYPOINT ["/bin/entrypoint.sh"]

而后build

[root@linuxEA /data/linuxea3]$ docker build -t marksugar/nginx:v3 .

run起来后,就会发起wget -O - -q http://${IP:-0.0.0.0}:${NGPORT:-80},说明状态健康

[marksugar@www.linuxea.com /data/linuxea3]$ docker run --name linuxea  --rm -e "NGPORT=8080" marksugar/nginx:v3
127.0.0.1 - - [08/Dec/2018:15:23:15 +0000] "GET / HTTP/1.1" 200 28 "-" "Wget" "-"
127.0.0.1 - - [08/Dec/2018:15:23:18 +0000] "GET / HTTP/1.1" 200 28 "-" "Wget" "-"
127.0.0.1 - - [08/Dec/2018:15:23:21 +0000] "GET / HTTP/1.1" 200 28 "-" "Wget" "-"
127.0.0.1 - - [08/Dec/2018:15:23:24 +0000] "GET / HTTP/1.1" 200 28 "-" "Wget" "-"
127.0.0.1 - - [08/Dec/2018:15:23:27 +0000] "GET / HTTP/1.1" 200 28 "-" "Wget" "-"
127.0.0.1 - - [08/Dec/2018:15:23:30 +0000] "GET / HTTP/1.1" 200 28 "-" "Wget" "-"
127.0.0.1 - - [08/Dec/2018:15:23:33 +0000] "GET / HTTP/1.1" 200 28 "-" "Wget" "-"
127.0.0.1 - - [08/Dec/2018:15:23:36 +0000] "GET / HTTP/1.1" 200 28 "-" "Wget" "-"

此时我将端口修改为81,而81并没有监听,wget将失败,也就没有了wget日志,而docker ps 将能看到状态为unhealthy,说明不健康

[marksugar@www.linuxea.com /data/linuxea3]$ docker ps -a
CONTAINER ID        IMAGE                        COMMAND                  CREATED             STATUS                      PORTS               NAMES
13fcf1bd14c4        marksugar/nginx:v4           "/bin/entrypoint.sh …"   19 seconds ago      Up 18 seconds (unhealthy)   80/tcp, 8080/tcp    linuxea

查看

docker inspect --format "{{json .State.Health }}" <container name> | jq

示例2#

我创建了一个小的node.js web服务器,只需用'OK'响应任何请求。但是,服务器还有一个切换开/关状态的开关,而不会实际关闭服务器的进程。这是它的代码:

"use strict";
const http = require('http');

function createServer () {
    return http.createServer(function (req, res) {
        res.writeHead(200, {'Content-Type': 'text/plain'});
        res.end('OK\n');
    }).listen(8080);
}

let server = createServer();

http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/plain'});
    if (server) {
        server.close();
        server = null;
        res.end('Shutting down...\n');
    } else {
        server = createServer();
        res.end('Starting up...\n');
    }
}).listen(8081);

当服务器处于开启状态时,它将在端口8080处侦听并返回OK到任何进入该端口的请求。curl 8081端口将关闭服务器,另一个curl将再次启用它:

[marksugar@www.linuxea.com ~]$ node server.js
# switch to another terminal
[marksugar@www.linuxea.com ~]$ curl 127.0.0.1:8080
# OK
[marksugar@www.linuxea.com ~]$ curl 127.0.0.1:8081
# Shutting down...
[marksugar@www.linuxea.com ~]$ curl 127.0.0.1:8080
# curl: (7) Failed to connect to 127.0.0.1 port 8080: Connection refused
[marksugar@www.linuxea.com ~]$ curl 127.0.0.1:8081
# Starting up...
[marksugar@www.linuxea.com ~]$ curl 127.0.0.1:8080
# OK

现在让我们将server.js放入带有运行状况检查的Dockerfile中,构建一个镜像并将其作为容器启动:

FROM node
COPY server.js /
EXPOSE 8080 8081
HEALTHCHECK --interval=5s --timeout=10s --retries=3 CMD curl -sS 127.0.0.1:8080 || exit 1
CMD [ "node", "/server.js" ]
[marksugar@www.linuxea.com ~]$ docker build . -t server:latest
# Lots, lots of output
[marksugar@www.linuxea.com ~]$ docker run -d --rm -p 8080:8080 -p 8081:8081 server
# ec36579aa452bf683cb17ee44cbab663d148f327be369821ec1df81b7a0e104b
[marksugar@www.linuxea.com ~]$ curl 127.0.0.1:8080
# OK

创建的容器ID开头ec3,这应该足以在以后识别它,所以现在我们可以跳转到健康检查

监视容器运行状况#

Docker用于检查容器健康状况的主要命令是 docker inspect。它产生巨大的JSON作为响应,但我们感兴趣的唯一部分是它的 State.Health属性:

[marksugar@www.linuxea.com ~]$ docker inspect ec3 | jq '.[].State.Health'
#{
#  "Status": "healthy",
#  "FailingStreak": 0,
#  "Log": [
#    {
#      "Start": "2017-06-27T04:07:03.975506353Z",
#      "End": "2017-06-27T04:07:04.070844091Z",
#      "ExitCode": 0,
#      "Output": "OK\n"
#    },
#...
}

当前状态是“健康的”,我们甚至可以看到健康检查记录 Log 收集。但是,在调用端口8081并等待3 * 5秒(允许三次检查失败)之后,镜像将会改变。

[marksugar@www.linuxea.com ~]$ curl 127.0.0.1:8081
# Shutting down...
# 15 seconds later
[marksugar@www.linuxea.com ~]$ docker inspect ec3 | jq '.[].State.Health'
#{
#  "Status": "unhealthy",
#  "FailingStreak": 4,
#  "Log": [
#  ...
#   {
#      "Start": "2017-06-27T04:16:27.668441692Z",
#      "End": "2017-06-27T04:16:27.740937964Z",
#      "ExitCode": 1,
#      "Output": "curl: (7) Failed to connect to 127.0.0.1 port 8080: Connection refused\n"
#    }
#  ]
#}

等了15秒多一点,所以健康检查连续4次失败(FailingStreak)。正如预期的那样,docker的状态确实变为“不健康”。

但是,只要至少一次健康检查成功,Docker就会将容器恢复到“健康”状态:

[marksugar@www.linuxea.com ~]$ curl 127.0.0.1:8081
# Starting up...
[marksugar@www.linuxea.com ~]$ docker inspect ec3 | jq '.[].State.Health.Status'
# "healthy"

使用Docker事件检查运行状况#

除了直接检查容器状态外,我们还可以听取docker events

[marksugar@www.linuxea.com ~]$ docker events --filter event=health_status
# 2017-06-27T00:23:03.691677875-04:00 container health_status: healthy ec36579aa452bf683cb17ee44cbab663d148f327be369821ec1df81b7a0e104b (image=server, name=eager_swartz)
# 2017-06-27T00:23:23.998693118-04:00 container health_status: unhealthy ec36579aa452bf683cb17ee44cbab663d148f327be369821ec1df81b7a0e104b (image=server, name=eager_swartz)

Docker事件可能有点繁琐,这就是我必须使用的原因--filter。命令本身不会立即退出并保持运行,在事件发生时打印出事件。

健康状况和Swarm服务#

为了尝试运行状况检查如何影响Swarm服务,我暂时将本地Docker实例转换为Swarm模式docker swarm init,现在我可以执行以下操作:

[marksugar@www.linuxea.com ~]$ docker service create -p 8080:8080 -p8081:8081 \
    --name server \
    --health-cmd='curl -sS 127.0.0.1:8080' \
    --health-retries=3 \
    --health-interval=5s \ 
    server
#unable to pin image server to digest: errors:
#denied: requested access to the resource is denied
#unauthorized: authentication required

#ohkvwbsk06vkjyx69434ndqij

这使用本地构建的server 镜像将新服务放入Swarm中。Docker对镜像是本地的并返回一堆错误这一事实并不满意,但最终确实返回了新创建的服务的ID:

docker service ls
#ID            NAME    MODE        REPLICAS  IMAGE
#ohkvwbsk06vk  server  replicated  1/1       server

curl 127.0.0.1:8080 将再次工作,发送请求8081将像往常一样关闭服务。但是,这次短时间后端口8080将在没有明确启用服务器的情况下再次开始工作。事情是,只要Swarm注意到容器变得不健康,因此整个服务不再满足所需状态(“运行”),它就会完全关闭容器并启动一个新容器。通过检查我们server 服务的任务集合,我们实际上可以看到它的痕迹 :

[marksugar@www.linuxea.com ~]$ docker service ps server
#ID            NAME          IMAGE   NODE  DESIRED STATE  CURRENT STATE              ERROR                             PORTS
#mt67hkhp7ycr  server.1      server  moby  Running        Running 50 seconds ago                                       
#pj77brhfhsjm   \_ server.1  server  moby  Shutdown       Failed about a minute ago  "task: non-zero exit (137): do…"

每个Swarm容器都有一个分配给它的任务。当容器死亡时,相应的任务也会关闭,Swarm会创建一对新任务和一个容器。docker service ps 显示给定服务和容器的整个任务链死亡和复活。在我们的特定情况下server,id的初始任务pj77brhfhsjm被标记为失败,docker inspect 甚至说明原因:

[marksugar@www.linuxea.com ~]$ docker inspect pj77 | jq '.[].Status.Err'
# "task: non-zero exit (137): dockerexec: unhealthy container"

“unhealthy container”,这就是原因。但最重要的是,整个服务自动从不健康的状态恢复,几乎没有明显的停机时间

Docker运行状况检查是一个小功能,允许将shell命令附加到容器并使用它来检查容器的内容是否足够活跃。对于Docker引擎独立模式下的容器,它只是为它添加健康/不健康的属性(health_status当值更改时加上docker事件),但在Swarm模式下,它实际上将关闭有故障的容器并创建一个停机时间非常短的新容器。