想象一下,你刚接手了一个微服务架构的大型项目,里面有几十个Pod在运行,有的在前台接待用户,有的在后台处理订单,还有的在默默记账。突然,前端的一个服务突然调不通后端的支付服务了,报错信息是“Connection refused”或者“Service Unavailable”。这时候,如果你是运维或者开发,脑子里是不是瞬间闪过一堆问题:IP变了怎么办?Pod死了怎么恢复?流量到底是怎么从外面进来的?
别慌,今天咱们就坐下来,像聊天一样,把这层神秘的面纱揭开。 Kubernetes(简称K8s)的网络模型,听起来高大上,其实核心逻辑非常朴素:每个Pod都有独立的IP,服务之间通过IP通信,而Service和Ingress则是为了把这些复杂的IP隐藏起来,提供稳定的入口和出口。
一、 基石:Pod IP 与网络隔离
首先,我们要明确K8s网络模型的一个核心假设,这也是它跟传统虚拟化网络最大的不同点:每个Pod都拥有一个独立的、唯一的IP地址。
1.1 为什么是Pod IP,而不是容器IP?
你可能会问,Pod里不是可以有多个容器吗?对,但K8s的设计哲学是:Pod是调度的最小单位,也是网络的最小单位。
当一个Pod被创建时,K8s会自动为它创建一个共享的Network Namespace。这意味着,Pod内的所有容器(包括Init Container)都共享同一个网络栈:相同的IP地址、端口空间、网络接口和路由表。
这就带来了一个极其重要的好处:容器间可以直接通过 localhost 通信。
比如,你有一个侧车(Sidecar)模式,一个主业务容器和一个日志收集容器在同一个Pod里。主容器写日志到 /var/log/app.log,侧车容器直接监听这个文件或者通过 localhost:8080 接收主容器发送的数据,完全不需要复杂的网络配置。
1.2 网络隔离的实现:CNI插件
那么,这些Pod之间是怎么隔离,又是怎么互通的呢?这就轮到 CNI(Container Network Interface) 插件出场了。
CNI不是K8s自带的,而是一个标准接口。K8s社区提供了标准的调用方式,而具体的网络实现(比如Calico、Flannel、Cilium)则通过CNI插件来完成。
常见的CNI插件有:
- Flannel:最简单的overlay网络,通过 VXLAN 或 Host-gw 模式,让不同Node上的Pod能互通。它像一个巨大的虚拟交换机,把整个集群的网络打通。
- Calico:基于BGP协议,性能更好,支持更精细的网络策略(Network Policy)。
- Cilium:基于eBPF技术,性能极强,且能提供更深层的可观测性和安全控制。
举个例子:
假设你在Node A上跑着Pod-1(IP: 10.244.1.5),在Node B上跑着Pod-2(IP: 10.244.2.5)。当Pod-1要访问Pod-2时,数据包从Pod-1发出,经过Node A的虚拟网桥,然后通过CNI插件配置的隧道(比如VXLAN)或者路由(Host-gw)传输到Node B,最后到达Pod-2。
对于应用来说,它感知不到中间复杂的网络传输,就像两台机器在同一个局域网里一样。这就是K8s网络模型的精髓:统一的全局虚拟网络。
1.3 网络隔离:Network Policy
有了互通的能力,自然也有隔离的需求。有时候,你不想让所有的Pod都能访问所有的服务。比如,支付服务只能被订单服务调用,不能被其他无关的服务打扰。
这时候,Network Policy 就派上用场了。它基于标签(Label)来控制Pod之间的流量。
注意: Network Policy 默认是不起作用的,除非你的CNI插件支持它(比如Calico、Cilium、Weave Net等)。
# 定义一个NetworkPolicy,只允许来自"app=frontend"的Pod访问当前Pod
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all-but-frontend
spec:
podSelector:
matchLabels:
app: payment-service
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
这段YAML的意思是:在payment-service这个Pod上,只允许app=frontend的Pod通过TCP 8080端口访问它,其他所有入站流量都被拒绝。
二、 枢纽:Service 服务发现
好了,Pod能互通了,但问题来了:Pod的IP是动态的,不可靠的。
在K8s中,Pod是“有状态”的,它们可能因为滚动更新、节点故障、弹性伸缩等原因随时被销毁和重建。每次重建,Pod的IP都会变。如果前端服务硬编码了后端服务的IP,一旦后端Pod重启,前端就瞎了。
这时候,Service 出现了。它是K8s中最重要的抽象之一,提供了一个稳定的访问入口。
2.1 Service 是什么?
简单来说,Service是一组Pod的抽象,它提供了一个固定的IP(ClusterIP)和DNS名称,使得客户端可以通过这个固定的地址访问到后面变化无常的Pod群。
Service背后连接的一群Pod,被称为Endpoints(端点)。K8s会实时监控这些Pod的状态,自动更新Endpoints列表。
2.2 Service 的三种类型
K8s提供了三种主要的Service类型,适用于不同的场景:
1. ClusterIP(默认)
这是最常用的类型。它在集群内部分配一个虚拟IP(ClusterIP),只有集群内部的Pod才能访问。
工作原理:
当你在集群内创建一个ClusterIP Service时,K8s会在每个Node上运行一个名为 kube-proxy 的组件。kube-proxy负责维护网络规则,将发往ClusterIP的流量转发到后端的Pod IP。
kube-proxy有三种实现模式:
- userspace模式:性能较差,已废弃。
- iptables模式:大多数集群的默认模式。通过iptables规则将流量重定向到后端Pod。
- ipvs模式:性能更好,支持更多的负载均衡算法(如轮询、最少连接等),适合大规模集群。
举例:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: my-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: ClusterIP
selector: app: my-app:表示这个Service会拦截所有带有app=my-app标签的Pod的流量。port: 80:Service监听的端口。targetPort: 8080:后端Pod实际监听的端口。
当你访问 http://my-service:80 时,流量会被kube-proxy根据负载均衡算法转发到某个app=my-app的Pod的8080端口。
2. NodePort
如果你需要从集群外部访问集群内部的Service,NodePort就派上用场了。它在每个Node上开放一个静态端口(范围通常是30000-32767),外部流量可以通过 NodeIP:NodePort 访问。
举例:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: my-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
nodePort: 30080
type: NodePort
现在,你可以通过 http://192.168.1.10:30080 从集群外访问这个服务了。注意,这里的192.168.1.10是任意一个Node的IP。
缺点:
- 端口管理混乱,每个Service都要占用一个NodePort。
- 安全风险,因为暴露了高端口。
3. LoadBalancer
这是最“高级”的类型,适用于云环境。它会在现有的NodePort基础上,自动向云提供商(如AWS、Azure、阿里云)申请一个外部负载均衡器,并将流量分发到NodePort。
举例:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: my-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: LoadBalancer
创建后,你会看到一个EXTERNAL-IP,这就是云厂商分配的负载均衡器IP。你只需要访问这个IP即可。
2.3 Service 的内部机制:kube-proxy 与 iptables/IPVS
你可能会好奇,流量到底是怎么从ClusterIP转发到Pod IP的?
这里涉及到一个关键组件:kube-proxy。
在每个Node上,kube-proxy会监听API Server,当Service或Endpoint发生变化时,它会实时更新本机的网络规则。
以iptables模式为例:
当你在集群内访问 my-service 的ClusterIP时,流量会经过iptables的规则链。kube-proxy会在PREROUTING链中添加规则,将发往ClusterIP的流量重定向到后端的某个Pod IP。
以IPVS模式为例:
IPVS(IP Virtual Server)是Linux内核提供的一个高性能负载均衡器。kube-proxy会创建IPVS规则,类似于LVS(Linux Virtual Server)。当流量到达Node时,IPVS会根据负载均衡算法(如轮询、最少连接)将流量转发到后端的真实服务器(Pod IP)。
对比:
- iptables:规则是扁平的,当Service和Endpoint数量很大时,规则链会变长,性能下降。
- IPVS:基于哈希表,性能更稳定,适合大规模集群。
2.4 DNS 服务发现
除了IP地址,K8s还提供了DNS服务发现。每个Service都会自动分配一个DNS名称,格式为 <service-name>.<namespace>.svc.cluster.local。
在你的Pod里,可以直接使用短名称访问其他Service,比如 http://my-service。
如何配置DNS?
K8s默认运行一个名为 kube-dns 或 CoreDNS 的DNS服务。当你创建一个Service时,CoreDNS会自动为该Service创建一条A记录。
你可以通过 nslookup 命令在Pod内测试DNS解析:
kubectl exec -it <pod-name> -- nslookup my-service
三、 大门:Ingress 流量入口
好了,我们已经解决了集群内部的通信问题。但现在,用户要从互联网访问你的应用,该怎么办?
你可能会想,直接用NodePort不就行了吗?对于小型项目,这确实可行。但一旦项目变大,问题就来了:
- 端口爆炸:几十个微服务,就要占用几十个NodePort,管理起来非常痛苦。
- SSL终止:如果每个Service都要配置HTTPS,需要在每个Service后面都部署SSL证书,管理起来很麻烦。
- 路径路由:你希望
/api路径访问支付服务,/路径访问首页服务,NodePort很难做到这种精细的路由控制。
这时候,Ingress 就出场了。
3.1 Ingress 是什么?
Ingress是K8s中用于管理外部访问集群内部Service的API对象。它本身只是一个配置,需要配合 Ingress Controller 才能生效。
Ingress Controller 是一个反向代理服务器,它监听Ingress资源的变化,并实时更新自己的配置,从而将外部流量路由到正确的Service。
常见的Ingress Controller有:
- Nginx Ingress Controller:最流行,基于Nginx。
- Traefik:轻量级,自动发现服务。
- HAProxy Ingress Controller:高性能,适合大规模场景。
- Istio Gateway:服务网格场景下的Ingress。
3.2 Ingress 的工作原理
当用户访问 https://www.example.com 时,流量首先到达Ingress Controller(通常是一个LoadBalancer类型的Service)。Ingress Controller根据Ingress资源中定义的路由规则,将流量转发到对应的Service。
举个例子:
假设你有两个微服务:
frontend:提供Web界面,监听80端口。backend:提供API接口,监听8080端口。
你想实现:
https://www.example.com->frontendhttps://www.example.com/api->backend
首先,创建两个Service:
# frontend service
apiVersion: v1
kind: Service
metadata:
name: frontend-service
spec:
selector:
app: frontend
ports:
- protocol: TCP
port: 80
targetPort: 80
type: ClusterIP
---
# backend service
apiVersion: v1
kind: Service
metadata:
name: backend-service
spec:
selector:
app: backend
ports:
- protocol: TCP
port: 8080
targetPort: 8080
type: ClusterIP
然后,创建一个Ingress资源:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
tls:
- hosts:
- www.example.com
secretName: tls-secret
rules:
- host: www.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: frontend-service
port:
number: 80
- path: /api
pathType: Prefix
backend:
service:
name: backend-service
port:
number: 8080
这段YAML的意思是:
- 监听
www.example.com域名。 - 使用TLS证书
tls-secret提供HTTPS支持。 - 访问根路径
/时,将流量转发到frontend-service的80端口。 - 访问
/api路径时,将流量转发到backend-service的8080端口。
最后,你需要部署一个Ingress Controller,比如Nginx Ingress Controller:
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/baremetal/deploy.yaml
部署完成后,Ingress Controller会自动读取Ingress资源,并配置Nginx,实现流量的精确路由。
3.3 Ingress 的优势
- 单一入口:所有外部流量都通过一个LoadBalancer进入集群,简化了网络架构。
- 路径路由:可以根据URL路径将流量分发到不同的Service。
- 主机路由:可以根据域名将流量分发到不同的Service。
- SSL终止:可以在Ingress层统一管理SSL证书,减轻后端服务的负担。
- 负载均衡:Ingress Controller本身提供了负载均衡能力,可以均匀分发流量到后端Pod。
四、 完整通信链路:从外到内
现在,让我们把整个链路串起来,看看一个请求从互联网到Pod的完整旅程。
假设用户访问 https://www.example.com/api,后端是运行在K8s集群内的 backend-service。
- DNS解析:用户浏览器解析
www.example.com,得到的是Ingress Controller的LoadBalancer IP(比如203.0.113.1)。 - 流量进入:用户请求发送到
203.0.113.1:443(HTTPS默认端口)。 - Ingress Controller处理:LoadBalancer将流量转发到集群内某个Node上的Ingress Controller Pod(比如Nginx)。
- 路由匹配:Ingress Controller根据Ingress资源中的规则,匹配到
/api路径,确定后端Service是backend-service。 - Service代理:Ingress Controller将流量转发到
backend-service的ClusterIP(比如10.96.0.10)的8080端口。 - kube-proxy处理:Node上的kube-proxy拦截发往
10.96.0.10:8080的流量,根据Endpoints列表,随机选择一个后端Pod(比如10.244.1.5:8080),将流量转发过去。 - Pod接收:流量最终到达
backendPod的8080端口,Pod内的应用处理请求并返回响应。 - 响应返回:响应沿着原路返回,最终到达用户浏览器。
整个过程对用户来说是完全透明的,他只需要知道 https://www.example.com/api 这个URL即可。
五、 常见问题与解决方案
在K8s网络实践中,经常会遇到各种问题。下面列举几个常见问题及其解决方案。
5.1 Pod无法访问外网
现象: Pod内执行 curl www.google.com 超时。
可能原因:
- iptables规则问题:kube-proxy的iptables规则可能拦截了出站流量。
- CNI插件配置问题:CNI插件可能没有正确配置路由或NAT。
- 防火墙问题:Node上的防火墙可能阻止了出站流量。
解决方案:
- 检查kube-proxy的运行状态和日志。
- 检查CNI插件的配置,确保IPAM(IP地址管理)工作正常。
- 检查Node上的iptables规则,确保没有意外拦截出站流量。
- 检查Node上的防火墙设置(如firewalld、ufw)。
5.2 Service无法访问后端Pod
现象: 访问Service的ClusterIP,但后端Pod没有响应。
可能原因:
- Selector不匹配:Service的selector与Pod的label不一致。
- Pod未就绪:
