Skip to content

容器互联

容器互联是指多个容器之间建立网络通信。本节介绍容器间通信的各种方式和最佳实践。

通信方式概述

方式说明推荐程度
用户定义网络通过容器名 DNS 解析⭐⭐⭐ 推荐
--link(已废弃)旧版连接方式❌ 不推荐
端口映射通过宿主机 IP 访问仅需从外部访问时
host 网络共享宿主机网络特殊高性能场景

方式一:用户定义网络(推荐)

同一用户定义网络中的容器,内置 DNS 自动解析容器名:

bash
# 创建网络
docker network create app-net

# 启动服务
docker run -d --name redis --network app-net redis:alpine
docker run -d --name api --network app-net myapi:latest

# api 容器通过名称访问 redis
# 在 api 应用中配置:redis://redis:6379

Node.js 连接 Redis(通过容器名):

javascript
const redis = require('redis');
const client = redis.createClient({
  url: 'redis://redis:6379'  // "redis" 是容器名
});

Python 连接 MySQL(通过容器名):

python
import pymysql
conn = pymysql.connect(
    host='mysql',  # "mysql" 是容器名
    user='root',
    password='secret',
    database='mydb'
)

旧版容器连接方式,现已不推荐使用:

bash
# 启动 MySQL
docker run -d --name mysql -e MYSQL_ROOT_PASSWORD=secret mysql:8.0

# 使用 --link 连接(会自动注入环境变量和 /etc/hosts)
docker run -d --name app --link mysql:db myapp

# 容器内可以通过 "db" 访问 MySQL(--link 的别名)

为什么废弃--link 单向连接,不支持用户定义网络的自动 DNS,且 Docker 官方已将其标记为遗留功能。

方式三:同一主机端口映射

通过宿主机 IP 和端口进行通信(性能稍低,但适用于跨网络场景):

bash
# 容器 A 暴露端口到宿主机
docker run -d --name service-a -p 8080:8080 service-a

# 容器 B 通过宿主机 IP 访问
# 注意:不能用 localhost,要用宿主机实际 IP 或 host.docker.internal
docker run -d --name service-b \
  -e SERVICE_A_URL=http://192.168.1.100:8080 \
  service-b

在 Docker Desktop(macOS/Windows)中,可以使用 host.docker.internal 访问宿主机:

bash
docker run -d --name app \
  -e DB_HOST=host.docker.internal \
  myapp

实战:前后端 + 数据库三层架构

bash
# 创建网络
docker network create webapp

# 1. 数据库层
docker run -d \
  --name mysql \
  --network webapp \
  -e MYSQL_ROOT_PASSWORD=secret \
  -e MYSQL_DATABASE=myapp \
  -v mysql-data:/var/lib/mysql \
  mysql:8.0

# 2. 等待 MySQL 启动(实际可以在应用中用重试逻辑)
sleep 30

# 3. 后端 API 层
docker run -d \
  --name api \
  --network webapp \
  -e DB_HOST=mysql \
  -e DB_PORT=3306 \
  -e DB_NAME=myapp \
  -e DB_USER=root \
  -e DB_PASS=secret \
  myapi:latest

# 4. 前端 + 反向代理
docker run -d \
  --name nginx \
  --network webapp \
  -p 80:80 \
  -v ./nginx.conf:/etc/nginx/conf.d/default.conf:ro \
  nginx:alpine

Nginx 配置:

nginx
upstream api {
    server api:3000;
}

server {
    listen 80;

    # 前端静态文件
    location / {
        root /usr/share/nginx/html;
        try_files $uri $uri/ /index.html;
    }

    # 转发 API 请求(通过容器名 "api" 访问)
    location /api {
        proxy_pass http://api;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

实战:微服务间通信

bash
# 创建微服务网络
docker network create microservices

# 用户服务
docker run -d --name user-service \
  --network microservices \
  user-service:latest

# 订单服务(依赖用户服务)
docker run -d --name order-service \
  --network microservices \
  -e USER_SERVICE_URL=http://user-service:8080 \
  order-service:latest

# API 网关(对外暴露)
docker run -d --name api-gateway \
  --network microservices \
  -p 80:80 \
  -e USER_SERVICE=http://user-service:8080 \
  -e ORDER_SERVICE=http://order-service:8081 \
  api-gateway:latest

验证容器间通信

bash
# 进入容器,测试连通性
docker exec -it api sh

# 测试 DNS 解析
nslookup mysql
# Server: 127.0.0.11(Docker 内置 DNS)
# Address: 127.0.0.11:53
# Name: mysql
# Address: 172.20.0.2

# 测试连通性
ping mysql
curl http://api:3000/health
nc -zv mysql 3306  # 测试 TCP 端口连通

容器等待依赖就绪

容器互联时,经常遇到依赖服务(如数据库)还没完全启动的问题:

方式一:应用内重试

javascript
// Node.js 数据库连接重试
const connectWithRetry = () => {
  mongoose.connect(MONGO_URI).then(() => {
    console.log('MongoDB connected');
  }).catch(err => {
    console.log('MongoDB connection failed, retrying in 5s...');
    setTimeout(connectWithRetry, 5000);
  });
};
connectWithRetry();

方式二:wait-for-it 脚本

dockerfile
# 下载 wait-for-it.sh
ADD https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh /wait-for-it.sh
RUN chmod +x /wait-for-it.sh

CMD ["/wait-for-it.sh", "mysql:3306", "--", "node", "app.js"]

方式三:Docker Compose 的 depends_on + healthcheck

参考 Docker Compose 基础 章节。

总结

容器互联的最佳实践:

  1. 使用用户定义网络,不要使用默认 bridge 网络
  2. 通过容器名访问,不要依赖 IP 地址(IP 可能变化)
  3. 应用层实现重试,处理服务启动顺序问题
  4. 合理分层:数据层、服务层、接入层使用不同网络
  5. 最小权限:不需要对外暴露的服务不映射端口