鏈路層的網卡聚合-基於Linux bonding

linux總是可以用一種最簡單的方式實現一個很複雜的功能,特別是網絡方面的 ,哪怕這個功能被認爲只是在高端設備上纔有,linux也可以很容易的實現,以前的文章已經說了不少次了,比如vlan功能,比如高級路由和防火牆功能等等,本文着重說一下linux的bonding,也就是端口聚合的功能模塊。不可否認,在網絡設備這個層面上上,linux搞出了兩個很成功的虛擬設備的概念,一個是tap網卡,另一個就是本文所講述的bonding,關於tap網卡的內容,請參閱之前關於Open×××的文章。
     如果有一個問題擺在眼前,那就是關於linux bonding有什麼比較好的資料,答案就是linux內核的文檔,該文檔在$KERNEL-ROOT/Documentation/networking/bonding.txt,我覺得沒有任何資料比這個更權威了。
一、bonding簡介
bonding是一個linux kernel的driver,加載了它以後,linux支持將多個物理網卡捆綁成一個虛擬的bond網卡,隨着版本的升級,bond驅動可配置的參數越來越多,而且配置本身也越來越方便了。
     我們在很多地方會使用到物理網卡端口匯聚的功能,比如我們想提升網絡速率,比如我們想提供熱備份,比如我們想把我們的主機配置成一個網橋,並且使之支持802.3ad動態端口聚合協議等等,然而最重要的還是兩點,第一點是負載均衡,第二點就是熱備份啦。
二、驅動以及Changes介紹
linux的bonding驅動的最初版本僅僅提供了基本的機制,而且需要在加載模塊的時候指定配置參數,如果想更改配置參數,那麼必須重新加載bonding模塊;然後modprobe支持一種rename的機制,也就是在modprobe的時候支持使用-o重新爲此模塊命名 ,這樣就可以實現一個模塊以不同的配置參數加載多次了,起初比如我有4個網口,想把兩個配置成負載均衡,兩個配置成熱備,這隻能手工重新將bonding編譯成不同的名稱來解決,modprobe有了-o選項之後,就可以兩次加載相同的驅動了,比如可以使用:
modprobe bonding -o bond0 mode=0
modprobe bonding -o bond1 mode=1

加載兩次bonding驅動,用lsmod看一下,結果是bond0和bond1,並沒有bonding,這是由於modprobe加載時命名了,然而最終,這個命名機制不再被支持了,因爲正如modprobe的man手冊所敘述的一樣,-o重命名機制主要適用於test。最後,bonding支持了sysfs的配置機制,對/sys/class/net/目錄下的文件進行讀或者寫就可以完成對驅動的配置。
     不管怎樣,在sysfs完全支持bonding配置之前,如果想往某一個bonding網卡添加設備或者刪除設備的時候,還是要使用經典且傳統的ioctl調用,因此必然需要一個用戶態程序與之對應,該程序就是ifenslave。
     我想,如果linux的所有關於設備的配置都能統一於sysfs,所有的關於內核和進程配置統一於procfs(內核是所有進程共享的地址空間,也有自己的內核線程以及進程0,因此對內核的配置應該在procfs中),對所有的消息,使用netlink通信,這就太好了,擺脫了命令式的ioctl配置,文件式(netlink使用的sendto之類的系統調用也可以歸爲文件系統調用相關的)的配置將更加高效,簡單以及好玩!
