分析用户请求K8S里ingress-nginx提供的ingress流量路径

前言

本文是个人的小小见解,欢迎大佬指出我文章的问题,一起讨论进步~

我个人的疑问点

  1. 进入的流量是如何自动判断进入iptables的四表?
  2. 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条规则

shellscript 复制代码
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:443


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
③ 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设备介绍

  1. Pod 流量的目的地是同一节点上的 Pod。
  2. Pod 流量的目的地是在不同节点上的 Pod。

整个工作流依赖于虚拟接口对和网桥,下面先来了解一下这部分的内容。

为了让一个 Pod 与其他 Pod 通信,它必须先访问节点的根命名空间。

通过虚拟以太网对来实现 Pod 和根命名空间的连接。

这些虚拟接口设备(veth 中的 v)连接并充当两个命名空间之间的隧道。

查看master节点上的网卡情况

复制代码
ip a

3: 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:ff
    inet 10.96.0.2/32 scope global kube-ipvs0
       valid_lft forever preferred_lft forever
    inet 10.97.82.53/32 scope global kube-ipvs0
       valid_lft forever preferred_lft forever
    inet 10.96.0.1/32 scope global kube-ipvs0
       valid_lft forever preferred_lft forever
    inet 10.100.104.51/32 scope global kube-ipvs0
       valid_lft forever preferred_lft forever
    inet 10.108.32.102/32 scope global kube-ipvs0
       valid_lft forever preferred_lft forever

在 Calico 的 IPIP 模式 中,kube-ipvs0tunl0eth0 网卡分别有不同的角色,它们与集群中的流量转发和路由机制密切相关。下面我会解释它们各自的作用以及它们之间的关系。

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.206)。

5.2 tunl0 网卡

  • 作用tunl0 是 Calico 在启用 IPIP 模式 时创建的虚拟网卡,用于支持 IPIP 隧道 。当 Pod 需要通过 IPIP 隧道跨节点通信时,流量会通过 tunl0 网卡进行封装。

  • 工作原理

    • 在 Calico IPIP 模式下,当流量从一个节点上的 Pod 发往另一个节点上的 Pod 时,流量会通过 tunl0 网卡进行封装。
    • tunl0 网卡会将流量封装为 IPIP 包,将其发送到目标节点的 tunl0 网卡。在目标节点上,calico-node 会解封装这个 IPIP 包,将流量转发到目标 Pod。
    • tunl0 网卡本身并不用于直接接收或发送应用层数据流量,它只是一个传输层的封装/解封装网卡。

5.3 eth0 网卡

  • 作用eth0 是 Kubernetes 节点上的 物理网卡,用于与集群外部或其他节点进行通信。

  • 工作原理

    • eth0 是节点与外部网络的主要接口。它用于处理来自外部网络的流量或集群内的普通数据流量。
    • 如果 Pod 在同一节点上,它们的流量通常通过 eth0 进行路由,而不需要经过 IPIP 隧道。
    • eth0 也可能用于与其他节点之间的通信(比如集群外部的访问请求),但是这取决于集群的路由策略和 Calico 配置。

5.4 这三者之间的关系

  1. 服务流量的转发(kube-ipvs0

    • 当 Pod 或外部请求访问某个服务的虚拟 IP(例如 10.97.82.53),流量会首先到达节点上的 kube-ipvs0 网卡。
    • kube-ipvs0 根据 IPVS 规则将流量转发到后端 Pod 的真实 IP(如 172.16.64.206)。这个转发过程是在同一节点内进行的,不需要 IPIP 隧道。
  2. 跨节点的 Pod 通信(tunl0

    • 如果目标 Pod 位于 不同节点 ,流量就需要通过 IPIP 隧道 进行跨节点传输。这时,tunl0 网卡会参与流量的封装。
    • 例如,流量从 ingress-nginx-controller Pod 发出,目标 Pod 在其他节点,流量会通过 tunl0 封装为 IPIP 包,通过节点的 eth0 网卡发送到目标节点。在目标节点,tunl0 解封装流量并将其转发给目标 Pod。
  3. 物理节点通信(eth0

    • eth0 是用于节点之间的通信(比如节点间的路由和外部通信)。
    • 对于 IPIP 模式下的跨节点流量,eth0 作为物理网络接口用于承载封装的 IPIP 流量,帮助传输流量到目标节点。

5.5 流量路径示例

ingress-nginx-controller Pod 中,流量访问服务 10.97.82.53:8888,目标 Pod IP 是 172.16.64.206:8888,并且目标 Pod 位于 不同节点

  1. 流量经过 kube-ipvs0

    • 请求首先到达 kube-ipvs0 网卡,kube-proxy 将流量从服务虚拟 IP(10.97.82.53)转发到后端 Pod IP(172.16.64.206)。
  2. 流量需要跨节点传输,进入 tunl0 隧道

    • 由于目标 Pod 位于另一个节点,calico-node 在源节点通过 tunl0 网卡将流量封装为 IPIP 包。
  3. 流量通过 eth0 传输到目标节点

    • 封装后的 IPIP 包通过源节点的 eth0 网卡发送到目标节点。
  4. 目标节点通过 tunl0 解封装流量并转发给目标 Pod

    • 在目标节点,calico-node 通过 tunl0 解封装 IPIP 包,并将流量转发(ARP)目标 Pod 172.16.64.206:8888

5.6 总结

  • kube-ipvs0 :由 kube-proxy 创建,负责处理服务虚拟 IP 的负载均衡,将流量转发到后端 Pod。
  • tunl0:由 Calico 创建,负责在跨节点通信时封装和解封装 IPIP 包,确保流量能够穿越节点之间的隧道。
  • eth0:物理网卡,用于节点间的通信,承载封装后的 IPIP 包并将流量传输到目标节点(ARP)。

它们之间的关系是:kube-ipvs0 负责服务负载均衡,tunl0 负责 IPIP 隧道的封装与解封装,而 eth0 作为物理网卡用于在节点之间传输流量。

需要跨节点的时候,需要进行tunl0进行封包

六 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.com
        server {
                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 connections
                        proxy_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-For
                        proxy_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 server

                        proxy_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 error
                        proxy_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

七 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:ff
    inet 172.16.219.83/32 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 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网卡进行分析

  1. IPIP封装

    • 由于172.16.64.206位于另一个节点,Calico会使用IPIP隧道将流量封装。
    • 原始IP包(源IP为172.16.219.89,目标IP为172.16.64.206)会被封装在一个新的IP包中。
    • 新的IP包的源IP是192.168.44.141master节点的IP),目标IP是192.168.44.142slave节点的IP)。
    • 这个新的IP包会通过tunl0网卡发送出去。

封装后的IP包结构

  • 外层IP头

    • 源IP:192.168.44.141master节点的IP)
    • 目标IP:192.168.44.142slave节点的IP)
    • 协议类型:IPIP(协议号4)
  • 内层IP头

    • 源IP:172.16.219.89(原始Pod的IP)
    • 目标IP:172.16.64.206(目标Pod的IP)
    • 协议类型:TCP/UDP等(取决于原始流量)

九 k8s-slave节点分析

  • 流量到达slave节点

    • slave节点192.168.44.142接收到这个IPIP封装的包。
    • tunl0网卡会解封装这个包,提取出原始的IP包(源IP为172.16.219.89,目标IP为172.16.64.206)。
    • 然后,slave节点会根据本地的路由表将流量转发到目标Pod 172.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

相关推荐
me8321 天前
【Java】踩坑实录:Spring Boot + Nginx 本地部署404终极排查:从80端口被占用到配置生效全流程
java·spring boot·nginx
merlin-mm1 天前
GPU 间的通信方式
云原生·容器·kubernetes
编码如写诗1 天前
【k8s】arm架构从零开始在线/离线部署k8s1.34.5+KubeSphere3.4.1
arm开发·架构·kubernetes
徐子元竟然被占了!!1 天前
docker-dockerfile练习
运维·docker·容器
Malone-AI1 天前
docker换镜像源(docker desktop)
docker·容器
qhqh3101 天前
k8s的service、ingress controller和ingress
云原生·容器·kubernetes
susu10830189111 天前
Ubuntu 离线环境 安装 Docker Compose
运维·docker·容器
fengyehongWorld1 天前
docker compose的使用
运维·docker·容器
卤炖阑尾炎1 天前
Web 技术基础与 Nginx 网站环境部署全解析
前端·nginx·microsoft
永远不会出bug1 天前
调整nginx代理 并获取到访问你网站的用户本机 IP
运维·tcp/ip·nginx