K8s网络组件全解析:CNI、Service、CoreDNS与Ingress协作原理
2026/9/8 9:20:17 网站建设 项目流程

做过几年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应用:

  1. 用户请求先到达Ingress Controller的Node节点端口(通常是80/443)
  2. Ingress Controller根据域名/路径规则,将请求转发到对应的Service ClusterIP
  3. Service把请求交给kube-proxy,kube-proxy通过iptables/IPVS做DNAT,把目标地址改成后端Pod IP
  4. 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: 3306

3. 实操:从网络规划到组件配置

3.1 部署前的网络规划

K8s集群的网络规划必须在部署前就想清楚,这是很多新手容易忽略的。一般来说,集群中至少有三张网络网段:

  • 宿主机节点网络:物理服务器的内网IP段
  • Pod网络:每个Pod动态分配的IP段
  • Service网络:Service ClusterIP所在的网段

这三个网段绝对不能互相重叠,也最好不要和公司现有的内网IP段重叠。以我常用的规划为例:

网络类型网段说明
宿主机网络192.168.10.0/24节点物理IP
Pod网络10.244.0.0/16Flannel默认网段,最多65534个Pod
Service网络10.96.0.0/12kubeadm默认网段,最多约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-10

Pod创建后,你会在容器里看到多了一张名为net1的网卡,它被分配在VLAN10网络中。

4. 常见问题与排查技巧实录

4.1 Pod无法分配IP

现象:Pod一直处于ContainerCreating状态,describe能看到CNI相关的报错。

排查思路分三步:

  1. 检查Flannel/Calico的Pod是否正常运行,Pod网络组件挂了,新Pod就分不到IP
  2. 检查IP池是否耗尽,Flannel的IP是宿主机的ip段子网拆分的,一个节点可用的Pod IP数量是有限的
  3. 检查CNI配置目录/etc/cni/net.d/里是否有正确配置,有些安装工具不写这个目录,CNI插件就找不到配置

4.2 Service无法访问

如果Pod之间能通,但通过Service访问失败,我一般按照下面这个顺序排查:

  1. 确认Service的selector对应到后端Pod,命令是kubectl get endpoints <service名>。如果Endpoints列表为空,说明selector写错或标签不匹配,Service后面没有Pod可用
  2. 确认kube-proxy正常运行,查看kubectl -n kube-system get pods | grep kube-proxy。kube-proxy挂了,iptables/IPVS规则就没人维护
  3. 查看iptables规则,使用iptables -t nat -L | grep <ClusterIP>,看DNAT规则是否生成
  4. 如果以上全部正常,最后检查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这条路上基本就没有跨不过去的坎了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询