3、Docker网络与存储:网络模式(Bridge/Host/Overlay)、数据持久化(Volume/Bind Mount)、跨容器通信实战

好,咱们今天聊点硬核的。Docker 的网络和存储,说白了就是让容器能「连得上」和「存得住」。我刚开始用 Docker 那会儿,觉得这俩东西没啥难的,结果一上生产环境就踩了不少坑。嗯,今天我把这些经验掰开揉碎了讲给你听。

3.1 Docker 网络模式:容器怎么「说话」

Docker 默认给你建了个叫 bridge 的网络。你跑个 docker run,容器就自动连上去了。但实际项目中,光靠默认的可不够。

3.1.1 Bridge 模式:最常用的「小局域网」

Bridge 模式,你可以理解成 Docker 在宿主机上搭了个虚拟交换机。每个容器都有一块虚拟网卡,插在这个交换机上。容器之间能互相通信,但和宿主机外面是隔离的。

我个人习惯,在单机开发环境里,就用默认的 bridge。比如我跑一个 MySQL 和一个 Web 应用:

# 创建自定义 bridge 网络(我推荐用自定义的,别用默认的)
docker network create my-net

# 启动 MySQL
docker run -d --name mysql --network my-net -e MYSQL_ROOT_PASSWORD=root mysql:8.0

# 启动 Web 应用,连同一个网络
docker run -d --name webapp --network my-net -p 8080:80 my-web-image

这样 Web 应用里直接用 mysql 这个主机名就能连上数据库。方便吧?

我的小技巧: 自定义 bridge 网络支持 DNS 解析,容器名就是主机名。默认的 bridge 不行,得靠 --link,但那玩意儿已经过时了。所以,别偷懒,自己建个网络吧。

3.1.2 Host 模式:性能优先的选择

Host 模式就简单粗暴了——容器直接共享宿主机的网络栈。没有网络隔离,性能最好,但安全性差一些。

我在项目中遇到过需要高吞吐量的日志采集器,比如 Filebeat 或 Fluentd。这时候用 Host 模式,能省掉一层 NAT 转换,延迟更低。

docker run -d --name log-collector --network host my-log-image

但要注意,端口冲突问题。容器里监听 80 端口,就等于宿主机监听了 80 端口。你想想看,要是两个容器都用 Host 模式监听同一个端口,那就打架了。

我曾经踩过的坑: 有一次在测试环境用了 Host 模式跑了一个 Nginx,结果忘了宿主机上本来就有个 Nginx。两个进程抢 80 端口,服务直接挂了。排查了半天才发现。所以,Host 模式慎用,尤其在生产环境。

3.1.3 Overlay 模式:跨主机的「魔法隧道」

Overlay 模式是 Swarm 集群的标配。它能让不同宿主机上的容器,像在同一个局域网里一样通信。底层用的是 VXLAN 技术,说白了就是隧道封装。

我刚开始接触 Overlay 网络时,觉得这东西挺神奇的。其实原理不复杂:每个宿主机上有个「数据面」的虚拟网卡,容器发出的包被封装成 UDP 包,发到目标宿主机上再解封装。

# 初始化 Swarm 集群
docker swarm init

# 创建 Overlay 网络
docker network create -d overlay my-overlay-net

# 在不同节点上启动服务
docker service create --name web --network my-overlay-net --replicas 3 my-web-image

这里要注意,Overlay 网络需要 Swarm 模式才能用。如果你只是单机玩,用不上它。

3.2 数据持久化:容器挂了,数据不能丢

容器是无状态的,这大家都知道。但数据库、配置文件这些,总不能每次重启都丢吧?Docker 给了我们两种方案:Volume 和 Bind Mount。

3.2.1 Volume:Docker 帮你管数据

Volume 是 Docker 推荐的方式。数据存在 Docker 的管理目录下(通常是 /var/lib/docker/volumes/),你不需要关心具体路径。

我个人习惯,所有重要的数据都用 Volume。比如 MySQL 的数据目录:

# 创建 Volume
docker volume create mysql-data

# 挂载到容器
docker run -d --name mysql \
  -v mysql-data:/var/lib/mysql \
  -e MYSQL_ROOT_PASSWORD=root \
  mysql:8.0

这样就算容器删了,数据还在。下次启动时重新挂载同一个 Volume,数据就回来了。

我的经验:docker volume inspect mysql-data 可以查看 Volume 的详细信息。调试时很有用。

3.2.2 Bind Mount:把宿主机目录「借」给容器

