使用 OVS 打通多节点上的容器通信(Underlay)

概述

在上一篇文章中,提到了怎么使用 OVS 构建 Overlay 网络的问题。那么这一篇中,主要会研究 Underlay 网络。关于 Overlay 和 Underlay 网络的区别,已经有很多文章详细阐述了。

使用 underlay 网络,最大的特点就是容器和 vm 会在同一个网络下,这样容器和 vm 之间是可以直接通信的。因此我们可以大概猜测出,一个 underlay 网络下,容器和 vm 之间都应该通过交换机进行连接。因此我们可以得出下面的网络拓扑图:

下面,我们就来实际操作一下,构建出一个这样的 underlay 网络。实验环境如下:

  • 物理机: 操作系统 ubuntu 19.10, open vSwitch 2.12.0

  • 虚拟机: centos/7,内核 4.3.3,open vSwitch 2.5.9

虚拟机的 vagrantfile 可以用下面的:

# -*- mode: ruby -*-
# vi: set ft=ruby :
Vagrant.configure("2") do |config|
  config.vm.box = "jiang/centos7_ovs"
  config.vm.box_version = "0.1"

  config.vm.box_check_update = false
  config.vm.network "public_network", ip: "192.168.3.2"
  config.vm.provider "virtualbox" do |vb|
    vb.memory = "2048"
	  vb.customize ["modifyvm", :id, "--nicpromisc2", "allow-all"]
    vb.customize ["modifyvm", :id, "--nicpromisc3", "allow-all"]
  end
end

因为设备问题,我没有办法用两台 Host 去组件整个网络,因此这里的目标就是只实现这一部分,并且只会启动两台虚拟机,其余的原理也是一样:

准备 Host 环境

Host 环境上主要是要通过 Open vSwitch 创建一个虚拟交换机,并且有两个 internal 类型的 port 供虚拟机接入。命令如下:

sudo ovs-vsctl add-br br0
sudo ovs-vsctl add-port br0 tap0 -- set Interface tap0 type=internal
sudo ovs-vsctl add-port br0 tap1 -- set Interface tap0 type=internal
sudo ip link set br0 up 
sudo ip link set tap0 up 
sudo ip link set tap1 up  
sudo ifconfig br0 192.168.3.1/24

现在,我们就准备好了 Host 的环境。下面我们使用 vagrant 启动虚拟机,提示选择桥接网络的时候,记得选择桥接 tap0 和 tap1,这样,虚拟机就连上了 vSwitch。

准备虚拟机环境

首先,启动在虚拟机上的 ovsdb-server 和 ovs-vswitchd:

sudo ovsdb-server --remote=punix:/usr/var/run/openvswitch/db.sock \
                                  --remote=db:Open_vSwitch,Open_vSwitch,manager_options \
                                  --private-key=db:Open_vSwitch,SSL,private_key \
                                  --certificate=db:Open_vSwitch,SSL,certificate \
                                  --bootstrap-ca-cert=db:Open_vSwitch,SSL,ca_cert \
                                  --pidfile --detach
sudo ovs-vswitchd --pidfile --detach

然后创建虚拟交换机,并且将虚拟机的网卡也接入该交换机:

sudo ovs-vsctl add-br br0
sudo ovs-vsctl add-port br0 enp0s8

下面我们启动一个容器 c1,并且通过 veth pair 接入到虚拟交换机上。

docker run -d --name c1 -it --net=none --privileged=true busybox sh
sudo ovs-docker add-port br0 eth0 c1 --ipaddress=192.168.3.21/24 --gateway=192.168.3.1

为了让 192.168.3.0/24 的流量都通过 br0 交换机,我们还要修改一下路由表,比如我这里默认的路由表规则如下:

192.168.3.0/24 dev enp0s8 proto kernel scope link src 192.168.3.2 metric 101

删除并重新创建:

sudo ip link set br0 up
sudo ip route del 192.168.3.0/24 dev enp0s8 proto kernel scope link src 192.168.3.2 metric 101 
sudo ip route add 192.168.3.0/24 dev br0 proto kernel scope link src 192.168.3.2 metric 101 

这个时候,我们可以测试一下,虚拟机到容器 c1 是否能够直接通信:

ping -c 3 192.168.3.21
PING 192.168.3.21 (192.168.3.21) 56(84) bytes of data.
64 bytes from 192.168.3.21: icmp_seq=1 ttl=64 time=1.11 ms
64 bytes from 192.168.3.21: icmp_seq=2 ttl=64 time=0.078 ms
64 bytes from 192.168.3.21: icmp_seq=3 ttl=64 time=0.067 ms

再测试一下宿主机到容器的通信

ping -c 3 192.168.3.21
PING 192.168.3.21 (192.168.3.21): 56 data bytes
64 bytes from 192.168.3.21: icmp_seq=0 ttl=64 time=0.740 ms
64 bytes from 192.168.3.21: icmp_seq=1 ttl=64 time=0.622 ms
64 bytes from 192.168.3.21: icmp_seq=2 ttl=64 time=0.605 ms

再测试一下容器到宿主机之间的通信:

docker exec -it c1 sh -c "ping -c 3 192.168.3.1"
PING 192.168.3.1 (192.168.3.1): 56 data bytes
64 bytes from 192.168.3.1: seq=0 ttl=64 time=0.768 ms
64 bytes from 192.168.3.1: seq=1 ttl=64 time=0.632 ms
64 bytes from 192.168.3.1: seq=2 ttl=64 time=0.680 ms

这里其实有个比较容器忽略的地方(对于我来说),就是虚拟机的网卡需要打开混杂(promiscuous) 模式。不然当流向 192.168.3.21 的包进到虚拟机的时候,虚拟机网卡发现 mac 地址不是自己的,就会直接抛弃。这个模式我是在上面提供的 Vagrantfile 中打开了。如果实验的时候发现有问题,可以往这个方向多多考虑。接着,我们用同样的方式启动第二台虚拟机即可。

最后测试两台vm上容器和容器之间的通信:

docker exec -it c1 sh -c "ping -c 3 192.168.3.31"
PING 192.168.3.31 (192.168.3.31): 56 data bytes
64 bytes from 192.168.3.31: seq=0 ttl=64 time=0.825 ms
64 bytes from 192.168.3.31: seq=1 ttl=64 time=0.796 ms
64 bytes from 192.168.3.31: seq=2 ttl=64 time=0.566 ms

这样,我们整个网络已经构建完毕,但是这里还有个问题,会出现数据包重复的现象,比如我从 vm0 到 vm1,vm0 会重复发送两次,vm1 会重复响应两次:

11:38:46.665277 IP 192.168.3.21 > 192.168.3.31: ICMP echo request, id 22272, seq 2, length 64
11:38:46.665318 IP 192.168.3.21 > 192.168.3.31: ICMP echo request, id 22272, seq 2, length 64
11:38:46.665776 IP 192.168.3.31 > 192.168.3.21: ICMP echo reply, id 22272, seq 2, length 64


11:38:46.665299 IP 192.168.3.21 > 192.168.3.31: ICMP echo request, id 22272, seq 2, length 64
11:38:46.665755 IP 192.168.3.31 > 192.168.3.21: ICMP echo reply, id 22272, seq 2, length 64
11:38:46.665794 IP 192.168.3.31 > 192.168.3.21: ICMP echo reply, id 22272, seq 2, length 64

也就是虚拟机到宿主机到包会重复。但是如果使用 ovs-tcpdump 来抓包,则不会出现这个问题。我能给出的解释是:因为 vm 和 host 在同一个底层的网络协议上,所以一个包会出现两次。

Last updated