如何在Docker容器内修复Ctrl+C问题

61
如果我连接到Docker容器。
$> docker exec -it my_container zsh

在容器中,我想要终止某个进程。我尝试使用ctrl+c组合键,但发现它需要很长的时间才能完成。我查阅了一些资料,似乎ctrl+c在容器中与预期有些不同。我的问题是,如何解决在容器中使用ctrl+c的问题?

10个回答

73
问题在于Ctrl-C发送一个信号给容器内的顶级进程,但该进程的反应不一定符合预期。容器内的顶级进程的ID为1,这意味着它不会获得通常进程所具有的默认信号处理程序。如果顶级进程是一个shell,则可以通过其自己的处理程序接收信号,但不会将其转发给在shell内执行的命令。详细信息在这里有解释。在这两种情况下,Docker容器似乎只是忽略了Ctrl-C。
docker 0.6.5开始,您可以在docker run命令中添加-t,这将附加一个pseudo-TTY。然后,您可以键入Control-C以分离容器而不终止它。
如果您使用-t-i,则Control-C将终止容器。当使用-i-t时,您必须使用Control-P Control-Q以分离而不终止。
测试1:
$ ID=$(sudo docker run -t -d ubuntu /usr/bin/top -b)
$ sudo docker attach $ID
Control-P Control-Q
$ sudo docker ps

容器仍然在列表中。
测试2:
$ ID=$(sudo docker run -t -i -d ubuntu /usr/bin/top -b)
$ sudo docker attach $ID
Control-C
$ sudo docker ps

容器不存在(已被终止)。如果在第二个示例中,您键入Control-P Control-Q而不是Control-C,容器仍将继续运行。

使用docker-entrypoint.sh bash脚本包装程序,阻塞容器进程并能够捕获ctrl-c。这个bash示例可能会有所帮助: https://rimuhosting.com/knowledgebase/linux/misc/trapping-ctrl-c-in-bash

#!/bin/bash

# trap ctrl-c and call ctrl_c()
trap ctrl_c INT

function ctrl_c() {
        echo "** Trapped CTRL-C"
}

for i in `seq 1 5`; do
    sleep 1
    echo -n "."
done

5
如果我理解得对的话,请纠正我,您所描述的 ctrl+c 是用于退出容器,而这不是我想要的。我想停止我手动启动的程序。 - Jeanluca Scaljeri
2
请详细说明脚本如何阻止容器进程,并在Ctrl-C陷阱中应该执行什么操作。 - Greg Bell
OT:我认为使用CTRL+C停止容器是一个糟糕的设计...我经常因为退出shell进程的习惯而意外停止容器。 - Maciek Rek
1
谢谢!现在我真的知道为什么总是要添加-it是个好主意了。顺便说一下,这也很容易记住。只需要输入 docker run -it ...! :-D - RayLuo

16

有时候,当我使用ctrl-C来终止容器内的一个进程时,容器会退出。

此外,我也发现容器中运行的进程有时会留下僵尸进程。

我发现,如果使用--init开关启动容器,则可以解决上述问题。这似乎使我的容器以更加“正常、符合UNIX预期的方式”运作。

注意:--init-i不同,后者是--interactive的简写。

如果您想了解有关--init开关的详细信息,请在包含有关docker run信息的Docker网页上阅读相关内容。该网页上的信息显示:“在容器内部运行一个init进程来转发信号和回收进程。”


16
使用Ctrl+\代替Ctrl+C,这会直接结束进程而不是优雅地请求关闭。(点击此处了解更多。)

16
对我来说不起作用。当正在运行的Docker容器没有响应Ctrl+C时,它也会忽略Ctrl+\ - eNca
它对我起作用了。我连接(ssh)到一个EC2实例(Amazon Linux),在其中运行了一个Docker容器。我在Mac上使用了iTerm2。 - Tharaka

9
when your docker terminal is not responding to Ctrl+C/Ctrl+D/Ctrl+/, try these steps:

#1>> Open another terminal session and enter the command:

    **`docker container ls`** 
        or
    **`docker container list`**


#2>> locate the container id from the above list and issue the docker container stop command:
    

**`docker stop <<containerId>>`**

#3>> next time when you launch the docker container, use the flag "**-it**"  to respond to the Ctrl+C event
    

docker run -it <>

    Now you can stop ,with control+C

易于使用和简单。谢谢。 - Aakash
1
遗憾的是这对我不起作用。 - Owl

8

