调试 Docker 容器
自从我上周关于Docker 和 SSL 的文章进入前七名后,我一直在思考一些我可能知道但却习以为常的事情,这些事情或许对其他人也有用。我可以讨论很多内容,比如容器的内部工作原理、Docker 的实际应用、Docker 如何渗透到你的 iptables 配置中等等,但我想这周的文章我会专注于一些简单却强大且非常容易上手的内容:Docker 容器的调试。
多年来,我在观察 Docker 新用户时注意到,容器实际上看起来就像是用各种管道连接在一起的小黑盒子,除了几盏灯和没有火焰之外,没有任何东西可以表明发生了什么。
我想它看起来大概是这样的。
首先,要了解发生了什么,可以查看容器的日志。大多数人都知道这一点,但你可以使用命令来完成此操作docker logs,如下所示。
$ docker logs website
[WEBSITE] Connecting to API...
[WEBSITE] Metal Tube connected to API
[WEBSITE] Loading blog posts via metal tube from API
[WEBSITE] Blog posts loaded
[WEBSITE] Listening on port 80
有时候,事情需要更长时间。你只能耐心等待,不过没关系,我们可以使用“-f或关注”按钮来观察和等待。
$ docker logs -f api
[API] Connecting to database...
[API] Connection established via glass pipe
[API] Request received from website, loading data...
[API] Data loaded [ 4%]
[API] Data loaded [11%]
[API] Data loaded [19%]
[API] Data loaded [24%]
这对于将日志输出到标准输出()来说非常棒,stdout但你知道,有时候人们会把一些非常奇怪的东西容器化。我见过各种各样的容器,包括容器中的容器。
有时应用程序会将信息写入日志文件,所以docker logs这可能帮不了你。也许你的数据库实际上并没有执行任何操作。或许它的运行速度非常慢。容器正在运行……
$ docker ps
CONTAINER ID IMAGE STATUS NAMES
1e98dd08aee8 project/web Up 10 minutes website
b76207f35778 project/api Up 10 minutes api
35f9ba3ed4c8 project/db Up 10 minutes database
……但不知何故,当我们检查容器日志时,却没有任何明显的异常,我们需要深入调查。
$ docker logs database
[DATABASE] Loading...
[DATABASE] Listening on port 21000
我们需要找到隐藏的日志。为了找到它们,我们必须进入……
闹鬼的迷宫我们可以使用docker exec命令轻松运行容器。它的工作原理是,您指定一个命令和一个要运行该命令的容器,Docker 会在容器内执行该命令。如果我们运行一个交互式 shell,就可以探索容器内部。嘘,这里有一个链接,您可以阅读更多关于Docker exec 的信息。
$ docker exec -ti database bash
root@35f9ba3ed4c8:/#
我们已经成功连接。你会注意到我们使用了这些-ti标志。根据 Docker 文档,-t这些--tty标志会分配一个伪终端(pusable-TTY),并且-i即使--interactive未连接,也会保持标准输入(stdin)打开。我们只需要知道,它能让我们连接到容器,这样我们就可以四处看看,找到那些烦人的日志。
这真是一个非常强大的技巧,这些年来我用过无数次了。有时候你需要查看正在运行的应用程序,或者查找一些日志,又或者你需要在应用程序运行时(最好是直接修改代码,而不是走标准的构建流程)进行一些修改。
连接到容器并生成 shell 后,你就可以自由探索了,这非常有用。你可以轻松地阅读日志。
$ docker exec -ti database bash
root@35f9ba3ed4c8:/# tail -f /var/log/database.log
[MyDB][0.0001] started spooling database contraption
[MyDB][0.0004] reading tables from an ancient .dat file
[MyDB][0.0012] unable to read data, tables.dat corrupted...
宾果。
有时,您可能无法生成 bash shell,尤其是在容器基于经过精简和清理的镜像的情况下,但您通常总能确保它sh可用。
$ docker exec -ti database sh
# ps -p $$
PID TTY TIME CMD
56 pts/0 00:00:00 sh
还有一些其他实用技巧。你可能不会经常用到它们,但偶尔它们会派上用场。首先是…… docker top。这类似于top我们都很熟悉的……,只不过它是在容器中运行的。容器通常只运行一个进程,但并非总是如此。
$ docker top database
PID USER TIME COMMAND
2157 999 0:35 mydb --bind_ip_all
然后就是这个docker stats命令。你可以针对特定容器运行它,也可以查看所有正在运行的容器。当你想要监控负载下的容器,或者怀疑存在内存泄漏时,它非常有用。我稍微简化了一下输出,但你应该明白我的意思。
$ docker stats api
ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O
b76207 api 0.31% 44.52MiB / 15.64GiB 0.28% 1.75kB / 0B 1.64MB / 14.2MB
现在,当我们运行命令时docker ps,我们可以看到一些关于容器的信息:它正在运行的镜像、它的状态、它的创建时间等等,然而容器实际上在幕后还有更多信息,例如它们的文件系统存储在哪里、它们被分配了什么 IP 地址,我们可以通过命令查看所有这些信息docker inspect。
实际输出内容要多得多,所以我只截取了一部分,以便您大致了解其形式。这大概只占实际可用信息的10%。
$ docker inspect website
[
{
"Id": "1e98dd08aee8b1c6e79eccf0551f4af88299a52fec567a8b27ad4711fe2ac287",
"Created": "2020-02-19T15:25:13.582120171Z",
"State": {
"Status": "running",
"Running": true,
"Paused": false,
"Restarting": false,
"OOMKilled": false,
"Dead": false
},
"Image": "sha256:6d5319f761c08cb3053c676a274ce6500f31c98fb1c1fcab8ab736e39968a2fe",
"LogPath": "/var/lib/docker/containers/1e1933e26402e3b062051c63c3fbc6d327641059213192ab6b94d1afb36484ca/1e1933e26402e3b062051c63c3fbc6d327641059213192ab6b94d1afb36484ca-json.log",
"Name": "/website",
"RestartCount": 0,
"Driver": "overlay2",
"Platform": "linux"
}
]
如果你知道要查找的内容,提取信息比浏览整个文档要容易得多。你可以使用--format参数来实现这一点。例如,获取容器的 IP 地址。
$ docker inspect --format '{{.NetworkSettings.IPAddress}}' api
172.17.0.3
我要介绍的最后一个技巧虽然与 Docker 关系不大,但仍然非常实用。有时,你可能在容器中运行着一些服务,但却无法连接到它们。这是防火墙问题?网络问题?还是应用程序问题?遇到问题时,最好的办法就是对可能的原因进行二分查找,也就是说,先排除一半,然后再排除另一半。例如,如果你能通过网络连接两个进程,那么就可以排除网络问题。
我们可以使用 netcat 来实现这一点nc。它或许是您数字工具箱中最通用、最强大的工具之一。其概念很简单:在一端创建一个监听器,在另一端创建一个连接器。
我们来创建一个桥接网络和两个容器来演示:一个运行 Alpine 系统,一个运行 Ubuntu 系统。有时ncAlpine 系统是预装的,有时我们需要安装 Ubuntu 系统,所以我们接下来就来看一下安装过程。
$ docker network create metal-tube
7507bfb685510ae7e93f808cdc2392bedf25b2912cc77abc3801d6c575795d30
--------------------------------------------------------------------
$ docker run --rm -ti --network metal-tube --name jeff alpine
jeff#
--------------------------------------------------------------------
$ docker run --rm -ti --network metal-tube --name alan ubuntu
alan# apt update -qq && apt install netcat -qqy
alan#
好了,准备就绪。我们在同一个网络上有两个容器,我们metal-tube把它们分别命名为jeff和。为了方便阅读,alan我把终端提示符改成了jeff#和。alan#
我之前在《使用 Let's Encrypt 和 Nginx 自动配置 SSL》这篇文章中讨论过用户自定义桥接网络,但最重要的是要知道它们内置了 DNS 服务器,允许你通过名称解析容器。不信?那就问问 Jeff 的 Alan 吧。
jeff# ping alan
PING alan (172.21.0.3): 56 data bytes
64 bytes from 172.21.0.3: seq=0 ttl=64 time=0.247 ms
现在,我们来看看能否从 Alan 连接到 Jeff。为此,我们首先在 Alan 上使用 netcat 设置一个监听器,并启用-llisten 参数、-p本地端口参数,以及为了了解通信内容而启用的-vv详细输出参数。
alan# nc -vvlp 9000
listening on [any] 9000 ...
现在它会一直挂起,直到收到数据。在 Jeff 服务器上,我们可以尝试通过 netcat 发送一些数据。具体做法是通过管道将一些数据回显到 netcat 进程中|。我们会-vv再次使用非常详细的输出标志。
jeff# echo "hello" | nc -vv alan 9000
alan (172.21.0.3:9000) open
sent 6, rcvd 0
太好了,看来成功了。如果失败了,我们可能会看到类似这样的错误信息nc: alan (172.21.0.3:9000): Connection refused。再运行一次命令,你就会明白了。一旦与 netcat 监听器的连接关闭,监听器进程就会退出。不过现在我们来看看 Alan 的情况。
alan# nc -vvlp 9000
listening on [any] 9000 ...
connect to [172.21.0.3] from jeff.metal-tube [172.21.0.2] 39993
hello
sent 0, rcvd 6
测试成功,因此我们知道这两个容器之间的 TCP 连接工作正常。现在,在本地环境下它们可以正常工作,但有时服务可能位于不同的物理服务器上,而这通常是测试机器间连接性的绝佳方法。您也不一定需要设置 netcat 监听器,可以直接使用 netcat 连接到服务,检查其是否正常运行并接受连接。这同样非常有用。
还有很多其他技巧可以用来调试容器,例如覆盖入口点、映射卷来监控文件系统等等,但以上这些是我95%的时间都会用到的。如果你有什么好技巧,不妨在下面的评论区分享一下?
文章来源:https://dev.to/adamkdean/debugging-docker-containers-17fh




