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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://jiangpengfei.gitbook.io/open-vswitch/jin-jie/shi-yong-ovs-da-tong-duo-jie-dian-shang-de-rong-qi-tong-xin-underlay.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