三、bonding配置參數
在內核文檔中,列舉了許多bonding驅動的參數,然後本文不是文檔的翻譯,因此不再翻譯文檔和介紹和主題無關的參數,僅對比較重要的參數進行介紹,並且這些介紹也不是翻譯,而是一些建議或者心得。
ad_select: 802.3ad相關。如果不明白這個,那不要緊,拋開Linux的bonding驅動,直接去看802.3ad的規範就可以了。列舉這個選項說明linux bonding驅動完全支持了動態端口聚合協議。
arp_interval和arp_ip_target: 以一個固定的間隔向某些固定的地址發送arp,以監控鏈路。有些配置下,需要使用arp來監控鏈路,因爲這是一種三層的鏈路監控 ,使用網卡狀態或者鏈路層pdu監控只能監控到雙絞線兩端的接口 的健康情況,而監控不到到下一條路由器或者目的主機之間的全部鏈路的健康狀況。
primary: 表示優先權,順序排列,當出現某種選擇事件時,按照從前到後的順序選擇網口,比如802.3ad協議中的選擇行爲。
fail_over_mac: 對於熱備模式是否使用同一個mac地址,如果不使用一個mac的話,就要完全依賴免費arp機制更新其它機器的arp緩存了。比如,兩個有網卡,網卡1和網卡2處於熱備模式,網卡1的mac是mac1,網卡2的mac是mac2,網卡1一直是master,但是網卡1突然down掉了,此時需要網卡2接替,然而網卡2的mac地址與之前的網卡1不同,別的主機回覆數據包的時候還是使用網卡1的mac地址來回復的,由於mac1已經不在網絡上了,這就會導致數據包將不會被任何網卡接收。因此網卡2接替了master的角色之後,最好有一個回調事件,處理這個事件的時候,進行一次免費的arp廣播,廣播自己更換了mac地址。
lacp_rate: 發送802.3ad的LACPDU,以便對端設備自動獲取鏈路聚合的信息。
max_bonds: 初始時創建bond設備接口的數量,默認值是1。但是這個參數並不影響可以創建的最大的bond設備數量。
use_carrier: 使用MII的ioctl還是使用驅動獲取保持的狀態,如果是前者的話需要自己調用mii的接口進行硬件檢測,而後者則是驅動自動進行硬件檢測(使用watchdog或者定時器),bonding驅動只是獲取結果,然而這依賴網卡驅動必須支持狀態檢測,如果不支持的話,網卡的狀態將一直是on。
mode: 這個參數最重要,配置以什麼模式運行,這個參數在bond設備up狀態下是不能更改的,必須先down設備(使用ifconfig bondX down)纔可以配置,主要的有以下幾個:
1.balance-rr or 0: 輪轉方式的負載均衡模式,流量輪流在各個bondX的真實設備之間分發。注意,一定要用狀態檢測機制,否則如果一個設備down掉以後,由於沒有狀態檢測,該設備將一直是up狀態,仍然接受發送任務,這將會出現丟包。
2.active-backup or 1: 熱備模式。在比較高的版本中,免費arp會在切換時自動發送,避免一些故障,比如fail_over_mac參數描述的故障。
3.balance-xor or 2: 我不知道既然bonding有了xmit_hash_policy這個參數,爲何還要將之單獨設置成一種模式,在這個模式中,流量也是分發的,和輪轉負載不同的是,它使用源/目的mac地址爲自變量通過xor|mod函數計算出到底將數據包分發到哪一個口。
4.broadcast or 3: 向所有的口廣播數據,這個模式很XX,但是容錯性很強大。
5.802.3ad or 4: 這個就不多說了,就是以802.3ad的方式運行。
...
xmit_hash_policy: 這個參數的重要性我認爲僅次於mode參數,mode參數定義了分發模式 ,而這個參數定義了分發策略 ,文檔上說這個參數用於mode2和mode4,我覺得還可以定義更爲複雜的策略呢。
1.layer2: 使用二層幀頭作爲計算分發出口的參數,這導致通過同一個網關的數據流將完全從一個端口發送,爲了更加細化分發策略,必須使用一些三層信息,然而卻增加了計算開銷,天啊,一切都要權衡!
2.layer2+3: 在1的基礎上增加了三層的ip報頭信息,計算量增加了,然而負載卻更加均衡了,一個個主機到主機的數據流形成並且同一個流被分發到同一個端口,根據這個思想,如果要使負載更加均衡,我們在繼續增加代價的前提下可以拿到4層的信息。
3.layer3+4: 這個還用多說嗎?可以形成一個個端口到端口的流,負載更加均衡。然而且慢! 事情還沒有結束,雖然策略上我們不想將同一個tcp流的傳輸處理並行化以避免re-order或者re-transmit,因爲tcp本身就是一個串行協議,比如Intel的8257X系列網卡芯片都在儘量減少將一個tcp流的包分發到不同的cpu,同樣,端口聚合的環境下,同一個tcp流也應該使用本policy使用同一個端口發送,但是不要忘記,tcp要經過ip,而ip是可能要分段的,分了段的ip數據報中直到其被重組(到達對端或者到達一個使用nat的設備)都再也不能將之劃爲某個tcp流了。ip是一個完全無連接的協議,它只關心按照本地的mtu進行分段而不管別的,這就導致很多時候我們使用layer3+4策略不會得到完全滿意的結果。可是事情又不是那麼嚴重,因爲ip只是依照本地的mtu進行分段,而tcp是端到端的,它可以使用諸如mss以及mtu發現之類的機制配合滑動窗口機制最大限度減少ip分段,因此layer3+4策略,很OK!
miimon和arp: 使用miimon僅能檢測鏈路層的狀態,也就是鏈路層的端到端連接(即交換機某個口和與之直連的本地網卡口),然而交換機的上行口如果down掉了還是無法檢測到,因此必然需要網絡層的狀態檢測,最簡單也是最直接的方式就是arp了,可以直接arp網關,如果定時器到期網關還沒有回覆arp reply,則認爲鏈路不通了。
四、我該如何配置呢
1.首先,傳統的方式肯定不妥,內核文檔上寫的有,大家參考便是,記住,首先要裝一個ifenslave
2.最新的sysfs配置方式
首先確認你的系統上有sys這個目錄,並且mount於它的文件系統是sysfs類型的。然後就是下面的步驟了,很簡單:
第零步:加載模塊
root@zyxx:modprobe bonding
第一步:進入相應目錄
root@zyxx:cd /sys/class/net/
第二步:查看一下文件,熟悉一下地形(該步驟可省略)
root@zyxx:/sys/class/net# ls
bond0 bonding_masters  eth0  eth1  eth2  eth3  eth4  eth5  lo

