【问题标题】:Why isn't this script killing Docker background process?为什么这个脚本没有杀死 Docker 后台进程?
【发布时间】:2021-01-03 18:40:48
【问题描述】:

我已阅读 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 容器。

为什么?我的脚本退出时如何停止进程?

【问题讨论】:

    标签: linux bash docker process background-process


    【解决方案1】:

    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
    

    【讨论】:

    • 是的,我熟悉破坏流程树的设计选择。但是使用 start/stop 的组合性较差。一个调用进程的进程调用一个进程......我希望有一种方法,我可以使用正常的进程语义而没有 Docker 雪花。
    【解决方案2】:

    这是 Docker 的恶作剧。

    我需要使用 --init 选项来运行 tini shim,因为

    在容器内以 PID 1 运行的进程被 Linux 特殊处理:它忽略任何具有默认操作的信号。因此,进程不会在 SIGINT 或 SIGTERM 上终止,除非它被编码为这样做。

    docker run --rm -p 5432:5432 -e POSTGRES_PASSWORD=password postgres:12 &
    

    【讨论】:

    • 很惊讶这会有所作为,因为应该对 postgres 进行编码以处理 sigterm,并且您正在从 pid 不是 1 的容器外部发送信号。您确定您正在运行 docker而不是像 podman 这样的工具?
    • 绝对是。如果你有 docker,欢迎你试试上面的脚本。
    • 见我上面的更新。我在有和没有--init 的情况下进行了测试,它们都确实有效。看起来 docker 客户端会通过 kill,但可能存在竞争条件,即在启动过程中,在 docker 附加之前,您正在杀死它。
    • @BMitch 很有趣。
    猜你喜欢
    • 2012-09-30
    • 1970-01-01
    • 2013-07-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-24
    相关资源
    最近更新 更多