Linux 下實踐 VxLAN:虛擬機和 Docker 場景


本文首發於我的公眾號 Linux雲計算網絡(id: cloud_dev),專注於干貨分享,號內有 10T 書籍和視頻資源,后台回復 「1024」 即可領取,歡迎大家關注,二維碼文末可以掃。
來源:https://www.cnblogs.com/wipan/p/9220615.html

在上篇文章中,我們已經探討了 VxLAN 的概念和基本原理,本文就基於 Linux 對 VxLAN 做一個實踐。如果有相關概念不懂的可以先看那篇文章。

01 Linux 上對 VxLAN 的支持

首先,來看下 Linux 對 VxLAN 的支持,Linux 對 VxLAN 協議的支持時間並不久,2012 年 Stephen Hemminger 才把相關的工作合並到 kernel 中,並最終出現在 kernel 3.7.0 版本。為了穩定性和很多的功能,你可以會看到某些軟件推薦在 3.9.0 或者 3.10.0 以后版本的 kernel 上使用 VxLAN。

這些版本的 Linux 對 VxLAN 的支持已經完備,支持單播和組播,IPv4 和 IPv6。利用 man 查看 ip 的 link 子命令,可以查看是否有 vxlan type,如下:

man ip link

以下的實驗在如下的環境中完成:

  • 操作系統版本:CentOS Linux release 7.4.1708 (Core)
  • 內核版本:3.10.0-693.2.2.el7.x86_64
  • 雲虛機vm1 eth0網絡接口IP 172.31.0.106,雲虛機vm2 eth0網絡接口IP 172.31.0.107

02 實驗1:最簡單的點對點 VxLAN

創建簡單的點對點 VxLAN 環境非常簡單。如下圖所示,只需要在兩個機器(物理機或者虛擬機都可以,本實驗中是雲上的虛擬機環境)中各創建一個 vxlan 類型的網絡接口即可,vxlan 類型的接口 vxlan1 可以作為的 VTEP。

在上面的環境中,注意我們將 vxlan 網絡接口配置上 IP 地址,在 10.0.0.0/24 網段內。在 IP 地址分配后,Linux 系統的路由表就會創建一條路由,去往 10.0.0.0/24 網段的報文走網絡接口 vxlan1 出去。vm1 上去往 10.0.0.0/24 的報文,在 vxlan1 上會做 VxLAN 封裝,內層地址是10.0.0.106,外層地址是172.31.0.106。VxLAN 報文通過物理網絡達到對端 vm2 上的 VETP vxlan1,在 vm2 的 vxlan1 接口上做VxLAN 協議的解封裝,從而結束整個過程。

上圖是一個物理上的示意圖,在邏輯上形成的 VxLAN overlay 網絡環境如下圖,虛線部分示意出來的 Overlay Network 和 VxLAN Tunnel 都是邏輯上的概念。如果有容器和虛機被接入邏輯上的 Overlay 網絡 10.0.0.0/24,它們完全不用感知底層物理網絡,看起來對端是和自己在同一個二層環境里,就是像是在 VTEP 設備的上面直接構建了一條 VxLAN Tunnel,把 Overlay 網絡里的網絡接口直接在二層打通。

具體的配置只需要 3 條命令。如下,在 vm1 上執行如下命令:

# ip link add vxlan1 type vxlan id 1 remote 172.31.0.107 dstport 4789 dev eth0
# ip link set vxlan1 up
# ip addr add 10.0.0.106/24 dev vxlan1

上面的第一條命令創建了一個 Linux 上類型為 vxlan 的網絡接口,名為 vxlan1。

  • id: VNI標識是1。
  • remote: 作為一個VTEP設備來封裝和解封VXLAN報文,需要知道將封裝好的VXLAN報文發送到哪個對端VTEP。Linux上可以利用group指定組播組地址,或者利用remote指定對端單播地址。在實驗的雲環境中默認不支持組播,這里利用remote指定點對點的對端IP地址為172.31.0.107。
  • dstport: 指定目的端口為4789。因為當Linux內核3.7版本首次實現VXLAN時,UDP端口還並沒有規定下來。很多廠商利用了8472這個端口,Linux也采用了相同的端口。后來IANA分配了4789作為VXLAN的目的UDP端口。如果你需要使用IANA端口,需要用dstport指定。
  • dev: 指定VTEP通過哪個物理device來通信,這里是使用eth0。

第二條命令讓 vxlan1 接口 up 起來。第三條命令給設備分配 IP 地址 10.0.0.106, 子網掩碼為 24 (255.255.255.0)。

在 vm2 上,利用類似方法創建名為 vxlan1 的網絡接口。

