11.2.2 单机桥接网络
最简单的Docker网络就是单机桥接网络。
从名称中可以看到两点。
- 单机 意味着该网络只能在单个Docker主机上运行,并且只能与所在Docker主机上的容器进行连接。
- 桥接 意味着这是802.1.d桥接的一种实现(二层交换机)。
Linux Docker创建单机桥接网络采用内置的桥接驱动,而Windows Docker创建时使用内置的NAT驱动。实际上,这两种驱动工作起来毫无差异。
图11.6展示了两个均包含相同本地桥接网络mynet的Docker主机。虽然网络是相同的,但却是两个独立的网络。这意味着图11.6中容器无法直接进行通信,因为并不在一个网络当中。
每个Docker主机都有一个默认的单机桥接网络。在Linux上网络名称为 bridge
,在Windows上叫作 nat
。除非读者通过命令行创建容器时指定参数 --network
,否则默认情况下,新创建的容器都会连接到该网络。
下面列出了 docker network ls
命令在刚完成安装的Docker主机上的输出内容。输出内容做了截取处理,只展示了每个主机上的默认网络。注意,网络的名称和创建时使用的驱动名称是一致的——这只是个巧合。
//Linux
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
333e184cd343 bridge bridge local
//Windows
> docker network ls
NETWORK ID NAME DRIVER SCOPE
095d4090fa32 nat nat local
docker network inspect
命令就是一个信息宝藏。如果读者对底层细节的内容感兴趣,强烈推荐仔细阅读该命令的输出内容。
docker network inspect bridge
[
{
"Name": "bridge", << 在 Windows 上是nat
"Id": "333e184...d9e55",
"Created": "2018-01-15T20:43:02.566345779Z",
"Scope": "local",
"Driver": "bridge", << 在 Windows 上是nat
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.17.0.0/16"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
<Snip>
}
]
在Linux主机上,Docker网络由Bridge驱动创建,而Bridge底层是基于Linux内核中久经考验达15年之久的Linux Bridge技术。这意味着Bridge是高性能并且非常稳定的!同时这还表示可以通过标准的Linux工具来查看这些网络,代码如下。
$ ip link show docker0
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc...
link/ether 02:42:af:f9:eb:4f brd ff:ff:ff:ff:ff:ff
在Linux Docker主机之上,默认的“bridge”网络被映射到内核中为“ docker0 ”的Linux网桥。可以通过 docker network inspect
命令观察到上面的输出内容。
$ docker network inspect bridge | grep bridge.name
"com.docker.network.bridge.name": "docker0",
Docker默认“bridge”网络和Linux内核中的“docker0”网桥之间的关系如图11.7所示。
图11.8对图11.7的内容进行了扩展,在顶部补充了接入“bridge”网络的容器。“bridge”网络在主机内核中映射到名为“docker0”的Linux网桥,该网桥可以通过主机以太网接口的端口映射进行反向关联。
接下来使用 docker network create
命令创建新的单机桥接网络,名为“localnet”。
//Linux
$ docker network create -d bridge localnet
//Windows
> docker network create -d nat localnet
新的网络创建成功,并且会出现在 docker network ls
命名的输出内容当中。如果读者使用Linux,那么在主机内核中还会创建一个新的Linux网桥。
接下来通过使用Linux brctl
工具来查看系统中的Linux网桥。读者可能需要通过命令 apt-get install bridge-utils
来安装 brctl
二进制包,或者根据所使用的Linux发行版选择合适的命令。
$ brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.0242aff9eb4f no
br-20c2e8ae4bbb 8000.02429636237c no
输出内容中包含了两个网桥。第一行是前文提过的 docker0
网桥,该网桥对应Docker中的默认网络 bridge
;第二个网桥(br-20c2e8ae4bbb)与新建的“localnet”Docker桥接网络相对应。两个网桥目前都没有开启STP,并且也都没有任何设备接入(对应的 interfaces
列为空)。
目前,主机上的网桥配置如图11.9所示。
接下来创建一个新的容器,并接入到新建桥接网络 localnet
当中。如果读者是在Windows上进行操作,需要将命令中“alpine sleep 1d”替换为“ microsoft/powershell:nanoserver pwsh.exe -Command Start-Sleep 86400
”。
$ docker container run -d --name c1 \
--network localnet \
alpine sleep 1d
容器现在接入了 localnet
网络当中。读者可以通过 docker network inspect
命令来确认。
$ docker network inspect localnet --format
'{{json .Containers}}' {
"4edcbd...842c3aa": {
"Name": "c1",
"EndpointID": "43a13b...3219b8c13",
"MacAddress": "02:42:ac:14:00:02",
"IPv4Address": "172.20.0.2/16",
"IPv6Address": ""
}
},
输出内容表明“ c1
”容器已经位于桥接(Bridge/NAT)网络 localnet
之上。
如果再次运行 brctl show
命令,就能看到 c1
的网络接口连接到了 br-20c2e8ae4bbb
网桥。
$ brctl show
bridge name bridge id STP enabled interfaces
br-20c2e8ae4bbb 8000.02429636237c no vethe792ac0
docker0 8000.0242aff9eb4f no
图11.10展示了上述关系。
如果在相同网络中继续接入新的容器,那么在新接入容器中是可以通过“c1”的容器名称来ping通的。这是因为新容器都注册到了指定的Docker DNS服务,所以相同网络中的容器可以解析其他容器的名称。
注:
Linux上默认的Bridge网络是不支持通过Docker DNS服务进行域名解析的。自定义桥接网络可以!
一起来测试一下。
(1)创建名为“c2”的容器,并接入“c1”所在的 localnet
网络。
//Linux
$ docker container run -it --name c2 \
--network localnet \
alpine sh
//Windows
> docker container run -it --name c2 `
--network localnet `
microsoft/powershell:nanoserver
当前终端会切换到“c2”容器中。
(2)在“c2”容器中,通过“c1”容器名称执行 ping
命令。
> ping c1
Pinging c1 [172.26.137.130] with 32 bytes of data:
Reply from 172.26.137.130: bytes=32 time=1ms TTL=128
Reply from 172.26.137.130: bytes=32 time=1ms TTL=128
Control-C
命令生效了!这是因为c2容器运行了一个本地DNS解析器,该解析器将请求转发到了Docker内部DNS服务器当中。DNS服务器中记录了容器启动时通过 --name
或者 --net-alias
参数指定的名称与容器之间的映射关系。
如果读者仍处于容器中,可以尝试运行一些网络相关的命令。这是一种很好的了解Docker容器网络工作原理的方式。下面的片段是在之前创建的Windows容器“c2”中运行 ipconfig
命令的输出内容。读者可以在前面 docker network inspect nat
命令输出中找到对应的IP地址。
> ipconfig
Windows IP Configuration
Ethernet adapter Ethernet:
Connection-specific DNS Suffix . :
Link-local IPv6 Address . . . . . : fe80::14d1:10c8:f3dc:2eb3%4
IPv4 Address. . . . . . . . . . . : 172.26.135.0
Subnet Mask . . . . . . . . . . . : 255.255.240.0
Default Gateway . . . . . . . . . : 172.26.128.1
到目前为止,本书提到的桥接网络中的容器只能与位于相同网络中的容器进行通信。但是,可以使用端口映射(Port Mapping)来绕开这个限制。
端口映射允许将某个容器端口映射到Docker主机端口上。对于配置中指定的Docker主机端口,任何发送到该端口的流量,都会被转发到容器。图11.11中展示了具体流量动向。
如图11.11所示,容器内部应用开放端口为80。该端口被映射到了Docker主机的 10.0.0.15
接口的5000端口之上。最终结果就是访问 10.0.0.15:5000
的所有流量都被转发到了容器的80端口。
接下来通过示例了解将容器上运行着Web服务的端口80,映射到Docker主机端口5000的过程。示例使用Linux的Nginx。如果读者使用Windows,可以将Nginx替换为某个Windows的Web服务镜像。
(1)运行一个新的Web服务容器,并将容器80端口映射到Docker主机的5000端口。
$ docker container run -d --name web \
--network localnet \
--publish 5000:80 \
nginx
(2)确认端口映射。
$ docker port web
80/tcp -> 0.0.0.0:5000
这表示容器80端口已经映射到Docker主机所有接口上的5000端口。
(3)通过Web浏览器访问Docker主机5000端口,验证配置是否生效,如图11.12所示。为了完成测试,读者需要知道Docker主机的IP地址或者DNS名称。如果读者使用Windows版Docker或者Mac版Docker,可以使用 localhost
或者 127.0.0.1
。
外部系统现在可以通过Docker主机的TCP端口5000,来访问运行在桥接网络上的Nginx容器了。
端口映射工作原理大致如此,但这种方式比较笨重并且不能扩展。举个例子,在只有单一容器的情况下,它可以绑定到主机的任意端口。这意味着其他容器就不能再使用已经被Nginx容器占用的5000端口了。这也是单机桥接网络只适用于本地开发环境以及非常小的应用的原因。