從 iptables 談 ServiceMesh 流量攔截

最近研究學習 Kubernetes 和 ServiceMesh 過程中都看到了 iptables 發揮重要作用獨擋一面的場景。
Kubernetes 中 iptables 作爲 kube-proxy 裏控制流量轉發的核心模式,通過在目標 node 的 iptables 中增加一些自定義鏈對流經到該 node 的數據包做 DNAT 和 SNAT 操作以實現路由、負載均衡和地址轉換。
ServiceMesh 服務網格中 Istio 通過 init 容器(啓動命令爲 istio-iptables)給 Sidecar 容器即 Envoy 代理做初始化,設置 iptables 端口轉發,從而實現流量劫持 & 轉發控制等服務治理相關。

But,仔細搜索大腦海馬體,發現對 iptables 的認知並不是那麼清晰,所以一起重新梳理一遍吧~~

一、iptables 基礎

提起 iptables 相信大家的第一反應是 -- 防火牆,不知道你們是不是,反正我的第一反應是這樣的,偷笑……
但其實 Linux 中包過濾防火牆全稱爲 Netfilter/Iptables。位於內核空間的 Netfilter 纔是防火牆真正的安全框架,它可以根據規則實現數據包的過濾、網絡地址轉換、內容修改等。Iptables 是位於用戶空間的一個命令行工具,用來操作 Netfilter,對 NetFilter 中的規則進行增刪改查,從而將用戶的安全設定同步進內核空間的 Netfilter。

後面用 iptables 代替 Netfilter/Iptables。我們先用一個思維導圖來看下 iptables 的核心概念(按優先級畫的表,實際上使用頻率及場景的話 filter 表和 nat 表使用的更多些)。

根據思維導圖我們可以看到 iptables 中有 五條鏈 -- PREROUTING 路由前 / INPUT 流入 / FORWARD 轉發 / OUTPUT 流出 / POSTROUTING 路由後 和 四張表 -- Raw/Mangle/Nat/Filter,各個表和鏈中又可以設定多條規則,從而對數據包進行控制。

裏面有幾個名詞需要解釋下:
鏈 (Chain):之所以先解釋鏈是因爲內核源碼裏最直接的 iptables 線索是通過鏈來體現的(思維導圖裏爲了按功能區分先畫的表)。鏈其實是按數據包流向劃分的一些鉤子函數 HOOK,內核源碼裏搜索 NF_HOOK 可以看到相關內容,根據數據包所處的不同網絡協議層,都有不同的鏈 HOOK,如 ARP 層、Bridge 層、IP 層、Application 層,具體各層的 HOOK 細節可以參考 NetFilter hooks[1]

表 (Table):表是按功能化分的規則集合,如 nat 表主要負責網絡地址轉換,filter 表主要負責過濾,mangle 表主要負責修改數據包標記等,就如同一個餐廳裏的服務員、大廚、配菜員、洗消工人、保安等分工不同。每條鏈中根據功能不同可以使用特定的幾個或全部表的規則。
規則 (Policy):按指定的條件來匹配數據包,匹配成功後執行相應的通過 / 丟棄等動作處理。

打個比方,有一家高檔的西餐廳:
**進門時,**服務員會先審視一遍顧客,因爲餐廳規定需着正裝進入所以光膀子大褲衩人字拖的就被拒之門外了,此時顧客還不服氣大吵大鬧的,這時候保安只能按違反公共環境保持安靜的要求把他們帶走了。另外一位顧客穿着得體但是帶了寵物,服務員指着旁邊告示牌上寫的告示禮貌的提醒顧客將寵物暫存在了前臺準備的寵物籠中。
**第二步開始點餐,**顧客點了一份七分熟的牛排和一份魚香肉絲,等會兒,這是西餐廳不賣魚香肉絲啊,西餐廳出現魚香肉絲會影響餐廳形象的,服務員非常有禮貌的拒絕了顧客魚香肉絲的需求並告知顧客魚香肉絲可以去旁邊的川菜館享用,同時正常下單了牛排的需求。
**第三步製作階段,**配菜員配菜,需按餐廳宣傳的牛排肉質的要求及分量進行配菜,然後交給大廚進行烹飪,這裏注意牛排要求七分熟,所以大廚特別認真的盯緊火候生怕過大或者過小,畢竟火候過了或者欠了都不滿足顧客需求,都會被顧客投訴。
**第四步買單階段,**顧客享用完美味的餐食後,離開餐桌準備買單,這時服務員會負責引導顧客到達收銀處進行買單,同時洗消工人進行餐桌餐具及廚餘垃圾的洗消工作。

