容器启动失败:端口冲突、资源限制、卷挂载错误、Entrypoint/CMD问题

各位好,我是老张。今天咱们聊聊容器启动失败这个老大难问题。

说实话,我入行头两年,遇到最多的就是容器起不来。每次看到 docker run 后面跟着一堆红色报错,心里就咯噔一下。后来摸爬滚打多了,发现其实就那几类问题——端口冲突、资源限制、卷挂载、还有 Entrypoint/CMD 配置。今天我把这些坑一个个给你拆开讲。

端口冲突:最容易被忽略的“隐形杀手”

先说说端口冲突。这玩意儿我遇到过太多次了。

有一次线上紧急发布,我敲了 docker run -p 8080:80 nginx,结果报错:

docker: Error response from daemon: driver failed programming external connectivity on endpoint ...
Bind for 0.0.0.0:8080 failed: port is already allocated.

当时我第一反应是“谁占了我的端口?” 后来发现是上一个容器没删干净。

为什么会这样? 说白了,宿主机上的端口是独占资源。你绑定了 8080,别人就不能再用。容器停止后,端口不会自动释放——除非你加了 --rm 参数。

排查三板斧:

  1. docker ps 看看有没有同名容器在跑
  2. docker port <容器名> 查看端口映射
  3. netstat -tulpn | grep 8080 查宿主机端口占用

我个人习惯,启动容器时一定加 --rm。这样容器停了,端口自动释放,省心。

小技巧: 如果你不确定端口是否被占,可以用 docker run -p 0:80 让 Docker 自动分配随机端口。调试阶段特别好用。

资源限制:容器“饿死”了怎么办?

资源限制这块,我踩过一个大坑。

有一次部署 Java 应用,容器启动后几秒就挂了。看日志,啥也没有。后来用 docker logs 一看,OOM(内存溢出)。

我当时就纳闷:明明宿主机有 64G 内存,怎么还会 OOM?

原因很简单: 我没给容器设置内存限制。Docker 默认不限制,但宿主机有 cgroup 兜底。如果容器内存暴涨,会被内核直接杀掉。

常见的资源限制参数:

参数 作用 示例
--memory 内存上限 --memory=512m
--cpus CPU 核心数 --cpus=1.5
--memory-swap 内存+交换分区上限 --memory-swap=1g
--pids-limit 进程数限制 --pids-limit=100

注意: 别把 --memory 设得太小。Java 应用至少 256m,否则 JVM 都起不来。我曾经把 --memory=128m 给一个 Spring Boot 应用,结果它连初始化都完不成。

排查资源问题,我一般用 docker stats 实时看资源占用。如果容器反复重启,用 docker inspect 看退出码——137 表示被 OOM 杀掉,139 是段错误。

卷挂载错误:文件去哪了?

卷挂载的问题,说白了就是“路径没对上”。

我记得有一次,同事说“我的配置文件明明挂载了,容器里就是读不到”。我过去一看,他写的是:

docker run -v /host/config:/app/config nginx

问题出在哪?宿主机上的 /host/config 目录不存在。Docker 不会报错,它会自动创建一个空目录挂载进去。结果容器里读到的配置文件是空的。

避坑指南:

  • 宿主机路径一定要存在,且权限正确
  • 容器内路径不要有空格或特殊字符
  • 挂载后,用 docker exec 进容器看一眼文件

我曾经犯过的错:/var/log 挂载到容器里,结果容器日志把宿主机磁盘写满了。后来我学乖了,挂载时加 :ro 只读权限,或者限制日志大小。

还有一个坑:Windows 和 Linux 路径分隔符不同。如果你在 Windows 上写 C:\Users\config,Docker 会报错。正确写法是 /c/Users/config 或者用双反斜杠。

Entrypoint/CMD 问题:容器为什么一闪而过?

这个问题,我刚开始学 Docker 时被折磨了好几天。

你写个 docker run ubuntu,容器启动后立刻退出。为什么?因为 Ubuntu 镜像的默认 CMD 是 /bin/bash,bash 执行完就退出了。

核心区别:

  • ENTRYPOINT:容器的主进程,固定不变
  • CMD:给 ENTRYPOINT 传的参数,或者默认执行命令

举个例子:

# Dockerfile
FROM alpine
ENTRYPOINT ["ping"]
CMD ["localhost"]

启动时:

docker run myping          # 相当于 ping localhost
docker run myping 8.8.8.8  # 相当于 ping 8.8.8.8

如果你把 ENTRYPOINT 和 CMD 写反了,或者用了错误的格式(JSON 数组 vs shell 字符串),容器就会出问题。

注意: JSON 数组格式 ["ping", "localhost"] 不会启动 shell。而 shell 格式 ping localhost 会启动 /bin/sh -c。如果你需要环境变量展开,必须用 shell 格式。

排查 Entrypoint/CMD 问题,我一般用 docker inspect 看容器的 Config.CmdConfig.Entrypoint。如果容器一闪而过,可以临时覆盖 CMD:

docker run --entrypoint /bin/sh myimage -c "sleep 3600"

这样容器就能保持运行,你再用 docker exec 进去调试。

综合排查流程

遇到容器启动失败,我一般按这个顺序查:

  1. 看日志: docker logs <容器名> 永远第一步
  2. 看状态: docker ps -a 看退出码
  3. 看配置: docker inspect 看 Entrypoint、CMD、挂载、端口
  4. 看资源: docker stats 看内存、CPU
  5. 看网络: docker network lsdocker network inspect

我的习惯: 写个 shell 脚本,把上面几步串起来。出问题一键执行,省得每次手动敲命令。脚本放在 GitHub 上,团队里共享。

好了,这一章就讲到这里。下一章咱们聊聊容器运行时的故障——比如 CPU 飙升、内存泄漏、磁盘写满这些头疼事。到时候见。