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,前面这些手动操作都可以自动化了。