概述
最近在面试的时候问了不少 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_conntrack
mangle
:处理封包,修改 headler 之类的nat
:进行位址转换操作filter
:进行封包过滤security
:SElinux 相关
Chains
来对应进行不同的行为。像是 “filter”Tables
进行封包过滤的流程,而 “nat” 针对连接进行位址转换操作。Chains
里面包含许多规则,主要有五种类型的Chains
PREROUTING
:处理路由规则前通过此Chains
,通常用于目的位址转换(DNAT)INPUT
:发往本机的封包通过此Chains
。FORWARD
:本机转发的封包通过此Chains
。OUTPUT
:处理本机发出的封包。POSTROUTING
:完成路由规则后通过此Chains
,通常用于源位址转换(SNAT)
Rules
规则会被逐一进行匹配,如果匹配,可以执行相应的动作
大致的工作流向情况分两种:
backend 为本机
1
2NIC → PREROUTING → INPUT → Local process
Local process → OUTPUT → POSTROUTING → NICbackend 目的地非本机
1
NIC→PREROUTING → FORWARD → POSTROUTING→NIC
下面是比较详细的流程,有包含 EBTABLES
,但这个看久头会昏,我这次会主要讨论 Network Layer 这一部分,然后用上面这张比较精简的图
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”
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。
1 | kind: Service |
service cluster ip 为 10.95.58.92
1 | kind: Service |
再次检查 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-POSTROUTING
当封包进入 “PREROUTING” 和 “OUTPUT”,会整个被 KUBE-SERVICES
Chain 整个绑架走,开始逐一匹配 KUBE-SERVICES
中的 rule 和打上标签。
kube-proxy
的用法是一种 O(n) 算法,其中的 n 随 k8s cluster 的规模同步增加,更简单的说就是 service 和 endpoint 的数量。
我这里会准备三个最常见的 service type 的
kube-proxy
路由流程
- cluster IP
- nodePort
- load balancer
clusterIP 流程
这里我使用 default/jeff-api(clusterIP: 10.95.57.19)
举例,我下面图过滤掉不必要的资讯
最后会到实际 pod 的位置,podIP: 10.95.35.31,hostIP: 10.20.0.128
是该 pod 所在 node 的 ip1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19kind: 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 的 ip1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36kind: 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)
1 | apiVersion: v1 |
在 NAT table 看到 KUBE-MARK-MASQ
和 KUBE-MARK-DROP
这两个规则主要是经过的封包打上标签,打上标签的封包会做相应的处理。KUBE-MARK-DROP
和 KUBE-MARK-MASQ
本质上就是使用 iptables 的 MARK 指令1
2-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 转换。
参考:
- https://en.wikipedia.org/wiki/Netfilter
- https://zh.wikipedia.org/wiki/Iptables
- https://phoenixnap.com/kb/iptables-tutorial-linux-firewall
- https://www .cnblogs.com/charlieroro/p/9588019.html
- https://github.com/kubernetes/kubernetes/blob/master/pkg/proxy/iptables/proxier.go
- https://www.lijiaocn.com/%E9% A1%B9%E7%9B%AE/2017/03/27/Kubernetes-kube-proxy.html
- https://juejin.im/post/6844904098605563912
- https://tizeen.github.io/2019/03/19/ kubernetes-service-iptables%E5%88%86%E6%9E%90/
- https://www.hwchiu.com/kubernetes-service-ii.html
- https://www.hwchiu.com/kubernetes-service-iii .html
- https://www.itread01.com/content/1542712570.html
来源:https://jeff-yen.medium.com/iptables-proxy-mode-in-kube-proxy-6862bb4b329