33

死磕k8s之calico-nodeport

 3 years ago
source link: https://mp.weixin.qq.com/s?__biz=MzI5ODQ2MzI3NQ%3D%3D&%3Bmid=2247495199&%3Bidx=1&%3Bsn=b7fc4409cdd686018255a4f5a5e0d3eb
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

qq6JfqB.png!mobile

序言

本篇文章将要聚焦于k8s在使用calico作为网络插件的时候,当pod以nodeport的形式暴露出来,我们以集群节点的ip加端口的形式访问的时候,流量如何转发到具体的pod,流量经过了哪些路由和哪些iptables链。如集群还没准备好,请参考[死磕k8s之calico-环境准备]。

我的环境

NZfE3mB.png!mobile

nginx pod信息

NAME                     READY   STATUS    RESTARTS   AGE     IP               NODE              NOMINATED NODE   READINESS GATESnginx-6799fc88d8-wfztd   1/1     Running   0          3h32m   192.168.231.70   shen-k8s-node-1   <none>           <none>

nodeport信息

NAME    TYPE       CLUSTER-IP    EXTERNAL-IP   PORT(S)          AGE    SELECTORnginx   NodePort   10.101.14.7   <none>        8080:32220/TCP   2d1h   app=nginx

注意

接下来的分析主要基于该表的路径

AZv6Bfu.png!mobile

开始发请求到nodeport

我目前nginx的pod在work节点1上面,为了减少分析过程,我在和k8s同网段的其他机器10.0.0.53上面请求work节点1的ip加端口

<span style="font-size: 15px;">curl 10.0.0.51:32220</span>

到达work节点1

01

首先会到达raw的PREROUTING

包的流向如下

AvABfqM.png!mobile

raw的PRESOUTING相对比较简单,在这里没有匹配到任何的规则

02

然后到达mangle的PREROUTING

包的流向如下

QNRVviz.png!mobile

对新来的包来说也没有匹配到任何规则,但是第二条规则会对以后的包做一个放行,效率更高

03

然后到了重要表nat的PREROUTING

在PREROUTING一般对包做DNAT操作

包的流量如下:

EriyiaI.png!mobile

此表的规则路径稍微复杂一些了,第三条规则匹配到了我们的请求的目标端口32220,注意下第4条规则,会给包打上0x4000/0x4000标签。最后到了第7条做了一个比较重要的dnat操作,此时请求的包的源地址还是10.0.0.53,但是包的目的地址会变成192.168.231.70,并且目标端口也会换成我们暴露的容器端口80。 192.168.231.70 正是pod nginx-6799fc88d8-wfztd 的地址。因为我们这里只有一个pod,所以只会匹配到这一条dnat规则,如果我们有多个pod的话,会按照该规则上的概率随机匹配到一条dnat规则。大家由此就会联想到service的负载均衡策略也可以这么做。

04

然后就开始第一次的路由选择了

7VVRNrE.png!mobile

上图是work节点1 的路由信息,据上面分析得到包的目的地址是192.168.231.70,此时正好匹配到红色框的路由。而虚拟网卡cali583c2cee4e2是和192.168.231.70这个pod的veth pair,下图可以求证

va2uyaq.png!mobile

第一张图是在pod内看到虚拟网络信息,第二张图示pod所在节点的网络接口信息,pod内if41正好对应节点上网卡cali583c2cee4e2@if4,该网卡和上面匹配到的路由一样,所以该包可以顺利到达pod内。

但是此时的源地址还是最初发送请求的地址10.13.0.53,如果以这个包转发到pod内,那么pod的回包就找不到正确发送回来的目的地址了,故接下来肯定会对该包多SNAT,也就是修改源地址。注意此时包还没有发出去,还会继续走其他的表。

05

匹配到了路由信息,此时会走的链是mangle的FORWARD

但该表没有对它作任何操作

06

接下来看看filter表的FORWARD

6zAV73e.png!mobile

该链看着挺复杂,其实没有对包作重要的操作。大概说几个比较关键的地方,第2条规则是能匹配到的,因为此时的包经过路由决策之后是需要去cali+开头的网卡。第3条恰恰精确匹配到了包要转发的虚拟网卡。第4条虽然给包打上了0x10000/0x10000,但是走到第8条的时候注意红色框出,这里重新打包为0x4000/0x4000了这里尤其注意了,不然会翻车。

07

然后是mangle的POSTROUTING

然而该表没有任何规则

08

接下来到了nat表的POSTROUTING链

nat表重要的是做了一个重要的操作SNAT

j2e22qz.png!mobile

该表重点就是第5条,依照前面分析有个0x400/0x400标记,不然就完了,下面就不会走到第6条去SNAT操作了。

通过抓包显示此时包的目的地址和源地址都被替换了。

M7nQvu3.png!mobile

09

此时包的路径分析基本已经结束了

因为包已经走到pod里面去了

10

此时我们的pod正好在请求的ip的节点上

如果pod在work节点2上包的走向又会怎么样呢?

那我们改一下该pod的调度策略:

kubectl edit deployment nginx
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
deployment.kubernetes.io/revision: "2"
creationTimestamp: "2020-11-09T05:56:04Z"
generation: 2
labels:
app: nginx
name: nginx
namespace: default
resourceVersion: "1086147"
selfLink: /apis/apps/v1/namespaces/default/deployments/nginx
uid: ec684051-3725-4f5e-9efd-d96fb3257cca
spec:
progressDeadlineSeconds: 600
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
app: nginx
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
creationTimestamp: null
labels:
app: nginx
spec:
containers:
- image: nginx
imagePullPolicy: Always
name: nginx
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: ClusterFirst
nodeSelector:
kubernetes.io/hostname: shen-k8s-node-2 #(节点2)
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
status:
availableReplicas: 1
conditions:
- lastTransitionTime: "2020-11-09T05:56:18Z"
lastUpdateTime: "2020-11-09T05:56:18Z"
message: Deployment has minimum availability.
reason: MinimumReplicasAvailable
status: "True"
type: Available
- lastTransitionTime: "2020-11-09T05:56:04Z"
lastUpdateTime: "2020-11-10T14:13:15Z"
message: ReplicaSet "nginx-854f5d4bbd" has successfully progressed.
reason: NewReplicaSetAvailable
status: "True"
type: Progressing
observedGeneration: 2
readyReplicas: 1
replicas: 1
updatedReplicas: 1

过段时间查看pod状态,最终会调度到work节点2上去

此时的iptables信息和路由信息我就贴到死磕k8s之calico-环境准备去,如果感兴趣可以去那里查看。

11

现在从上面第3条的nat的PREROUTING

开始继续分析

主要是第7条规则有变化,变成了新的pod的ip:192.168.233.136

<span style="font-size: 15px;">-A KUBE-SEP-PAOOOQUDSCRRYKFR -p tcp -m comment --comment &quot;default/nginx:8080-80&quot; -m tcp -j DNAT --to-destination 192.168.233.136:80</span>

12

然后会经历一波路由策略

iueQVjf.png!mobile

和上面的不一样,这里是网段匹配了,会匹配到最后一条路由。该路由的会将包从网关10.0.0.52用网卡tunl0这个网卡转发出去。此时需要注意2点:

  • 网关正好是work节点2上的ip,而pod也被调度到该节点上

  • tunl0是一个特殊的虚拟网卡,由于我的环境是用的ipip模式,故有一个这个通道网卡,用来封装该报的目的地址和源地址,当改包到达了目的地址的主机的时候,在目的主机的tunl0网卡会把该包拆出来,得到最初的包。

13

mangle的FORWARD链没有任何规则

忽略

14

filter的FORWARD链没有重要的规则

但是有一条需要注意,其他的和上面的流程分析一致