# ip link add vxlan1 type vxlan id 1 remote 172.31.0.106 dstport 4789 dev eth0
# ip link set vxlan1 up
# ip addr add 10.0.0.107/24 dev vxlan1

以上簡單的命令就完成了所有配置。用 ifconfig 可以看到 vxlan1 網絡接口,如下:

# ifconfig vxlan1
vxlan1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1450
        inet 10.0.0.106  netmask 255.255.255.0  broadcast 0.0.0.0
        ether 22:2d:c4:f0:c7:29  txqueuelen 1000  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

看下 vm1 的如下路由表,去往目的網段 10.0.0.0/24 的報文將走 vxlan1 接口。

# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         172.31.0.253    0.0.0.0         UG    0      0        0 eth0
10.0.0.0        0.0.0.0         255.255.255.0   U     0      0        0 vxlan1
169.254.0.0     0.0.0.0         255.255.0.0     U     1002   0        0 eth0
172.31.0.0      0.0.0.0         255.255.255.0   U     0      0        0 eth0

在 vm1 上 ping overlay 網絡的對端 IP 地址 10.0.0.107,可以 ping 通。

# ping 10.0.0.107 -c 3
PING 10.0.0.107 (10.0.0.107) 56(84) bytes of data.
bytes from 10.0.0.107: icmp_seq=1 ttl=64 time=0.447 ms
bytes from 10.0.0.107: icmp_seq=2 ttl=64 time=0.361 ms
bytes from 10.0.0.107: icmp_seq=3 ttl=64 time=0.394 ms

--- 10.0.0.107 ping statistics ---
packets transmitted, 3 received, 0% packet loss, time 2000ms
rtt min/avg/max/mdev = 0.361/0.400/0.447/0.042 ms

在 ping 包的同時,用 tcpdump 抓 vm1 eth0 網卡的包。因為報文到達 eth0 前經過了網絡接口 vxlan1, 完成了 VxLAN 的封裝,所以在抓包結果里應該能看到完整的 VxLAN 報文。

抓包時可以只抓和對端 172.31.0.107 通信的報文,如下:

# tcpdump -i eth0 host 172.31.0.107 -s0 -v -w vxlan_vni_1.pcap

抓包結果如下,wireshark 自動將 UDP 目的端口為 4789 的報文識別成 VxLAN 報文,直接顯示內層的報文,protocol 為 ICMP 協議。如果使用 Linux 默認接口 8472,顯示的應該是 UDP 協議,還需要修改 wireshark 的協議設置,讓其識別成 VxLAN。

03 實驗2:容器跨主機通信

上面最簡單的點對點 VxLAN 實驗只是個簡答的演示,沒有太多實際工程意義,本節用容器通信來演示一個更加完整的場景。

場景描述:在 vm1 和 vm2 上各部署一個 docker 容器,默認情況下,一個容器宿主機上的容器能夠直接用私網 IP 地址通信,因為它們利用一個網橋接在一起。而不同宿主機上的容器無法直接用私網 IP 地址通信。k8s 等 docker 部署軟件中的網絡組建實際上完成了這部分工作,讓不同宿主機的容器能夠直接通信。本節使用原生 docker,以及在宿主機上自建的 vxlan 網絡接口,來打通不同宿主機上容器,讓它們可以直接利用內網IP通信。

注意:因為實驗在雲上的虛擬機上完成,上面提到的容器宿主機,用的是雲上的虛擬機。容器宿主機也可以是物理機,實驗效果不變。

3.1 准備 docker 容器

安裝 docker 的過程不展開了,docker 官方文檔有詳細的描述。在 Linux 安裝了 docker 后,可以看到多了一個 docker0 的網絡接口,默認在 172.17.0.0/16 網段。這個是連接本地多個容器的網橋。

