S6:容器的健康檢查以及依賴(lài)檢查

聲明:所有的實(shí)驗(yàn)示例大部分來(lái)自《learn-docker-in-a-month-of-lunches》的作者Elton Stoneman,但運(yùn)行結(jié)果并不都是照搬,大部分實(shí)驗(yàn)結(jié)果可能與原書(shū)不同


一、健康檢查(health checks)和依賴(lài)檢查(dependency checks)概述

Q--什么是健康檢查(health checks)?有什么用?
A--健康檢查用于判斷一個(gè)容器是否正常工作,容器處于運(yùn)行狀態(tài)并不代表容器是正常工作的,因?yàn)槿萜鞅憩F(xiàn)的行為可能完全不符合預(yù)期,這樣得容器我們稱(chēng)之為不正常工作的容器,而健康檢查就用于這方面的檢查。健康檢查可以?xún)H僅只是一個(gè)普通指令,也可以是一個(gè)腳本,一串邏輯,具體什么樣的返回結(jié)果才算正常,由你自己決定。如果容器被標(biāo)記為不健康后,會(huì)產(chǎn)生一個(gè)事件,這樣運(yùn)行該容器的平臺(tái)就可以根據(jù)該事件做出相應(yīng)的響應(yīng),比如啟動(dòng)一個(gè)新的容器替換這個(gè)不健康的容器。

Q--什么是依賴(lài)檢查(denpendency checks)?有什么用?
A--依賴(lài)檢查用于在容器啟動(dòng)時(shí)(沒(méi)錯(cuò)!只在啟動(dòng)時(shí))檢查該容器依賴(lài)的服務(wù)是否已經(jīng)運(yùn)行了,如果沒(méi)有,該容器進(jìn)入退出狀態(tài)。作用就是保持容器與容器之間的依賴(lài)關(guān)系以及啟動(dòng)順序。依賴(lài)檢查可以很簡(jiǎn)單,比如單純的通過(guò)curl指令訪問(wèn)一下目標(biāo)服務(wù),如果有正確的響應(yīng)則算通過(guò),當(dāng)然實(shí)際上檢測(cè)標(biāo)準(zhǔn)應(yīng)當(dāng)更加完善才好。

Q--Docker Compose有depends_on設(shè)置項(xiàng)設(shè)置容器間的依賴(lài)關(guān)系以及啟動(dòng)順序,為什么還需要依賴(lài)檢查?
A--因?yàn)镈ocker Compose僅僅只能在一臺(tái)機(jī)器上控制依賴(lài)關(guān)系。如果應(yīng)用太大,就需要被分布在許多臺(tái)機(jī)器上,這時(shí)候啟動(dòng)時(shí)的依賴(lài)關(guān)系可就很難維護(hù)了。再者強(qiáng)硬的使用依賴(lài)關(guān)系規(guī)定A容器必須運(yùn)行在B容器之前,這樣也不是很好,比如,假設(shè)A有50個(gè)容器,那么B想要運(yùn)行則必須等這50個(gè)全部運(yùn)行后才可以運(yùn)行,然而此時(shí)恰恰就有1個(gè)容器怎么都運(yùn)行不起來(lái),那么這段時(shí)間B也運(yùn)行不起來(lái),整個(gè)應(yīng)用在這段時(shí)間都是不正常的。若是使用依賴(lài)檢查的話,只要通過(guò)了依賴(lài)檢查容器就可以啟動(dòng),而不需要管其他什么亂七八糟的,比如,只要有20個(gè)A容器啟動(dòng)了,A服務(wù)就"表現(xiàn)"正常了,那么B在啟動(dòng)時(shí)就可以通過(guò)依賴(lài)檢查,因此就可以啟動(dòng)了。


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

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

WORKDIR /app
//...

這是某個(gè)鏡像的Dockerfile中的一部分,健康檢查相關(guān)的部分就定義在這里,HEALTHCHECK后面接的指令就是用于健康檢查的指令。該指令每隔一段時(shí)間就執(zhí)行一次,如果執(zhí)行的返回值異常超過(guò)一定次數(shù),則該容器會(huì)被視為不健康的。默認(rèn)是每30秒執(zhí)行一次,3次連續(xù)異常則被判斷為不健康的。

  • 運(yùn)行一個(gè)帶健康檢查的容器
$ 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選項(xiàng)用于指定創(chuàng)建鏡像所需要依據(jù)的Dockerfile
docker container run命令中其實(shí)可以指定和健康檢查的相關(guān)的配置,比如:
--health-cmd指定要執(zhí)行的健康檢查的命令
--health-interval指定健康檢查命令執(zhí)行的時(shí)間間隔,默認(rèn)ms
--health-retries指定失敗多少次,容器會(huì)被標(biāo)記為不健康的
--health-start-period指定在多少秒后才正式開(kāi)始計(jì)算失敗次數(shù)
--health-timeout健康檢查的超時(shí)時(shí)間,超時(shí)會(huì)被認(rèn)為是檢查失敗

  • 查看容器的狀態(tài)
