S6:容器的健康检查以及依赖检查

声明:所有的实验示例大部分来自《learn-docker-in-a-month-of-lunches》的作者Elton Stoneman,但运行结果并不都是照搬,大部分实验结果可能与原书不同


一、健康检查(health checks)和依赖检查(dependency checks)概述

Q--什么是健康检查(health checks)?有什么用?
A--健康检查用于判断一个容器是否正常工作,容器处于运行状态并不代表容器是正常工作的,因为容器表现的行为可能完全不符合预期,这样得容器我们称之为不正常工作的容器,而健康检查就用于这方面的检查。健康检查可以仅仅只是一个普通指令,也可以是一个脚本,一串逻辑,具体什么样的返回结果才算正常,由你自己决定。如果容器被标记为不健康后,会产生一个事件,这样运行该容器的平台就可以根据该事件做出相应的响应,比如启动一个新的容器替换这个不健康的容器。

Q--什么是依赖检查(denpendency checks)?有什么用?
A--依赖检查用于在容器启动时(没错!只在启动时)检查该容器依赖的服务是否已经运行了,如果没有,该容器进入退出状态。作用就是保持容器与容器之间的依赖关系以及启动顺序。依赖检查可以很简单,比如单纯的通过curl指令访问一下目标服务,如果有正确的响应则算通过,当然实际上检测标准应当更加完善才好。

Q--Docker Compose有depends_on设置项设置容器间的依赖关系以及启动顺序,为什么还需要依赖检查?
A--因为Docker Compose仅仅只能在一台机器上控制依赖关系。如果应用太大,就需要被分布在许多台机器上,这时候启动时的依赖关系可就很难维护了。再者强硬的使用依赖关系规定A容器必须运行在B容器之前,这样也不是很好,比如,假设A有50个容器,那么B想要运行则必须等这50个全部运行后才可以运行,然而此时恰恰就有1个容器怎么都运行不起来,那么这段时间B也运行不起来,整个应用在这段时间都是不正常的。若是使用依赖检查的话,只要通过了依赖检查容器就可以启动,而不需要管其他什么乱七八糟的,比如,只要有20个A容器启动了,A服务就"表现"正常了,那么B在启动时就可以通过依赖检查,因此就可以启动了。


二、健康检查
  • HEALTHCHECK指令
//...
# app image
FROM diamol/dotnet-aspnet

ENTRYPOINT ["dotnet", "/app/Numbers.Api.dll"]
HEALTHCHECK CMD curl --fail http://localhost/health

WORKDIR /app
//...

这是某个镜像的Dockerfile中的一部分,健康检查相关的部分就定义在这里,HEALTHCHECK后面接的指令就是用于健康检查的指令。该指令每隔一段时间就执行一次,如果执行的返回值异常超过一定次数,则该容器会被视为不健康的。默认是每30秒执行一次,3次连续异常则被判断为不健康的。

  • 运行一个带健康检查的容器
$ cd diamol/ch08/exercises/numbers
$ docker image build -t ch08-numbers-api -f ./numbers-api/Dockerfile.v2 .
$ docker container run -d -p 8080:80 --health-interval 5s ch08-numbers-api:v2

docker image build命令中的-f选项用于指定创建镜像所需要依据的Dockerfile
docker container run命令中其实可以指定和健康检查的相关的配置,比如:
--health-cmd指定要执行的健康检查的命令
--health-interval指定健康检查命令执行的时间间隔,默认ms
--health-retries指定失败多少次,容器会被标记为不健康的
--health-start-period指定在多少秒后才正式开始计算失败次数
--health-timeout健康检查的超时时间,超时会被认为是检查失败

  • 查看容器的状态
$ docker container ls
CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS                    PORTS                  NAMES
2589f5acff74        ch08-numbers-api:v2   "dotnet /app/Numbers…"   24 minutes ago      Up 24 minutes (healthy)   0.0.0.0:8080->80/tcp   competent_sanderson

在STATUS一栏中能看到(healthy)的状态,则代表该容器是健康的。
因为该应用有一个人为的bug,即访问localhost:8080/rng三次后再访问会报错,所以接下来我们将人为触发这个bug,然后再查看容器的状态

  • 触发bug,查看容器状态
$ curl localhost:8080/rng
$ curl localhost:8080/rng
$ curl localhost:8080/rng
//等待15秒左右
$ docker container ls
CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS                      PORTS                  NAMES
2589f5acff74        ch08-numbers-api:v2   "dotnet /app/Numbers…"   37 minutes ago      Up 37 minutes (unhealthy)   0.0.0.0:8080->80/tcp   competent_sanderson

可以看到该容器被标记为不健康了

  • 查看健康检查的日志
