如何理解docker网络

194 阅读5分钟

       容器和服务如此强大是因为可以将它们“连接”在一起,本文来深入学习一下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模式

这两种模式都是用作跨主机通信时使用的,详细不在本文阐述. 最后引用下网络上的一个表示容器和外界网络访问的示意图: