在 Ubuntu 环境下部署 Docker,容器间通信和数据共享是基础但极其关键的环节。很多新手容易混淆“网络互通”和“数据持久化/共享”这两个概念,其实它们对应的是 Docker 的两个不同核心机制:Network(网络)与 Volume/Data Volume(卷/挂载点)。
下面我从生产环境常用的角度,分场景拆解这两种需求的具体实现方式,尽量避开那些花里胡哨的理论,直接上干货。
一、 容器间通信:解决“能不能互相访问”的问题
默认情况下,Docker 创建的容器之间是隔离的,无法通过 localhost 或 IP 地址直接通信。要实现通信,主要有以下三种主流方案,按推荐程度排序:
1. 使用自定义 Bridge 网络(最推荐)
这是目前最标准、最稳定的做法。Docker 默认的 bridge 网络虽然也能通,但不支持 DNS 解析,容器重启后 IP 会变,导致配置失效。
操作逻辑:
创建一个自定义的网络,将需要通信的容器加入同一个网络。在这个网络中,容器可以通过服务名称(即容器名)互相 ping 通,无需关心 IP。
# 1. 创建自定义网络
docker network create my-internal-net
# 2. 启动容器 A (例如 Nginx),指定网络
docker run -d --name web-server --network my-internal-net nginx:latest
# 3. 启动容器 B (例如 PHP-FPM),指定同一网络
docker run -d --name php-fpm --network my-internal-net php:8.0-fpm
# 4. 验证通信
# 进入容器 B,尝试 ping 容器 A 的名字
docker exec -it php-fpm ping web-server
优点:
- DNS 自动解析:容器间通过名字通信,IP 变动不影响连接。
- 隔离性:只有加入该网络的容器才能通信,其他容器默认不可达,安全性更高。
2. 使用 --link 参数(已过时,不推荐)
早期版本常用 docker run --link container_name:alias。虽然简单,但已被官方标记为废弃。它会导致容器耦合度高,且不支持动态扩展,维护起来非常痛苦。除非你在维护一个十年前的老项目,否则请忽略此方法。
3. 共享 Host 网络模式(特定场景)
如果两个容器都需要暴露端口到宿主机,或者对网络延迟极度敏感,可以使用 network_mode: host。
# docker-compose.yml 示例
services:
app1:
image: myapp
network_mode: "host"
app2:
image: myapp
network_mode: "host"
注意:
- 使用 Host 模式时,容器直接使用宿主机的网络栈,端口不能冲突。
- 失去了 Docker 网络的隔离优势,安全性较低。
- 适合数据库集群内部同步等对性能要求极高且信任度高的场景。
二、 数据共享:解决“数据存在哪、怎么共用”的问题
容器是临时的,一旦删除,里面的数据就没了。因此,我们需要将数据从容器内部映射到宿主机或其他容器。
1. Bind Mount(绑定挂载):开发调试首选
直接将宿主机的某个目录挂载到容器内。
docker run -d
--name data-consumer
-v /home/ubuntu/shared-data:/data
ubuntu:latest
特点:
- 实时同步:宿主机修改文件,容器内立即生效。
- 权限问题:需要注意 UID/GID 匹配。Ubuntu 宿主机的用户 ID 通常是 1000,如果容器内运行的是 root (UID 0),可能会出现权限拒绝。建议在
docker run中使用-u $(id -u):$(id -g)来切换用户。 - 适用场景:代码开发、配置文件管理、临时数据交换。
2. Named Volumes(命名卷):生产环境首选
由 Docker 引擎管理,存储在 /var/lib/docker/volumes/ 下,而不是宿主机的任意位置。
# 1. 创建卷
docker volume create shared_data_vol
# 2. 容器 A 写入数据
docker run -d --name writer -v shared_data_vol:/data alpine ash -c "echo 'hello' > /data/file.txt"
# 3. 容器 B 读取数据
docker run -d --name reader -v shared_data_vol:/data alpine cat /data/file.txt
特点:
- 解耦:数据不依赖于宿主机具体路径,迁移方便。
- 备份恢复:Docker 提供了
docker run --rm -v shared_data_vol:/data -v $(pwd):/backup alpine tar cvf /backup/data.tar /data这样的便捷备份命令。 - 多容器共享:多个容器可以同时挂载同一个 Volume,实现数据共享。
- 适用场景:数据库存储、日志收集、应用状态数据。
3. tmpfs Mount(内存挂载):敏感数据
如果数据不需要持久化,且希望提高 I/O 性能,可以使用 tmpfs。
docker run -d --name temp-app -v /tmp-data:tmpfs nginx
特点:
- 数据仅存在于内存中,容器重启后数据丢失。
- 适用于存放密码、密钥等临时敏感信息。
三、 实战组合:用 Docker Compose 优雅管理
在实际工作中,我们很少手动敲 docker run,而是使用 docker-compose.yml 来定义网络和数据卷。这才是真正的“大神级”日常用法。
假设我们要搭建一个 Web 服务 + 数据库,并共享静态资源:
version: '3.8'
services:
# Web 服务
web:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./html:/usr/share/nginx/html # Bind mount: 方便修改网页
- static_files:/data/static # Named Volume: 共享静态文件
networks:
- app-network
# 后端服务
backend:
image: python:3.9-slim
command: python manage.py runserver 0.0.0.0:8000
volumes:
- ./code:/app # Bind mount: 代码热更新
- static_files:/data/static # 挂载同一个 Volume,与 Web 共享
networks:
- app-network
# 数据库
db:
image: postgres:13
environment:
POSTGRES_PASSWORD: example
volumes:
- db_data:/var/lib/postgresql/data # Named Volume: 数据持久化
networks:
- app-network
# 定义网络
networks:
app-network:
driver: bridge # 自定义 Bridge 网络,自动 DNS 解析
# 定义数据卷
volumes:
static_files:
driver: local # 显式声明驱动,默认为 local
db_data:
driver: local
在这个架构中:
- 通信:
web容器可以通过域名backend或db直接访问其他服务,因为它们在同一个app-network中。 - 数据共享:
static_files这个 Volume 被web和backend同时挂载,实现了静态资源的共享。 - 数据持久化:
db_data确保即使容器重建,数据库内容也不会丢失。
四、 避坑指南 & 最佳实践
- 不要直接在容器内写数据:永远记住容器是无状态的。任何需要长期保存的数据,必须通过 Volume 或 Bind Mount 映射出去。
- 权限陷阱:在 Ubuntu 上使用 Bind Mount 时,如果容器内应用以非 root 用户运行,而挂载的文件属主是 root,会报
Permission denied。解决方法:- 在宿主机提前
chown好目录权限。 - 或者在 Dockerfile 中创建对应的用户和组,并在
docker run时指定-u。
- 在宿主机提前
- 网络隔离原则:不要把业务容器和外部互联网直接桥接。如果需要对外提供服务,只暴露必要的端口(如 80, 443),内部服务(如 Redis, MySQL)不应暴露端口给宿主机,只能通过内部网络通信。
- 清理垃圾:定期运行
docker system prune清理未使用的镜像、容器和网络,避免磁盘被占满。特别是那些不再使用的匿名 Volume。
总结来说,通信靠自定义 Bridge 网络,数据靠 Named Volumes。掌握这两点,就能覆盖 90% 以上的 Docker 部署场景。
云技术博客