提到 Kubernetes (K8s) 的网络模型,很多刚上手的朋友第一反应就是头大:“为什么我的 Pod 连不上数据库?”、“为什么 Service 转发请求有时候超时?”、“Ingress 到底是怎么把外网流量引到内网的?”。别急,咱们今天不背那些枯燥的 RFC 标准文档,我把这层“黑魔法”窗户纸捅破,用大白话加上实打实的例子,带你彻底搞懂 K8s 网络从内到外的完整链路。
你可以把 K8s 集群想象成一个巨大的、自动化的城市。在这个城市里,Pod 是住在一个个独立公寓里的居民,Service 是城市的邮政系统和交通指挥中心,而 Ingress 则是城市的海关和大型物流枢纽。要想让信息(数据)顺畅流动,我们必须先理清它们各自的职责和连接方式。
一、 Pod 间互通:扁平化网络的奇迹
在传统的虚拟机时代,每个 VM 都有独立的 IP 段,跨网段通信需要复杂的 NAT(网络地址转换)和路由配置。但在 K8s 的设计哲学里,所有 Pod 无论分布在哪个节点上,都应当能够直接通过 IP 地址互相通信。这听起来像天方夜谭,但它是如何实现的?
1. CNI 插件:幕后英雄
K8s 本身并不直接管理网卡,它把网络交给社区标准的 CNI (Container Network Interface) 插件来处理。常见的 CNI 有 Flannel, Calico, Cilium 等。虽然底层实现不同(有的用 VXLAN 隧道,有的用 BGP 路由),但它们的核心目标一致:为每个 Pod 分配一个唯一的、全局可达的 IP 地址。
当你在集群中创建一个 Pod 时,CNI 插件会在宿主机上创建一对虚拟网卡(veth pair),一端连进容器的网络命名空间,另一端连到宿主机的虚拟交换机或物理桥接设备上。接着,它会配置路由表,确保来自该 Pod 的数据包能被正确转发到其他节点的 Pod。
2. 实战演示:Pod 间的直接 Ping 通
让我们通过一个简单的例子来看看这种“扁平化”网络是如何工作的。假设我们有两个简单的 Nginx Pod,分别运行在集群的不同节点上。
首先,部署第一个 Pod nginx-pod-1:
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod-1
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
部署第二个 Pod nginx-pod-2:
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod-2
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
现在,我们要测试它们之间的连通性。通常,我们会进入 nginx-pod-1 去 ping nginx-pod-2 的 IP。
# 获取 nginx-pod-2 的 IP 地址
kubectl get pod nginx-pod-2 -o wide
# 假设输出中 IP 是 10.244.1.5
# 进入 nginx-pod-1 并执行 ping
kubectl exec -it nginx-pod-1 -- /bin/sh
# 在容器内部执行
ping 10.244.1.5
如果一切正常,你会看到 ICMP 包成功往返。这就是 K8s 网络的魅力所在:不需要知道对方在哪个节点,只需要知道它的 Pod IP,就能直接对话。
注意:这里有一个重要的前提——网络策略(NetworkPolicy)。默认情况下,大多数 CNI 插件允许所有 Pod 间通信。但如果你开启了 NetworkPolicy,比如只允许特定标签的 Pod 访问,那么上述的 Ping 可能会失败。这在生产环境中是非常关键的安全措施,防止恶意 Pod 扫描整个集群。
二、 Service 负载均衡:稳定的入口与动态的后端
虽然 Pod 间可以直接通信,但 Pod 是不稳定的。它们可能因为故障重启、节点维护被驱逐,或者副本数增加导致 IP 变化。如果业务代码硬编码了某个 Pod 的 IP,一旦 IP 变了,服务就挂了。
这时候,Service 就登场了。Service 提供了一个稳定的虚拟 IP(ClusterIP)和一个 DNS 名称,作为后端 Pod 组的抽象入口。它解决了两个核心问题:
- 稳定性:不管后端 Pod 怎么变,Service 的 ClusterIP 不变。
- 负载均衡:将流量均匀分发到多个健康的 Pod 副本上。
1. Service 的类型与工作原理
K8s 主要有四种 Service 类型:
- ClusterIP(默认):只在集群内部可访问,用于微服务间调用。
- NodePort:在每个节点上开放一个静态端口,外部可通过
<NodeIP>:<NodePort>访问。 - LoadBalancer:云厂商会自动创建一个外部负载均衡器(如 AWS ELB),将流量转发到 NodePort 或 ClusterIP。
- ExternalName:将 Service 映射到一个外部 DNS 名称。
我们以最常见的 ClusterIP 为例,看看它是如何实现负载均衡的。
2. 深入 kube-proxy:流量是如何转发的?
Service 的核心组件是 kube-proxy。它运行在每个节点上,负责监听 API Server 中 Service 和 Endpoint 的变化,并根据这些变化更新节点上的网络规则。
目前,kube-proxy 主要有两种模式:
- userspace 模式(已弃用):性能较差,流量经过内核态到用户态再回到内核态。
- iptables/ipvs 模式(主流):直接在 Linux 内核中使用 iptables 或 IPVS 规则进行转发,性能极高。
IPVS 模式详解:
如果你使用 IPVS 模式,kube-proxy 会在内核中创建一个虚拟服务器(Virtual Server),并将后端 Pod 的 IP 和端口添加到这个虚拟服务器的真实服务器列表中。当流量到达节点的 ClusterIP 时,Linux 内核会根据 IPVS 算法(如轮询、最少连接等)直接将数据包转发给对应的 Pod IP。
3. 实战演示:创建 Service 并验证负载均衡
让我们为刚才的两个 Nginx Pod 创建一个 Service。
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: nginx # 选择标签为 app: nginx 的 Pod
ports:
- protocol: TCP
port: 80 # Service 暴露的端口
targetPort: 80 # 转发到 Pod 的端口
type: ClusterIP
应用这个 Service:
kubectl apply -f nginx-service.yaml
现在,我们可以通过 Service 的名称或 ClusterIP 来访问 Nginx。K8s 内置了 CoreDNS,所以我们可以直接用 nginx-service 这个名字。
# 在任意 Pod 中测试
kubectl run -it --rm test-pod --image=busybox --restart=Never -- sh
# 在容器中执行
wget -qO- http://nginx-service
每次执行 wget,你看到的 HTML 内容虽然相同,但背后处理的 Pod 可能是不同的。你可以开启调试模式查看具体的转发日志,或者通过增加 Pod 副本数,观察 kube-proxy 如何动态调整后端列表。
小贴士:如果你发现 Service 的流量没有均匀分布,检查
kube-proxy的模式是否为 IPVS,并查看/etc/proxy-config.conf中的调度算法设置(默认为 rr,即轮询)。
三、 Ingress 网关:七层流量的智能指挥官
Service 解决了集群内部的负载均衡,但它工作在四层(TCP/UDP)。如果你需要根据域名(www.example.com vs api.example.com)或 URL 路径(/api/v1 vs /static)来决定流量去向,Service 就力不从心了。
这时,Ingress 出场了。Ingress 不是 K8s 的原生资源类型,而是一个 API 对象集合,它描述了外部流量如何进入集群的规则。但要真正生效,你需要一个 Ingress Controller(如 Nginx Ingress Controller, Traefik, HAProxy 等)。
1. Ingress 的工作原理
Ingress Controller 是一个长期运行的 Pod,它通常部署在集群中,并绑定一个 LoadBalancer Service(或者 NodePort)。它监听 K8s API Server 中 Ingress 资源的变化,然后根据 Ingress 定义的规则,动态生成配置文件(如 Nginx 的 .conf 文件),并重新加载 Nginx 服务。
简单来说:
- 用户定义 Ingress 资源。
- Ingress Controller 读取 Ingress 规则。
- Ingress Controller 生成反向代理配置。
- 反向代理(如 Nginx)根据配置将流量转发到对应的 Service。
2. 实战演示:配置基于域名的 Ingress
假设我们有三个微服务:frontend, backend-api, backend-admin。我们希望:
- 访问
www.myapp.com时,流量进入frontendService。 - 访问
api.myapp.com时,流量进入backend-apiService。 - 访问
admin.myapp.com时,流量进入backend-adminService。
首先,确保这三个 Service 已经存在。然后,创建 Ingress 资源:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: multi-host-ingress
annotations:
kubernetes.io/ingress.class: "nginx" # 指定使用的 Ingress Controller
spec:
rules:
- host: www.myapp.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: frontend-service
port:
number: 80
- host: api.myapp.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: backend-api-service
port:
number: 80
- host: admin.myapp.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: backend-admin-service
port:
number: 80
应用这个 Ingress:
kubectl apply -f ingress.yaml
此时,Ingress Controller(比如 Nginx)会检测到这个变化,并在其配置中添加三个 server 块,分别监听 www.myapp.com, api.myapp.com, admin.myapp.com,并将请求转发到对应的 Service。
3. TLS 加密:保护数据传输
在生产环境中,HTTP 明文传输是不安全的。Ingress 支持 TLS 终止,即在 Ingress Controller 处解密 HTTPS 流量,然后将 HTTP 流量转发给后端的 Service。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: tls-example
spec:
tls:
- hosts:
- www.myapp.com
secretName: myapp-tls-secret # 引用包含证书和私钥的 Secret
rules:
- host: www.myapp.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: frontend-service
port:
number: 80
你需要先创建一个 Secret,包含你的 SSL 证书和私钥:
kubectl create secret tls myapp-tls-secret \
--cert=path/to/tls.crt \
--key=path/to/tls.key
这样,用户通过 https://www.myapp.com 访问时,流量会在 Ingress Controller 处被安全地终止,然后以 HTTP 形式进入集群内部。
四、 常见问题排查与最佳实践
理解了原理,我们再来聊聊实际使用中容易踩的坑。
1. Pod 间无法通信?
- 检查 NetworkPolicy:是否限制了特定命名空间或标签的 Pod 访问?
- 检查 CNI 状态:
kubectl get pods -n kube-system | grep calico或flannel,确保 CNI Pod 正常运行。 - 检查节点防火墙:某些云厂商或自定义环境中,节点间的安全组可能阻止了 CNI 所需的隧道端口(如 VXLAN 的 4789 端口)。
2. Service 无法访问?
- 检查 Endpoints:
kubectl get endpoints <service-name>,确认是否有后端 Pod 的 IP 被列入。如果没有,说明 Selector 匹配不到任何 Pod,或者 Pod 处于非 Ready 状态。 - 检查 kube-proxy:
kubectl logs -n kube-system <kube-proxy-pod-name>,查看是否有错误日志。 - 检查 DNS:确保 CoreDNS 正常运行,且 Pod 的
/etc/resolv.conf配置正确。
3. Ingress 404 或 502 错误?
- 检查 Ingress Controller 日志:这是第一步!
kubectl logs -n ingress-nginx <ingress-controller-pod-name>。 - 检查 Host 匹配:确保请求的 Host 头与 Ingress 中定义的
host完全一致。 - 检查 Path 类型:
Prefix和Exact的区别要搞清楚。例如,/api的 Prefix 匹配/api/v1,但不匹配/apiv1。 - 检查后端 Service 是否存在:Ingress 引用的 Service 必须存在且端口正确。
4. 最佳实践建议
- 使用 IPVS 模式:如果集群规模较大,优先选择 IPVS 模式的 kube-proxy,性能更好。
- 启用 Ingress 的 TLS:始终使用 HTTPS,保护用户数据安全。
- 限制 Ingress 的访问来源:利用 Annotation 或 NetworkPolicy 限制只有特定的 IP 段或 CIDR 才能访问 Ingress,减少攻击面。
- 监控与告警:对 Ingress Controller 的 QPS、延迟、错误率进行监控,及时发现性能瓶颈。
结语
K8s 的网络模型看似复杂,实则逻辑清晰:Pod 提供计算单元,CNI 提供互联基础,Service 提供稳定入口,Ingress 提供智能路由。掌握这些概念,你就掌握了 K8s 网络的一半江山。
记住,理论是骨架,实践是血肉。多动手敲命令,多查看日志,多分析流量,你会发现 K8s 网络并没有想象中那么神秘。希望这篇文章能帮你拨开迷雾,让你在 K8s 的海洋中游刃有余。如果有具体问题,欢迎随时交流,我们一起探讨!
