大腦與肌肉之間的那條線
上一篇給了我們軟體定義網路的大點子:把做決策的控制平面(control plane)從每一台交換器裡抽出來,裝進一個邏輯上集中、又擁有全網視野的控制器裡,讓每台交換器只當一個又快又笨、單純搬運封包的資料平面(data plane)。這就帶出一個我們含糊帶過的明顯問題:如果大腦住在控制器裡、肌肉住在交換器裡,那麼在它們之間那條線上,究竟有什麼東西在傳?控制器到底對交換器說了些什麼?
承載這段對話的協定,叫做南向 API(southbound API)——說「南」是因為在常見的圖裡,控制器坐在上頭、交換器掛在它下面。最有名的南向協定、也是掀起整場 SDN 運動的那一個,就是 OpenFlow。而 OpenFlow 真正在做的事,只有一件:讓控制器能夠寫入、讀取,並收到通知——對象是每台交換器裡的一個資料結構:一張比對—動作表(match-action table),又叫流量表(flow table)。這篇剩下的內容,全都只是在拆解這一張表而已。
一條流量表項目到底寫了什麼
把流量表想成一份接待員照著走的「如果—就」規則清單,由上往下讀。每一列,叫做一條流量項目(flow entry),有三個部分。第一是比對(match)樣式:一組要去看的標頭欄位,你可以把某個欄位釘死成確切的值,也可以留成萬用字元,意思是「不在乎」。第二是一串動作(action):對任何比對中的封包要做什麼——從第 3 埠轉送出去、丟掉、改寫某個欄位、往上送給控制器,諸如此類。第三是一個優先序(priority),外加一些計數器(counter),每當這條規則被觸發就往上加,這正是控制器之後用來看見交換器一直在做什麼的方式。
比對欄位橫切過你先前學的那套分層,而這正是重點所在。某條項目可以比對一個 MAC 位址(第二層的概念),下一條比對一個 IPv4 位址(第三層),再下一條比對一個 TCP 埠號(第四層)。對 OpenFlow 來說,這些只不過是封包那層層相套的標頭裡、位於已知偏移位置上的一段段位元組。一台典型的 OpenFlow 交換器,可以一次比對大約十來個這樣的欄位——輸入埠、來源與目的 MAC、來源與目的 IP、協定、來源與目的埠號,再加上幾個。所以單一規則可以寬到「任何封包都算」,也可以窄到「從這一台主機、送往那一台主機、走 443 埠的 TCP 流量」。
FLOW TABLE (read top to bottom; first match wins) prio match action packets ---- ------------------------------------- -------------- ------- 200 in_port=1, dst_ip=10.0.0.7, tcp_dst=80 output:3 14823 100 dst_ip=10.0.0.0/24 output:2 391 50 eth_type=ARP output:CONTROLLER 6 0 * (wildcard: matches anything) output:CONTROLLER 17
追一個封包走過這張表
整個資料平面的演算法就在這裡,而且簡單到近乎難為情。一個封包到達。交換器拿它去和各條流量項目比對,並在所有比中的規則裡,挑出優先序最高的那一條。(如果你 IP 章節裡的最長前綴比對還記得住,這就是同一種味道的「最明確者勝」,只是推廣到了任何欄位上。)接著它執行那條項目的動作、把該項目的計數器加一,然後處理下一個封包。沒有路徑計算、沒有路由協定的你來我往、沒有思考——就只是比對再動作,每秒幾百萬次,跑在硬體裡。
- 一個封包從第 1 埠進來,目的地是 IP 10.0.0.7 的 TCP 80 埠。交換器讀出它的標頭欄位。
- 它掃過流量表找比對。優先序 200 的規則和優先序 100 的規則都比中了這個封包;那條全萬用字元規則也會比中。
- 優先序最高者勝出,於是選中優先序 200 的項目。它的動作寫著 output:3,所以封包從第 3 埠送出去。
- 交換器把那條項目的計數器加一(從 14823 變成 14824)就完成了。總共做的工:一次查表加一次轉送——不需要動用控制器。
注意這如何重新框定了轉送表這個老概念。傳統路由器是自己建出它的轉送表的,靠的是燒進盒子裡的某個路由協定。OpenFlow 交換器的流量表裝著同一類「哪個封包走哪個埠」的知識,但交換器本身一點也沒去算它——每一條項目都是控制器從上頭寫進來的。肌肉仍然全速搬運封包;只是它再也不決定封包該往哪去了。
當什麼都比不中的時候
最有意思的一刻,是一條全新流量的第一個封包——比方說某條交換器從沒見過的全新連線的最頭一個封包。如果沒有任何明確規則比中,它就一路落到底下那條兜底項目,而那條的動作是「送給控制器」。交換器把封包包起來,透過 OpenFlow 往北送出去,成為一則 packet-in 訊息:實際上等於在說:「這個我不知道該怎麼辦——你來決定。」這是慢速的大腦唯一一次去插手某個別封包的時候,也是 SDN 網路如何學習的核心。
控制器憑著它的全網視野,決定像這樣的封包該怎麼處理。接著它做了件聰明事:與其去保姆般照看這條流量未來的每個封包,它往下回送一則 flow-mod 訊息,要交換器裝上一條新的流量項目。從此以後,這條流量後面的每個封包都會比中那條剛裝好的規則,以硬體速度被處理掉,再也碰不到控制器。所以控制器在逐封包層級的介入,只在最前頭付一次帳,然後攤平到整條流量上。
這揭露了兩種控制風格。在「反應式(reactive)」模式裡,交換器一開始是空的,每當一條新流量出現就向控制器詢問,正如剛才所述——很靈活,但每條流量的最頭一個封包都得吃掉一趟控制器的來回延遲。在「主動式(proactive)」模式裡,控制器在任何流量到達之前就把流量表預先載好,就像細心的主人在客人現身前先把每張椅子都擺好,於是封包永遠不會比不中,控制器也永遠不在關鍵路徑上。真實部署會把兩者混用:對大宗的預期流量用主動式規則,對意外狀況用反應式處理。
往上、往下,還有那個瓶頸
我們一直在看南向 API——控制器往下對交換器說話。但維運者並不想用流量項目來思考,就像駕駛不想用火星塞來思考一樣。所以在控制器之上坐著北向 API:這是應用程式和網路工程師用來表達他們想要什麼的介面——「把訪客網路和財務部門隔開」、「把影片送上負載較輕的路徑」——然後讓控制器把這些意圖往下翻譯成正確的一堆低階流量項目。南向是控制器指揮肌肉的方式;北向是人類與應用程式指揮控制器的方式。
好處是實打實的。因為流量表是由帶著全域視野的軟體寫出來的,你得到了靈活性(可以用任何你喜歡的欄位組合來轉送,而不只是目的 IP),也得到了能見度(那些逐規則的計數器,給了控制器一幅即時、全網的每條流量畫面)。想要一套新的轉送政策?推下新的項目就好——不用新協定,也不用在每台盒子上刷韌體。這也是為什麼便宜、不綁廠商的白牌交換器(white-box switch)變得可行:當腦子住在控制器裡,交換器只需要把比對—動作跑得快,所以普通的商用矽晶硬體就夠用了。