跳转至

Docker 疑难杂症

本文汇总 Docker 使用中常见的故障现象、原因分析与解决命令。


🟢存储与空间

du · df · thin_check · overlay2 · inode。磁盘、inode、device-mapper 与挂载相关故障。

flowchart TD
    A[容器报空间不足] --> B{df -Th 磁盘满?}
    B -->|是| C[清理日志 / 迁移数据目录]
    B -->|否| D{df -i inode 满?}
    D -->|是| E[inode64 / large_dir 重新挂载]
    D -->|否| F[检查容器默认大小 / overlay2 占用]

1. 迁移 Docker 存储目录

默认情况下 Docker 将容器存放在 /var/lib/docker,该目录过大会导致容器无法启动(ERROR: cannot create temporary directory!)。

# 查看系统存储占用
du -h --max-depth=1
sudo systemctl stop docker
sudo mv /var/lib/docker /data/
sudo ln -s /data/docker /var/lib/docker
sudo systemctl start docker

方式一,修改 systemd 启动文件:

sudo vim /lib/systemd/system/docker.service
ExecStart=/usr/bin/dockerd --graph=/data/docker/

方式二,修改 /etc/docker/daemon.json

{
    "live-restore": true,
    "graph": [ "/data/docker/" ]
}

注意事项

迁移时用 mv 直接移动,或用 cp 复制时须保留权限和属性,否则可能因普通用户运行容器而出现权限问题(如无法使用 /tmp)。尽量不用软链接,部分编排系统(如 k8s)不支持。

# 使用 mv
sudo mv /var/lib/docker /data/docker

# 使用 cp(保留属性)
sudo cp -arv /data/docker /data2/docker

2. Docker 设备空间不足

现象一:物理磁盘真的满了

# 查看物理磁盘空间
df -Th
# 查看 Docker 基本信息,确认存储驱动与可用容量
docker info

解决:清理无效数据(清除日志),或将 Docker 数据迁移到大分区。

# 显示哪些容器目录具有最大的日志文件
du -d1 -h /var/lib/docker/containers | sort -h

# 清除指定容器日志文件内容
cat /dev/null > /var/lib/docker/containers/container_id/container_log_name

现象二:容器默认大小限制(CentOS7 默认 10G)

日志中提示 No space left on device

/etc/docker/daemon.json
{
    "live-restore": true,
    "storage-opt": [ "dm.basesize=20G" ]
}

或修改 systemd 启动文件:

sudo systemctl stop docker
sudo rm -rf /var/lib/docker
sudo vim /usr/lib/systemd/system/docker.service
# 将 ExecStart=/usr/bin/dockerd 改为:
ExecStart=/usr/bin/dockerd --storage-opt dm.basesize=20G
sudo systemctl start docker
sudo systemctl daemon-reload

现象三:inode 节点数满

ext3 使用 inode table 存储 inode 信息,xfs 使用 B+ tree;默认 B+ tree 只用前 1TB 空间,写满后无法写入 inode

# 查看 inode 使用情况
sudo df -i

# 重新挂载,指定 inode64 扩展到整个文件系统
sudo mount -o remount -o noatime,nodiratime,inode64,nobarrier /dev/vda1

现象四:ext4 目录文件数过多

ext4 基于 BTree 索引,默认层级为 2,约限制目录下文件数在 2k–3kw 以内;未开启 large_dir 时会报 No space left on device

# 排查思路
df -ih                                      # 检查磁盘与 inode 是否足够
fallocate -l 10G /cache/test                # 排除 mount-bind 缺陷
fdisk -l                                    # 确认文件系统类型
dmesg                                       # 查看内核日志,定位 htree level 报错
tune2fs -l                                  # 确认是否开启 large_dir
tune2fs -O large_dir /dev/sdd               # 开启 large_dir

4. Docker 容器文件损坏

容器无法关闭、重启、删除,报错:

b'devicemapper: Error running deviceCreate (CreateSnapDeviceRaw) dm_task_run failed'
sudo systemctl stop docker
sudo rm -rf /var/lib/docker/containers
sudo thin_check /var/lib/docker/devicemapper/devicemapper/metadata
sudo thin_check --clear-needs-check-flag /var/lib/docker/devicemapper/devicemapper/metadata
sudo systemctl start docker

10. Docker NFS 挂载报错

