Docker 容器化实战踩坑:我被网络和存储卷坑了3次后终于搞懂了

说实话,我第一次用 Docker 部署项目的时候,心里想的是“这玩意儿不就是个轻量级虚拟机吗能有多难”。结果现实给了我3个响亮的耳光子——网络连不上、存储卷写不进去、容器间互相访问不了。整整折腾了两天,才终于把这些坑填平。今天把我的血泪教训分享出来,希望你不要再踩我踩过的坑。

踩坑现场一:Docker 网络连接失败,容器 ping 不了外网

问题描述

刚开始用 Docker,我信心满满地拉了一个 nginx 镜像启动:

docker run -d nginx

容器启动了,但是访问不了。ping 百度显示 bad address 'www.baidu.com',curl 也不行。我当时就蒙了——这也太不稳定了吧?

原因分析

Docker 安装时会创建一个默认的 bridge 网络(docker0),但是这个网络有时候会出问题。最常见的原因是:

  1. Docker 服务没有正常启动
  2. 防火墙规则阻止了 docker0 网桥
  3. DNS 配置被宿主机覆盖

解决方案

第一步,检查 Docker 服务状态:

# 重启 Docker 服务
sudo systemctl restart docker

# 检查 Docker 网络
docker network ls
docker network inspect bridge

第二步,如果服务正常但还是不通,手动配置 DNS:

# 在启动容器时指定 DNS
docker run -d --dns 8.8.8.8 nginx

# 或者修改 Docker 配置文件
sudo vi /etc/docker/daemon.json

添加以下内容:

{
  "dns": ["8.8.8.8", "114.114.114.114"]
}

然后重启 Docker:

sudo systemctl daemon-reload
sudo systemctl restart docker

血的教训:生产环境一定要提前配置好 DNS,不要用默认的。

踩坑现场二:存储卷权限被拒绝,容器内无法写入文件

问题描述

我想把宿主机的目录挂载到容器里,用 -v 参数:

docker run -v /data/myapp:/app/data myapp:latest

结果容器启动后,应用程序报错说没有权限写入 /app/data。我检查了宿主机的目录权限是 777,应该是所有人都能写的,怎么到容器里就不行了呢?

原因分析

这个问题坑了很多人。根本原因是 容器内用户的 UID/GID 可能和宿主机不同。比如宿主机目录属于用户 A(UID 1000),但容器内进程以 root(UID 0)或者其他用户运行,导致权限不匹配。

解决方案

方法一:指定容器内用户身份运行

# 以 root 用户运行(如果应用允许)
docker run -v /data/myapp:/app/data -u root myapp:latest

方法二:修改宿主机目录权限为 777(不推荐生产环境)

chmod -R 777 /data/myapp

方法三(推荐):在 Dockerfile 中创建与宿主机相同 UID 的用户

# 在 Dockerfile 中
RUN groupadd -g 1000 appgroup && \
    useradd -r -u 1000 -g appgroup appuser
    
USER appuser

方法四(最推荐):使用 Docker Compose 并配置用户

version: '3.8'
services:
  myapp:
    image: myapp:latest
    volumes:
      - /data/myapp:/app/data
    user: "1000:1000"  # 指定 UID:GID

血的教训:挂载目录前,先确认容器内进程的用户身份,尽量保持 UID 一致。

踩坑现场三:容器内无法访问宿主机上的 MySQL/Redis

问题描述

我的应用是 Docker 跑的,但是数据库 MySQL 还在宿主机上运行。我配置了 localhost:3306 连接数据库,结果容器怎么都连不上。我还以为是数据库配置问题,折腾了大半天。

原因分析

在 Docker 容器内部,localhost 指的是容器自己,不是宿主机!容器内的 localhost 是一个独立的网络命名空间,和宿主机的 localhost 完全隔离。

解决方案

方法一:使用宿主机的内网 IP

# 先查看宿主机内网 IP
hostname -I | awk '{print $1}'

# 然后在容器中使用这个 IP
docker run -e DB_HOST=192.168.1.100 myapp:latest

方法二(推荐):使用 Docker Compose 并配置 extra_hosts

