如何调试和修复常见的Docker问题
介绍
Docker 使您可以轻松地将应用程序和服务包装在容器中,以便您可以在任何地方运行它们。 不幸的是,在构建映像和集成应用程序所需的所有层时可能会出现问题,尤其是当您不熟悉 Docker 映像和容器时。 在与其他容器通信时,您可能会遇到拼写错误、运行时库和模块问题、命名冲突或问题。
在此针对 Docker 新手的故障排除指南中,您将解决构建 Docker 映像时的问题、解决运行容器时的命名冲突以及修复容器之间通信时出现的问题。
先决条件
要完成本教程,您需要
- Docker 安装在服务器或本地机器上。
要在服务器上安装 Docker,您可以按照操作指南 for CentOS 7 或 for Ubuntu 16.04 。
您可以访问Docker网站或按照官方安装文档在本地机器上安装Docker。
第 1 步 — 解决 Dockerfile 的问题
您可能遇到的最常见问题是当您从 Dockerfile
构建 Docker 映像时。 在我们深入研究之前,让我们澄清一下图像和容器之间的区别。
- 图像 是一个只读资源,您使用名为
Dockerfile
的配置文件创建。 这是您通过 Docker Hub 或您的私有注册表发布和共享的内容。 - container 是您根据构建的映像创建的读取 和 写入实例。
您可以在教程 Docker 解释:使用 Dockerfiles 自动构建映像 中了解有关这些概念的更多信息。
当您查看 Dockerfile
时,您可以清楚地看到 Docker 使用构建映像的分步过程,因为 Dockerfile
中的每一行对应于该过程中的一个步骤。 这通常意味着如果您到达某个步骤,那么前面的所有步骤都成功完成。
让我们创建一个小项目来探讨您在使用 Dockerfile
时可能遇到的一些问题。 在您的主目录中创建一个 docker_image
目录,然后使用 nano
或您喜欢的编辑器在该文件夹中创建一个 Dockerfile
mkdir ~/docker_image nano ~/docker_image/Dockerfile
将以下内容添加到这个新文件中:
~/docker_image/Dockerfile
# base image FROM debian:latest # install basic apps RUN aapt-get install -qy nano
这段代码中有一个故意的错字。 你能发现吗? 尝试从这个文件构建一个镜像,看看 Docker 如何处理一个错误的命令。 使用以下命令创建映像:
docker build -t my_image ~/docker_image
您将在终端中看到此消息,指示错误:
OutputStep 2 : RUN aapt-get install -qy nano ---> Running in 085fa10ffcc2 /bin/sh: 1: aapt-get: not found The command '/bin/sh -c aapt-get install -qy nano' returned a non-zero code: 127
最后的错误信息表示步骤 2 中的命令有问题。 在这种情况下,这是我们故意的错字:我们使用 aapt-get
而不是 apt-get
。 但这也意味着上一步执行正确。
修改Dockerfile
并进行修正:
Dockerfile
# install basic apps RUN apt-get install -qy nano
现在再次运行 docker build
命令:
docker build -t my_image ~/docker_image
现在您将看到以下输出:
OutputSending build context to Docker daemon 2.048 kB Step 1 : FROM debian:latest ---> ddf73f48a05d Step 2 : RUN apt-get install -qy nano ---> Running in 9679323b942f Reading package lists... Building dependency tree... E: Unable to locate package nano The command '/bin/sh -c apt-get install -qy nano' returned a non-zero code: 100
纠正错字后,该过程加快了一点,因为 Docker 缓存了第一步,而不是重新下载基础映像。 但正如你从输出中看到的,我们有一个新的错误。
我们用作映像基础的 Debian 发行版找不到文本编辑器 nano
,尽管我们知道它在 Debian 软件包存储库中可用。 基本映像带有缓存的元数据,例如存储库和可用包的列表。 当您从中提取数据的实时存储库发生更改时,您可能偶尔会遇到一些缓存问题。
要解决此问题,请修改 Dockerfile 以在安装任何新软件包之前 对源 进行清理和更新。 再次打开配置文件:
nano ~/docker_image/Dockerfile
将以下突出显示的行添加到文件中,above 安装 nano
的命令:
~/docker_image/Dockerfile
# base image FROM debian:latest # clean and update sources RUN apt-get clean && apt-get update # install basic apps RUN apt-get install -qy nano
保存文件并再次运行 docker build
命令:
docker build -t my_image ~/docker_image
这次该过程成功完成。
OutputSending build context to Docker daemon 2.048 kB Step 1 : FROM debian:latest ---> a24c3183e910 Step 2 : RUN apt-get install -qy nano ---> Running in 2237d254f172 Reading package lists... Building dependency tree... Reading state information... Suggested packages: spell The following NEW packages will be installed: nano ... ---> 64ff1d3d71d6 Removing intermediate container 2237d254f172 Successfully built 64ff1d3d71d6
让我们看看当我们将 Python 3 和 PostgreSQL 驱动程序添加到我们的映像时会发生什么。 再次打开 Dockerfile
。
nano ~/docker_image/Dockerfile
并添加两个新步骤来安装 Python 3 和 Python PostgreSQL 驱动程序:
~/docker_image/Dockerfile
# base image FROM debian:latest # clean and update sources RUN apt-get clean && apt-get update # install basic apps RUN apt-get install -qy nano # install Python and modules RUN apt-get install -qy python3 RUN apt-get install -qy python3-psycopg2
保存文件,退出编辑器,重新构建镜像:
docker build -t my_image ~/docker_image
从输出中可以看出,软件包安装正确。 由于前面的步骤被缓存,该过程也完成得更快。
OutputSending build context to Docker daemon 2.048 kB Step 1 : FROM debian:latest ---> ddf73f48a05d Step 2 : RUN apt-get clean && apt-get update ---> Using cache ---> 2c5013476fbf Step 3 : RUN apt-get install -qy nano ---> Using cache ---> 4b77ac535cca Step 4 : RUN apt-get install -qy python3 ---> Running in 93f2d795fefc Reading package lists... Building dependency tree... Reading state information... The following extra packages will be installed: krb5-locales libgmp10 libgnutls-deb0-28 libgssapi-krb5-2 libhogweed2 libk5crypto3 libkeyutils1 libkrb5-3 libkrb5support0 libldap-2.4-2 libnettle4 libp11-kit0 libpq5 libsasl2-2 libsasl2-modules libsasl2-modules-db libtasn1-6 Suggested packages: gnutls-bin krb5-doc krb5-user libsasl2-modules-otp libsasl2-modules-ldap libsasl2-modules-sql libsasl2-modules-gssapi-mit libsasl2-modules-gssapi-heimdal python-psycopg2-doc The following NEW packages will be installed: krb5-locales libgmp10 libgnutls-deb0-28 libgssapi-krb5-2 libhogweed2 libk5crypto3 libkeyutils1 libkrb5-3 libkrb5support0 libldap-2.4-2 libnettle4 libp11-kit0 libpq5 libsasl2-2 libsasl2-modules libsasl2-modules-db libtasn1-6 python3-psycopg2 0 upgraded, 18 newly installed, 0 to remove and 0 not upgraded. Need to get 5416 kB of archives. After this operation, 10.4 MB of additional disk space will be used. ... Processing triggers for libc-bin (2.19-18+deb8u6) ... ---> 978e0fa7afa7 Removing intermediate container d7d4376c9f0d Successfully built 978e0fa7afa7
注意:Docker 缓存了构建过程,所以你可能会遇到这样一种情况:你在构建中运行更新,Docker 缓存了这个更新,一段时间后你的基础发行版再次更新它的源,留下你过时的来源,尽管在 Dockerfile
中进行了清理和更新。 如果您在容器内安装或更新软件包时遇到问题,请在容器内运行 apt-get clean && apt-get update
。
密切关注 Docker 输出以确定拼写错误的位置,并在构建时和容器内部运行更新,以确保您不会受到缓存包列表的阻碍。
语法错误和缓存问题是您在 Docker 中构建映像时可能遇到的最常见问题。 现在让我们看看从这些镜像运行容器时可能出现的问题。
第 2 步 — 解决容器命名问题
随着您启动更多容器,您最终会遇到名称冲突。 命名冲突是您尝试创建与系统上已存在的容器同名的容器。 让我们探索如何正确处理命名、重命名和删除容器以避免冲突。
让我们从我们在上一节中构建的镜像启动一个容器。 我们将在这个容器内运行一个交互式 bash 解释器来测试。 执行以下命令:
docker run -ti my_image bash
当容器启动时,你会看到一个等待指令的 root 提示:
现在您有了一个正在运行的容器,让我们看看您可能会遇到什么样的问题。
当您以刚才的方式运行容器时,无需显式设置名称,Docker 会为容器分配一个随机名称。 您可以通过在正在运行的容器之外的 Docker 主机上运行 docker ps
命令来查看所有正在运行的容器及其对应的名称。
在 Docker 主机上打开一个新终端并运行以下命令:
docker ps
此命令输出正在运行的容器列表及其名称,如以下示例所示:
OutputCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 80a0ca58d6ec my_image "bash" 22 seconds ago Up 28 seconds loving_brahmagupta
上述输出中的名称 loving_brahmagupta
是上例中 Docker 自动分配给容器的名称; 你的会有不同的名字。 在非常简单的情况下,让 Docker 为您的容器分配一个名称是可以的,但可能会带来严重的问题; 当我们部署时,我们需要一致地命名容器,以便我们可以引用它们并轻松地自动化它们。
要为容器指定名称,我们可以在启动容器时使用 --name
参数,也可以将正在运行的容器重命名为更具描述性的名称。
从 Docker 主机的终端运行以下命令:
docker rename your_container_name python_box
然后列出您的容器:
docker ps
您将在输出中看到 python_box
容器,确认您已成功重命名容器:
OutputCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 80a0ca58d6ec my_image "bash" 24 minutes ago Up 24 minutes python_box
要关闭容器,请在包含正在运行的容器的终端的提示符处键入 exit
:
exit
如果这不是一个选项,您可以使用以下命令从 Docker 主机上的另一个终端终止容器:
docker kill python_box
当你以这种方式杀死容器时,Docker 返回刚刚被杀死的容器的名称:
Outputpython_box
要确保 python_box
不再存在,请再次列出所有正在运行的容器:
docker ps
正如预期的那样,容器不再列出:
OutputCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
现在您可能认为您可以启动另一个名为 python_box
的容器,但让我们看看当我们尝试时会发生什么。
这次我们将使用 --name
参数来设置容器的名称:
docker run --name python_box -ti my_image bash
Outputdocker: Error response from daemon: Conflict. The name "/python_box" is already in use by container 80a0ca58d6ecc80b305463aff2a68c4cbe36f7bda15e680651830fc5f9dda772. You have to remove (or rename) that container to be able to reuse that name.. See 'docker run --help'.
当您构建映像并重用现有映像的名称时,现有映像将被覆盖,正如您已经看到的那样。 容器稍微复杂一些,因为您无法覆盖已经存在的容器。
Docker 说 python_box
已经存在,即使我们刚刚杀死它,它甚至没有与 docker ps
一起列出。 它没有运行,但如果您想再次启动它,它仍然可用。 我们停止了它,但我们没有删除它。 docker ps
命令只显示 running 容器,而不是 all 容器。
要列出 Docker 容器的 all、运行和其他情况,请将 -a
标志(--all
的别名)传递给 docker ps
:
docker ps -a
现在我们的 python_box
容器出现在输出中:
OutputCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 80a0ca58d6ec my_image "bash" 12 minutes ago Exited (137) 6 minutes ago python_box
该容器以 Exited (137)
状态存在,这就是我们在尝试创建具有相同名称的新容器时遇到命名问题的原因。
当你想完全移除一个容器时,你使用 docker rm
命令。 在终端中执行此命令:
docker rm python_box
Docker 再次输出刚刚删除的容器的名称:
Outputpython_box
Warning:如果容器仍在运行,此命令将失败并输出错误消息,因此请确保先停止或终止它。
现在我们删除了前一个容器,让我们创建一个名为 python_box
的新容器:
docker run --name python_box -ti my_image bash
该过程完成,我们再次看到一个 root shell:
现在让我们杀死并删除容器,以避免将来出现问题。 从 Docker 主机上的另一个终端会话中,终止容器并使用以下命令将其删除:
docker kill python_box && docker rm python_box
我们将两个命令链接在一起,因此输出会显示两次容器名称。 第一个输出验证我们已经杀死了容器,另一个确认我们已经删除了它。
Outputpython_box python_box
遇到名称问题时请记住 docker ps -a
,并确保在尝试使用相同名称重新创建容器之前停止并删除容器。
命名容器可以让您更轻松地管理基础架构。 名称还使容器之间的通信变得容易,您将在接下来看到。
第 3 步 — 解决容器通信问题
Docker 使实例化多个容器变得容易,因此您可以在每个容器中运行不同甚至冗余的服务。 如果一项服务出现故障或受到损害,您只需将其替换为新服务,同时保持其余基础设施完好无损。 但是您可能会遇到使这些容器相互通信的问题。
让我们创建两个可以通信的容器,这样我们就可以探索潜在的通信问题。 我们将使用我们现有的镜像创建一个运行 Python 的容器,以及另一个运行 PostgreSQL 实例的容器。 我们将为该容器使用 Docker Hub 提供的官方 PostgreSQL 映像。
让我们首先创建 PostgreSQL 容器。 我们将通过使用 --name
标志为这个容器命名,以便在将它与其他容器链接时可以轻松识别它。 我们称之为 postgres_box
。
以前,当我们启动一个容器时,它会在前台运行,接管我们的终端。 我们想在后台启动 PostgreSQL 数据库容器,我们可以使用 --detach
标志来完成。
最后,我们将运行 postgres
命令,而不是运行 bash
,该命令将在容器内启动 PostgreSQL 数据库服务器。
执行以下命令启动容器:
docker run --name postgres_box --detach postgres
Docker 将从 Docker Hub 下载镜像并创建容器。 然后它将返回在后台运行的容器的完整 ID:
OutputUnable to find image 'postgres:latest' locally latest: Pulling from library/postgres 6a5a5368e0c2: Already exists 193f770cec44: Pull complete ... 484ac0d6f901: Pull complete Digest: sha256:924650288891ce2e603c4bbe8491e7fa28d43a3fc792e302222a938ff4e6a349 Status: Downloaded newer image for postgres:latest f6609b9e96cc874be0852e400381db76a19ebfa4bd94fe326477b70b8f0aff65
列出容器以确保这个新容器正在运行:
docker ps
输出确认 postgres_box
容器正在后台运行,暴露端口 5432
,PostgreSQL 数据库端口:
OutputCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 7a230b56cd64 postgres_box "/docker-entrypoint.s" Less than a second ago Up 2 seconds 5432/tcp postgres
现在让我们启动 Python 容器。 为了让 Python 容器内运行的程序能够“看到”postgres_box
容器中的服务,我们需要使用 postgres_box
容器X201X] 参数。 要创建链接,我们指定容器的名称,后跟链接的名称。 我们将使用链接名称从 Python 容器内部引用 postgres_box
容器。
发出以下命令以启动 Python 容器:
docker run --name python_box --link postgres_box:postgres -ti my_image bash
现在让我们尝试从 python_box
容器内部连接到 PostgreSQL。
我们之前在 python_box
容器中安装了 nano
,所以让我们用它来创建一个简单的 Python 脚本来测试与 PostgreSQL 的连接。 在 python_box
容器的终端中,执行以下命令:
nano pg_test.py
然后将以下 Python 脚本添加到文件中:
pg_test.py
"""Test PostgreSQL connection.""" import psycopg2 conn = psycopg2.connect(user='postgres') print(conn)
保存文件并退出编辑器。 让我们看看当我们尝试从脚本连接到数据库时会发生什么。 在容器中执行脚本:
python3 pg_test.py
我们看到的输出表明连接到数据库时出现问题:
OutputTraceback (most recent call last): File "pg_test.py", line 5, in <module> conn = psycopg2.connect(database="test", user="postgres", password="secret") File "/usr/lib/python3/dist-packages/psycopg2/__init__.py", line 164, in connect conn = _connect(dsn, connection_factory=connection_factory, async=async) psycopg2.OperationalError: could not connect to server: No such file or directory Is the server running locally and accepting connections on Unix domain socket "/var/run/postgresql/.s.PGSQL.5432"?
我们已经确保 postgres_box
容器正在运行,并且我们已经将它链接到 python_box
容器,那么发生了什么? 好吧,当我们尝试连接时,我们从未指定数据库主机,所以 Python 尝试连接到本地运行的数据库,但这不起作用,因为服务不在本地运行,它在不同的容器中运行,就好像它在另一台计算机上。
您可以使用创建链接时设置的名称访问链接容器。 在我们的例子中,我们使用 postgres
来引用运行我们的数据库服务器的 postgres_box
容器。 您可以通过查看 python_box
容器中的 /etc/hosts
文件来验证这一点:
cat /etc/hosts
您将看到所有可用的主机及其名称和 IP 地址。 我们的 postgres
服务器清晰可见。
Output127.0.0.1 localhost ::1 localhost ip6-localhost ip6-loopback fe00::0 ip6-localnet ff00::0 ip6-mcastprefix ff02::1 ip6-allnodes ff02::2 ip6-allrouters 172.17.0.2 postgres f6609b9e96cc postgres_box 172.17.0.3 3053f74c8c13
所以让我们修改我们的 Python 脚本并添加主机名。 打开文件。
nano pg_test.py
然后在连接字符串中指定主机:
/pg_test.py
"""Test PostgreSQL connection.""" import psycopg2 conn = psycopg2.connect(host='postgres', user='postgres') print(conn)
保存文件,然后再次运行脚本。
python3 pg_test.py
这次脚本完成没有任何错误:
Output<connection object at 0x7f64caec69d8; dsn: 'user=postgres host=7a230b56cd64', closed: 0>
当您尝试连接到其他容器中的服务时,请记住容器名称,并编辑您的应用程序凭据以引用这些容器的链接名称。
结论
我们刚刚介绍了您在使用 Docker 容器时可能遇到的最常见问题,从构建镜像到部署容器网络。
Docker 有一个 --debug
标志,主要用于 Docker 开发人员。 但是,如果想了解更多关于 Docker 内部的信息,请尝试在调试模式下运行 Docker 命令以获得更详细的输出:
docker -D [command] [arguments]
虽然软件中的容器已经存在了一段时间,但 Docker 本身只存在了三年,而且可能相当复杂。 花点时间熟悉这些术语和 生态系统 ,你会发现一些起初有点陌生的概念很快就会变得很有意义。