Python 程序对 NFS 挂载文件加锁时,报 OSError: [Errno 9] Bad file descriptor

with open(self.mount(path), 'rb') as fileobj:
    fcntl.flock(fileobj, fcntl.LOCK_EX)
    data = fileobj.read()

原因:flock() 在旧内核中对 NFS 支持受限。Linux 2.6.12 起 NFS 客户端才支持 flock()(以整文件字节范围锁模拟)。该问题也曾是 RedHat 内核错误,在 kernel-3.10.0-693.18.1.el7 中修复。对 NFSv3/NFSv4 需升级内核版本。

26. Docker 容器磁盘不足

/var/lib/docker/overlay2 占用过大。该目录以 md5 命名,是 Docker 使用的文件存储驱动目录。

# 查看默认文件驱动
docker info | grep "Storage Driver"

# 查看磁盘消耗大户
sudo du -sh /var/lib/docker/overlay2

层目录含义:

  • diff:差分信息,容器的可读可写层,初始为空。
  • merged:容器运行时的挂载点。
  • upperdir:容器层。
  • lowerdir:初始镜像层。
# 查看哪个容器层占用大量磁盘
sudo du -sh /var/lib/docker/overlay2/* | grep G | sort -rn

# 查找对应层所属容器
docker ps -q | \
    xargs docker inspect --format '{{.State.Pid}}, {{.Id}}, {{.Name}}, {{.GraphDriver.Data.WorkDir}}' | \
    grep xxx

# 查看容器的层数信息(从低到高)
docker inspect xxx | grep -E "LowerDir|UpperDir|MergedDir|WorkDir"

27. Docker 挂载无法删除

rm: cannot remove 'config-docker-app.yml': Device or resource busy

原因是服务启动时把文件硬挂载进容器:

cat /proc/mounts | grep "config"
# /dev/sda /opt/app/config/config-docker-app.yml ext4 rw,relatime,data=ordered 0 0

🟢网络与端口

docker0 · default-address-pools · firewalld · IPv6。容器互通、网段冲突与端口限制。

8. Docker 容器网络互通

Nginx 容器代理本机服务时一直 502,原因是容器中的 localhost 指容器自身而非宿主机。

# 错误配置
location /api {
    proxy_pass http://localhost:8080;
}

解决:改为宿主机 IP(如 docker0 的 172.17.0.1):

# 查询宿主机 IP
ip addr show docker0
location /api {
    proxy_pass http://172.17.0.1:8080;
}

或让容器与宿主共用网络,此时容器的 localhost 就是宿主机的 localhost

docker run -d -p 80:80 --network=host $PWD:/etc/nginx nginx

11. Docker 使用默认网段

多个服务有时能互通、有时不能,原因是内部私有地址网段不一致(如 172.17-172.31192.169.0-192.168.224)。手动指定 Docker 启动网段即可。

/etc/docker/daemon.json
{
    "registry-mirrors": ["https://vec0xydj.mirror.aliyuncs.com"],
    "default-address-pools": [{"base": "172.17.0.0/12", "size": 24}]
}

完整配置见下文第 5 条「Docker 容器优雅重启」。

18. Docker 绑定到 IPv6 上

系统禁用 IPv6 后启动 docker-compose 报错:

Error starting userland proxy: listen tcp6 [::]:80: socket: address family not supported by protocol.
# 确认系统配置
cat /etc/sysctl.conf | grep ipv6
# net.ipv6.conf.all.disable_ipv6=1
version: "3"
services:
  app:
    restart: on-failure
    container_name: app_web
    image: app:latest
    ports:
      - "0.0.0.0:80:80/tcp"
    volumes:
      - "./app_web:/data"
    networks:
      - app_network
networks:
  app_network:

阻止映射到 IPv6:

{
  "ipv6": false,
  "fixed-cidr-v6": "2001:db8:1::/64"
}
echo '1' > /proc/sys/net/ipv6/conf/lo/disable_ipv6
echo '1' > /proc/sys/net/ipv6/conf/all/disable_ipv6
echo '1' > /proc/sys/net/ipv6/conf/default/disable_ipv6
/etc/init.d/networking restart
ip addr show | grep net6

20. Docker 端口网络限制

部署架构 nginx(80) -> web1(8080) -> web2(8081) 时,Nginx 报:

nginx connect() failed (113: No route to host) while connecting to upstream

原因:防火墙过滤。开放端口或关闭防火墙。

# 检查开放端口
sudo firewall-cmd --permanent --zone=public --list-ports

# 开启需要路由的端口
sudo firewall-cmd --permanent --zone=public --add-port=8080/tcp
sudo firewall-cmd --permanent --zone=public --add-port=8081/tcp
firewall-cmd --reload
# 关闭防火墙
sudo systemctl stop firewalld.service
sudo systemctl disable firewalld.service

23. Docker 不使用默认网段

内部规划网段与 dockerd 默认网段冲突导致端口不通(Connection refused)。手动指定网段并重启服务:

sudo cat /etc/docker/daemon.json
# { "default-address-pools": [{"base": "192.168.100.0/20", "size": 24}] }

sudo systemctl restart docker

# 验证
ip a
docker network inspect app | grep Subnet

若报 could not find an available, non-overlapping IPv4 address pool,需合理划分子网:

# 给定 10.210.200.0/24,每个子网 16 个 IP,则 size=28
sudo cat /etc/docker/daemon.json
# { "default-address-pools": [{"base": "10.210.200.0/24", "size": 28}] }

base 指定划分子网的起始网段,size 指定每个子网可用的 IP 数量。

33. 网桥转发被 Docker 默认策略阻断

ip netns + bridge 做网络实验时,各命名空间已接入 br0 却仍 ping 不通。原因是系统为 bridge 开启了 iptables 功能,所有经过 br0 的数据包都受 iptables 规则限制;而 Docker 出于安全考虑把 filter 表 FORWARD 链默认策略设成了 DROP,不符合 Docker 规则的包无法被转发。

# 方法一:关闭系统 bridge 的 iptables 功能
echo 0 > /proc/sys/net/bridge/bridge-nf-call-iptables

# 方法二(推荐,不影响 Docker):为 br0 放行 FORWARD
iptables -A FORWARD -i br0 -j ACCEPT

🟢镜像与仓库

16. Docker 删除镜像报错

docker rmi 3ccxxxx2e862
# Error response from daemon: conflict: unable to delete 3ccxxxx2e862 (cannot be forced) - image has dependent child images

原因是存在其他镜像(TAG)引用了该镜像。先查询依赖关系,再根据 TAG 删除:

# 查询依赖
docker image inspect --format='{{.RepoTags}} {{.Id}} {{.Parent}}' \
    $(docker image ls -q --filter since=<image_id>)

# 根据 TAG 删除
docker rmi -f c565xxxxc87f

# 删除悬空镜像
docker rmi $(docker images --filter "dangling=true" -q --no-trunc)

21. Docker 无法获取镜像

Error response from daemon: manifest for docker.escapelife.site/app:0.10 not found

原因:镜像 TAG 写错(应为 0.0.10)。出现该报错通常代表镜像不存在。

# 登录私有仓库后,认证信息记录在用户家目录(base64,不安全)
cat .docker/config.json

24. Docker 添加私有仓库

x509: certificate signed by unknown authority
/etc/docker/daemon.json
{
    "insecure-registries": ["192.168.31.191:5000"]
}
sudo systemctl restart docker
docker login 私库地址 -u 用户名 -p 密码

29. Docker 容器导入异常

docker: Error response from daemon: No command specified

原因是导入命令组合错误,注意配对使用:

docker save app:0.1 -o app_0.1.tar
docker load -i app_0.1.tar
docker export -o app_0.1.tar a404c6c174a2
docker import app_0.1.tar

🟢compose 与命令

-p 项目名 · -it · COMPOSE_HTTP_TIMEOUT · YAML 引号。串台、TTY、超时与变量解析。

12. Docker 服务启动串台

在不同目录用 docker-compose 启动两组服务时会串台,导致无法同时启动。原因是 compose 会给容器加 label,其中 com.docker.compose.project 取启动配置文件所在目录的最底层子目录名:

A: /data1/app/docker-compose.yml
B: /data2/app/docker-compose.yml   # 两者 project 值都是 app

解决:调整目录结构,或用 -p 手动指定项目名。

A: /data/app1/docker-compose.yml
B: /data/app2/docker-compose.yml
docker-compose -f ./docker-compose.yml -p app1 up -d

13. Docker 命令调用报错

CI 中执行脚本报错,提示设备不是 tty:

docker exec -it <container_name> psql -Upostgres ......
参数 解释
-i / --interactive 即使没有附加也保持 STDIN 打开;执行命令需要开启
-t / --tty 分配一个伪终端

原因:-t 分配伪 TTY,而 CI 执行 job 时并非在 TTY 终端中,故报错。去掉 -t 即可。

14. Docker 定时任务异常

Crontab 中通过 Docker 备份 MySQL,备份结果为空:

0 */6 * * * \
    docker exec -it <container_name> sh -c \
        'exec mysqldump --all-databases -uroot -ppassword ......'