結合這個場景屢一下鏈、表和規則的關係:
上客階段(鏈 1):服務員(表 1,保障餐廳形象)-- 規則 1 要求穿着得體、規則 2 要求禁止攜帶寵物;保安(表 5,維持餐廳秩序) -- 規則 1 禁止大吵大鬧
點餐階段(鏈 2):服務員(表 1,保障餐廳形象)-- 拒絕提供非西餐食品
製作階段(鏈 3):配菜員(表 2,保障餐廳食材品質)-- 嚴格篩選高品質肉類及蔬菜;大廚(表 3,保障餐廳食物口感)-- 嚴格控制火候、調味劑分量及每個步驟時長等
買單階段(鏈 4):服務員(表 1,保障餐廳形象)、洗消工人(表 4,保障餐廳衛生環境)-- 都有具體的衛生要求標準

現在是不是清楚些了,iptables 中鏈是主線條,然後每條鏈按需要執行特定功能表中的一條或多條規則,如上客階段需要服務員表中的規則和保安表中的規則。

iptables 中的四張表按優先級 raw –> mangle –> nat –> filter 執行。然後具體的規則按照從前往後的優先級執行,要求越嚴格的規則應該放在越靠前面。
思維導圖中對四張表分別做了簡要的功能概述,接下來我們看下五條鏈以及他們根據功能需要用到哪些表的規則。

現在我們對數據包的流向及相應的表和鏈的控制有了個大概的瞭解,如果還需要更詳細的逐步的查看請參考 iptables 四個表五條鏈 [2]。

二、iptables 命令使用

官方定義的命令格式如下:

iptables -t 表名 <-A/I/D/R/L/F> 規則鏈名 [規則號] <-i/o 網卡名> -p 協議名 <-s 源IP> --sport 源端口 <-d 目標IP> --dport 目標端口 -j 動作  

-t 指定表名,未指定表名時默認爲 Filter 表
-A和-I都是添加規則,-A增加的規則放在現有規則的最後,-I添加的規則放在規則號指定的位置,該位置原先的規則往後順位。
-D 刪除規則號指定的規則
-R 替換規則號指定的規則
-L 查看相應的規則
-F 清楚某條鏈或者表的規則
-i/o 指定輸入和輸出的網卡
-p 指定數據包協議,如 tcp、udp、icmp 等,這裏支持簡單的表達式,如 -p !tcp 去除 tcp 外的所有協議
-s和-sport分別指定數據包源 IP 地址及端口
-d和-dport分別指定數據包目標 IP 地址及端口
-j 指定前述的參數匹配上數據包以後執行的動作。常用的處理動作包括 ACCEPT 放行、REJECT 拒絕、DROP 丟棄、REDIRECT 重定向、DNAT 修改目的 IP 及端口、SNAT 修改源 IP 及端口等等

另外還有一些高級的參數,如參數 tcp-flags (只過濾 TCP 中的一些包,比如 SYN 包,ACK 包,FIN 包,RST 包等等)、參數 limit 限制數據包的平均流量、參數 state 過濾特定狀態 (如 Established、Invalid 等) 的數據包,類似的參數還有不一一列舉了。

下面結合一些實際應用場景看看具體怎麼使用吧~~

  1. 查看服務器上 nat 表的所有規則
[@hbhly_65_203 ~]# iptables -L -n -v -t nat
Chain PREROUTING (policy ACCEPT 11 packets, 2250 bytes)
pkts bytes target     prot opt in     out     source       destination

Chain INPUT (policy ACCEPT 11 packets, 2250 bytes)
pkts bytes target     prot opt in     out     source       destination

Chain OUTPUT (policy ACCEPT 35 packets, 2838 bytes)
pkts bytes target     prot opt in     out     source       destination

Chain POSTROUTING (policy ACCEPT 35 packets, 2838 bytes)
pkts bytes target     prot opt in     out     source       destination

這裏再次看到了 nat 表只包含了 PREROUTING/INPUT/OUTPUT/POSTROUTING 四條鏈的規則,不包含 FORWARD 鏈的規則。

  1. 禁用 SSHD 默認的 22 端口
iptables -t filter -A INPUT -p tcp --dport 22 -j DROP
  1. 只允許特定網段 10.160.0.0/16 訪問本機的 10.160.100.1 的 SSHD(22 端口) 服務
 #設置默認的drop,再允許特定的網段進入和出去
 iptables -P INPUT DROP
 iptables -P OUTPUT DROP
 iptables -P FORWARD DROP

 iptables -t filter -A INPUT -s 10.160.0.0/16 -d 10.160.100.1 -p tcp --dport 22 -j ACCEPT
 iptables -t filter -A OUTPUT -s 10.160.100.1 -d 10.160.0.0/16 -p tcp --dport 22 -j ACCEPT
  1. 過濾掉狀態有問題的 http 包。只允許 http80 端口且限定連接狀態爲 Established 和 Related 的數據包
iptables -A INPUT -p tcp  --sport 80 -m state --state ESTABLISHED,RELATED -j ACCEPT
  1. 開啓兒童上網模式,星期一到星期五的 8:00-21:00 禁止遊戲相關網頁”game“
iptables -I FORWARD -s 192.168.0.0/24 -m string --string "game" -m time --timestart 8:00 --timestop 21:00 --days Mon,Tue,Wed,Thu,Fri -j DROP
  1. 生產環境 mysql 數據庫僅允許內網特定 ip 訪問
iptables –A INPUT –s 10.160.41.1 –p tcp –dport 3306 –j ACCEPT
  1. 將目的 IP 爲 10.160.132.55 且目的端口爲 9090 的我們做 DNAT 修改目標地址處理,重定向到 10.162.37.1:8080
iptables  -A INPUT -d 10.160.132.55 -p tcp --dport 9090 -j DNAT --to 10.162.37.1:8080
  1. 攔截所有入站 tcp80 端口和 8080 端口數據包重定向到某個代理服務的 15001 端口進行統一處理
iptables -A INPUT -p tcp --dport 80,8080 -j REDIRECT --to-ports 15001

ps: 這裏已經看到 ServiceMesh 服務網格中 sidecar 模式的 Envoy 代理裏實現流量攔截從而進行統一的服務治理的一點身影了~~~

三、ServiceMesh 中 iptables 的發揮

ServiceMesh 中 iptables 怎麼發揮作用的吶?Istio 通過爲 Pod 注入 Init 容器來爲 Pod 設置 iptables 規則進行端口轉發。Init 容器是一種專用容器,它在應用程序容器啓動之前運行,用來包含一些應用鏡像中不存在的實用工具或安裝腳本。它的啓動命令是

istio-iptables -p 15001 -z 15006 -u 1337 -m REDIRECT -i '*' -x "" -b '*'

感興趣可以具體查看 istio 中源碼 https://github.com/istio/istio/blob/master/tools/istio-iptables,這裏不再單獨拿出來說了。

istio 中 iptables 命令格式如下:

istio-iptables -p PORT -u UID -g GID [-m mode] [-b ports] [-d ports] [-i CIDR] [-x CIDR] [-h]

  -p: 指定重定向所有 TCP 流量的 Envoy 端口(默認爲 $ENVOY_PORT = 15001)
  -u: 指定未應用重定向的用戶的 UID。通常,這是代理容器的 UID(默認爲 $ENVOY_USER 的 uid,istio_proxy 的 uid 或 1337)
  -g: 指定未應用重定向的用戶的 GID。(與 -u param 相同的默認值)
  -m: 指定入站連接重定向到 Envoy 的模式,“REDIRECT” 或 “TPROXY”(默認爲 $ISTIO_INBOUND_INTERCEPTION_MODE)
  -b: 逗號分隔的入站端口列表,其流量將重定向到 Envoy(可選)。使用通配符 “*” 表示重定向所有端口。爲空時表示禁用所有入站重定向(默認爲 $ISTIO_INBOUND_PORTS)
  -d: 指定要從重定向到 Envoy 中排除(可選)的入站端口列表,以逗號格式分隔。使用通配符“*” 表示重定向所有入站流量(默認爲 $ISTIO_LOCAL_EXCLUDE_PORTS)
  -i: 指定重定向到 Envoy(可選)的 IP 地址範圍,以逗號分隔的 CIDR 格式列表。使用通配符 “*” 表示重定向所有出站流量。空列表將禁用所有出站重定向(默認爲 $ISTIO_SERVICE_CIDR)
  -x: 指定將從重定向中排除的 IP 地址範圍,以逗號分隔的 CIDR 格式列表。使用通配符 “*” 表示重定向所有出站流量(默認爲 $ISTIO_SERVICE_EXCLUDE_CIDR)。

啓動之後 netstat 查看就發現相關端口了

咱們上面用到的那行啓動命令,直白的講就是讓 Envoy 代理可以攔截所有的進出 pod 的流量,所有入站(inbound)流量重定向到 15006 端口(sidecar),再攔截應用容器的出站(outbound)流量經過 sidecar 處理(通過 15001 端口監聽)後再出站。厲害了,大有一種不管什麼牛鬼蛇神都要來我這裏報個到,我來決定你們下一步的走向的架勢,就像 windows 的電腦管家拿到了絕對控制權還不是想彈個啥窗就彈個啥,所有流量都攔截了再進行流量控制、熔斷限流、負載均衡等服務治理就大有可爲了。
這個命令其實是 iptables 命令的變形,但是換湯不換藥,根本原理是一樣的,最終也是生成了一系列的 iptables 規則,可以服務器上自己驗證下,爲了方便標註每一步的過程我按代碼格式貼出來了~

