理解 Net Device Ingress 和 Egress 雙重角色
本文是書稿《圖解 VPC & K8s 網(wǎng)絡(luò)模型》其中一篇。書稿還在繼續(xù)寫,進(jìn)度不快也不慢,因?yàn)槎绮患币膊辉?。好肉需要慢燉,好書需要多磨?/span>
為什么要單獨(dú)講這個(gè)話題呢?因?yàn)槲以诤屯掠懻?K8s 網(wǎng)絡(luò)尤其是網(wǎng)絡(luò)數(shù)據(jù)流向的時(shí)候,會(huì)反復(fù)提及到網(wǎng)絡(luò)設(shè)備,無(wú)論它是物理的還是虛擬的。而網(wǎng)絡(luò)設(shè)備在我們所討論到的數(shù)據(jù)流場(chǎng)景里,時(shí)而在接收數(shù)據(jù),時(shí)而在發(fā)送數(shù)據(jù)。也就是說(shuō)它同時(shí)扮演著雙重身份:Ingress 和 Egress。
另外我在整理 eBPF 相關(guān)的內(nèi)容,尤其是 tc eBPF 的時(shí)候,再一次發(fā)現(xiàn)如果不能準(zhǔn)確地在數(shù)據(jù)流中識(shí)別出網(wǎng)絡(luò)設(shè)備是 Ingress 還是 Egress ,就無(wú)法將代碼邏輯和實(shí)際運(yùn)行結(jié)果對(duì)上號(hào),更勿談能理解tc eBPF 了。
這樣的雙重角色扮演就如同一個(gè)調(diào)皮的孩子,總是帶上面具在錯(cuò)綜復(fù)雜的網(wǎng)絡(luò)里面東躲西藏,肆意玩耍。而當(dāng)你好不容易抓到它時(shí),卻讓你猜猜此時(shí)此刻他是誰(shuí)。
簡(jiǎn)單來(lái)說(shuō):對(duì)于網(wǎng)卡而言,無(wú)論它是物理的還是虛擬的,對(duì)于 Ingress 角色,它是首先觸碰到數(shù)據(jù)的人,而對(duì)于 Egress 角色,它是最后一個(gè)碰到數(shù)據(jù)的人。
本文我們從一個(gè)簡(jiǎn)單的物理網(wǎng)卡開始,然后對(duì) veth、 bridge 還有 tc eBPF ,分別展開聊聊:
- 當(dāng)網(wǎng)卡扮演 Ingress 角色時(shí),它從哪里接收數(shù)據(jù),又將數(shù)據(jù)遞交給了誰(shuí)?
- 當(dāng)網(wǎng)卡扮演 Egress 角色時(shí),它從哪里接收數(shù)據(jù),又將數(shù)據(jù)遞交給了誰(shuí)?
1、單個(gè)物理網(wǎng)卡
圖1
這是一個(gè)簡(jiǎn)單的圖,圖中有一張物理網(wǎng)卡。我們的臺(tái)式機(jī)通常是這樣的配置。橘色的線代表著輸入流程,而藍(lán)色的線表示輸出流程。
(1)輸入流程 Ingress
對(duì)于這張網(wǎng)卡而言,輸入過(guò)程伴隨著以下幾個(gè)重要的事情:物理網(wǎng)卡首先接收到物理信號(hào) -> 物理網(wǎng)卡通過(guò) DMA 機(jī)制將數(shù)據(jù)保存至其專屬的 RingBuffer 里面 -> 向 CPU 發(fā)起中斷 -> OS kernel thread ksoftirqd/x 不斷地消費(fèi) RingBuffer 里面的數(shù)據(jù)。
這里的 ksoftirqd 是一個(gè)內(nèi)核線程,每個(gè) CPU 一個(gè),x 為 CPU 編號(hào)。如 ksoftirqd/0 為 0 號(hào) CPU 上運(yùn)行的內(nèi)核線程。
ksoftirqd/x 將數(shù)據(jù)以 skb 為處理粒度依次穿過(guò)鏈路層、網(wǎng)絡(luò)層、TCP/UDP 傳輸層 。不過(guò) skb 在鏈路層和網(wǎng)絡(luò)層還可能直接 forward 給其它網(wǎng)卡,那這樣的話傳輸層就不會(huì)收到這個(gè) skb 了。
整個(gè)過(guò)程如圖 2 所示,你可以從整體上感受一下。標(biāo)號(hào) 1 及 1.x 為數(shù)據(jù)輸入和生產(chǎn)過(guò)程,這是本文的重點(diǎn)。而標(biāo)號(hào) 3 為數(shù)據(jù)消費(fèi)過(guò)程,它帶著 skb 從入口處的 net_rx_action() 沿著協(xié)議棧由底向上穿越協(xié)議棧,這個(gè)過(guò)程對(duì)本文所述的所有 Ingress 場(chǎng)景都是通用的,故后文不再贅述這部分。
圖2
總結(jié):當(dāng)物理網(wǎng)卡扮演 Ingress 角色時(shí),它從主機(jī)外接收數(shù)據(jù),將數(shù)據(jù)遞交給了環(huán)形隊(duì)列,然后由 ksoftirqd/x 進(jìn)行后續(xù)的處理,這個(gè)處理過(guò)程也稱為網(wǎng)絡(luò)棧下半部分。
(2)輸出過(guò)程 Egress
從圖 1 中,我們大致可以看出來(lái),對(duì)于輸出過(guò)程,數(shù)據(jù)來(lái)源有兩種,分別是通過(guò) ip_forward() 過(guò)程和通過(guò) ip_local_out() 過(guò)程送過(guò)來(lái)的數(shù)據(jù)。我們還會(huì)發(fā)現(xiàn),在發(fā)送數(shù)據(jù)的路徑上,這兩個(gè)過(guò)程只是起點(diǎn)有些不同,剩下的路程大家都一樣。
ip_forward() 過(guò)程與 skb 在 IP 層路由結(jié)果強(qiáng)相關(guān)。如圖 3 所示,具體來(lái)說(shuō)經(jīng)過(guò)路由的判定,可能需要把 skb forward 至本機(jī)網(wǎng)絡(luò)設(shè)備或者網(wǎng)絡(luò)中的其它主機(jī)處理,不過(guò)無(wú)論是哪種情況,都需要將 skb 送往本機(jī)的一個(gè)網(wǎng)絡(luò)設(shè)備。
圖3
而 ip_local_out() 過(guò)程則對(duì)應(yīng)了本機(jī)進(jìn)程通過(guò) socket 發(fā)送數(shù)據(jù)的場(chǎng)景,如圖 4 所示。這張圖最后標(biāo)注的“觸發(fā) NET_RX 類型軟中斷”是數(shù)據(jù)已經(jīng)被網(wǎng)卡發(fā)送完后發(fā)生的事情,中斷的目的是為了清理 skb ,略過(guò)不表。
圖 4 ,圖片來(lái)源:“開發(fā)內(nèi)功修煉”公眾號(hào)
總結(jié):當(dāng)物理網(wǎng)卡扮演 Egress 角色時(shí),它從本機(jī) TCP/IP 協(xié)議棧接收數(shù)據(jù),將數(shù)據(jù)通過(guò)驅(qū)動(dòng)程序送離本機(jī)。
2、veth-pair
是不是覺(jué)得單個(gè)網(wǎng)卡的場(chǎng)景其實(shí)很容易分辨出來(lái) Ingress 和 Egress ?
別得意,我們來(lái)加點(diǎn)難度。我們知道 K8s 的默認(rèn) CNI flannel 用到了 veth 。veth 是什么以及它的特性二哥就不細(xì)說(shuō)了。我們聊一個(gè)話題:圖 5 中,當(dāng)左側(cè)進(jìn)程向右側(cè)進(jìn)程通信發(fā)送數(shù)據(jù)時(shí), 左端的 veth_left 是 Ingress 還是 Egress ? 右端的 veth_right 呢?
圖 5
結(jié)合圖 5 上的箭頭示意,答案應(yīng)該不難猜。對(duì) veth_left 來(lái)說(shuō),它扮演的是 Egress 角色,因?yàn)檫M(jìn)程需要通過(guò)它把數(shù)據(jù)發(fā)送出去。對(duì) veth_right 而言是 Ingress ,因?yàn)樗枰?fù)責(zé)接收數(shù)據(jù)并把它送給右側(cè)的進(jìn)程。
下一個(gè)問(wèn)題:既然 veth_left 扮演了 Egress 角色,流量從離開 network namespace 1 之后去哪里了?既然 veth_right 是 Ingress ,那它從哪里接收到流量的?
答案都在圖 6 里面。圖中標(biāo)號(hào) 2 及 2.x 在進(jìn)行數(shù)據(jù)發(fā)送的工作,都屬于 veth_left 的 Egress 的過(guò)程,這個(gè)過(guò)程是發(fā)生在 network namespace 1 里面的,函數(shù)調(diào)用棧和圖 4 一樣。而標(biāo)號(hào) 3 為數(shù)據(jù)消費(fèi)也即 veth_right 的 Ingress 過(guò)程,這個(gè)過(guò)程和物理網(wǎng)卡一模一樣。
圖 6
總結(jié):既然 veth 是一對(duì)虛擬網(wǎng)卡,那我們把對(duì)它倆的總結(jié)放在一起。
當(dāng) veth 網(wǎng)卡扮演 Egress 角色時(shí),如圖 7 中的 veth_left,它從其所在的 network namespace TCP/IP 協(xié)議棧接收數(shù)據(jù),并將數(shù)據(jù)遞交給了 per CPU input_pkt_queue 隊(duì)列,并觸發(fā)軟件中斷。
當(dāng) veth 網(wǎng)卡扮演 Ingress 角色時(shí),如圖 7 中的 veth_right,它并沒(méi)有物理網(wǎng)卡那種環(huán)形隊(duì)列,而是由 ksoftirqd/x 直接從 per CPU input_pkt_queue 隊(duì)列讀取 veth_left 塞進(jìn)來(lái)的數(shù)據(jù)。
veth_left 和 veth_right 共享了同一個(gè) queue 。典型的生產(chǎn)者 / 消費(fèi)者設(shè)計(jì)模式的即視感有沒(méi)有?
圖 7
3、bridge
上一節(jié),二哥把 veth pair 單獨(dú)拿出來(lái)和大家一起觀賞??伤鼈兘K究不是花瓶,它們被創(chuàng)造出來(lái)是要有實(shí)際使用價(jià)值的。veth 典型使用場(chǎng)景就是把一端插入到 bridge 里面,如圖 8 所示。
從 veth 的特性來(lái)說(shuō),流量從下圖 veth1-left 流出后,會(huì)進(jìn)入 veth1-right ,這也就意味著流量進(jìn)入了網(wǎng)橋。
我想這個(gè)時(shí)候你可以確定 veth1-left 是 Egress ,而 veth1-right 是 Ingress 。那么對(duì)于 bridge 的 Port 1 和 Port 2 呢?再進(jìn)一步,對(duì)于 veth2-left 和 veth2-right 呢 ?
圖 8
其實(shí)對(duì)于 bridge 這種虛擬的網(wǎng)橋,它的 port 口也是一個(gè)虛擬的概念,說(shuō)得更直白一點(diǎn),在內(nèi)核里它就是一個(gè)數(shù)據(jù)結(jié)構(gòu):struct net_bridge_port 。這個(gè)結(jié)構(gòu)里有 3 個(gè)重要的成員:br / port_no / dev 。下面的代碼用于插入網(wǎng)絡(luò)設(shè)備到 bridge ,這 3 個(gè)成員的作用顯而易見。
對(duì)于圖 8 來(lái)說(shuō), Port 1 (net_bridge_port) 就是一個(gè)粘合劑,左手 bridge ,右手 veth1-right 。理解了這點(diǎn)也就明白了對(duì)于 bridge 的 Port 而言,它是沒(méi)有所謂的 Ingress 和 Egress 的概念的。
Port 1 接收數(shù)據(jù)其實(shí)是 veth1-right 在 Ingress,而 bridge 把這個(gè)流量 forward 給 veth2-right 時(shí),veth2-right 其實(shí)在扮演 Egress 角色。那流量從 veth2-right 傳至 veth2-left 的過(guò)程和 veth1-left 向 veth1-right 發(fā)送數(shù)據(jù)的過(guò)程是完全一樣的。
總結(jié):當(dāng) veth 這樣的虛擬網(wǎng)卡插入在 bridge 上時(shí):
圖8 中 veth1-left 扮演 Egress 角色,它從其所在的 network namespace TCP/IP 協(xié)議棧接收數(shù)據(jù),將數(shù)據(jù)遞交給了 per CPU input_pkt_queue 隊(duì)列,并觸發(fā)軟件中斷。
veth1-right 扮演 Ingress 角色,它并沒(méi)有物理網(wǎng)卡那種環(huán)形隊(duì)列,而是由 ksoftirqd/x 直接從 per CPU input_pkt_queue 隊(duì)列讀取 veth1-left 塞進(jìn)來(lái)的數(shù)據(jù)。當(dāng) ksoftirqd/x 把流量送至鏈路層時(shí),從 br_forward() 開始進(jìn)入 forward 流程。這個(gè)流程的效果就是流量從 veth1-right 轉(zhuǎn)至 veth2-right 發(fā)送出去了。
那自然 veth2-right 這個(gè)時(shí)候就扮演了 Egress 角色,veth2-left 扮演了 Ingress 角色。
4、veth-pair plus
如果你沒(méi)有暈的話,那抖索一下精神,我們開始 veth-pair 的進(jìn)階版。
上一節(jié)我們看到 veth 和 bridge 搭配使用的場(chǎng)景。veth 另一端一定要插在 bridge 上嗎?從圖 9 你也看到了,答案是:不一定。
圖 9
現(xiàn)在我們知道,在圖 9 中,從 container-1 發(fā)出的流量經(jīng)過(guò) veth 發(fā)出后,veth-p 會(huì)以 Ingress 角色開始接收。根據(jù)前文的解釋,當(dāng)網(wǎng)卡進(jìn)行 Ingress 時(shí),流量會(huì)被 ksoftirqd/x 送往協(xié)議棧進(jìn)一步處理。這個(gè)處理的過(guò)程當(dāng)然也就包括了圖 9 中的路由過(guò)程。
圖 9 這里巧妙的地方是:流量是產(chǎn)生于容器內(nèi),但對(duì)這份流量的路由卻發(fā)生在主機(jī) root(default) network namespance 里面,使用的也是主機(jī)的路由表。如果路由結(jié)果發(fā)現(xiàn)需要把這份流量發(fā)往其它主機(jī),那自然流量就從主機(jī)的 eth0 這個(gè)網(wǎng)卡設(shè)備離開了。在這個(gè)過(guò)程中,主機(jī)其實(shí)扮演了網(wǎng)關(guān)的角色。
說(shuō)到這里,你能理解圖 10 的工作過(guò)程了嗎?它是 K8s host-gateway 網(wǎng)絡(luò)模型,顧名思義,這種網(wǎng)絡(luò)模型以 host 為 gateway ,更具體地說(shuō),host 的 root network namespace 充當(dāng)了路由的角色。
圖 10
5、tc eBPF
如果你對(duì) tc 和 eBPF 了解得不多或者不感興趣,可以跳過(guò)這部分。
以 Cilium 為代表的 K8s CNI 提供商一直在嘗試使用 eBPF 代替 iptables 以便優(yōu)化服務(wù)網(wǎng)格數(shù)據(jù)面性能。其中 bpf_redicrect() 函數(shù)即為其中一項(xiàng)優(yōu)化產(chǎn)出。
bpf_redicrect() 函數(shù)的特性用一句話就能解釋清楚:當(dāng) veth Ingress 時(shí),將流量直接通過(guò) bpf_redirect() 重定向到另一個(gè) veth Ingress。如圖 11 所示。
不過(guò)如果你對(duì) veth pair 哪一端在何時(shí)會(huì)扮演 Ingress 角色了解得不是很清楚的話,上面這句話其實(shí)會(huì)把你繞暈。
圖 11
但在看完二哥這篇文章后,希望你不會(huì)再暈了。在圖 11 中,從右下 Pod 出來(lái)的流量會(huì)流到位于 host network ns 這一端的 veth 上,這個(gè) veth 是以 Ingress 角色工作的。
你看到在它身上附上了一個(gè) eBPF 小蜜蜂圖標(biāo),表示這個(gè)時(shí)候 eBPF 程序會(huì)介入執(zhí)行,執(zhí)行的結(jié)果就是流量被直接通過(guò) dev_forward_skb() forward 給了另外一個(gè)同樣位于 host network ns 端的 veth (如圖 11 箭頭所指的那個(gè) veth),當(dāng)然對(duì)這個(gè) veth 而言,它會(huì)扮演 Egress 角色。
這個(gè)過(guò)程也可以用下面這樣的函數(shù)調(diào)用層次圖來(lái)表示。
下面是網(wǎng)易輕舟的一篇文章里面所附的圖。它畫出了網(wǎng)易輕舟基于 Cilium 網(wǎng)絡(luò)方案的探索和實(shí)踐細(xì)節(jié),包括跨節(jié)點(diǎn) Pod 間通信、同節(jié)點(diǎn) Pod 間通信、Pod 訪問(wèn)外網(wǎng)等各類常見的場(chǎng)景。
圖中 cilium_net/cilium_host? 是一對(duì) veth pair ,它們?cè)?Kernel 4.19? + Cilium ?1.8? 部署中已經(jīng)沒(méi)什么作用了(事實(shí)上社區(qū)在考慮去掉它們)。
我想有了上面所有的鋪墊和知識(shí),至少對(duì)標(biāo)號(hào) ① ② 所示的流程,你應(yīng)該能看得懂了。
圖 12
6、總結(jié)
文末,二哥來(lái)做個(gè)小總結(jié):
- 對(duì)于網(wǎng)卡而言,無(wú)論它是物理的還是虛擬的,對(duì)于 Ingress 角色,它是首先觸碰到數(shù)據(jù)的人,而對(duì)于 Egress 角色,它是最后一個(gè)碰到數(shù)據(jù)的人。
- 對(duì)于 Ingress 過(guò)程,無(wú)論是物理網(wǎng)卡還是虛擬網(wǎng)卡,在它接收到數(shù)據(jù)后,總是通過(guò) ksoftirqd/x 進(jìn)行網(wǎng)絡(luò)棧下半部分處理。
- 對(duì)于 Egress 過(guò)程,無(wú)論是物理網(wǎng)卡還是虛擬網(wǎng)卡,在它從鏈路層那里拿到數(shù)據(jù)后,總是通過(guò)網(wǎng)卡驅(qū)動(dòng)程序?qū)?shù)據(jù)送離本設(shè)備。
- ?Ingress 過(guò)程和 Egress 過(guò)程在內(nèi)核中的處理路徑完全不同,也更無(wú)對(duì)稱可言。
- 對(duì)于 veth ,無(wú)論是搭配 bridge 還是用于 host-gateway 場(chǎng)景,在數(shù)據(jù)流經(jīng)過(guò)的關(guān)鍵位置區(qū)分是 Ingress 還是 Egress ,有助于理解系統(tǒng)對(duì)數(shù)據(jù)流的處理行為。
- tc eBPF 強(qiáng)依賴 Ingress 和 Egress,理解了它們也才能更好地理解 bpf_redirect() 。