<span style="font-size: 15px;">-A cali-FORWARD -i cali+ -m comment --comment &quot;cali:8ZoYfO5HKXWbB3pk&quot; -j cali-from-wl-dispatch</span>

<span style="font-size: 15px;">-A cali-FORWARD -o cali+ -m comment --comment &quot;cali:jdEuaPBe14V2hutn&quot; -j cali-to-wl-dispatch</span>

上面2条规则目前来说都匹配不到了,由于上面的路由改变了。

15

mangle的POSTROUTING没有任何规则

故忽略

16

接下来nat的POSTROUTING会做一个SNAT操作

其他的规则和上面的分析一致,但是注意这一条规则

<span style="font-size: 15px;">-A cali-POSTROUTING -o tunl0 -m comment --comment &quot;cali:JHlpT-eSqR1TvyYm&quot; -m addrtype ! --src-type LOCAL --limit-iface-out -m addrtype --src-type LOCAL -j MASQUERADE</span>

该规则会被匹配到直接做了以work节点1的tunl0的ip为源地址的SNAT操作。此时包为192.168.231.64 -> 192.168.233.136

17

此时包就从tunl0通道留到work节点2的ens192网卡上了

又要走一遍work节点2上的四表五链。

下面是我的抓包信息,可以佐证:

r2eqM3b.png!mobile

18

raw表的PREROUTING和上面的分析一致

没有操作

mangle表的PREROUTING此时也没有什么操作。nat表的PRESOUTING上上面分析的有差别了,就会匹配到

<span style="font-size: 15px;">-A KUBE-NODEPORTS -p tcp -m comment --comment &quot;default/nginx:8080-80&quot; -m tcp --dport 32220 -j KUBE-MARK-MASQ</span>

<span style="font-size: 15px;">-A KUBE-NODEPORTS -p tcp -m comment --comment &quot;default/nginx:8080-80&quot; -m tcp --dport 32220 -j KUBE-SVC-DR2DYVPMBY3GPZ5L</span>

因为此时的端口是80了。

19

然后会做路由决策

此时的路由为:

yiQrm2r.png!mobile

该路由能精确匹配到最后一条,注意该网卡和pod里面的网卡一一对应,veth pair.

work节点2网卡:

FfMz2uJ.png!mobile

pod的网卡信息:

emyuyyi.png!mobile

所以该包会从calib992f6c0b80转发到pod里面去。

20

接下来会走到mangle的FORWARD表,

但是没有规则可匹配

nat的FORWARD也没有什么好分析的,该包也直接进入mangle的POSTROUTING,但是没有规则。然后到了nat的POSTROUTING,此时需要注意一点的是:

-A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN

和前面的分析的差别就在这里,会直接转发到pod里面去了,到此包的旅程就解析完成了。

简单总结

  • 以nodeport的形式的包会被SNAT和DNAT,而且再SNAT之前会被打上很重要的标签0x400/0x400。

  • nodeport的形式的包会走forward链,不会进到INPUT和OUTPUT链。

  • 当nodeport的service的后面有多个终端的时候,也就是多个pod,会按照概率随机走到哪个pod。当流量真正落到的pod的节点不是请求的IP节点的时候,还会通过tunl0这个calico独有的网卡到另外一个节点继续转发。所以当知道pod所在的节点的时候,直接请求该节点的ip,效率会更高,因为会少一些网络的链路操作,当然这种优化个人绝对没有必要。

  • 后续会有其他的情况分析:集群内访问pod的ip,集群内访问service。。。

文章作者:张其

原文链接:

https://blog.csdn.net/u010927340/article/details/109630096

(文章由作者独家投稿

END

Kubernetes CKA实战培训班推荐:

北京:11月20-22日

上海:11月27-29日

FZrmA3U.jpg!mobile

fA7FNjZ.jpg!mobile

觉得有价值,点个 “在看” ,和朋友们一起成长


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK