4. Docker网络与存储:打通容器的任督二脉

好,咱们进入第四章。这一章我打算聊聊Docker里两个最实在的东西——网络和存储。

说实话,我刚开始用Docker那会儿,最头疼的就是这两块。网络搞不清楚,容器之间互相访问不了;存储没弄明白,容器一删数据全没了。后来踩了不少坑,才慢慢摸清门道。今天我把这些经验掰开揉碎了讲给你听。

4.1 Docker网络模式:三种模式,三种哲学

Docker的网络模式,说白了就是解决一个问题:容器怎么跟外界通信。Docker官方提供了三种内置模式,我一个个说。

4.1.1 bridge模式(默认模式)

这是Docker默认的网络模式。你每次docker run不加--network参数,用的就是它。

它的原理很简单:Docker会在宿主机上创建一个虚拟网桥docker0,然后每个容器都会分配一个独立的网络命名空间,通过veth pair连接到这个网桥上。容器之间可以通过IP地址互相访问。

核心特点:

  • 容器拥有独立的网络栈(IP、端口、路由表)
  • 容器之间默认可以互通(通过IP)
  • 容器访问外部网络需要NAT(网络地址转换)
  • 外部访问容器需要端口映射(-p参数)

我在项目中遇到过一个问题:两个容器都跑在默认的bridge网络上,但其中一个就是ping不通另一个。查了半天,发现是iptables规则被改了。嗯,这里要注意——bridge模式依赖iptables做NAT和转发,如果你改了防火墙规则,容器网络可能就断了。

4.1.2 host模式

host模式就简单粗暴了——容器直接使用宿主机的网络栈,不隔离。

说白了,容器里的进程就像直接跑在宿主机上一样,端口也是共享的。你启动一个Nginx容器用host模式,它直接监听宿主机的80端口,不需要-p映射。

注意:host模式牺牲了网络隔离性。我曾经在生产环境吃过亏——两个都用host模式的容器,端口冲突导致服务挂了。所以我的建议是:除非你明确知道自己在做什么,否则别用host模式

那什么时候用host模式呢?我个人习惯在两种场景下用:

  • 性能敏感型应用(比如高频交易、实时流处理),因为跳过NAT能减少延迟
  • 需要直接操作宿主机网络设备的工具(比如网络监控、抓包工具)

4.1.3 none模式

none模式,顾名思义——没有网络。容器只有一个loopback接口(127.0.0.1),完全隔离。

你可能会问:这有什么用?

我告诉你,还真有用。比如你跑一个离线计算任务,或者一个只处理本地文件的工具,根本不需要网络。用none模式反而更安全——就算容器被攻破了,攻击者也出不去。

我的经验:做安全审计的时候,我会把不需要网络的容器全部改成none模式。少一个攻击面,多一分安全。

4.2 容器间通信:不只是IP直连

容器之间怎么通信?最直接的方式就是通过IP。但IP是动态分配的,容器重启后IP就变了。你想想看,如果A容器写死了B容器的IP,B一重启,A就找不到了。这多尴尬。

Docker提供了更好的方案——自定义网络DNS解析

4.2.1 自定义bridge网络

你可以自己创建一个bridge网络,然后把容器都加进去。在这个网络里,容器之间可以通过容器名直接通信,Docker内置的DNS会自动解析。

# 创建自定义网络
docker network create my-net

# 启动两个容器,都连接到my-net
docker run -d --name web --network my-net nginx
docker run -d --name app --network my-net my-app

# 在app容器里,可以直接ping web
docker exec app ping web

我个人习惯用自定义网络代替默认的bridge。原因很简单:默认bridge不支持通过容器名解析,而自定义网络支持。这一个小差别,能让你的容器编排灵活很多。

4.2.2 容器间通信的几种方式

通信方式 适用场景 注意事项
同一网络内容器名通信 微服务架构、前后端分离 需要自定义网络
IP直连 临时调试、快速验证 IP会变,不推荐生产使用
宿主机端口映射 外部访问容器 端口冲突风险
容器链接(--link) 旧项目兼容 已废弃,不推荐使用

避坑指南:我曾经用--link参数连接两个容器,后来升级Docker版本发现这个功能被标记为废弃了。迁移到自定义网络花了我一整天。所以——新项目直接用自定义网络,别碰--link

