对分配伪 TTY 的 Docker -t 选项感到困惑

Confused about Docker -t option to Allocate a pseudo-TTY

这个选项到底有什么作用?我已经阅读了很多关于 TTY 的内容,但我仍然感到困惑。我在没有 -t 而只有 -i 的情况下玩了玩,似乎期望用户输入的程序在没有 -t 的情况下会抛出错误。为什么启用伪 TTY 很重要?

根据 Google 搜索,-t 论点没有得到很好的记录,也没有被很多人经常提及。

当您通过在 Bash 提示符下键入 docker 来显示所有 docker 客户端参数的列表(应该是什么)时,它甚至不会出现(最新的1.8.1 版本).

事实上,如果您尝试通过键入 docker -t --help 来获得有关此论点的具体帮助,如果给出这个令人惊讶的模糊答复:

flag provided but not defined: -t

所以,不能怪你对这个论点感到困惑!

Docker 在线文档中提到 "Allocate a pseudo-tty" 并且通常与 -i:

一起使用

https://docs.docker.com/reference/run/

我在很棒的 jwilder/nginx-proxy docker 容器的文档中看到它以下列方式使用:

docker run -d -p 80:80 --name nginx -v /tmp/nginx:/etc/nginx/conf.d -t nginx

在这种情况下,它所做的是将输出发送到此 docker 容器内的 'virtual' tty(Bash 命令 prompt/terminal)。然后,您可以通过 运行 docker 命令 docker logs CONTAINER 查看此输出,其中 CONTAINER 是此容器 ID 的前几个字符。可以通过键入 docker ps -a

找到此 CONTAINER ID

我在下面 link 中看到了这个 -t 论点,它说

The -t and -i flags allocate a pseudo-tty and keep stdin open even if not attached. This will allow you to use the container like a traditional VM as long as the bash prompt is running.

https://coreos.com/os/docs/latest/getting-started-with-docker.html

希望对您有所帮助!我不确定为什么没有记录或使用太多。也许它是实验性的,将在即将发布的版本中作为记录的功能实施。

我对 -t 的了解如下:

docker exec -ti CONTAINER bash - 允许我在容器中 "login"。感觉就像 ssh-ing(不是)。

但问题是当我想恢复数据库时。

通常我这样做docker exec -ti mysql.5.7 mysql - 这里我在容器中执行mysql命令并得到一个交互式终端。

我在前面的命令中添加了 <dump.sql 以便我可以恢复数据库。但是失败了 cannot enable tty mode on non tty input.

删除 -t 有帮助。还是不明白为什么:

docker exec -i mysql.5.7 mysql < dump.sql

最后一个有效。希望这对人们有所帮助。

-t 选项涉及 Unix/Linux 如何处理终端访问。过去,终端是硬线连接,后来是基于调制解调器的连接。它们有物理设备驱动程序(它们是真正的设备)。一旦广义网络投入使用,就开发了一个伪终端驱动程序。这是因为它在理解可以使用哪些终端功能而无需直接将其写入您的程序(阅读 sttycurses 上的手册页)之间创建了一个分离。

因此,以此为背景,运行 一个没有选项的容器,默认情况下您有一个标准输出流(因此 docker run | <cmd> 有效); 运行 和 -i,你会添加标准输入流(所以 <cmd> | docker run -i 有效);使用 -t,通常是 -it 的组合,并且您添加了一个终端驱动程序,如果您正在与进程交互,这可能就是您想要的。它基本上使容器启动看起来像一个终端连接会话。

-it指示Docker分配一个连接到容器标准输入的伪TTY,在容器中创建一个交互式bashshell。

--interactive, -i false 即使未连接,也要保持 STDIN 打开

--tty, -t false 分配伪TTY

https://docs.docker.com/engine/reference/commandline/run/

迟到的答案,但可能对某人有帮助

docker run/exec -i 会将容器内命令的 STDIN 连接到 docker run/exec 本身的 STDIN。

所以

  • docker run -i alpine cat 给你一个空行等待输入。输入 "hello" 你会得到一个回显 "hello"。在您发送 CTRL+D 之前容器不会退出,因为主进程 cat 正在等待来自无限流的输入docker run.
  • 的终端输入
  • 另一方面,echo "hello" | docker run -i alpine cat 将打印 "hello" 并立即退出,因为 cat 注意到输入流已结束并自行终止。

