如何调试和修复常见的Docker问题

来自菜鸟教程
跳转至:导航、​搜索

介绍

Docker 使您可以轻松地将应用程序和服务包装在容器中,以便您可以在任何地方运行它们。 不幸的是,在构建映像和集成应用程序所需的所有层时可能会出现问题,尤其是当您不熟悉 Docker 映像和容器时。 在与其他容器通信时,您可能会遇到拼写错误、运行时库和模块问题、命名冲突或问题。

在此针对 Docker 新手的故障排除指南中,您将解决构建 Docker 映像时的问题、解决运行容器时的命名冲突以及修复容器之间通信时出现的问题。

先决条件

要完成本教程,您需要

  • Docker 安装在服务器或本地机器上。

要在服务器上安装 Docker,您可以按照操作指南 for CentOS 7for 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 本身只存在了三年,而且可能相当复杂。 花点时间熟悉这些术语和 生态系统 ,你会发现一些起初有点陌生的概念很快就会变得很有意义。