[譯] 雲原生世界中的資料包標記(packet mark)(LPC, 2020)
譯者序
本文翻譯自 2020 年 Joe Stringer 在 Linux Plumbers Conference 的一篇分享: Packet Mark In a Cloud Native World 。 探討一個在網路和安全領域 非常重要但又討論甚少的主題 :skb mark。
skb mark 是打在 核心資料包 (skb )上的 數字標記 ,例如,可能是一個 16bit 或 32bit 整數表示。這個 mark 只存在於每臺主機內部 ,當包從網絡卡發出去之後,這個資訊 就丟失了 —— 也就是說,它並 沒有儲存在任何 packet header 中 。
skb mark 用於傳遞狀態資訊。在主機的網路處理路徑上,網路應用(network applications)可以在一個地方給包打上 mark,稍後在另一個地方根據 mark 值對包進行相 應操作,據此可以實現 NAT、QoS、LoadBalancing 等功能。
這裡的一個問題是: mark 是一個開放空間 ,目前還沒有任何行業規範,因此 任何應用 可以往裡面寫入任何值 —— 只要稍後它自己能正確解讀就行了,但每個 skb 的 mark 只有一 份。顯而易見, 當主機內同時運行了多個網路應用並且它們都在使用 skb mark 時 (例 如,kube-proxy + Cilium),就有可能發生衝突,導致包被莫名其妙地轉發、丟棄或 修改等問題,因為它們彼此並不感知對方的 mark 語義。
由於譯者水平有限,本文不免存在遺漏或錯誤之處。如有疑問,請查閱原文。
以下是譯文。
準備這次分享時,我產生了一個疑問是: 網際網路是如何連線到一起的 (how is the internet held together)?比如,是靠膠帶(duct tape)嗎?—— 這當然是開玩笑。
今天討論的主題 —— skb mark
—— 要比膠帶嚴肅的多。另外,本文將聚焦在雲原生( cloud native)領域,因為過去 2~5 年這一領域出現了很多新的網路外掛(software plugins),正是它們在控制著現在的網路。
1 背景
1.1 Linux 世界中的 mark
mark 在不同子系統中有不同的叫法,例如,
-
fw_mark
我不確定這是 firewall mark 還是 forwarding mark 的縮寫。 iptables 和核心路由層 (routing layer)會用到這個 mark。
// include/linux/skbuff.h,程式碼來自 Kernel 4.19,下同。譯註 struct sk_buff { ... union { __u32 mark; __u32 reserved_tailroom; }; ... }
-
ct_mark
連線跟蹤(conntrack)的 mark,這個 mark 並沒有打在
skb->mark
上,但使 用方式是類似的:先將資訊存到 mark,到了用的地方再取出來,根據 mark 狀態 進行相應處理。 -
skb_mark
OVS 裡面的一個 mark,雖然和
skb->mark
不是一個東西,但二者是強關聯的。 -
SO_MARK
(include/uapi/asm-generic/socket.h
)使用者空間 socket 層的 mark。應用層可以用
setsockopt()
將某些資訊傳遞到 netfilter 和 tc 之類的子系統中。後面會看到使用案例。 -
xfrm_mark
來自 變換子系統 (transform subsystem)。
// include/uapi/linux/xfrm.h struct xfrm_mark { __u32 v; /* value */ __u32 m; /* mask */ };
-
pkt_mark
OVS 欄位,引入的目的是對 OVS
skb_mark
做通用化,因為後者用於 Linux,而 OVS 可能執行在非 Linux 機器上。
1.2 mark 有什麼用?
說了這麼多,那這些 mark 到底有什麼用? —— 如果不設定,那它們就沒什麼用。
換句話說, mark 能發揮多少作用、完成哪些功能 ,全看應用怎麼用它 。 比如當需要程式設計控制核心的處理行為時,就會和這些 mark 打交道。很多黑科技就源於此。
1.3 mark 註冊中心
如果你開發了一個網路軟體,流量收發都沒問題。但設定了某些 mark 位之後, 流量就莫名其妙地在某些地方消失了,就像進入了黑洞,或者諸如此類的一些事情。 這很可能是機器上執行的其他軟體也在用 mark,和你的衝突了。
不幸的是, 當前並沒有一個權威機構能告訴你,哪些軟體在使用 mark,以及它們是如何 使用的 。因此,想要在自己的應用中設定 mark 欄位時,如何通知到外界,以及如何確保 不會與別人的 mark 衝突,就是一件很困難的事情,因為沒有一箇中心式的註冊中心在管理 這些 mark —— 直到大約一個月之前,Dave 發了下面這條推文:
Dave 建立了這個 github repo,但注意,這裡並不是教大家如何使用 mark,這也不是一個 決策機構,而 只是一份文件,記錄大家正在使用的 mark 。如果你在用自己的 mark 方 案,強烈建議你記錄到到這個 repo。
1.4 Cilium 網路
我來自 Cilium 團隊,Cilium 是一個雲原生網路方案,提供了眾多的網路、可觀測性和安全能力:
我們會用到 mark,比如處理 kube-proxy + Cilium 的相容問題。
1.5 眾多 CNCF 網路外掛
為了準備這次分享,我還潛入雲原生領域進行了諸多探索。下圖是一些 CNCF 雲原生網路插 件,它們多少都用到了 mark。
想了解具體的某個外掛是如何使用 mark 的,可以找到它的原始碼,然後搜尋 mark
關鍵字。
這裡的一個考慮是:不同網路外掛提供的功能可能是 可疊加 的。 舉個例子,如果你已經在用 flannel,然後又想用 Cilium 的可觀測性和安全能力,那就可 以同時執行這兩種網路外掛 —— 顯然,這裡的 前提 是:Cilium 和 flannel 要對核心如 何處理包有一致的理解,這樣才能確保 Cilium 沿某個路徑轉發包時,它們不會被丟棄( drop)。要做到這一點,Cilium 就需要理解包括 flannel 在內的一些元件是如何設定和使 用 mark 的。
下面我們就來看一些 mark 的典型使用場景。
2 使用案例
這裡整理了 7 個使用案例。我們會看到它們要完成各自的功能,分別需要使用 mark 中 的幾個位元位。
2.1 網路策略(network policy)
第一個場景是網路安全策略,這是 K8s 不可或缺的組成部分。
這種場景用一個位元位就夠了,可以表示兩個值:
- drop:白名單模式,預設全部 drop,顯式配置 allow 列表
- allow:黑名單模式,預設全部 allow,顯式配置 drop 列表
預設 drop 模式
預設情況下, K8s 會自帶一條 iptables 規則,drop 掉沒有顯式放行(allow)的流量 。
工作機制比較簡單:
首先,在一條 iptables chain 中給經過的包打上一個 drop 標記(佔用 skb->mark
中一個位元就夠了),
(k8s node) $ iptables -t nat -L ... Chain KUBE-MARK-DROP (0 references) target prot opt source destination MARK all -- anywhere anywhere MARK or 0x8000 # 所有經過這條規則的包執行:skb->mark |= 0x8000
稍後在另一條 chain 中檢查這個標誌位,如果仍然處於置位狀態,就丟棄這個包:
(k8s node) $ iptables -L Chain INPUT (policy ACCEPT) target prot opt source destination KUBE-FIREWALL all -- anywhere anywhere # 如果這條規則前面沒有其他規則,就會跳轉到下面的 KUBE-FIREWALL ... Chain KUBE-FIREWALL (2 references) target prot opt source destination DROP all -- anywhere anywhere # /* drop marked packets */ mark match 0x8000/0x8000
預設 allow 模式
這是白名單模式的變種:先給每個包打上允許通行(allow)標記,也是佔用一個位元,稍 後再通過檢查 skb->mark
有沒有置位來決定是否放行。
另一個類似的場景是 加解密 : 對需要加密的流量設定某些 mark,然後在執行加密的地方做檢查,對設定了 skb->mark
的執行加密,沒有設定的不執行。
通用處理模式
總結起來,這些場景的使用模式都是類似的: 通過 packet mark 和 iptables 規則實現複雜的流量路徑控制 ,
skb->mark
典型場景:netfilter -> netfilter 流量過濾。
2.2 透明加密(transparent encryption)
加解密需要兩個位元:一個加密標誌位,一個解密標誌位。
-
常規的做法就是設定加密位元位,表示接下來需要對這個包做加密;或者設定解密位表示 要做解密。
-
變種:有很多的可用祕鑰,在 mark 存放要用的祕鑰索引(index)。
典型場景:{ eBPF, netfilter } -> xfrm
2.3 Virtual IP Service(DNAT)
這種 Service 會有一個 VIP 作為入口,然後通過 DNAT 負載均衡到後端例項(backends)。
典型情況下,完成這個功能需要一或兩個位元,設定之後來表示需要對這些包做 DNAT。但 嚴格來說,這不是唯一的實現方式。你也可以自己寫一些邏輯來匹配目的 IP 和埠,然後 對匹配到的包執行 DNAT。
如果核心版本較老,那我們基於 eBPF 的 Service 實現可能會受限,此時就需 要與其他軟體協同工作才能提供完整的 Service 功能。
我遇到過的一個場景是 OVS bridge(OVS -> routing -> OVS)。OVS 會設定一些 mark, 然後傳給核心的策略路由模組,核心做策略路由之後再重新轉發回 OVS,在 OVS 完成最終的 DNAT。
我遇到的最複雜的場景可能是 kube-router,我們會將 Service 資訊寫入核心,kube-router 會檢視 Service 列表,提取三元組雜湊成 30bit 寫入 skb-mark
,稍後核心裡的 IPVS 再根據規則匹配這 些 mark 做某些負載均衡。
典型場景:{ eBPF, netfilter } -> netfilter
2.4 IP Masquerade(動態 SNAT)
和前面 Service/DNAT 類似,這裡是設定某些位元位來做 SNAT。例如在前面某個地方設定 mark, 稍後在 IPVS 裡檢查這個 mark,然後通過 IP masquerade 做某些形式的負載均衡。
兩個變種:
-
設定一個位元位,表示不要做 SNAT(1 bit, skip SNAT)。
網路外掛負責配置容器的網路連通性。但容器能否被叢集外(或公網)訪問就因外掛 而異了。如果想讓應用被公網訪問,就需要通過某種方式配置一個公網 IP 地址。 這裡討論的就是這種場景。
典型情況下,此時仍然只需要一個 bit,表明 不要對設定了 mark 的包做 SNAT(非公 網流量) ;沒有設定 mark 包需要做 masquerade/SNAT,這些是公網流量。
具體到 Cilium CNI plugin,可以在建立 pod 時宣告 帶哪些 label 的 pod 在出叢集時( egress)應當使用哪個特定的 源 IP 地址 。例如,一臺 node 上運行了三個應用的 pod,這些 pod 訪問公網時,可以分別使用不同的 src ip 做 SNAT 到公網。
上面的場景中, 連線都是主動從 node 發起的 ,例如,node 內的應用主動訪問公 網。實際中還有很多的連線是 從外部發起的,目的端是 node 內的應用 。例如,來 自 VPN 的訪問 node 內應用的流量。
這種情況下,如果將流量轉發到本機協議棧網路,可以給它們打上一個 mark,表示要 做 SNAT。這樣響應流量也會經過這個 node,然後沿著反向路徑回到 VPN 客戶端。
-
用 32bit,選擇用哪個 SRC IP 做 SNAT/Masquerade。
典型場景:{eBPF, OVS, netfilter} -> netfilter
2.5 Multi-homing
非對稱路徑
這種場景在 AWS 環境中最常見。
背景資訊:每個 AWS node(EC2)都有
- 一個 primary 裝置,提供了到外部的網路連通性, node 預設路由走這裡 。
- 多個 secondary 裝置( node 預設路由不經過它們 ),每個裝置上有多個獨立的 IP 地 址,典型情況下是 8 個。在 node 上部署容器時,會從這些 secondary IP 地址中選擇 一個來用。當 8 個地址用完之後,可以再分配一個 secondary device attach 到 EC2 (所以每個 EC2 都有多個網路)。
當 容器訪問另一臺 node 上的容器時 ,流量需要傳送到對端容器所佔用的那個 secondary 裝置上。 這裡會用到 源路由 (source routing),也叫 策略路由 (policy routing)。 預設情況下,策略路由的工作方式是:
- 首先匹配包的 SRC IP,選擇對應的路由表,
- 然後在該路由表中再按 DST IP 匹配路由,
- 對於我們這裡的場景,最終會匹配到經過某條 secondary device 的路由,然後通過這 個 secondary interface 將包傳送出去。
當實現 Service 或類似功能時,對於接收端 node,主要有兩種型別的流量:
- 從 secondary device 進來的、目的是本機容器的外部流量。也就是上面我們提到的流 量(Pod-to-Pod 流量);
- 從 primary device 進來的、目的是本機容器的流量(例如 NodePort Service 流量)。
對於第二種,不做特殊處理就會有問題:
- 請求能正常從 primary device 進來,然後轉發給容器,被容器正確處理,至此這裡都沒問題,
- 但從上面的分析可知,如果沒有額外處理,響應流量會從 secondary 裝置傳送到其所在 的網路。
導致的問題是:來的路徑和回去的路徑不一致(非對稱路徑),回包會被丟棄。
這裡就是 最經典地會用到 mark 的地方 :
- 當流量從 primary 裝置進來時,設定一個位元位,記錄在連線跟蹤的 mark(conntrack mark)中。
- 當響應從 pod 發出時,查詢連線跟蹤記錄。如果設定了這個 mark,就表明這個包需要從 主裝置路由出去。
這樣就解決了非對稱路徑的問題。
管理網與業務網分離:socket mark
另一個是 VPN 場景,每臺 node 上可能會跑一個 management agent,負責配 置 VPN 網路。
這種情況下,肯定不能將管理網本身的流量也放到 VPN 網路。 此時就可以用到 socket mark 。這個狀態會 傳遞給路由層 ,在路由決策時使用。
這樣做到了管理流量和 VPN 流量的分離。
典型:{ socket, netfilter } -> routing
2.6 應用身份(application identity)
Application identity (應用身份)用於網路層的訪問控制。
在 Cilium 中, 每個 endpoint 都對應一個 identity ( N:1
),表示這個容器的安全 身份。Identity 主要用來實現 network policy,佔用的位元數:
- 一般用
16bit
表示 (業界慣例),Cilium 單叢集 時也是這樣, - 如果需要 跨叢集/多叢集 做安全策略,那這個 identity 會擴充套件到
24bit
,多出來的 8bit 表示 cluster ID。
可以在出向(egress)和入向(ingress)做安全控制:
- 如果 pod 想訪問其他服務,可以在它的出向(egress)做策略,設定能訪問和不能訪問 哪些資源。如果沒有設定任何策略,就會使用 預設的 allow all 策略 。
- 在接收端 pod 的入向(ingress)也可以做策略控制,過濾哪些源過來的允許訪問,哪些不允許。
這裡比較好的一點是:可以 將 identity 以 mark 的方式打在每個包上 ,這樣看到 identity 就知道了包的來源,因此安全策略的實現就可以變得簡單:從包上提取 identity 和 IP、port 等資訊,去查詢有沒有對應的放行策略就行了。
當與別的系統整合時,這裡會變得更有意思。例如有個叫 portmap 的 CNI 外掛,可以做 CNI chaining,感興趣可以去看看。整合時最大的問題是,無法保證在打標(mark)和檢查 mark 之間會發生什麼事情。
典型路徑:{ eBPF, netfilter } -> routing -> eBPF
2.7 服務代理(service proxy)
這裡的最後一個案例是服務代理(service proxy)。
Proxy 會終結來自客戶端的請求,然後將其重定向到本機協議棧,隨後請求被監聽在本機協 議棧的服務(service)收起。
根據具體場景的不同,需要使用至少一個位元位:
-
1 bit, route locally
設定了這個位元位,就表示在本機做路由轉發。
我見過的大部分 service proxy 實際上只需要一個位元,但在實現上,有些卻佔用了整改 mark(16bit)。
-
16 bit tproxy port towards proxy
在老核心上,Cilium 會通過 mark 傳遞一個 16bit 的 tproxy port(從 eBPF 傳遞給 Netfilter 子系統),以此指定用哪個代理來轉發流量。
-
16+ bit Identity from proxy
還可以通過 proxy 傳遞 identity。 這樣就能夠在處理 flow 的整個過程中儲存這份狀態(retain that state)。
典型路徑:
- eBPF -> { netfilter, routing }
- netfilter -> routing
- socket -> { eBPF, netfilter },
3 思考、建議和挑戰
這裡討論一些使用 mark 時的挑戰,以及如何與其他網路應用互操作(interoperate), 因為多個網路應用可能在同時對核心網路棧進行程式設計(programming the stack)。
3.1 mark 使用方案設計
首先理解最簡單問題:如果要開發一個會設定 skb->mark
的網路應用,那 如何分配 mark 中的每個位元?
有兩種方式:
-
位元位方式:每個 bit 都有特定的語義。
例如 32bit mark 能提供 32 個功能,每個功能都可以獨立開啟或關閉。 因此,當有多個應用時,就可以說應用 A 使用這個位元,應用 B 使用另一個位元,合 理地分配這些位元空間。
這種方式的一個問題是: 最多隻能提供 32 種功能 。
-
Full mark 方式:將 mark 作為一個整形值。
這樣可以用到整個整形變數的空間,能提供的功能比前一種多的多。例如 32bit 可以提供 42 億個不同的值。
根據我的觀察,很多的軟體在實現中都只使用了一個 bit,如果想做一些更瘋狂和有趣的事 情,那需要將擴充到 32bit,然後在子系統之間傳遞這些資訊。
3.2 位元位過載
如果你需要 60 個功能,那顯然應該用 4 個位元位來編碼這些功能,而不是使用 60 個獨 立的位元位。但這種方式也有明顯的限制:每個功能無法獨立開啟或關閉。因此用哪種方式 ,取決於你想和哪個子系統整合。
解決這個問題的另一種方式是: 過載(overload)某些位元位 。
- 例如,同樣是最低 4bit,在 ingress 和 egress 上下文中,分別表示不同的含義。
- 又如,根據包的地址範圍來解釋 mark 的含義,到某些地址範圍的包,這些位元表示一種 意思;到其他地址範圍的,表示另一種意思。
這顯然帶來了一些有趣的挑戰。一旦開始 overload 這些 marks,理論上總能構建出 能與其他軟體互操作的軟體。
這個過程中,找到從哪裡開始下手是很重要的。這就是我開始注意到前面提到的那些網路軟 件的原因之一:因為 所有這些工作最後都是與人打交道 。例如, iptables 設定了第 15bit 表示 drop 的事實,意味著 其他所有外掛都要遵守 這些語義 ,並且其他人要避免使用這個 bit。這樣當多個不同的網路外掛或軟體需要協 同工作來提供一組互補或增強的功能時,它們才不會彼此衝突。即, 不同外掛或軟體之間要 對位元位的語義有一致的理解 。
對於 Cilium 來說,這是由我們的使用者驅動的,如果使用者已經使用了某些外掛,並且希望在 這個外掛之外同時執行 Cilium,我們就只能從尋求與這些外掛的相容開始。
3.3 釋出和遵守 mark 方案
那麼,我們該如何共享自己的使用方案呢?即,在與其他外掛一起執行時,哪個位元位表示 什麼意思,這些位元位提供哪些功能。
從網路應用角度來說,這裡很重要的一點是: 理清自己的功能,以及協同工作的他軟體的 功能 。例如,如果使用者同時運行了 Cilium CNI 和另一個 CNI 外掛,後者也提供了加 密功能;那從 Cilium 的角度來說,我們就無需開啟自己的加密功能,讓底層外掛來做就行了 。
從現實的角度來說,
- 讓開發者遵守這些規範、理解它們是如何工作的,是一件有成本的事情;
- 從複雜度的角度來說,如何管理和部署也是一件很有挑戰的事情,因為更多的軟體或外掛 意味著更有可能出錯,排障也會更加困難。本文主要關注在如何分配位元,如何定義語 義,如何與其他應用共享互操作。
3.4 深入理解網路棧
需要說明的是,在實際中,mark 並不是唯一軟體的整合點(integration point)。
不同的外掛可能都會插入 iptables 規則,匹配特定的目的地址、源地址等;甚至還可能 用 mark 來做新的策略路由,應用在不同的領域。
所以,如果你真要實現一個功能,能用到的資訊其實不止是 32bit 的 mark,還可以用包頭 中的欄位、連線跟蹤中的狀態(conntrack status)等等。因此最終,你會坐下來研究網路 棧流程圖,理解你的包是如何穿過 TC、eBPF 和 Netfilter 等的。
此外,還需要理解不同的軟體、它們各自的機制,以及包經過這些不同的子系統時的不同路 徑,這會因你啟用的功能以及包的源和目的地址等而異。例如,這裡最常見的場景之一是: 請求流量是正常的,但響應流量卻在某個地方消失了,最終發現是因 multi-home 問題被路 由到了不同的裝置。
3.5 少即是多
如果有更多的 bit 可用,你會用來做什麼?
對於 Cilium 來說,我們正在積極探索 用 eBPF 統一子系統之間的協作方式 , 這樣就可以避免在 eBPF 和 Netfilter、Conntrack 等子系統之間傳遞大量元資料了。 如果能原生地在 eBPF 中實現處理邏輯,那就能使用 eBPF 領域的標準工具, 進而就能推理出包的轉發路徑等等,從而減少 mark 的使用。在這種方式下,和其他 軟體整合就會輕鬆很多,因為我們並沒有佔用這些 mark。
當然,這並不是說只有 eBPF 能統一子系統之間的協作,你用 OVS、Netfilter 等等方式, 理論上也能統一。
另一個經常會討論到的問題是:我們 能否擴充套件 mark 空間 ?直接擴充套件 skb->mark
字 段我認為是太可能的。
- 相比之下,新增一個 skb mark extension 之類的新欄位,用這個欄位做一些事情還是有 可能的,這樣就有更多的通用位元(generic bits)來做事情。
- 另一種方式是:將某些使用場景規範化。從通用空間中將某些 bits 拿出來,單獨作為某 些場景的專用位元,定義它們的語義,這樣它們之間的互操作就方便多了。但這種方式 會消耗一部分 mark 空間,留給其他網路應用的 mark 空間會變得更小。
4 總結
最後總結,packet mark 是一種非常強大的機制,使我們能 在不同子系統之間傳遞各種狀態資訊 。
另外,如何定義 mark 的語義,使用者有很大的靈活性。當然,反面是如果你的軟體想要 和其他網路軟體協同工作,那必須事前約定,大家使用的 mark 不能有衝突, 並且彼此還要理解對方的語義(例子:kube-proxy + Cilium 場景)。 這顯然會帶來很多的不確定性,當你試圖實現某些新功能時,可能就會發現這個 mark 對我 來說很有用,但會不會和別的軟體衝突,只有等實際部署到真實環境之後可能才會發現。很 可能直到這時你才會發現:原來這個 mark 已經被某個軟體使用了、它的使用方式是這樣的 、等等。
因此,我希望前面提到的 mark registry 能幫我們解決這個問題,希望大家將自己在用的 mark 以文件的方式集中到那個 repo。這也算是一個起點,由此我們就能知道,哪些應用的 mark 方式是和我的有衝突的。然後就能深入這個特定專案的原始碼,來看能否解決這些衝突 。
另外應該知道,mark 能提供的功能數,以及相應的場景數,要遠遠多於 mark 的位元數。 關鍵在於你需要多少功能。
5 相關連結
Cilium
- http://cilium.io
- http://cilium.io/slack
- http://github.com/cilium/cilium
- http://twitter.com/ciliumproject
Mark registry
- http://github.com/fwmark/registry
- [譯] 寫給工程師:關於證書(certificate)和公鑰基礎設施(PKI)的一切(SmallStep, 2018)
- [譯] 基於角色的訪問控制(RBAC):演進歷史、設計理念及簡潔實現(Tailscale, 2021)
- [譯] Control Group v2(cgroupv2 權威指南)(KernelDoc, 2021)
- [譯] Linux Socket Filtering (LSF, aka BPF)(KernelDoc,2021)
- [譯] LLVM eBPF 彙編程式設計(2020)
- [譯] Cilium:BPF 和 XDP 參考指南(2021)
- BPF 進階筆記(三):BPF Map 核心實現
- BPF 進階筆記(二):BPF Map 型別詳解:使用場景、程式示例
- BPF 進階筆記(一):BPF 程式型別詳解:使用場景、函式簽名、執行位置及程式示例
- 原始碼解析:K8s 建立 pod 時,背後發生了什麼(四)(2021)
- 原始碼解析:K8s 建立 pod 時,背後發生了什麼(三)(2021)
- [譯] 邁向完全可程式設計 tc 分類器(NetdevConf,2016)
- [譯] 雲原生世界中的資料包標記(packet mark)(LPC, 2020)
- [譯] 利用 eBPF 支撐大規模 K8s Service (LPC, 2019)
- 計算規模驅動下的網路方案演進
- 邁入 Cilium BGP 的雲原生網路時代
- [譯] BeyondProd:雲原生安全的一種新方法(Google, 2019)