死磕k8s之calico-nodeport
Posted K8S中文社区
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了死磕k8s之calico-nodeport相关的知识,希望对你有一定的参考价值。
序言
本篇文章将要聚焦于k8s在使用calico作为网络插件的时候,当pod以nodeport的形式暴露出来,我们以集群节点的ip加端口的形式访问的时候,流量如何转发到具体的pod,流量经过了哪些路由和哪些iptables链。如集群还没准备好,请参考[死磕k8s之calico-环境准备]。
我的环境
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
注意
接下来的分析主要基于该表的路径
开始发请求到nodeport
我目前nginx的pod在work节点1上面,为了减少分析过程,我在和k8s同网段的其他机器10.0.0.53上面请求work节点1的ip加端口
curl 10.0.0.51:32220
到达work节点1
01
首先会到达raw的PREROUTING
包的流向如下
raw的PRESOUTING相对比较简单,在这里没有匹配到任何的规则
02
然后到达mangle的PREROUTING
包的流向如下
对新来的包来说也没有匹配到任何规则,但是第二条规则会对以后的包做一个放行,效率更高
03
然后到了重要表nat的PREROUTING
在PREROUTING一般对包做DNAT操作
包的流量如下:
此表的规则路径稍微复杂一些了,第三条规则匹配到了我们的请求的目标端口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
然后就开始第一次的路由选择了
上图是work节点1 的路由信息,据上面分析得到包的目的地址是192.168.231.70,此时正好匹配到红色框的路由。而虚拟网卡cali583c2cee4e2是和192.168.231.70这个pod的veth pair,下图可以求证
第一张图是在pod内看到虚拟网络信息,第二张图示pod所在节点的网络接口信息,pod内if41正好对应节点上网卡cali583c2cee4e2@if4,该网卡和上面匹配到的路由一样,所以该包可以顺利到达pod内。
但是此时的源地址还是最初发送请求的地址10.13.0.53,如果以这个包转发到pod内,那么pod的回包就找不到正确发送回来的目的地址了,故接下来肯定会对该包多SNAT,也就是修改源地址。注意此时包还没有发出去,还会继续走其他的表。
05
匹配到了路由信息,此时会走的链是mangle的FORWARD
但该表没有对它作任何操作
06
接下来看看filter表的FORWARD
该链看着挺复杂,其实没有对包作重要的操作。大概说几个比较关键的地方,第2条规则是能匹配到的,因为此时的包经过路由决策之后是需要去cali+开头的网卡。第3条恰恰精确匹配到了包要转发的虚拟网卡。第4条虽然给包打上了0x10000/0x10000,但是走到第8条的时候注意红色框出,这里重新打包为0x4000/0x4000了这里尤其注意了,不然会翻车。
07
然后是mangle的POSTROUTING
然而该表没有任何规则
08
接下来到了nat表的POSTROUTING链
nat表重要的是做了一个重要的操作SNAT
该表重点就是第5条,依照前面分析有个0x400/0x400标记,不然就完了,下面就不会走到第6条去SNAT操作了。
通过抓包显示此时包的目的地址和源地址都被替换了。
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
-A KUBE-SEP-PAOOOQUDSCRRYKFR -p tcp -m comment --comment "default/nginx:8080-80" -m tcp -j DNAT --to-destination 192.168.233.136:80
12
然后会经历一波路由策略
和上面的不一样,这里是网段匹配了,会匹配到最后一条路由。该路由的会将包从网关10.0.0.52用网卡tunl0这个网卡转发出去。此时需要注意2点:
网关正好是work节点2上的ip,而pod也被调度到该节点上
13
mangle的FORWARD链没有任何规则
忽略
14
filter的FORWARD链没有重要的规则
但是有一条需要注意,其他的和上面的流程分析一致
-A cali-FORWARD -i cali+ -m comment --comment "cali:8ZoYfO5HKXWbB3pk" -j cali-from-wl-dispatch
-A cali-FORWARD -o cali+ -m comment --comment "cali:jdEuaPBe14V2hutn" -j cali-to-wl-dispatch
上面2条规则目前来说都匹配不到了,由于上面的路由改变了。
15
mangle的POSTROUTING没有任何规则
故忽略
16
接下来nat的POSTROUTING会做一个SNAT操作
其他的规则和上面的分析一致,但是注意这一条规则
-A cali-POSTROUTING -o tunl0 -m comment --comment "cali:JHlpT-eSqR1TvyYm" -m addrtype ! --src-type LOCAL --limit-iface-out -m addrtype --src-type LOCAL -j MASQUERADE
该规则会被匹配到直接做了以work节点1的tunl0的ip为源地址的SNAT操作。此时包为192.168.231.64 -> 192.168.233.136
17
此时包就从tunl0通道留到work节点2的ens192网卡上了
又要走一遍work节点2上的四表五链。
下面是我的抓包信息,可以佐证:
18
raw表的PREROUTING和上面的分析一致
没有操作
mangle表的PREROUTING此时也没有什么操作。nat表的PRESOUTING上上面分析的有差别了,就会匹配到
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/nginx:8080-80" -m tcp --dport 32220 -j KUBE-MARK-MASQ
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/nginx:8080-80" -m tcp --dport 32220 -j KUBE-SVC-DR2DYVPMBY3GPZ5L
因为此时的端口是80了。
19
然后会做路由决策
此时的路由为:
该路由能精确匹配到最后一条,注意该网卡和pod里面的网卡一一对应,veth pair.
work节点2网卡:
pod的网卡信息:
所以该包会从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。。。
序言
我的环境
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>
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTORnginx NodePort 10.101.14.7 <none> 8080:32220/TCP 2d1h app=nginx
注意
开始发请求到nodeport
curl 10.0.0.51:32220
首先会到达raw的PREROUTING
包的流向如下
raw的PRESOUTING相对比较简单,在这里没有匹配到任何的规则
然后到达mangle的PREROUTING
包的流向如下
对新来的包来说也没有匹配到任何规则,但是第二条规则会对以后的包做一个放行,效率更高
03
然后到了重要表nat的PREROUTING
在PREROUTING一般对包做DNAT操作
包的流量如下:
此表的规则路径稍微复杂一些了,第三条规则匹配到了我们的请求的目标端口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
然后就开始第一次的路由选择了
第一张图是在pod内看到虚拟网络信息,第二张图示pod所在节点的网络接口信息,pod内if41正好对应节点上网卡cali583c2cee4e2@if4,该网卡和上面匹配到的路由一样,所以该包可以顺利到达pod内。
但是此时的源地址还是最初发送请求的地址10.13.0.53,如果以这个包转发到pod内,那么pod的回包就找不到正确发送回来的目的地址了,故接下来肯定会对该包多SNAT,也就是修改源地址。注意此时包还没有发出去,还会继续走其他的表。
05
匹配到了路由信息,此时会走的链是mangle的FORWARD
但该表没有对它作任何操作
06
接下来看看filter表的FORWARD
该链看着挺复杂,其实没有对包作重要的操作。大概说几个比较关键的地方,第2条规则是能匹配到的,因为此时的包经过路由决策之后是需要去cali+开头的网卡。第3条恰恰精确匹配到了包要转发的虚拟网卡。第4条虽然给包打上了0x10000/0x10000,但是走到第8条的时候注意红色框出,这里重新打包为0x4000/0x4000了这里尤其注意了,不然会翻车。
07
然后是mangle的POSTROUTING
然而该表没有任何规则
08
接下来到了nat表的POSTROUTING链
nat表重要的是做了一个重要的操作SNAT
该表重点就是第5条,依照前面分析有个0x400/0x400标记,不然就完了,下面就不会走到第6条去SNAT操作了。
09
此时包的路径分析基本已经结束了
因为包已经走到pod里面去了
10
此时我们的pod正好在请求的ip的节点上
如果pod在work节点2上包的走向又会怎么样呢?
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
11
现在从上面第3条的nat的PREROUTING
开始继续分析
-A KUBE-SEP-PAOOOQUDSCRRYKFR -p tcp -m comment --comment "default/nginx:8080-80" -m tcp -j DNAT --to-destination 192.168.233.136:80
12
然后会经历一波路由策略
和上面的不一样,这里是网段匹配了,会匹配到最后一条路由。该路由的会将包从网关10.0.0.52用网卡tunl0这个网卡转发出去。此时需要注意2点:
网关正好是work节点2上的ip,而pod也被调度到该节点上
13
mangle的FORWARD链没有任何规则
忽略
14
filter的FORWARD链没有重要的规则
但是有一条需要注意,其他的和上面的流程分析一致
-A cali-FORWARD -i cali+ -m comment --comment "cali:8ZoYfO5HKXWbB3pk" -j cali-from-wl-dispatch
-A cali-FORWARD -o cali+ -m comment --comment "cali:jdEuaPBe14V2hutn" -j cali-to-wl-dispatch
15
mangle的POSTROUTING没有任何规则
故忽略
16
接下来nat的POSTROUTING会做一个SNAT操作
-A cali-POSTROUTING -o tunl0 -m comment --comment "cali:JHlpT-eSqR1TvyYm" -m addrtype ! --src-type LOCAL --limit-iface-out -m addrtype --src-type LOCAL -j MASQUERADE
17
此时包就从tunl0通道留到work节点2的ens192网卡上了
又要走一遍work节点2上的四表五链。
下面是我的抓包信息,可以佐证:
18
raw表的PREROUTING和上面的分析一致
没有操作
mangle表的PREROUTING此时也没有什么操作。nat表的PRESOUTING上上面分析的有差别了,就会匹配到
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/nginx:8080-80" -m tcp --dport 32220 -j KUBE-MARK-MASQ
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/nginx:8080-80" -m tcp --dport 32220 -j KUBE-SVC-DR2DYVPMBY3GPZ5L
19
然后会做路由决策
该路由能精确匹配到最后一条,注意该网卡和pod里面的网卡一一对应,veth pair.
work节点2网卡:
pod的网卡信息:
所以该包会从calib992f6c0b80转发到pod里面去。
20
接下来会走到mangle的FORWARD表,
但是没有规则可匹配
nat的FORWARD也没有什么好分析的,该包也直接进入mangle的POSTROUTING,但是没有规则。然后到了nat的POSTROUTING,此时需要注意一点的是:
-A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN
简单总结
以nodeport的形式的包会被SNAT和DNAT,而且再SNAT之前会被打上很重要的标签0x400/0x400。
nodeport的形式的包会走forward链,不会进到INPUT和OUTPUT链。
当nodeport的service的后面有多个终端的时候,也就是多个pod,会按照概率随机走到哪个pod。当流量真正落到的pod的节点不是请求的IP节点的时候,还会通过tunl0这个calico独有的网卡到另外一个节点继续转发。所以当知道pod所在的节点的时候,直接请求该节点的ip,效率会更高,因为会少一些网络的链路操作,当然这种优化个人绝对没有必要。
后续会有其他的情况分析:集群内访问pod的ip,集群内访问service。。。
以上是关于死磕k8s之calico-nodeport的主要内容,如果未能解决你的问题,请参考以下文章