第三步:看看當前有哪些bond設備
root@zyxx:/sys/class/net# cat bonding_masters
bond0

第四步:從一個bond設備添加或者刪除一個以太網卡設備
root@zyxx:/sys/class/net# echo +(-)X > bonding_masters 
#註釋:上一條命令中的+號表示添加設備,而-號表示刪除設備,+X中的X表示任意一個你喜歡的名字,-X中的X表示bonding_masters中已經存在的一個名字
第五步:進入新創建的bondMy,並且盡情配置吧
root@zyxx:/sys/class/net/bondMy/bonding# ls
active_slave   ad_num_ports    ad_select      arp_validate   lacp_rate   mode          primary  use_carrier
ad_actor_key   ad_partner_key  arp_interval   downdelay      miimon      num_grat_arp  slaves   xmit_hash_policy
ad_aggregator  ad_partner_mac  arp_ip_target  fail_over_mac  mii_status  num_unsol_na  updelay

1.增加eth2到bondMy
root@zyxx:/sys/class/net/bondMy/bonding# echo +eth2 > slaves
2.設置鏈路監控間隔
root@zyxx:/sys/class/net/bondMy/bonding# echo 100 > miimon
3.設置mode爲熱備
root@zyxx:/sys/class/net/bondMy/bonding# echo 1 > mode
...
第七步:感慨
整個配置步驟很簡單,模塊只需要加載一次,以後動態配置就一切OK了。
五、bonding驅動的實現
在看了精彩的配置並且實際上已經配置出一個很好用的網絡後,我肯定會急切的看一下源代碼的實現,這也是我喜歡linux的原因,因爲它可以讓你隨意patch。實際上bonding的驅動非常簡單,和tap一樣的,基本就是三大部分:
第一部分:初始化
這部分很簡單,就是初始化一個net_device,然後註冊進去,這就不多說了
第二部分:實現用戶配置接口
該接口有兩種,第一種就是傳統的基於ioctl的方式配置,就是實現一個ioctl即可,另一種就是通過sysfs實現,也很簡單,實現一些attitude的store/show方法即可,不管採用哪種方式,最終都要調用一個函數,那就是netdev_set_master,該函數中最重要的事就一個,那就是將物理網卡的master設置成我們在第一部分初始化的那個bond設備:
slave->master = master;
第三部分:初始化傳輸和接收接口
對於傳輸接口,很簡單,和多端口網橋類似。bond設備在初始化時將start_xmit初始化成bond_start_xmit,在bond_start_xmit中有一個switch-case,switch什麼呢?當然是bonding的mode了,比如在mode0,也就是輪轉負載的mode下,bond_start_xmit調用如下代碼段:
switch-case:bond_xmit_roundrobin...
bond_for_each_slave_from(bond, slave, i, start_at) {
    if (IS_UP(slave->dev) && (slave->link == BOND_LINK_UP) && (slave->state == BOND_STATE_ACTIVE)) {
        res = bond_dev_queue_xmit(bond, skb, slave->dev);
        break;
    }
}
對於接收接口,所有的設備都從netif_receive_skb開始數據包的協議分發,在該函數的開始處有下面代碼:
if (orig_dev->master) {
    if (skb_bond_should_drop(skb))
        null_or_orig = orig_dev;
    else
        skb->dev = orig_dev->master;
}
orig_dev是物理網卡,如果不出意外,在其擁有master的情況下,也就是使用用戶接口將物理網卡綁到一個bond設備上後,物理網卡的master字段將被設置,經過這段代碼,skb的dev將會被設置成master,也就是說bond設備。接下來的向上層協議的傳輸就由bond設備來完成了,因爲所有的三層信息完全都在bond設備上,而沒有在物理網卡orig_dev上。
六、高級主題
不可否認,本文的解析是極其淺顯的,如果想獲得更高級的主題和一些很深入細節的主題,或者想了解一些關於交換機的知識以及性能方面的問題,請參考Linux關於bonding的文檔,該文檔在$KERNEL-ROOT/Documentation/networking/bonding.txt,強烈建議閱讀,讀了該文檔之後,你就是bonding方面的專家啦。
附:談談配置方式
linux的sysfs和proc(sysctl等)機制可以實現文件io的方式配置系統參數和設備,回想當初學習思科/華爲設備配置時,花了上萬元RMB之後,最後的收穫就是隻會打“?”就OK了,後來隨着學習的深入,發現windows的netsh也是這種配置方式,甚至感覺比思科/華爲設備的配置方式更加簡單,但是歸根結底都是命令式的,所謂的命令式其實就是類似英語的,主-謂-賓(定-狀-補),主語就是系統的當前user,謂語就是命令,賓語就是目標設備或者系統本身,定狀補描述命令參數,這類命令對於習慣於自然語言的人們來講是很方便的,然而記憶的開銷卻不小。linux通過文件讀寫的方式可以實現類似的配置,不知道是好是壞,反正我在初用sysfs配置bonding的時候第一個感覺就是“好極了”,再也不用使用man ifenslave了,再也不用vim文檔了,kobject將bonding的相關內容組織在$sysfstoor/class/net/目錄下,只要你會訪問文件,你就能進行配置,而且bonding的新版本幾乎廢棄了原來的ifenslave的ioctl方式,任何關於bonding配置都可以用sysfs來進行。
     最重要的是,文件式或者netlink的配置相比ioctl式的經典命令式配置,可以減少設備vfs層的膨脹,我們可以看看ioctl層次的代碼,是多麼的凌亂不堪,每添加一個新命令就需要修改甚至好幾層ioctl的代碼,其實就是添加一個case語句,用以分發新添加的命令,這樣就會導致ioctl代碼雖然分層但是卻解決不了設備驅動和接口過度耦合的問題。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章