问题描述
我已阅读 How do I kill background processes / jobs when my shell script exits?,但我无法让它工作。
IDK 如果是 Docker 恶作剧或其他什么。
#!/bin/bash -e
base="$(dirname "$0")"
trap 'kill $(jobs -p)' SIGINT SIGTERM EXIT
docker run --rm -p 5432:5432 -e POSTGRES_PASSWORD=password postgres:12 &
while ! nc -z localhost 5432; do
sleep 0.1
done
# uh-oh,error
false
当我运行它时,我只剩下一个正在运行的 Docker 容器。
为什么?当我的脚本退出时如何停止进程?
解决方法
Docker 是一个客户端/服务器应用程序,由瘦客户端 docker
和服务器 dockerd
组成。当你运行一个容器时,客户端会向服务器发出一些 API 调用,一个是创建容器,另一个是启动它,由于你没有分离运行它,它运行一个附加 API。当您终止 docker 进程时,它会与容器分离,不再向您显示日志,并终止该客户端部分。但是 dockerd 服务器仍在运行容器,直到容器内的进程(在容器命名空间内以 pid 1 运行)退出。您从未终止该进程,因为它是从 dockerd 守护进程生成的,而不是直接从 docker 客户端生成的。
要解决此问题,我的建议是运行带有容器名称或 ID 的 docker stop
,作为陷阱处理程序的一部分。我什至不想在后台运行 docker,而是通过 -d
运行分离。
跟进,在本地测试脚本,当您运行这样附加的客户端时,看起来杀死 docker 客户端确实会发送 docker stop 信号。但是,有一种竞争条件可能会导致在数据库运行之前停止。命令:
nc -z localhost 5432
总是会在 postgresql 开始侦听端口之前成功,因为 docker 创建了一个转发端口。例如:
$ nc -z localhost 5432 && echo it works
$ docker run -itd --rm -p 5432:5432 busybox tail -f /dev/null
c72427053124608fe18c31e5d6f3307d74a5cdce018503e9fff85dbc039b4fff
$ nc -z localhost 5432 && echo it works
it works
$ docker stop c72
c72
$ nc -z localhost 5432 && echo it works
但是,如果我在脚本中运行 sleep,这会迫使它等待足够长的时间让容器完成启动,并完成附加,容器将停止。
更好的脚本版本如下所示,它通过检查日志等待数据库完全启动,并更改陷阱以运行 docker stop
命令:
#!/bin/bash -e
base="$(dirname "$0")"
trap 'kill $(jobs -p)' SIGINT SIGTERM EXIT
cid=$(docker run --rm -d -p 5432:5432 -e POSTGRES_PASSWORD=password postgres:12)
# leaving the kill assuming you have other background processes
trap 'docker stop $cid; kill $(jobs -p)' SIGINT SIGTERM EXIT
# waiting for the db to actually start,assuming later steps need the db to be up
while ! docker logs "$cid" 2>&1 | grep -q "database system is ready to accept connections" ; do
sleep 0.1
done
# uh-oh,error
false
,
这是 Docker 的恶作剧。
我需要使用 --init
选项来运行 tini shim,因为
在容器内作为 PID 1 运行的进程被 Linux 特殊对待:它忽略任何具有默认操作的信号。因此,进程不会在 SIGINT 或 SIGTERM 上终止,除非它被编码为这样做。
docker run --rm -p 5432:5432 -e POSTGRES_PASSWORD=password postgres:12 &