上节我们讲 Docker Compose 的时候,涉及到多个 docker 容器的通信,我们是通过指定宿主机 ip 和端口的方式。
因为 mysql、redis 的 Docker 容器都映射到了宿主机的端口,那 nest 的容器就可以通过宿主机来实现和其他容器的通信。
Docker 的实现原理那节我们讲过,Docker 通过 Namespace 的机制实现了容器的隔离,其中就包括 Network Namespace。
因为每个容器都有独立的 Network Namespace,所以不能直接通过端口访问其他容器的服务。
那如果这个 Network Namespace 不只包括一个 Docker 容器呢??
可以创建一个 Network Namespace,然后设置到多个 Docker 容器,这样这些容器就在一个 Namespace 下了,不就可以直接访问对应端口了?
Docker 确实支持这种方式,叫做桥接网络。
通过 docker network 来创建:
docker network create common-network
然后把之前的 3 个容器停掉、删除,我们重新跑:
docker stop mysql-container redis-container nest-container
docker rm mysql-container redis-container nest-container
这次跑的时候要指定 --network:
docker run -d --network common-network -v /Users/guang/mysql-data:/var/lib/mysql --name mysql-container mysql
通过 --network 指定桥接网络为我们刚创建的 common-network。
不需要指定和宿主机的端口映射。
然后跑 redis 容器:
docker run -d --network common-network -v /Users/guang/aaa:/data --name redis-container redis
同样也不需要指定和宿主机的端口映射,只需要指定挂载的数据卷就行:
然后 nest 的部分我们要改下代码:
修改 AppModule 的代码,改成用容器名来访问:
然后 docker build:
docker build -t mmm .
之后 docker run:
docker run -d --network common-network -p 3000:3000 --name nest-container mmm
nest 容器是要指定和宿主机的端口映射的,因为宿主机要访问这个端口的网页。
然后 docker logs 看下日志:
docker logs nest-container
可以看到打印了 sql 语句,说明 mysql 连接成功了:
浏览器访问 http://localhost:3000
然后再看下日志:
docker logs nest-container
打印了 redis 的 key 说明 redis 服务也连接成功了。
这就是桥接网络。
之前我们是通过宿主机 ip 来互相访问的:
现在可以通过容器名直接互相访问了:
原理前面讲过,就是 Namespace。
本来是 3 个独立的 Network Namespace:
桥接之后就这样了:
Namespace 下包含多个子 Namespace,互相能通过容器名访问。
比起端口映射到宿主机,再访问宿主机 ip 的方式,简便太多了。
那在 Docker Compose 里怎么使用这种方式呢?
之前我们是这样写的:
现在改成这样:
version: '3.8'
services:
nest-app:
build:
context: ./
dockerfile: ./Dockerfile
depends_on:
- mysql-container
- redis-container
ports:
- '3000:3000'
networks:
- common-network
mysql-container:
image: mysql
volumes:
- /Users/guang/mysql-data:/var/lib/mysql
networks:
- common-network
redis-container:
image: redis
volumes:
- /Users/guang/aaa:/data
networks:
- common-network
networks:
common-network:
driver: bridge
version 是指定 docker-compose.yml 的版本,因为不同版本配置不同。
把 mysql-container、redis-container 的 ports 映射去掉,指定桥接网络为 common-network。
然后下面通过 networks 指定创建的 common-network 桥接网络,网络驱动程序指定为 bridge。
其实我们一直用的网络驱动程序都是 bridge,它的含义是容器的网络和宿主机网络是隔离开的,但是可以做端口映射。比如 -p 3000:3000、-p 3306:3306 这样。
然后执行:
docker-compose down --rmi all
就会删除 3 个容器和它们的镜像:
之后再
docker-compose up
可以看到,会先 build dockerfile 产生镜像,然后把 3 个镜像跑起来。
看到打印的 sql 说明 mysql 服务连接成功了。
(这个过程可能因为 mysql 容器没跑起来而连接失败几次,等一会就好了)
浏览器访问下:
也拿到了 redis 的 key,说明 redis 服务跑成功了:
这就是在 docker-compose 里使用桥接网络的方式。
不过,其实不指定 networks 也可以,docker-compose 会创建个默认的。
先把容器、镜像删掉:
docker-compose down --rmi all
把 networks 部分注释掉,重新跑:
你会发现它创建了一个默认的 network:
mysql 和 redis 的访问都是正常的:
所以,不手动指定 networks,也是可以用桥接网络的。
案例代码在小册仓库。
总结
上节我们是把 mysql、redis 的端口映射到宿主机,然后 nest 的容器里通过宿主机 ip 访问这两个服务的。
但其实有更方便的方式,就是桥接网络。
通过 docker network create 创建一个桥接网络,然后 docker run 的时候指定 --network,这样 3 个容器就可以通过容器名互相访问了。
在 docker-compose.yml 配置下 networks 创建桥接网络,然后添加到不同的 service 上即可。
或者不配置 networkds,docker-compose 会生成一个默认的。
实现原理就是对 Network Namespace 的处理,本来是 3个独立的 Namespace,当指定了 network 桥接网络,就可以在 Namespace 下访问别的 Namespace 了。
多个容器之间的通信方式,用桥接网络是最简便的。