17.5 Linux网络虚拟化

Docker的本地网络实现其实就是利用了Linux上的网络命名空间和虚拟网络设备(特别是veth pair)。熟悉这两部分的基本概念,有助于理解Docker网络的实现过程。

1.基本原理

直观上看,要实现网络通信,机器需要至少一个网络接口(物理接口或虚拟接口)与外界相通,并可以收发数据包;此外,如果不同子网之间要进行通信,需要额外的路由机制。

Docker中的网络接口默认都是虚拟的接口。虚拟接口的最大优势就是转发效率极高。这是因为Linux通过在内核中进行数据复制来实现虚拟接口之间的数据转发,即发送接口的发送缓存中的数据包将被直接复制到接收接口的接收缓存中,而无需通过外部物理网络设备进行交换。对于本地系统和容器内系统来看,虚拟接口跟一个正常的以太网卡相比并无区别,只是它速度要快得多。

Docker容器网络就很好地利用了Linux虚拟网络技术,在本地主机和容器内分别创建一个虚拟接口,并让它们彼此连通(这样的一对接口叫做veth pair),如图17-4所示。

17.5 Linux网络虚拟化 - 图1

图17-4 Linux虚机网络技术

2.网络创建过程

一般情况下,Docker创建一个容器的时候,会具体执行如下操作:

1)创建一对虚拟接口,分别放到本地主机和新容器的命名空间中;

2)本地主机一端的虚拟接口连接到默认的docker0网桥或指定网桥上,并具有一个以veth开头的唯一名字,如veth1234;

3)容器一端的虚拟接口将放到新创建的容器中,并修改名字作为eth0。这个接口只在容器的命名空间可见;

4)从网桥可用地址段中获取一个空闲地址分配给容器的eth0(例如172.17.0.2/16),并配置默认路由网关为docker0网卡的内部接口docker0的IP地址(例如172.17.42.1/16)。

完成这些之后,容器就可以使用它所能看到的eth0虚拟网卡来连接其他容器和访问外部网络。

用户也可以通过docker network命令来手动管理网络,将在后续第21章介绍。

在使用docker run命令启动容器的时候,可以通过—net参数来指定容器的网络配置。

有5个可选值bridge、none、container、host和用户定义的网络:

·—net=bridge:默认值,在Docker网桥docker0上为容器创建新的网络栈。

·—net=none:让Docker将新容器放到隔离的网络栈中,但是不进行网络配置。之后,用户可以自行进行配置。

·—net=container:NAME_or_ID:让Docker将新建容器的进程放到一个已存在容器的网络栈中,新容器进程有自己的文件系统、进程列表和资源限制,但会和已存在的容器共享IP地址和端口等网络资源,两者进程可以直接通过lo环回接口通信。

·—net=host:告诉Docker不要将容器网络放到隔离的命名空间中,即不要容器化容器内的网络。此时容器使用本地主机的网络,它拥有完全的本地主机接口访问权限。容器进程可以跟主机其他root进程一样打开低范围的端口,可以访问本地网络服务,比如D-bus,还可以让容器做一些影响整个主机系统的事情,比如重启主机。因此使用这个选项的时候要非常小心。如果进一步的使用—privileged=true参数,容器甚至会被允许直接配置主机的网络栈。

·—net=user_defined_network:用户自行用network相关命令创建一个网络,通过这种方式将容器连接到指定的已创建网络上去。

3.手动配置网络

用户使用—net=none后,Docker将不对容器网络进行配置。下面,将手动完成配置网络的整个过程。通过这个过程,可以了解到Docker配置网络的更多细节。

首先,启动一个/bin/bash容器,指定—net=none参数:


  1. $ docker run -i -t --rm --net=none base /bin/bash
  2. root@63f36fc01b5f:/#

在本地主机查找容器的进程id,并为它创建网络命名空间:


  1. $ docker inspect -f '{{.State.Pid}}' 63f36fc01b5f
  2. 2778
  3. $ pid=2778
  4. $ sudo mkdir -p /var/run/netns
  5. $ sudo ln -s /proc/$pid/ns/net /var/run/netns/$pid

检查桥接网卡的IP和子网掩码信息:


  1. $ ip addr show docker0
  2. 21: docker0: ...
  3. inet 172.17.42.1/16 scope global docker0
  4. ...

创建一对“veth pair”接口A和B,绑定A接口到网桥docker0,并启用它:


  1. $ sudo ip link add A type veth peer name B
  2. $ sudo brctl addif docker0 A
  3. $ sudo ip link set A up

将B接口放到容器的网络命名空间,命名为eth0,启动它并配置一个可用IP(桥接网段)和默认网关:


  1. $ sudo ip link set B netns $pid
  2. $ sudo ip netns exec $pid ip link set dev B name eth0
  3. $ sudo ip netns exec $pid ip link set eth0 up
  4. $ sudo ip netns exec $pid ip addr add 172.17.42.99/16 dev eth0
  5. $ sudo ip netns exec $pid ip route add default via 172.17.42.1

以上,就是Docker配置网络的具体过程。

当容器终止后,Docker会清空容器,容器内的网络接口会随网络命名空间一起被清除,A接口也会自动从docker0卸载并清除。

此外,在删除/var/run/netns/下的内容之前,用户可以使用ip netns exec命令在指定网络命名空间中进行配置,从而更新容器内的网络配置。