当我尝试在 Docker 容器中运行 Rust 可执行文件 mdbook 时,遇到了类似的问题。该可执行文件启动了一个简单的 Web 服务器,我想通过按下 Ctrl+C 来停止它,但并没有起作用。

$ docker -ti --rm -p 4321:4321 my-docker-image mdbook serve --hostname 0.0.0.0 --port 4321
2019-08-16 14:00:11 [INFO] (mdbook::book): Book building has started
2019-08-16 14:00:11 [INFO] (mdbook::book): Running the html backend
2019-08-16 14:00:11 [INFO] (mdbook::cmd::serve): Serving on: http://0.0.0.0:4321
2019-08-16 14:00:11 [INFO] (ws): Listening for new connections on 0.0.0.0:3001.
2019-08-16 14:00:11 [INFO] (mdbook::cmd::watch): Listening for changes...
^C^C

受到@NID回答的启发,我使用通用的bash脚本docker-entrypoint.sh封装了mdbook可执行文件,这解决了问题(无需显式捕获INT信号)。

$ docker -ti --rm -p 4321:4321 my-docker-image docker-entrypoint.sh mdbook serve --hostname 0.0.0.0 --port 4321
2019-08-16 14:00:11 [INFO] (mdbook::book): Book building has started
2019-08-16 14:00:11 [INFO] (mdbook::book): Running the html backend
2019-08-16 14:00:11 [INFO] (mdbook::cmd::serve): Serving on: http://0.0.0.0:4321
2019-08-16 14:00:11 [INFO] (ws): Listening for new connections on 0.0.0.0:3001.
2019-08-16 14:00:11 [INFO] (mdbook::cmd::watch): Listening for changes...
^C $

docker-entrypoint.sh文件的内容非常简单:

#!/bin/bash

$@

1
这很好,谢谢。我唯一看到的缺点是你必须写下整个命令。 - Jeanluca Scaljeri
打开你最喜欢的编辑器,输入上面提到的两行代码并将其保存为 docker-entrypoint.sh :-) 然后通过 ADD 命令将此文件添加到你的 Dockerfile 中。 - eNca

5

我试过了@Remy Orange提供的--init解决方案并且它对我有效。在一些搜索后,包括i)如何在docker run中使用 --init 参数,ii) Tini的优势是什么? 和 iii) init,我写下了以下详细解决方案:

  1. 在Ubuntu上安装tini
  • 通过启动方式安装:
$ sudo apt update && sudo apt install tini
  • 或者,如果您的发行版中没有可用的tini或者版本太旧,请参考此处的Dockerfile添加tini。
  1. 使用--init运行您的Docker容器:
docker run -ti --init --rm YOUR_DOCKER_CONTAINER_EXMAPLE bash
  1. 然后您进入 Docker 容器,可以运行一些进程或实验。例如,运行 Python 代码,然后您可以使用 Ctrl + C 键来取消这个 Python 代码,就像在 Ubuntu 上(即在 Docker 容器之外的普通终端)所做的那样。

  2. 查看我的示例截图:

  • 启动 Ctrl + C (即 ^C) 来取消 Python 进程: enter image description here

  • 它会停止,并显示预期的 KeyboardInterruptenter image description here


我正在使用Ubuntu 16.04。 - ccj5351
或者您也可以 1) 运行 "docker exec -it SOME_CONTAINER_ID ps -aus" 在容器中查找 SOME_PID;然后 2) 运行 "docker exec -it SOME_CONTAINER_ID kill -9 SOME_PID" 来杀死 Docker 容器内的进程。 - ccj5351

4
如果你使用 Docker Compose,你可以添加 init 参数来将信号转发到容器:
version: "2.4"
services:
  web:
    image: alpine:latest
    init: true

为使其生效,你需要在 docker exec 命令中加入选项 -ti

3

我浪费了大约2个小时。

新命令--(工作正常)

sudo docker stop 
sudo docker rm   
sudo docker run -t

旧命令 --(不再起作用)

sudo docker stop
sudo docker rm   
sudo docker run
Ctrl + C
sudo docker start

希望这可以帮助到某些人。

2

对我而言,只有在使用docker run -it <容器ID/名称>运行容器之后,Ctrl+C才能起作用。


2
这对我来说根本行不通。 - Owl

2

对于仍然遇到这个问题的人,对我有用的是Ctrl+dCtrl+cCtrl+z都不起作用。


docker run之后Ctrl+Z正常工作了,谢谢! - Nigrimmist
2
@Nigrimmist 不要啊! - Johan Boulé

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接