如果您在退出上述任一项后尝试 docker ps,您将找不到任何 运行 容器。在这两种情况下,cat 本身已终止,因此 docker 已终止容器。

现在对于“-t”,它告诉 docker 中的主进程它的输入是一个终端设备。

所以

  • docker run -t alpine cat 会给你一个空行,但如果你尝试键入 "hello",你将不会得到任何回显。这是因为当 cat 连接到终端输入时,此输入未连接到您的输入。您输入的 "hello" 未达到 cat 的输入。 cat 正在等待从未到达的输入。
  • echo "hello" | docker run -t alpine cat 也会给你一个空行,并且不会在 CTRL-D 上退出容器,但你会没有得到回显 "hello" 因为你没有通过 -i

如果你发送CTRL+C,你会得到你的shell,但是如果你尝试docker ps 现在,您看到 cat 容器仍然是 运行。这是因为 cat 仍在等待从未关闭的输入流。如果不与 -i.

结合使用,我还没有发现 -t 有任何有用的用途

现在,一起 -it。这告诉 cat 它的输入是一个终端,同时将这个终端连接到作为终端的 docker run 的输入。 docker run/exec 将确保它自己的输入在传递给 cat 之前实际上是一个 tty。这就是为什么如果你尝试 echo "hello" | docker run -it alpine cat 你会得到 input device is not a TTY 的原因,因为在这种情况下,docker run 本身的输入是来自前一个 echo 的管道,而不是 [=14= 所在的终端]被执行

最后,如果 -i 可以将您的输入连接到 cat 的输入,您为什么需要传递 -t?这是因为如果它是一个终端,命令会以不同的方式对待输入。例子也很好地说明了这一点

  • docker run -e MYSQL_ROOT_PASSWORD=123 -i mariadb mysql -u root -p 会给你一个密码提示。如果您输入密码,字符会被打印出来。
  • docker run -i alpine sh 会给你一个空行。如果你输入像 ls 这样的命令,你会得到一个输出,但你不会得到提示或彩色输出。

在最后两种情况下,您会出现这种行为,因为 mysqlshell 都没有将输入视为 tty,因此没有使用 tty 特定的行为,例如屏蔽输入或为输出着色。

在linux中当你运行一个命令时,你需要一个终端(tty)来执行它。

所以当你想连接到docker(或docker容器中的运行命令)时,你必须提供选项-t,它考虑了终端内部docker 容器。

每个进程都有三个数据流,即STDIN/ STDOUT/ STDERR。当容器中的进程 运行 时,默认情况下终端与容器中进程 运行 的 STDOUT 流连接。因此,在终端中执行 运行 docker run 命令时,所有输出流都将可见。但是如果你想为容器中的 运行 进程提供输入,那么你必须连接到进程的 STDIN 通道,这不是默认情况,而是通过 docker run -i 命令完成的。

-t用于交互式/格式化输入操作。

-it 组合选项称为 交互 模式。

默认情况下,容器只有一个标准输出流(即,docker run | CMD 有效),要与我们的容器交互,我们需要这些选项:

  • -i 添加标准输入流(即,CMD | docker run 有效);
  • -t 分配伪 TTY master/slave 对,从属部分绑定到容器中的 运行 进程,主控部分绑定到你的 docker 命令.

stdin 流将容器附加到 shell 的 stdin(Docker 继承了 shell 的 stdin 流),而 TTY 线路规则使您能够进行交互以键盘方式使用容器。

TTY 线路规程由内核提供给 TTY 设备的低级功能组成,例如编辑缓冲区和基本线路编辑命令。

如下图,可以通过以下命令查看标准文件描述符:

docker run --rm -i ubuntu sh -c "ls -l /proc/$$/fd"

如果删除 -i,您会看到标准输入指向 /dev/null(即没有分配流)。

这里的大部分答案都是很好的概念性答案,但我发现它们遗漏了太多细节,让我坐在电脑前无法使用这些信息。 的答案正在走向程序化,但让我们尝试更进一步。

首先是一些理论

The TTY Demystified中的两张图片是关键:

我不能声称完全理解这张图,但这里感兴趣的关系是,当 xterm(或 ubuntu 中的 gnome-terminal;由其中一个“用户进程”气泡表示时上图)打开,它启动一个 bash(或默认的 shell),然后通过内核伪终端 (PTY) 主机和从机向它发送键盘输入:

xterm -> ptmx (pty master) -> pts (pty slave) -> bash

第二张图片代表这个简短的 bash 会话中涉及的进程:

>>> cat
>>> ls | sort
...

信息的关键位是 TTY 和 stdin、stdout、stderr 行。这表明每个进程都关联到一个 TTY(电传终端),并且它们的 3 个流(stdin、stdout、stderr)非常自然地关联到这个 TTY,除了管道或重定向的情况(注意管道 ls | sort 将 ls 的标准输出关联到 sort 的标准输入)。

现在对理论进行一些检验

我们可以通过键入 tty:

找到 bash 使用的伪终端
>>> tty
/dev/pts/2

Bash 因此关联到 PTY 从站编号 2(这可能意味着有另一个终端打开,关联到 master/slave 对 1)。我们还可以获得 bash 的标准输入、标准输出和标准错误流:

>>> ls -l /proc/$$/fd
lrwx------ 1 samlaf samlaf 64 Jun 17 21:50 0 -> /dev/pts/2
lrwx------ 1 samlaf samlaf 64 Jun 17 21:50 1 -> /dev/pts/2
lrwx------ 1 samlaf samlaf 64 Jun 17 21:50 2 -> /dev/pts/2

确实,它们都与bash的天然TTY从机有关。 ($$是一个bash变量,是returnsbash的PID,我们同样可以用ps手写找到。

最后使用这个理论来回答最初的 Docker 问题

我们重现上述步骤,但这次在 docker 容器内:

>>> docker run --rm -t ubuntu tty
/dev/pts/0
>>> docker run --rm ubuntu tty
not a tty

这是有道理的,因为 -t allocates a pseudo-terminal.

-i 相关命令更难解释。

>>> docker run --rm ubuntu bash -c "ls -l /proc/$$/fd"
lrwx------ 1 root root 64 Jun 18 02:37 0 -> /dev/null
l-wx------ 1 root root 64 Jun 18 02:37 1 -> pipe:[9173789]
l-wx------ 1 root root 64 Jun 18 02:37 2 -> pipe:[9173790]
>>> docker run --rm -t ubuntu bash -c "ls -l /proc/$$/fd"
lrwx------ 1 root root 64 Jun 18 02:39 0 -> /dev/pts/0
lrwx------ 1 root root 64 Jun 18 02:39 1 -> /dev/pts/0
lrwx------ 1 root root 64 Jun 18 02:39 2 -> /dev/pts/0
>>> docker run --rm -it ubuntu bash -c "ls -l /proc/$$/fd"
lrwx------ 1 root root 64 Jun 18 02:39 0 -> /dev/pts/0
lrwx------ 1 root root 64 Jun 18 02:39 1 -> /dev/pts/0
lrwx------ 1 root root 64 Jun 18 02:39 2 -> /dev/pts/0

我仍然无法弄清楚 -i 到底是做什么的...我希望得到一些帮助! 我能找到的唯一有趣的命令似乎有所区别:

>>> docker run --rm -a stdout -i ubuntu bash -c "ls -l /proc/$$/fd"
lr-x------ 1 root root 64 Jun 18 02:43 0 -> pipe:[9199896]
l-wx------ 1 root root 64 Jun 18 02:43 1 -> pipe:[9199897]
l-wx------ 1 root root 64 Jun 18 02:43 2 -> pipe:[9199898]
>>> docker run --rm -a stdout ubuntu bash -c "ls -l /proc/$$/fd"
lrwx------ 1 root root 64 Jun 18 02:43 0 -> /dev/null
l-wx------ 1 root root 64 Jun 18 02:43 1 -> pipe:[9197938]
l-wx------ 1 root root 64 Jun 18 02:43 2 -> pipe:[9197939]

Docker documentation 提到 -a “附加到作为输入传递的流”,但我无法找到这意味着什么的解释,以及它的相关性-i 选项。