> For the complete documentation index, see [llms.txt](https://jiangpengfei.gitbook.io/open-vswitch/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://jiangpengfei.gitbook.io/open-vswitch/jin-jie/shi-yong-ovs-da-tong-duo-jie-dian-shang-de-rong-qi-tong-xin-underlay.md).

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

### 概述

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

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

![图1 underlay 网络示意图](/files/-M6gy33qpJxhV1tlxzV2)

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

* 物理机: 操作系统 ubuntu 19.10,  open vSwitch 2.12.0&#x20;
* 虚拟机: 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 去组件整个网络，因此这里的目标就是只实现这一部分，并且只会启动两台虚拟机，其余的原理也是一样：

![](/files/-M6h35-NjLUC1vL9s8Yb)

### 准备 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 在同一个底层的网络协议上，所以一个包会出现两次。