$ iptables -t nat -L 
# PREROUTING 鏈:將所有入站 TCP 流量跳轉到 ISTIO_INBOUND 鏈上,這裏不要好奇怎麼又多出來了個上面沒講的鏈,iptables是可以自定義鏈的哈,k8s和istio中都有大量的自定義鏈
Chain PREROUTING (policy ACCEPT)
target         prot opt     source               destination
ISTIO_INBOUND  tcp  --      anywhere             anywhere

# INPUT 鏈:沒有規則,正常跳轉到OUTPUT鏈
Chain INPUT (policy ACCEPT)
 target     prot opt   source               destination

# OUTPUT 鏈:將所有出站數據包跳轉到 ISTIO_OUTPUT 鏈上
Chain OUTPUT (policy ACCEPT)
target        prot opt    source               destination
ISTIO_OUTPUT  tcp  --     anywhere             anywhere

# POSTROUTING 鏈:沒有規則
Chain POSTROUTING (policy ACCEPT)
target        prot opt    source               destination

# ISTIO_INBOUND 鏈:將所有目的地爲 9080 端口的入站流量重定向到 ISTIO_IN_REDIRECT 鏈上
Chain ISTIO_INBOUND (1 references)
target             prot opt   source               destination
ISTIO_IN_REDIRECT  tcp  --    anywhere             anywhere      tcp dpt:9080

# ISTIO_IN_REDIRECT 鏈:將所有的入站流量跳轉到本地的 15001 端口,至此成功將流量攔截到了Envoy代理
Chain ISTIO_IN_REDIRECT (1 references)
target     prot opt    source               destination
REDIRECT   tcp  --     anywhere             anywhere             redir ports 15001

# ISTIO_OUTPUT 鏈:這裏需要注意區分兩種情況,所有非 localhost 的流量全部轉發到 ISTIO_REDIRECT。爲了避免流量在該 Pod 中無限循環,所有到 istio-proxy 用戶空間的流量(啓動命令行中通過參數有進行過濾)都返回到它的調用點中的下一條規則即 OUTPUT 鏈,因爲跳出 ISTIO_OUTPUT 規則之後就進入下一條鏈 POSTROUTING。如果目的地非 localhost 就跳轉到 ISTIO_REDIRECT;如果流量是來自 istio-proxy 用戶空間的,那麼就跳出該鏈,返回它的調用鏈繼續執行下一條規則(OUPT 的下一條規則,無需對流量進行處理);所有的非 istio-proxy 用戶空間的目的地是 localhost 的流量就跳轉到 ISTIO_REDIRECT
Chain ISTIO_OUTPUT (1 references)
target          prot opt     source               destination
ISTIO_REDIRECT  all  --      anywhere            !localhost
RETURN          all  --      anywhere             anywhere     owner UID match istio-proxy
RETURN          all  --      anywhere             anywhere     owner GID match istio-proxy 
RETURN          all  --      anywhere             localhost
ISTIO_REDIRECT  all  --      anywhere             anywhere

# ISTIO_REDIRECT 鏈:將所有流量重定向到 Envoy的 15001 端口
Chain ISTIO_REDIRECT (2 references)
target     prot opt    source               destination
REDIRECT   tcp  --     anywhere             anywhere      redirect ports 15001

結合官網給的過程圖更容易理解些,特別詳細了,順着不同顏色的箭頭按數字順序看就 OK,我就不再獻醜了~~

怎麼樣,看似高深莫測的 ServiceMesh 服務網格經由這麼一看是不是感覺沒那麼神祕了吶,當然也不是說這就是它的全部了,iptables 只是其流量攔截的基本技術面,其他內容後面有空可以再一起深挖。
與此同時,不知道你有沒有發現問題,與傳統服務模式相比,這種所有數據包要到達目的地每一步都要經過一系列的攔截代理轉發,勢必會對服務的性能及負載等產生或多或少的影響,這也是網格技術如此火熱卻長期有價無市沒有普及應用的一個重要原因,當然還有其他的一些原因,同時社區也在竭盡全力的去優化這部分,相信未來可期。

四、總結

本文中我們一起梳理了 iptables 的基礎概念,如各種規則、四個表、五條鏈等,又一起根據實際場景探討了 iptables 命令的使用,而後進一步將其結合時下火熱的 ServiceMesh 技術中 Envoy 代理進行流量攔截的原理對 sidecar 模式進行了理解,Kubernetes 中也有 iptables 的深入應用,後面我們找時間再具體談論這塊兒,敬請期待~~

參考資料

[1]

NetFilter hooks: https://wiki.nftables.org/wiki-nftables/index.php/Netfilter_hooks

[2]

iptables 四個表五條鏈: https://blog.csdn.net/longbei9029/article/details/53056744

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/nRTMu4J9eBOu1ydkII_toQ