# ifconfig docker0
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1450
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255
        ether 02:42:44:e8:74:e8  txqueuelen 0  (Ethernet)
        RX packets 6548  bytes 360176 (351.7 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 7489  bytes 40249455 (38.3 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

使用默認 172.17.0.0/16 網段,docker 容器的 IP 地址都會從 172.17.0.2 開始分配。為了能使 vm1 和 vm2 上的容器使用不同的IP地址,在利用 docker run 啟動容器的時候需要能自定義 IP 地址,而利用 --ip 參數自定義 IP 地址的功能只能在自定網絡中支持,所以先創建一個自定義網絡,指定網段 172.18.0.0/16。

# docker network create --subnet 172.18.0.0/16 mynetwork
3231f89d69f6b3fbe2550392ebe4d00daa3d19e251f66ed2d81f61f2b9184362
# docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
1cb284a6cb33        bridge              bridge              local
069538be0246        host                host                local
3231f89d69f6        mynetwork           bridge              local
0b7934996485        none                null                local

利用 docker network ls 查看,可以看到一個新的 bridge 網絡被創建,名稱為我指定的 mynetwork。利用 ifconfig 可以看到多了一個網絡接口,名字不是 dockerXX,而直接以 br 開頭,是一個網橋。

br-3231f89d69f6: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 172.18.0.1  netmask 255.255.0.0  broadcast 172.18.255.255
        ether 02:42:97:22:a5:f9  txqueuelen 0  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

創建一個新的容器,如下:

# docker run -itd --net mynetwork --ip 172.18.0.2 centos
16bbaeaaebfccd2a497e3284600f5c0ce230e89678e0ff92f6f4b738c6349f8d
  • --net指定自定義網絡
  • --ip指定IP地址
  • centos指定image

查看容器 ID 和狀態,並且登錄 SHELL,如下:

# docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
16bbaeaaebfc        centos              "/bin/bash"         2 minutes ago       Up 2 minutes                            condescending_swartz
# docker exec -it 16bbaeaaebfc /bin/bash
[root@16bbaeaaebfc /]# ifconfig
bash: ifconfig: command not found

注意:docker 為了創建容器的效率,通常都用了 size 很小的 image,意味着很多常用工具需要安裝,比如 centos image 里面的 ifconfig。可以利用 yum whatprovides ifconfig 命令查看 ifconfig 輸入哪個包,查到屬於 net-tools-2.0-0.22.20131004git.el7.x86_64包,直接用 yum install net-tools -y 安裝即可。再執行 ifconfig 命令,可以看到容器 eth0 網卡的 IP 地址為 172.18.0.2。

[root@16bbaeaaebfc /]# ifconfig eth0
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.18.0.2  netmask 255.255.0.0  broadcast 172.18.255.255
        ether 02:42:ac:12:00:02  txqueuelen 0  (Ethernet)
        RX packets 3319  bytes 19221325 (18.3 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 2015  bytes 132903 (129.7 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

在 vm2 上執行同樣的操作,在創建新容器的時候,指定 IP 地址為 172.18.0.3,容器的環境即准備完畢。在 vm1 上的 centos 容器中 ping 172.18.0.3,和預期一致,是無法 ping 通的。

[root@16bbaeaaebfc /]# ping 172.18.0.3 -c 2
PING 172.18.0.3 (172.18.0.3) 56(84) bytes of data.
From 172.18.0.2 icmp_seq=1 Destination Host Unreachable
From 172.18.0.2 icmp_seq=2 Destination Host Unreachable

--- 172.18.0.3 ping statistics ---
packets transmitted, 0 received, +2 errors, 100% packet loss, time 1000ms
pipe 2
[root@16bbaeaaebfc /]# ping 172.18.0.1 -c 2
PING 172.18.0.1 (172.18.0.1) 56(84) bytes of data.
bytes from 172.18.0.1: icmp_seq=1 ttl=64 time=0.060 ms
bytes from 172.18.0.1: icmp_seq=2 ttl=64 time=0.079 ms

--- 172.18.0.1 ping statistics ---
packets transmitted, 2 received, 0% packet loss, time 999ms
rtt min/avg/max/mdev = 0.060/0.069/0.079/0.012 ms

3.2 創建 VxLAN 接口接入 docker 網橋

先來梳理下docker及docker容器在Linux宿主機網絡模塊中做的操作,梳理清楚之后會發現打通不同宿主機上docker容器的方法非常簡單。從宿主Linux系統的視角看操作系統中的網絡設備,總結如下:

  • docker0接口:網橋,在安裝完docker后默認被創建,網段是172.17.0.0/16,網橋的默認IP地址為172.17.0.1。
  • br-xxxx接口:網橋,在創建完自定義docker網絡完被創建,網段是被用戶指定的172.18.0.0/16,網橋的默認IP地址為172.18.0.1。
  • vethxxxx接口:veth網絡接口,在創建一個具體的docker容器后被創建,如果有N個運行的容器,就會有N個veth網絡接口。容器中的eth0接口和宿主機的veth網絡接口是一個veth網絡對,Linux上的veth接口作為一個端口連接入docker網橋,如docker0或其他自定義網橋。這也是為什么一個宿主機上的docker容器能夠默認通信的原因,因為它們創建后就被接入到了同一個網橋上。

為了方便理解,在默認網段172.17.0.0/16中創建2個容器,在自定義網段中上文已經創建了1個docker容器,利用btctl查看網橋及其接口,如下:

# brctl show
bridge name    bridge id        STP enabled    interfaces
br-3231f89d69f6        8000.02429722a5f9    no        veth2fa4c50
docker0        8000.024244e874e8    no        vethc7cd982
                                       vethd3d0c18

從上面的輸出結果可以看到,默認網橋docker0上,有vethc7cd982和vethd3d0c18兩個網絡接口接入。在定義網絡網橋br-3231f89d69f6一個端口上,veth2fa4c50網絡接口接入。這三個veth網絡接口分別連接着一個docker容器的eth0網絡接口,連接着同一個網橋的veth網絡接口vethc7cd982和vethd3d0c18默認二層能通。

有了上面的梳理和本文第一節VXLAN網絡接口的基礎知識,想必打通不同宿主機上docker容器的方法也比較清晰了。思路就是在兩個容器宿主機上各創建一個VXLAN接口,並且將VXLAN接口接入docker網橋的端口上,如下圖:

有了VXLAN接口的連接后,從vm1上docker容器發出的包到達docker網橋后,可以從網橋的VXLAN接口出去,從而報文在VETP(VXLAN接口)處被封裝成VXLAN報文,再從物理網絡上到達對端VETP所在的主機vm2。對端VTEP能正確解包VXLAN報文的話,隨后即可將報文通過vm2上的docker網橋送到上層的docker容器中。

具體的配置如下,在vm1上:

# ip link add vxlan_docker type vxlan id 200 remote 172.31.0.107 dstport 4789 dev eth0
# ip link set vxlan_docker up
# brctl addif br-3231f89d69f6 vxlan_docker
  • 第一條命令創建VNI為200的VXLAN網絡接口,名稱為vxlan_docker,參數設置和場景1中的各個參數類似。
  • 第三條命令把新創建的VXLAN接口vxlan_docker接入到docker網橋br-3231f89d69f6中。

在vm2上,輸入如下命令:

# ip link add vxlan_docker type vxlan id 200 remote 172.31.0.106 dstport 4789 dev eth0
# ip link set vxlan_docker up
# brctl addif br-f4b35af34313 vxlan_docker

在vm1的docker容器上再ping 172.18.0.3,結果如下,ping可以通。注意RTT的時間,ping 172.18.0.3的RTT在10^(-1)毫秒級別,ping 172.18.0.1的RTT在10^(-2)毫秒級別,前者是走物理網絡的延遲,后者是協議棧的延遲,兩者有量級上的差別。

# docker exec -it 16bbaeaaebfc ifconfig eth0
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.18.0.2  netmask 255.255.0.0  broadcast 172.18.255.255
        ether 02:42:ac:12:00:02  txqueuelen 0  (Ethernet)
        RX packets 3431  bytes 19230266 (18.3 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 2132  bytes 141908 (138.5 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

# docker exec -it 16bbaeaaebfc ping 172.18.0.3 -c 2
PING 172.18.0.3 (172.18.0.3) 56(84) bytes of data.
bytes from 172.18.0.3: icmp_seq=1 ttl=64 time=0.544 ms
bytes from 172.18.0.3: icmp_seq=2 ttl=64 time=0.396 ms

--- 172.18.0.3 ping statistics ---
packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 0.396/0.470/0.544/0.074 ms
#
# docker exec -it 16bbaeaaebfc ping 172.18.0.1 -c 2
PING 172.18.0.1 (172.18.0.1) 56(84) bytes of data.
bytes from 172.18.0.1: icmp_seq=1 ttl=64 time=0.072 ms
bytes from 172.18.0.1: icmp_seq=2 ttl=64 time=0.072 ms

--- 172.18.0.1 ping statistics ---
packets transmitted, 2 received, 0% packet loss, time 999ms
rtt min/avg/max/mdev = 0.072/0.072/0.072/0.000 ms

04 總結

最后說明,本節只是為了演示 Linux VxLAN 的用於而構造了這個簡單但沒有實際用處的場景,在跨主機環境的容器之間利用 VxLAN 從二層打通。在工程中做容器跨主機通信時有很多方面需要考慮,也有很多項目在致力於這方面的研究。比如 Flannel,通過給每台宿主機分配一個子網的方式為容器提供虛擬網絡,它基於 Linux TUN/TAP,使用 UDP 封裝 IP 包來實現 L3 overlay 網絡,並借助 etcd 維護網絡的分配情況。


公眾號后台回復“加群”,帶你進入高手如雲交流群

我的公眾號 「Linux雲計算網絡」(id: cloud_dev) ,號內有 10T 書籍和視頻資源,后台回復 「1024」 即可領取,分享的內容包括但不限於 Linux、網絡、雲計算虛擬化、容器Docker、OpenStack、Kubernetes、工具、SDN、OVS、DPDK、Go、Python、C/C++編程技術等內容,歡迎大家關注。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM