Kubernetes 网络流量全链路追踪手册
🛣️ 深度解密:Kubernetes 网络流量全链路追踪手册
作者:小小 (Xiaoxiao) —— 你的资深技术布道师
阅读时长:约 25 分钟
难度:⭐⭐⭐⭐ (涉及 Linux 内核网络、Netfilter、CNI 原理)
核心标签:Packet WalkNetfilterIptablesIPVSKlipperIngress
🧭 导览 (Table of Contents)
- 🛑 认知重塑:是谁决定了流量的去向?
- 纠正“Pod 决定论”误区
- 核心原则:工人与调度员
- 🧱 第一层:裸奔时代 (HostNetwork)
- 原理:Namespace 的共享机制
- 路径追踪:最快,但也最危险
- 🕳️ 第二层:打洞时代 (HostPort)
- 原理:CNI 插件的 DNAT 魔法
- 为什么它没有负载均衡能力?
- 🚪 第三层:内核转发时代 (NodePort)
- 原理:Kube-proxy 与 iptables/IPVS 的共舞
- 路径追踪:数据包的第一次“整容” (DNAT)
- 👮♂️ 第四层:代理中介时代 (K3s ServiceLB)
- 原理:用户态代理与内核态转发的混合双打
- K3s 特有的
svclb机制解密
- 🚦 第五层:智能路由时代 (Ingress)
- 原理:应用层 (L7) 的上帝视角
- 性能优化:为什么它敢绕过 Service?
- 📊 终极上帝视角 (God View)
- 全模式流量路径对比图 (Mermaid)
- 核心特性对比表
- 🙋♀️ 小小 Q&A:避坑指南
1. 🛑 认知重塑:是谁决定了流量的去向?
在开始追踪数据包之前,我们必须先扫除一个阻碍了 90% 初学者理解网络的巨大障碍。
❌ 常见误区:“是 Pod 的配置决定了流量方式吗?”
很多同学认为:“因为我的 Pod 配置了 NodePort,所以流量才走 NodePort。” 或者 “Pod 发现自己是 ServiceLB,所以把流量转给别人。”
🙅♀️ 错!大错特错!
✅ 真相:Pod 是“盲”的
Pod(以及里面的容器进程)对此一无所知。
- Pod 就像流水线上的工人:它只知道监听本地的端口(比如 80),谁发给它数据包,它就处理,处理完了就返回。
- Service/Ingress 就像调度员:它们负责在 Pod 之外,构建复杂的道路系统,把流量引导到工人的工位上。
核心决策链:
- Service/Ingress 定义规则:你写 YAML 告诉 K8s “我要 NodePort”。
- K8s 组件执行规则:Kube-proxy、Ingress Controller、CNI 插件根据你的指令,去修改 Linux 内核的路由表、iptables 规则或 Nginx 配置文件。
- Pod 被动接收:Pod 根本不知道流量经历了什么九九八十一难才到达它这里。
💡 小小比喻:
Pod 是你家房子。NodePort、Ingress 是外面的路牌和导航系统。
你坐在家里(Pod),根本不知道客人是看路牌来的,还是用 GPS 导航来的。你只知道有人敲门了。
2. 🧱 第一层:裸奔时代 (HostNetwork)
这是 Kubernetes 网络中最原始、最简单,同时也是 性能天花板 的模式。
1.1 ⚙️ 核心原理:Namespace 的“寄生”机制
在 Docker/K8s 的世界里,容器之所以被称为“容器”,是因为它们被 Linux Namespace 隔离在一个个独立的盒子里。
- 普通 Pod:拥有自己独立的 Network Namespace。它们像住在独立的公寓里,有自己的门牌号(虚拟 IP)、自己的网线(veth pair)和路由表。
- HostNetwork Pod:彻底放弃隔离。它不创建新的网络空间,而是直接**“寄生”**在宿主机的
initNetwork Namespace 中。
这意味着:
Pod 里的进程“看”到的网络世界,和宿主机上的 SSH 进程看到的一模一样。它们共享同一张网卡、同一个 IP、同一张路由表。容器与宿主机在网络层面实现了“人剑合一”。
1.2 📦 流量路径追踪 (Packet Walk)
场景:用户访问宿主机 Node A,IP 为 192.168.1.100。
- 入站 (Ingress):
- 数据包通过物理网线,光速到达宿主机的物理网卡 (
eth0)。 - 网卡触发中断,将数据包送入内核 Ring Buffer。
- 数据包通过物理网线,光速到达宿主机的物理网卡 (
- 内核协议栈 (Kernel Stack):
- 内核网络栈(TCP/IP Stack)解析数据包头部,发现目标 IP 就是 本机 IP (192.168.1.100)。
- 内核移除 IP 头和 TCP 头,准备向上层交付数据。
- Socket 匹配 (The Truth):
- 内核查询 Socket 表:“谁在监听 TCP 80?”
- 关键点:内核发现正是 Pod 里的 Nginx 进程在监听。
- 注意:在内核眼里,这个 Nginx 进程和宿主机上跑的普通进程没有任何区别!内核根本不知道它属于哪个容器。
- 交付 (Delivery):
- 内核直接将数据从内核空间拷贝到 Nginx 进程的用户空间缓冲区。通信完成。
graph TD
User((👤 用户)) -->|访问 NodeIP| PhyNIC[🔌 物理网卡 eth0]
subgraph NodeA ["🖥️ Node A (宿主机)"]
PhyNIC -->|中断/DMA| KernelStack[⚙️ 宿主机内核协议栈]
subgraph UserSpace [用户空间]
KernelStack -->|Socket 匹配: 直接交付| App["🚀 容器进程 (Nginx)"]
end
note1[📝 Note: 容器与宿主机共享 IP 和端口<br>无 NAT 损耗] -.-> App
end
style App fill:#f9f,stroke:#333,stroke-width:2px
style KernelStack fill:#ff9,stroke:#333
✅ 极致性能 (Zero Overhead):
这是一个 “0 跳” 路径。
- 没有 iptables/IPVS 的 NAT 转换。
- 没有 CNI 插件的 Bridge 桥接。
- 没有 VXLAN/IPIP 的封包解包。
- 它就是物理机原生网络的性能。
❌ 致命缺陷:
- 调度死锁:宿主机 80 端口是物理资源,全机唯一。一旦被 Pod A 占了,Pod B 就别想在同节点启动。这导致 K8s 的调度器常常无节点可选。
- 安全裸奔:Pod 能监听到宿主机上所有的网络流量,甚至能操作防火墙规则,安全风险极高。
3. 🕳️ 第二层:打洞时代 (HostPort)
很多同学分不清 HostNetwork 和 HostPort,觉得都是用宿主机端口,有啥区别?
- HostNetwork 是**“睡在客厅”**:Pod 没有独立的网络空间,完全裸露,没有隐私。
- HostPort 是**“睡在房间,但在墙上凿了个洞”**:Pod 依然拥有独立的 IP 和 Namespace(隐私受保护),只是通过端口映射与外界沟通。
3.1 ⚙️ 核心原理:CNI 的 DNAT 魔法 (Bypassing Kube-proxy)
在这种模式下,Kube-proxy 是完全不知情的。这是 CNI 插件(如 Flannel, Calico)干的“私活”。
CNI 插件会在宿主机的 iptables (NAT 表) 或专门的 CNI Chain 中,插入一条静态的 DNAT (目标地址转换) 规则。这就像是在防火墙上硬生生开了一条专用通道。
3.2 📦 流量路径追踪
场景:
- 宿主机:Node A,IP
192.168.1.100。 - Pod:运行在 Node A 上,虚拟 IP
10.42.1.5,监听端口 8080。 - 映射:
宿主机:18080->Pod:8080。
- 入站 (Ingress):
- 用户访问
(192.168.1.100)Node-A-IP:18080。 - 数据包到达宿主机网卡。
- 用户访问
- 内核 Netfilter (PREROUTING 链):
- 拦截:数据包刚进门,就被 CNI 设置的 iptables 规则拦住。
- DNAT (偷梁换柱):内核根据规则,直接将数据包头部的目标地址
(192.168.1.100)Node-A-IP:18080修改为(10.42.1.5)Pod-IP:8080。 - 注意:此时数据包的目标已经变成了一个虚拟的 Pod IP。
- 路由决策 (Routing):
- 内核查路由表:发现
(10.42.1.5)Pod-IP属于 CNI 管理的虚拟网段(例如10.42.x.x)。 - 内核决定将包扔给
cni0网桥(或者 Calico 的虚拟接口)。
- 内核查路由表:发现
- 转发与交付 (Forwarding):
- 数据包通过 veth pair (虚拟网线),穿越 Network Namespace 的边界,最终钻进了 Pod 的肚子里。
graph TD
User((👤 用户)) -->|访问 NodeIP:18080| PhyNIC[🔌 物理网卡 eth0]
subgraph NodeA [🖥️ Node A]
PhyNIC --> Netfilter[🛡️ 内核 Netfilter / PREROUTING]
Netfilter -->|1. CNI 规则拦截| DNAT[🔄 DNAT: 目标变更为 PodIP:8080]
DNAT -->|2. 路由决策| Bridge[🌉 cni0 网桥 / Calico veth]
subgraph PodNS [📦 Pod Network Namespace]
Bridge -->|veth pair| Container[🚀 容器进程]
end
end
style DNAT fill:#f96,stroke:#333,stroke-width:2px
style Netfilter fill:#ff9,stroke:#333
🧠 小小深度剖析:为什么它没有负载均衡?
很多同学问:“我启动了 3 个副本,都配了 HostPort,为什么不能负载均衡?”原因: HostPort 的 DNAT 规则是 “静态绑定” 且 “本地优先” 的。
- 它明确写死了:“收到 18080,就转给 本机 (Localhost) 的这个 Pod IP”。
- 它不认识隔壁机器的 Pod。
- 端口互斥:正因为它是静态映射,所以 一台机器上只能跑一个 占用该端口的 Pod。你想扩容?对不起,端口冲突(Port Conflict)。
4. 🚪 第三层:内核转发时代 (NodePort)
这是 Kubernetes Service 的基石。此时,Kube-proxy 闪亮登场。
4.1 ⚙️ 核心原理:虚拟 IP 与全集群调度
NodePort 的魔法在于:无论你访问集群里的哪一台机器,都能找到后端的 Pod。
这是通过 Kube-proxy 维护的庞大 iptables 或 IPVS 规则集来实现的。
虽然大家在宿主机上分别占用了不同的物理端口(比如 30001 和 30002),但在 K8s 内部逻辑中,它们都各自映射到了自己专属的 Service ClusterIP 及其虚拟端口。
4.2 📦 流量路径追踪 (NodePort 模式)
kind: Service
spec:
type: NodePort # 开启节点端口暴露
ports:
- name: http
port: 380 # Service 内部虚拟端口 (ClusterIP)
targetPort: 80 # 转发到 Pod 容器的端口 (必须与上面 containerPort 一致)
nodePort: 30080 # 🚀 外部访问端口 (范围 30000-32767)
场景:
- 用户访问:Node A,IP
192.168.1.100,端口30080。 - 目标 Pod:Pod B,运行在 Node B (192.168.1.200) 上,虚拟 IP
10.42.2.8,端口 80。 - 路径:
宿主机30080->service:380->Pod-B-IP:80
- 入站 (Ingress):
- 数据包通过网线到达 Node A 的物理网卡,目标端口 30080。
- 内核 Netfilter (KUBE-SERVICES 链):
- 拦截:iptables/IPVS 规则捕获到发往
30080的流量。 - 逻辑匹配 (Service):内核识别出这个端口对应的是 Service
my-app(逻辑上映射到(10.43.1.10)ClusterIP-my-app:380)。 - 负载均衡 (LB Decision):内核直接读取该
Service关联的 Endpoints 列表[(10.42.1.5)Pod-A-IP:80, (10.42.2.8)Pod-B-IP:80]。 - 决策:根据随机或轮询算法,内核决定:“这次发给 Pod B”。
- DNAT (整容):【关键动作】 内核直接将数据包头部的目标 IP:Port 从
(192.168.1.100)Node-A-IP:30080修改为(10.42.2.8)Pod-B-IP:80。 - (注:这里直接跳过了 ClusterIP,一步到位指向了 Pod)
- 拦截:iptables/IPVS 规则捕获到发往
- 路由决策 (Routing):
- 内核查路由表,发现新的目标 IP
(10.42.2.8)Pod-B-IP不在本地网段,而是属于 CNI 覆盖网络(Overlay)。
- 内核查路由表,发现新的目标 IP
- 封装 (Encapsulation):
- 内核将“整容后”的数据包交给 CNI 插件(如 Flannel)。
- Flannel 将其封装在一个巨大的 UDP 包里 (VXLAN),外层信封的目标 IP 写上 (192.168.1.200)Node B 的物理 IP。
- 跨节点传输:
- 封装包飞过物理网络,到达 Node B。
- 解包与交付:
- Node B 拆开外层信封,发现里层是发给
(10.42.2.8)Pod-B-IP:80的。 - 通过
cni0网桥,将数据包精准投递给 Pod B 容器。
- Node B 拆开外层信封,发现里层是发给
- 注意:
- 物理层 (Node):资源是共享的,所以 NodePort 必须不同(30001 vs 30002),否则会冲突。
- 虚拟层 (ClusterIP):资源是隔离的,每个 Service 都有自己的专属 IP。所以它们的内部端口都可以叫 80,互不干扰。
graph TD
User((👤 用户))
subgraph Node_A ["🖥️ Node A (入口机器)"]
NIC_A["🔌 物理网卡<br>IP: 192.168.1.100<br>NodePort: 30080"]:::hardware
Service_Logic["⚙️ Service 逻辑层<br>ClusterIP: 10.43.1.10<br>Virtual Port: 380"]:::logic
NAT_Layer["🛡️ Netfilter / IPVS (NAT 表)"]:::netfilter
end
subgraph Node_B ["🖥️ Node B (目标机器)"]
NIC_B["🔌 Node B 网卡<br>IP: 192.168.1.200"]:::hardware
Pod_B["🚀 Pod B (Target)<br>IP: 10.42.2.8<br>TargetPort: 80"]:::pod
end
User -- "1. 访问 NodePort<br>Dst: 192.168.1.100:30080" --> NIC_A
NIC_A -- "2. 匹配 Service 规则" --> Service_Logic
Service_Logic -- "3. 选中 Endpoint (Pod B)" --> NAT_Layer
%% NAT 内部循环
NAT_Layer -- "4. DNAT (改目标 IP)<br>Dst 变更为: 10.42.2.8:80" --> NAT_Layer
NAT_Layer -- "5. SNAT (改源 IP/Masquerade)<br>Src 变更为: CNI Gateway IP" --> NAT_Layer
NAT_Layer -- "6. 跨节点隧道 (VXLAN)<br>Outer Dst: 192.168.1.200" --> NIC_B
NIC_B -- "7. 解包 & 交付" --> Pod_B
💡 关键点:
- 一步到位:虽然 YAML 里写了
NodePort -> Port -> TargetPort,但在内核里,是直接从NodePort -> TargetPort(Pod IP) 的。- L4 能力:NodePort 只管 TCP/UDP 数据包的搬运,不看 HTTP 内容。
5. 👮♂️ 第四层:代理中介时代 (K3s ServiceLB)
这是 K3s 为了解决“裸机环境无负载均衡器”痛点而发明的特殊模式。简单来说,它是在 NodePort 的机制之上,外挂了一个 **“驻场中介”**。
5.1 ⚙️ 核心原理:Svclb 的接力跑
K3s 会为每一个 LoadBalancer Service 自动创建一个 DaemonSet (svclb-xxx)。
- svclb Pod:利用
HostPort霸占宿主机的低位端口 (如 88/443/80)。 - 角色:它充当了一个 “流量二传手” (Forwarder)。它打通了宿主机物理网络与 K8s 内部 Service 网络。
- (注:举例用 88 端口是为了防止理解混淆,这并不是一个统一端口,一个
svclbPod 只为一个 Service 服务)。
5.2 📦 流量路径追踪 (复杂的接力)
场景:
- 用户访问:Node A
192.168.1.100,端口88。 - svclb Pod:运行在 Node A,虚拟 IP
10.42.1.9。 - 目标 Service:ClusterIP
10.43.0.50。 - 目标 Pod:Pod C,运行在 Node C,虚拟 IP
10.42.3.6。
- 第一跳:用户 -> svclb Pod (进入代理层)
- 数据包到达 Node A 网卡。
- 拦截:宿主机内核发现端口 88 被
svclbPod 的HostPort绑定。 - DNAT:内核将目标 IP 修改为
(10.42.1.9)svclb-Pod-IP,数据包跨越网络命名空间,钻进 了svclbPod 内部。 - 接收:
svclb内部的转发进程(或 iptables 规则)捕获到该请求。
- 第二跳:svclb Pod -> Service ClusterIP (回归内核层)
svclb既然是中介,它不知道具体哪个 Pod 活著。它只认 Service。- 发起请求:
svclb将流量的目标地址重写为(10.43.0.50)Service-ClusterIP。 - 出站:数据包离开
svclbPod,重新回到 Node A 的宿主机内核空间。
- 第三跳:Service -> 目标 Pod (标准 Kube-proxy 逻辑)
- 此时,流量变成了标准的“集群内访问”。
- 内核拦截:Node A 的 iptables/IPVS 拦截到发往
(10.43.0.50)ClusterIP的流量。 - 负载均衡 (LB Decision):内核查看路由表,决定将流量分发给 Node C 上的业务 Pod。
- DNAT:目标 IP 再次变为
(10.42.3.6)Pod-C-IP。
- 第四跳:跨节点到达
- 数据包封装后,通过 CNI 隧道飞往 Node C,最终交付给业务 Pod。
graph TD
User((👤 用户)) -- "1. TCP 请求<br>Dst: 192.168.1.100:88" --> PhyNIC[🔌 Node A 物理网卡<br>IP: 192.168.1.100]:::hardware
subgraph NodeA ["🖥️ Node A (入口节点)"]
PhyNIC -- "2. HostPort 规则拦截<br>DNAT -> 10.42.1.9:88" --> SVCLB_Pod["🤖 svclb Pod (代理中介)<br>Pod IP: 10.42.1.9"]:::svclb
subgraph SVCLB_Context ["User Space (用户态代理)"]
SVCLB_Pod -- "3. Socket 接收" --> ProxyApp["🔄 转发进程 (Klipper-LB)"]:::process
ProxyApp -- "4. 发起新连接 (重写目标)<br>Dst: Service VIP 10.43.0.50:80" --> Kernel_A["⚙️ 宿主机内核 (Iptables/IPVS)<br>KUBE-SERVICES Chain"]:::kernel
end
Kernel_A -- "5. 匹配 Service VIP" --> Route{⚖️ Service LB 决策}:::kernel
Route -- "6. 选中 Endpoint (Pod C)<br>DNAT -> 10.42.3.6:80" --> Tunnel["📦 隧道封装 (VXLAN)<br>Outer IP: 192.168.1.200"]:::hardware
end
Tunnel == "7. 跨节点传输 (UDP Tunnel)" ==> NodeC_Entry["🔌 Node C 物理网卡<br>IP: 192.168.1.200"]:::hardware
subgraph NodeC_Env ["🖥️ Node C (目标节点)"]
NodeC_Entry -- "8. 解包 & 转发" --> TargetPod["🚀 业务 Pod C<br>IP: 10.42.3.6<br>Port: 80"]:::pod
end
⚠️ 性能警示 (小小划重点):
相比纯粹的 NodePort,K3s ServiceLB 的路径明显变长了。
- 折返跑:流量先从宿主机进 Pod,再从 Pod 出宿主机,多了一次 Network Namespace 的切换。
- 性能损耗:在高并发场景下,这个
svclbPod 会消耗额外的 CPU 和内存资源,且增加了网络延迟。它是为了“易用性”(使用 88 端口)而对“性能”做出的妥协。
6. 🚦 第五层:智能路由时代 (Ingress)
Ingress 是应用层 (L7) 的王者。与 NodePort 这种“搬运工”不同,Ingress 是一个 **“拆包员”**。它不仅看 IP 端口,还要拆开 HTTP 协议包,看里面的 域名 (Host) 和 路径 (Path)。
6.1 ⚙️ 核心原理:绕过 Service 的“短路”机制 (Headless Logic)
虽然你在 Ingress YAML 里写的是 backend: serviceName,但在实际运行中,现代 Ingress Controller (Nginx/Traefik) 极其聪明。
它们会 **“短路”** 掉 Service VIP(ClusterIP)。
- 普通路径:Nginx -> Service VIP -> iptables NAT -> Pod IP。
- Ingress 路径:Nginx -> (直接查 Endpoints) -> Pod IP。
- 目的:减少一跳 NAT,提升性能,且实现应用层负载均衡。
6.2 📦 流量路径追踪 (Smart Routing)
场景:
- 用户访问:域名
http://app.com。 - Ingress Pod:运行在 Node A,虚拟 IP
10.42.1.7。 - 目标 Pod:Pod B,虚拟 IP
10.42.2.3。
- 入站 (L7 Handshake):
- 用户请求到达 Ingress Controller (Nginx) 的 Pod。
- 关键点:Nginx 进程与用户建立了完整的 TCP 连接,并接收了 HTTP 请求。
- 应用层处理 (L7 Processing):
- Nginx 解析 HTTP Header,提取 Host
app.com和 Path/api。 - 查表 (Memory Lookup):Nginx 不会将流量发给 Service VIP(那样就瞎了)。它查看自己内存中实时同步的 Upstream 列表(这个列表是它监听 K8s API 中的 Endpoints 获知的)。
- 此时,Nginx 手里握着一份后端 Pod IP 名单:
[(10.42.1.2)Pod-A, (10.42.2.3)Pod-B]。
- Nginx 解析 HTTP Header,提取 Host
- 智能决策 (L7 LB):
- Nginx 根据配置算法(如 Sticky Session/Cookie 会话保持、Least Conn)智能选中了
(10.42.2.3)Pod-B。 - 注意:这是 iptables 做不到的,因为 iptables 看不懂 Cookie。
- Nginx 根据配置算法(如 Sticky Session/Cookie 会话保持、Least Conn)智能选中了
- 直达 (Direct Routing):
- 发起新连接:Nginx 作为一个客户端,向
(10.42.2.3)Pod-B发起一个新的 TCP 连接。 - 透传:数据包通过 CNI 网络直达目标 Pod,完全绕过了 Service 的 iptables 规则。
- 发起新连接:Nginx 作为一个客户端,向
graph TD
User((👤 用户)) -- "HTTP GET Host: app.com<br>Dst: 192.168.1.100:80" --> IngressNIC[🔌 Node A 物理网卡<br>IP: 192.168.1.100]:::hardware
subgraph NodeA ["🖥️ Node A (Ingress 节点)"]
IngressNIC -- "交付给监听进程" --> IngressPod
subgraph Ingress_NS ["Ingress Pod 内部 (Namespace)"]
IngressPod["🚦 Ingress Controller (Nginx)<br>Pod IP: 10.42.1.7"]:::nginx
IngressPod -- "1. L7 解析<br>Host: app.com<br>Path: /api" --> L7_Logic["🧠 Nginx 路由逻辑"]:::logic
L7_Logic -- "2. 查内存 Upstream 表" --> Upstream_List["📋 Endpoints 列表 (内存)<br>✅ 10.42.2.3:80 (Target)<br>⚪ 10.42.1.2:80 (Other)"]:::logic
%% 短路逻辑展示
ServiceVIP("👻 Service VIP (逻辑存在)<br>ClusterIP: 10.43.0.50"):::bypass
Upstream_List -. "🚫 短路: 绕过 Kube-proxy/NAT" .-> ServiceVIP
end
Upstream_List -- "3. 发起新 TCP 连接 (直连)<br>Src: 10.42.1.7 -> Dst: 10.42.2.3:80" --> Socket_Out["📤 出站 Socket"]:::nginx
end
Socket_Out == "CNI 隧道传输 (VXLAN)<br>Outer IP: 192.168.1.200" ==> NodeB_NIC["🔌 Node B 物理网卡<br>IP: 192.168.1.200"]:::hardware
subgraph NodeB ["🖥️ Node B (业务节点)"]
NodeB_NIC -- "解包 & 交付" --> TargetPod["🚀 业务 Pod B<br>IP: 10.42.2.3<br>Port: 80"]:::pod
end
🚀 性能优化 (小小划重点):
这种模式的杀手锏在于 “会话保持 (Sticky Session)”。
如果你的电商网站需要用户登录(Session 存在内存里),你必须保证同一个用户的请求永远打到同一个 Pod。
- NodePort (L4):做不到,它只认 IP,不认 Cookie。
- Ingress (L7):完美支持,因为它看懂了你的 Cookie。
7. 📊 终极上帝视角 (God View)
为了让你一图胜千言,小小为你绘制了这五种模式的流量全景图。
graph TD
User[👤 用户 User]
subgraph "No_LB_Zone (无负载均衡区)"
direction TB
HostNet[🧱 HostNetwork]
HostP[🕳️ HostPort]
User -- "1. 必须指定 Node IP" --> HostNet
User -- "1. 必须指定 Node IP" --> HostP
HostNet -- "0跳 (共享网卡)" --> PodA[📦 本机 Pod]
HostP -- "DNAT (本机转发)" --> PodA
end
subgraph "Kernel_LB_Zone (内核负载均衡区)"
direction TB
NodePort[🚪 NodePort]
User -- "2. 任意 Node IP:30080" --> NodePort
NodePort -- "iptables/IPVS (L4 LB)" --> Route{⚖️ 路由决策}
Route -- "直达" --> PodB[📦 本机 Pod]
Route -- "CNI 隧道" --> PodC[📦 远端 Pod]
end
subgraph "Proxy_LB_Zone (代理负载均衡区)"
direction TB
Svclb[👮♂️ K3s ServiceLB]
User -- "3. 任意 Node IP:80" --> Svclb
Svclb -- "用户态代理 (User Space)" --> SvclbPod[🤖 svclb Pod]
SvclbPod -- "重新发起请求" --> SvcVIP((🌐 ClusterIP))
SvcVIP -- "iptables/IPVS" --> Route2{⚖️ 路由决策}
Route2 --> PodB
Route2 --> PodC
end
subgraph "Smart_Routing_Zone (智能路由区)"
direction TB
Ing[🚦 Ingress]
User -- "4. 域名 app.com" --> Ing
Ing -- "解析 HTTP 头" --> IngPod[🤖 Nginx/Traefik]
IngPod -- "查 Endpoints (L7 LB)" --> Route3{⚖️ 智能决策}
Route3 -- "直连 Pod IP" --> PodB
Route3 -- "直连 Pod IP" --> PodC
end
style No_LB_Zone fill:#ffebee,stroke:#ef5350,stroke-width:2px
style Kernel_LB_Zone fill:#e3f2fd,stroke:#42a5f5,stroke-width:2px
style Proxy_LB_Zone fill:#fff3e0,stroke:#ffb74d,stroke-width:2px
style Smart_Routing_Zone fill:#e8f5e9,stroke:#66bb6a,stroke-width:2px
⚔️ 核心特性大比拼
| 模式 | 流量层级 | 负载均衡 (LB) | 跨节点访问 | 端口占用 | 核心组件 |
|---|---|---|---|---|---|
| HostNetwork | 物理层 | ❌ 无 | ❌ 仅本机 | ⚠️ 宿主机端口 | 无 |
| HostPort | 链路层 (DNAT) | ❌ 无 | ❌ 仅本机 | ⚠️ 宿主机端口 | CNI 插件 |
| NodePort | 传输层 (L4) | ✅ 有 (随机/轮询) | ✅ 支持 | ⚠️ 30000+ | Kube-proxy |
| K3s ServiceLB | 应用层 (Proxy) | ✅ 有 (依赖 Service) | ✅ 支持 | ⚠️ 80/443 | svclb DaemonSet |
| Ingress | 应用层 (L7) | ✅✅ 强 (智能算法) | ✅ 支持 | ✅ 复用 80/443 | Nginx/Traefik |
8. 🙋♀️ 小小 Q&A:避坑指南
Q1: 我用了 HostPort,为什么 Service 找不到这个 Pod?
👩🏫 小小:
Service 依靠Selector找 Pod,这没问题,Endpoint 列表里会有这个 Pod。
但是!流量路径不同。Service 的流量是走 Kube-proxy 的,而 HostPort 的流量是直接走 CNI DNAT 的。
坑点:虽然 Service 能找到 Pod,但如果你通过 HostPort 访问,流量是直接“钻洞”进去的,完全绕过了 Service 的负载均衡机制。
Q2: K3s ServiceLB 那么好用(能用 80 端口),为什么生产环境推荐用 Ingress?
👩🏫 小小:
- 资源消耗:ServiceLB 每暴露一个服务,就要全集群跑一遍
svclbPod,太浪费内存。Ingress 只需要跑一组 Controller 就能代理成百上千个服务。- 功能:ServiceLB 只有简单的 TCP 转发。Ingress 支持 SSL 卸载、路径重写、黑白名单、限流等高级功能。
Q3: 为什么我的 NodePort 访问此时通,彼时不通?
👩🏫 小小:
检查你的 防火墙!
NodePort 需要所有节点之间不仅 UDP (CNI 隧道) 通,TCP (业务端口) 也要通。而且,如果你的 Pod 所在的节点防火墙把入站流量封了,虽然 Service 把流量调度过去了,但包被扔了。
📝 总结
- HostNetwork/HostPort:是单兵作战的特种兵,快但局限,适合 CNI 插件或监控探针。
- NodePort:是全军调度的指挥官,稳健但端口难看,适合内部调试或对接外部 LB。
- K3s ServiceLB:是灵活的中介,方便但有“中间商赚差价”,适合家庭实验或边缘单机。
- Ingress:是智慧的大脑,处理复杂 Web 业务的终极选择,生产环境首选。
希望这篇“验尸级”的报告,能帮你彻底打通 Kubernetes 网络的任督二脉!收藏起来,下次抓包抓不到的时候,拿出来对照着看!😉