$ docker container inspect $(docker container ls --last 1 -q)
//...
"State": {
            "Status": "running",
            "Running": true,
            "Paused": false,
            "Restarting": false,
            "OOMKilled": false,
            "Dead": false,
            "Pid": 32116,
            "ExitCode": 0,
            "Error": "",
            "StartedAt": "2020-03-17T14:07:33.190455031Z",
            "FinishedAt": "0001-01-01T00:00:00Z",
            "Health": {
                "Status": "unhealthy",
                "FailingStreak": 128,
                "Log": [
                    {
                        "Start": "2020-03-17T22:55:33.399820121+08:00",
                        "End": "2020-03-17T22:55:33.542807814+08:00",
                        "ExitCode": 22,
                        "Output": "  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current\n                                 Dload  Upload   Total   Spent    Left  Speed\n\r  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0\r  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0\ncurl: (22) The requested URL returned error: 500 Internal Server Error\n"
                    },
                    {
                        "Start": "2020-03-17T22:55:38.572044692+08:00",
                        "End": "2020-03-17T22:55:38.715793476+08:00",
                        "ExitCode": 22,
                        "Output": "  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current\n                                 Dload  Upload   Total   Spent    Left  Speed\n\r  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0\r  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0\ncurl: (22) The requested URL returned error: 500 Internal Server Error\n"
                    },
                    {
                        "Start": "2020-03-17T22:55:43.74781023+08:00",
                        "End": "2020-03-17T22:55:43.887389369+08:00",
                        "ExitCode": 22,
                        "Output": "  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current\n                                 Dload  Upload   Total   Spent    Left  Speed\n\r  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0\r  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0\ncurl: (22) The requested URL returned error: 500 Internal Server Error\n"
                    },
//...

--last是选择最近运行的那个容器,在State下的Health下的Log项中记录了每次检查失败的记录


三、依赖检查

注意:依赖检查不像健康检查一样有特殊的指令可以指定,所以依赖检查实际上就是一个简单的条件语句,如果检查通过,则执行运行服务所需要的关键命令,如果没通过,则会因为没有什么其他命令可执行而进入到退出状态。比如:

CMD curl --fail http://numbers-api/rng && dotnet Numbers.Web.dll

&&表示如果curl --fail http://numbers-api/rng(即依赖检查)没有出错,则执行dotnet Numbers.Web.dll,否则不执行该命令,由于该命令是整个该容器(服务)的启动命令,不执行该命令就等效于容器任务执行完成(因为没有其他命令可以执行),故而进入到退出状态。


四、健康检查与依赖检查在Docker Compose工具中的应用
  • Docker Compose中健康检查的配置
services:
  numbers-api:
    image: ch08-numbers-api:vmtk
    ports:
      - "8087:80"
    healthcheck:
      test: ["CMD" , "curl" , "--fail" , "http://localhost/health"]
      interval: 5s
      timeout: 1s
      retries: 2
      start_period: 5s
    networks:
      - app-net
//...

这是compose文件中的一部分,healthcheck项下就是关于健康检查的配置:
test后面接一个数组,放的是健康检查的命令,每个字符串占用一个数组元素
intervaltimeoutretriesstart_period前面已经介绍过了,不再赘述

  • Docker Compose中依赖检查的配置
//...
numbers-web:
    image: ch08-numbers-web:v3
    restart: on-failure
    environment:
      - RngApi__Url=http://numbers-api/rng
    ports:
      - "8088:80"
    healthcheck:
      test: ["CMD", "dotnet", "Utilities.HttpCheck.dll", "-t", "150"]
      interval: 5s
      timeout: 1s
      retries: 2
      start_period: 10s
    networks:
      - app-net
//...

numbers-web服务是依赖于numbers-api服务的,但是我们可以发现它并没有配置depends_on设置项,原因在开头已经解释过了。这里最关键的配置在于restart: on-failure一项,它告诉Docker Compose如果该容器启动失败则重启该容器,这样当容器因为依赖检查失败而进入到退出状态时,Docker Compose会重启它,而此时该容器所依赖的容器可能已经成功运行了,因此该容器也就会通过依赖检查而成功启动。


五、其他
  • 注意事项:
    • 健康检查是会耗费计算机资源的,所以务必注意健康检查在资源占用和使用效果上的平衡,如果健康检查太严格,那么将过于耗费计算机资源,如果健康检查太宽松,那么健康检查的实际效果可能会大打折扣,而由于没有及时检测到问题,用户的体验将会持续下降。
  • 建议在程序中编写一套自己的工具用于健康检查和依赖检查,而不是使用外部工具,比如curl。理由如下:
    • 减少了需要添加的额外工具,越多的外部工具意味着越多的风险
    • 你自己应用的状态如何只有你自己最清楚,利用外部工具所能达到的效果可能并不如意
    • 你自己编写的工具,你可以检测任何你想要的东西,并根据这些数据来利用更完善的逻辑去判断服务的健康状态
    • 因为是属于程序内部的工具,所以使用的库将是一个统一的库,使用的配置将是一个统一的配置,管理方便
    • 将会得到Docker跨平台的优点,检查结果不会因为平台而影响

参考文档:
[1] learn-docker-in-a-month-of-lunches
[2] 官方文档


附:
[1] Elton Stoneman的github项目

你可能感兴趣的:(S6:容器的健康检查以及依赖检查)