最近在面試的時候問了不少 network request 如何到 k8s service backend 的問題,覺得可以整合一下網絡上的資料,這篇主要討論 iptables proxy mode。大部分的情況沒有在使用 userspace proxy modes, ipvs proxy mode 可能要等到下一次討論。
事先準備要先了解 iptable 工作機制,建議可以看這一篇:https://phoenixnap.com/kb/iptables-tutorial-linux-firewall,當然 wikipedia 也是寫的不錯,我下面的文字也大多數引用:https://zh.wikipedia.org/wiki/Iptables
快速帶過 iptable說到 iptable 要先了解 Tables, Chains 和 Rueles。
Table 指不同類型的封包處理流程,總共有五種,不同的 Tables 處理不同的行為raw:處理異常,追蹤狀態 -> /proc/net/nf_conntrackmangle:處理封包,修改 headler 之類的Chains 來對應進行不同的行為。像是 「filter」 Tables 進行封包過濾的流程,而 「nat」 針對連接進行位址轉換操作。Chains 裡面包含許多規則,主要有五種類型的 ChainsPREROUTING:處理路由規則前通過此 Chains,通常用於目的位址轉換(DNAT)FORWARD:本機轉發的封包通過此 Chains。POSTROUTING:完成路由規則後通過此 Chains,通常用於源位址轉換(SNAT)Rules 規則會被逐一進行匹配,如果匹配,可以執行相應的動作大致的工作流向情況分兩種:NIC → PREROUTING → INPUT → Local process
Local process → OUTPUT → POSTROUTING → NICNIC→PREROUTING → FORWARD → POSTROUTING→NIC下面是比較詳細的流程,有包含 EBTABLES,但這個看久頭會昏,我這次會主要討論 Network Layer 這一部分,然後用上面這張比較精簡的圖
Netfilter pic of wikipedia
Kube-proxy 修改了 filter,nat 兩個表,自定義了KUBE-SERVICES,KUBE-NODEPORTS,KUBE-POSTROUTING,KUBE-FORWARD,KUBE-MARK-MASQ 和 KUBE-MARK-DROP,所以我這次會 focus on filter ,nat 兩個 Table
1. filter table 有三個 Chain 「INPUT」 「OUTPUT」 「FORWARD」https://sites.google.com/site/mrxpalmeiras/linux/iptables-routing
kube-proxy 在 filter table 的 「INPUT」 「OUTPUT」 chain 增加了 KUBE-FIREWALL 在 「INPUT」 「OUTPUT」 「FORWARD」 chain 增加了 KUBE-SERVICES
KUBE_FIREWALL 會丟棄所有被 KUBE-MARK-DROP 標記 0x8000 的封包,而標記的動作可以在其他的 table 中(像是第二部分提到的 NAT table 中)
而 filter table 的 KUBE-SERVICES 可以過濾封包,假如一個 service 沒有對應的 endpoint,就會被 reject,這裡我先要建立一個 service 和沒有正確設定 endpoint。
kind: Service
apiVersion: v1
metadata:
name: test-error-endpoint
namespace: default
spec:
ports:
- protocol: TCP
port: 7777
targetPort: 7777
---
kind: Endpoints
apiVersion: v1
metadata:
name: test-error-endpoint
namespace: defaultservice cluster ip 為 10.95.58.92
kind: Service
apiVersion: v1
metadata:
name: test-error-endpoint
namespace: default
selfLink: /api/v1/namespaces/default/services/test-error-endpoint
uid: 5d415d63-6fc3-444e-8b5a-29015b436a83
resourceVersion: ' 73026369'
creationTimestamp: '2020-11-17T05:48:52Z'
spec:
ports:
- protocol: TCP
port: 7777
targetPort: 7777
clusterIP: 10.95.58.92
type: ClusterIP
sessionAffinity: None
status:
loadBalancer: {}再次檢查 iptable,就可以看到 default/test-error-endpoint: has no endpoints -> tcp dpt:7777 reject-with icmp-port-unreachable
2. nat table 有三個 Chain 「PREROUTING」 「OUTPUT」 「POSTROUTING」在前兩個封包處理流程是比較相似和複雜的,大體來說是藉由客制化的規則,來處理符合條件封包,幫它們找到正確的 k8s endpoint (後面會細講),在 POSTROUTING 主要是針對 k8s 處理的封包(標記 0x4000 的封包),在離開 node 的時候做 SNAT
(inbound) 在 「PREROUTING」 將所有封包轉發到 KUBE-SERVICES(outbound) 在 「OUTPUT」 將所有封包轉發到 KUBE-SERVICES(outbound) 在 「POSTROUTING」 將所有封包轉發到 KUBE-POSTROUTINGhttps://sites.google.com/site/mrxpalmeiras/linux/iptables-routing
當封包進入 「PREROUTING」 和 「OUTPUT」,會整個被 KUBE-SERVICES Chain 整個綁架走,開始逐一匹配 KUBE-SERVICES 中的 rule 和打上標籤。
nat tables
kube-proxy 的用法是一種 O(n) 算法,其中的 n 隨 k8s cluster 的規模同步增加,更簡單的說就是 service 和 endpoint 的數量。
KUBE-SERVICES
「我這裡會準備三個最常見的 service type 的 kube-proxy 路由流程
」clusterIP 流程這裡我使用 default/jeff-api(clusterIP: 10.95.57.19) 舉例,我下面圖過濾掉不必要的資訊
最後會到實際 pod 的位置,podIP: 10.95.35.31,hostIP: 10.20.0.128 是該 pod 所在 node 的 ip
kind: Pod
apiVersion: v1
metadata:
name: jeff-api-746f4c9985-5qmw6
generateName: jeff-api-746f4c9985-
namespace: default
spec:
containers:
- name: promotion-api
image: 'gcr.io/jeff-project/jeff /jeff-api:202011161901'
ports:
- name: 80tcp02
containerPort: 80
protocol: TCP
nodeName: gke-sit-jeff-k8s-tw-01-default-pool-7983af35-ug91
status:
phase: Running
hostIP: 10.20.0.128
podIP: 10.95.35.31
nodePort 流程這裡有一個關鍵就是 KUBE-NODEPORTS 一定是在 KUBE-SERVICES 最後一項,iptables 在處理 packet 會先處理 ip 為 cluster ip 的 service,當全部的 KUBE-SVC-XXXXXX 都對應不到的時候就會使用 nodePort 去匹配。
我們看實際 pod 的資訊,podIP: 10.95.32.17,hostIP: 10.20.0.124 是其中一臺 node 的 ip
kind: Service
apiVersion: v1
metadata:
name: jeff-frontend
namespace: jeff-frontend
spec:
ports:
- protocol: TCP
port: 80
targetPort: 80
nodePort: 31929
selector:
app: jeff-frontend
clusterIP: 10.95.58.51
type: NodePort
externalTrafficPolicy: Cluster
---
kind: Pod
apiVersion: v1
metadata:
name: jeff-frontend-c94bf68d9-bbmp8
generateName: jeff-frontend-c94bf68d9-
namespace: jeff-frontend
spec:
containers:
- name: jeff-frontend
image: 'gcr.io/jeff-project/jeff/jeff-image:jeff-1.0.6.5'
ports:
- name: http
containerPort: 80
protocol: TCP
nodeName: gke-sit-jeff-k8s-tw-01-default -pool-b5692f8d-enk7
status:
phase: Running
hostIP: 10.20.0.124
podIP: 10.95.32.17
load balancer流程假如目的地 IP 是 load balancer 就會使用 KUBE-FW-XXXXXX,我建立一個 internal load balancer service 和 endpoint 指到 google postgresql DB(10.28.193.9)
apiVersion: v1
kind: Service
metadata:
annotations:
cloud.google.com/load-balancer-type: Internal
networking.gke.io/internal-load-balancer-allow-global-access: 'true'
name: external-postgresql
spec :
ports:
- protocol: TCP
port: 5432
targetPort: 5432
type: LoadBalancer
---
apiVersion: v1
kind: Endpoints
metadata:
name: external-postgresql
subsets:
- addresses:
- ip: 10.28.193.9
ports:
- port: 5432
protocol : TCP在 NAT table 看到 KUBE-MARK-MASQ 和 KUBE-MARK-DROP 這兩個規則主要是經過的封包打上標籤,打上標籤的封包會做相應的處理。KUBE-MARK-DROP 和 KUBE-MARK-MASQ 本質上就是使用 iptables 的 MARK 指令
-A KUBE-MARK-DROP -j MARK --set-xmark 0x8000/0x8000
-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000如果打上了 0x8000 到後面 filter table (上面提到 KUBE_FIREWALL )就會丟棄。
如果打上了 0x4000 k8s 將會在 PREROUTING table 的 KUBE-POSTROUTING chain 對它進行 SNAT 轉換。
POSTROUTING table
KUBE-POSTROUTING Chain
參考:
https://en.wikipedia.org/wiki/Netfilterhttps://zh.wikipedia.org/wiki/Iptableshttps://phoenixnap.com/kb/iptables-tutorial-linux-firewallhttps://www .cnblogs.com/charlieroro/p/9588019.htmlhttps://github.com/kubernetes/kubernetes/blob/master/pkg/proxy/iptables/proxier.gohttps://www.lijiaocn.com/%E9% A1%B9%E7%9B%AE/2017/03/27/Kubernetes-kube-proxy.htmlhttps://juejin.im/post/6844904098605563912https://tizeen.github.io/2019/03/19/ kubernetes-service-iptables%E5%88%86%E6%9E%90/https://www.hwchiu.com/kubernetes-service-ii.htmlhttps://www.hwchiu.com/kubernetes-service-iii .htmlhttps://www.itread01.com/content/1542712570.html「來源:https://jeff-yen.medium.com/iptables-proxy-mode-in-kube-proxy-6862bb4b329
」