Bind Mount 就是把宿主机上的一个目录直接挂到容器里。开发时特别方便,因为你可以实时修改代码,容器里立刻生效。

我记得刚开始做 Python 后端开发时,每次改代码都要重新构建镜像,烦死了。后来用了 Bind Mount,开发效率直接翻倍。

# 把当前目录挂载到容器的 /app 目录
docker run -d --name my-app \
  -v $(pwd):/app \
  -p 8000:8000 \
  my-python-image

但要注意,Bind Mount 的权限问题。宿主机上的目录权限会直接影响容器。我曾经因为宿主机目录权限是 root,导致容器里的应用写不了日志,排查了半天。

避坑指南: 生产环境尽量用 Volume,别用 Bind Mount。Bind Mount 更适合开发调试。另外,Bind Mount 如果宿主机目录不存在,Docker 不会自动创建,会报错。而 Volume 会自动创建。

3.3 跨容器通信实战:让容器「协作」起来

好了,理论讲完了,咱们来点实战。假设我们要部署一个 Python Web 应用,后端用 Redis 做缓存,MySQL 做持久化存储。三个容器需要互相通信。

3.3.1 架构设计

先画个简单的架构:

  • Web 容器:Python Flask 应用,监听 5000 端口
  • Redis 容器:缓存服务,监听 6379 端口
  • MySQL 容器:数据库,监听 3306 端口

它们都在同一个自定义 bridge 网络里,通过容器名互相访问。

3.3.2 网络与存储配置

咱们一步步来:

# 1. 创建网络
docker network create app-net

# 2. 创建 Volume
docker volume create mysql-data
docker volume create redis-data

# 3. 启动 MySQL
docker run -d --name mysql \
  --network app-net \
  -v mysql-data:/var/lib/mysql \
  -e MYSQL_ROOT_PASSWORD=root \
  -e MYSQL_DATABASE=myapp \
  mysql:8.0

# 4. 启动 Redis
docker run -d --name redis \
  --network app-net \
  -v redis-data:/data \
  redis:7-alpine

# 5. 启动 Web 应用
docker run -d --name web \
  --network app-net \
  -p 5000:5000 \
  -e DB_HOST=mysql \
  -e REDIS_HOST=redis \
  my-python-app:latest

看到没?Web 应用里配置 DB_HOST=mysql,它就能通过主机名 mysql 连上数据库。Redis 同理。

3.3.3 Python 代码里的连接方式

在 Flask 应用里,连接数据库和 Redis 的代码大概是这样的:

import os
import redis
import pymysql

# 从环境变量读取主机名
db_host = os.getenv('DB_HOST', 'localhost')
redis_host = os.getenv('REDIS_HOST', 'localhost')

# 连接 MySQL
conn = pymysql.connect(
    host=db_host,
    user='root',
    password='root',
    database='myapp'
)

# 连接 Redis
cache = redis.Redis(host=redis_host, port=6379, db=0)

这样,容器启动时只要传对了环境变量,就能自动找到对方。是不是很简单?

我的建议: 实际项目中,别把数据库密码直接写在环境变量里。用 Docker Secrets 或者专门的密钥管理服务。这里为了演示,我就简化了。

3.4 网络排障小技巧

容器通信出问题了怎么办?别慌,我教你几招:

  1. 检查网络列表docker network ls 看看网络是否存在
  2. 检查容器是否在同一个网络docker inspect 容器名 | grep Networks
  3. 进入容器测试连通性docker exec -it 容器名 ping 另一个容器名
  4. 查看容器日志docker logs 容器名 看看有没有连接错误

我曾经遇到过一个诡异的问题:两个容器在同一个网络里,但就是连不上。后来发现是容器里的应用用了 localhost 而不是容器名。你想想看,每个容器的 localhost 都是自己,怎么可能连到别人呢?

记住这个原则: 跨容器通信,永远用容器名或服务名,别用 localhost127.0.0.1

3.5 本章小结

好,咱们来捋一捋今天的内容:

  • 网络模式:Bridge 适合单机隔离,Host 适合高性能场景,Overlay 适合跨主机集群
  • 数据持久化:Volume 是生产首选,Bind Mount 适合开发调试
  • 跨容器通信:用自定义 bridge 网络 + 容器名,简单又可靠

这些知识点,说白了就是 Docker 运维的「基本功」。你把这些搞明白了,后面学 Docker Compose 和 Kubernetes 就会轻松很多。下一章咱们聊聊 Docker Compose,那个才是真正的「一键部署」利器。

嗯,今天就到这儿。有什么问题,咱们评论区见。