Docker Network Introduction

本文将介绍Docker中Bridge/Macvlan/Overlay这几种网络是如果工作的(并不涉及代码实现)。

预备知识

介绍Docker的网络之前,读者需要先了解一些基本的网络概念,包括二层网络、广播域、三层网络、分组转发等等。另外还需要知道怎么使用Docker以及Docker Swarm。阅读本文的过程中,把涉及的一些命令动手敲一遍,将会事半功倍(本文使用Ubuntu16.04,为简单起见,所有命令都在root下执行)。

Namespace

Namespace是Linux内核提供的一种资源隔离技术,也称为容器技术。同一个namespace内的程序可以访问一组它专属的资源,比如进程号、用户、网络、文件系统等等,不同namespace之间的资源相互隔离。

Namespace技术让轻量级的虚拟化实现起来非常方便,Docker便是基于此技术的一种应用,它提供了Docker实例之间的资源隔离。由于其轻量级,创建删除都很方便,难怪有同学感叹,你这玩意儿是在是太虚了。。

Linux内核提供了6种namespace隔离,网络是其中一种。一个网络namespace有自己独立的网络接口、IP地址、路由表、端口等资源。后面说到namespace都默认指网络namespace。

Linux默认有一个namespace,可以称之为root namespace。通过ip netns命令可以创建新的namespace,并进行配置。

创建一个namespace,这个命令同时会在/var/run/netns/中创建相应的链接

# ip netns add ns1

列出当前的namespace

# ip netns ls
ns1
# ls /var/run/netns/
ns1

通过ip netns exec可以在namespace内运行命令,比如配置接口和地址

# ip netns exec ns1 ip addr show
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
# ip netns exec ns1 ip link set lo up
# ip netns exec ns1 ip addr add 1.2.3.4/24 dev lo
# ip netns exec ns1 ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet 1.2.3.4/24 scope global lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
# ip netns exec ns1 ping 1.2.3.4 -c 3
PING 1.2.3.4 (1.2.3.4) 56(84) bytes of data.
64 bytes from 1.2.3.4: icmp_seq=1 ttl=64 time=0.116 ms
64 bytes from 1.2.3.4: icmp_seq=2 ttl=64 time=0.123 ms
64 bytes from 1.2.3.4: icmp_seq=3 ttl=64 time=0.070 ms

--- 1.2.3.4 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 1998ms
rtt min/avg/max/mdev = 0.070/0.103/0.123/0.023 ms

在网络层面来看,启动一个Docker实例,就相当于在一个网络namespace里面去执行程序。如下,启动一个alpine实例并显示地址

# docker run -d --name test-net alpine /bin/sleep 100000
# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
c764da1fbc9e        alpine              "/bin/sleep 100000"      2 minutes ago       Up 2 minutes                            test-net
# docker exec test-net /sbin/ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
732: eth0@if733: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP 
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.2/16 scope global eth0
       valid_lft forever preferred_lft forever
# docker exec test-net /sbin/ip route show
default via 172.17.0.1 dev eth0 
172.17.0.0/16 dev eth0  src 172.17.0.2

Docker并不会像ip netns一样去创建/var/run/netns/链接,所以用ip netns ls看不到它的namespace。不过我们仍然可以根据CONTAINER ID查到某个Docker实例的PID,进一步查到namespace

# ip netns ls
ns1
# docker inspect --format '{{.State.Pid}}' c764da1fbc9e
30052
# ln -sfT /proc/30052/ns/net /var/run/netns/30052
# ip netns ls
30052 (id: 0)
ns1
# ip netns exec 30052 ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
732: eth0@if733: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.17.0.2/16 scope global eth0
       valid_lft forever preferred_lft forever

此时仍然可以通过ip netns exec来执行命令,修改Docker实例的网络配置,和通过docker exec进入实例去配置网络有一样的效果(后面将省略loopback接口等无关信息)

# ip netns exec 30052 ip addr add 1.2.3.4/24 dev eth0
# docker exec test-net /sbin/ip addr show
732: eth0@if733: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP 
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.2/16 scope global eth0
       valid_lft forever preferred_lft forever
    inet 1.2.3.4/24 scope global eth0
       valid_lft forever preferred_lft forever

