操屁眼的视频在线免费看,日本在线综合一区二区,久久在线观看免费视频,欧美日韩精品久久综

新聞資訊

    文需要讀者熟悉 Ethernet(以太網(wǎng))的基本原理和 Linux 系統(tǒng)的基本網(wǎng)絡(luò)命令,以及 TCP/IP 協(xié)議族并了解傳統(tǒng)的網(wǎng)絡(luò)模型和協(xié)議包的流轉(zhuǎn)原理。文中涉及到 Linux 內(nèi)核的具體實(shí)現(xiàn)時,均以內(nèi)核 v4.19.215 版本為準(zhǔn)。

    一 內(nèi)核網(wǎng)絡(luò)包接收流程

    1 從網(wǎng)卡到內(nèi)核協(xié)議棧

    如圖[1],網(wǎng)絡(luò)包到達(dá) NC(Network Computer,本文指物理機(jī))時,由 NIC(Network Interface Controller,網(wǎng)絡(luò)接口控制器,俗稱網(wǎng)卡)設(shè)備處理,NIC 以中斷的方式向內(nèi)核傳遞消息。Linux 內(nèi)核的中斷處理分為上半部(Top Half)和下半部(Bottom Half)。上半部需要盡快處理掉和硬件相關(guān)的工作并返回,下半部由上半部激活來處理后續(xù)比較耗時的工作。

    具體到 NIC 的處理流程如下:當(dāng) NIC 收到數(shù)據(jù)時,會以 DMA 方式將數(shù)據(jù)拷貝到 Ring Buffer (接收隊列) 里描述符指向的映射內(nèi)存區(qū)域,拷貝完成后會觸發(fā)中斷通知 CPU 進(jìn)行處理。這里可以使用 ethtool -g {設(shè)備名,如eth0} 命令查看 RX/TX (接收/發(fā)送)隊列的大小。CPU 識別到中斷后跳轉(zhuǎn)到 NIC 的中斷處理函數(shù)開始執(zhí)行。此時要區(qū)分 NIC 的工作模式,在早先的非 NAPI(New API)[2]模式下,中斷上半部更新相關(guān)的寄存器信息,查看接收隊列并分配 sk_buff 結(jié)構(gòu)指向接收到的數(shù)據(jù),最后調(diào)用 netif_rx() 把 sk_buff 遞交給內(nèi)核處理。在 netif_rx() 的函數(shù)的流程中,這個分配的 sk_buff 結(jié)構(gòu)被放入 input_pkt_queue隊列后,會把一個虛擬設(shè)備加入poll_list 輪詢隊列并觸發(fā)軟中斷 NET_RX_SOFTIRQ 激活中斷下半部。此時中斷上半部就結(jié)束了,詳細(xì)的處理流程可以參見 net/core/dev.c 的 netif_rx() -> netif_rx_internal() -> enqueue_to_backlog() 過程。下半部 NET_RX_SOFTIRQ 軟中斷對應(yīng)的處理函數(shù)是 net_rx_action(),這個函數(shù)會調(diào)用設(shè)備注冊的 poll() 函數(shù)進(jìn)行處理。非 NAPI 的情況下這個虛擬設(shè)備的 poll() 函數(shù)固定指向 process_backlog() 函數(shù)。這個函數(shù)將 sk_buff 從 input_pkt_queue 移動到 process_queue 中,調(diào)用 __netif_receive_skb() 函數(shù)將其投遞給協(xié)議棧,最后協(xié)議棧相關(guān)代碼會根據(jù)協(xié)議類型調(diào)用相應(yīng)的接口進(jìn)行后續(xù)的處理。特別地,這里的 enqueue_to_backlog() 以及 process_backlog() 函數(shù)也用于和啟用了 RPS 機(jī)制后的相關(guān)邏輯。

    非 NAPI(New API)模式下每個網(wǎng)絡(luò)包的到達(dá)都會觸發(fā)一次中斷處理流程,這么做降低了整體的處理能力,已經(jīng)過時了。現(xiàn)在大多數(shù) NIC 都支持 NAPI 模式了。NAPI 模式下在首包觸發(fā) NIC 中斷后,設(shè)備就會被加入輪詢隊列進(jìn)行輪詢操作以提升效率,輪詢過程中不會產(chǎn)生新的中斷。為了支持 NAPI,每個 CPU 維護(hù)了一個叫 softnet_data 的結(jié)構(gòu),其中有一個 poll_list 字段放置所有的輪詢設(shè)備。此時中斷上半部很簡單,只需要更新 NIC 相關(guān)的寄存器信息,以及把設(shè)備加入poll_list 輪詢隊列并觸發(fā)軟中斷 NET_RX_SOFTIRQ就結(jié)束了。中斷下半部的處理依舊是 net_rx_action() 來調(diào)用設(shè)備驅(qū)動提供的 poll() 函數(shù)。只是 poll() 此時指向的就是設(shè)備驅(qū)動提供的輪詢處理函數(shù)了(而不是非 NAPI 模式下的內(nèi)核函數(shù) process_backlog())。這個設(shè)備驅(qū)動提供的輪詢 poll() 函數(shù)最后也會調(diào)用 __netif_receive_skb() 函數(shù)把 sk_buff 提交給協(xié)議棧處理。

    非 NAPI 模式和 NAPI 模式下的流程對比如下(其中灰色底色是設(shè)備驅(qū)動要實(shí)現(xiàn)的,其他都是內(nèi)核自身的實(shí)現(xiàn)):

    關(guān)于 NAPI 模式網(wǎng)絡(luò)設(shè)備驅(qū)動的實(shí)現(xiàn)以及詳細(xì)的 NAPI 模式的處理流程,這里提供一篇文章和其譯文作為參考[3](強(qiáng)烈推薦)。這篇文章很詳細(xì)的描述了 Intel Ethernet Controller I350 這個 NIC 設(shè)備的收包和處理細(xì)節(jié)(其姊妹篇發(fā)包處理過程和譯文[4])。另外收包這里還涉及到多網(wǎng)卡的 Bonding 模式(可以在/proc/net/bonding/bond0 里查看模式)、網(wǎng)絡(luò)多隊列(sudo lspci -vvv 查看 Ethernet controller 的 Capabilities信息里有 MSI-X: Enable+ Count=10 字樣說明 NIC 支持,可以在 /proc/interrupts 里查看中斷綁定情況)等機(jī)制。這些本文都不再贅述,有興趣的話請參閱相關(guān)資料[5]。

    2 內(nèi)核協(xié)議棧網(wǎng)絡(luò)包處理流程

    前文說到 NIC 收到網(wǎng)絡(luò)包構(gòu)造出的 sk_buff 結(jié)構(gòu)最終被 __netif_receive_skb() 提交給了內(nèi)核協(xié)議棧解析處理。這個函數(shù)首先進(jìn)行 RPS[5] 相關(guān)的處理,數(shù)據(jù)包會繼續(xù)在隊列里轉(zhuǎn)一圈(一般開啟了 RSS 的網(wǎng)卡不需要開啟 RPS)。如果需要分發(fā)包到其他 CPU 去處理,則會使用 enqueue_to_backlog() 投遞給其他 CPU 的隊列,并在 process_backlog()) 中觸發(fā) IPI(Inter-Processor Interrupt,處理器間中斷,于 APIC 總線上傳輸,并不通過 IRQ)給其他 CPU 發(fā)送通知(net_rps_send_ipi()函數(shù))。

    最終,數(shù)據(jù)包會由 __netif_receive_skb_core() 進(jìn)行下一階段的處理。這個處理函數(shù)主要的功能有:

    • 處理ptype_all 上所有的 packet_type->func(),典型場景是 tcpdump 等工具的抓包回調(diào)(paket_type.type 為 ETH_P_ALL,libcap 使用 AF_PACKET Address Family)
    • 處理 VLAN(Virtual Local Area Network,虛擬局域網(wǎng))報文 vlan_do_receive() 以及處理網(wǎng)橋的相關(guān)邏輯(skb->dev->rx_handler() 指向了 br_handle_frame())
    • 處理 ptype_base上所有的 packet_type->func() , 將數(shù)據(jù)包傳遞給上層協(xié)議層處理,例如指向 IP 層的回調(diào) ip_rcv() 函數(shù)

    截至目前,數(shù)據(jù)包仍舊在數(shù)據(jù)鏈路層的處理流程中。這里復(fù)習(xí)下 OSI 七層模型與 TCP/IP 五層模型:

    在網(wǎng)絡(luò)分層模型里,后一層即為前一層的數(shù)據(jù)部分,稱之為載荷(Payload)。一個完整的 TCP/IP 應(yīng)用層數(shù)據(jù)包的格式如下[6]:

    __netif_receive_skb_core() 的處理邏輯中需要關(guān)注的是網(wǎng)橋和接下來 IP 層以及 TCP/UDP 層的處理。首先看 IP 層,__netif_receive_skb_core() 調(diào)用 deliver_skb(),后者調(diào)用具體協(xié)議的 .func() 接口。對于 IP 協(xié)議,這里指向的是 ip_rcv() 函數(shù)。這個函數(shù)做了一些統(tǒng)計和檢查之后,就把包轉(zhuǎn)給了 Netfilter [7]框架并指定了函數(shù) ip_rcv_finish() 進(jìn)行后續(xù)的處理(如果包沒被 Netfilter 丟棄)。經(jīng)過路由子系統(tǒng)檢查處理后,如果包是屬于本機(jī)的,那么會調(diào)用 ip_local_deliver() 將數(shù)據(jù)包繼續(xù)往上層協(xié)議轉(zhuǎn)發(fā)。這個函數(shù)類似之前的邏輯,依舊是呈遞給 Netfilter 框架并指定函數(shù) ip_local_deliver_finish() 進(jìn)行后續(xù)的處理,這個函數(shù)最終會檢查和選擇對應(yīng)的上層協(xié)議接口進(jìn)行處理。

    常見的上層協(xié)議比如 TCP 或者 UDP 協(xié)議的流程不在本文討論的范圍內(nèi),僅 TCP 的流程所需要的篇幅足以超過本文所有的內(nèi)容。這里給出 TCP 協(xié)議(v4)的入口函數(shù) tcp_v4_rcv() 以及 UDP 協(xié)議的入口函數(shù) udp_rcv() 作為指引自行研究,也可以閱讀其他的資料進(jìn)行進(jìn)一步的了解[9]。

    3 Netfilter/iptables 與 NAT(網(wǎng)絡(luò)地址轉(zhuǎn)換)

    關(guān)于 Netfilter 框架需要稍微著重的強(qiáng)調(diào)一下,因為后文要提到的網(wǎng)絡(luò)策略和很多服務(wù)透出的實(shí)現(xiàn)都要使用 Netfilter 提供的機(jī)制。

    Netfilter 是內(nèi)核的包過濾框架(Packet Filtering Framework)的實(shí)現(xiàn)。簡單說就是在協(xié)議棧的各個層次的包處理函數(shù)中內(nèi)置了很多的 Hook 點(diǎn)來支持在這些點(diǎn)注冊回調(diào)函數(shù)。

    圖片來自 Wikimedia,可以點(diǎn)開參考文獻(xiàn)[8]查看大圖(svg 矢量圖,可以調(diào)大網(wǎng)頁顯示百分比繼續(xù)放大)。

    Linux 上最常用的防火墻 iptables 即是基于 Netfilter 來實(shí)現(xiàn)的(nftables 是新一代的防火墻)。iptables 基于表和鏈(Tables and Chains)的概念來組織規(guī)則。注意這里不要被“防火墻”這個詞誤導(dǎo)了,iptables 所能做的不僅僅是對包的過濾(Filter Table),還支持對包進(jìn)行網(wǎng)絡(luò)地址轉(zhuǎn)換(NAT Table)以及修改包的字段(Mangle Table)。在網(wǎng)絡(luò)虛擬化里,用的最多的便是 NAT 地址轉(zhuǎn)換功能。通常此類功能一般在網(wǎng)關(guān)網(wǎng)絡(luò)設(shè)備或是負(fù)載均衡設(shè)備中很常見。當(dāng) NC 需要在內(nèi)部進(jìn)行網(wǎng)絡(luò)相關(guān)的虛擬化時,也是一個類似網(wǎng)關(guān)以及負(fù)載均衡設(shè)備了。

    在設(shè)置 iptables 的 NAT 規(guī)則前,還需要打開內(nèi)核的包轉(zhuǎn)發(fā)功能 echo "1" > /proc/sys/net/ipv4/ip_forward 才可以。另外建議也打開 echo "1" /proc/sys/net/bridge/bridge-nf-call-iptables 開關(guān)(可能需要 modprobe br_netfilter)。bridge-nf-call-iptables 從上面的源碼分析就能理解,網(wǎng)橋的轉(zhuǎn)發(fā)處理是在 Netfilter 規(guī)則之前的。所以默認(rèn)情況下二層網(wǎng)橋的轉(zhuǎn)發(fā)是不會受到三層 iptables 的限制的,但是很多虛擬化網(wǎng)絡(luò)的實(shí)現(xiàn)需要 Netfilter 規(guī)則生效,所以內(nèi)核也支持了讓網(wǎng)橋的轉(zhuǎn)發(fā)邏輯也調(diào)用一下 Netfilter 的規(guī)則。這個特性默認(rèn)情況不開啟,所以需要檢查開關(guān)。至于具體的 iptables 命令,可以參考這篇文章和其譯文[10]進(jìn)行了解,本文不再討論。

    這里強(qiáng)調(diào)下,Netfilter 的邏輯運(yùn)行在內(nèi)核軟中斷上下文里。如果 Netfilter 添加了很多規(guī)則,必然會造成一定的 CPU 開銷。下文在提到虛擬化網(wǎng)絡(luò)的性能降低時,很大一部分開銷便是源自這里。

    二 虛擬網(wǎng)絡(luò)設(shè)備

    在傳統(tǒng)的網(wǎng)絡(luò)認(rèn)知里,網(wǎng)絡(luò)就是由帶有一個或多個 NIC 的一組 NC 使用硬件介質(zhì)和 switch(交換機(jī))、Router(路由器)所組成的一個通信集合(圖片來自 [11],下同):

    網(wǎng)絡(luò)虛擬化作為 SDN(Software?Defined?Network,軟件定義網(wǎng)絡(luò))的一種實(shí)現(xiàn),無非就是虛擬出 vNIC(虛擬網(wǎng)卡)、vSwitch(虛擬交換機(jī))、vRouter(虛擬路由器)等設(shè)備,配置相應(yīng)的數(shù)據(jù)包流轉(zhuǎn)規(guī)則而已。其對外的接口必然也是符合其所在的物理網(wǎng)絡(luò)協(xié)議規(guī)范的,比如 Ethernet 和 TCP/IP 協(xié)議族。

    隨著 Linux 網(wǎng)絡(luò)虛擬化技術(shù)的演進(jìn),有了若干種虛擬化網(wǎng)絡(luò)設(shè)備,在虛擬機(jī)和虛擬容器網(wǎng)絡(luò)中得到了廣泛的應(yīng)用。典型的有 Tap/Tun/Veth、Bridge 等:

    • Tap/Tun 是 Linux 內(nèi)核實(shí)現(xiàn)的一對虛擬網(wǎng)絡(luò)設(shè)備,Tap/Tun 分別工作在二層/三層。Linux 內(nèi)核通過 Tap/Tun 設(shè)備和綁定該設(shè)備的用戶空間之間交換數(shù)據(jù)。基于 Tap 驅(qū)動即可實(shí)現(xiàn)虛擬機(jī) vNIC 的功能,Tun 設(shè)備做一些其他的轉(zhuǎn)發(fā)功能。
    • Veth 設(shè)備總是成對創(chuàng)建(Veth Pair),一個設(shè)備收到內(nèi)核發(fā)送的數(shù)據(jù)后,會發(fā)送到另一個設(shè)備上去,可以把 Veth Pair 可以想象成一對用網(wǎng)線連接起來的 vNIC 設(shè)備。
    • Bridge 是工作在二層的虛擬網(wǎng)橋。這是虛擬設(shè)備,雖然叫網(wǎng)橋,但其實(shí)類似 vSwitch 的設(shè)計。當(dāng) Bridge 配合 Veth 設(shè)備使用時,可以將 Veth 設(shè)備的一端綁定到一個Bridge 上,相當(dāng)于真實(shí)環(huán)境把一個 NIC 接入一個交換機(jī)里。

    虛擬機(jī)和容器的網(wǎng)絡(luò)在傳輸流程上有些區(qū)別,前者比如 KVM 一般是使用 Tap 設(shè)備將虛擬機(jī)的 vNIC 和宿主機(jī)的網(wǎng)橋 Bridge 連接起來。而容器的 Bridge 網(wǎng)絡(luò)模式是將不同 Namespace 里的 Veth Pair 連接網(wǎng)橋 Bridge 來實(shí)現(xiàn)通信(其他方式下文討論)。

    Linux Bridge 配合橋接或者 NAT 模式很容易可以實(shí)現(xiàn)同主機(jī)或跨主機(jī)的虛擬機(jī)/容器之間通信,而且 Bridge 本身也支持 VLAN 的配置,可以實(shí)現(xiàn)一些三層交換機(jī)的能力。但是很多廠商都在研發(fā)功能更豐富的虛擬交換機(jī),流行的有 Cisco Nexus 1000V、 VMware Virtual Switch 以及廣泛使用的開源的 Open vSwitch[12] 等。利用 vSwitch,可以構(gòu)建出支持更多封裝協(xié)議、更高級的虛擬網(wǎng)絡(luò):

    1 Linux Bridge + Veth Pair 轉(zhuǎn)發(fā)

    VRF(Virtual Routing and Forwarding,虛擬路由轉(zhuǎn)發(fā))在網(wǎng)絡(luò)領(lǐng)域中是個很常見的術(shù)語。上世紀(jì)九十年代開始,很多二層交換機(jī)上就能創(chuàng)建出 4K 的 VLAN 廣播域了。4K 是因為 VLAN 標(biāo)簽的格式遵循 802.1q 標(biāo)準(zhǔn),其中定義的 VLAN ID 是 12 位的緣故(802.1q in 802.1q 可以做到 4094*4094 個,0 和 4095 保留)。如今 VRF 概念被引入三層,單個物理設(shè)備上也可以有多個虛擬路由/轉(zhuǎn)發(fā)實(shí)例了。Linux 的 VRF 實(shí)現(xiàn)了對三層的網(wǎng)絡(luò)協(xié)議棧的虛擬,而 Network Namespace(以下簡稱 netns)虛擬了整個網(wǎng)絡(luò)棧。一個 netns 的網(wǎng)絡(luò)棧包括:網(wǎng)卡(Network Interface)、回環(huán)設(shè)備(Loopback Device)、路由表(Routing Table)和 iptables 規(guī)則。本文使用 netns 進(jìn)行演示(畢竟在討論容器),下文使用 ip[14] 命令創(chuàng)建和管理 netns 以及 Veth Pair 設(shè)備。

    創(chuàng)建、查看、刪除 Network Namespace

    # 創(chuàng)建名為 qianyi-test-1 和 add qianyi-test-2 的命名 netns,可以在 /var/run/netns/ 下查看
    ip netns add qianyi-test-1
    ip netns add qianyi-test-2
    
    # 查看所有的 Network Namespace
    ip netns list
    
    # 刪除 Network Namespace
    ip netns del qianyi-test-1
    ip netns del qianyi-test-2

    執(zhí)行結(jié)果如圖(刪除先不執(zhí)行):

    有興趣的話可以使用 strace 命令跟蹤這個創(chuàng)建過程看看 ip 命令是怎么創(chuàng)建的(strace ip netns add qianyi-test-1)。

    在 netns 中執(zhí)行命令

    # 在 qianyi-test-1 這個 netns 中執(zhí)行 ip addr 命令(甚至可以直接執(zhí)行 bash 命令得到一個 shell)
    # nsenter 這個命令也很好用,可以 man nsenter 了解
    ip netns exec qianyi-test-1 ip addr

    執(zhí)行結(jié)果如下:

    圖片

    這個新創(chuàng)建的 netns 里一貧如洗,只有一個孤獨(dú)的 lo 網(wǎng)卡,還是 DOWN 狀態(tài)的。下面開啟它:

    開啟 lo 網(wǎng)卡,這個很重要

    ip netns exec qianyi-test-1 ip link set dev lo up
    ip netns exec qianyi-test-2 ip link set dev lo up

    狀態(tài)變成了 UNKOWN,這是正常的。這個狀態(tài)是驅(qū)動提供的,但是 lo 的驅(qū)動沒有做這個事情。

    創(chuàng)建 Veth Pair 設(shè)備

    # 分別創(chuàng)建 2 對名為 veth-1-a/veth-1-b 和 veth-2-a/veth-2-b 的 Veth Pair 設(shè)備
    ip link add veth-1-a type veth peer name veth-1-b
    ip link add veth-2-a type veth peer name veth-2-b

    使用 ip addr 命令可以查看:

    8-9,10-11 便是上面創(chuàng)建出來的 2 對 Veth Pair 設(shè)備,此時它們都沒有分配 IP 地址且是 DOWN 狀態(tài)。

    將 Veth Pair 設(shè)備加入 netns

    # 將 veth-1-a 設(shè)備加入 qianyi-test-1 這個 netns
    ip link set veth-1-a netns qianyi-test-1
    
    # 將 veth-1-b 設(shè)備加入 qianyi-test-2 這個 netns
    ip link set veth-1-b netns qianyi-test-2
    
    # 為設(shè)備添加 IP 地址/子網(wǎng)掩碼并開啟
    ip netns exec qianyi-test-1 ip addr add 10.0.0.101/24 dev veth-1-a
    ip netns exec qianyi-test-1 ip link set dev veth-1-a up
    
    ip netns exec qianyi-test-2 ip addr add 10.0.0.102/24 dev veth-1-b
    ip netns exec qianyi-test-2 ip link set dev veth-1-b up

    此時我們分別在兩個 netns 中執(zhí)行 ip addr 命令,即可看到設(shè)備已經(jīng)存在,且路由表(route 或 ip route 命令)也被默認(rèn)創(chuàng)建了:

    這里操作和查看設(shè)備都必須采用 ip netns exec {...} 的方式進(jìn)行,如果命令很多,也可以把執(zhí)行的命令換成 bash,這樣可以方便的在這個 shell 里對該 netns 進(jìn)行操作。

    現(xiàn)在通過 veth-1-a/veth-1-b 這對 Veth Pair 聯(lián)通了 qianyi-test-1 和 qianyi-test-2 這兩個 netns,這兩個 netns 之間就可以通過這兩個 IP 地址相互訪問了。

    ping 的同時在 101 上抓包的結(jié)果如下:

    可以很清楚的看到,eth-1-a(10.0.0.101)先通過 ARP (Address Resolution Protocol,地址解析協(xié)議)詢問 10.0.0.102 的 MAC 地址。得到回應(yīng)后,就以 ICMP (Internet Control Message Protocol,Internet 報文控制協(xié)議) request 和 reply 了,這也正是 ping 使用的協(xié)議。

    ARP 解析的緩存信息也可以通過 arp 命令查看:

    此時的網(wǎng)絡(luò)連接模式是這樣的:

    這種連接模式,就很像是現(xiàn)實(shí)中把兩個帶有 NIC 的設(shè)備用網(wǎng)線連接起來,然后配置了相同網(wǎng)段的 IP 后彼此就可以通信了。那如果超過一個設(shè)備需要建立互聯(lián)呢?現(xiàn)實(shí)中就需要交換機(jī)等網(wǎng)絡(luò)設(shè)備了。還記得前文中說的 Linux 自帶的 Bridge 么?接下來就使用 Bridge 機(jī)制建立網(wǎng)絡(luò)。

    進(jìn)行下面的試驗前需要把 veth-1-a/veth-1-b 這對 Veth Pair 從 qianyi-test-1 和 qianyi-test-2 移動回宿主機(jī)的 netns 里,恢復(fù)初始的環(huán)境。

    # 宿主機(jī)的 netns id 是 1(有些系統(tǒng)可能不是,請查詢相關(guān)系統(tǒng)的文檔)
    ip netns exec qianyi-test-1 ip link set veth-1-a netns 1
    ip netns exec qianyi-test-2 ip link set veth-1-b netns 1

    創(chuàng)建 Linux Bridge 并配置網(wǎng)絡(luò)

    # 創(chuàng)建一個名為 br0 的 bridge 并啟動(也可以使用 brctl 命令)
    ip link add br0 type bridge
    ip link set br0 up
    
    # 將 veth-1-a/veth-1-b 和 veth-2-a/veth-2-b 這兩對 Veth Pair 的 a 端放入 qianyi-test-1 和 qianyi-test-2
    ip link set veth-1-a netns qianyi-test-1
    ip link set veth-2-a netns qianyi-test-2
    
    # 為 veth-1-a 和 veth-2-a 配置 IP 并開啟
    ip netns exec qianyi-test-1 ip addr add 10.0.0.101/24 dev veth-1-a
    ip netns exec qianyi-test-1 ip link set dev veth-1-a up
    
    ip netns exec qianyi-test-2 ip addr add 10.0.0.102/24 dev veth-2-a
    ip netns exec qianyi-test-2 ip link set dev veth-2-a up
    
    # 將 veth-1-a/veth-1-b 和 veth-2-a/veth-2-b 這兩對 Veth Pair 的 b 端接入 br0 網(wǎng)橋并開啟接口
    ip link set veth-1-b master br0
    ip link set dev veth-1-b up
    ip link set veth-2-b master br0
    ip link set dev veth-2-b up

    執(zhí)行完可以查看創(chuàng)建好的網(wǎng)橋和配置好的 IP,實(shí)際上 brctl show 命令顯示的結(jié)果更易懂,可以很清楚的看到 veth-1-b 和 veth-2-b 連接在了網(wǎng)橋的接口上。當(dāng) Veth Pair 的一端連接在網(wǎng)橋上時,就會從“網(wǎng)卡”退化成一個“水晶頭”。

    當(dāng)下模式抓包的結(jié)果并沒有什么區(qū)別,但網(wǎng)絡(luò)連接模式不同:

    按照這個模式,如果有更多的 Network Namespace 和 Veth Pair 的話,使用相同的方式添加就可以水平擴(kuò)展了。

    但是嘗試從 qianyi-test-1 中 ping 宿主機(jī)自然是不通的,因為沒有任何網(wǎng)絡(luò)規(guī)則可以訪問到宿主機(jī)的網(wǎng)絡(luò):

    上面的截圖中有個 docker0 的網(wǎng)橋。當(dāng)機(jī)器上安裝了 Docker 之后會被自動設(shè)置好這個網(wǎng)橋供 Docker 使用。可能你注意到了,這個名為 docker0 的網(wǎng)橋居然是有 IP 地址的。現(xiàn)實(shí)中的網(wǎng)橋自然是沒有 IP 的,但是 Linux Bridge 這個虛擬設(shè)備是可以設(shè)置的。當(dāng) Bridge 設(shè)置 IP 之后,就可以將其設(shè)置成這個內(nèi)部網(wǎng)絡(luò)的網(wǎng)關(guān)(Gateway),再配合路由規(guī)則就可以實(shí)現(xiàn)最簡單的虛擬網(wǎng)絡(luò)跨機(jī)通信了(類似現(xiàn)實(shí)中的三層交換機(jī))。

    下面繼續(xù)給 br0 網(wǎng)橋創(chuàng)建地址并在 veth-1-a 和 veth-2-a 上設(shè)置其為默認(rèn)的網(wǎng)關(guān)地址:

    # 確認(rèn)路由轉(zhuǎn)發(fā)開啟
    echo "1" > /proc/sys/net/ipv4/ip_forward
    
    # 為 br0 設(shè)置 IP 地址
    ip addr add local 10.0.0.1/24 dev br0
    
    # 為 veth-1-a 和 veth-2-a 設(shè)置默認(rèn)網(wǎng)關(guān)
    ip netns exec qianyi-test-1 ip route add default via 10.0.0.1
    ip netns exec qianyi-test-2 ip route add default via 10.0.0.1

    此時就能成功的訪問宿主機(jī)地址了(宿主機(jī)上的路由表在 ip link set br0 up 這一步自動創(chuàng)建了):

    網(wǎng)絡(luò)模型進(jìn)一步變成了這樣:

    如果此時,另一臺宿主機(jī)上也存在另一個網(wǎng)段的網(wǎng)橋和若干個 netns 的話,怎么讓他們互通呢?分別在兩邊宿主機(jī)上配置一條到目的宿主機(jī)的路由規(guī)則就好了。假設(shè)另一臺宿主機(jī)的 IP 地址是 10.97.212.160,子網(wǎng)是 10.0.1.0/24 的話,那么需要在當(dāng)前機(jī)器上加一條 10.0.1.0/24 via 10.97.212.160 的規(guī)則,10.97.212.160 上加一條 10.0.0.0/24 via 10.97.212.159 的規(guī)則就可以了(或者 iptables 配置 SNAT/DNAT 規(guī)則)。那如果有 N 臺呢?那就會是個 N * N 條的規(guī)則,會很復(fù)雜。這就一個簡單的 Underlay 模式的容器通信方案了。缺點(diǎn)也很明顯,要求對宿主機(jī)底層網(wǎng)絡(luò)有修改權(quán),且比較難和底層網(wǎng)絡(luò)解耦。那如果能在物理網(wǎng)絡(luò)上構(gòu)建出一個橫跨所有宿主機(jī)的虛擬網(wǎng)橋,把所有相關(guān)的 netns 里面的設(shè)備都連接上去,不就可以解耦了么。這就是 Overlay Network(覆蓋網(wǎng)絡(luò))方案,下文會進(jìn)行闡述。至于本節(jié)其他虛擬網(wǎng)絡(luò)設(shè)備的安裝和配置(比如 Open vSwitch)也是比較類似的,這里不再贅述,有興趣的話可以自行查看文檔并測試。

    2 Overlay 網(wǎng)絡(luò)方案之 VXLAN

    VXLAN(Virtual eXtensible Local Area Network,虛擬可擴(kuò)展局域網(wǎng),RFC7348)[16],VLAN 的擴(kuò)展協(xié)議,是由 IETF 定義的 NVO3(Network Virtualization over Layer 3)標(biāo)準(zhǔn)技術(shù)之一(其他有代表性的還有 NVGRE、STT)。但是 VXLAN 和 VLAN 想要解決的問題是不一樣的。VXLAN 本質(zhì)上是一種隧道封裝技術(shù),它將數(shù)據(jù)鏈路層(L2)的以太網(wǎng)幀(Ethernet frames)封裝成傳輸層(L4)的 UDP 數(shù)據(jù)報(Datagrams),然后在網(wǎng)絡(luò)層(L3)中傳輸。效果就像數(shù)據(jù)鏈路層(L2)的以太網(wǎng)幀在一個廣播域中傳輸一樣,即跨越了三層網(wǎng)絡(luò)卻感知不到三層的存在。因為是基于 UDP 封裝,只要是 IP 網(wǎng)絡(luò)路由可達(dá)就可以構(gòu)建出龐大的虛擬二層網(wǎng)絡(luò)。也因為是基于高層協(xié)議再次封裝,性能會比傳統(tǒng)的網(wǎng)絡(luò)低 20%~30% 左右(性能數(shù)據(jù)隨著技術(shù)發(fā)展會有變化,僅代表當(dāng)前水平)。

    這里簡要介紹下 VXLAN 的 2 個重要概念:

    • VTEP(VXLAN Tunnel Endpoints,VXLAN 隧道端點(diǎn)),負(fù)責(zé) VXLAN 報文的封裝和解封,對上層隱藏了鏈路層幀的轉(zhuǎn)發(fā)細(xì)節(jié)
    • VNI(VXLAN Network Identifier,VXLAN 網(wǎng)絡(luò)標(biāo)識符),代表不同的租戶,屬于不同 VNI 的虛擬網(wǎng)絡(luò)之間不能直接進(jìn)行二層通信。

    VXLAN 的報文格式如圖[17]:

    Linux kernel v3.7.0 版本開始支持 VXLAN 網(wǎng)絡(luò)。但為了穩(wěn)定性和其他功能,請盡量選擇 kernel v3.10.0 及之后的版本。下面我們使用 10.97.212.159 和 11.238.151.74 這兩臺機(jī)器創(chuàng)建一個測試的 VXLAN 網(wǎng)絡(luò)。

    # 10.97.212.159 上操作
    
    # 創(chuàng)建名為 qianyi-test-1 和 add qianyi-test-2 的命名 netns,可以在 /var/run/netns/ 下查看
    ip netns add qianyi-test-1
    ip netns add qianyi-test-2
    
    # 開啟 lo 網(wǎng)卡,這個很重要
    ip netns exec qianyi-test-1 ip link set dev lo up
    ip netns exec qianyi-test-2 ip link set dev lo up
    
    # 創(chuàng)建一個名為 br0 的 bridge 并啟動(也可以使用 brctl 命令)
    ip link add br0 type bridge
    ip link set br0 up
    
    # 分別創(chuàng)建 2 對名為 veth-1-a/veth-1-b 和 veth-2-a/veth-2-b 的 Veth Pair 設(shè)備
    ip link add veth-1-a type veth peer name veth-1-b
    ip link add veth-2-a type veth peer name veth-2-b
    
    # 將 veth-1-a/veth-1-b 和 veth-2-a/veth-2-b 這兩對 Veth Pair 的 a 端放入 qianyi-test-1 和 qianyi-test-2
    ip link set veth-1-a netns qianyi-test-1
    ip link set veth-2-a netns qianyi-test-2
    
    # 為 veth-1-a 和 veth-2-a 配置 IP 并開啟
    ip netns exec qianyi-test-1 ip addr add 10.0.0.101/24 dev veth-1-a
    ip netns exec qianyi-test-1 ip link set dev veth-1-a up
    
    ip netns exec qianyi-test-2 ip addr add 10.0.0.102/24 dev veth-2-a
    ip netns exec qianyi-test-2 ip link set dev veth-2-a up
    
    # 將 veth-1-a/veth-1-b 和 veth-2-a/veth-2-b 這兩對 Veth Pair 的 b 端接入 br0 網(wǎng)橋并開啟接口
    ip link set veth-1-b master br0
    ip link set dev veth-1-b up
    ip link set veth-2-b master br0
    ip link set dev veth-2-b up
    
    # 11.238.151.74 上操作
    
    # 創(chuàng)建名為 qianyi-test-3 和 add qianyi-test-4 的命名 netns,可以在 /var/run/netns/ 下查看
    ip netns add qianyi-test-3
    ip netns add qianyi-test-4
    
    # 開啟 lo 網(wǎng)卡,這個很重要
    ip netns exec qianyi-test-3 ip link set dev lo up
    ip netns exec qianyi-test-4 ip link set dev lo up
    
    # 創(chuàng)建一個名為 br0 的 bridge 并啟動(也可以使用 brctl 命令)
    ip link add br0 type bridge
    ip link set br0 up
    
    # 分別創(chuàng)建 2 對名為 veth-3-a/veth-3-b 和 veth-4-a/veth-4-b 的 Veth Pair 設(shè)備
    ip link add veth-3-a type veth peer name veth-3-b
    ip link add veth-4-a type veth peer name veth-4-b
    
    # 將 veth-3-a/veth-3-b 和 veth-4-a/veth-4-b 這兩對 Veth Pair 的 a 端放入 qianyi-test-3 和 qianyi-test-4
    ip link set veth-3-a netns qianyi-test-3
    ip link set veth-4-a netns qianyi-test-4
    
    # 為 veth-3-a 和 veth-4-a 配置 IP 并開啟
    ip netns exec qianyi-test-3 ip addr add 10.0.0.103/24 dev veth-3-a
    ip netns exec qianyi-test-3 ip link set dev veth-3-a up
    
    ip netns exec qianyi-test-4 ip addr add 10.0.0.104/24 dev veth-4-a
    ip netns exec qianyi-test-4 ip link set dev veth-4-a up
    
    # 將 veth-3-a/veth-3-b 和 veth-4-a/veth-4-b 這兩對 Veth Pair 的 b 端接入 br0 網(wǎng)橋并開啟接口
    ip link set veth-3-b master br0
    ip link set dev veth-3-b up
    ip link set veth-4-b master br0
    ip link set dev veth-4-b up

    這一長串的命令和之前的步驟完全一致,構(gòu)建了一個如下圖所示的網(wǎng)絡(luò)環(huán)境:

    這個環(huán)境里,10.0.0.101 和 10.0.0.102 是通的,10.0.0.103 和 10.0.0.104 也是通的,但是顯然 10.0.0.101/10.0.0.102 和 10.0.0.103/10.0.0.104 是無法通信的。

    接下來配置 VXLAN 環(huán)境打通這四個 netns 環(huán)境:

    # 10.97.212.159 上操作(本機(jī)有多個地址時可以用 local 10.97.212.159 指定)
    ip link add vxlan1 type vxlan id 1 remote 11.238.151.74 dstport 9527 dev bond0
    ip link set vxlan1 master br0
    ip link set vxlan1 up
    
    # 11.238.151.74 上操作(本機(jī)有多個地址時可以用 local 11.238.151.74 指定)
    ip link add vxlan2 type vxlan id 1 remote 10.97.212.159 dstport 9527 dev bond0
    ip link set vxlan2 master br0
    ip link set vxlan2 up

    使用 brctl show br0 命令可以看到兩個 VXLAN 設(shè)備都連接上去了:

    然后從 10.0.0.101 上就可以 ping 通 10.0.0.103 了:

    在 10.0.0.101 上抓的包來看,就像是二層互通一樣:

    直接查看 arp 緩存,也是和二層互通一模一樣:

    使用 arp -d 10.0.0.103 刪掉這個緩存項目,在宿主機(jī)上重新抓包并保存文件,然后用 WireShark 打開看看(因為上面設(shè)置的不是 VXLAN 默認(rèn)端口 4789,還需要設(shè)置 WireShark 把抓到的 UDP 解析為 VXLAN 協(xié)議):

    我們得到了預(yù)期的結(jié)果。此時的網(wǎng)絡(luò)架構(gòu)如圖所示:

    那么問題來了,這里使用 UDP 協(xié)議能實(shí)現(xiàn)可靠通信嗎?當(dāng)然可以,可靠性不是這一層考慮的事情,而是里層被包裹的協(xié)議需要考慮的。完整的通信原理其實(shí)也并不復(fù)雜,兩臺機(jī)器上各自有 VTEP(VXLAN Tunnel Endpoints,VXLAN 隧道端點(diǎn))設(shè)備,監(jiān)聽著 9527 端口上發(fā)送的 UDP 數(shù)據(jù)包。在收到數(shù)據(jù)包后拆解通過 Bridge 傳遞給指定的設(shè)備。那 VETP 這個虛擬設(shè)備怎么知道類似 10.0.0.3 這樣的地址要發(fā)送給哪臺機(jī)器上的 VETP 設(shè)備呢?這可是虛擬的二層網(wǎng)絡(luò),底層網(wǎng)絡(luò)上可不認(rèn)識這個地址。事實(shí)上在 Linux Bridge 上維護(hù)著一個名為 FDB(Forwarding Database entry)的二層轉(zhuǎn)發(fā)表,用于保存遠(yuǎn)端虛擬機(jī)/容器的 MAC 地址、遠(yuǎn)端 VTEP 的 IP,以及 VNI 的映射關(guān)系,可以通過 bridge fdb 命令來對 FDB 表進(jìn)行操作:

    # 新增條目
    bridge fdb add <remote_host_mac_addr> dev <vxlan_interface> dst <remote_host_ip_addr>
    
    # 刪除條目
    bridge fdb del <remote_host_mac_addr> dev <vxlan_interface>
    
    # 替換條目
    bridge fdb replace <remote_host_mac_addr> dev <vxlan_interface> dst <remote_host_ip_addr>
    
    # 顯示條目
    bridge fdb show

    上面這個簡單的實(shí)驗就 2 臺機(jī)器,使用了命令的方式直接指定了彼此的 VTEP 地址,當(dāng) fdb 表查不到信息時發(fā)給對方就行了,這是最簡單的互聯(lián)模式。大規(guī)模 VXLAN 網(wǎng)絡(luò)下,就需要考慮如何發(fā)現(xiàn)網(wǎng)絡(luò)中其他的 VETP 地址了。解決這個問題一般有 2 種方式:一是使用組播/多播( IGMP, Internet Group Management Protocol),把節(jié)點(diǎn)組成一個虛擬的整體,包不清楚發(fā)給誰的話就廣播給整個組了(上述實(shí)驗中的創(chuàng)建 VETH 設(shè)備的命令修改為組播/多播地址比如 224.1.1.1 就行,remote 關(guān)鍵字也要改成 group,具體請參閱其他資料);二是通過外部的分布式控制中心來收集 FDB 信息并分發(fā)給同一個 VXLAN 網(wǎng)絡(luò)的所有節(jié)點(diǎn)。組播/多播受限于底層網(wǎng)絡(luò)的支持情況和大規(guī)模下的的性能問題,比如很多云網(wǎng)絡(luò)上不一定允許這么做。所以下文在討論和研究 K8s 的網(wǎng)絡(luò)方案時會看到很多網(wǎng)絡(luò)插件的實(shí)現(xiàn)采用的都是類似后者的實(shí)現(xiàn)方式。

    這節(jié)就介紹到這里了。當(dāng)然 Overlay 網(wǎng)絡(luò)方案也不止 VXLAN 這一種方式,只是目前很多主流的方案都采用了這種方式。其他的 Overlay 模式看上去眼花繚亂,其實(shí)說白了,無非就是 L2 over L4,L2 over L3,L3 over L3 等等各種包裝方式罷了,懂了基本原理之后都沒什么大不了的。網(wǎng)絡(luò)虛擬化的設(shè)備和機(jī)制也有很多[18],細(xì)說的話一天都說不完,但是基本的網(wǎng)絡(luò)原理掌握之后,無非是各種協(xié)議包的流轉(zhuǎn)罷了。

    三 K8s 的網(wǎng)絡(luò)虛擬化實(shí)現(xiàn)

    1 K8s 的網(wǎng)絡(luò)模型

    每一個 Pod 都有它自己的 IP 地址,這就意味著你不需要顯式地在每個 Pod 之間創(chuàng)建鏈接, 你幾乎不需要處理容器端口到主機(jī)端口之間的映射。這將創(chuàng)建一個干凈的、向后兼容的模型,在這個模型里,從端口分配、命名、服務(wù)發(fā)現(xiàn)、 負(fù)載均衡、應(yīng)用配置和遷移的角度來看,Pod 可以被視作虛擬機(jī)或者物理主機(jī)。

    Kubernetes 對所有網(wǎng)絡(luò)設(shè)施的實(shí)施,都需要滿足以下的基本要求(除非有設(shè)置一些特定的網(wǎng)絡(luò)分段策略):

    • 節(jié)點(diǎn)上的 Pod 可以不通過 NAT 和其他任何節(jié)點(diǎn)上的 Pod 通信
    • 節(jié)點(diǎn)上的代理(比如:系統(tǒng)守護(hù)進(jìn)程、kubelet)可以和節(jié)點(diǎn)上的所有 Pod 通信

    備注:僅針對那些支持 Pods 在主機(jī)網(wǎng)絡(luò)中運(yùn)行的平臺(比如:Linux):

    • 那些運(yùn)行在節(jié)點(diǎn)的主機(jī)網(wǎng)絡(luò)里的 Pod 可以不通過 NAT 和所有節(jié)點(diǎn)上的 Pod 通信

    這個模型不僅不復(fù)雜,而且還和 Kubernetes 的實(shí)現(xiàn)廉價的從虛擬機(jī)向容器遷移的初衷相兼容, 如果你的工作開始是在虛擬機(jī)中運(yùn)行的,你的虛擬機(jī)有一個 IP,這樣就可以和其他的虛擬機(jī)進(jìn)行通信,這是基本相同的模型。

    Kubernetes 的 IP 地址存在于 Pod 范圍內(nèi) - 容器共享它們的網(wǎng)絡(luò)命名空間 - 包括它們的 IP 地址和 MAC 地址。這就意味著 Pod 內(nèi)的容器都可以通過 localhost 到達(dá)各個端口。這也意味著 Pod 內(nèi)的容器都需要相互協(xié)調(diào)端口的使用,但是這和虛擬機(jī)中的進(jìn)程似乎沒有什么不同, 這也被稱為“一個 Pod 一個 IP”模型。

    這幾段話引用自 K8s 的官方文檔[19],簡單概括下就是一個 Pod 一個獨(dú)立的 IP 地址,所有的 Pod 之間可以不通過 NAT 通信。這個模型把一個 Pod 的網(wǎng)絡(luò)環(huán)境近似等同于一個 VM 的網(wǎng)絡(luò)環(huán)境。

    2 K8s 的主流網(wǎng)絡(luò)插件實(shí)現(xiàn)原理

    K8s 中的網(wǎng)絡(luò)是通過插件方式實(shí)現(xiàn)的,其網(wǎng)絡(luò)插件有 2 種類型:

    • CNI 插件:遵守 CNI(Container Network Interface,容器網(wǎng)絡(luò)接口)規(guī)范,其設(shè)計上偏重互操作性
    • Kubenet 插件:使用 bridge 和 host-local CNI 插件實(shí)現(xiàn)了基本的 cbr0

    圖片來自[20],本文只關(guān)注 CNI 接口插件。主流的 K8s 網(wǎng)絡(luò)插件有這些[21],本文選出 github star 數(shù)在千以上的幾個項目分析下:

    • Flannel:https://github.com/flannel-io/flannel
    • Calico:https://github.com/projectcalico/calico
    • Cilium:https://github.com/cilium/cilium

    Flannel

    CNI 是由 CoreOS 提出的規(guī)范,那就先看下 CoreOS 自己的 Flannel 項目的設(shè)計。Flannel 會在每臺機(jī)宿主機(jī)上部署一個名為 flanneld 的代理進(jìn)程,網(wǎng)段相關(guān)的數(shù)據(jù)使用 Kubernetes API/Etcd 存儲。Flannel 項目本身是一個框架,真正為我們提供容器網(wǎng)絡(luò)功能的,是 Flannel 的后端實(shí)現(xiàn)。

    目前的 Flannel 有下面幾種后端實(shí)現(xiàn):VXLAN、host-gw、UDP 以及阿里云和其他大廠的支持后端(云廠商都是實(shí)驗性支持),還有諸如 IPIP、IPSec 等一些隧道通信的實(shí)驗性支持。按照官方文檔的建議是優(yōu)先使用 VXLAN 模式,host-gw 推薦給經(jīng)驗豐富且想要進(jìn)一步提升性能的用戶(云環(huán)境通常不能用,原因后面說),UDP 是 Flannel 最早支持的一種性能比較差的方案,基本上已經(jīng)棄用了。

    下文分別對這三種模式進(jìn)行分析。

    1)VXLAN

    使用 Linux 內(nèi)核 VXLAN 封裝數(shù)據(jù)包的方式和原理上文已經(jīng)介紹過了,F(xiàn)lannel 創(chuàng)建了一個名為 flannel.1 的 VETH 設(shè)備。因為 flanneld 進(jìn)程的存在,所以注冊和更新新的 VETH 設(shè)備關(guān)系的任務(wù)就依靠這個進(jìn)程了。所以好像也沒什么需要說的了,就是每個新的 K8s Node 都會創(chuàng)建 flanneld 這個 DeamonSet 模式的守護(hù)進(jìn)程。然后注冊、更新新的 VETH 設(shè)備就變得非常自然了,全局的數(shù)據(jù)自然也是保存在 Etcd 里了。這種方式圖已經(jīng)畫過了,無非是設(shè)備名字不一樣(VETH 叫 flannel.1,網(wǎng)橋叫 cni0)而已。

    2)host-gw

    顧名思義,host-gw 就是把宿主機(jī) Host 當(dāng)做 Gateway 網(wǎng)關(guān)來處理協(xié)議包的流動。這個方式上文其實(shí)也演示過了,至于節(jié)點(diǎn)的變化和路由表的增刪也是依靠 flanneld 在做的。這個方案優(yōu)缺點(diǎn)都很明顯,最大的優(yōu)點(diǎn)自然是性能,實(shí)打?qū)嵉闹苯愚D(zhuǎn)發(fā)(性能整體比宿主機(jī)層面的通信低 10%,VXLAN 可是20% 起步,甚至 30%)。缺點(diǎn)也很明顯,這種方式要求宿主機(jī)之間是二層連通的,還需要對基礎(chǔ)設(shè)施有掌控權(quán)(編輯路由表),這個在云服務(wù)環(huán)境一般較難實(shí)現(xiàn),另外規(guī)模越來越大時候的路由表規(guī)模也會隨之增大。這種方式原理上比較簡單,圖不畫了。

    3)UDP

    每臺宿主機(jī)上的 flanneld 進(jìn)程會創(chuàng)建一個默認(rèn)名為 flannel0 的 Tun 設(shè)備。Tun 設(shè)備的功能非常簡單,用于在內(nèi)核和用戶應(yīng)用程序之間傳遞 IP 包。內(nèi)核將一個 IP 包發(fā)送給 Tun 設(shè)備之后,這個包就會交給創(chuàng)建這個設(shè)備的應(yīng)用程序。而進(jìn)程向 Tun 設(shè)備發(fā)送了一個 IP 包,那么這個 IP 包就會出現(xiàn)在宿主機(jī)的網(wǎng)絡(luò)棧中,然后根據(jù)路由表進(jìn)行下一跳的處理。在由 Flannel 管理的容器網(wǎng)絡(luò)里,一臺宿主機(jī)上的所有容器都屬于該宿主機(jī)被分配的一個“子網(wǎng)”。這個子網(wǎng)的范圍信息,所屬的宿主機(jī) IP 地址都保存在 Etcd 里。flanneld 進(jìn)程均監(jiān)聽著宿主機(jī)上的 UDP 8285 端口,相互之間通過 UDP 協(xié)議包裝 IP 包給目的主機(jī)完成通信。之前說過這個模式性能差,差在哪里?這個方案就是一個在應(yīng)用層模擬實(shí)現(xiàn)的 Overlay 網(wǎng)絡(luò)似得(像不像一個用戶態(tài)實(shí)現(xiàn)的 VETH 設(shè)備?),數(shù)據(jù)包相比內(nèi)核原生支持的 VXLAN 協(xié)議在用戶態(tài)多了一次進(jìn)出(flanneld 進(jìn)程封包/拆包過程),所以性能上損失要大一些。

    Calico

    Calico 是個挺有意思的項目,基本思想是把宿主機(jī)完全當(dāng)成路由器,不使用隧道或 NAT 來實(shí)現(xiàn)轉(zhuǎn)發(fā),把所有二三層流量轉(zhuǎn)換成三層流量,并通過宿主機(jī)上的路由配置完成包的轉(zhuǎn)發(fā)。

    Calico 和之前說的 Flannel 的 host-gw 模式區(qū)別是什么?首先 Calico 不使用網(wǎng)橋,而是通過路由規(guī)則在不同的 vNiC 間轉(zhuǎn)發(fā)數(shù)據(jù)。另外路由表也不是靠 Etcd 存儲和通知更新,而是像現(xiàn)實(shí)環(huán)境一樣直接用 BGP(Border Gateway Protocol, 邊界網(wǎng)關(guān)協(xié)議)進(jìn)行路由表數(shù)據(jù)交換。BGP 挺復(fù)雜的,詳細(xì)的闡述這個協(xié)議有點(diǎn)費(fèi)勁(而且我也不是很懂),在本文里只需要知道這個協(xié)議使得路由設(shè)備可以相互發(fā)送和學(xué)習(xí)對方的路由信息來充實(shí)自己就可以了,有興趣的話請查閱其他資料進(jìn)一步了解。回到 Calico,宿主機(jī)上依舊有個名為 Felix 的守護(hù)進(jìn)程和一個名為 BIRD的 BGP 客戶端。

    上文說過,F(xiàn)lannel 的 host-gw 模式要求宿主機(jī)二層是互通的(在一個子網(wǎng)),在 Calico 這里依然有這個要求。但是 Calico 為不在一個子網(wǎng)的環(huán)境提供了 IPIP 模式的支持。開啟這個模式之后,宿主機(jī)上會創(chuàng)建一個 Tun 設(shè)備以 IP 隧道(IP tunnel)的方式通信。當(dāng)然用了這個模式之后,包又是L3 over L3 的 Overlay 網(wǎng)絡(luò)模式了,性能也和 VXLAN 模式相當(dāng)。

    全路由表的通信方式也沒有額外組件,配置好 IP 路由轉(zhuǎn)發(fā)規(guī)則后全靠內(nèi)核路由模塊的流轉(zhuǎn)來做。IPIP 的架構(gòu)圖也是大同小異的,也不畫了。

    Cilium

    eBPF-based Networking, Security, and Observability

    光從這個介紹上就看出來 Cilium 散發(fā)出的那種與眾不同的氣息。這個項目目前的 Github Star 數(shù)字快過萬了,直接力壓前面兩位。Cilium 部署后會在宿主機(jī)上創(chuàng)建一個名為 cilium-agent 的守護(hù)進(jìn)程,這個進(jìn)程的作用就是維護(hù)和部署 eBPF 腳本來實(shí)現(xiàn)所有的流量轉(zhuǎn)發(fā)、過濾、診斷的事情(都不依賴 Netfilter 機(jī)制,kenel > v4.19 版本)。從原理圖的角度畫出來的架構(gòu)圖很簡單(配圖來自 github 主頁):

    Cilium 除了支持基本的網(wǎng)絡(luò)連通、隔離與服務(wù)透出之外,依托 eBPF 所以對宿主機(jī)網(wǎng)絡(luò)有更好的觀測性和故障排查能力。這個話題也很大,本文就此收住。這里給兩幾篇寫的很好的文章何其譯文可以進(jìn)一步了解22。

    3 K8s 容器內(nèi)訪問隔離

    上文介紹了網(wǎng)絡(luò)插件的機(jī)制和實(shí)現(xiàn)原理,最終可以構(gòu)建出一個二層/三層連通的虛擬網(wǎng)絡(luò)。默認(rèn)情況下 Pod 間的任何網(wǎng)絡(luò)訪問都是不受限的,但是內(nèi)部網(wǎng)絡(luò)中經(jīng)常還是需要設(shè)置一些訪問規(guī)則(防火墻)的。

    針對這個需求,K8s 抽象了一個名為 NetworkPolicy 的機(jī)制來支持這個功能。網(wǎng)絡(luò)策略通過網(wǎng)絡(luò)插件來實(shí)現(xiàn),要使用網(wǎng)絡(luò)策略就必須使用支持 NetworkPolicy 的網(wǎng)絡(luò)解決方案。為什么這么說?因為不是所有的網(wǎng)絡(luò)插件都支持 NetworkPolicy 機(jī)制,比如 Flannel 就不支持。至于 NetworkPolicy 的底層原理,自然是使用 iptables 配置 netfilter 規(guī)則來實(shí)現(xiàn)對包的過濾的。NetworkPolicy 配置的方法和 iptables/Netfilter 的原理細(xì)節(jié)不在本文范圍內(nèi),請參閱其他資料進(jìn)行了解24。

    4 K8s 容器內(nèi)服務(wù)透出

    在一個 K8s 集群內(nèi)部,在網(wǎng)絡(luò)插件的幫助下,所有的容器/進(jìn)程可以相互進(jìn)行通信。但是作為服務(wù)提供方這是不夠的,因為很多時候,服務(wù)的使用方不會在同一個 K8s 集群內(nèi)的。那么就需要一種機(jī)制將這個集群內(nèi)的服務(wù)對外透出。K8s 使用 Service 這個對象來完成這個能力的抽象。Service 在 K8s 里是個很重要的對象,即使在 K8s 內(nèi)部進(jìn)行訪問,往往也是需要 Service 包裝的(一來 Pod 地址不是永遠(yuǎn)固定的,二來總是會有負(fù)載均衡的需求)。

    一句話概括 Service 的原理就是:Service = kube-proxy + iptables 規(guī)則。當(dāng)一個 Service 創(chuàng)建時,K8s 會為其分配一個 Cluster IP 地址。這個地址其實(shí)是個 VIP,并沒有一個真實(shí)的網(wǎng)絡(luò)對象存在。這個 IP 只會存在于 iptables 規(guī)則里,對這個 VIP:VPort 的訪問使用 iptables 的隨機(jī)模式規(guī)則指向了一個或者多個真實(shí)存在的 Pod 地址(DNAT),這個是 Service 最基本的工作原理。那 kube-proxy 做什么?kube-proxy 監(jiān)聽 Pod 的變化,負(fù)責(zé)在宿主機(jī)上生成這些 NAT 規(guī)則。這個模式下 kube-proxy 不轉(zhuǎn)發(fā)流量,kube-proxy 只是負(fù)責(zé)疏通管道。

    K8s 官方文檔比較好的介紹了 kube-proxy 支持的多種模式和基本的原理[26]。早先的 userspace 模式基本上棄用了,上面所述的 iptables 隨機(jī)規(guī)則的做法在大規(guī)模下也不推薦使用了(想想為什么)。現(xiàn)在最推薦的當(dāng)屬 IPVS 模式了,相對于前兩種在大規(guī)模下的性能更好。如果說 IPVS 這個詞比較陌生的話,LVS 這個詞恐怕是我們耳熟能詳?shù)摹T谶@個模式下,kube-proxy 會在宿主機(jī)上創(chuàng)建一個名為 kube-ipvs0 的虛擬網(wǎng)卡,然后分配 Service VIP 作為其 IP 地址。最后 kube-proxy 使用內(nèi)核的 IPVS 模塊為這個地址設(shè)置后端 POD 的地址(ipvsadm 命令可以查看)。其實(shí) IPVS 在內(nèi)核中的實(shí)現(xiàn)也是用了 Netfilter 的 NAT 機(jī)制。不同的是,IPVS 不會為每一個地址設(shè)置 NAT 規(guī)則,而是把這些規(guī)則的處理放到了內(nèi)核態(tài),保證了 iptables 規(guī)則的數(shù)量基本上恒定,比較好的解決了之前的問題。

    上面說的只是解決了負(fù)載均衡的問題,還沒提到服務(wù)透出。K8s 服務(wù)透出的方式主要有 NodePort、LoadBalancer 類型的 Service(會調(diào)用 CloudProvider 在公有云上為你創(chuàng)建一個負(fù)載均衡服務(wù))以及 ExternalName(kube-dns 里添加 CNAME)的方式。對于第二種類型,當(dāng) Service 繁多但是又流量很小的情況下,也可以使用 Ingress 這個 Service 的 Service 來收斂掉[27]。Ingress 目前只支持七層 HTTP(S) 轉(zhuǎn)發(fā)(Service 目前只支持四層轉(zhuǎn)發(fā)),從這個角度猜猜 Ingress 怎么實(shí)現(xiàn)的?來張圖看看吧[28](當(dāng)然還有很多其他的 Controller[29]):

    對于這部分,本文不再進(jìn)行詳細(xì)闡述了,無非就是 NAT,NAT 多了就想辦法收斂 NAT 條目。按照慣例,這里給出一篇特別好的 kube-proxy 原理闡述的文章供進(jìn)一步了解[30]。

    四 總結(jié)

    網(wǎng)絡(luò)虛擬化是個很大的話題,很難在一篇文章中完全講清楚。盡管這篇文章盡量想把重要的知識節(jié)點(diǎn)編織清楚,但受限于作者本人的精力以及認(rèn)知上的限制,可能存在疏忽甚至錯漏。如有此類問題,歡迎在評論區(qū)討論/指正。參考文獻(xiàn)里給出了很多不錯的資料值得進(jìn)一步去學(xué)習(xí)(部分地址受限于網(wǎng)絡(luò)環(huán)境,可能需要特定的方式才能訪問)。

    參考文獻(xiàn)

    1、TCP Implementation in Linux: A Brief Tutorial, Helali Bhuiyan, Mark McGinley, Tao Li, Malathi Veeraraghavan, University of Virginia:https://www.semanticscholar.org/paper/TCP-Implementation-in-Linux-%3A-A-Brief-Tutorial-Bhuiyan-McGinley/f505e259fb0cd8cf3f75582d46cd209fd9cb1d1a
    2、NAPI, linuxfoundation, https://wiki.linuxfoundation.org/networking/napi
    3、Monitoring and Tuning the Linux Networking Stack: Receiving Data, Joe Damato,譯文:Linux 網(wǎng)絡(luò)棧監(jiān)控和調(diào)優(yōu):接收數(shù)據(jù)(2016):http://arthurchiao.art/blog/tuning-stack-rx-zh/
    4、Monitoring and Tuning the Linux Networking Stack: Sending Data, Joe Damato, 譯文:Linux 網(wǎng)絡(luò)棧監(jiān)控和調(diào)優(yōu):發(fā)送數(shù)據(jù)(2017):http://arthurchiao.art/blog/tuning-stack-tx-zh/
    5、Scaling in the Linux Networking Stack, https://github.com/torvalds/linux/blob/master/Documentation/networking/scaling.rst
    6、Understanding TCP internals step by step for Software Engineers and System Designers, Kousik Nath
    7、Netfilter, https://www.netfilter.org/
    8、Netfilter-packet-flow, https://upload.wikimedia.org/wikipedia/commons/3/37/Netfilter-packet-flow.svg
    9、Analysis TCP in Linux, https://github.com/fzyz999/Analysis_TCP_in_Linux
    10、NAT - Network Address Translation, 譯文:NAT - 網(wǎng)絡(luò)地址轉(zhuǎn)換(2016):http://arthurchiao.art/blog/nat-zh/
    11、Virtual networking in Linux, By M. Jones, IBM Developer:https://developer.ibm.com/tutorials/l-virtual-networking/
    12、Open vSwitch, http://www.openvswitch.org/
    13、Linux Namespace, https://man7.org/linux/man-pages/man7/namespaces.7.html
    14、ip, https://man7.org/linux/man-pages/man8/ip.8.html
    15、Veth, https://man7.org/linux/man-pages/man4/veth.4.html
    16、VxLAN, https://en.wikipedia.org/wiki/Virtual_Extensible_LAN
    17、QinQ vs VLAN vs VXLAN, John, https://community.fs.com/blog/qinq-vs-vlan-vs-vxlan.htm
    18、Introduction to Linux interfaces for virtual networking, Hangbin Liu:https://developers.redhat.com/blog/2018/10/22/introduction-to-linux-interfaces-for-virtual-networking#
    19、Cluster Networking, 英文地址https://kubernetes.io/zh/docs/concepts/cluster-administration/networking/
    20、THE CONTAINER NETWORKING LANDSCAPE: CNI FROM COREOS AND CNM FROM DOCKER, Lee Calcote:https://thenewstack.io/container-networking-landscape-cni-coreos-cnm-docker/
    21、CNI - the Container Network Interface, https://github.com/containernetworking/cni
    22、Making the Kubernetes Service Abstraction Scale using eBPF, [譯] 利用 eBPF 支撐大規(guī)模 K8s Service (LPC, 2019):https://linuxplumbersconf.org/event/4/contributions/458/
    23、基于 BPF/XDP 實(shí)現(xiàn) K8s Service 負(fù)載均衡 (LPC, 2020)https://linuxplumbersconf.org/event/7/contributions/674/

    24、A Deep Dive into Iptables and Netfilter Architecture, Justin Ellingwood:https://www.digitalocean.com/community/tutorials/a-deep-dive-into-iptables-and-netfilter-architecture

    25、Iptables Tutorial 1.2.2, Oskar Andreasson:https://www.frozentux.net/iptables-tutorial/iptables-tutorial.html

    26、Virtual IPs and service proxies, 英文地址:https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies

    27、Ingress, 英文地址:https://kubernetes.io/docs/concepts/services-networking/ingress/

    28、NGINX Ingress Controller, https://www.nginx.com/products/nginx-ingress-controller/

    29、Ingress Controllers, 英文地址:https://kubernetes.io/docs/concepts/services-networking/ingress-controllers/

    30、Cracking kubernetes node proxy (aka kube-proxy), [譯] 深入理解 Kubernetes 網(wǎng)絡(luò)模型:自己實(shí)現(xiàn) Kube-Proxy 的功能:https://cloudnative.to/blog/k8s-node-proxy/

    原文鏈接:https://developer.aliyun.com/article/813025?utm_content=g_1000310145

    本文為阿里云原創(chuàng)內(nèi)容,未經(jīng)允許不得轉(zhuǎn)載。

    背景

    近期,閑魚核心應(yīng)用出現(xiàn)了一個比較難解決的問題。在該應(yīng)用集群中,會隨機(jī)偶現(xiàn)一兩個實(shí)例,其JVM運(yùn)行在一個掛起的狀態(tài),深入分析stack文件發(fā)現(xiàn),此時jvm中有大量的線程在等待一把沒有任何線程持有的鎖。問題實(shí)例所在的機(jī)器負(fù)載相對正常機(jī)器要輕很多,而其線程數(shù)則大幅增多。由于該問題在邏輯上的沖突,加上周邊問題的復(fù)雜性,使得研究、分析、解決該問題變得相對來說困難與曲折。本文將系統(tǒng)性地介紹如何解決這個問題,并找出問題背后的原因。

    問題分析

    在實(shí)際解決這個問題的時候,我們發(fā)現(xiàn)不僅問題本身顯得不合常理,其周邊環(huán)境也相對來說不友善,給問題的分析與解決帶來了較大的困難。

    • 集群中隨機(jī)出現(xiàn)。問題隨機(jī)出現(xiàn)在該應(yīng)用集群中的一個或幾個實(shí)例中,無法提前預(yù)知其出現(xiàn)規(guī)律。

    • 單機(jī)出現(xiàn)時間不可預(yù)知,現(xiàn)場捕捉困難,捕捉風(fēng)險大,一般發(fā)現(xiàn)已經(jīng)為事后,無現(xiàn)場第一手?jǐn)?shù)據(jù)。從單個機(jī)器,或者單個實(shí)例看,則是出現(xiàn)概率非常低,出現(xiàn)時間完全隨機(jī)。這使得蹲點(diǎn)單臺機(jī)器以捕捉這個問題的思路幾乎行不通,策略擴(kuò)大至整個集群又可能出現(xiàn)穩(wěn)定性及性能問題。

    • 問題出現(xiàn)頻率低。出現(xiàn)頻率大概在一到兩天一次。

    • 問題表現(xiàn)復(fù)雜。該問題的表現(xiàn)很復(fù)雜,不僅從第一眼看去不合常理,JVM內(nèi)部出現(xiàn)了大量線程在等待一把沒有任何線程持有的鎖。另外,問題機(jī)器的負(fù)載非常低,基本上在5%以內(nèi),相當(dāng)于空載,而JVM中線程數(shù)卻非常多,最多發(fā)現(xiàn)過接近4k個線程。

    • 問題周邊環(huán)境復(fù)雜。該問題出現(xiàn)前后,應(yīng)用先后引入了rxjava、協(xié)程,應(yīng)用為早期應(yīng)用,服務(wù)結(jié)構(gòu)復(fù)雜,而log4j問題又和網(wǎng)上大量的文章情形不符。

    • 驗證困難。理論分析完成后,無法在線上復(fù)現(xiàn)及驗證,安全性、穩(wěn)定性、數(shù)據(jù)等都不允許直接在線上驗證。

    解決方案

    解決這個問題的主要按照以下六步,一步步排除法,最終定位并解決問題。按照先易后難,先直接后理論,先數(shù)據(jù)后源碼的順序,總結(jié)出來以下六步,大體上投入時間逐步增加,難度也逐漸增加。

    step1. 代碼bug查找

    代碼問題指的是業(yè)務(wù)代碼本身邏輯問題把JVM帶入了某種故障狀態(tài)。問題的分析及排除很簡單,通過觀察應(yīng)用日志即可。

    step2. 現(xiàn)場捕獲

    定位了問題,問題也就解決了一半。

    一般來說,定位問題主要有兩個分類,即時定位,事后定位。

    前一種是指我們實(shí)時直接監(jiān)控JVM信息,在關(guān)鍵信息異常時,即發(fā)生動作。配合周期性的信息采集,基本可以對問題發(fā)生時刻前后數(shù)據(jù)精準(zhǔn)采集和對比,做法一般是采用JVM代理方式或JMS方式。JVM代理分為C語言和Java語言代理,C語言代理運(yùn)行在JVM層,可以做到即時Java代碼發(fā)生故障故障,依然可以正常采集信息。Java代理相對C語言代理來說編寫起來方便,實(shí)際上C語言部分任務(wù)還是通過JNI接口構(gòu)造Java對象執(zhí)行的。JMS方式可以實(shí)時采集各種指標(biāo),也是目前監(jiān)控主要采用等方式。缺點(diǎn)是對應(yīng)用的侵入性非常大,不適合解決問題用。

    事后定位是指通過日志監(jiān)控等較緩慢的方式去對問題發(fā)生時刻定位,由于該問題的特殊性,日志無法提供需要的信息以判斷故障,另外,日志無法采集我們需要的信息,尤其是JVM內(nèi)部線程和鎖的信息。

    在后續(xù)現(xiàn)象的觀察中,發(fā)現(xiàn)了一個比較普遍的現(xiàn)象,應(yīng)用由正常轉(zhuǎn)為故障需要一個漫長的時間,應(yīng)用可能在臨界區(qū)停留相當(dāng)長的時間,極端例子中應(yīng)用在線程數(shù)提升后依然能夠正常運(yùn)行接近24小時,之后發(fā)生了自恢復(fù)。另外,在和JVM組同學(xué)對接的時候,又被告知阿里jdk對C代理支持可能由于安全原因被關(guān)閉。基本上宣布這個問題的研究進(jìn)入了下一個階段。step3. io hang

    考慮到大部分實(shí)例業(yè)務(wù)日志打印緩慢或者根本不再打印,可能原因是io方面出了問題,通過查看容器硬件監(jiān)控及應(yīng)用火焰圖,可以輕松將IO問題排除。

    step4. 鎖分析鎖問題主要包含死鎖丟鎖死鎖的特點(diǎn)很明顯,一旦發(fā)生死鎖,則與該鎖相關(guān)的線程都將停止。首先這點(diǎn)和大量實(shí)例運(yùn)行緩慢不符,其次,這個問題可以輕易通過stack文件排除。丟鎖主要和協(xié)程有關(guān),和死鎖相似,考慮到協(xié)程可能在切換過程中發(fā)生丟鎖,造成的現(xiàn)象和該問題很類似,即沒有線程持有的鎖。丟鎖最主要的問題也是不可恢復(fù),一旦丟鎖,則JVM相關(guān)線程就永遠(yuǎn)不可恢復(fù),和該問題不符。另外,觀察大部分stack文件發(fā)現(xiàn),此時JVM中的協(xié)程數(shù)量并不多,線程池Worker實(shí)例也在變化。

    step5. 資源耗盡查看

    資源耗盡是指JVM運(yùn)行過程中由于部分資源緊張,程序雖然可以正常運(yùn)行,但是限于部分資源緊張,必須等待其他線程釋放了持有資源后,當(dāng)前線程才可以繼續(xù)運(yùn)行。資源包括軟件資源硬件資源軟件資源是指在JVM運(yùn)行過程中,有設(shè)定上限的軟件資源,如堆、Reserved Code Cache、元數(shù)據(jù)區(qū)等,在實(shí)際觀察中,發(fā)現(xiàn)上述資源均未出現(xiàn)明顯的資源耗盡情況。硬件資源主要分析在JVM運(yùn)行過程中,所在機(jī)器硬件資源如CPU、內(nèi)存、網(wǎng)絡(luò)等硬件資源使用情況。其中,在觀察中發(fā)現(xiàn),內(nèi)存資源出現(xiàn)了明顯的問題,由于問題機(jī)器線程數(shù)大幅增加,導(dǎo)致問題機(jī)器JVM總使用內(nèi)存超出了機(jī)器的物理內(nèi)存。加上監(jiān)控進(jìn)程與機(jī)器本身的進(jìn)程,很容易得出一個結(jié)論,JVM此時在將部分資源扇出至page頁。實(shí)際上,JVM此時在部分基于硬盤運(yùn)行。如果此時JVM進(jìn)行一個牽涉面很廣的搶鎖任務(wù),那么就有可能發(fā)生悲劇。而在該問題中,應(yīng)用采用了log4j作為日志記錄工具,查看相關(guān)源碼可以看出,log4j采用了java monitor來控制日志打印,防止日志結(jié)構(gòu)混亂及數(shù)據(jù)破壞。而作為流量入口日志,所有的業(yè)務(wù)線程都會進(jìn)行進(jìn)行打印,因此也會進(jìn)行搶鎖。
    1. publicvoid callAppenders(LoggingEvent event) {

    2. int writes = 0;


    3. for(Category c = this; c != ; c = c.parent) {

    4. // Protected against simultaneous call to addAppender, removeAppender,...

    5. synchronized(c) {

    6. if(c.aai != ) {

    7. writes += c.aai.appendLoopOnAppenders(event);

    8. }

    9. if(!c.additive) {

    10. break;

    11. }

    12. }

    13. }


    14. if(writes == 0) {

    15. repository.emitNoAppenderWarning(this);

    16. }

    17. }

    查看HotSpot源碼,在退出臨界區(qū)時,首先要做的是把鎖狀態(tài)重置,也即對象頭重置及Montior對象當(dāng)前owner置,然后才會喚醒所有相關(guān)線程搶鎖。如果此時內(nèi)存放不下所有有關(guān)線程,隨著線程的喚醒,活躍線程會被扇出以提供內(nèi)存空間。大量的扇入和扇出使得這個過程顯得很緩慢,也就出現(xiàn)了一個沒有任何線程持有的鎖,實(shí)際上JVM此時在進(jìn)行一個艱難的搶鎖任務(wù)。

    1. for(p = w ; p != ; p = p->_next) {

    2. guarantee (p->TState== ObjectWaiter::TS_CXQ, "Invariant") ;

    3. p->TState= ObjectWaiter::TS_ENTER ;

    4. p->_prev = q ;

    5. q = p ;

    6. }


    7. // Prepend the RATs to the EntryList

    8. if(_EntryList!= ) {

    9. q->_next = _EntryList;

    10. _EntryList->_prev = q ;

    11. }

    step6. 框架層源碼閱讀

    在前面步驟中大致定位了一個大的方向,線程數(shù)增加導(dǎo)致內(nèi)存不足。接下來需要深入框架層去分析引起線程數(shù)增加的可能原因。先后對HSF、Modulet、Mtop、netty等框架進(jìn)行了源碼級別的分析,主要跟蹤各個框架線程分配策略。其中,HSF默認(rèn)設(shè)置的線程池模型擾動抗性很低。在HSF框架中,netty線程池將任務(wù)提交到HSF Provider線程池,HSF Provider線程池采用業(yè)務(wù)隔離設(shè)計,在一次對外服務(wù)中,HSF Provider大量調(diào)用HSF Consumer,而Consumer會被提交至Consumer線程池中執(zhí)行。在該應(yīng)用中,Provider和Consumer線程池容量比例大于200:1。

    而根據(jù)業(yè)務(wù)實(shí)際,合理的比例應(yīng)該在1:1附近。

    失衡的線程池結(jié)構(gòu),極容易服務(wù)發(fā)生網(wǎng)絡(luò)抖動、回環(huán)調(diào)用時使Consumer線程池服務(wù)能力下降,進(jìn)而使整個應(yīng)用實(shí)例對外服務(wù)能力下降。而有規(guī)律的故障不應(yīng)該和無規(guī)律的抖動有關(guān)。

    回環(huán)與問題出現(xiàn)頻率之間讀者可以通過概率論進(jìn)行分析,假定100臺機(jī)器,則每次請求會有1/100的概率發(fā)生回環(huán),同理,每10000次請求就會發(fā)生雙回環(huán),1M次請求則是3回環(huán)。在該問題中,概率論分析和實(shí)際情況是契合的。

    在研究框架層的時候,發(fā)現(xiàn)了回環(huán)調(diào)用對系統(tǒng)的危害。但是還有一個疑問需要回答,回環(huán)調(diào)用完成后,應(yīng)用應(yīng)該能恢復(fù),而線上實(shí)際情況是,自恢復(fù)是個小概率事件。結(jié)合前一節(jié)可以得出一個結(jié)論,回環(huán)調(diào)用使應(yīng)用Consumer線程池處理能力下降,進(jìn)而使上游線程池水位逐漸提升直至被打滿。而數(shù)量過度增加的線程池使得內(nèi)存資源緊張,導(dǎo)致JVM基于磁盤運(yùn)行而搶鎖困難,搶鎖過程的拉長使得沒有任何線程持有鎖這個常規(guī)狀態(tài)下的瞬時狀態(tài)被拉長,JVM服務(wù)能力大打折扣,而duct平臺由于策略原因不能應(yīng)對該問題的特殊情況導(dǎo)致其無法啟動切流,流量照常打入JVM。于是就形成了一個惡性循環(huán),線程數(shù)提升導(dǎo)致JVM進(jìn)入一種非常規(guī)狀態(tài),服務(wù)能力下降,而流量照常,導(dǎo)致線程數(shù)很難下降。于是,JVM長時間運(yùn)行在一個非常緩慢的狀態(tài),從表現(xiàn)上來看就是jvm掛起。下表為一個較有代表性的流量對比(實(shí)際上故障機(jī)狀態(tài)跨度非常大,這兩臺機(jī)器較為典型而已)

    實(shí)驗驗證

    接下來,本文采用阿里PAS壓測平臺,對預(yù)發(fā)機(jī)器進(jìn)行了壓測驗證。由于線上問題復(fù)雜,無法復(fù)現(xiàn)線上的環(huán)境,只能對其誘因進(jìn)行驗證。下表為壓測過程中應(yīng)用的性能表現(xiàn)。由于壓測模式限制,所支持的最大tps在超時的情況下非常低,如表所示只有80左右,考慮到壓測環(huán)境機(jī)器數(shù)量,回環(huán)數(shù)量還要打折。

    從下圖可以看出,平均RT為2500ms左右,絕大多數(shù)請求都在超時狀態(tài)。

    壓測結(jié)果表明,回環(huán)不需要多高的流量,就能把應(yīng)用實(shí)際服務(wù)能力大打折扣。考慮到線上還有其他類型的請求,填充在回環(huán)之間,這會使線程池迅速打滿,并使得處理回環(huán)請求的時間加長,惡化應(yīng)用從回環(huán)調(diào)用中恢復(fù)的能力。總結(jié)和思考在JVM出現(xiàn)問題的時候,首先要閱讀業(yè)務(wù)代碼,這個雖然看似作用不大,卻有可能以相當(dāng)?shù)土拇鷥r解決問題。之后,主要思路就是捕獲現(xiàn)場,現(xiàn)場捕獲將極大程度上有助于問題的解決。如果該步驟不可行,或者成本相對較高,可以先去排查周邊原因。這主要包括IO硬軟件資源,在執(zhí)行這些排查的時候,要留意這些方面出問題的表現(xiàn)和實(shí)際問題的表現(xiàn)契合度。比較明顯的就是一旦死鎖、丟鎖,或者IO hang,則程序無法從故障狀態(tài)恢復(fù),相關(guān)線程也不能繼續(xù)執(zhí)行。這些特點(diǎn)可以協(xié)助排除部分大的方向。最后,對資源耗盡的排查,則是基于本文所述問題的一個基本特點(diǎn),絕大多數(shù)JVM運(yùn)行緩慢而不是停止運(yùn)行。所以,資源緊張成為一個解決問題的大方向,并最終定位了問題。深入到框架層主要是從理論上分析問題產(chǎn)生的原因,然后在結(jié)合實(shí)際情況,分析整個解決思路的正確性。讀者在遇到類似JVM問題時,可參考本文所述的方法與步驟,對實(shí)際問題進(jìn)行分析與研究。

    閑魚技術(shù)團(tuán)隊不僅是阿里巴巴集團(tuán)旗下閑置交易社區(qū)的創(chuàng)造者,更是移動與高并發(fā)大數(shù)據(jù)應(yīng)用新技術(shù)的引導(dǎo)者與創(chuàng)新者。我們與Google Flutter/Dart小組密切合作,為社區(qū)貢獻(xiàn)了多個高star的項目和大量PR。我們正在積極探索深度學(xué)習(xí)和視覺技術(shù)在互動、交易、社區(qū)場景的創(chuàng)新應(yīng)用。閑魚技術(shù)與集團(tuán)中間件團(tuán)隊共同打造的FaaS平臺每天支持?jǐn)?shù)以千萬級用戶的高并發(fā)訪問場景。

    就是現(xiàn)在!客戶端/服務(wù)端java/架構(gòu)/前端/質(zhì)量工程師面向社會+校園招聘,base杭州阿里巴巴西溪園區(qū),一起做有創(chuàng)想空間的社區(qū)產(chǎn)品、做深度頂級的開源項目,一起拓展技術(shù)邊界成就極致!

    *投喂簡歷給小閑魚→guicai.gxy@alibaba-inc.com

    開源項目、峰會直擊、關(guān)鍵洞察、深度解讀

    請認(rèn)準(zhǔn)閑魚技術(shù)

網(wǎng)站首頁   |    關(guān)于我們   |    公司新聞   |    產(chǎn)品方案   |    用戶案例   |    售后服務(wù)   |    合作伙伴   |    人才招聘   |   

友情鏈接: 餐飲加盟

地址:北京市海淀區(qū)    電話:010-     郵箱:@126.com

備案號:冀ICP備2024067069號-3 北京科技有限公司版權(quán)所有