1. 概述
在使用 Docker 的过程中,我们通常会构建多个容器,这些容器通过隔离的网络进行通信。但在某些场景下,我们希望这些容器运行在同一台主机上并能相互通信,例如在进行集成测试时。Docker 提供了多种网络配置方式来支持这种需求,比如通过容器名、静态 IP 或直接访问主机。
本教程将演示如何使用 Docker Compose 来实现同一台主机上两个容器之间的通信。
2. Docker 环境准备
我们以两个基于 Alpine 镜像的容器为例,验证它们是否可以通过网络互相通信。
首先我们准备一个自定义的 Dockerfile
,添加一些常用的调试命令,如 ping
和 bash
:
FROM alpine:latest
MAINTAINER baeldung.com
RUN apk update && apk add bash && apk add iputils
然后我们使用 docker-compose
命令来启动和管理容器:
# 启动容器
docker-compose -f yaml-file up -d
# 停止并删除容器
docker-compose -f yaml-file down
# 清理未使用的网络(推荐在开始前执行)
docker network prune
Docker 会根据 docker-compose.yml
所在目录名自动生成网络名称,例如 目录名_default
。若未显式指定网络名,默认使用 default
。
3. 使用 DNS 通信
Docker 内置了一个 DNS 服务,它将容器名等别名映射到对应的 IP 地址。即使 IP 地址发生变化,容器仍可通过别名访问。
我们创建一个 docker-compose.yml
文件如下:
services:
alpine-app-1:
container_name: alpine-app-1
image: alpine-app-1
build:
context: ..
dockerfile: Dockerfile
tty: true
alpine-app-2:
container_name: alpine-app-2
image: alpine-app-2
build:
context: ..
dockerfile: Dockerfile
tty: true
启动容器后,使用 docker network inspect
查看网络信息:
docker network inspect dns_default
输出中会显示两个容器的 IP 地址,例如:
"IPv4Address": "172.25.0.2/16"
"IPv4Address": "172.25.0.3/16"
我们可以通过 IP、容器名或容器 ID 进行 ping 测试:
docker exec -it alpine-app-1 ping 172.25.0.3
docker exec -it alpine-app-1 ping alpine-app-2
docker exec -it alpine-app-1 ping 577c6ac4aae4
所有方式都能成功通信,响应如下:
64 bytes from alpine-app-2.dns_default (172.25.0.3): icmp_seq=1 ttl=64 time=0.152 ms
✅ 小结:Docker 的 DNS 服务使得容器可以通过名称或 ID 直接通信,非常适合本地服务间调用或集成测试。
4. 使用静态 IP 地址
虽然 Docker 默认会自动分配 IP 地址,但有时我们希望为容器分配固定的 IP,以确保服务地址的稳定性。
4.1. 使用 Bridge 网络配置静态 IP
我们显式定义一个自定义的 bridge 网络,并为每个容器指定 IP:
services:
alpine-app-1:
container_name: alpine-app-1
build:
context: ..
dockerfile: Dockerfile
image: alpine-app-1
tty: true
networks:
network-example:
ipv4_address: 10.5.0.2
alpine-app-2:
container_name: alpine-app-2
build:
context: ..
dockerfile: Dockerfile
image: alpine-app-2
tty: true
networks:
network-example:
ipv4_address: 10.5.0.3
networks:
network-example:
driver: bridge
ipam:
config:
- subnet: 10.5.0.0/16
gateway: 10.5.0.1
启动后,我们可以通过 ifconfig
在主机上看到新增的桥接网络接口:
br-2fda6ab68472: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.5.0.1 netmask 255.255.0.0 broadcast 10.5.255.255
容器之间依然可以通过 IP、名称或 ID 通信:
docker exec -it alpine-app-1 ping 10.5.0.3
docker exec -it alpine-app-1 ping alpine-app-2
✅ 小结:使用静态 IP 可以让服务地址更稳定,适用于生产环境或需要固定 IP 的服务部署。
4.2. 使用 Macvlan 网络
如果希望容器拥有独立的 MAC 地址,并直接连接物理网络,可以使用 Macvlan 网络。这种方式只适用于 Linux 系统。
services:
alpine-app-1:
container_name: alpine-app-1
build:
context: ..
dockerfile: Dockerfile
image: alpine-app-1
tty: true
networks:
network-example:
ipv4_address: 192.168.2.2
alpine-app-2:
container_name: alpine-app-2
build:
context: ..
dockerfile: Dockerfile
image: alpine-app-2
tty: true
networks:
network-example:
ipv4_address: 192.168.2.3
networks:
network-example:
driver: macvlan
driver_opts:
parent: enp0s3
ipam:
config:
- subnet: 192.168.2.0/24
gateway: 192.168.2.1
⚠️ 注意:使用 Macvlan 时,主机无法直接访问容器,需要手动配置路由规则:
sudo ip link add macvlan-net link enp0s3 type macvlan mode bridge \
&& sudo ip addr add 192.168.2.50/32 dev macvlan-net \
&& sudo ip link set macvlan-net up
sudo ip route add 192.168.2.0/24 dev macvlan-net
✅ 小结:Macvlan 是实现容器直接接入物理网络的理想选择,适合对网络性能和拓扑结构有较高要求的场景。
5. 使用 host.docker.internal 访问主机
当容器需要访问运行在主机上的服务时,可以使用 host.docker.internal
这个特殊 DNS 名称。它指向 Docker 主机的默认网关地址。
5.1. 创建一个 Node.js 接口服务
我们创建一个简单的 Node.js 服务,监听 8080 端口并返回 “Hello World”:
var express = require('express')
var app = express()
app.get('/', function (req, res) {
res.send('Hello World!')
})
app.listen(8080, function () {
console.log('app listening on port 8080!')
})
对应的 Dockerfile
:
FROM node:8.16.1-alpine
WORKDIR /app
COPY host_docker_internal/package.json /app
COPY host_docker_internal/index.js /app
RUN npm install
CMD node index.js
EXPOSE 8080
5.2. 配置 Alpine 容器访问主机服务
我们在 docker-compose.yml
中为 Alpine 容器添加 host.docker.internal
的解析(仅适用于 Linux):
services:
alpine-app-1:
container_name: alpine-app-1
extra_hosts:
- host.docker.internal:host-gateway
build:
context: ..
dockerfile: Dockerfile
image: alpine-app-1
tty: true
networks:
- first-network
node-app:
container_name: node-app
build:
context: ..
dockerfile: Dockerfile.node
image: node-app
ports:
- 8080:8080
networks:
- second-network
networks:
first-network:
driver: bridge
second-network:
driver: bridge
然后从 Alpine 容器中访问 Node.js 接口:
docker exec -it alpine-app-1 curl -i -X GET http://host.docker.internal:8080
返回结果:
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 12
ETag: W/"c-Lve95gjOVATpfV8EL5X4nxwjKHE"
Date: Tue, 24 Jan 2023 18:33:38 GMT
Connection: keep-alive
Hello World!
我们可以查看 /etc/hosts
文件确认解析:
172.17.0.1 host.docker.internal
✅ 小结:host.docker.internal
是容器访问主机服务的便捷方式,尤其适用于本地开发调试。
6. 总结
本文介绍了几种在同一台机器上实现 Docker 容器间通信的方式:
通信方式 | 说明 | 适用场景 |
---|---|---|
DNS 通信 | 容器通过名称或 ID 相互访问 | 本地服务间通信、集成测试 |
静态 IP | 显式分配 IP,适合地址固定的服务 | 生产环境、网络地址固定需求 |
Macvlan | 容器拥有独立 MAC 地址,接入物理网络 | 高性能网络、直接接入 LAN |
host.docker.internal | 容器访问主机服务 | 本地开发调试、服务依赖主机 |
如需完整代码示例,可参考 GitHub 仓库。