容器和服务如此强大是因为可以将它们“连接”在一起,本文来深入学习一下docker底层网络的实现原理。我们从将一个简单例子出发,引出docker的六种网络模式,然后从linux底层原理和命令出发来阐述docker网络的实现原理。
一个简单的例子
首先,我们来看一个简单的例子,在同一个host上创建两个容器,来看看host和两个容器之间是否可以互通。说明一下,本次为了方便演示网络的各种操作,不同于以往直接在mac上操作,而是专门找了一台linux(Centos)测试机,所以如果是在mac上操作的话很多命令是没法直接运行的.
#1:创建两个容器
$ docker run -dit --name alpine1 alpine
$ docker run -dit --name alpine2 alpine
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ab45ce37cc84 alpine "/bin/sh" 10 seconds ago Up 10 seconds alpine2
66a9d3904129 alpine "/bin/sh" 16 seconds ago Up 15 seconds alpine1
#2:查看两个容器的IP(如下图分别为172.17.0.4/16和172.17.0.3/16)
如上图所示,两个容器的ip分别为172.17.0.4/16(对应容器ab45ce37cc84)和172.17.0.3/16(对应容器66a9d3904129)
#3:验证容器之间的连通性
$ docker exec -it ab45ce37cc84 ping -c 2 172.17.0.3
PING 172.17.0.3 (172.17.0.3): 56 data bytes
64 bytes from 172.17.0.3: seq=0 ttl=64 time=0.127 ms
64 bytes from 172.17.0.3: seq=1 ttl=64 time=0.092 ms
--- 172.17.0.3 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.092/0.109/0.12
$ docker exec -it 66a9d3904129 ping -c 2 172.17.0.4
PING 172.17.0.4 (172.17.0.4): 56 data bytes
64 bytes from 172.17.0.4: seq=0 ttl=64 time=0.141 ms
64 bytes from 172.17.0.4: seq=1 ttl=64 time=0.100 ms
--- 172.17.0.4 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.100/0.120/0.141 ms
如上面命令演示,我们发现这两个容器之间是可以相互网络通信的.
#4:验证主机和容器之间的连通性
我的测试主机的ip是172.16.229.130,直接上图展示命令结果,发现容器和宿主机也是可以ping通的.
#5:结论
docker在默认情况下,同一个host上创建的容器是可以相互通信的,他们的ip地址会在同一个网段并且从2开始分配(本例的两个容器是3和4是因为2已经被之前创建的容器占用了),容器和宿主机也是可以相互ping通的。看起来我们简简单单就有了有了三台具备独立ip且能够相互访问的机器,那么到底docker是怎么做到的 ?
三个ip能够相互通信意味着他们存在网络路由,我们首先来看看路由情况:
$ docker exec -it 66a9d3904129 arp -n
? (172.17.0.4) at 02:42:ac:11:00:04 [ether] on eth0
? (172.17.0.1) at 02:42:54:c5:ed:a8 [ether] on eth0
$ docker exec -it ab45ce37cc84 arp -n
? (172.17.0.3) at 02:42:ac:11:00:03 [ether] on eth0
? (172.17.0.1) at 02:42:54:c5:ed:a8 [ether] on eth0
$ arp -n
Address HWtype HWaddress Flags Mask Iface
172.17.0.4 ether 02:42:ac:11:00:04 C docker0
172.17.0.3 ether 02:42:ac:11:00:03 C docker0
172.16.239.253 ether ee:ff:ff:ff:ff:ff C eth0
从上面命令执行发现两个容器直接相互存在路由之外,另外一条路由都是跟docker0在通信,查询下host上的是否有这个东西,如下图命令执行所示确实有个docker0的网络接口,其ip地址为172.17.0.1,mac地址02:42:54:c5:ed:a8
再根据前文的所述,我们可以画出以下网络拓扑图:
docker做的事情其实很简单:
-
docker安装的时候默认创建了一个叫docker0的网桥
-
运行容器alpine1时创建一对虚拟接口用来通信,一端(veth2581f05@if12)放在网桥上,一端(eth0@if13)放在容器里
-
运行容器alpine2时也创建一对虚拟接口用来通信,一端(veth7794f23@if14)放在网桥上,一端(eth0@if15)放在容器里
这其实是docker默认使用的网桥模式!
网桥模式的底层原理是什么?
除了网桥模式之外,docker是否还有其他网络模式呢?
网桥模式的底层原理
我们知道,docker容器的隔离技术是基于底层linux namespace和cgroup技术,详见神雕在之前的文章 云原生第一篇 。当时第一篇文章由于旨在从全局的角度阐述云原生技术,并未对具体某个技术做详述,亦没有实际的demo。本文正好借分析docker网络底层原理的机会,来对namespace资源隔离技术补充一个实际的例子。
linux默认有6个命名空间用来做资源隔离,其中网络命名空间(network namespce)用来隔离网络设备、网络栈、端口等资源,很显然本文上面例子中,看上去三台独立的能够相互通信的“机器”其实就是docker利用network namespace做到的!
docker创建运行一个新的容器,就会为这个容器创建独立的namespace用来做隔离(包括网络、进程、mount等等资源),所以我们抛开容器的概念,上面例子的图我们做一下修改如下:
-
两个容器可以认为是两个命名空间(ns0, ns1)
-
docker0其实是一个Linux网桥
-
容器和docker0的通信其实是linux网络虚拟接口(veth-peer)
接下来开始演示:
#1: 创建两个namespace
$ ip netns add ns1
$ ip netns add ns2
$ ip netns list
ns2
ns1
#2: 创建一对veth-peer(veth1 <=> ceth1),并设置ns1
创建一对虚拟接口veth1和ceth1,启动veth1并设置其ip地址172.18.0.21/16
$ ip link add veth1 type veth peer name ceth1
$ ip link set veth1 up
$ ip addr add 172.18.0.21/16 dev veth1
设置ceth1的namespace为ns1,进入ns1,然后启动回环接口lo、ceth1,并设置ceth1的IP地址为172.168.0.11/6
$ ip link set ceth1 netns ns1
$ nsenter --net=/var/run/netns/ns1
$ ip link set lo up$ ip link set ceth1 up
$ ip addr add 172.18.0.11/16 dev ceth1
#3: 创建一对veth-peer(veth2 <=> ceth2),并设置ns2
步骤和#2雷同,命令如下(记得先exit一下,因为#2进入了ns1)
#4: 创建并启动网桥br0,并设置veth1/veth2
$ sudo ip link add br0 type bridge
$ ip link set br0 up
$ ip link set veth1 master br0
$ ip link set veth2 master br0
#5: check一下目前的网络拓扑
宿主机(Host):
第一个namespace(ns1):
第二个namespace(ns2):
整体网络拓扑:
#6: 来验证一下联通性
host主命名空间的网桥和namespace1是相互可以通信的,ns2也是一样的。因此,以上我们手动操作的工作,其实在创建容器的时候,docker帮我们做了。相信看到这里,我们对docker的网络模式(至少是网桥模式)有了比较深入的理解。接下来我们在介绍一下docker其他几种网络模式!
六种网络模式
$ docker info | grep Network
Network: bridge host ipvlan macvlan null overlay
docker有六种常规的网络模式,其中默认是brige模式的工作原理上文已经有详细介绍。我们可以通过--net来指定容器的网络模式,例如:
docker run -it --name bs4 --net xxx busybox
其他我们也可以手动创建(定制)需要的brige,例如:
docker network create --subnet=192.168.250.1/24 mybridge
docker run -it --name bs4 --net mybridge busybox
除此之外,还有另外五种模式我们简单介绍下:
- Host模式:–net=host
容器不会获得一个独立的network namespace,而是与宿主机共用一个。这就意味着容器将不会虚拟出自己的网卡,配置自己的IP等,而是使用宿主机的IP和端口。容器除了网络,其他都是隔离的。
- null模式:–net=none
此模式下容器没有网络,不分配局域网的IP
- Container模式:–net=container:Name/ID
与指定的容器使用同一个network namespace,具有同样的网络配置信息,两个容器除了网络,其他都还是隔离的。
- macvlan和overlay模式
这两种模式都是用作跨主机通信时使用的,详细不在本文阐述. 最后引用下网络上的一个表示容器和外界网络访问的示意图: