VXLAN SDN网络示例

容器网络 vxlan 实现示例

环境准备

计划实现容器 overlay 网络 CIDR 10.233.0.0/16

准备 2-3 台服务器,以模拟跨主机通信。

172.16.1.11 ubuntu1
172.16.1.12 ubuntu2
172.16.1.13 ubuntu3

使用的 vagrant 配合 virtualbox 启动虚拟机,一个 Vagranfile 文件如下:

# -*- mode: ruby -*-
# vi: set ft=ruby :

$script = <<-SCRIPT
sudo ip route del 0/0
sudo ip route add default via 172.16.1.1
SCRIPT

Vagrant.configure("2") do |config|
  config.vm.box = "generic/ubuntu1804"
  config.vm.hostname = "ubuntu-1"
  config.vm.network "private_network",  ip: "172.16.1.11"
  # default router
  config.vm.provision "shell",
    run: "always",
    inline: $script
end

关于 vagrant 的使用和网络路由等配置不在这里讨论。

设置 vxlan 通信

vxlan 支持点对点/多播/手动配置等方式进行跨主机的 vxlan 通信,点对点模式通信不适用于超过两个节点的集群。
为了简化配置,使用节点间的多播地址进行 vxlan 间的通信。

基于多播的 vxlan 模式在主机网络(hosting network)不支持多播时无法使用,对于该问题至以及优化,在后续章节描述。

由于不同主机的 vxlan 之间使用 UDP 的多播(IGMP)进行通信以交换不同节点信息,所以需要选择同一个多播地址来完成,这里选择了 239.1.1.1.

此外,还需要选择一个 VNI(vxlan network identifier),作为该 L2 vxlan 的数据包标志,这里选择了 42.

ubuntu1 上:

# 创建一个vxlan设备
sudo ip link add vxlan0 type vxlan id 42 dstport 4789 group 239.1.1.1 dev eth1
sudo ip link set vxlan0 up

其他机器上进行相同的配置

正常情况下,此时不同主机上的 vxlan0 已经形成了一个 L2 网络了。

测试一下:

由于 vxlan0 没有 ip 地址,无法通过 L3 进行连通性测试,
可以使用 L2 的 ping 命令进行 L2 测试, ping 提供了参数 -I 可以指定 arp 数据包通过哪个网卡发送出去。

任意选择一台主机(ubuntu1),任意选择一个 IP 地址作为 ping 目的地址:

$ ping -I vxlan0 10.0.0.1
ping: Warning: source address might be selected on device other than vxlan0.
PING 10.0.0.1 (10.0.0.1) from 172.16.1.11 vxlan0: 56(84) bytes of data.
...

在其他主机(ubuntu2)上对 vxlan0 网卡抓包:

$ sudo tcpdump -nn -i vxlan0
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on vxlan0, link-type EN10MB (Ethernet), capture size 262144 bytes
11:18:22.186819 ARP, Request who-has 10.0.0.1 tell 172.16.1.12, length 28
11:18:23.189696 ARP, Request who-has 10.0.0.1 tell 172.16.1.12, length 28
...

arp 数据包已经成功的通过 vxlan 发送到了其他主机,L2 网络已经连通。

除了使用 ping 外,还可以对 vxlan0 设置 IP 地址

ubuntu1 vxlan0 上设置 IP 10.16.0.1/16

$ sudo ip address add 10.16.0.1/16 dev vxlan0
$ ping 10.16.0.2
ping  10.16.0.2PING 10.16.0.2 (10.16.0.2) 56(84) bytes of data.
64 bytes from 10.16.0.2: icmp_seq=1 ttl=64 time=0.622 ms
64 bytes from 10.16.0.2: icmp_seq=2 ttl=64 time=0.918 ms

ubuntu1 vxlan0 上设置 IP 10.16.0.2/16

$ sudo ip address add 10.16.0.2/16 dev vxlan0
$ sudo tcpdump -nn  -i vxlan0 -vvv
tcpdump: listening on vxlan0, link-type EN10MB (Ethernet), capture size 262144 bytes
01:40:09.770148 ARP, Ethernet (len 6), IPv4 (len 4), Request who-has 10.16.0.2 tell 10.16.0.1, length 28
01:40:09.770181 ARP, Ethernet (len 6), IPv4 (len 4), Reply 10.16.0.2 is-at 3a:39:6e:d0:2f:6b, length 28
01:40:09.770421 IP (tos 0x0, ttl 64, id 16657, offset 0, flags [DF], proto ICMP (1), length 84)
    10.16.0.1 > 10.16.0.2: ICMP echo request, id 2971, seq 1, length 64
01:40:09.770445 IP (tos 0x0, ttl 64, id 37746, offset 0, flags [none], proto ICMP (1), length 84)
    10.16.0.2 > 10.16.0.1: ICMP echo reply, id 2971, seq 1, length 64
