理解容器中超易混淆 Attach 和 Exec 的异同
docker
(或podman
,或containerd
)attach
和exec
命令之间的区别是一个常见的混淆源。这是可以理解的——这两个命令有相似的参数,乍一看也有相似的行为。但是,attach
和exec
不可互换。它们旨在涵盖不同的用例,并且命令的实现也不同。但是,可能很难记住何时使用哪个命令。
容器管理 - 看看里面
首先,快速回顾一下docker
架构的样子:
三个关键要点:
容器管理架构是分层的 一个容器是一个孤立受限的环境+一个进程 在容器管理器和容器之间有一个 shim
组件。
attach命令来做什么
在容器内部,有一个常规的Linux
进程。与每个正常进程一样,它具有stdio
流 - stdin
、stdout
和stderr
。但是当容器以分离(即类似守护进程)模式启动时,这些流会发生什么:
$ docker run -d nginx
回到过去,当您将进程作为守护进程启动时(即,将其与启动进程分离),它将重新指向PID 1
,并且它的 stdio
流将被简单地关闭。但是,我们都知道如今使用stdout
和stderr
流进行日志记录是多么方便,这要归功于容器!
Docker
提出了一个聪明的想法,即在容器和系统的其余部分之间放置一个额外的进程,称为容器运行时shim
。在上面的示例中,容器管理器实际上启动了一个 shim
进程,该进程又使用 OCI
兼容的运行时(例如 runc
)来启动实际的容器。
这是成为守护进程的 shim
进程 - 它重新指向PID 1
,并且其stdio
流已关闭:
然而,shim
控制了容器的stdio
流!
守护程序的 shim
进程从容器的stdout
和stderr
读取并将读取的字节转储到日志驱动程序。默认情况下,shim
关闭容器的stdin
流,但如果-i
传递给相应的docker run
命令,它可以保持打开状态。
容器运行时 shim
实际上充当服务器!它提供了连接到它的 RPC
手段(例如,一个 UNIX
套接字)。当您这样做时,它开始将容器的stdout
和stderr
流式传输回套接字的末端。它还可以从此套接字读取并将数据转发到容器的stdin
。因此,类似attach
到容器的stdio
流!
所以,最后,当你运行时docker attach <container>
,你基本上创建了一个中继:
terminal <-> docker <-> dockerd <-> shim <-> container's stdio streams
attach和logs之间的区别
在上图中,docker attach
将容器的日志流式传输回终端。但是,该docker logs
命令执行类似的操作。那么,有什么区别呢?
该logs
命令提供了各种选项来过滤日志,而attach
在这方面则充当一个简单的tail
. 但更重要的是,logs
命令建立的流始终是单向的,并连接到容器的日志,而不是直接连接到容器的stdio
流。
该logs
命令只是将容器日志的内容流式传输回您的终端,仅此而已。因此,无论您如何创建容器(交互式或非交互式,是否由伪终端控制),在使用logs
命令时都不会意外影响容器。
但是,何时使用attach
:
如果容器是在交互模式 ( -i
)下创建的,则在attach-ing
到容器后在终端中键入的所有内容都将发送到其stdin
。您可以(有意或无意地)向容器发送信号 -例如, ctrl+c
在attach
发送SIGINT
到容器时击中您的一端。
exec命令来做什么
我希望attach
命令现在已经解决了。所以,是时候解决exec
对手了!
该exec
命令实际上是一个完全不同的故事。在attach
的情况下,我们将终端连接到现有容器(读取、处理)。但是,该exec
命令会启动一个全新的容器!换句话说,exec
是run
命令的一种形式(它本身只是create+
的快捷方式start
)。
注:该
OCI
运行规格不具有run
或exec
命令!查看#345
和#388
以获取有关exec
功能实际上是如何冗余的有趣讨论,并且可以在仅实现create
和start
命令的运行时中重现。
#345 https://github.com/opencontainers/runtime-spec/issues/345
#388 https://github.com/opencontainers/runtime-spec/pull/388
但为什么接着说docker exec --help:
Usage: docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
Run a command in a running container
这里的技巧是,exec
命令创建的辅助容器共享目标容器的所有隔离边界!即,相同的net
、pid
、mount
等命名空间、相同的cgroups
层次结构等。因此,从外部看,感觉就像在现有容器内部运行命令。
attach
和exec
命令的混淆是因为exec-uted
命令也是一个拥有自己的stdio
流的进程。因此,您可以选择是否exec
处于分离模式、是否保持标准输入打开、是否分配伪 TTY
等。此外,当exec-ing
时,中继看起来非常相似:
terminal <-> docker-cli <-> dockerd <-> shim <-> command's stdio streams
其他实现
上面的图表和例子主要是关于docker
的,但其他容器管理器,如containerd
或crio
,在运行、执行、attach
或logs
命令时表现类似。
Podman
可能是无守护进程容器管理器最突出的例子。然而,即使是 podman
也使用容器运行时垫片。当您attach
到达podman
的容器时,中继中的跳数就少了一跳。
不过,有趣的样本是 Kubernetes
。Kubernetes
不直接管理容器。相反,每个集群节点都有一个本地代理,称为kubelet
,它又期望节点上存在兼容的容器运行时。但是,在最低级别上,仍然存在相同的垫片和流程:
与docker
非常相似,Kubernetes
的命令行客户端(kubectl
)也提供了类似的UX
执行、attach
和logs
命令。不同之处在于Kubernetes
使用的是pod
而不是容器。幸运的是,pod
只是一组半熔合的容器,所以我们目前学到的所有东西仍然适用。
由于attahc
、logs
和exec
工作在容器级别上,每个kubectl attach
、kubectl logs
和kubectl exec
都需要指定目标容器(-c <name>
)以及pod名称。除非这个pod
用kubectl.kubernetes.io/default-container
注释过。
结论
所以,总结一下:
容器是隔离且受限的执行环境。 传统上,每个容器有一个主进程。 容器通常以分离模式启动(即,像守护进程)。 容器运行时 shim
包装容器进程并将其stdout
和stderr
流式传输到日志。运行时 shim
允许attach-ing
将终端与容器的stdio
流连接起来。可以重用已运行容器的隔离方式来启动容器。 该 exec
命令类似于run
从另一个容器重用所有命名空间和cgroup
的命令。由于 exec-ing
默认发生在attach
模式下,它可能看起来与attach
命令相似,但其目的和实现却大不相同。
参考资料
https://iximiuz.com/en/posts/containers-101-attach-vs-exec/
本文转载自:「云原生CTO」,原文:https://tinyurl.com/32etuh3s,版权归原作者所有。