4.3 数据持久化:容器可以删,数据不能丢

容器是无状态的——你删掉一个容器,它里面所有的数据就没了。日志、数据库文件、上传的图片……全没了。

我第一次意识到这个问题,是在测试环境删了一个MySQL容器。嗯,当时数据库里还有测试数据,我忘了备份。从那以后,我再也不敢不挂载数据卷就跑数据库了。

Docker提供了三种数据持久化方式:数据卷(Volume)绑定挂载(Bind Mount)tmpfs挂载。我重点讲前两个。

4.3.1 数据卷(Volume)

数据卷是Docker推荐的方式。它由Docker管理,存储在宿主机的一个特定目录下(通常是/var/lib/docker/volumes/)。

# 创建数据卷
docker volume create my-data

# 挂载数据卷到容器
docker run -d --name db \
  -v my-data:/var/lib/mysql \
  mysql:8.0

数据卷的好处:

  • 由Docker管理,不需要关心具体路径
  • 支持卷驱动,可以挂载到远程存储(比如NFS、云存储)
  • 多个容器可以共享同一个数据卷
  • 备份和迁移方便

我的习惯:生产环境一律用数据卷。绑定挂载虽然方便,但权限问题容易搞出幺蛾子。数据卷由Docker管理,权限控制更规范。

4.3.2 绑定挂载(Bind Mount)

绑定挂载就是把宿主机上的一个目录或文件,直接挂载到容器里。说白了,就是共享文件夹。

# 把宿主机当前目录挂载到容器的/app目录
docker run -d --name dev \
  -v $(pwd):/app \
  node:18

绑定挂载最适合开发环境——你改了代码,容器里立刻就能看到。我开发Node.js应用时,经常用这种方式,改完代码直接刷新浏览器就能看到效果,不用重新构建镜像。

注意:绑定挂载有个坑——权限问题。宿主机上的文件所有者UID和容器里的用户UID可能不一致。我曾经因为这个,容器里的应用写不了日志文件。解决方案是确保UID一致,或者在容器里用user: root(不推荐生产用)。

4.3.3 数据卷 vs 绑定挂载

对比项 数据卷(Volume) 绑定挂载(Bind Mount)
管理方式 Docker管理 用户管理
存储位置 /var/lib/docker/volumes/ 任意路径
跨宿主机迁移 支持(卷驱动) 不支持
权限控制 自动处理 需手动处理
适用场景 生产环境、数据库 开发环境、配置文件

4.4 实战:一个完整的Web应用部署

说了这么多,咱们来点实际的。假设你要部署一个Web应用,前端用Nginx,后端用Node.js,数据库用PostgreSQL。怎么用Docker网络和存储把它们串起来?

# 1. 创建自定义网络
docker network create web-app-net

# 2. 创建数据卷
docker volume create pg-data

# 3. 启动数据库
docker run -d --name postgres \
  --network web-app-net \
  -v pg-data:/var/lib/postgresql/data \
  -e POSTGRES_PASSWORD=mysecret \
  postgres:15

# 4. 启动后端应用
docker run -d --name backend \
  --network web-app-net \
  -e DB_HOST=postgres \
  -e DB_PORT=5432 \
  my-backend:latest

# 5. 启动前端Nginx
docker run -d --name frontend \
  --network web-app-net \
  -p 80:80 \
  -v ./nginx.conf:/etc/nginx/nginx.conf:ro \
  nginx:alpine

你看,这里用到了:

  • 自定义网络:三个容器都在web-app-net里,通过容器名互相访问
  • 数据卷:PostgreSQL的数据持久化到pg-data卷里,容器删了数据还在
  • 绑定挂载:Nginx的配置文件从宿主机挂载进去,方便修改
  • 端口映射:只有前端Nginx暴露80端口给外部,后端和数据库都不暴露

核心原则:网络层面,能隔离就隔离;存储层面,能持久化就持久化。这两个原则做到了,你的容器化应用就稳了一大半。

好了,这一章的内容就到这儿。网络和存储是Docker的基石,搞懂了它们,你就能搭建出真正可用的容器化应用。下一章咱们聊聊Docker Compose——怎么用一份YAML文件管理多个容器。到时候你会发现,有了Compose,前面这些手动操作都可以自动化了。