努力
加油

Ubuntu下多个Docker容器之间如何通信和数据共享?

在 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

在这个架构中:

  1. 通信:web 容器可以通过域名 backend 或 db 直接访问其他服务,因为它们在同一个 app-network 中。
  2. 数据共享:static_files 这个 Volume 被 web 和 backend 同时挂载,实现了静态资源的共享。
  3. 数据持久化:db_data 确保即使容器重建,数据库内容也不会丢失。

四、 避坑指南 & 最佳实践

  1. 不要直接在容器内写数据:永远记住容器是无状态的。任何需要长期保存的数据,必须通过 Volume 或 Bind Mount 映射出去。
  2. 权限陷阱:在 Ubuntu 上使用 Bind Mount 时,如果容器内应用以非 root 用户运行,而挂载的文件属主是 root,会报 Permission denied。解决方法:
    • 在宿主机提前 chown 好目录权限。
    • 或者在 Dockerfile 中创建对应的用户和组,并在 docker run 时指定 -u。
  3. 网络隔离原则:不要把业务容器和外部互联网直接桥接。如果需要对外提供服务,只暴露必要的端口(如 80, 443),内部服务(如 Redis, MySQL)不应暴露端口给宿主机,只能通过内部网络通信。
  4. 清理垃圾:定期运行 docker system prune 清理未使用的镜像、容器和网络,避免磁盘被占满。特别是那些不再使用的匿名 Volume。

总结来说,通信靠自定义 Bridge 网络,数据靠 Named Volumes。掌握这两点,就能覆盖 90% 以上的 Docker 部署场景。