原因:Crontab 执行不是交互式的,-i 多余。需要回显用 -t,需要交互式会话用 -i

15. Docker 变量使用引号

compose 中添加环境变量时引号处理容易踩坑:

# 无法找到 TEST_VAR
TEST_VAR="test"

# 可以找到
TEST_VAR=test
# docker 本身已正确处理引号
docker run -it --rm -e TEST_VAR="test" test:latest

原因:Compose 解析 YAML 时把引号也作为值的一部分,TEST_VAR="test" 实际被解析为 'TEST_VAR="test"'。结论:能不用引号就不用引号。若值形如日期(2022-01-01),使用 '/" 引起来可避免被 yaml.load 当作 date 类型。

19. Docker 容器启动超时

ERROR: for xxx  UnixHTTPConnectionPool(host='localhost', port=None): Read timed out. (read timeout=70)
ERROR: An HTTP request took too long to complete. Retry with --verbose to obtain debug information.

按提示加大超时环境变量:

sudo vim /etc/profile
export COMPOSE_HTTP_TIMEOUT=500
export DOCKER_CLIENT_TIMEOUT=500

另需排查 I/O 瓶颈,例如 vscode 远程搜索目录产生的 rg 进程占满 I/O:

sudo iotop

28. Docker 数组环境变量

