前言
本文是个人的小小见解,欢迎大佬指出我文章的问题,一起讨论进步~
我个人的疑问点
- 进入的流量是如何自动判断进入iptables的四表?
- k8s nodeport模式的原理?
一 本机环境介绍
节点名 | 节点IP | K8S版本 | CNI插件 |
---|---|---|---|
Master | 192.168.44.141 | 1.29.2 | calico(IPIP模式) |
k8s-slave | 192.168.44.142 | 1.29.2 | calico(IPIP模式) |
二 容器介绍
ingress-nginx与 jupyter分别在2台服务器上
节点名 | 节点IP | 容器名 | 容器IP | svc地址 |
---|---|---|---|---|
master | 192.168.44.141 | ingress-nginx-controller-556d4fff79-9jdmq | 172.16.219.89 | NodePort 10.100.104.51 80:80/TCP,443:443/TCP |
k8s-slave | 192.168.44.142 | jupyter-deployment-57454c6795-xpgfk | 172.16.64.206 | ClusterIP 10.97.82.53 8888/TCP |
三 请求方介绍
本地IP地址 10.250.0.34,通过添加/etc/hosts解析域名。因为ingress-nginx的SVC是Nodeport使用的是本机 80与443端口,所以访问 192.168.44.141 的80 443端口即可访问域名
192.168.44.141 nginx.jcrose.com
四 请求链路分析
开始分析iptables iptables -nL -t nat
iptables是按照规则从上至下逐条匹配,开始分析Prerouting
流入的报文在路由决策
raw.PREROUTING -> mangle.PREROUTING -> nat.PREROUTING -> mangle.FORWARD -> filter.FORWARD -> mangle.POSTROUTING -> nat.POSTROUTING
- raw.PREROUTING:设置流量标记,跳过连接跟踪(可选)。
- mangle.PREROUTING:修改流量属性或标记流量(如 QoS 或 TTL)。
- nat.PREROUTING:目标地址转换(DNAT),将流量重定向到正确的后端服务。
- mangle.FORWARD:在转发流量时修改流量属性(如标记),用于流量控制。
- filter.FORWARD:过滤流量,决定是否允许流量继续转发(基于安全策略)。
- mangle.POSTROUTING:在出站流量时修改流量属性(如标记、TTL)。
- nat.POSTROUTING:源地址转换(SNAT),确保流量从正确的 IP 地址发送出去。
最重要的2个部分 nat.PREROUTING 目标地址转换(DNAT),nat.POSTROUTING源地址转换(SNAT)。
4.1 Nat.PREROUTING链
IP: 192.168.44.141:80
host: jupyter.jcrose.com
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
cali-PREROUTING all -- 0.0.0.0/0 0.0.0.0/0 /* cali:6gwbT8clXdHdC1b1 */
KUBE-SERVICES all -- 0.0.0.0/0 0.0.0.0/0 /* kubernetes service portals */
CNI-HOSTPORT-DNAT all -- 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL
4.1.1 cali-PREROUTING规则
这里使用了 cali-fip-dnat(Calico 使用 IP-in-IP 或 VXLAN 隧道模式则不会使用该链)
Chain cali-PREROUTING (1 references)
target prot opt source destination
cali-fip-dnat all -- 0.0.0.0/0 0.0.0.0/0 /* cali:r6XmIziWUJsdOK6Z */
4.1.2 KUBE-SERVICES规则
规则如下
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
.............................
KUBE-SERVICES all -- 0.0.0.0/0 0.0.0.0/0 /* kubernetes service portals */
.............................
分析下面Chain KUBE-SERVICES的4条规则
Chain KUBE-SERVICES (2 references)
target prot opt source destination
RETURN all -- 127.0.0.0/8 0.0.0.0/0
KUBE-MARK-MASQ all -- !172.16.0.0/12 0.0.0.0/0 /* Kubernetes service cluster ip + port for masquerade purpose */ match-set KUBE-CLUSTER-IP dst,dst
KUBE-NODE-PORT all -- 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL # 这里只有当请求的node IP是本机才会查询
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 match-set KUBE-CLUSTER-IP dst,dst
① RETURN规则
返回一切来自于 127.0.0.0本地请求
② KUBE-MARK-MASQ规则
match-set KUBE-CLUSTER-IP dst,dst 需要匹配ipset里面的地址是否存在。
因为 KUBE-CLUSTER-IP 是用于访问集群内部的IP而不是nodeport,因此此条规则也没有,跳过
Name: KUBE-CLUSTER-IP
Type: hash:ip,port
Revision: 6
Header: family inet hashsize 1024 maxelem 65536 bucketsize 12 initval 0xd824d3cd
Size in memory: 536
References: 3
Number of entries: 7
Members:
10.97.82.53,tcp:8888
10.96.0.2,udp:53
10.96.0.2,tcp:53
10.100.104.51,tcp:80
10.100.104.51,tcp:443
10.108.32.102,tcp:443
10.96.0.1,tcp:443Chain KUBE-MARK-MASQ (3 references)
target prot opt source destination
MARK all -- 0.0.0.0/0 0.0.0.0/0 MARK or 0x4000
③ KUBE-NODE-PORT规则
规则如下
Chain KUBE-SERVICES (2 references)
............
KUBE-NODE-PORT all -- 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL # 这里只有当请求的node IP是本机才会查询
.................
因为本机IP为 192.168.44.141,请求过来的IP满足,因此进入下一条链 KUBE-NODE-PORT
Chain KUBE-NODE-PORT (1 references)
target prot opt source destination
KUBE-MARK-MASQ tcp -- 0.0.0.0/0 0.0.0.0/0 /* Kubernetes nodeport TCP port for masquerade purpose */ match-set KUBE-NODE-PORT-TCP dst
KUBE-NODE-PORT-TCP 的ipset规则如下
root@master:~# ipset --list KUBE-NODE-PORT-TCP
Name: KUBE-NODE-PORT-TCP
Type: bitmap:port
Revision: 3
Header: range 0-65535
Size in memory: 8264
References: 1
Number of entries: 2
Members:
80
443
match-set KUBE-NODE-PORT-TCP dst 匹配 ipset的 KUBE-NODE-PORT-TCP,如果匹配到了则进入下一步
KUBE-MARK-MASQ
这里对经过的流量打上标签 0x4000 继续在当前链中向下匹配下一个规则
Chain KUBE-MARK-MASQ (3 references)
target prot opt source destination
MARK all -- 0.0.0.0/0 0.0.0.0/0 MARK or 0x4000
④ ACCEPT规则(在上一步对流量打了标记之后来到这里)
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 match-set KUBE-CLUSTER-IP dst,dst
KUBE-CLUSTER-IP的ipset规则如下
root@master:~# ipset --list KUBE-CLUSTER-IP
Name: KUBE-CLUSTER-IP
Type: hash:ip,port
Revision: 6
Header: family inet hashsize 1024 maxelem 65536 bucketsize 12 initval 0xd824d3cd
Size in memory: 536
References: 3
Number of entries: 7
Members:
10.97.82.53,tcp:8888
10.96.0.2,udp:53
10.96.0.2,tcp:53
10.100.104.51,tcp:80
10.100.104.51,tcp:443
10.108.32.102,tcp:443
10.96.0.1,tcp:443
没有找到相关目标:192.168.44.141的规则,所以跳过这条规则
4.1.3 CNI-HOSTPORT-DNAT
这里去 CNI-DN-50c24a55650b462c3ecc9 匹配 80与443端口
Chain CNI-HOSTPORT-DNAT (2 references)
target prot opt source destination
CNI-DN-50c24a55650b462c3ecc9 tcp -- 0.0.0.0/0 0.0.0.0/0 /* dnat name: "k8s-pod-network" id: "f5c87b58cf224ed0b1ce05b973b73ab23ffdd9c5f92904961b08e7b09a8e6373" */ multiport dports 80,443
① CNI-DN-50c24a55650b462c3ecc9规则
这里有4个打标记 0x2000 的规则,这里不一一解释,只分析 2个DNAT的规则
Chain CNI-DN-50c24a55650b462c3ecc9 (1 references)
target prot opt source destination
CNI-HOSTPORT-SETMARK tcp -- 172.16.219.89 0.0.0.0/0 tcp dpt:80 #MARK 172.16.219.71 MARK or 0x2000
CNI-HOSTPORT-SETMARK tcp -- 127.0.0.1 0.0.0.0/0 tcp dpt:80 #MARK 172.16.219.71 MARK or 0x2000
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 to:172.16.219.89:80
CNI-HOSTPORT-SETMARK tcp -- 172.16.219.89 0.0.0.0/0 tcp dpt:443 #MARK 172.16.219.71 MARK or 0x2000
CNI-HOSTPORT-SETMARK tcp -- 127.0.0.1 0.0.0.0/0 tcp dpt:443 #MARK 172.16.219.71 MARK or 0x2000
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:443 to:172.16.219.89:443
这2个DNAT把来源的目标:192.168.44.141:80 (请求头 http://jupyter.jcrose.com) 修改为目标 172.16.219.89:80(ingress-nginx的 pod IP地址)
五 veth设备介绍
- Pod 流量的目的地是同一节点上的 Pod。
- Pod 流量的目的地是在不同节点上的 Pod。
整个工作流依赖于虚拟接口对和网桥,下面先来了解一下这部分的内容。
为了让一个 Pod 与其他 Pod 通信,它必须先访问节点的根命名空间。
通过虚拟以太网对来实现 Pod 和根命名空间的连接。
这些虚拟接口设备(veth 中的 v)连接并充当两个命名空间之间的隧道。
查看master节点上的网卡情况
ip a3: kube-ipvs0: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN group default link/ether d6:37:7a:8c:62:e2 brd ff:ff:ff:ff:ff:ffinet 10.96.0.2/32 scope global kube-ipvs0valid_lft forever preferred_lft foreverinet 10.97.82.53/32 scope global kube-ipvs0valid_lft forever preferred_lft foreverinet 10.96.0.1/32 scope global kube-ipvs0valid_lft forever preferred_lft foreverinet 10.100.104.51/32 scope global kube-ipvs0valid_lft forever preferred_lft foreverinet 10.108.32.102/32 scope global kube-ipvs0valid_lft forever preferred_lft forever
在 Calico 的 IPIP 模式 中,kube-ipvs0
、tunl0
和 eth0
网卡分别有不同的角色,它们与集群中的流量转发和路由机制密切相关。下面我会解释它们各自的作用以及它们之间的关系。
5.1 kube-ipvs0 网卡
-
作用:
kube-ipvs0
是在 IPVS 模式 下由kube-proxy
创建的虚拟网卡。它用于实现 Kubernetes 服务的负载均衡。它会处理从集群外部(或其他 Pod)流向服务虚拟 IP(VIP)的流量,并将这些流量根据负载均衡规则转发到服务的后端 Pod。 -
工作原理:
- 在 Kubernetes 中,
kube-proxy
使用 IPVS(IP Virtual Server) 来管理负载均衡。kube-ipvs0
是用来承载这些负载均衡规则的网络设备,流量经过kube-ipvs0
时会被转发到相应的后端 Pod。 kube-ipvs0
会将流量从服务的虚拟 IP(VIP)转发到实际的 Pod IP(如172.16.64.20
6)。
- 在 Kubernetes 中,
5.2 tunl0 网卡
-
作用:
tunl0
是 Calico 在启用 IPIP 模式 时创建的虚拟网卡,用于支持 IPIP 隧道。当 Pod 需要通过 IPIP 隧道跨节点通信时,流量会通过tunl0
网卡进行封装。 -
工作原理:
- 在 Calico IPIP 模式下,当流量从一个节点上的 Pod 发往另一个节点上的 Pod 时,流量会通过
tunl0
网卡进行封装。 tunl0
网卡会将流量封装为 IPIP 包,将其发送到目标节点的tunl0
网卡。在目标节点上,calico-node
会解封装这个 IPIP 包,将流量转发到目标 Pod。tunl0
网卡本身并不用于直接接收或发送应用层数据流量,它只是一个传输层的封装/解封装网卡。
- 在 Calico IPIP 模式下,当流量从一个节点上的 Pod 发往另一个节点上的 Pod 时,流量会通过
5.3 eth0 网卡
-
作用:
eth0
是 Kubernetes 节点上的 物理网卡,用于与集群外部或其他节点进行通信。 -
工作原理:
eth0
是节点与外部网络的主要接口。它用于处理来自外部网络的流量或集群内的普通数据流量。- 如果 Pod 在同一节点上,它们的流量通常通过
eth0
进行路由,而不需要经过 IPIP 隧道。 - eth0 也可能用于与其他节点之间的通信(比如集群外部的访问请求),但是这取决于集群的路由策略和 Calico 配置。
5.4 这三者之间的关系
-
服务流量的转发(
kube-ipvs0
):- 当 Pod 或外部请求访问某个服务的虚拟 IP(例如
10.97.82.53
),流量会首先到达节点上的kube-ipvs0
网卡。 kube-ipvs0
根据 IPVS 规则将流量转发到后端 Pod 的真实 IP(如172.16.64.20
6)。这个转发过程是在同一节点内进行的,不需要 IPIP 隧道。
- 当 Pod 或外部请求访问某个服务的虚拟 IP(例如
-
跨节点的 Pod 通信(
tunl0
):- 如果目标 Pod 位于 不同节点,流量就需要通过 IPIP 隧道 进行跨节点传输。这时,
tunl0
网卡会参与流量的封装。 - 例如,流量从
ingress-nginx-controller
Pod 发出,目标 Pod 在其他节点,流量会通过tunl0
封装为 IPIP 包,通过节点的eth0
网卡发送到目标节点。在目标节点,tunl0
解封装流量并将其转发给目标 Pod。
- 如果目标 Pod 位于 不同节点,流量就需要通过 IPIP 隧道 进行跨节点传输。这时,
-
物理节点通信(
eth0
):eth0
是用于节点之间的通信(比如节点间的路由和外部通信)。- 对于 IPIP 模式下的跨节点流量,
eth0
作为物理网络接口用于承载封装的 IPIP 流量,帮助传输流量到目标节点。
5.5 流量路径示例
在 ingress-nginx-controller
Pod 中,流量访问服务 10.97.82.53:8888
,目标 Pod IP 是 172.16.64.206:8888
,并且目标 Pod 位于 不同节点:
-
流量经过
kube-ipvs0
:- 请求首先到达
kube-ipvs0
网卡,kube-proxy
将流量从服务虚拟 IP(10.97.82.53
)转发到后端 Pod IP(172.16.64.20
6)。
- 请求首先到达
-
流量需要跨节点传输,进入
tunl0
隧道:- 由于目标 Pod 位于另一个节点,
calico-node
在源节点通过tunl0
网卡将流量封装为 IPIP 包。
- 由于目标 Pod 位于另一个节点,
-
流量通过
eth0
传输到目标节点:- 封装后的 IPIP 包通过源节点的
eth0
网卡发送到目标节点。
- 封装后的 IPIP 包通过源节点的
-
目标节点通过
tunl0
解封装流量并转发给目标 Pod:- 在目标节点,
calico-node
通过tunl0
解封装 IPIP 包,并将流量转发(ARP)目标 Pod172.16.64.206:8888
。
- 在目标节点,
5.6 总结
kube-ipvs0
:由kube-proxy
创建,负责处理服务虚拟 IP 的负载均衡,将流量转发到后端 Pod。tunl0
:由 Calico 创建,负责在跨节点通信时封装和解封装 IPIP 包,确保流量能够穿越节点之间的隧道。eth0
:物理网卡,用于节点间的通信,承载封装后的 IPIP 包并将流量传输到目标节点(ARP)。
它们之间的关系是:kube-ipvs0
负责服务负载均衡,tunl0
负责 IPIP 隧道的封装与解封装,而 eth0
作为物理网卡用于在节点之间传输流量。
需要跨节点的时候,需要进行tunl0进行封包
nginx_318">六 ingress-nginx容器处理流量
容器 ingress-nginx-controller-556d4fff79-9jdmq
IP 172.16.219.89收到请求
进入容器 查看 nginx location规则找到 目标域名 jupyter.jcrose.com
kubectl exec -it ingress-nginx-controller-556d4fff79-9jdmq -n ingress-nginx -- cat /etc/nginx/nginx.conf
server jupyter.jcrose.com 部分指明了 namespace,service_name,service_port
## start server jupyter.jcrose.comserver {server_name jupyter.jcrose.com ;listen 80 ;listen [::]:80 ;listen 443 ssl http2 ;listen [::]:443 ssl http2 ;set $proxy_upstream_name "-";ssl_certificate_by_lua_block {certificate.call()}location / {set $namespace "default";set $ingress_name "demo-jupyter";set $service_name "jupyter-service";set $service_port "8888";set $location_path "/";set $global_rate_limit_exceeding n;rewrite_by_lua_block {lua_ingress.rewrite({force_ssl_redirect = false,ssl_redirect = true,force_no_ssl_redirect = false,preserve_trailing_slash = false,use_port_in_redirects = false,global_throttle = { namespace = "", limit = 0, window_size = 0, key = { }, ignored_cidrs = { } },})balancer.rewrite()plugins.run()}# be careful with `access_by_lua_block` and `satisfy any` directives as satisfy any# will always succeed when there's `access_by_lua_block` that does not have any lua code doing `ngx.exit(ngx.DECLINED)`# other authentication method such as basic auth or external auth useless - all requests will be allowed.#access_by_lua_block {#}header_filter_by_lua_block {lua_ingress.header()plugins.run()}body_filter_by_lua_block {plugins.run()}log_by_lua_block {balancer.log()monitor.call()plugins.run()}port_in_redirect off;set $balancer_ewma_score -1;set $proxy_upstream_name "default-jupyter-service-8888";set $proxy_host $proxy_upstream_name;set $pass_access_scheme $scheme;set $pass_server_port $server_port;set $best_http_host $http_host;set $pass_port $pass_server_port;set $proxy_alternative_upstream_name "";client_max_body_size 1m;proxy_set_header Host $best_http_host;# Pass the extracted client certificate to the backend# Allow websocket connectionsproxy_set_header Upgrade $http_upgrade;proxy_set_header Connection $connection_upgrade;proxy_set_header X-Request-ID $req_id;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $remote_addr;proxy_set_header X-Forwarded-Host $best_http_host;proxy_set_header X-Forwarded-Port $pass_port;proxy_set_header X-Forwarded-Proto $pass_access_scheme;proxy_set_header X-Forwarded-Scheme $pass_access_scheme;proxy_set_header X-Scheme $pass_access_scheme;# Pass the original X-Forwarded-Forproxy_set_header X-Original-Forwarded-For $http_x_forwarded_for;# mitigate HTTPoxy Vulnerability# https://www.nginx.com/blog/mitigating-the-httpoxy-vulnerability-with-nginx/proxy_set_header Proxy "";# Custom headers to proxied serverproxy_connect_timeout 5s;proxy_send_timeout 60s;proxy_read_timeout 60s;proxy_buffering off;proxy_buffer_size 4k;proxy_buffers 4 4k;proxy_max_temp_file_size 1024m;proxy_request_buffering on;proxy_http_version 1.1;proxy_cookie_domain off;proxy_cookie_path off;# In case of errors try the next upstream server before returning an errorproxy_next_upstream error timeout;proxy_next_upstream_timeout 0;proxy_next_upstream_tries 3;proxy_pass http://upstream_balancer;proxy_redirect off;}}## end server jupyter.jcrose.com
nginx_466">七 ingress-nginx容器请求流量
在nginx location获取到的数据如下
set $namespace "default";set $ingress_name "demo-jupyter";set $service_name "jupyter-service";set $service_port "8888";
发送的请求
jupyter-service.default.svc.cluster.local:8888
7.1 由coredns获取到 ip地址
服务名 | 方向 | 节点IP | SVC地址 | 端口 | 容器IP |
---|---|---|---|---|---|
ingress-nginx-controller-556d4fff79-9jdmq | 源地址 | 192.168.44.141 | 10.100.104.51 | 80(本次使用的) | 172.16.219.89(本次使用的) |
jupyter-deployment-57454c6795-xpgfk | 目标地址 | 192.168.44.142 | 10.97.82.53(本次使用的) | 8888 | 172.16.64.206 |
来源: 172.16.219.89:80(ingress-nginx的 pod ip以及端口)
请求的地址 10.97.82.53:8888(jupyter的svc地址)
7.2 查看该容器的路由表
ingress-nginx-controller-556d4fff79-9jdmq:/etc/nginx$ route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 169.254.1.1 0.0.0.0 UG 0 0 0 eth0
169.254.1.1 0.0.0.0 255.255.255.255 UH 0 0 0 eth0
可以看到 0.0.0.0 指向了 169.254.1.1(eth0),查看容器ip地址
ingress-nginx-controller-556d4fff79-9jdmq:/etc/nginx$ ip addr
4: eth0@if9: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1480 qdisc noqueue state UP link/ether 3a:0d:53:fb:ec:92 brd ff:ff:ff:ff:ff:ffinet 172.16.219.83/32 scope global eth0valid_lft forever preferred_lft foreverinet6 fe80::380d:53ff:fefb:ec92/64 scope link valid_lft forever preferred_lft forever
这里容器获取的是 /32 位主机地址,表示将容器 A 作为一个单点的局域网。
eth0@if9为在宿主机上创建的虚拟网桥的一端,另一端为 calibfbdf93cf51。
而calibfbdf93cf51又开启了proxy arp,所以流量就从pod内部到达了master 节点
解释:
当一个数据包的目的地址不是本机时,就会查询路由表,从路由表中查到网关后,它首先会通过 ARP
获得网关的 MAC 地址,然后在发出的网络数据包中将目标 MAC 改为网关的 MAC,而网关的 IP 地址不会出现在任何网络包头中。也就是说,没有人在乎这个 IP 地址究竟是什么,只要能找到对应的 MAC 地址,能响应 ARP 就行了。
Calico 利用了网卡的代理 ARP 功能。代理 ARP 是 ARP 协议的一个变种,当 ARP 请求目标跨网段时,网关设备收到此 ARP 请求,会用自己的 MAC 地址返回给请求者,这便是代理 ARP(Proxy ARP)。
八 从Master节点流出的流量分析
服务名 | 方向 | 节点IP | IP地址 | 端口 |
---|---|---|---|---|
ingress-nginx-controller-556d4fff79-9jdmq | 源地址 | 192.168.44.141 | 172.16.219.89(使用) | 80 |
jupyter-deployment-57454c6795-xpgfk | 目标地址 | 192.168.44.142 | 10.97.82.53(使用) | 8888 |
容器eth0 => calico虚拟网卡 => ipvs网卡 => 需要跨节点则 tunl网卡,封装ip(可选,不一定会用它) => 宿主机 eth0
- 查看svc网段是否通过tunl0网卡的网段? 查看本机的 ip a的ipvs网卡,存在这个IP 10.97.82.53
8.1 查看ipvsadm的负载均衡
root@master:~# ipvsadm --list
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags-> RemoteAddress:Port Forward Weight ActiveConn InActConn
.............
TCP 10.97.82.53:8888 rr-> 172.16.64.206:8888 Masq 1 0 0
.....................
得到目标的pod地址,172.16.64.206
8.2 master节点的路由表
Master节点的路由表,可以看到有一条规则是
Destination Gateway Genmask Flags Metric Ref Use Iface
172.16.64.192 192.168.44.142 255.255.255.192 UG 0 0 0 tunl0
给定的网络信息是:
- 网络地址:172.16.64.192
- 子网掩码:255.255.255.192
首先,可以将子网掩码转换为网络范围。255.255.255.192(即 /26)表示每个子网有64个地址(2^6)。因此,这个网段的范围是:
- 网络地址:172.16.64.192
- 广播地址:172.16.64.255
- 可用的IP范围:172.16.64.193 到 172.16.64.254
因此,该网段的IP范围是 172.16.64.193 到 172.16.64.254。
目标 172.16.64.206 IP存在 于这个IP范围,因此主机的下一跳地址为:192.168.44.142主机的 Tunl0网卡。
root@master:~# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 192.168.44.2 0.0.0.0 UG 100 0 0 ens33
172.16.64.192 192.168.44.142 255.255.255.192 UG 0 0 0 tunl0
172.16.219.64 0.0.0.0 255.255.255.192 U 0 0 0 *
172.16.219.87 0.0.0.0 255.255.255.255 UH 0 0 0 cali141c3047c6b
172.16.219.88 0.0.0.0 255.255.255.255 UH 0 0 0 cali780b4ee0aa6
172.16.219.89 0.0.0.0 255.255.255.255 UH 0 0 0 calibfbdf93cf51
172.16.219.90 0.0.0.0 255.255.255.255 UH 0 0 0 calie7fabde01ba
192.168.44.0 0.0.0.0 255.255.255.0 U 100 0 0 ens33
192.168.44.2 0.0.0.0 255.255.255.255 UH 100 0 0 ens33
跨节点
- IPIP模式下,node间的Pod访问会使用IPIP技术对出node的ip报进行隧道封装
- Pod的ip都是由calico-node设置的IP地址池进行分配的,Calico会为每一个node分配一小段网络。
8.3 对tunl0网卡进行分析
-
IPIP封装:
- 由于
172.16.64.206
位于另一个节点,Calico会使用IPIP隧道将流量封装。 - 原始IP包(源IP为
172.16.219.
89,目标IP为172.16.64.206
)会被封装在一个新的IP包中。 - 新的IP包的源IP是
192.168.44.141
(master
节点的IP),目标IP是192.168.44.142
(slave
节点的IP)。 - 这个新的IP包会通过
tunl0
网卡发送出去。
- 由于
封装后的IP包结构
-
外层IP头:
- 源IP:
192.168.44.141
(master
节点的IP) - 目标IP:
192.168.44.142
(slave
节点的IP) - 协议类型:IPIP(协议号4)
- 源IP:
-
内层IP头:
- 源IP:
172.16.219.
89(原始Pod的IP) - 目标IP:
172.16.64.206
(目标Pod的IP) - 协议类型:TCP/UDP等(取决于原始流量)
- 源IP:
九 k8s-slave节点分析
-
流量到达
slave
节点:slave
节点192.168.44.142
接收到这个IPIP封装的包。tunl0
网卡会解封装这个包,提取出原始的IP包(源IP为172.16.219.
89,目标IP为172.16.64.206
)。- 然后,
slave
节点会根据本地的路由表将流量转发到目标Pod172.16.64.206
。
省流版总结
源主机 | 源端口 | 目标地址 | 目标端口 | 注释 | |
---|---|---|---|---|---|
发起者 | 10.250.0.32 | 随机端口 | jupyter.jcrose.com | 80 | |
Windows /etc/host解析域名 | 10.250.0.32 | 随机端口 | 192.168.44.141 | 80 | |
Iptables规则解析Nodeport | 192.168.44.141 | 随机端口 | 172.16.219.89 | 80 | 通过nodeport的相关iptables获取到 ingress-nginx的pod IP |
ingress-nginx发起请求 | 172.16.219.89 | 80 | 10.97.82.53 | 8888 | 通过 nginx.conf获取 jupyter.jcrose.com的信息,通过coredns获取到 SVC地址。 |
ingress-nginx发起请求 | 192.168.44.141 | 随机端口 | 192.168.44.142 | 8888 | 这里流量经过了tunl0网卡把封包,把源地址和目标地址都修改为了目标主机 |
jupyter容器处理流量 | 172.16.219.89 | 随机端口 | 172.16.64.206 | 8888 | 经过slave tunl0网卡的流量自动解包 获取到真实的源地址和目标地址(ingress-nginx的配置可以修改显示源地址或者容器地址) |
查看 ingress-nginx-controller的日志证实确实如下
192.168.44.141 - - [24/Jan/2025:09:23:17 +0000] "GET / HTTP/1.1" 302 0 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" 459 0.005 [default-jupyter-service-8888] [] 172.16.64.206:8888 0 0.004 302 5a0bfabc5d693d4a7cae94921207f78c
192.168.44.141 - - [24/Jan/2025:09:23:17 +0000] "GET /lab? HTTP/1.1" 302 0 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" 463 0.002 [default-jupyter-service-8888] [] 172.16.64.206:8888 0 0.001 302 b4218276d6b133d8c68c237ea856fe79
192.168.44.141 - - [24/Jan/2025:09:23:17 +0000] "GET /login?next=%2Flab%3F HTTP/1.1" 200 6251 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" 479 0.022 [default-jupyter-service-8888] [] 172.16.64.206:8888 6251 0.022 200 24d3466f6b14ca223118edb7b58fd367
源地址: 192.168.44.141(端口这里看不到)
目标地址: 172.16.64.206:8888