嘿,朋友。如果你刚接触 Kubernetes (K8s),或者正在被那些复杂的网络配置搞得头大,那么请坐好,喝杯咖啡。今天我们要聊的不是枯燥的教科书定义,而是 K8s 网络世界里最核心、也最让人头疼的部分:怎么让成千上万个“孤岛”般的 Pod 连成一张网?
想象一下,你住在一个巨大的公寓楼里(这就是你的集群)。每个房间(Pod)都有自己的门牌号(IP),但问题是:
- 房间的布局经常变,今天住张三,明天换李四,门牌号可能还会变。
- 你想找住在隔壁的朋友聊天,或者想给整栋楼的住户发广播通知。
- 外面的人想找到特定的房间,但他们不知道具体是哪个房间号。
Kubernetes 的设计哲学非常优雅且强硬:它承诺每个 Pod 都有一个独立的、全球唯一的 IP 地址,并且所有 Pod 都可以不经过 NAT(网络地址转换)直接互相通信。 听起来很美好?是的,但这只是基础。真正的魔法在于 Service 和 Ingress,以及底层那些看不见的 CNI(容器网络接口插件)是如何配合演出的。
让我们一层层剥开这个洋葱,看看里面到底藏着什么。
第一层:Pod 之间的直接通信——CNI 的魔力
在 K8s 的世界里,Pod 是最小的部署单元。一个 Pod 里可以跑一个或多个容器。对于网络来说,Pod 是网络的基本原子。
为什么 Pod 有自己的 IP?
在传统的 Docker 时代,我们常用 bridge 模式。但在 K8s 中,如果每个 Pod 都依赖宿主机的 Docker Bridge,那么跨节点通信就会变得极其复杂,而且安全性难以保证。
为了解决这个问题,K8s 引入了 CNI (Container Network Interface) 标准。你可以把 CNI 想象成一个“网络插件市场”。你选择 Calico、Flannel、Weave 还是 Cilium?不同的插件有不同的实现方式,但它们都必须遵守同一个契约:给 Pod 分配一个 IP,并确保该 Pod 能访问集群内其他任何 Pod,也能被其他任何 Pod 访问。
举个例子:Flannel vs. Calico
- Flannel 比较“老实”。它通常在每个节点上创建一个虚拟网桥(如
flannel.1),然后通过 VXLAN 隧道将所有节点的子网打通。这就好比在每个公寓楼之间修了一条条地下隧道,数据封装后通过隧道传输。简单,但性能稍逊一筹,因为涉及到封包和解包。 - Calico 则更“激进”。它利用 Linux 内核的路由表,通过 BGP(边界网关协议)在节点间交换路由信息。这意味着数据包不需要隧道封装,直接走三层路由。这就像是在城市里建立了直接的地铁线路,直达目的地,速度更快,扩展性更好。
代码视角:查看 Pod 网络
当你登录到一个节点,运行以下命令时,你实际上是在窥探 CNI 插件的工作现场:
# 假设你的 Pod 名叫 my-pod,namespace 是 default
kubectl get pod my-pod -o jsonpath='{.status.podIP}'
输出可能是一个类似 10.244.1.5 的地址。这个 IP 是谁给的?是你的 CNI 插件。它被注入到了 Pod 的网络命名空间(Network Namespace)中。
关键点: 无论你的 Pod 在哪里重启,无论它被调度到哪个节点,只要它属于同一个集群,这个 IP 就应该能互通。当然,Pod 重启后 IP 通常会变(除非使用了静态 IP 或 StatefulSet 的特殊配置),这就是为什么我们不能直接硬编码 Pod IP 来进行服务发现的原因。
第二层:Service 发现——当 IP 变得不可靠时
这里有一个残酷的现实:Pod 是短暂的(Ephemeral)。 它们可能因为故障被杀死,因为扩缩容被创建,因为滚动更新被替换。每次重启,Pod 的 IP 都可能改变。
如果你写了一个应用,代码里硬编码了 http://10.244.1.5:8080 去连接后端数据库,那么一旦数据库所在的 Pod 重启,IP 变了,你的应用就挂了。
这时候,Service 登场了。
Service 是什么?
Service 是一个抽象概念,它定义了一组逻辑上的 Pod 集合,以及访问它们的策略。你可以把它理解为一个稳定的入口或负载均衡器。
Service 本身也有一个 IP,叫做 ClusterIP。这个 ClusterIP 在整个集群生命周期内通常是固定的(或者至少在服务对象不变的情况下保持稳定)。
Service 的工作原理:kube-proxy
谁负责把对 ClusterIP 的请求转发到后端具体的 Pod IP 上?答案是 kube-proxy。它在每个节点上运行,维护着 Service 到 Pod 的映射关系。
kube-proxy 有三种主要模式:
- userspace 模式(旧版,已废弃):流量经过用户态内核态切换,性能差,不推荐。
- iptables 模式(默认常见模式):kube-proxy 监听 API Server,当 Service 或 Endpoint 变化时,动态更新宿主机上的 iptables 规则。
- 优点:实现简单,兼容性好。
- 缺点:随着 Service 数量增加,iptables 规则会爆炸,导致刷新延迟,甚至影响性能。
- ipvs 模式(高性能推荐):基于 Linux 内核的 IPVS(IP Virtual Server),使用哈希表存储后端服务器列表。
- 优点:性能极高,支持更多负载均衡算法(轮询、最少连接等),规则刷新速度快。
- 建议:如果你的集群规模较大,强烈建议切换到 ipvs 模式。
深入理解 Endpoints
Service 只是一个“名字”,它真正指向的是 Endpoints(端点)。Endpoints 控制器会实时监测拥有特定 Label 的 Pod 的变化,并更新 Endpoints 列表。
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: MyApp # <--- 关键!只有标签为 app:MyApp 的 Pod 才会被选中
ports:
- protocol: TCP
port: 80
targetPort: 9376
上面的 YAML 文件告诉 K8s:“找一个叫 my-service 的服务,它背后是所有标签为 app:MyApp 的 Pod,对外暴露 80 端口,内部转发到 9376 端口。”
当你访问 my-service 的 ClusterIP 时,kube-proxy 会根据负载均衡算法,将请求随机或按权重分发到其中一个 Pod 的 IP 上。
第三层:跨节点与外部通信——NodePort, LoadBalancer, Ingress
现在,集群内部的 Pod 能互相通信,Service 也能稳定地提供负载均衡了。但如果外面的人(浏览器、移动端 App)想访问你的服务呢?或者不同集群之间的服务想通信呢?
1. NodePort:最粗暴的方式
NodePort 会在每个集群节点的固定端口(范围通常是 30000-32767)上打开一个端口,并将流量转发到 Service。
apiVersion: v1
kind: Service
metadata:
name: my-nodeport-service
spec:
type: NodePort
selector:
app: MyApp
ports:
- port: 80
targetPort: 9376
nodePort: 30007
- 优点:简单直接,不需要额外的基础设施。
- 缺点:端口管理混乱,只能做简单的 TCP/UDP 负载均衡,不支持 HTTP 高级特性(如域名路由)。
2. LoadBalancer:云厂商的恩赐
如果你在使用 AWS、Azure、GCP 或阿里云等公有云,你可以使用 type: LoadBalancer。K8s 会与云厂商的 API 交互,自动创建一个云上的负载均衡器(ELB/ALB),并将流量转发到集群内的 NodePort 或 Pod。
- 优点:自动化程度高,拥有公网 IP,适合直接暴露服务。
- 缺点:成本较高(每个 LB 可能产生费用),且依赖云平台。
3. Ingress:HTTP 路由的艺术
这是现代微服务架构中最常用的方式。Ingress 不是一个 Service 类型,而是一个资源对象,它需要配合 Ingress Controller(如 Nginx Ingress, Traefik, HAProxy)一起工作。
Ingress 的核心价值在于七层(HTTP/HTTPS)路由能力。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ingress
spec:
rules:
- host: www.myapp.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: api-service
port:
number: 80
- path: /
pathType: Prefix
backend:
service:
name: web-frontend
port:
number: 80
在这个例子中:
- 访问
www.myapp.com/api-> 转发到api-service - 访问
www.myapp.com/-> 转发到web-frontend
为什么这很重要? 因为它允许你在一个公网 IP 下托管多个域名和路径,实现了统一入口。同时,Ingress Controller 通常支持 SSL/TLS 终止、Rewrite、Rate Limiting 等高级功能。
第四层:高级网络策略与安全——NetworkPolicy
有了网络,就有了风险。如果某个 Pod 被入侵,攻击者可能会利用内网横向移动,扫描其他敏感服务(如数据库)。
Kubernetes 提供了 NetworkPolicy,它是基于声明式的网络安全模型。默认情况下,K8s 允许所有 Pod 之间的通信(Allow All)。你需要显式地编写策略来拒绝或允许流量。
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all-ingress
spec:
podSelector: {}
policyTypes:
- Ingress
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-web-to-api
spec:
podSelector:
matchLabels:
app: api
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: web
ports:
- protocol: TCP
port: 8080
这段策略的意思是:
- 首先,
deny-all-ingress策略阻止所有入站流量(这是一个好习惯,先白名单再放行)。 - 然后,
allow-web-to-api策略明确允许标签为app: web的 Pod 访问标签为app: api的 Pod 的 8080 端口。
注意: NetworkPolicy 的有效性依赖于你的 CNI 插件是否支持。Flannel 原生不支持,需要额外配置;而 Calico、Cilium、Weave 等都原生支持。
第五层:服务网格(Service Mesh)——超越基础网络
当你的微服务数量达到几十上百个,且需要细粒度的流量控制、监控、熔断、链路追踪时,传统的 Kubernetes Service + Ingress 就显得力不从心了。
这时,Service Mesh(如 Istio, Linkerd)出现了。
Service Mesh 的核心思想是将网络功能从应用程序中剥离出来,放入一个侧边车代理(Sidecar Proxy,如 Envoy)。
- 传统方式:应用代码里调用 Service IP。
- Istio 方式:应用代码依然调用 Service IP,但流量先经过 Sidecar 代理。Sidecar 可以记录日志、检查证书、执行熔断策略,然后再转发给后端 Pod。
这种方式带来了极大的灵活性,但同时也增加了复杂度。你需要权衡:是真的需要这么细粒度的控制吗?还是简单的 Kubernetes NetworkPolicy 就够了?
实战演练:排查网络问题的“侦探指南”
作为专家,我见过太多人因为网络问题崩溃。下面是一个标准的排查流程,希望能帮到你。
场景:Pod A 无法访问 Pod B 的 Service
步骤 1:确认 Service 是否存在且正确
kubectl get svc <service-name> -n <namespace>
kubectl describe svc <service-name> -n <namespace>
检查 Endpoints 部分是否有 IP。如果没有,说明 Selector 匹配不到任何 Pod,或者 Pod 状态不是 Running。
步骤 2:检查 DNS 解析 K8s 内置 CoreDNS。尝试在 Pod A 中 ping 或 nslookup Service 名称。
kubectl exec -it pod-a -n <namespace> -- nslookup <service-name>.<namespace>.svc.cluster.local
如果解析失败,检查 CoreDNS Pod 的状态和日志。
步骤 3:检查 kube-proxy 和 iptables/ipvs 规则 在节点上查看:
iptables -L -n | grep <service-cluster-ip>
# 或者如果使用 ipvs
ipvsadm -Ln
确保有对应的转发规则。
步骤 4:检查 NetworkPolicy 如果有启用 NetworkPolicy,确认是否允许了从 Pod A 到 Pod B 的流量。
kubectl get networkpolicy -n <namespace>
kubectl describe networkpolicy <policy-name> -n <namespace>
步骤 5:抓包分析
如果以上都没问题,使用 tcpdump 在节点上抓取流量。
# 在 Pod B 所在节点执行
tcpdump -i any host <pod-b-ip> and port <target-port>
看数据包是否到达节点,是否被丢弃,或者是否返回了错误。
给小朋友的比喻:K8s 网络就像“学校食堂系统”
为了让你更直观地理解,我们把 K8s 网络比作一所大学食堂:
- Pod:就是一个个打饭窗口。每个窗口有一个编号(IP),比如 101 号窗口卖红烧肉,102 号窗口卖糖醋排骨。
- CNI:就是校园道路网。它保证无论你在哪个宿舍(节点),都能走到任何一个打饭窗口。道路是连通的,没有围墙阻隔。
- Service:就是一个统一的订餐热线,比如“红烧肉专线”。你不需要知道具体是哪个窗口(101 还是 105)在做红烧肉,你只需要打这个电话。电话那头会自动帮你接通正在营业的那个窗口。如果 101 窗口下班了,105 窗口上岗了,电话依然畅通,因为你打的是“专线”而不是“101 窗口”。
- Endpoints:就是当前正在营业的红烧肉窗口列表。系统实时更新这个列表,确保电话能接通。
- Ingress:就像是食堂入口处的指示牌和导览机器人。你走进大门,说“我要吃红烧肉”,机器人把你指引到正确的区域。如果你说“我要看电影”,机器人就把你指引到电影院。它负责根据你要的内容(URL 路径)把你引导到正确的服务。
- NetworkPolicy:就像是食堂的安全规定。比如,“只有持有学生证的人才能进入教工专用窗口”,或者“禁止携带宠物进入”。它限制了谁能访问哪个窗口,保障了安全。
结语:没有银弹,只有权衡
Kubernetes 的网络模型强大而灵活,但它也带来了复杂性。从 CNI 的选择到 Service 类型的决定,再到 NetworkPolicy 的制定,每一步都需要根据你的业务场景进行权衡。
- 小集群?Flannel + iptables 足够。
- 大集群?Calico/Cilium + ipvs 是标配。
- 需要外部访问?Ingress + Nginx/Traefik 是主流。
- 需要极致安全和微服务治理?考虑 Service Mesh。
希望这篇文章能帮你理清思路。记住,理解原理比死记命令更重要。当你下次遇到网络问题时,试着回到这些基本概念,你会发现答案往往就在其中。
如果你还有其他具体问题,欢迎随时交流。毕竟,在这个数字世界里,我们都是学习者,也都是分享者。
