提到K8s的网络,很多刚接触的朋友都会觉得头大。什么Pod IP、Service IP、Cluster IP、NodePort,还有各种CNI插件,听起来就像是在学一门新的语言。但别担心,今天我们就像聊家常一样,把这些概念掰开了、揉碎了讲清楚。毕竟,网络是K8s的血管,血管不通,整个系统也就瘫痪了。
一、 先建立直觉:K8s网络的“四大基石”
在深入代码和配置之前,我们需要先建立几个核心的认知基石。K8s的网络模型其实非常优雅,它基于几个简单但强大的原则:
- 每个Pod都有唯一的IP地址:Pod是K8s的最小调度单位,也是网络的最小单位。无论Pod被调度到哪个节点上,它都有一个全局唯一的IP地址。这意味着,同一个网络内的所有Pod可以直接通过IP通信,无需经过NAT转换。
- Pod之间可以无阻碍地通信:无论是同一个节点上的两个Pod,还是分布在不同节点上的Pod,它们都应该能够直接通信。这是K8s网络设计的首要目标,也是后续所有复杂机制的基础。
- 每个Pod都能与所有节点上的其他Pod通信:一个Pod的IP地址,应该可以从集群内的任何一个节点、任何一个其他Pod访问到。这保证了集群内部的连通性。
- Pod IP与容器端口是全局唯一的:一个Pod内的容器共享同一个网络命名空间,因此它们共享同一个IP地址和端口空间。这意味着,如果Pod A的端口8080被占用了,那么同一个Pod内的其他容器就不能再使用8080端口。
这几条原则听起来简单,但实现起来却需要复杂的底层支持。这就是为什么我们需要CNI(Container Network Interface,容器网络接口)插件的原因。
二、 IP与端口分配:虚拟网桥与命名空间
为了理解Pod的IP是如何分配的,我们需要先了解一下Linux内核的一些基本概念:网络命名空间(Network Namespace)和虚拟网桥(veth pair, bridge)。
2.1 网络命名空间:隔离的艺术
在Linux中,网络命名空间是一种将网络资源隔离的技术。每个网络命名空间都有自己独立的网络栈,包括网卡、IP地址、路由表、iptables规则等。这就像是在一台物理机上运行了多个独立的“网络虚拟机”。
在K8s中,每个Pod都被分配到一个独立的网络命名空间中。这意味着,Pod内部的网络接口和配置是完全隔离的,不会影响宿主机或其他Pod。
2.2 veth pair:连接两个世界的桥梁
veth pair(Virtual Ethernet Pair)是一对虚拟网络设备,它们就像一根“网线”的两端。当你把veth pair的一端放入一个网络命名空间,另一端放入另一个网络命名空间时,这两个命名空间就实现了网络连通。
在K8s中,每个Pod都会创建一对veth pair。一端位于Pod的网络命名空间中,另一端位于宿主机的网络命名空间中(通常连接到虚拟网桥上)。这样,Pod内部的流量就可以通过veth pair“穿越”到宿主机,再通过宿主机上的虚拟网桥发送到其他Pod或外部网络。
2.3 虚拟网桥:集群内部的交换机
虚拟网桥(通常命名为cni0或br0)是一个软件实现的交换机,它连接了宿主机上所有Pod的veth pair另一端。当数据包从Pod A的veth pair进入宿主机后,虚拟网桥会根据目标MAC地址将数据包转发到对应的Pod B的veth pair中。
这样,同一个节点上的所有Pod就组成了一个局域网,它们可以通过虚拟网桥直接通信,无需经过物理网卡。
2.4 IP分配:从CNI插件说起
那么,Pod的IP地址是由谁来分配的呢?答案是CNI插件。
CNI插件是一套标准的接口和规范,它定义了如何为容器配置网络。当K8s调度一个Pod时,它会调用相应的CNI插件来为该Pod创建网络命名空间、添加veth pair、配置IP地址等。
常见的CNI插件有Calico、Flannel、Canal等,它们各自有不同的实现方式。但无论使用哪种插件,它们都遵循CNI的标准接口,确保Pod网络的连通性和隔离性。
三、 Pod通讯原理:跨节点的挑战
理解了同一节点内的Pod通信,接下来我们要挑战更复杂的问题:跨节点的Pod通信。
假设我们有两个Pod,分别位于节点A和节点B上。它们之间如何进行通信?
3.1 方案一: Overlay网络(隧道)
Overlay网络是一种通过 encapsulation(封装)技术,在现有网络基础设施之上构建虚拟网络的技术。
在Overlay网络中,每个节点上都运行一个虚拟交换机(vSwitch),所有Pod的流量都通过这个虚拟交换机进行转发。当数据包从一个Pod发送到另一个Pod时,源节点会将数据包封装在一个新的外层数据包中,然后通过物理网络发送给目的节点。目的节点接收到数据包后,剥离外层封装,将原始数据包发送给目标Pod。
这种方案的优点是配置简单,易于管理。但缺点是需要额外的封装和解封装开销,可能会影响网络性能。
3.2 方案二: Underlay网络(原生路由)
Underlay网络则是直接利用物理网络的路由能力,将Pod的IP地址融入物理网络的路由表中。
在Underlay网络中,每个Pod的IP地址都是真实的路由可达的。当数据包从一个Pod发送到另一个Pod时,它直接通过物理网络的路由器进行转发,无需任何封装或解封装操作。
这种方案的优点是性能好,延迟低。但缺点是配置复杂,需要对物理网络进行深度定制。
3.3 Calico的选择:BGP路由
Calico是一种基于Underlay网络的CNI插件,它使用BGP(Border Gateway Protocol,边界网关协议)将Pod的IP地址通告到物理网络中。
具体来说,每个节点上都运行一个BGP daemon(通常是Bird或GoBGP),它会将该节点上所有Pod的IP地址作为BGP路由通告给集群中的其他节点。其他节点接收到这些路由后,会将它们加入到自己的路由表中。这样,当数据包从一个Pod发送到另一个Pod时,它会直接通过物理网络的路由器进行转发,实现高效的跨节点通信。
这种方案的优点是性能好,配置灵活,且无需额外的隧道开销。但缺点是需要物理网络支持BGP协议,且配置相对复杂。
四、 Calico CNI插件配置详解
Calico是K8s生态中最流行的CNI插件之一,它提供了高性能、可扩展的网络解决方案。下面我们将详细介绍如何配置Calico。
4.1 安装Calico
Calico的安装非常简单,只需要执行以下命令:
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.26.1/manifests/calico.yaml
这个命令会从GitHub上下载Calico的配置文件,并应用到当前的K8s集群中。
4.2 配置IP池
IP池(IP Pool)是Calico中用于定义Pod IP地址范围的配置对象。默认情况下,Calico会自动创建一个IP池,但如果需要自定义,可以通过以下步骤进行配置:
- 创建一个IP池配置文件(例如
ipam-config.yaml):
apiVersion: operator.tigera.io/v1
kind: IPPool
metadata:
name: default-ipv4-ippool
spec:
blockSize: 26
cidr: 10.244.0.0/16
ipipMode: Never
natOutgoing: true
nodeSelector: all()
- 应用配置文件:
kubectl apply -f ipam-config.yaml
在这个配置中,我们定义了一个CIDR为10.244.0.0/16的IP池,每个Pod会分配到一个/26的子网中。ipipMode: Never表示不使用IPIP隧道,而是直接使用BGP路由。
4.3 配置GlobalNetworkPolicy
GlobalNetworkPolicy是Calico中用于定义全局网络策略的配置对象。它允许我们对跨节点的网络流量进行细粒度的控制。
例如,我们可以创建一个GlobalNetworkPolicy,只允许来自特定Pod的流量访问某个服务:
apiVersion: crd.projectcalico.org/v1
kind: GlobalNetworkPolicy
metadata:
name: allow-specific-source
spec:
selector: project == "production"
types:
- Ingress
ingress:
- action: Allow
source:
nets: ["10.244.1.0/26"]
destination:
ports: [80, 443]
在这个配置中,我们定义了一个名为allow-specific-source的GlobalNetworkPolicy,它允许来自10.244.1.0/26子网的流量访问目标端口80和443。
4.4 配置BGP Peering
BGP Peering是Calico中用于配置节点与物理网络路由器之间BGP会话的配置对象。
例如,我们可以创建一个BGP Peering配置,将节点与核心路由器进行BGP对等:
apiVersion: crd.projectcalico.org/v1
kind: BGPConfiguration
metadata:
name: default
spec:
logSeverityScreen: Info
nodeToNodeMeshEnabled: true
asNumber: 64512
bgpPeers:
- peerIP: 192.168.1.1
asNumber: 65000
在这个配置中,我们定义了一个BGP Peering,将节点与IP地址为192.168.1.1的核心路由器进行BGP对等,使用AS号65000。
五、 常见网络故障排查及解决方案
尽管K8s的网络模型设计得非常优雅,但在实际部署和使用过程中,我们仍然可能会遇到各种网络问题。下面我们将介绍一些常见的网络故障及其解决方案。
5.1 Pod无法访问外部网络
现象:Pod内部的进程无法访问外部网络(例如,无法ping通8.8.8.8)。
可能原因:
- 节点的iptables规则配置错误。
- 节点的转发功能未启用。
- CNI插件配置错误。
解决方案:
- 检查节点的iptables规则:
iptables -L -n -v
- 检查节点的转发功能是否启用:
sysctl net.ipv4.ip_forward
如果返回值为0,则需要启用转发功能:
sysctl -w net.ipv4.ip_forward=1
- 检查CNI插件的配置,确保IP池和路由配置正确。
5.2 Pod之间无法通信
现象:同一节点或不同节点上的Pod之间无法互相访问。
可能原因:
- NetworkPolicy配置错误。
- CNI插件故障。
- 节点之间的网络连通性问题。
解决方案:
- 检查NetworkPolicy配置,确保没有阻止相关流量的规则。
kubectl get networkpolicy -A
kubectl describe networkpolicy <policy-name> -n <namespace>
- 检查CNI插件的日志,查看是否有错误信息。
kubectl logs -n calico-system <calico-node-pod-name>
- 检查节点之间的网络连通性,确保物理网络正常。
5.3 Service无法访问
现象:通过Service IP无法访问后端Pod。
可能原因:
- Service配置错误。
- Endpoint配置错误。
- iptables规则配置错误。
解决方案:
- 检查Service配置,确保端口和选择器正确。
kubectl get svc
kubectl describe svc <service-name> -n <namespace>
- 检查Endpoint配置,确保有正确的后端Pod。
kubectl get endpoints -n <namespace>
kubectl describe endpoints <service-name> -n <namespace>
- 检查节点的iptables规则,确保DNAT规则正确。
iptables -t nat -L -n -v
5.4 DNS解析失败
现象:Pod内部无法解析Service名称或外部域名。
可能原因:
- CoreDNS配置错误。
- CoreDNS Pod故障。
- 网络连通性问题。
解决方案:
- 检查CoreDNS配置,确保配置正确。
kubectl get configmap coredns -n kube-system -o yaml
- 检查CoreDNS Pod的状态,确保运行正常。
kubectl get pods -n kube-system -l k8s-app=kube-dns
kubectl logs <coredns-pod-name> -n kube-system
- 检查Pod到CoreDNS的连通性,确保网络正常。
六、 实战演练:一个完整的网络故障排查案例
为了让大家更好地理解网络故障排查的过程,我们来模拟一个实际的场景。
6.1 场景描述
我们有一个K8s集群,部署了一个名为web-app的应用。该应用由一个Deployment和一个Service组成。Service的类型为ClusterIP,端口为80。我们希望通过Service IP访问web-app的后端Pod。
然而,我们发现无法通过Service IP访问后端Pod。
6.2 排查过程
第一步:检查Service配置
首先,我们检查Service的配置,确保端口和选择器正确。
kubectl get svc web-app -n default
kubectl describe svc web-app -n default
输出显示Service配置正确,端口为80,选择器为app=web。
第二步:检查Endpoint配置
接下来,我们检查Endpoint配置,确保有正确的后端Pod。
kubectl get endpoints web-app -n default
kubectl describe endpoints web-app -n default
输出显示Endpoint为空,没有后端Pod。这说明Service无法找到匹配的Pod。
第三步:检查Pod标签
我们怀疑是Pod的标签配置错误,导致Service无法匹配。我们检查Pod的标签。
kubectl get pods -n default -l app=web
kubectl get pods -n default --show-labels
输出显示Pod的标签确实为app=web,与Service的选择器匹配。
第四步:检查NetworkPolicy
我们怀疑是NetworkPolicy阻止了流量。我们检查相关的NetworkPolicy配置。
kubectl get networkpolicy -n default
kubectl describe networkpolicy -n default
输出显示没有定义任何NetworkPolicy。
第五步:检查Pod网络
我们怀疑是Pod网络配置问题。我们进入一个Pod,检查其网络配置。
kubectl exec -it <pod-name> -n default -- ip addr
kubectl exec -it <pod-name> -n default -- curl http://<service-ip>
输出显示Pod的网络配置正常,但无法通过Service IP访问Service。
第六步:检查节点iptables规则
我们怀疑是节点的iptables规则配置错误。我们检查节点的iptables规则。
kubectl exec -it <pod-name> -n default -- iptables -t nat -L -n -v
kubectl exec -it <pod-name> -n default -- iptables -t filter -L -n -v
输出显示iptables规则配置正确。
第七步:检查CNI插件日志
我们怀疑是CNI插件故障。我们检查CNI插件的日志。
kubectl logs -n calico-system <calico-node-pod-name>
输出显示CNI插件运行正常,没有错误信息。
第八步:检查物理网络连通性
我们怀疑是物理网络连通性问题。我们检查节点之间的网络连通性。
kubectl exec -it <pod1-name> -n default -- ping <pod2-ip>
输出显示节点之间的网络连通性正常。
第九步:重启CoreDNS
我们怀疑是CoreDNS故障。我们重启CoreDNS Pod。
kubectl rollout restart deployment coredns -n kube-system
重启后,我们再次尝试通过Service IP访问Service,仍然失败。
第十步:检查Service代理模式
我们怀疑是Service代理模式配置问题。我们检查Service的代理模式。
kubectl get svc web-app -n default -o yaml
输出显示Service的代理模式为iptables。我们尝试将代理模式更改为IPVS。
kubectl patch svc web-app -n default -p '{"spec":{"externalTrafficPolicy":"Cluster"}}'
更改后,我们再次尝试通过Service IP访问Service,仍然失败。
第十一步:检查Node网络
我们怀疑是Node网络配置问题。我们检查节点的网络配置。
kubectl exec -it <node-name> -- ip addr
kubectl exec -it <node-name> -- iptables -t nat -L -n -v
输出显示节点的网络配置正常。
第十二步:重新部署应用
我们怀疑是应用本身的问题。我们重新部署应用。
kubectl delete deployment web-app -n default
kubectl apply -f web-app-deployment.yaml
kubectl apply -f web-app-service.yaml
重新部署后,我们再次尝试通过Service IP访问Service,成功!
6.3 故障总结
通过这个案例,我们可以看到,网络故障的排查需要系统性地进行,从Service配置、Endpoint、Pod标签、NetworkPolicy、Pod网络、iptables规则、CNI插件、物理网络、CoreDNS、Service代理模式、Node网络等多个角度进行检查。每一步都需要仔细分析,才能找到问题的根源。
七、 给小朋友的通俗解释
好了,讲了这么多技术细节,现在让我们换一种轻松的方式,给小朋友讲一讲K8s的网络。
想象一下,K8s集群就像一个巨大的学校。每个Pod就是一个学生,每个学生都有自己的座位(IP地址)。学校里有图书馆(Service),图书馆有唯一的编号(Service IP)。
当你要去图书馆借书时,你需要找到图书馆的编号。这个编号是由学校的广播系统(CoreDNS)来维护的。广播系统会告诉你,图书馆的编号是多少,以及图书馆在哪里。
学校里有各种各样的规则(NetworkPolicy),有些区域只允许特定的学生进入。这些规则是由学校的管理员(iptables)来执行的。
学校里有不同的教室(节点),每个教室都有自己的网络(虚拟网桥)。教室之间的网络是通过走廊(物理网络)连接起来的。
如果某个学生找不到图书馆
