做过几年K8s运维和容器化改造的老哥应该都有类似感受:集群本身不难搭,真正让人挠头的是网络。Pod起不来、Service访问不通、跨节点ping不通、域名解析超时……这些问题十有八九都能追溯到网络组件上。
网上讲K8s网络的文章不少,但大多局限在“怎么装Flannel”或者“怎么配Service”,很少有人把Pod网络、Service、DNS、Ingress、NetworkPolicy这些组件之间的协作关系串起来讲。结果就是你照教程配一遍能跑,换个场景一改就废,出了问题也不知道该查哪一层。
这篇文章就以“k8s网络组件及关系”为主线,把K8s网络模型、CNI插件、kube-proxy、CoreDNS、Ingress Controller这些核心组件的职责和协作机制彻底拆一遍,再附上我实际部署和排障过程中的操作记录。不管你是刚接触K8s的小白,还是准备K8s面试的进阶选手,或者正准备在K8s上部署LNMP这类有状态业务,这篇文章都值得你花十五分钟看完。
1. 从网络模型说起:理解组件关系的地基
1.1 K8s网络模型的三条铁律
K8s的网络模型和Docker原生网络完全是两个思路。Docker时代,容器通过docker0网桥和宿主机共享网络,容器要对外提供服务得靠-p端口映射,容器之间要通信得靠--link或者共享网络命名空间。小规模玩一玩没问题,一旦上了集群,几十台节点、几百个容器互相通信,这种模式立刻崩溃——端口映射冲突、容器IP怎么分配、跨节点容器怎么互访,全是问题。
K8s从设计上直接推翻了Docker这一套,定下了三条铁律:
- 所有Pod可以直接通信,不需要NAT转换
- 所有Node可以直接和所有Pod通信,不需要NAT转换
- Pod看到的自己的IP,和集群里其他组件看到的Pod IP是一致的
这三条说白了就是让整个集群“看起来像一台计算机”——每个Pod都是这台“大电脑”里的一个进程,天然分配一个合法IP,彼此直接访问,谁也不用管“NAT映射”这回事。
这个模型带来的好处是巨大的:你不需要任何端口映射,Pod的IP可以随着调度漂移到集群任意节点;你也不需要关心“这个Pod到底在哪个节点上”,只要拿到Pod IP就能访问。
1.2 四层网络组件分工
为了实现上面三条铁律,K8s引入了多个网络组件,各管一段,缺一不可。我习惯把它们分成四个层级来理解:
- Pod网络层:解决Pod IP分配和跨节点通信,实际由CNI插件实现
- Service网络层:解决Pod IP漂移和负载均衡问题,由kube-proxy实现
- DNS解析层:解决Service名字到IP的解析问题,由CoreDNS实现
- 入口流量层:解决外部流量如何进入集群,由Ingress Controller实现
这四个层级是典型的“下层为上层服务”的关系。没有Pod网络,Pod之间根本互相访问不了,Service转发的后端就不存在;没有Service,Pod IP一变就得改配置;没有CoreDNS,你只能天天背IP;没有Ingress,外部流量就只能全走NodePort,端口管理会变成噩梦。
1.3 一次请求完整流经的组件链路
我把这四层关系用一个完整的访问链路串起来,你就明白了。假设你在浏览器里输入http://shop.example.com访问一个部署在K8s里的Web应用:
- 用户请求先到达Ingress Controller的Node节点端口(通常是80/443)
- Ingress Controller根据域名/路径规则,将请求转发到对应的Service ClusterIP
- Service把请求交给kube-proxy,kube-proxy通过iptables/IPVS做DNAT,把目标地址改成后端Pod IP
- Pod网络(CNI/Flannel/Calico)负责把这个数据包送到目标Pod所在的节点和虚拟网卡上
注意一个细节:DNS解析发生在哪一步?如果用户在集群外访问,域名解析是用户自己的DNS在做,解析结果是Ingress Controller所在节点的IP。如果集群内的Pod要访问Service,那么是CoreDNS在解析,解析结果是Service的ClusterIP。
理解了这条链路,后面所有问题都好定位了。
2. 核心网络组件逐个拆解
2.1 CNI插件:Pod网络的真正实现者
先说CNI。CNI全称Container Network Interface,是一套标准规范,定义了容器运行时怎么调用网络插件给Pod配置网络。K8s调用CNI插件的流程是:kubelet发现新Pod要创建,调用CNI插件,CNI插件去创建虚拟网卡、分配IP、配置路由。
CNI插件目前有三套主流的实现:
- Flannel:最简单,基于VXLAN或host-gw的Overlay网络,部署一条命令,性能有少量损耗,适合中小集群和个人学习
- Calico:基于BGP路由协议,把Pod网段的路由信息广播给所有节点,性能更好,而且原生支持NetworkPolicy
- Cilium:基于eBPF技术,性能最强,功能最丰富,但学习曲线陡,适合对性能有极致要求的场景
我自己的经验,如果你只是想快速搭一套环境学习K8s,Flannel完全够用;如果是要上生产,建议直接上Calico,省得到时候还得换。
这里多说一句Flannel的VXLAN工作原理。Flannel在每台节点上会创建一个flannel.1虚拟网卡,同时维护一张路由表。当节点A上的Pod要访问节点B上的Pod时,数据包会走宿主机的flannel.1网卡,被封装成VXLAN包,通过真实的物理网络传输到节点B,然后解封装投递到目标Pod。这个过程对Pod来说是透明的,Pod根本感知不到底层有Overlay隧道,这也是“看起来像一台大电脑”这个比喻最生动的地方。
2.2 kube-proxy与Service:虚拟IP背后的转发机制
Service在K8s中是一个虚拟概念,它的ClusterIP并没有绑定在任何真实的网络设备上。那请求发到ClusterIP,怎么就到了Pod呢?答案是kube-proxy。
kube-proxy有三种运行模式。最常见的是iptables模式:kube-proxy监听API Server上Service和Endpoint的变化,为每个Service创建对应的iptables规则。当一个数据包的目标地址是ClusterIP时,iptables会做DNAT,把目标地址改成某个后端Pod的IP,然后把包转发出去。后端Pod的选择是纯随机的,近似负载均衡。
另一种是IPVS模式。IPVS是Linux内核自带的LVS模块,底层是netfilter,但用的是hash表而不是链表,在大规模Service下效率比iptables高得多。IPVS还支持更多负载均衡算法,例如轮询、最少连接、加权等。
三种Service类型也值得讲透:
- ClusterIP:默认类型,集群内只能通过虚拟IP访问。典型场景是后端服务之间互相调用
- NodePort:在每个Node上开一个30000-32767的端口,访问任意节点的这个端口,都会转发到Service
- LoadBalancer:云厂商的负载均衡器接进来,常用于对外暴露服务
我想提醒一个容易踩坑的点:千万不要在真实网络里和Service的ClusterIP网段冲突。如果你所在的公司内网恰好有10.96.0.0/12这个网段,而你Service的ClusterIP也设成10.96.0.0/12,那么集群内访问任意10.96.x.x的地址都会优先走内网路由,导致服务全挂,且极难排查。
2.3 CoreDNS:集群内部的“域名服务器”
没有DNS之前,一个Pod想访问另一个Pod的服务,只能写死ClusterIP。但Pod是随时可能重建的,Service的IP虽然比Pod稳定,但如果Service删了重建,ClusterIP照样变。
CoreDNS的出现就是为了解决“名字到IP”的问题。K8s集群内部默认安装了CoreDNS,部署在kube-system命名空间下,它的Pod IP在集群中是固定注册的。
CoreDNS的核心名称规则是<service名字>.<命名空间>.svc.cluster.local。比如在default命名空间下有一个叫nginx的Service,那么在同一个命名空间里的Pod可以直接访问nginx,跨命名空间要写nginx.default.svc.cluster.local,最简写法是nginx.default。
这个机制给业务带来的直接好处是:你不用关心服务IP地址到底是多少,只要记住一个逻辑名就行。这一点在做LNMP这类多组件架构时特别实用——PHP容器里配置数据库连接,DNS地址直接写mysql就完了,IP变没变根本不用管。
2.4 Ingress Controller:七层智能入口
Service的NodePort确实可以对外提供服务访问,但问题很明显:一旦服务多起来,每个服务都要占一个端口,管理混乱不说,四层转发也无法按域名、按URL路径做精细化路由。
Ingress就是为了解决这个问题出现的。但需要注意,Ingress只是一个Kubernetes API资源对象,它本身不做转发,真正干活的是Ingress Controller,最常见的是Nginx Ingress Controller和Traefik。
Ingress Controller的工作原理是:监听API Server中Ingress资源的变化,动态生成Nginx配置文件,然后reload Nginx进程。当你定义了一条Ingress规则,比如“域名shop.example.com,路径/api,转发到后端service: api-service:8080”,Ingress Controller就会把这条规则写进Nginx配置,之后凡是匹配该域名+路径的请求,都会被Nginx转发到后端Service。
用Ingress之后,整个集群的七层入口节点就集中到一个或少数几个Node上,只占用80/443端口,这就是它相比NodePort最核心的优势。
2.5 NetworkPolicy:网络安全的守门员
默认情况下,K8s集群内所有Pod之间是可以互相通信的,没有任何访问控制。这在生产环境非常危险,比如金融系统的web容器并不应该访问数据库容器,但它们默认能访问。
NetworkPolicy就是来做这个限制的。它的核心逻辑是:定义一组Pod,只允许来自指定来源的流量进来,只允许去往指定目标的流量出去。
需要特别强调的是,NetworkPolicy不是K8s核心组件自动实现的,它依赖CNI插件的支持。Flannel默认不支持NetworkPolicy,所以很多人装了Flannel之后创建NetworkPolicy规则,发现完全不生效,仔细查了一下才发现是底层不支持。Calico原生支持NetworkPolicy,这也是我推荐生产环境首选Calico的原因之一。
下面是一个简单的NetworkPolicy YAML示例,限制只允许app=frontend的Pod访问标签为db的Pod的3306端口:
apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-frontend-to-db spec: podSelector: matchLabels: app: db ingress: - from: - podSelector: matchLabels: app: frontend ports: - protocol: TCP port: 33063. 实操:从网络规划到组件配置
3.1 部署前的网络规划
K8s集群的网络规划必须在部署前就想清楚,这是很多新手容易忽略的。一般来说,集群中至少有三张网络网段:
- 宿主机节点网络:物理服务器的内网IP段
- Pod网络:每个Pod动态分配的IP段
- Service网络:Service ClusterIP所在的网段
这三个网段绝对不能互相重叠,也最好不要和公司现有的内网IP段重叠。以我常用的规划为例:
| 网络类型 | 网段 | 说明 |
|---|---|---|
| 宿主机网络 | 192.168.10.0/24 | 节点物理IP |
| Pod网络 | 10.244.0.0/16 | Flannel默认网段,最多65534个Pod |
| Service网络 | 10.96.0.0/12 | kubeadm默认网段,最多约1048574个虚拟IP |
有人会问,为什么Pod网络用10.244开头,Service用10.96开头?这两个其实是kubeadm和Flannel的默认值,你用其它私有网段也行,只要保证不冲突。
这里有一个实操细节:使用kubeadm初始化集群时,--pod-network-cidr这个参数必须和你后面要安装的CNI插件的网段一致。比如你初始化时写的--pod-network-cidr=10.244.0.0/16,Flannel默认配置也是10.244.0.0/16,这样才能对上。如果你初始化时手欠写成10.200.0.0/16,后面Flannel还是默认10.244,那么集群Pod网络的网段对不上,Flannel起不来,Pod就会一直Pending。
3.2 安装Flannel并验证Pod网络
以kubeadm搭好的集群为例,装Flannel其实就一条命令:
kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml安装完成后,检查Flannel Pod是否正常运行:
kubectl -n kube-system get pods | grep flannel输出类似这样说明Flannel已经正常:
kube-flannel-ds-jxxxx 1/1 Running 0 2m接着可以做一个跨节点连通性测试。假设你有两个节点node1和node2,分别创建一个测试Pod,互相ping对方Pod IP:
kubectl run test-a --image=busybox -- sleep 3600 kubectl run test-b --image=busybox -- sleep 3600 kubectl exec -it test-a -- ping <test-b的Pod IP>如果能ping通,说明Pod网络已经正常。如果ping不通,第一步要检查Flannel的日志:
kubectl -n kube-system logs -f daemonset/kube-flannel-ds最常见的报错是“Found default interface with no IP”,这说明Flannel选网卡的时候选错了,没有选到你的物理网卡。解决办法是在Flannel DaemonSet的环境变量里强制指定网卡名,比如--iface=eth1,重启后就好了。
3.3 Service与Ingress配置示例:以LNMP为例
部署LNMP时,很多初学的朋友会犯一个经典的网络配置错误:Nginx容器里配置PHP-FPM的地址,写成localhost。这样当然不通,因为Nginx和PHP-FPM是两个Pod,它们各自有独立的网络命名空间,必须通过Service名或Pod IP去访问。
我直接给一个最小但完整的LNMP网络配置示例。先创建MySQL的Service和Pod:
apiVersion: v1 kind: Service metadata: name: mysql spec: selector: app: mysql ports: - port: 3306 targetPort: 3306 --- apiVersion: apps/v1 kind: Deployment metadata: name: mysql spec: selector: matchLabels: app: mysql template: metadata: labels: app: mysql spec: containers: - name: mysql image: mysql:8.0 env: - name: MYSQL_ROOT_PASSWORD value: "123456"然后是PHP-FPM和Nginx,Nginx配置里的fastcgi_pass,直接写PHP-FPM的Service名,而不是localhost:
fastcgi_pass php-fpm:9000;PHP代码里连接数据库的host,也写MySQL的Service名而不是localhost:
$db = new PDO('mysql:host=mysql;port=3306;dbname=test', 'root', '123456');这套配置只要你的Service和Pod能正常通信就能跑起来。如果访问不通,用kubectl exec进到Nginx容器里,先ping一下php-fpm这个Service名是否解析到ClusterIP,再telnet一下9000端口是否通,基本就能定位问题。
3.4 多网络插件Multus与VLAN配置
常规K8s集群每Pod只分配一张网卡,属于“管理网络”。但在某些特定场景,比如电信行业的NFV、视频处理、高性能计算等业务,Pod不仅需要管理网,还需要第二张甚至第三张物理网络,比如接入VLAN网络。
这时候就要用Multus。Multus本身是一个“超级CNI”,它不是替代Flannel或Calico,而是把多个CNI插件组合起来,让一个Pod同时拥有多张网卡。可以说,它相当于一个CNI的调度器。
Multus的用法是创建一个NetworkAttachmentDefinition,定义第二张网卡使用的CNI插件和网络参数。以VLAN为例,可以这样定义:
apiVersion: k8s.cni.cncf.io/v1 kind: NetworkAttachmentDefinition metadata: name: vlan-10 spec: config: '{ "cniVersion": "0.3.1", "type": "bridge", "bridge": "br-vlan10", "vlan": 10, "ipam": { "type": "host-local", "subnet": "10.10.10.0/24" } }'然后在Pod模板的annotations里声明要附加这个网络:
annotations: k8s.v1.cni.cncf.io/networks: vlan-10Pod创建后,你会在容器里看到多了一张名为net1的网卡,它被分配在VLAN10网络中。
4. 常见问题与排查技巧实录
4.1 Pod无法分配IP
现象:Pod一直处于ContainerCreating状态,describe能看到CNI相关的报错。
排查思路分三步:
- 检查Flannel/Calico的Pod是否正常运行,Pod网络组件挂了,新Pod就分不到IP
- 检查IP池是否耗尽,Flannel的IP是宿主机的ip段子网拆分的,一个节点可用的Pod IP数量是有限的
- 检查CNI配置目录
/etc/cni/net.d/里是否有正确配置,有些安装工具不写这个目录,CNI插件就找不到配置
4.2 Service无法访问
如果Pod之间能通,但通过Service访问失败,我一般按照下面这个顺序排查:
- 确认Service的selector对应到后端Pod,命令是
kubectl get endpoints <service名>。如果Endpoints列表为空,说明selector写错或标签不匹配,Service后面没有Pod可用 - 确认kube-proxy正常运行,查看
kubectl -n kube-system get pods | grep kube-proxy。kube-proxy挂了,iptables/IPVS规则就没人维护 - 查看iptables规则,使用
iptables -t nat -L | grep <ClusterIP>,看DNAT规则是否生成 - 如果以上全部正常,最后检查DNS。进入一个Pod里执行
nslookup <service名>,看能否解析出ClusterIP
4.3 NodePort外部访问不通
NodePort在集群内部能通,但集群外访问不了,这类问题的原因通常不在K8s内部,而是宿主机网络和防火墙层面的问题。
首先检查服务端口是否真的绑定在节点上,执行ss -tlnp | grep 30080。其次查看节点的安全组或防火墙规则,确认30080端口是否对目标IP段放行。如果用了云厂商的负载均衡接LoadBalancer类型的Service,还要在控制台检查后端实例的端口健康检查是否通过。
4.4 K8s面试常问的网络题速查
我整理几个常见的面试题和思路供参考:
- K8s和Docker的区别:Docker是容器运行时,负责单个容器的创建和运行;K8s是容器编排平台,负责成百上千个容器的调度、伸缩、服务发现和故障恢复。两者不是替代关系,K8s底层默认就用containerd(Docker的后续替代品)或Docker作为运行时
- 简述K8s Pod之间的通信流程:Pod之间通过CNI插件分配的IP直接通信,跨节点则通过VXLAN隧道或BGP路由转发
- Service的ClusterIP能ping通吗:不一定能ping通。ClusterIP是虚拟IP,没有绑定真实网卡,kube-proxy只维护iptables/IPVS转发规则,ICMP包能不能通取决于节点和CNI的实现
- Flannel和Calico的主要区别:Flannel使用VXLAN封包,简单但性能有损耗,不支持NetworkPolicy;Calico使用BGP路由,性能好,支持NetworkPolicy
5. 给新手的学习建议
最后分享几条我用真金白银换来的经验。
第一,学习K8s网络,不要急着上生产级方案,先用kubeadm搭一套单节点的集群,装上Flannel,把Pod网络、Service、DNS这三个东西玩转,再去接触Ingress、NetworkPolicy这些进阶内容。地基稳了,上面盖楼才不容易塌。
第二,强烈建议花点时间学一下iptables和ipvs的基本规则。K8s的Service转发底层就是靠它们实现,你只有真正用iptables -t nat -L -n看过K8s生成的规则长什么样,才能理解Service的转发原理,排查问题也会快很多。
第三,遇到网络问题,不要盲目重启,先按层次从下往上排查:Pod能不能互访,Service能不能转发,DNS能不能解析,Ingress有没有正确路由。这条链路捋顺了,90%的问题都能定位到原因。
第四,如果你要在K8s上部署LNMP这类多组件应用,记得“DNS优先于IP”这个原则。写配置文件的时候,能写Service名就不写IP,能写域名就不写IP,这样不管底层Pod如何漂移,你的配置都不用改。
K8s网络这潭水,说深也深,说浅也浅。把CNI、Service、DNS、Ingress这四层组件的关系理清楚,把它们的转发机制吃透,你在K8s这条路上基本就没有跨不过去的坎了。