容器網(wǎng)絡 vxlan 實現(xiàn)示例
環(huán)境準備
計劃實現(xiàn)容器 overlay 網(wǎng)絡 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 的使用和網(wǎng)絡路由等配置不在這里討論。
設置 vxlan 通信
vxlan 支持點對點/多播/手動配置等方式進行跨主機的 vxlan 通信,點對點模式通信不適用于超過兩個節(jié)點的集群。
為了簡化配置,使用節(jié)點間的多播地址進行 vxlan 間的通信。
基于多播的 vxlan 模式在主機網(wǎng)絡(hosting network)不支持多播時無法使用,對于該問題至以及優(yōu)化,在后續(xù)章節(jié)描述。
由于不同主機的 vxlan 之間使用 UDP 的多播(IGMP)進行通信以交換不同節(jié)點信息,所以需要選擇同一個多播地址來完成,這里選擇了 239.1.1.1.
此外,還需要選擇一個 VNI(vxlan network identifier),作為該 L2 vxlan 的數(shù)據(jù)包標志,這里選擇了 42.
ubuntu1 上:
# 創(chuàng)建一個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 已經(jīng)形成了一個 L2 網(wǎng)絡了。
測試一下:
由于 vxlan0 沒有 ip 地址,無法通過 L3 進行連通性測試,
可以使用 L2 的 ping 命令進行 L2 測試, ping 提供了參數(shù) -I 可以指定 arp 數(shù)據(jù)包通過哪個網(wǎng)卡發(fā)送出去。
任意選擇一臺主機(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 網(wǎng)卡抓包:
$ 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 數(shù)據(jù)包已經(jīng)成功的通過 vxlan 發(fā)送到了其他主機,L2 網(wǎng)絡已經(jīng)連通。
除了使用 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 是一個二層設備,所有接入到網(wǎng)橋上的設備都能夠發(fā)現(xiàn)其他接入該網(wǎng)橋的設備。可以將veth0 vxlan0 通過網(wǎng)橋接入到一起,實現(xiàn)通過 veth0 發(fā)出的流量能夠進入 vxlan0,從而實現(xiàn)跨主機通信。
接入網(wǎng)橋后,vxlan0 的所有流量將進入網(wǎng)橋。
# 創(chuàng)建一個網(wǎng)橋設備
sudo ip link add name br0 type bridge
sudo ip link set br0 up
# 將vxlan加入網(wǎng)橋
sudo ip link set vxlan0 master br0
將 vxlan0 加入網(wǎng)橋后,可以在一臺機器使用 ping -I br0 10.0.0.1,在其他機器上sudo tcpdump -i br0 抓包檢驗 L2 通信,也是能夠正常通信。
模擬容器環(huán)境
ip 命令提供了 netns 子命令來幫助管理多個網(wǎng)絡空間(NS_NETWORK),可使用該子命令創(chuàng)建一個新的網(wǎng)絡空間,借此來模擬容器環(huán)境。
在 ubuntu1,ubuntu2 上創(chuàng)建一個"容器":
# 創(chuàng)建一個網(wǎng)絡空間 ns1,作為“容器”
sudo ip netns add ns1
# 創(chuàng)建 veth pair,一端名稱為 veth0,一端名稱為 eth-tmp
sudo ip link add veth0 type veth peer name eth-tmp
# 將 eth-tmp 的一端放入網(wǎng)絡空間 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
將容器也接入網(wǎng)橋
sudo ip link set veth0 master br0
理論上,此時跨主機的 L2 已經(jīng)能夠通信。在容器內執(zhí)行 ping - tcpdump 也能夠跨主機 L2 聯(lián)通,但此時 arp 源 IP 為 0.0.0.0 無意義。
設置容器
我們需要給容器設置 IP 地址路由,檢驗 L3 通信是否正常。
把該網(wǎng)段的第一個 IP 地址
10.233.0.1作為網(wǎng)關地址預留,不分配給容器,之所以需要網(wǎng)關,是能夠將數(shù)據(jù)包路由出去且可以在網(wǎng)關上進行網(wǎng)絡策略配置。
為容器設置 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 出去下一跳為網(wǎng)關地址 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
設置網(wǎng)關
我們在上面的配置中將 10.233.0.1 作為了網(wǎng)關,所有容器的流量都將發(fā)送至該地址,卻未配置擁有該 IP 的設備。
可以將 10.233.0.1 設置在 br0 或者 vxlan0 上都可。
我們將 br0 作為網(wǎng)關進行設置,每臺主機都需要配置:
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
此時,跨主機容器已經(jīng)能夠順利通信,主機到容器也能夠正常通信。
現(xiàn)在來梳理一下,從一個容器到另一個容器的 ping 命令下面都發(fā)生了什么。
以從 ubuntu1(172.16.1.11) 容器 10.233.0.2 到 ubuntu2(172.16.1.12)容器 10.233.0.3 為例:
- 容器:10.233.0.2 網(wǎng)絡下執(zhí)行 ping,生成 arp 數(shù)據(jù)包,
who has 10.233.0.3 tell 10.233.0.2 - 容器:根據(jù)到路由表
default via 10.233.0.1 dev eth0,將數(shù)據(jù)包從 eth0 發(fā)出。 - 主機:veth0 收到數(shù)據(jù)包,將數(shù)據(jù)包轉至 br0(10.233.0.1).br0 尋找 fdb 將數(shù)據(jù)包從 vxlan0 發(fā)出。
- ...
NAT out
目前為止,能夠做到主機到容器,但尚不能做到從容器內部訪問外網(wǎng)。
為了能夠讓容器訪問外網(wǎng),還需要設置 NAT,將從目的地址為非容器網(wǎng)段的數(shù)據(jù)包進行 NAT 轉換。
涉及到數(shù)據(jù)包轉發(fā),需要開啟內核/proc/sys/net/ipv4/ip_forward
# 開啟IP轉發(fā)
sudo sysctl -w net.ipv4.ip_forward=1
# 將從10.233.0.0/16源地址且目的地址非10.233.0.0/16的數(shù)據(jù)包進行偽裝(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 之間協(xié)調。
如果在不支持多播的 underlay 網(wǎng)絡中,則上述模式無法工作。需要手動維護 vxlan 配置。包含 forwading database ,arp table 等。
上文中我們知道了一個容器需要與跨節(jié)點的容器通信需要經(jīng)過以下流程:
以 10.233.0.2 至 10.233.0.3 為例
-
ubuntu1上容器10.233.0.2查找本地路由表,發(fā)現(xiàn)默認路由至default via 10.233.0.1 dev eth0。 -
ubuntu1上容器10.233.0.2發(fā)送數(shù)據(jù)包,但此時不知道10.233.0.1MAC ,發(fā)送 arp 詢問who has 10.233.0.1 tell 10.233.0.2。 -
ubuntu1上br0擁有 IP10.233.0.1,應答自己的 MAC10.233.0.1 is-at <ubuntu1-br-mac>。 -
ubuntu1上br0準備轉發(fā)數(shù)據(jù)包,查詢 arp 表,發(fā)現(xiàn)無該 IP-MAC 映射。 -
ubuntu1上br0發(fā)起 arp 詢問who has 10.233.0.2 tell 10.233.0.1.在 L2 時,該 arp 報文目的 MAC 為廣播地址FF:FF:FF:FF:FF:FF。 -
ubuntu1上vxlan0收到該 arp,準備進行 L2 轉發(fā), 根據(jù) fdb 默認項00:00:00:00:00:00 dst 233.9.9.9 via eth1,將從 vxlan -> 轉換成 UDP 包從eth1通過多播地址發(fā)出。 - 關于多播組如何維護等細節(jié)暫時略過,也不需要關心,要知道的是發(fā)往多播地址的數(shù)據(jù)包會復制發(fā)往該組中的每個成員。
- 多播組其他成員
ubuntu2上eth1收到 udp 數(shù)據(jù)包,接報后發(fā)往vxlan0。 -
ubuntu2上vxlan0收到 L2 報文后發(fā)至br0。 -
ubuntu2上br0查找自身 arp 表不存在該映射,發(fā)起 arp 詢問who has 10.233.0.2 tell 10.233.0. -
ubuntu2上容器10.233.0.3收到 L3 arp 詢問后回復10.233.0.3 is-at <container2-mac> -
ubuntu2上br0收到 arp 響應,記錄 arp 項10.233.0.3 dev br0 lladdr <container2-mac> -
ubuntu2上br0回復10.233.0.3 is-at <container2-mac> -
ubuntu1上vxlan0收到 L2 包,記錄 fdb 項<container2-mac> dev br0 dst 172.16.1.12 self,后發(fā)送至br0 -
ubuntu1br0收到 arp 響應,記錄 arp 項10.233.0.3 dev br0 lladdr <container2-mac> -
ubuntu1br0后續(xù)轉發(fā)10.233.0.3的數(shù)據(jù)包將直接使用<container2-mac>。
在上述流程中主要有兩個地方需要發(fā)送廣播/組播包,第一個為 arp 記錄詢問,第二個為 fdb 詢問。
手動維護 fdb
如果能夠手動維護 arp 表與 fdb 表,則可以在不支持組播的網(wǎng)絡中運行。若手動維護 arp 表,
則可以將 vxlan 設置 nolearning 禁止自動學習,而使用手動設置。
# 創(chuàng)建一個vxlan設備
sudo ip link add vxlan0 type vxlan id 42 dstport 4789 dev eth1 nolearning
相比上次的 vxlan 創(chuàng)建,去掉了 group 233.9.9.9表示使用多播來發(fā)現(xiàn)其他節(jié)點的 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 默認項目(下文)具有相同作用。表示默認數(shù)據(jù)包發(fā)往 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 時發(fā)起 ARP 詢問。
只需要讓 br0 或者 vxlan0 知道 IP-MAC 映射即可。
但是我們使用 br0 作為網(wǎng)關的方式進行使用,默認情況下 vxlan0 沒有 IP,僅作為二層數(shù)據(jù)處理,并不響應 arp 請求,只能在 br0 上記錄該 arp。
$ sudo ip n add 10.233.0.3 dev br0 lladdr <container2-mac>
此時,發(fā)往 10.233.0.3 的數(shù)據(jù)包不觸發(fā) arp 詢問,但此時 fdb 并無該 mac 地址記錄,將使用默認出口發(fā)出。