something about healthech for docker container

在 Docker compose 文件里使用健康检查的方案变迁。
背景:小厂,用不起 kubernetes,只能自己生写 docker compose 来部署 Docker 容器。以下以一个在容器里监听 tcp 端口 8090 的服务为例来描述一下我用到过的健康检查的方案的变迁。

curl

文档里都讲是

1
2
3
4
5
6
7
# docker compose file
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:8090/ || exit 1"]
interval: 30s
timeout: 3s
retries: 3
start_period: 10s

这种方式,但是实际上,我经手的大多 image,都没有统一的健康检查 url,这样直接访问,很有可能出各种结果,返回的 return code 不是 200 的结果,这样的话,这个健康检查会过不去,但是服务却是好的。所以,经过研究,发现更好的应该是:

1
2
3
4
5
6
7
# docker compose file
healthcheck:
test: ["CMD-SHELL", "curl -s -o /dev/null http://localhost:8090/ || exit 1"]
interval: 30s
timeout: 3s
retries: 3
start_period: 10s

当然,这个解决不了一个致命问题:好些个基础 image,都不带命令 curl,然后我就开始了尝试下一个方案。

/proc/net/tcp

服务绑定监听端口,肯定会更新 /proc/net/tcp 这个文件,只不过端口号得转换成十六进制。就像这样:

1
2
3
4
5
6
7
8
# Docker compose file
healthcheck:
# Check that entries for ports 8000 exist in the /proc/net/tcp file. Port 8000 is represented as :1F40.
test: ["CMD-SHELL", "sleep 2 && grep ':1F40' /proc/net/tcp"]
interval: 30s
timeout: 5s
retries: 3
start_period: 30s

一直工作挺好,直到某一天我遇到一个基于 openjdk:8u342 的 Docker image,跑着 java 的程序,这个 java 程序监听着端口 8090,明明工作正常,但是健康检查却过不去,进到容器里一看,/proc/net/tcp 文件里的确没有 “:1f9a”(printf "%x\n" 8090)!

非常诡异!一顿好查。

1
apt update && apt install -y iproute2 procps

然后:

1
ss -nalpt

发现有

*:8090

但是 /proc/net/tcp 里始终没有 “:1f9a”,最后偶然间发现文件 /proc/net/tcp6,打开看:

0: 00000000000000000000000000000000:1F9A 00000000000000000000000000000000:0000 0A 00000000:00000000

“:1F9A” 赫然在这里!

回头找原因,只能认为是 java 程序绑定监听端口时优先绑了 IPv6 的地址,但是由于 kernel 参数:net.ipv6.bindv6only=0 (缺省就是这样),从而绑在 IPv6 地址上的端口,在 IPv4 的地址上也能访问。

好吧,/proc/net/tcp 方案不是不行了,而是还要考虑到文件 /proc/net/tcp6

/dev/tcp/{hostname}/{port}

1
2
3
4
5
6
7
# Docker compose file
healthcheck:
test: ["CMD-SHELL", "echo > /dev/tcp/localhost/8090 || exit 1"]
interval: 30s
timeout: 3s
retries: 3
start_period: 10s

这个方案就有一个缺点:bash 可用但 sh 下不可用