本文从本人博客搬运,原文格式更加美观,可以移步原文阅读:Docker容器网络通信机制
原理
当Docker启动时,会自动在主机上创建一个docker
虚拟网桥,实际上是 Linux 的一个 bridge,可以理解为一个软件交换机,它会在挂载到它的网口之间进行转发。同时,Docker 随机分配一个本地未占用的私有网段中的一个地址给 docker
接口。比如典型的 172.17.0.1
,掩码为 255.255.0.0
。此后启动的容器内的网口也会自动分配一个同一网段(172.17.0.0/16
)的地址
我们可以通过命令来查看docker虚拟网桥的信息
docker network ls # 查看docker的网络列表
docker inspect 网络名称 # 查看某个网络的详细信息
也可以通过查看容器详细信息,得到其连接哪个网络
当创建一个 Docker 容器的时候,同时会创建了一对 veth pair
接口(当数据包发送到一个接口时,另外一个接口也可以收到相同的数据包)。这对接口一端在容器内,即 eth0
;另一端在宿主机本地并被挂载到 docker
网桥,名称以 veth
开头(例如 vethAQI2QT
)。通过这种方式,宿主机可以跟容器通信,容器之间也可以通过docker
网桥相互通信。Docker就创建了在宿主机和所有容器之间一个虚拟共享网络
- 安装docker时创建的默认
docker
网桥,用docker network ls
查看其名称是bridge
,如果在宿主机上用ifconfig
查看,实际上是对应了docker0
虚拟网卡
- 我们启动的所有容器,如果不做特殊的网络指定,默认都是连接到
docker
网桥上。每启动一个容器,都会在宿主机上创建一个veth
开头的虚拟网卡,用来进行容器与宿主机、容器与容器间的通信。容器通过DHCP获取一个与docker0同网段的IP地址
- 我们启动的所有容器,默认都是连接到
docker
网桥上,但是这不是一个好的实践。假设我们有多个应用,每个应用启动了自己独有的mysql
、redis
、tomcat
等容器,那么默认情况下所有容器都连接到同一个网桥,此时应用1的tomcat发送给应用1自己的mysql的数据,应用2和应用3的所有容器也都能收到,这增加了不必要的网络流量压力:
所以最佳的方式是:以应用为单位划分网络,同个应用对应的容器连接到1个网络,不同应用之间的容器做到网络隔离
docker默认的网络除了bridge
外,还有host
和none
-
host
:如果指定容器网络为host,那么表示容器与宿主机共享网络,即容器的ip与宿主机相同。这样做的好处是访问宿主机就相当于访问容器,提升网络性能,不需要宿主机转发给容器。坏处是容器与宿主机共享端口,不能冲突。我们可以在启动容器时通过--network host
指定为host网络docker run -d --network host --name redis redis:5.0.8
当指定网络为host之后,进入容器可以发现容器拥有与宿主机相同的ip配置
而默认bridge模式的容器只有自己的ip
host模式可以用于所有应用和中间件部署在同一台机器的情况下,应用在容器中连接中间件只需指定ip为localhost即可,因为此时localhost就是代表了宿主机
-
none
:如果指定容器的网络为none,那么将无法通过网络访问容器,容器只有一个本地回环地址。如果禁止容器联网,为了安全可以采用这种方式
网络相关命令
1.查看网络
docker network ls # 查看docker的网络列表
docker inspect 网络名称 # 查看某个网络的详细信息
2.创建网络
docker network create 网络名称
每创建一个网络,宿主机上都会生成类似于docker0
的虚拟网卡,作为该网络容器之间通信的桥梁
3.启动容器时指定网络
docker run -d -p 6380:6379 --name redis01 --network 网络名称 redis:5.0.8
可以发现,我们创建的网络baobao,默认网段是172.18.0.1
,与默认docker
的172.17.0.1
刚好区分开
启动容器指定网络时,必须保证网络已经存在
4.容器间的网络通信
连接到同一个网络的容器可以互相通信,容器启动时docker网络会为其分配ip,我们可以利用ip与其他容器通信
首先我们启动2个redis容器,指定一个自定义网络
docker run -d -p 6380:6379 --name redis01 --network baobao redis:5.0.8
docker run -d -p 6381:6379 --name redis02 --network baobao redis:5.0.8
然后通过查看网络的详细信息可以知道网络下所有容器的ip
然后我们进入到redis01的内部,尝试ping redis02的ip。没有ping命令可以先用下列命令安装
apt-get update
apt install iputils-ping
虽然用ip可以完成容器间通信,但是如果容器ip发生变化,通信将会失败。从Docker 1.10版本开始,docker daemon实现了一个内嵌的DNS server,使容器可以直接通过容器名通信。使用默认的bridge网络,不能通过DNS server实现通过容器名通信,但是使用自定义bridge网络可以做到通过容器名互相通信。所以在自定义网络的情况下,更推荐的方式是用容器名称进行通信。我们尝试在redis01中ping redis02的容器名称
注意:
- 采用容器名称互相通信的方式,必须在创建容器时指定自定义网络才会生效。如果容器连接到默认的
docker0
网络是无法用容器名通信的- 如果没有给容器指定自定义网络,那么会连接到默认的
docker0
网段,此时可以用ip进行容器互联,但是这个ip是通过DHCP动态分配的,所以这种情况下更推荐直接用宿主机ip+容器映射端口进行容器间通信- 如果指定了自定义网络,那么推荐用容器名称进行通信
5.将容器连接到另一个网络
假如我们要将网络1的某个容器同时挂到网络2下,可以使用下列命令
docker network connect 网络名称 容器名称
执行完成后该容器与网络2就连通了,可以与网络2下的所有容器通信。其原理很简单,就是docker会给该容器再分配一个网络2网段的ip,相当于容器有2个ip,一个是网络1网段的,另一个是网络2网段的
如果想让容器与网络再断开连接,可以用下列命令
docker network disconnect 网络名称 容器名称