Docker有一个广泛使用的网络辅助配置程序pipework(https://github.com/jpetazzo/pipework),就是通过这种方式来运行的。

Veth

前面提到不同namespace之间的网络是相互隔离的,那么Docker内的程序怎么和外界通信呢?这要用到Linux中的一种虚拟网络接口,叫veth接口,虚拟以太网接口。这是一种二层接口,有属于它的MAC地址。

Veth接口总是成对出现,就像一根管道的两个端口。它有个特性,从一个端口接收到的数据,一定会从另外一个端口发送出来,并且它的两个端口可以位于不同的namespace内。没错,它就是一根管道,可以在不同namespace之间传递网络数据。

继续用之前创建的ns1来做实验,创建一对veth接口,并将其中一个放置到ns1里面

# ip link add veth-a type veth peer name veth-b
# ip link set veth-b netns ns1
# ip netns exec ns1 ip addr show
1942: veth-b@if1943: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 9a:59:db:92:a8:27 brd ff:ff:ff:ff:ff:ff link-netnsid 0
# ip addr show
1943: veth-a@if1942: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether be:7a:74:87:a3:fe brd ff:ff:ff:ff:ff:ff link-netnsid 1

可以看到veth-a在root namespace内,veth-b在ns1内。给他们都配置上IP

# ip link set veth-a up
# ip addr add 1.1.1.1/24 dev veth-a
# ip netns exec ns1 ip link set veth-b up
# ip netns exec ns1 ip addr add 1.1.1.2/24 dev veth-b

显示配置,并在rootns1之间互相ping, 验证网络连通性

# ip addr show
1943: veth-a@if1942: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether be:7a:74:87:a3:fe brd ff:ff:ff:ff:ff:ff link-netnsid 1
    inet 1.1.1.1/24 scope global veth-a
       valid_lft forever preferred_lft forever

# ping 1.1.1.2 -c 3
PING 1.1.1.2 (1.1.1.2) 56(84) bytes of data.
64 bytes from 1.1.1.2: icmp_seq=1 ttl=64 time=0.090 ms
64 bytes from 1.1.1.2: icmp_seq=2 ttl=64 time=0.155 ms
64 bytes from 1.1.1.2: icmp_seq=3 ttl=64 time=0.120 ms

--- 1.1.1.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2001ms
rtt min/avg/max/mdev = 0.090/0.121/0.155/0.029 ms

# ip netns exec ns1 ip addr show
1942: veth-b@if1943: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 9a:59:db:92:a8:27 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 1.1.1.2/24 scope global veth-b
       valid_lft forever preferred_lft forever

# ip netns exec ns1 ping 1.1.1.1 -c 3
PING 1.1.1.1 (1.1.1.1) 56(84) bytes of data.
64 bytes from 1.1.1.1: icmp_seq=1 ttl=64 time=0.110 ms
64 bytes from 1.1.1.1: icmp_seq=2 ttl=64 time=0.172 ms
64 bytes from 1.1.1.1: icmp_seq=3 ttl=64 time=0.147 ms

--- 1.1.1.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 1999ms
rtt min/avg/max/mdev = 0.110/0.143/0.172/0.025 ms

可以看到,通过veth接口,两个namespace之间可以进行网络通信。

在ns1内更改veth-b接口名字为eth0

# ip netns exec ns1 ip link set veth-b down
# ip netns exec ns1 ip link set veth-b name eth0
# ip netns exec ns1 ip link set eth0 up
# ip netns exec ns1 ip addr show
1942: eth0@if1943: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 9a:59:db:92:a8:27 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 1.1.1.2/24 scope global eth0
       valid_lft forever preferred_lft forever

Docker实例启动的时候,也创建了这么一对veth接口,一个放在实例的namespace内(取名eth0),一个放在root namespace内。你也可以新增一对veth接口,然后把其中一个放入Docker实例内,就相当于给这个实例增加了一个网络接口,就像pipework可以实现的那样。

通过namespace技术,Linux实现了网络资源的隔离,不同namespace可以配置完全一样的地址而不会冲突,里面的程序也可以监听相同的端口而不冲突。

再通过veth技术,Linux实现了namespace之间的数据传递。

非root namespace的数据到了root namespace之后怎么转发到外面的网络呢?这里介绍三种方式,即bridge、macvlan和overlay。

bridge

Linux中有一种虚拟接口,叫bridge接口,我们可以把一个bridge接口当成一个二层交换机。把若干个veth接口连接到一个bridge接口后,这些veth接口就位于同一个二层网络内,或者说处于同一个广播域。

可以用brctl来管理bridge接口,比如创建、添加成员。继续用之前的veth接口来做实验,创建一个bridge接口并把veth-a加进去

# brctl addbr br1
# brctl show
bridge name   bridge id           STP enabled   interfaces
br1           8000.000000000000   no        
# brctl addif br1 veth-a
# brctl show
bridge name   bridge id           STP enabled   interfaces
br1           8000.be7a7487a3fe   no            veth-a

可以给bridge接口也配置IP地址(同时把veth-a的IPv4地址删除)

# ip link set br1 up
# ip addr add 1.1.1.3/24 dev br1
# ip addr del 1.1.1.1/24 dev veth-a
# ip addr show
1943: veth-a@if1942: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br1 state UP group default qlen 1000
    link/ether be:7a:74:87:a3:fe brd ff:ff:ff:ff:ff:ff link-netnsid 1
1950: br1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether be:7a:74:87:a3:fe brd ff:ff:ff:ff:ff:ff
    inet 1.1.1.3/24 scope global br1
       valid_lft forever preferred_lft forever

# ping 1.1.1.2 -c 3
PING 1.1.1.2 (1.1.1.2) 56(84) bytes of data.
64 bytes from 1.1.1.2: icmp_seq=1 ttl=64 time=0.058 ms
64 bytes from 1.1.1.2: icmp_seq=2 ttl=64 time=0.100 ms
64 bytes from 1.1.1.2: icmp_seq=3 ttl=64 time=0.102 ms

--- 1.1.1.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 1999ms
rtt min/avg/max/mdev = 0.058/0.086/0.102/0.022 ms

可以看到,通过br1接口的地址,仍然可以和ns1内的IP地址通信。

既然是一个二层网络,再增加一个接口,连接到另一个namespace ns2,可以让ns1ns2互相通信(注意,这个实验最好在一台没有安装Docker的Linux上执行,因为Docker配置了一些iptables规则限制,可能导致不同namespace连接不通)

# ip netns add ns2
# ip link add veth-1 type veth peer name veth-2
# ip link set veth-1 up
# ip link set veth-2 netns ns2
# ip netns exec ns2 ip link set veth-2 up
# ip netns exec ns2 ip addr add 1.1.1.4/24 dev veth-2
# brctl addif br1 veth-1
# brctl show
bridge name    bridge id            STP enabled    interfaces
br1            8000.b65343994e0b    no             veth-1
                                                   veth-a
# ip netns exec ns2 ping 1.1.1.2 -c 3
PING 1.1.1.2 (1.1.1.2) 56(84) bytes of data.
64 bytes from 1.1.1.2: icmp_seq=1 ttl=64 time=0.089 ms
64 bytes from 1.1.1.2: icmp_seq=2 ttl=64 time=0.097 ms
64 bytes from 1.1.1.2: icmp_seq=3 ttl=64 time=0.117 ms

--- 1.1.1.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 1998ms
rtt min/avg/max/mdev = 0.089/0.101/0.117/0.011 ms

安装启动Docker之后,它会创建一个默认的bridge接口,即docker0

# ip addr show
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:a1:5e:88:ea brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 scope global docker0
       valid_lft forever preferred_lft forever

Docker实例创建后,随之创建一对veth接口,其中一个绑定到docker0,另一个划分到实例对应的namespace内。这样实例内的程序就能访问docker0的IP地址了。

Docker proxy

macvlan

vmware

overlay

sdn

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 一、Docker 简介 Docker 两个主要部件:Docker: 开源的容器虚拟化平台Docker Hub: 用...
    R_X阅读 4,431评论 0 27
  • 转载自 http://blog.opskumu.com/docker.html 一、Docker 简介 Docke...
    极客圈阅读 10,604评论 0 120
  • 写这个系列文章主要是对之前做项目用到的docker相关技术做一些总结,包括docker基础技术Linux命名空间,...
    __七把刀__阅读 5,870评论 0 16
  • 电影总是演不到结局 就像我们总也看不见未来以后的路还那么长 我不知道结局 也不做假设 我只想看见生命最完整的样子 ...
    哀慕熙荣阅读 100评论 0 1
  • (稻盛哲学学习会)打卡第28天 姓名:沈丹萍 部门:设计部 组别:待定 读【知~学习】 读《道盛和夫自传》第三章:...
    沈丹萍分水碶阅读 168评论 0 0