version: '3.8'
services:
  myapp:
    image: myapp:latest
    extra_hosts:
      - "host.docker.internal:host-gateway"
    environment:
      - DB_HOST=host.docker.internal

这样容器内就可以用 host.docker.internal 访问宿主机了。

方法三:如果是 Linux 系统,还可以使用 --network host 模式

docker run --network host myapp:latest

但是注意,--network host 模式下容器会共享宿主机的网络命名空间,不能再映射端口了。

血的教训:Docker 容器和宿主机是隔离的,localhost 在容器内指的是容器自己,想访问宿主机得用特殊地址。

踩坑现场四:Docker Compose 服务间互相访问不了

问题描述

我用 Docker Compose 起了两个服务:myapp 和 redis。配置了 links 或者同一个网络,但是 myapp 怎么都连不上 redis。服务名明明配置对了啊?

原因分析

Docker Compose 默认会创建一个网络,所有服务都在这个网络里,应该是能互相访问的。问题通常是:

  1. 服务名拼写错误
  2. 端口号写错
  3. 服务还没启动完就去连接
  4. 网络配置有问题

解决方案

第一步,检查服务是否在同一个网络:

docker network ls
docker network inspect myapp_default  # 替换为你的网络名

第二步,检查服务是否能解析:

# 进入 myapp 容器测试
docker exec -it myapp_myapp_1 ping redis

# 或者用 nslookup
docker exec -it myapp_myapp_1 nslookup redis

第三步,检查 Docker Compose 配置:

version: '3.8'
services:
  myapp:
    build: .
    networks:
      - myapp_network
    depends_on:
      - redis  # 确保 redis 先启动
    
  redis:
    image: redis:alpine
    networks:
      - myapp_network

networks:
  myapp_network:
    driver: bridge

关键是确保:

  1. 两个服务在同一个 networks
  2. 使用 depends_on 确保启动顺序
  3. 服务名要完全匹配(区分大小写)

血的教训:Docker Compose 的服务发现是基于 DNS 的,服务名就是域名,大小写敏感。

踩坑现场五:镜像构建缓存导致的问题

问题描述

我修改了代码,重新构建镜像后,容器里跑的还是旧代码!明明 Dockerfile 没变,咋回事?

原因分析

Docker 构建时会使用缓存来加速。但是有时候缓存会导致问题:

  1. 前面层没变,后面的层不会重新构建
  2. COPY 命令可能缓存了旧文件
  3. ADD 和 COPY 的上下文问题

解决方案

方法一:强制重新构建,不使用缓存

docker build --no-cache -t myapp:latest .

方法二:在 Dockerfile 中合理安排指令顺序

# 变化频繁的指令放后面
FROM node:18-alpine

# 变化少的指令放前面(利用缓存)
WORKDIR /app
COPY package*.json ./
RUN npm install

# 变化频繁的指令放后面
COPY . .
RUN npm run build

方法三:使用 BuildKit 构建(支持更好的缓存)

# 启用 BuildKit
export DOCKER_BUILDKIT=1

# 构建
docker build -t myapp:latest .

方法四:分别构建前后端镜像(如果是大项目)

# 先构建前端
docker build -t myapp-frontend:latest ./frontend

# 再构建后端
docker build -t myapp-backend:latest ./backend

血的教训:修改代码后构建,最好用 --no-cache 强制重新构建,或者合理安排 Dockerfile 指令顺序。

写在最后

Docker 看起来简单,但是里面的坑是真不少。网络、存储卷、权限、服务间通信、构建缓存——每一个都能让新手折腾半天。

我的建议是:

  1. 先跑通再优化:先用最简单的命令把服务跑起来,再慢慢加配置
  2. 多用 Docker Compose:配置文件能记录所有参数,方便排查问题
  3. 看日志:大部分问题都能在日志里找到答案
  4. 了解原理:知道 Docker 网络、存储卷的原理,出了问题才能快速定位

希望这篇文章能帮你少踩几个坑。如果还有其他 Docker 相关的问题,欢迎评论区交流。


相关文章推荐

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