Docker,这个让无数开发者和运维工程师高呼“真香”的容器化技术,凭借其轻量、快速、可移植的特性,极大地简化了应用的开发、测试和部署流程。但即便是再“香”的技术,也难免有“闹脾气”的时候。你是不是也遇到过这样的“抓狂瞬间”:docker pull
一个镜像,结果半天拉不下来,或者提示“找不到镜像”?好不容易拉下来了,docker run
一下,容器秒退,连个影子都看不见?或者容器跑起来了,却发现它像个“孤岛”,既访问不了外部网络,外部也访问不了它?
别急,这些都是 Docker 使用过程中非常常见的“拦路虎”。就像开车上路,偶尔会遇到爆胎、熄火、或者导航失灵一样,使用 Docker 时遇到这些问题,并不代表 Docker 本身不好,而是说明我们需要掌握一些基本的“故障排查”和“维修保养”技巧。毕竟,再厉害的“老司机”,也得懂点“修车”的门道,对吧?
这篇“Docker 排障实战指南”,就是要为你装备一个“Docker 急救箱”。我们将聚焦于 Docker 用户最常遇到的三大类“疑难杂症”:镜像拉取失败、容器无法启动、以及容器网络不通。针对每一种问题,我们会分析常见的错误现象,提供一套系统化的排查思路,并给出相应的解决方案和实用命令。让你在下次遇到这些“小麻烦”时,能够从容应对,快速定位问题,让你的 Docker 之旅重新畅通无阻!
排查第一守则:万能的 docker logs
与 docker inspect
– 你的“听诊器”和“显微镜”
在咱们一头扎进各种具体的错误场景之前,有两件“法宝”你必须先掌握,它们几乎是排查所有 Docker 容器问题的起点,就像医生的“听诊器”和“显微镜”一样重要:
docker logs <容器名或容器ID>
:查看容器的“心声”。 对于那些正在运行,或者刚刚启动就“光速”退出的容器,docker logs
命令会显示该容器内应用程序的标准输出 (STDOUT) 和标准错误 (STDERR)。这里面往往就包含了应用程序为什么会启动失败、或者为什么会异常退出的直接原因。比如,可能是配置文件没找到、数据库连不上、端口被占用(容器内部的)、或者代码里抛了个未捕获的异常等等。所以,遇到容器启动问题,第一反应永远是去看docker logs
!docker inspect <容器名/ID 或 镜像名/ID>
:容器/镜像的“体检报告”。docker inspect
命令会以 JSON 格式输出关于某个容器或镜像的非常详细的配置信息和元数据。你可以从中看到容器的启动命令、环境变量、网络设置(IP 地址、端口映射、连接的网络)、挂载的数据卷、健康检查状态、甚至是容器被 OOM Killer “干掉”的记录等等。当你想深入了解一个容器的“生理构造”或查找某些配置细节时,docker inspect
是你的不二之选。
记住这两件“法宝”,能让你在排查 Docker 问题时少走很多弯路。
“仓库空空如也?”—— Docker 镜像拉取失败排查
当你兴致勃勃地想从 Docker Hub 或其他镜像仓库拉取一个镜像来尝鲜或部署应用,结果 docker pull some_image:latest
命令却给你甩回来一堆错误,那感觉可真不爽。别急,我们来看看常见的“拉不动”原因和对策。
常见错误信息“诊断书”:
manifest for <image_name>:<tag> not found
或Error response from daemon: manifest for ... not found: manifest unknown: manifest unknown
:这通常意味着你指定的镜像名称或标签(tag)在仓库中不存在。pull access denied for <image_name>, repository does not exist or may require 'docker login'
:这说明你可能尝试拉取一个私有镜像,但没有登录;或者镜像确实不存在,或者仓库名拼错了。Error response from daemon: Get https://registry-1.docker.io/v2/...: net/http: TLS handshake timeout
或dial tcp: lookup index.docker.io: no such host
或类似的网络错误:这些通常指向网络连接问题,比如 DNS 解析失败、防火墙拦截、或者网络本身不通。no space left on device
:磁盘空间不足了,没地方放新拉下来的镜像。Error response from daemon: ... (i/o timeout)
或连接被重置:可能是网络不稳定,或者 Docker Hub/私有仓库本身暂时有问题。
排查思路与“药方”:
- 镜像名称和标签(Tag)是否正确无误? 这是最容易犯的低级错误。仔细检查你输入的镜像名称和标签是否拼写正确,特别是官方镜像 (如
nginx
,ubuntu
) 和用户/组织下的镜像 (如bitnami/wordpress
,yourname/my-custom-image
) 的格式。一个字母、一个数字、一个下划线或连字符的差别,都可能导致“查无此镜”。很多时候,我们想当然地以为某个镜像是官方的,但实际上它可能在某个组织名下。同时,确认你指定的标签确实存在,不是所有的镜像都有:latest
标签,或者你可能需要一个特定版本的标签(如:1.20-alpine
)。可以去 Docker Hub (hub.docker.com
) 或你的私有仓库网站上搜索确认一下。 - 如果你拉取的是私有镜像,是否已经登录 (
docker login
)? 私有镜像仓库(包括 Docker Hub 上的私有仓库,以及像 Harbor, JFrog Artifactory, AWS ECR, Azure ACR, Google GCR 等自建或云厂商提供的私有仓库)通常都需要你先使用docker login <你的仓库地址>
命令,输入用户名和密码(或访问令牌)进行认证后才能拉取。如果忘了登录,或者登录凭证已过期,就会收到pull access denied
的错误。 [提示:请将以下代码片段复制并粘贴到 WordPress 的“代码”区块中]# 登录 Docker Hub (默认) sudo docker login # 登录指定的私有仓库 sudo docker login your-private-registry.example.com
- 网络连接是否畅通?DNS 解析是否正常? 这是导致拉取失败的另一大常见原因。你需要确认:
- 你的服务器能否访问外网? 尝试
ping baidu.com
或curl https://www.baidu.com
看看。 - DNS 解析是否能正确解析 Docker Hub 或你的私有仓库的域名? 尝试
ping registry-1.docker.io
(Docker Hub 的实际后端之一) 或ping your-private-registry.example.com
。如果 ping 不通域名但能 ping 通 IP(比如ping 8.8.8.8
),那很可能是 DNS 问题。检查你服务器的/etc/resolv.conf
文件中的 DNS 服务器设置是否正确。有时候,重启 Docker 服务 (sudo systemctl restart docker
) 也能刷新 Docker 守护进程的 DNS 配置。 - 防火墙或安全组是否拦截了出站连接? 确保你的服务器防火墙(如
ufw
,firewalld
,iptables
)或云平台的安全组规则没有阻止 Docker 守护进程访问外部仓库的 HTTPS (443) 端口。 - 是否需要配置 HTTP/HTTPS 代理? 如果你的服务器处于一个需要通过代理才能访问外网的环境,你需要为 Docker 守护进程配置代理。具体方法是编辑 Docker 的 systemd 服务配置文件(通常在
/etc/systemd/system/docker.service.d/http-proxy.conf
或类似路径)或者 Docker 配置文件/etc/docker/daemon.json
,添加类似HTTPS_PROXY=http://your-proxy-server:port/
的设置,然后重载配置并重启 Docker 服务。 - Docker Hub 或私有仓库本身是否暂时服务不可用? 偶尔,即使是 Docker Hub 也可能出现服务中断。可以查看 Docker 的官方状态页面 (
status.docker.com
) 或你的私有仓库服务商的状态通知。
- 你的服务器能否访问外网? 尝试
- 磁盘空间是否不足? Docker 镜像(特别是包含很多层或大文件的镜像)可能会占用不少磁盘空间。如果你的服务器磁盘(通常是
/var/lib/docker
所在的那个分区)满了,拉取新镜像自然会失败,并提示no space left on device
。使用df -h
命令检查磁盘使用情况。 解决方法: 清理磁盘空间!首先,可以使用 Docker 自带的清理命令来移除不再使用的容器、镜像、数据卷和网络: [提示:请将以下代码片段复制并粘贴到 WordPress 的“代码”区块中]sudo docker system prune # 清理已停止的容器、悬空镜像、未使用网络等 sudo docker system prune -a # 清理所有未被容器使用的镜像和已停止的容器等 (更彻底) sudo docker volume prune # 清理所有未被容器使用的数据卷
如果这些还不够,你可能需要手动删除一些不再需要的大型镜像 (docker rmi <image_id>
),或者清理系统上其他占空间的文件。 - Docker 守护进程 (Daemon) 是否运行正常? 检查 Docker 服务的状态:
sudo systemctl status docker
。如果服务未运行或处于失败状态,尝试重启它:sudo systemctl restart docker
。你还可以查看 Docker 守护进程的日志来获取更详细的错误信息:sudo journalctl -u docker.service -f
(实时跟踪) 或sudo journalctl -u docker.service --since "1 hour ago"
(查看最近一小时的日志)。
“引擎发动失败?”—— Docker 容器无法启动排查
镜像终于拉下来了,你信心满满地敲下 docker run ...
,结果容器要么瞬间退出,要么在 docker ps
里根本看不到它的身影,或者虽然在 docker ps -a
里能看到它(状态通常是 Exited (X)
),但就是跑不起来。这就像汽车引擎发动了一下就熄火,或者根本打不着火,真是让人头大。
常见“症状”表现:
- 执行
docker run
命令后,命令提示符立刻返回,没有任何错误信息,但用docker ps
查看不到正在运行的容器。 - 使用
docker ps -a
可以看到容器,但其状态是Exited (X)
,其中X
是一个非零的退出码,比如Exited (1)
,Exited (127)
,Exited (137)
。不同的退出码可能暗示不同的错误原因。 - 容器虽然在运行 (
docker ps
能看到),但容器内的应用程序没有按预期工作(比如 Web 服务端口不通,或者应用日志里报错)。
排查思路与“药方”:
- 第一要务:查看容器日志 (
docker logs <container_name_or_id>
)! 再次强调,这是排查容器启动失败的**首要步骤,也是最重要的步骤**。容器内的应用程序在启动过程中如果遇到问题(比如配置文件缺失、依赖服务无法连接、参数错误、端口被占用等),通常会将错误信息输出到标准输出或标准错误,这些都会被 Docker 捕获并记录在容器日志中。 [提示:请将以下代码片段复制并粘贴到 WordPress 的“代码”区块中]# 假设你的容器名叫 my-app-container,或者用它的 ID sudo docker logs my-app-container
仔细阅读日志内容,它往往会直接告诉你应用程序为什么启动失败。比如,你可能会看到类似 "FileNotFoundException: /app/config.yml", "Error: connect ECONNREFUSED 127.0.0.1:3306", "Address already in use: :8080" 这样的具体错误。 - 检查 Dockerfile 中的
CMD
或ENTRYPOINT
指令是否正确。CMD
和ENTRYPOINT
定义了容器启动时默认执行的命令。如果这个命令本身写错了(比如可执行文件路径不对、参数错误、或者脚本本身有语法错误),容器自然无法正常启动。 排查方法:- 仔细检查 Dockerfile 中
CMD
/ENTRYPOINT
的语法,特别是路径和参数。 - 尝试以交互模式进入容器(如果镜像本身包含 shell 的话),手动执行一下
CMD
/ENTRYPOINT
中的命令,看看是否能成功运行,以及会报什么错。 [提示:请将以下代码片段复制并粘贴到 WordPress 的“代码”区块中]# 假设你的镜像是 my-image,并且它里面有 /bin/sh # --rm 表示容器退出后自动删除,-it 表示交互式终端 sudo docker run --rm -it --entrypoint /bin/sh my-image # 进入容器后,手动执行你 CMD/ENTRYPOINT 里的命令 # /app/start-my-app.sh --config /etc/app.conf
- 确保
CMD
/ENTRYPOINT
中指定的可执行文件在镜像是存在的,并且有执行权限(特别是如果你在 Dockerfile 中使用了USER
指令切换到非 root 用户)。
- 仔细检查 Dockerfile 中
- 宿主机端口是否与容器映射的端口冲突? 如果你在
docker run
时使用了-p <宿主机端口>:<容器端口>
(比如-p 80:8080
) 来将容器的某个端口映射到宿主机上,但宿主机的那个端口(比如 80)已经被其他服务占用了,那么容器就无法启动,并且通常会提示类似 "Error starting userland proxy: listen tcp 0.0.0.0:80: bind: address already in use" 的错误。 解决方法:- 在宿主机上使用
sudo netstat -tulnp | grep :<宿主机端口号>
(比如sudo netstat -tulnp | grep :80
) 命令,查看是哪个进程占用了该端口。 - 要么停掉占用该端口的宿主机服务(如果你不需要它了),要么在
docker run
时换一个宿主机上未被占用的端口进行映射(比如-p 8081:8080
)。
- 在宿主机上使用
- 数据卷挂载 (Volume Mounts) 是否有问题? 如果你在
docker run
时使用了-v <宿主机路径>:<容器内路径>
来挂载数据卷,可能会因为以下原因导致容器启动失败或运行异常:- 宿主机路径不存在: 如果你指定的宿主机路径不存在,Docker 可能会自动创建一个目录(取决于 Docker 版本和配置),但如果应用程序依赖这个路径下的特定文件,而这些文件又没被正确挂载进去,就会出问题。
- 权限问题: 容器内的应用程序(以其在容器内的用户身份运行)可能没有权限读取或写入你从宿主机挂载进来的那个目录或文件。你需要确保宿主机上该目录/文件的权限设置,允许容器内用户进行必要的操作。
- 数据卷语法错误或挂载了不期望的内容: 检查
-v
参数的语法是否正确。有时,错误的挂载可能会覆盖掉容器镜像中原有的重要文件,导致应用无法启动。
-v
参数启动容器,看看是否能成功。如果能,说明问题很可能出在数据卷挂载上。然后仔细检查路径、权限和内容。 - 容器资源是否不足(内存/CPU)? 如果你的应用程序在容器内启动时需要较多的内存或 CPU 资源,而宿主机当前可用资源不足,或者你通过
docker run
的--memory
,--cpus
等参数限制了容器可用的资源过低,都可能导致容器启动失败,或者启动后很快因为资源耗尽而被 OOM Killer (Out Of Memory Killer) 杀死(这时退出码通常是137
)。 排查方法:- 使用
docker stats <container_id>
(如果容器能短暂运行的话) 查看容器的实时资源使用情况。 - 使用
docker inspect <container_id>
查看容器的配置,特别是State.OOMKilled
字段是否为true
。 - 在宿主机上执行
dmesg | grep -i "oom-killer"
或dmesg | grep -i "killed process"
查看内核日志,看是否有 OOM 记录。 - 尝试在
docker run
时不加资源限制参数,或者适当增加分配给容器的内存/CPU(如果宿主机资源允许)。
- 使用
- 容器依赖的其他服务是否已就绪? 如果你的应用程序容器在启动时,需要连接到另一个服务(比如数据库容器、消息队列容器、或者外部 API),而那个依赖服务此时尚未启动、网络不通、或者配置不正确(比如连接地址、用户名密码错误),那么你的应用程序容器很可能会因为无法建立连接而启动失败或不断重启。 解决方法:
- 如果你使用 Docker Compose 来编排多个容器,可以在
docker-compose.yml
文件中使用depends_on
指令来定义服务间的启动顺序依赖(但这只保证了依赖服务先启动,不保证它已完全就绪并可接受连接)。 - 更可靠的方法是在你的应用程序代码中实现连接重试机制,或者在容器的启动脚本 (
ENTRYPOINT
脚本) 中加入等待依赖服务就绪的逻辑(比如通过循环尝试连接,或者使用像wait-for-it.sh
,dockerize
这样的工具)。
- 如果你使用 Docker Compose 来编排多个容器,可以在
- Dockerfile 中的配置是否正确? 比如,
WORKDIR
指令设置的工作目录是否正确?应用程序是否能在这个目录下找到它需要的文件?ENV
指令设置的环境变量是否符合应用预期?这些都可能影响容器的正常启动。
“信号中断?”—— Docker 容器网络不通排查
容器终于跑起来了,但你发现它像个“与世隔绝的孤岛”:容器内访问不了外网,或者宿主机也访问不了容器内应用监听的端口,或者容器之间无法互相通信。这通常都跟 Docker 的网络配置有关。
常见“网络故障”场景:
- 容器内无法访问互联网(比如
ping baidu.com
不通,apt-get update
失败)。 - 同一宿主机上的两个容器之间无法通过容器名或 IP 地址互相访问。
- 宿主机无法通过映射的端口访问容器内运行的服务。
- 从局域网内其他机器或公网无法访问宿主机上映射出来的容器服务端口。
排查思路与“药方”:
- 基础网络检查不可少:
- 容器内 DNS 解析是否正常? 首先尝试在容器内
ping
一个外网域名: [提示:请将以下代码片段复制并粘贴到 WordPress 的“代码”区块中]sudo docker exec -it <容器名或ID> ping -c 3 baidu.com
如果 ping 不通,但 ping IP (如ping -c 3 220.181.38.148
) 是通的,说明是 DNS 解析问题。查看容器内的 DNS 配置: [提示:请将以下代码片段复制并粘贴到 WordPress 的“代码”区块中]sudo docker exec -it <容器名或ID> cat /etc/resolv.conf
Docker 容器默认会使用 Docker 守护进程配置的 DNS 服务器(通常是宿主机的/etc/resolv.conf
内容,或者由 Docker 守护进程的--dns
参数指定,或者对于用户自定义桥接网络,Docker 会提供一个嵌入式 DNS 服务器在127.0.0.11
)。你需要确保这些 DNS 服务器是可达且能正常工作的。如果宿主机/etc/resolv.conf
配置的是本地 DNS (如127.0.0.53
for systemd-resolved),你需要确保该本地 DNS 服务允许来自 Docker 网络的查询,或者为 Docker 守护进程明确指定公共 DNS (如8.8.8.8
)。 - 宿主机防火墙是否拦截? 检查宿主机的防火墙规则(如
ufw
,firewalld
,iptables
)。Docker 在创建网络和端口映射时,通常会自动添加一些iptables
规则(比如在DOCKER
链和FORWARD
链中)。你需要确保防火墙的默认策略(特别是FORWARD
链的策略,默认可能是DROP
)没有阻止 Docker 容器的网络流量。对于ufw
,你可能需要明确允许来自 Docker 网桥接口(如docker0
)的流量转发。 [提示:请将以下代码片段复制并粘贴到 WordPress 的“代码”区块中]# 查看 ufw 状态和规则 sudo ufw status verbose # 查看 iptables 规则 (特别是 FORWARD 链和 DOCKER 相关链) sudo iptables -L -n -v sudo iptables -t nat -L -n -v
- 容器内 DNS 解析是否正常? 首先尝试在容器内
- 场景:容器无法访问外部网络 (Internet)
- 首先确认宿主机本身的网络是通的。
- 检查宿主机的 IP 转发功能是否已开启: [提示:请将以下代码片段复制并粘贴到 WordPress 的“代码”区块中]
cat /proc/sys/net/ipv4/ip_forward
输出应该是1
。如果是0
,则需要开启它。可以在/etc/sysctl.conf
(或/etc/sysctl.d/
下的配置文件) 中设置net.ipv4.ip_forward = 1
,然后执行sudo sysctl -p
使其生效。 - 检查 Docker 是否正确配置了 NAT (网络地址转换) 规则,使得容器可以使用宿主机的 IP 地址访问外网。通常,Docker 会在
iptables
的nat
表的POSTROUTING
链中添加一条MASQUERADE
规则,针对从 Docker 网络接口(如docker0
或用户自定义网络的网桥)出去的流量。 [提示:请将以下代码片段复制并粘贴到 WordPress 的“代码”区块中]sudo iptables -t nat -L POSTROUTING -n -v | grep MASQUERADE
如果没有看到针对你的 Docker 网络的 MASQUERADE 规则,可能是 Docker 网络配置出了问题,尝试重启 Docker 服务或检查 Docker 守护进程配置。
- 场景:同一宿主机上的容器之间无法互相通信
- 是否在同一个 Docker 网络中? Docker 提供了多种网络驱动,默认情况下,新创建的容器会连接到名为
bridge
的默认桥接网络 (通常是docker0
接口)。在默认桥接网络中,容器之间可以通过 IP 地址互相访问,但**不推荐**通过容器名进行 DNS 解析(功能受限或不可靠)。 最佳实践是创建用户自定义的桥接网络 (User-defined bridge network),并将需要互相通信的容器都连接到这个自定义网络上。在用户自定义桥接网络中,Docker 提供了内置的 DNS 服务,允许容器之间直接通过**容器名**作为主机名进行互相访问和解析。 [提示:请将以下代码片段复制并粘贴到 WordPress 的“代码”区块中]# 1. 创建一个用户自定义网络 sudo docker network create my-app-net # 2. 启动容器时,将它们连接到这个网络 sudo docker run -d --name service-a --network my-app-net image-a sudo docker run -d --name service-b --network my-app-net image-b # 现在,在 service-a 容器内部,你可以直接通过主机名 "service-b" 来访问 service-b 容器 # (例如:ping service-b, 或者应用配置中使用 http://service-b:port/)
- 使用
docker network ls
查看所有 Docker 网络,使用docker network inspect <网络名>
查看特定网络的详细信息(包括连接到该网络的容器列表及其 IP 地址)。
- 是否在同一个 Docker 网络中? Docker 提供了多种网络驱动,默认情况下,新创建的容器会连接到名为
- 场景:宿主机无法访问容器暴露的端口
- 端口是否正确发布 (Publish)? 在
docker run
时,你是否使用了-p <宿主机端口>:<容器端口>
(精确映射) 或-P
(将所有 EXPOSE 的端口随机映射到宿主机高位端口) 参数?如果忘了发布端口,宿主机自然访问不到。 使用docker ps
命令查看正在运行的容器,注意看PORTS
列,它会显示端口的映射关系 (如0.0.0.0:80->8080/tcp
表示宿主机的 80 端口映射到了容器的 8080 端口)。 - 容器内的应用程序是否真的在监听你期望的端口和正确的 IP 地址? 即使你正确映射了端口,如果容器内的应用程序没有在那个容器端口上监听,或者它只监听了容器内的某个特定 IP (比如
127.0.0.1
而不是0.0.0.0
或所有接口),宿主机也无法访问。你需要进入容器内部,使用netstat -tulnp
(如果容器内有该命令) 或类似命令,检查应用程序实际监听的端口和地址。 [提示:请将以下代码片段复制并粘贴到 WordPress 的“代码”区块中]sudo docker exec -it <容器名或ID> netstat -tulnp
确保你的应用监听的是0.0.0.0:<容器端口>
或:::<容器端口>
(IPv6),这样它才能接受来自 Docker 网桥的连接。
- 端口是否正确发布 (Publish)? 在
- 场景:从局域网内其他机器或公网无法访问宿主机上映射的容器服务端口 这个问题除了要满足“宿主机无法访问容器端口”的所有条件外,还需要额外检查:
- 宿主机防火墙是否允许外部访问该宿主机端口? 你需要在宿主机的防火墙上(如
ufw
,firewalld
)明确添加入站规则,允许外部 IP 访问你映射到宿主机的那个端口。 - 云平台安全组或网络 ACL 是否放行了该端口? 如果你的 Docker 主机是在云平台上(如 AWS, Azure, GCP, 阿里云, 腾讯云),你需要检查该主机关联的安全组 (Security Group) 或网络访问控制列表 (Network ACL) 是否允许外部流量访问该端口。
- 端口映射时,宿主机绑定的 IP 地址是否正确? 默认情况下,
-p <宿主机端口>:<容器端口>
会将宿主机端口绑定到0.0.0.0
(即所有可用网络接口)。如果你想只允许特定 IP 访问,或者宿主机有多个 IP 地址,你可能需要明确指定绑定的宿主机 IP,如-p <宿主机IP>:<宿主机端口>:<容器端口>
。 - 如果你使用了反向代理 (如 Nginx, Apache) 在容器前面,那么你需要检查反向代理的配置是否正确地将外部请求代理到了 Docker 容器映射出来的那个宿主机端口上。
- 宿主机防火墙是否允许外部访问该宿主机端口? 你需要在宿主机的防火墙上(如
结论:Docker 排障“三板斧”,从容应对各种“疑难杂症”
Docker 虽然强大,但遇到问题时也确实能让人“抓耳挠腮”。不过,只要我们掌握了正确的排查思路和工具,大多数常见的 Docker “疑难杂症”都能迎刃而解。
记住这套“排障三板斧”:
- “望闻问切”先看日志:
docker logs
和docker inspect
是你最好的“侦探助手”,它们往往能直接告诉你容器的“病因”。 - 追根溯源查配置:
- 对于**镜像拉取失败**,重点检查镜像名/标签、仓库登录、网络连接(DNS/防火墙/代理)、磁盘空间。
- 对于**容器启动失败**,核心是分析容器日志,然后检查
CMD
/ENTRYPOINT
、宿主机端口冲突、数据卷挂载、资源限制、以及依赖服务。 - 对于**网络不通**,从容器内 DNS 和宿主机防火墙开始,然后根据具体场景(容器到外网、容器间、外部到容器)分别检查 IP 转发、Docker 网络模式、端口映射、以及应用监听配置。
- 层层排除,耐心细致: Docker 的问题有时候是多个因素叠加造成的。保持耐心,一步步排除可能性,通常都能找到症结所在。
希望这篇指南能成为你 Docker 工具箱里的一件称手“兵器”,让你在未来的 Docker 之旅中,面对各种报错信息时,能够更加从容不迫,快速定位并解决问题,充分享受容器化带来的便捷与高效!
还有疑问?常见问题解答 (FAQs)
- 问:
docker ps -a
里看到我的容器状态是Exited (0)
,这是正常的吗?为什么它没有一直运行? 答:Exited (0)
通常表示容器内的主进程(由CMD
或ENTRYPOINT
指定的)**正常执行完毕并成功退出了**,退出码0
在 Linux/Unix 世界里一般代表“成功”或“无错误”。这种情况是否“正常”,取决于你容器的设计用途。如果你的容器是设计用来执行一个一次性的任务(比如一个数据处理脚本、一个编译任务、或者一个简单的echo "Hello World"
命令),那么任务完成后正常退出 (Exited (0)
) 就是完全预期的行为。但如果你的容器是设计用来运行一个需要**持续提供服务的后台应用**(比如一个 Web 服务器如 Nginx,一个数据库服务,或者一个长时间运行的应用程序),那么它不应该在启动后立刻就Exited (0)
。如果后台服务型容器出现这种情况,你需要检查:1)CMD
/ENTRYPOINT
是否正确地以前台模式启动了你的服务(很多服务默认会以守护进程/后台模式运行,这在容器里是不对的,容器需要一个前台主进程来维持运行)。2) 服务本身是否因为某些原因(比如配置完成、无事可做)而设计为自动退出。3) 查看docker logs
看是否有相关信息。 - 问: 我的 Dockerfile 在执行某个
RUN
指令时失败了,但错误信息一闪而过,我该怎么调试? 答: Dockerfile 构建失败时,错误信息通常会在docker build
的输出中显示。如果信息太长或滚动太快,你可以:1) 将构建日志输出重定向到一个文件:sudo docker build -t myimage . > build.log 2>&1
,然后查看build.log
。2) 如果想在失败的那一层进行交互式调试,可以尝试找到失败指令之前最后一个成功构建的层的 ID(在docker build
输出中通常能看到---> Running in ...
和---> 8abc123def45
这样的层ID),然后基于那个层启动一个交互式容器:sudo docker run --rm -it 8abc123def45 /bin/sh
(假设基础镜像是 Alpine 或包含/bin/sh
)。进入容器后,你就可以手动执行那个失败的RUN
指令中的命令,一步步排查是哪里出了问题。 - 问: 我感觉我的某个 Docker 容器占用了非常高的 CPU 或内存,有什么快速度量和限制的方法吗? 答: 1) **查看实时资源占用:** 使用
sudo docker stats
命令,它会像top
命令一样,实时显示所有正在运行容器的 CPU 使用率、内存使用量、网络 I/O 和磁盘 I/O。你可以只看特定容器:sudo docker stats <容器名或ID>
。2) **限制资源:** 在启动容器时,可以通过docker run
的参数来限制其可用的 CPU 和内存:--cpus="1.5"
: 限制容器最多使用 1.5 个 CPU核心。--memory="512m"
: 限制容器最多使用 512MB 内存。--memory-swap="-1"
: (通常与--memory
配合) 如果不希望容器使用 swap 分区,可以设为-1
(禁用 swap) 或者等于--memory
的值 (不额外分配 swap)。
docker-compose.yml
文件中的服务定义下通过deploy.resources.limits
(Swarm模式) 或直接的cpus
,mem_limit
(非Swarm模式,版本有差异) 来设置资源限制。 - 问: 我的
/var/lib/docker
目录越来越大,怎么安全地清理 Docker 占用的磁盘空间,而不会误删重要数据? 答: Docker 确实会因为累积的镜像、停止的容器、未使用的数据卷和网络而占用大量磁盘空间。安全清理的关键是理解哪些是可以放心删除的:- 已停止的容器 (Stopped containers): 它们不再运行,但其文件系统层仍然存在。除非你还需要它们用于调试或重启,否则可以删除。
sudo docker container prune
或sudo docker rm $(sudo docker ps -aq -f status=exited)
。 - 悬空镜像 (Dangling images): 这些是没有任何标签 (tag) 且不被任何容器使用的镜像层,通常是构建新版本镜像后旧版本留下的。
sudo docker image prune
或sudo docker rmi $(sudo docker images -f "dangling=true" -q)
。 - 所有未被任何容器使用的镜像 (All unused images):
sudo docker image prune -a
(这个命令会提示你确认,因为它会删除所有没有被正在运行或已停止的容器引用的镜像,包括那些有标签的但你可能还想保留的镜像,请谨慎使用)。 - 未使用的数据卷 (Unused volumes): 如果你创建了数据卷但没有容器再使用它们,它们会一直占用空间。
sudo docker volume prune
或sudo docker volume rm $(sudo docker volume ls -qf dangling=true)
。注意: 这个命令只会删除“匿名数据卷”或明确标记为悬空的数据卷。对于通过-v /host/path:/container/path
方式绑定的宿主机目录,你需要自己去宿主机上清理。对于“命名数据卷”,你需要确认它们确实不再需要才手动删除 (docker volume rm <volume_name>
)。 - 未使用的网络 (Unused networks):
sudo docker network prune
。 - 一键清理所有 (System Prune):
sudo docker system prune
会执行上述大部分清理操作(停止的容器、悬空镜像、未使用网络)。加上-a
参数 (sudo docker system prune -a
) 会更彻底,包括清理所有未被使用的镜像。加上--volumes
参数 (sudo docker system prune -a --volumes
) 则会连未被使用的数据卷也一并清理掉(这是最彻底的,请极度小心,确保没有重要数据在未被使用的命名数据卷中!)。
docker ps -a
,docker images -a
,docker volume ls
检查一下当前状态,确保你了解要删除的是什么。 - 已停止的容器 (Stopped containers): 它们不再运行,但其文件系统层仍然存在。除非你还需要它们用于调试或重启,否则可以删除。
- 问:
docker stop <container>
和docker kill <container>
有什么区别?我应该用哪个? 答: 它们都用于停止正在运行的容器,但方式不同:docker stop <container>
:优雅停止。 当你执行docker stop
时,Docker 首先会向容器内的主进程发送一个SIGTERM
信号(这是一个请求进程终止的信号,应用程序通常可以捕获这个信号并执行一些清理操作,如保存数据、关闭连接等)。Docker 会等待一段超时时间(默认是 10 秒)。如果在这段时间内,容器内的主进程自行退出了,那就皆大欢喜。如果超时后进程还没退出,Docker 才会发送一个SIGKILL
信号(这是一个强制杀死进程的信号,进程无法捕获或忽略)来强行终止容器。docker kill <container>
:强制停止。 当你执行docker kill
时,Docker 会直接向容器内的主进程发送一个SIGKILL
信号 (或者你可以通过--signal
参数指定其他信号),立即强制终止进程,不会给它任何清理或优雅退出的机会。
docker stop
,因为它给了应用程序一个“体面”退出的机会,可以避免数据丢失或状态不一致。只有当docker stop
长时间无法停止容器(比如应用卡死,无法响应SIGTERM
),或者你确定需要立即强制终止容器时,才考虑使用docker kill
。在自动化脚本中,通常也是先尝试stop
,如果失败或超时再kill
。