01:40:10.889570 IP (tos 0x0, ttl 64, id 16923, offset 0, flags [DF], proto ICMP (1), length 84)
...

验证完成后记得移除 vxlan0 上的 IP:

sudo ip addr flush dev vxlan0

使用 bridge 来接入容器

linux bridge 是一个二层设备,所有接入到网桥上的设备都能够发现其他接入该网桥的设备。可以将veth0 vxlan0 通过网桥接入到一起,实现通过 veth0 发出的流量能够进入 vxlan0,从而实现跨主机通信。

接入网桥后,vxlan0 的所有流量将进入网桥。

# 创建一个网桥设备
sudo ip link add name br0 type bridge
sudo ip link set br0 up

# 将vxlan加入网桥
sudo ip link set vxlan0 master br0

将 vxlan0 加入网桥后,可以在一台机器使用 ping -I br0 10.0.0.1,在其他机器上sudo tcpdump -i br0 抓包检验 L2 通信,也是能够正常通信。

模拟容器环境

ip 命令提供了 netns 子命令来帮助管理多个网络空间(NS_NETWORK),可使用该子命令创建一个新的网络空间,借此来模拟容器环境。

在 ubuntu1,ubuntu2 上创建一个"容器":

# 创建一个网络空间 ns1,作为“容器”
sudo ip netns add ns1
# 创建 veth pair,一端名称为 veth0,一端名称为 eth-tmp
sudo ip link add veth0 type veth peer name eth-tmp
# 将 eth-tmp 的一端放入网络空间 ns1
sudo ip link set eth-tmp netns ns1
# 重命名 ns1 中 eth-tmp 为 eth0
sudo ip netns exec ns1 ip link set eth-tmp name eth0
# 启用所有设备
sudo ip netns exec ns1 ip link set eth0 up
sudo ip netns exec ns1 ip link set lo up
sudo ip link set veth0 up

将容器也接入网桥

sudo ip link set veth0 master br0

理论上,此时跨主机的 L2 已经能够通信。在容器内执行 ping - tcpdump 也能够跨主机 L2 联通,但此时 arp 源 IP 为 0.0.0.0 无意义。

设置容器

我们需要给容器设置 IP 地址路由,检验 L3 通信是否正常。

把该网段的第一个 IP 地址10.233.0.1作为网关地址预留,不分配给容器,之所以需要网关,是能够将数据包路由出去且可以在网关上进行网络策略配置。

为容器设置 IP:

ubuntu1 设置 10.233.0.2:

# 设置 ns1 中 eth0 的IP地址为"容器"IP地址: 10.233.0.2
sudo ip netns exec ns1 ip address add 10.233.0.2/16 dev eth0
# 此时在容器内部,还没有路由,需要添加路由
# 设置所有流量都通过容器 eth0 出去下一跳为网关地址 10.233.0.1
sudo ip netns exec ns1 ip route add default via 10.233.0.1 dev eth0

ubuntu2 设置 10.233.0.3:

sudo ip netns exec ns1 ip address add 10.233.0.3/16 dev eth0
sudo ip netns exec ns1 ip route add default via 10.233.0.1 dev eth0

设置网关

我们在上面的配置中将 10.233.0.1 作为了网关,所有容器的流量都将发送至该地址,却未配置拥有该 IP 的设备。

可以将 10.233.0.1 设置在 br0 或者 vxlan0 上都可。

我们将 br0 作为网关进行设置,每台主机都需要配置:

sudo ip addr add 10.233.0.1/16 dev br0

测试一下:

在 ubuntu1(10.233.0.2) 上:

$ sudo ip netns exec ns1 ping 10.233.0.3
PING 10.233.0.3 (10.233.0.3) 56(84) bytes of data.
64 bytes from 10.233.0.3: icmp_seq=1 ttl=64 time=1.26 ms
64 bytes from 10.233.0.3: icmp_seq=2 ttl=64 time=0.626 ms

此时,跨主机容器已经能够顺利通信,主机到容器也能够正常通信。

现在来梳理一下,从一个容器到另一个容器的 ping 命令下面都发生了什么。

以从 ubuntu1(172.16.1.11) 容器 10.233.0.2 到 ubuntu2(172.16.1.12)容器 10.233.0.3 为例:

  1. 容器:10.233.0.2 网络下执行 ping,生成 arp 数据包,who has 10.233.0.3 tell 10.233.0.2
  2. 容器:根据到路由表 default via 10.233.0.1 dev eth0 ,将数据包从 eth0 发出。
  3. 主机:veth0 收到数据包,将数据包转至 br0(10.233.0.1).br0 寻找 fdb 将数据包从 vxlan0 发出。
  4. ...

NAT out

目前为止,能够做到主机到容器,但尚不能做到从容器内部访问外网。

为了能够让容器访问外网,还需要设置 NAT,将从目的地址为非容器网段的数据包进行 NAT 转换。

涉及到数据包转发,需要开启内核/proc/sys/net/ipv4/ip_forward

