容器启动失败:端口冲突、资源限制、卷挂载错误、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 参数。
排查三板斧:
docker ps看看有没有同名容器在跑docker port <容器名>查看端口映射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.Cmd 和 Config.Entrypoint。如果容器一闪而过,可以临时覆盖 CMD:
docker run --entrypoint /bin/sh myimage -c "sleep 3600"
这样容器就能保持运行,你再用 docker exec 进去调试。
综合排查流程
遇到容器启动失败,我一般按这个顺序查:
- 看日志:
docker logs <容器名>永远第一步 - 看状态:
docker ps -a看退出码 - 看配置:
docker inspect看 Entrypoint、CMD、挂载、端口 - 看资源:
docker stats看内存、CPU - 看网络:
docker network ls和docker network inspect
我的习惯: 写个 shell 脚本,把上面几步串起来。出问题一键执行,省得每次手动敲命令。脚本放在 GitHub 上,团队里共享。
好了,这一章就讲到这里。下一章咱们聊聊容器运行时的故障——比如 CPU 飙升、内存泄漏、磁盘写满这些头疼事。到时候见。