compose 中通过环境变量传数组,程序读取到的是字符串。

environment:
  - APP_CONFIG_EMAIL_LIST='["a", "b"]'   # 字符串
environment:
  - APP_CONFIG_EMAIL_LIST=['a', 'b']     # 正确,配合 yaml.load 可解析为 list
from app.config import get_config
get_config('APP_CONFIG_EMAIL_LIST')   # ['a', 'b']
type(get_config('APP_CONFIG_EMAIL_LIST'))  # <class 'list'>

🟢权限与运行

3. Docker 缺共享链接库

docker-compose --version 报错:

error while loading shared libraries: libz.so.1: failed to map segment from shared object: Operation not permitted

原因是 Docker 命令需要对 /tmp 目录有访问权限,重新挂载即可:

sudo mount /tmp -o remount,exec

6. Docker 容器无法删除

docker rm -fConflict, cannot remove the default name of the container,且 ps -ef 查不到对应进程。原因通常是容器启动后主机重启且未优雅终止容器,残留文件让系统认为旧容器仍存在。

# 删除对应容器文件夹
sudo rm -rf /var/lib/docker/containers/f8e8c3...65720

# 重启服务
sudo systemctl restart docker.service

9. Docker 容器总线错误

容器内运行程序报 Bus error (core dumped),原因是 shm 分区太小导致共享内存不足。未设置 --shm-size 时默认分配 64M,跑 PyTorch 多进程任务容易不足。

# 查看确认
df -TH

# 启动时指定 shm 大小(单位 b,k,m,g)
docker run -it --rm --shm-size=200m pytorch/pytorch:latest

# docker-compose 中配置
# shm_size: '2gb'

Note

容器内磁盘空间不足同样会导致 bus error,需清理文件或分配更大的磁盘空间。

17. Docker 普通用户切换

用普通用户启动 Nginx 时报权限错误:

nginx: [alert] could not open error log file: open() "/var/log/nginx/error.log" failed (13: Permission denied)
[emerg] 23#23: mkdir() "/var/cache/nginx/client_temp" failed (13: Permission denied)

解决:将 Nginx 需要的文件/目录配置到无权限问题的路径。

user  www-data;
worker_processes  1;

error_log  /data/logs/master_error.log warn;
pid        /dev/shm/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    gzip               on;
    sendfile           on;
    tcp_nopush         on;
    keepalive_timeout  65;

    client_body_temp_path  /tmp/client_body;
    fastcgi_temp_path      /tmp/fastcgi_temp;
    proxy_temp_path        /tmp/proxy_temp;
    scgi_temp_path         /tmp/scgi_temp;
    uwsgi_temp_path        /tmp/uwsgi_temp;

    include /etc/nginx/conf.d/*.conf;
}

22. Docker 使容器不退出

服务启动异常且 restart: on-failure 时容器无限重启,不便排查。可让容器 hang 住:

# 类似原理
docker run -it --rm --entrypoint=/bin/bash xxx/app:latest
# 使用 command
tty: true
command: tail -f /dev/null

# 使用 entrypoint
tty: true
entrypoint: tail -f /dev/null
# Compose 完整示例
version: "3"
services:
  app:
    image: ubuntu:latest
    tty: true
    entrypoint: /usr/bin/tail
    command: "-f /dev/null"
apiVersion: v1
kind: Pod
metadata:
  name: ubuntu
spec:
  containers:
    - name: ubuntu
      image: ubuntu:latest
      command: ["/bin/bash", "-c", "--"]
      args: ["while true; do sleep 30; done;"]

30. Docker 日志输出授权

supervisor 启动 nginx 报 EACCES

INFO spawnerr: unknown error making dispatchers for 'nginx': EACCES
INFO gave up: nginx entered FATAL state, too many start retries too quickly

原因:容器中服务使用普通用户,而日志文件属主是 root。另在类 K8S 平台需输出到终端时,/dev/fd/1/dev/stdout 可能对普通用户不可写,需提前授权:

chown app /dev/fd/1
chown app /dev/stdout
chown app /dev/stderr

31. Docker 文件权限问题

# Rust 程序报错
OS can't spawn a new worker thread

# Tomcat 服务报错
Cannot find /usr/local/tomcat/bin/setclasspath.sh

原因是权限异常,解决方式为更新 runc 或给容器加特权开关:

--privileged --security-opt="seccomp=unconfined"
privileged: true

32. Docker 提示权限受限

容器内以 root 运行仍报 Operation not permitted

OpenBLAS blas thread init: pthread create failed for thread 1 of 16: Operation not permitted
ls: cannot access '/etc/docker-entrypoint-initdb.d/': Operation not permitted

原因:宿主机内核版本过低,新版本镜像使用的特性不受支持。

# compose
privileged: true

🟢守护进程与系统配置

live-restore · systemctl reload|restart dockerd · localtime。优雅重启与时间同步。

5. Docker 容器优雅重启

默认守护进程终止时会关闭正在运行的容器。自 Docker-ce 1.12 起,可在配置中添加 live-restore 使容器在守护进程不可用时保持运行(Windows 平台暂不支持)。

/etc/docker/daemon.json
{
  "live-restore": true
}
# 在守护进程停机期间保持容器存活
sudo dockerd --live-restore

# 重载(相当于发送 SIGHUP 给 dockerd)
sudo systemctl reload docker

# 网络设置需要 restart 才能生效
sudo systemctl restart docker

完整示例:

/etc/docker/daemon.json
{
    "registry-mirrors": ["https://vec0xydj.mirror.aliyuncs.com"],
    "experimental": true,
    "default-runtime": "nvidia",
    "live-restore": true,
    "runtimes": {
        "nvidia": {
            "path": "/usr/bin/nvidia-container-runtime",
            "runtimeArgs": []
        }
    },
    "default-address-pools": [
        {
            "scope": "local",
            "base": "172.17.0.0/12",
            "size": 24
        }
    ]
}

25. Docker 解决时间同步

容器内时间为 UTC,宿主机为 CST,相差 8 小时。

docker run -d --name 'app' \
    -v /etc/localtime:/etc/localtime \
    escape/nginx:v1
ENV TimeZone=Asia/Shanghai
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
environment:
  TZ: Asia/Shanghai

🟢语言与编码

7. Docker 容器中文异常

MySQL 容器查询中文字段异常,locale -a 只显示 CC.UTF-8POSIX。原因是使用 POSIX 字符集,不支持中文。

# 临时解决
docker exec -it some-mysql env LANG=C.UTF-8 /bin/bash
# 永久解决
docker run --name some-mysql \
    -e MYSQL_ROOT_PASSWORD=my-secret-pw \
    -d mysql:tag --character-set-server=utf8mb4 \
    --collation-server=utf8mb4_unicode_ci

Tip

K8S 进入 pod 不能输入中文时也可用同样方法解决。