# 开启IP转发
sudo sysctl -w net.ipv4.ip_forward=1
# 将从10.233.0.0/16源地址且目的地址非10.233.0.0/16的数据包进行伪装(MASQUERADE)
sudo iptables -t nat --append POSTROUTING --src 10.233.0.0/16 ! --dest 10.233.0.0/16 --jump MASQUERADE

手动维护 vxlan

上文使用的多播地址 239.1.1.1 进行 vxlan 之间协调。

如果在不支持多播的 underlay 网络中,则上述模式无法工作。需要手动维护 vxlan 配置。包含 forwading database ,arp table 等。

上文中我们知道了一个容器需要与跨节点的容器通信需要经过以下流程:

以 10.233.0.2 至 10.233.0.3 为例

  1. ubuntu1上容器10.233.0.2查找本地路由表,发现默认路由至default via 10.233.0.1 dev eth0
  2. ubuntu1上容器10.233.0.2发送数据包,但此时不知道10.233.0.1 MAC ,发送 arp 询问who has 10.233.0.1 tell 10.233.0.2
  3. ubuntu1br0拥有 IP10.233.0.1,应答自己的 MAC10.233.0.1 is-at <ubuntu1-br-mac>
  4. ubuntu1br0 准备转发数据包,查询 arp 表,发现无该 IP-MAC 映射。
  5. ubuntu1br0发起 arp 询问who has 10.233.0.2 tell 10.233.0.1.在 L2 时,该 arp 报文目的 MAC 为广播地址FF:FF:FF:FF:FF:FF
  6. ubuntu1vxlan0收到该 arp,准备进行 L2 转发, 根据 fdb 默认项 00:00:00:00:00:00 dst 233.9.9.9 via eth1,将从 vxlan -> 转换成 UDP 包从 eth1 通过多播地址发出。
  7. 关于多播组如何维护等细节暂时略过,也不需要关心,要知道的是发往多播地址的数据包会复制发往该组中的每个成员。
  8. 多播组其他成员 ubuntu2eth1收到 udp 数据包,接报后发往 vxlan0
  9. ubuntu2vxlan0收到 L2 报文后发至 br0
  10. ubuntu2br0 查找自身 arp 表不存在该映射,发起 arp 询问who has 10.233.0.2 tell 10.233.0.
  11. ubuntu2 上容器10.233.0.3收到 L3 arp 询问后回复10.233.0.3 is-at <container2-mac>
  12. ubuntu2br0 收到 arp 响应,记录 arp 项10.233.0.3 dev br0 lladdr <container2-mac>
  13. ubuntu2br0 回复 10.233.0.3 is-at <container2-mac>
  14. ubuntu1vxlan0收到 L2 包,记录 fdb 项<container2-mac> dev br0 dst 172.16.1.12 self,后发送至br0
  15. ubuntu1 br0 收到 arp 响应,记录 arp 项10.233.0.3 dev br0 lladdr <container2-mac>
  16. ubuntu1 br0 后续转发 10.233.0.3 的数据包将直接使用<container2-mac>

在上述流程中主要有两个地方需要发送广播/组播包,第一个为 arp 记录询问,第二个为 fdb 询问。

手动维护 fdb

如果能够手动维护 arp 表与 fdb 表,则可以在不支持组播的网络中运行。若手动维护 arp 表,
则可以将 vxlan 设置 nolearning 禁止自动学习,而使用手动设置。

# 创建一个vxlan设备
sudo ip link add vxlan0 type vxlan id 42 dstport 4789 dev eth1 nolearning

相比上次的 vxlan 创建,去掉了 group 233.9.9.9表示使用多播来发现其他节点的 vtep。
对应的 fdb 表项中没有任何记录。

作为替代,可以手动设置以下记录

bridge fdb append 00:00:00:00:00:00 dev vxlan0 dst 172.16.1.12
bridge fdb append 00:00:00:00:00:00 dev vxlan0 dst 172.16.1.13

这条记录设置了默认的出口,与使用组播时的 fdb 默认项目(下文)具有相同作用。表示默认数据包发往 172.16.1.12 172.16.1.13 两个 vtap。

$ sudo bridge fdb show dev vxlan0
00:00:00:00:00:00 dst 233.9.9.9 via eth1

手动维护 arp

除了 fdb 表,arp 表也需要手动维护,放置不知道目的 MAC 时发起 ARP 询问。

只需要让 br0 或者 vxlan0 知道 IP-MAC 映射即可。

但是我们使用 br0 作为网关的方式进行使用,默认情况下 vxlan0 没有 IP,仅作为二层数据处理,并不响应 arp 请求,只能在 br0 上记录该 arp。

$ sudo ip n add 10.233.0.3 dev br0 lladdr <container2-mac>

此时,发往 10.233.0.3 的数据包不触发 arp 询问,但此时 fdb 并无该 mac 地址记录,将使用默认出口发出。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容