在上一篇文章K8s网络实战分析之Calico-ipip模式中,我们通过Pod之间进行ping操作,对基于Calico-IPIP模式的K8s网络进行了实战学习与分析。单单进行Pod-Pod的访问只是K8s的基础功能,基于Service的访问才是K8s的网络核心。本文将基于上一篇文章所搭建的K8s网络,与大家一起探讨,Service访问的IP报文在K8s集群内的流动与原理!
为了方便操作,所有的pod都是部署的nginx容器,同时统一增添了一个nginx-service,配置如下:(实验中使用的curl等指令,容器内并没有预置,需要大家自行更新安装)。
apiVersion: v1
kind: ReplicationController
metadata:
name: nginx
spec:
replicas: 3
selector:
app: nginx
template:
metadata:
name: nginx
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
apiVersion: v1
kind: Service
metadata:
name: nginx-service
labels:
name: nginx-service
spec:
type: NodePort
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
nodePort: 30001
网络设备结构如图,yaml配置中replicas=3,共有3个容器,其中Pod3与Pod2皆在node2中,为了简洁表示,暂没有画出。
service信息如下,在K8s内部可以通过CLUSTER-IP:80进行访问,在外可以通过nodeIP:30001进行访问。
kubectl get svc -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
nginx-service NodePort 10.96.193.229 <none> 80:30001/TCP 9d app=nginx
我们知道,Service调用默认是轮询调用,Service会将访问请求随机转发到其中一个pod进行。根据Service的调用过程的不同,本节将分为四个部分进行实战分析,分别是跨node调用,同node内调用,同Pod内调用和集群外调用。
从Pod2发出请求 curl nginx-service ,到接收来自pod1的reply,IP报的流动是怎样的呢?
如果看过我的上一篇文章K8s网络实战分析之Calico-ipip模式,大家一定会非常肯定,IP的流动过程如下。那么恭喜您,猜测是正确的!
想要知道更多的细节,抓包是必不可少的。在各个节点处通过tcpdump进行抓包分析,得到IP报的流动如下:
Pod2->Pod1
Pod2: curl 10.96.193.229Pod2:
IP 10.100.9.206.45764 > 10.96.193.229.80: Flags [P.], seq 1:78, ack 1, win 219, options [nop,nop,TS val 3411647532 ecr 3411645523], length 77: HTTP: GET / HTTP/1.1
node2:
Tunl0
IP 10.100.9.206.45764 > 10.100.15.150.80: Flags [P.], seq 1:78, ack 1, win 219, options [nop,nop,TS val 3411647532 ecr 3411645523], length 77: HTTP: GET / HTTP/1.1Eth0
172.31.127.252 > 172.31.112.2: IP 10.100.9.206.45764 > 10.100.15.150.80: Flags [P.], seq 0:77, ack 1, win 219, options [nop,nop,TS val 3412008415 ecr 3412006406], length 77: HTTP: GET / HTTP/1.1 (ipip-proto-4)
Node1:
Eth0
IP 172.31.127.252 > 172.31.112.2: IP 10.100.9.206.45764 > 10.100.15.150.80: Flags [P.], seq 0:77, ack 1, win 219, options [nop,nop,TS val 3412008415 ecr 3412006406], length 77: HTTP: GET / HTTP/1.1 (ipip-proto-4)
Tunl0
IP 10.100.9.206.45764 > 10.100.15.150.80: Flags [P.], seq 1:78, ack 1, win 219, options [nop,nop,TS val 3411647532 ecr 3411645523], length 77: HTTP: GET / HTTP/1.1
Pod1:
tcpdump -i eth0 -nn
IP 10.100.9.206.45764 > 10.100.15.150.80: Flags [P.], seq 1:78, ack 1, win 219, options [nop,nop,TS val 3411887650 ecr 3411885641], length 77: HTTP: GET / HTTP/1.1
Pod1 reply Pod2
Pod1:
IP 10.100.15.150.80 > 10.100.9.206.53142: Flags [P.], seq 1:239, ack 78, win 217, options [nop,nop,TS val 3429334979 ecr 3429336988], length 238: HTTP: HTTP/1.1 200 OK
Node1
Tunl0
IP 10.100.15.150.80 > 10.100.9.206.53288: Flags [P.], seq 1:239, ack 78, win 217, options [nop,nop,TS val 3429693343 ecr 3429695353], length 238: HTTP: HTTP/1.1 200 OK
Eth0
IP 172.31.112.2 > 172.31.127.252: IP 10.100.15.150.80 > 10.100.9.206.53142: Flags [P.], seq 1:239, ack 78, win 217, options [nop,nop,TS val 3429334979 ecr 3429336988], length 238: HTTP: HTTP/1.1 200 OK (ipip-proto-4)
Node2
Eth0
IP 172.31.112.2 > 172.31.127.252: IP 10.100.15.150.80 > 10.100.9.206.53142: Flags [P.], seq 1:239, ack 78, win 217, options [nop,nop,TS val 3429334979 ecr 3429336988], length 238: HTTP: HTTP/1.1 200 OK (ipip-proto-4)
Tunl0
IP 10.100.15.150.80 > 10.100.9.206.53288: Flags [P.], seq 1:239, ack 78, win 217, options [nop,nop,TS val 3429693343 ecr 3429695353], length 238: HTTP: HTTP/1.1 200 OK
Pod2
IP 10.96.193.229.80 > 10.100.9.206.53288: Flags [P.], seq 1:239, ack 78, win 217, options [nop,nop,TS val 3429693343 ecr 3429695353], length 238: HTTP: HTTP/1.1 200 OK
对照数据,我们画出ip报流动示意图
与上以前文章中的ping 10.100.15.150相比,node节点之间的通信依然是使用Calico的IPIP模式,通过tunl0进行连接。值得注意的点有以下几个:
如果对iptables不是很熟悉,推荐大家看一下朱双印的iptables系列,非常好的介绍iptables的博客。
我们对上节注意点1中的iptables规则进行具体的分析。从Cali.c2发出的流量,进入到Linux网络协议栈后,会被iptables在多个阶段进行拦截过滤重定向等操作,主要作用点如图。
从PREROUTING链进行跟踪,本次ip报经在iptables中的主要的作用链如下:
当iptables随机选择了Pod1( KUBE-SEP-RR4LYAAU6NR5VL5B)作为本次调用的endpoint后,该链会进行一个DNAT操作,–to-destination 10.100.15.150:80,将dst host 从原来的service host转为Pod1的host。
从Pod2发出请求 curl nginx-service ,到接收来自同node内pod3的reply,IP报的流动是怎样的呢?
通过抓包,获取到各个节点上的IP流动如下:
Pod2->Pod3
curl 10.96.193.229
Pod1
IP 10.100.9.206.48024 > 10.96.193.229.80: Flags [P.], seq 1:78, ack 1, win 219, options [nop,nop,TS val 3416946428 ecr 3416946428], length 77: HTTP: GET / HTTP/1.1
Pod3
IP 10.100.9.206.48024 > 10.100.9.207.80: Flags [P.], seq 1:78, ack 1, win 219, options [nop,nop,TS val 3416946428 ecr 3416946428], length 77: HTTP: GET / HTTP/1.1
Pod3 reply Pod1
Pod3
IP 10.100.9.207.80 > 10.100.9.206.48024: Flags [P.], seq 1:239, ack 78, win 217, options [nop,nop,TS val 3416946428 ecr 3416946428], length 238: HTTP: HTTP/1.1 200 OK
Pod1
IP 10.96.193.229.80 > 10.100.9.206.48024: Flags [P.], seq 1:239, ack 78, win 217, options [nop,nop,TS val 3416946428 ecr 3416946428], length 238: HTTP: HTTP/1.1 200 OK
对照数据,我们画出ip报流动示意图:
同node内调用,并没有调用tunl0进行IPIP封装,直接由Cali.c2转发到了Cali.fa,中间经过了DNAT转换和ip route路由,与此前并没有啥不同,不再赘述。这里我们多说一句,就是iptables作用点和ip route路由作用点其实不是一前一后的关系,他们都在IP协议栈里。准确来说,在通过PREROUTING后,会进行ip route路由,接着再进行其他的iptables作用。大概的样子如下图,红色为iptables作用点,黄色为route作用点。只有从PREROUNTING进来的ip报,才会进行ip route哦
从Pod2发出请求 curl nginx-service ,到接收来自同pod的reply,IP报的流动是怎样的呢?
可能大家会觉得和上一节同node内调用一样,那就错啦!!!先卖个关子,下面为大家进行展示。
通过抓包,获取到各个节点上的IP流动如下:
Pod2->Pod2
发送
IP 10.100.9.206.49586 > 10.96.193.229.80: Flags [P.], seq 1:78, ack 1, win 219, options [nop,nop,TS val 3420700250 ecr 3420700250], length 77: HTTP: GET / HTTP/1.1
收到
172.31.127.252.49586 > 10.100.9.206.80: Flags [P.], seq 1:78, ack 1, win 219, options [nop,nop,TS val 3420700250 ecr 3420700250], length 77: HTTP: GET / HTTP/1.1
Pod2 reply Pod2
回复
IP 10.100.9.206.80 > 172.31.127.252.49586: Flags [P.], seq 1:239, ack 78, win 217, options [nop,nop,TS val 3420700250 ecr 3420700250], length 238: HTTP: HTTP/1.1 200 OK
收到回复
10.96.193.229.80 > 10.100.9.206.49586: Flags [P.], seq 1:239, ack 78, win 217, options [nop,nop,TS val 3420700250 ecr 3420700250], length 238: HTTP: HTTP/1.1 200 OK
对照数据,我们画出ip报流动示意图,图中红色代表红色代表发出请求的路径,黄色代表服务器的相应路径。
与同node内调用相比,可以发现,发送请求在经过iptables后,不仅仅进行了DNAT,也进行了SNAT,其src host 由Pod2的ip 10.100.9.206转为了node2的主机ip 172.31.127.252。
为什么要经过SNAT其实很好理解:如果没有SNAT,服务器会看到自己给自己发送的请求,那么,它回复谁去呢?怕是要迷茫了吧哈哈。
###iptables分析
让我们进入iptables,去看看有什么不同的地方。
与之前相比,同Pod内调用多匹配了一条链:
-A KUBE-SEP-5YGHV5ZP5SB5PVUU -s 10.100.9.206/32 -j KUBE-MARK-MASQ
这会对该ip报进行打标操作,然后在KUBE-POSTROUTING中,进行了MASQUERADE操作
-A KUBE-POSTROUTING -m comment --comment “kubernetes service traffic requiring SNAT” -m mark --mark 0x4000/0x4000 -j MASQUERADE
MASQUERADE操作就是一种自动指定src host的SNAT操作。相信大家看到这肯定明白了同Pod调用和同node调用的区别了吧。同Pod会额外进行SNAT的转换哦。
通过直接访问nodeip:30001,我们可以进行集群外调用实战。与此前的分析方法一样,通过抓包,我们直接贴出ip报流向图:
总结如下:
总结3可能有点绕,我们将会对其iptables进行详细分析。iptables链路图如下:
黄色箭头为公共流向,红色箭头为同node中pod调用,绿色箭头为异node中pod调用。可以看到,
-A KUBE-SEP-5YGHV5ZP5SB5PVUU -s 10.100.9.206/32 -j KUBE-MARK-MASQ
由于src host不是10.100.9.206/32,ip报被打上tag,然后在最后的KUBE-POSTEROUTING中,进行了MASQUERADME,也就是自动SNAT,将src host由172.31.112.1转换为了tunl0的地址 10.100.15.128、
通过上面的实战,相信大家对service调用的各种情况都有了直观的认识,总结来说
希望对大家有所帮助~