$ 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)的狀態(tài),則代表該容器是健康的。
因?yàn)樵搼?yīng)用有一個(gè)人為的bug,即訪問(wèn)localhost:8080/rng三次后再訪問(wèn)會(huì)報(bào)錯(cuò),所以接下來(lái)我們將人為觸發(fā)這個(gè)bug,然后再查看容器的狀態(tài)

  • 觸發(fā)bug,查看容器狀態(tài)
$ 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

可以看到該容器被標(biāo)記為不健康了

  • 查看健康檢查的日志
$ 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是選擇最近運(yùn)行的那個(gè)容器,在State下的Health下的Log項(xiàng)中記錄了每次檢查失敗的記錄


三、依賴(lài)檢查

注意:依賴(lài)檢查不像健康檢查一樣有特殊的指令可以指定,所以依賴(lài)檢查實(shí)際上就是一個(gè)簡(jiǎn)單的條件語(yǔ)句,如果檢查通過(guò),則執(zhí)行運(yùn)行服務(wù)所需要的關(guān)鍵命令,如果沒(méi)通過(guò),則會(huì)因?yàn)闆](méi)有什么其他命令可執(zhí)行而進(jìn)入到退出狀態(tài)。比如:

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

&&表示如果curl --fail http://numbers-api/rng(即依賴(lài)檢查)沒(méi)有出錯(cuò),則執(zhí)行dotnet Numbers.Web.dll,否則不執(zhí)行該命令,由于該命令是整個(gè)該容器(服務(wù))的啟動(dòng)命令,不執(zhí)行該命令就等效于容器任務(wù)執(zhí)行完成(因?yàn)闆](méi)有其他命令可以執(zhí)行),故而進(jìn)入到退出狀態(tài)。


四、健康檢查與依賴(lài)檢查在Docker Compose工具中的應(yīng)用
  • 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項(xiàng)下就是關(guān)于健康檢查的配置:
test后面接一個(gè)數(shù)組,放的是健康檢查的命令,每個(gè)字符串占用一個(gè)數(shù)組元素
interval、timeout、retriesstart_period前面已經(jīng)介紹過(guò)了,不再贅述

  • Docker Compose中依賴(lài)檢查的配置
//...
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服務(wù)是依賴(lài)于numbers-api服務(wù)的,但是我們可以發(fā)現(xiàn)它并沒(méi)有配置depends_on設(shè)置項(xiàng),原因在開(kāi)頭已經(jīng)解釋過(guò)了。這里最關(guān)鍵的配置在于restart: on-failure一項(xiàng),它告訴Docker Compose如果該容器啟動(dòng)失敗則重啟該容器,這樣當(dāng)容器因?yàn)橐蕾?lài)檢查失敗而進(jìn)入到退出狀態(tài)時(shí),Docker Compose會(huì)重啟它,而此時(shí)該容器所依賴(lài)的容器可能已經(jīng)成功運(yùn)行了,因此該容器也就會(huì)通過(guò)依賴(lài)檢查而成功啟動(dòng)。


五、其他
  • 注意事項(xiàng):
    • 健康檢查是會(huì)耗費(fèi)計(jì)算機(jī)資源的,所以務(wù)必注意健康檢查在資源占用和使用效果上的平衡,如果健康檢查太嚴(yán)格,那么將過(guò)于耗費(fèi)計(jì)算機(jī)資源,如果健康檢查太寬松,那么健康檢查的實(shí)際效果可能會(huì)大打折扣,而由于沒(méi)有及時(shí)檢測(cè)到問(wèn)題,用戶(hù)的體驗(yàn)將會(huì)持續(xù)下降。
  • 建議在程序中編寫(xiě)一套自己的工具用于健康檢查和依賴(lài)檢查,而不是使用外部工具,比如curl。理由如下:
    • 減少了需要添加的額外工具,越多的外部工具意味著越多的風(fēng)險(xiǎn)
    • 你自己應(yīng)用的狀態(tài)如何只有你自己最清楚,利用外部工具所能達(dá)到的效果可能并不如意
    • 你自己編寫(xiě)的工具,你可以檢測(cè)任何你想要的東西,并根據(jù)這些數(shù)據(jù)來(lái)利用更完善的邏輯去判斷服務(wù)的健康狀態(tài)
    • 因?yàn)槭菍儆诔绦騼?nèi)部的工具,所以使用的庫(kù)將是一個(gè)統(tǒng)一的庫(kù),使用的配置將是一個(gè)統(tǒng)一的配置,管理方便
    • 將會(huì)得到Docker跨平臺(tái)的優(yōu)點(diǎn),檢查結(jié)果不會(huì)因?yàn)槠脚_(tái)而影響

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


附:
[1] Elton Stoneman的github項(xiàng)目

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過(guò)簡(jiǎn)信或評(píng)論聯(lián)系作者。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容