携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第25天。
在kubernetes集群内,访问一个pod应用可以通过pod IP,但是pod删除重建后IP会发生变化,而且在高可用多副本场景下直接通过IP访问,则会有客户端需要实现负载均衡等问题。为了解决上述问题,kubernetes在客户端和pod间引入了一个抽象层:service。
service通过labelSelector与pod关联,客户端通过service访问pod。访问类型可以分为两类:
-
通过service VIP访问:通过访问service clusterIP代理到后端pod。
-
通过service域名访问:在kubernetes集群内创建一个service对象后,kubernetes会生成一条
{serviceName}.{namespace}.svc.cluster.local
DNS记录,客户端通过该DNS记录最终访问pod。
通过service域名访问时,根据service的配置又可以分为两类:
-
normal service:客户端向集群内的DNS服务(如coreDNS)解析service域名后得到的是service的VIP,之后流程和上述通过service VIP访问一样。
-
headless service:所谓headless service是指service对象spec.clusterIP=None的service。解析headless service域名时,得到的不再是service VIP,而是某一个后端pod的IP。
kube-proxy
当在kubernetes集群中创建一个service对象后,controller-manager组件会自动创建一个和service名称相同的endpoints对象。endpoints对象中的IP就是该service通过labelSelector关联的且已就绪pod IP,controller-manager里的endpoints controller会监听pod的状态,实时维护endpoints对象中的数据。
新版本kubernetes(v1.21+)中,controller-manager组件除了会创建endpoints对象,一般还会创建两个endpointsSlice对象,这两个endpointsSlice对象一个负责ipv4,一个负责ipv6,名称是service名称加上一个后缀。
endpointsSlice会记录service关联的所有pod IP,即使这个pod非ready状态。
kube-proxy在kubernetes集群中以daemonSet形式启动,也就每个节点上都会启动一个kube-proxy服务的pod。kube-proxy的pod通过监听集群中service和endpoints资源的变化,刷新节点上的iptables/ipvs规则,从而实现访问service VIP代理到后端pod的功能
。
三种模式
kube-proxy先后出现了三种模式:userspace
、iptables
、ipvs
,其中userspace模式是通过用户态程序实现转发,因性能问题基本被弃用,当前主流的模式是iptables和ipvs。kube-proxy默认配置是iptables模式,可以通过修改kube-system命名空间下名称为kube-proxy的configMap资源的mode字段来选择kube-proxy的模式。
本文只介绍kube-proxy iptables模式实现的普通clusterIP类型的service原理,其它类型的service原理大家可以参考本文和其它资料自行分析。如无特殊场景,下文不再对kube-proxy模式和service类型做特别说明。
本文内容基于
kube-proxy:v1.15.0
+iptables:1.4.21
+flannel(vxlan)0.16.1:
。
iptables
之所以称作iptables模式,是因为该模式是基于iptables规则实现的。在《iptables》一文中,我们对iptables基础知识做了简单说明,其中有几个知识点在本文中需要用到,我们先回顾一下:
- 表的优先级
iptables 5张表的优先级从高到低分别是:raw、mangle、nat、filter、security。
- 链的执行顺序
iptables链下的规则是顺序执行的,且JUMP动作支持从一条链跳到另一条链。
- 链与表
iptables有5条系统预定义的链,也可以自定义规则链,但自定义链不能自动触发,因此一个数据包过来都是从系统预定义的链开始匹配的;iptables也不是每个链上都能挂表,链与表以及数据包流转逻辑如下图所示:
原理分析
环境准备
先从apiserver和controller-manager的启动参数中看看service VIP和pod IP的分配范围,这两个值会在后面用到:
# ps -ef | grep apiserver | grep -v grep | grep service-cluster-ip-range
... --service-cluster-ip-range=10.1.0.0/16 ...
# ps -ef | grep controller-manager | grep -v grep | grep cluster-cidr
... --cluster-cidr=10.244.0.0/16 ...
我们接着创建一个service以及一个2副本的deployment(注意使deployment的2个pod调度到不同的节点),deployment配置的pod镜像是nginx,nginx服务起来后会监听80端口。
Note:
在只有master和worker两个节点的环境下,为了使普通pod能调度到master节点,可以删除master上的污点(taint):
# kubectl taint node {masterNodeName} node-role.kubernetes.io/master-
service yaml:
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
spec:
selector:
app: nginx
ports:
- name: http
protocol: TCP
port: 8080
targetPort: 80
deployment yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
查看service、endpoint、node和pod信息:
# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-svc ClusterIP 10.1.190.219 <none> 8080/TCP 9m34s
# kubectl get endpoints
NAME ENDPOINTS AGE
nginx-svc 10.244.0.3:80,10.244.1.7:80 18m
kubectl get node -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
vm-12-11-centos Ready <none> 4d15h v1.15.0 10.0.12.11 <none> CentOS Linux 7 (Core) 3.10.0-1160.45.1.el7.x86_64 docker://18.6.1
vm-12-7-centos Ready master 4d15h v1.15.0 10.0.12.7 <none> CentOS Linux 7 (Core) 3.10.0-1160.45.1.el7.x86_64 docker://18.6.1
# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-55fc968d9-4pddn 1/1 Running 0 13m 10.244.1.7 vm-12-11-centos <none> <none>
nginx-55fc968d9-kh24k 1/1 Running 0 13m 10.244.0.3 vm-12-7-centos <none> <none>
iptables规则
kube-proxy生成的链都会包含KUBE
字段,执行命令的时候可以加上该字段过滤。我们来看看此时kube-proxy在iptables 5张表中生成的链与规则:
iptables详细参数可以参考文档:ipset.netfilter.org/iptables-ex…
- raw表
# iptables -t raw -S | grep KUBE#
- mangle表
# iptables -t mangle -S | grep KUBE#
- nat表(输出结果已过滤不相关规则)
# iptables -t nat -S | grep KUBE
-N KUBE-MARK-DROP
-N KUBE-MARK-MASQ
-N KUBE-NODEPORTS
-N KUBE-POSTROUTING
-N KUBE-SEP-FNO4E6JYD7EGUHTP
-N KUBE-SEP-SMDVMBZNJPO5AA7R
-N KUBE-SERVICES
-N KUBE-SVC-ELCM5PCEQWBTUJ2I
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A OUTPUT -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING
-A KUBE-MARK-DROP -j MARK --set-xmark 0x8000/0x8000
-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -m mark --mark 0x4000/0x4000 -j MASQUERADE
-A KUBE-SEP-FNO4E6JYD7EGUHTP -s 10.244.1.7/32 -j KUBE-MARK-MASQ
-A KUBE-SEP-FNO4E6JYD7EGUHTP -p tcp -m tcp -j DNAT --to-destination 10.244.1.7:80
-A KUBE-SEP-SMDVMBZNJPO5AA7R -s 10.244.0.3/32 -j KUBE-MARK-MASQ
-A KUBE-SEP-SMDVMBZNJPO5AA7R -p tcp -m tcp -j DNAT --to-destination 10.244.0.3:80
-A KUBE-SERVICES ! -s 10.244.0.0/16 -d 10.1.190.219/32 -p tcp -m comment --comment "default/nginx-svc:http cluster IP" -m tcp --dport 8080 -j KUBE-MARK-MASQ
-A KUBE-SERVICES -d 10.1.190.219/32 -p tcp -m comment --comment "default/nginx-svc:http cluster IP" -m tcp --dport 8080 -j KUBE-SVC-ELCM5PCEQWBTUJ2I
-A KUBE-SVC-ELCM5PCEQWBTUJ2I -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-SMDVMBZNJPO5AA7R
-A KUBE-SVC-ELCM5PCEQWBTUJ2I -j KUBE-SEP-FNO4E6JYD7EGUHTP
- filter表
# iptables -t filter -S | grep KUBE
-N KUBE-EXTERNAL-SERVICES
-N KUBE-FIREWALL
-N KUBE-FORWARD
-N KUBE-SERVICES
-A INPUT -m conntrack --ctstate NEW -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A INPUT -m conntrack --ctstate NEW -m comment --comment "kubernetes externally-visible service portals" -j KUBE-EXTERNAL-SERVICES
-A INPUT -j KUBE-FIREWALL
-A FORWARD -m comment --comment "kubernetes forwarding rules" -j KUBE-FORWARD
-A FORWARD -m conntrack --ctstate NEW -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A OUTPUT -m conntrack --ctstate NEW -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A OUTPUT -j KUBE-FIREWALL
-A KUBE-FIREWALL -m comment --comment "kubernetes firewall for dropping marked packets" -m mark --mark 0x8000/0x8000 -j DROP
-A KUBE-FORWARD -m conntrack --ctstate INVALID -j DROP
-A KUBE-FORWARD -m comment --comment "kubernetes forwarding rules" -m mark --mark 0x4000/0x4000 -j ACCEPT
-A KUBE-FORWARD -s 10.244.0.0/16 -m comment --comment "kubernetes forwarding conntrack pod source rule" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A KUBE-FORWARD -d 10.244.0.0/16 -m comment --comment "kubernetes forwarding conntrack pod destination rule" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
- security表
# iptables -t security -S | grep KUBE#
Note:
如果在nat表中看到KUBE-SEP-XXX链下的规则DNAT后是
--to-destination :0 --persistent --to-destination :0 --persistent --to-destination
之类的数据而不是--to-destination IP:端口
,可能是iptables版本太低了。
规则分析
从上文可以看出,kube-proxy iptables模式只在nat表和filter表增加了iptables规则,接下来我们分别对nat表和filter表的规则做详细梳理:
- nat表
前文提到过用户自定义链是不能直接执行的,只能从系统预定义的链跳转过来,在nat表规则中我们首先看到在系统预定义的PREROUTING、OUTPUT和POSTROUTING链中各增加了一条规则,其中PREROUTING链是外部包进来的第一条链,OUTPUT链是本地进程包出去的第一条链,POSTROUTING链是数据包出宿主机网路协议栈的最后一条链
。
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A OUTPUT -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING
可以看出PREROUTING链和OUTPUT链都有一条无条件匹配规则跳转到KUBE-SERVICES链,POSTROUTING链则是有一条规则跳转到KUBE-POSTROUTING链。
我们继续分析KUBE-SERVICES链和KUBE-POSTROUTING链。
1. KUBE-SERVICES链
-A KUBE-SERVICES ! -s 10.244.0.0/16 -d 10.1.190.219/32 -p tcp -m comment --comment "default/nginx-svc:http cluster IP" -m tcp --dport 8080 -j KUBE-MARK-MASQ
-A KUBE-SERVICES -d 10.1.190.219/32 -p tcp -m comment --comment "default/nginx-svc:http cluster IP" -m tcp --dport 8080 -j KUBE-SVC-ELCM5PCEQWBTUJ2I
上面是KUBE-SERVICES链的两条规则,第一条规则的意思是:源IP非10.244.0.0/16网段
&& 目的IP是10.1.190.219/32网段
&& TCP协议
&& 目的端口是8080
的数据包会跳转到KUBE-MARK-MASQ,KUBE-MARK-MASQ链规则如下,也就是会给数据包打上一个0x4000/0x4000的标记(标记只在当前网络协议栈有效)。
-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000
KUBE-MARK-MASQ链里没有DROP、REJECT等动作,于是执行完KUBE-MARK-MASQ链后又会回到KUBE-SERVICES链继续执行第二条规则。第二条规则是目的IP是10.1.190.219/32网段
&& TCP协议
&& 目的端口是8080
的数据包会跳转到KUBE-SVC-ELCM5PCEQWBTUJ2I链。
到这里我们把这两条规则综合起来看看:10.244.0.0/16是pod IP网段,10.1.190.219/32是service nginx-svc的clusterIP,也就是service VIP,目标端口8080是service中配置的port字段数值。于是我们可以“翻译”这两条规则为:非pod IP访问service会打上0x4000/0x4000标记
,所有访问service的数据包都会跳转到KUBE-SVC-XXX链
。
1.1 KUBE-SVC-ELCM5PCEQWBTUJ2I链
-A KUBE-SVC-ELCM5PCEQWBTUJ2I -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-SMDVMBZNJPO5AA7R
-A KUBE-SVC-ELCM5PCEQWBTUJ2I -j KUBE-SEP-FNO4E6JYD7EGUHTP
KUBE-SVC-ELCM5PCEQWBTUJ2I链中也有两条规则,第一条规则有--mode random --probability 0.50000000000
配置。iptables的random
随机功能中,--probability是随机概率
,--probability 0.5表示有0.5的概率执行本规则。注意如果当前规则命中了概率,执行完当前规则后是结束当前链而不是继续匹配后续的规则
。于是可以知道,这两条规则命中的概率都是0.5,这也是iptables实现service负载均衡的原理。
Note:
如果有三条规则且期望这三条规则的概率是一样(即都为1/3),那么这三条规则配置的--probability参数分别为
1/3
、1/2
、1
,而不是1/3、1/3、1/3。
1.1.1 KUBE-SEP-SMDVMBZNJPO5AA7R链
-A KUBE-SEP-SMDVMBZNJPO5AA7R -s 10.244.0.3/32 -j KUBE-MARK-MASQ-A KUBE-SEP-SMDVMBZNJPO5AA7R -p tcp -m tcp -j DNAT --to-destination 10.244.0.3:80
我们接着分析KUBE-SEP-SMDVMBZNJPO5AA7R链里的两条规则,第一条规则是源IP是10.244.0.3/32
的数据包会跳转到KUBE-MARK-MASQ打标记,最终所有的包都会DNAT到10.244.0.3:80
,10.244.0.3:80正是pod nginx-55fc968d9-kh24k的IP和service nginx-svc中配置的targetPort。于是这两条规则结合前面的规则翻译过来就是:如果pod通过service IP+端口访问自己会被打上标记,所有通过service IP+端口访问的包都会DNAT到{pod IP}:{service targetPort}
。
1.1.2 KUBE-SEP-FNO4E6JYD7EGUHTP链
-A KUBE-SEP-FNO4E6JYD7EGUHTP -s 10.244.1.7/32 -j KUBE-MARK-MASQ
-A KUBE-SEP-FNO4E6JYD7EGUHTP -p tcp -m tcp -j DNAT --to-destination 10.244.1.7:80
KUBE-SEP-FNO4E6JYD7EGUHTP链内容其实和1.1.1的KUBE-SEP-SMDVMBZNJPO5AA7R链内容差不多,就是另外一个pod的配置而已,这里不再赘述。
2. KUBE-POSTROUTING链
POSTROUTING有一条无条件跳转到KUBE-POSTROUTING链的规则,且KUBE-POSTROUTING链只有一条规则,该规则是匹配有0x4000/0x4000标记的数据包做MASQUERADE,MASQUERADE会做SNAT,且SNAT IP就是出口网络设备的IP
。
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -m mark --mark 0x4000/0x4000 -j MASQUERADE
- filter表
分析完了nat表我们再来看看filter表。kube-proxy在filter表的INPUT链、FORWARD链和OUTPUT链都有增加规则,但当前环境下KUBE-SERVICES链(注意不要与nat表KUBE-SERVICE链混淆)和KUBE-EXTERNAL-SERVICES链都是空链(即只有链没规则),于是:
1. INPUT链
INPUT链下当前只会跳到KUBE-FIREWALL链执行,KUBE-FIREWALL链下唯一一条规则也比较简单:打了0x8000/0x8000标记的包将被丢弃(DROP)。
-A KUBE-FIREWALL -m comment --comment "kubernetes firewall for dropping marked packets" -m mark --mark 0x8000/0x8000 -j DROP
2. OUTPUT链
当前OUTPUT链和INPUT链一样,也是跳转到KUBE-FIREWALL,不再赘述。
3. FORWARD链
-A FORWARD -m comment --comment "kubernetes forwarding rules" -j KUBE-FORWARD
-A FORWARD -m conntrack --ctstate NEW -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A FORWARD -s 10.244.0.0/16 -j ACCEPT
-A FORWARD -d 10.244.0.0/16 -j ACCEPT
FORWARD链上有四条规则,我们先看后三条规则。第二条规则是基于conntrack模块匹配数据包状态是NEW的,跳转到KUBE-SERVICES链,但当前KUBE-SERVICES链是空链;第三条规则匹配源IP是10.244.0.0/16网段ACCEPT,第四条规则匹配目的IP是10.244.0.0/16网段ACCEPT。
再来看第一条规则,第一条规则是无条件跳转到KUBE-FORWARD链,KUBE-FORWARD链内容如下:
-A KUBE-FORWARD -m conntrack --ctstate INVALID -j DROP
-A KUBE-FORWARD -m comment --comment "kubernetes forwarding rules" -m mark --mark 0x4000/0x4000 -j ACCEPT
-A KUBE-FORWARD -s 10.244.0.0/16 -m comment --comment "kubernetes forwarding conntrack pod source rule" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A KUBE-FORWARD -d 10.244.0.0/16 -m comment --comment "kubernetes forwarding conntrack pod destination rule" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
KUBE-FORWARD链也有4条规则,第一条规则基于conntrack模块匹配状态是INVALID的数据包DROP;第二条规则匹配0x4000/0x4000标记ACCEPT;第三条规则匹配源IP是pod IP网段且conntrack状态是RELATED或者ESTABLISHED的数据包ACCEPT;最后一条规则匹配目的IP是pod IP网段且conntrack状态是RELATED或者ESTABLISHED的数据包ACCEPT。
小结
上述iptables规则分析完之后,我们对kube-proxy iptables模式下实现的service功能做个总结:
kube-proxy iptables模式最主要的链是:KUBE-SERVICES、KUBE-SVC-XXX、KUBE-SEP-XXX。其中:
KUBE-SERVICES链
:访问集群内service的数据包入口,它会根据匹配到的service IP:port跳转到KUBE-SVC-XXX链。
KUBE-SVC-XXX链
:对应service对象,基于random功能实现了流量的负载均衡。
KUBE-SEP-XXX链
:通过DNAT将service IP:port替换成后端pod IP:port,从而将流量转发到相应的pod。
通过前文iptables规则的分析也可以知道,service clusterIP仅仅是iptables规则的匹配条件,物理上并不存在一个IP是service clusterIP的网络设备
,因此service clusterIP无法ping通:
# ping -c 1 10.1.190.219PING 10.1.190.219 (10.1.190.219) 56(84) bytes of data.--- 10.1.190.219 ping statistics ---1 packets transmitted, 0 received, 100% packet loss, time 0ms
实战分析
通过前文内容,我们对kube-proxy iptables模式实现的service有了一些理解,接下来我们再从宿主机上通过svc ip访问其它机器上pod
的实际场景来看看数据包在宿主机iptables中的规则匹配流程。我们重新整理下当前的环境信息:
Note:
pod中的iptables没有特殊规则,可以看做全放通,这里不再关注。
下述iptables规则只保留了主要规则,缩进格式是为了更清晰的体现链的跳转。
请求命令为宿主机上执行
curl 10.1.190.219:8080
,pod内抓取的报文仅截取了TCP三次握手。
去程逻辑图
回程逻辑图
去程iptables规则
/*************节点vm-12-7-centos*************/
// {src: localProcess, dst: 10.1.190.219:8080}
// nat表OUTPUT链
-A OUTPUT -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A KUBE-SERVICES ! -s 10.244.0.0/16 -d 10.1.190.219/32 -p tcp -m comment --comment "default/nginx-svc:http cluster IP" -m tcp --dport 8080 -j KUBE-MARK-MASQ
-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000
-A KUBE-SERVICES -d 10.1.190.219/32 -p tcp -m comment --comment "default/nginx-svc:http cluster IP" -m tcp --dport 8080 -j KUBE-SVC-ELCM5PCEQWBTUJ2I
-A KUBE-SVC-ELCM5PCEQWBTUJ2I -j KUBE-SEP-FNO4E6JYD7EGUHTP
-A KUBE-SEP-FNO4E6JYD7EGUHTP -p tcp -m tcp -j DNAT --to-destination 10.244.1.7:80
// filter表OUTPUT链
-A OUTPUT -j KUBE-FIREWALL
// {src: localProcess, dst: 10.244.1.7:80}
// 路由选择
10.244.1.0/24 via 10.244.1.0 dev flannel.1 onlink
// nat表POSTROUTING链
-A POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -m mark --mark 0x4000/0x4000 -j MASQUERADE
// MASQUERADE修改源IP+端口:{src: localProcess, dst: 10.1.190.219:8080} => {src: 10.244.0.0.50396, dst: 10.244.1.7:80}
/*************节点vm-12-11-centos*************/
// {src: 10.244.0.0.50396, dst: 10.244.1.7:80}
// nat表PREROUTING链
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
// 路由决策
10.244.1.0/24 dev cni0 proto kernel scope link src 10.244.1.1
// filter表FORWARD链
-A FORWARD -m comment --comment "kubernetes forwarding rules" -j KUBE-FORWARD
// nat表POSTROUTING链
-A POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING
回程iptables规则
/*************节点vm-12-11-centos*************/
// {src: 10.244.1.7:80, dst: 10.244.0.0.50396}
// nat表PREROUTING链
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
// 路由决策
10.244.0.0/24 via 10.244.0.0 dev flannel.1 onlink
// filter表FORWARD链
-A FORWARD -m comment --comment "kubernetes forwarding rules" -j KUBE-FORWARD
// nat表POSTROUTING链
-A POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING
/*************节点vm-12-7-centos*************/
// 出去的报文经MASQUERADE后回来的报文会转换回来:{src: 10.244.1.7:80, dst: 10.244.0.0.50396} => {src: 10.244.1.7:80, dst: localProcess}
// nat表PREROUTING链
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
// 路由决策
// filter表INPUT链
-A INPUT -j KUBE-FIREWALL
目标pod内tcpdump抓包
因客户端宿主机POSTROUTING处做了MASQUERADE,pod看到的请求IP是客户端宿主机上的flannel.1设备的IP。
[root@VM-12-11-centos ~]# nsenter -t 10319 -n
[root@VM-12-11-centos ~]# tcpdump -i eth0 -n
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
21:31:19.107449 IP 10.244.0.0.50396 > 10.244.1.7.http: Flags [S], seq 2721445788, win 29200, options [mss 1460,sackOK,TS val 3626323491 ecr 0,nop,wscale 7], length 0
21:31:19.107480 IP 10.244.1.7.http > 10.244.0.0.50396: Flags [S.], seq 2272988590, ack 2721445789, win 27960, options [mss 1410,sackOK,TS val 1413096198 ecr 3626323491,nop,wscale 7], length 0
21:31:19.107676 IP 10.244.0.0.50396 > 10.244.1.7.http: Flags [.], ack 1, win 229, options [nop,nop,TS val 3626323491 ecr 1413096198], length 0
小结
通过该场景验证以及逻辑分析,我们可以得出结论:当在集群内(node或者pod)访问service clusterIP:port时,都会经过PREROUTING/OUTPUT做DNAT到一个pod IP:port,并且在这个过程中有两种情况下会被打上0x4000/0x4000标记:
-
访问端IP非pod IP网段IP(如宿主机上直接访问);
-
pod通过service clusterIP访问到了自己。
带有0x4000/0x4000标记的数据包如果到了POSTROUTING后,会做MASQUERADE。
结语
前文分析nat表时,我们过滤掉了不相关service对应的iptables规则,我们来看看没过滤时的KUBE-SERVICE链:
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A OUTPUT -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A KUBE-SERVICES ! -s 10.244.0.0/16 -d 10.1.0.10/32 -p udp -m comment --comment "kube-system/kube-dns:dns cluster IP" -m udp --dport 53 -j KUBE-MARK-MASQ
-A KUBE-SERVICES -d 10.1.0.10/32 -p udp -m comment --comment "kube-system/kube-dns:dns cluster IP" -m udp --dport 53 -j KUBE-SVC-TCOU7JCQXEZGVUNU
-A KUBE-SERVICES ! -s 10.244.0.0/16 -d 10.1.0.10/32 -p tcp -m comment --comment "kube-system/kube-dns:dns-tcp cluster IP" -m tcp --dport 53 -j KUBE-MARK-MASQ
-A KUBE-SERVICES -d 10.1.0.10/32 -p tcp -m comment --comment "kube-system/kube-dns:dns-tcp cluster IP" -m tcp --dport 53 -j KUBE-SVC-ERIFXISQEP7F7OF4
-A KUBE-SERVICES ! -s 10.244.0.0/16 -d 10.1.0.10/32 -p tcp -m comment --comment "kube-system/kube-dns:metrics cluster IP" -m tcp --dport 9153 -j KUBE-MARK-MASQ
-A KUBE-SERVICES -d 10.1.0.10/32 -p tcp -m comment --comment "kube-system/kube-dns:metrics cluster IP" -m tcp --dport 9153 -j KUBE-SVC-JD5MR3NA4I4DYORP
-A KUBE-SERVICES ! -s 10.244.0.0/16 -d 10.1.190.219/32 -p tcp -m comment --comment "default/nginx-svc:http cluster IP" -m tcp --dport 8080 -j KUBE-MARK-MASQ
-A KUBE-SERVICES -d 10.1.190.219/32 -p tcp -m comment --comment "default/nginx-svc:http cluster IP" -m tcp --dport 8080 -j KUBE-SVC-ELCM5PCEQWBTUJ2I
-A KUBE-SERVICES ! -s 10.244.0.0/16 -d 10.1.0.1/32 -p tcp -m comment --comment "default/kubernetes:https cluster IP" -m tcp --dport 443 -j KUBE-MARK-MASQ
-A KUBE-SERVICES -d 10.1.0.1/32 -p tcp -m comment --comment "default/kubernetes:https cluster IP" -m tcp --dport 443 -j KUBE-SVC-NPX46M4PTMTKRN6Y
-A KUBE-SERVICES -m comment --comment "kubernetes service nodeports; NOTE: this must be the last rule in this chain" -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS
iptables规则是链式顺序执行的,可以看出,随着service数量的增加,匹配规则也会越来越多,这在性能上会有严重的问题。为了解决大规模场景下的性能问题,kube-proxy推出了ipvs模式,下篇文章会详细分析kube-proxy的ipvs模式原理,敬请期待。