镜像是静态只读模板,由多层文件系统构成;容器是其动态运行实例,含可写层、命名空间隔离与cgroups资源限制。
镜像和容器是两个不同层级的概念:镜像是静态的、只读的文件系统快照,是应用的“模板”;容器是镜像的运行实例,是一个带有命名空间隔离、cgroups资源限制、独立进程树的动态进程。容器进程僵死或卡死,不是镜像的问题,而是容器运行时状态异常的表现——根源在进程行为、信号处理、资源约束或内核交互层面。
查清卡死类型,对症下称
容器“卡死”不是单一现象,需先区分是哪一类阻塞:
-
启动卡住(docker run 不返回):大概率没加
-d,容器前台运行并占住终端;也可能是 CMD 启动的进程立即退出又没日志,表现为“一闪而过”;或 ENTRYPOINT 脚本陷入阻塞(如等待 stdin、挂起在 execve 调用) -
运行中卡死(docker exec -it 进不去、docker top 无响应):常见于 Alpine 镜像中
/bin/sh卡在 seccomp 拦截、PID 1 未转发信号、或容器内进程处于不可中断睡眠(D 状态),比如 NFS 挂载超时、磁盘 I/O 故障 - 停止卡住(docker stop 后一直 stopping):主进程未响应 SIGTERM,或 PID 1 是 shell 而非 init,无法回收子进程,导致僵尸堆积、信号丢失;也可能因文件系统挂载未就绪,进程阻塞在 umount 或 sync 调用
-
系统级假死(docker ps / docker stats 全部无响应):宿主机 socket 句柄耗尽、inode 耗尽、或 containerd 与 dockerd 通信中断;典型表现是
lsof -p $(pgrep dockerd)显示数万 sock 文件描述符
从 PID 1 和信号入手保退出可靠
绝大多数僵死都和 PID 1 处理不当有关。Linux 容器中,PID 1 必须能:
- 接收并转发 SIGTERM 给子进程
- 调用 wait() 回收僵尸子进程
- 不被 SIGKILL 绕过(普通 kill -9 对 init 无效)
避免用 shell 脚本直接作 PID 1:
错误写法CMD ["./start.sh"] → shell 启动后不加 exec,它自己成为 PID 1,但不会转发信号
CMD ["exec", "./start.sh"] 或使用轻量 init:ENTRYPOINT ["/sbin/tini", "--"];也可启动时加 --init 参数
监控+限制双管齐下防资源耗尽
OOM Killed(Exit Code 137)、文件描述符满、TIME_WAIT 套接字堆积,都会让进程看似“卡住”。关键动作:
- 启动时强制设内存上限:
docker run --memory=1g --memory-swap=1g,避免吃光宿主机内存触发全局 OOM - 限制文件句柄:
--ulimit nofile=65536:65536,并在容器内检查ulimit -n - 观察实时资源:
docker stats --no-stream看是否持续上涨;docker inspect xxx | grep -i oomkilled确认是否被杀过 - 查内核层压力:
cat /proc/net/sockstat看 sockets used 是否逼近上限;cat /proc/sys/fs/file-nr看 fd 使用率
应急排查不依赖 Docker API
当 docker ps 已失效,说明 daemon 层可能已失联。此时绕过 API 直查内核:
- 找容器真实 PID:
docker inspect -f '{{.State.Pid}}' xxx,再ls -l /proc/$PID/ns看命名空间是否完整(缺项即异常) - 看进程状态:
ps -o pid,ppid,stat,comm -p $PID,若 STAT 列为D(不可中断睡眠),基本确认 I/O 卡死 - 跟踪 exec 行为:
strace -f -e trace=clone,execve,mount docker exec xxx /bin/sh,看卡在哪一系统调用 - 临时释放资源:
echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse缩短 TIME_WAIT 复用,sysctl -w fs.file-max=2097152扩容 fd 上限


















