0%

[Docker] Health Check and Restart Unhealthy Container

前言

在沒有 HEALTHCHECK 指令之前,Docker 只能透過 process 是否退出來判斷 container 的狀態,不過有時候是服務已經無法正常運作了,但 process 沒有退出,這樣會導致該服務仍然可以接收用戶請求,但是無法正常回應。

Health Check

在 Docker 版本 1.12 之後提供了 HEALTHCHECK 指令,可以設定一行 command 用來判斷服務的狀態是否正常,這樣可以更準確地判斷服務狀態。

Container 啟動後的初始狀態為 starting, 在 HEALTHCHECK 指令檢查成功後,狀態會更改為 healthy,如果連續失敗超過指定次數則會改為 unhealthy.

HEALTHCHECK options:

  • --interval: Health check 時間間隔,預設為 30 秒
  • --timeout: 當 Health check 超過此設定的時間,則會視為失敗,預設為 30 秒
  • --retries: 當 Health check 連續失敗次數超過此設定時,則會將狀態更改為 unhealthy,預設為 3 次
  • --start-period: 啟動時間,預設為 0 秒

HEALTHCHECK 可以透過 Dockerfile 或是 docker-compose file 做設定:

Dockerfile example

在 Dockerfile 中,HEALTHCHECK 指令格式為 HEALTHCHECK [options] CMD <command>, <command> 可以是 shell 指令或是 exec 格式 (和其他 Dockerfile 指令相同,可以參考 ENTRYPOINT)。而一個 Dockerfile 中只能有一個 HEALTHCHECK 指令,如果同時有多個 HEALTHCHECK 指令,則只有最後一個有效。

<command> 的返回值代表 container 的狀態:

  • 0: 成功,container is healthy
  • 1: 失敗,如果失敗超過指定次數,則 container 為 unhealthy
  • 2: reserved, 不要使用這個值

假設我們的 container 服務是 web 服務,我們可以使用 curl 來檢查服務是否正常運作,例如: 每 30 秒檢查一次 http://localhost:3000 是否可在 5 秒內回應請求:

1
2
3
4
5
6
# ...

HEALTHCHECK --interval=30s --timeout=5s --retries=5 --start_period=30s \
CMD curl -fs http://localhost:3000/ || exit 1

# ...

Docker-compose example

docker-compose.yml 中,healthcheck 範例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
version: "3.7"

services:
api:
restart: always
image: api
container_name: api
ports:
- 3000:3000
build:
context: ./api
healthcheck:
test: curl -fs http://localhost:3000/ || exit 1
interval: 30s
timeout: 5s
retries: 5
start_period: 30s
networks:
- net
networks:
net:
name: net
driver: bridge

其中 test 必須是 string 或 list. 如果是 list, 第一個 item 必須是 NONE, CMDCMD-SHELL。如果是 string, 則等同於 CMD-SHELL

確認健康狀態

在設定好 health check 指令之後,接著啟動 container,檢查 container 狀態時可以看到初始狀態是 health: starting:

1
2
3
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3c7b9ca321d2 api:1.0.0 "uwsgi --ini /home/d…" 5 seconds ago Up 2 seconds (health: starting) 0.0.0.0:3000->3000/tcp api

過 30 秒之後再執行一次 docker ps 可以看到 container 的狀態變成 healthy:

1
2
3
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3c7b9ca321d2 api:1.0.0 "uwsgi --ini /home/d…" 35 seconds ago Up 32 seconds (healthy) 0.0.0.0:3000->3000/tcp api

而如果連續失敗超過指定次數,狀態會變成 unhealthy

HEALTHCHECK command 的任何 output (包含 stdoutstderr) 都會被儲存在健康狀態中,可以使用 docker inspect 來查看:

1
2
3
4
5
6
7
8
9
10
11
$ docker inspect --format '{{json .State.Health}}' api
{
"Status":"healthy",
"FailingStreak":0,
"Log":[{
"Start":"2021-04-25T15:08:05.579571483+08:00",
"End":"2021-04-25T15:08:05.871891851+08:00",
"ExitCode":0,
"Output":"store HEALTHCHECK output here"
}]
}

Restart Unhealthy Container

以上的步驟只有檢查 container 的健康狀態,但沒有針對 unhealthy container 做任何處理,這部分我們可以搭配 docker-autoheal 來重啟 unhealthy container.

這部分可以直接使用 docker 執行,或是寫在 docker-compose file 中:

  • 使用 docker 指令:

    1
    2
    3
    4
    5
    6
    $ docker run -d \
    --name autoheal \
    --restart=always \
    -e AUTOHEAL_CONTAINER_LABEL=all \
    -v /var/run/docker.sock:/var/run/docker.sock \
    willfarrell/autoheal
  • 透過 docker-compose file 設定:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    version: "3.7"

    services:
    autoheal:
    restart: always
    image: willfarrell/autoheal
    container_name: autoheal
    environment:
    - AUTOHEAL_CONTAINER_LABEL=all
    volumes:
    - /var/run/docker.sock:/var/run/docker.sock

    接著執行 docker-compose up --build -d autoheal 即可。

最後就可以確認一下 unhealthy container 是否有自動重啟~

參考資料