K8s网络组件全解析:CNI、Service、CoreDNS与Ingress协作原理

发布时间:2026/9/8 9:20:25
K8s网络组件全解析:CNI、Service、CoreDNS与Ingress协作原理 做过几年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转发的后端就不存在没有ServicePod IP一变就得改配置没有CoreDNS你只能天天背IP没有Ingress外部流量就只能全走NodePort端口管理会变成噩梦。1.3 一次请求完整流经的组件链路我把这四层关系用一个完整的访问链路串起来你就明白了。假设你在浏览器里输入http://shop.example.com访问一个部署在K8s里的Web应用用户请求先到达Ingress Controller的Node节点端口通常是80/443Ingress Controller根据域名/路径规则将请求转发到对应的Service ClusterIPService把请求交给kube-proxykube-proxy通过iptables/IPVS做DNAT把目标地址改成后端Pod IPPod网络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网段的路由信息广播给所有节点性能更好而且原生支持NetworkPolicyCilium基于eBPF技术性能最强功能最丰富但学习曲线陡适合对性能有极致要求的场景我自己的经验如果你只是想快速搭一套环境学习K8sFlannel完全够用如果是要上生产建议直接上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的端口访问任意节点的这个端口都会转发到ServiceLoadBalancer云厂商的负载均衡器接进来常用于对外暴露服务我想提醒一个容易踩坑的点千万不要在真实网络里和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示例限制只允许appfrontend的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节点物理IPPod网络10.244.0.0/16Flannel默认网段最多65534个PodService网络10.96.0.0/12kubeadm默认网段最多约1048574个虚拟IP有人会问为什么Pod网络用10.244开头Service用10.96开头这两个其实是kubeadm和Flannel的默认值你用其它私有网段也行只要保证不冲突。这里有一个实操细节使用kubeadm初始化集群时--pod-network-cidr这个参数必须和你后面要安装的CNI插件的网段一致。比如你初始化时写的--pod-network-cidr10.244.0.0/16Flannel默认配置也是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 IPkubectl run test-a --imagebusybox -- sleep 3600 kubectl run test-b --imagebusybox -- 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的环境变量里强制指定网卡名比如--ifaceeth1重启后就好了。3.3 Service与Ingress配置示例以LNMP为例部署LNMP时很多初学的朋友会犯一个经典的网络配置错误Nginx容器里配置PHP-FPM的地址写成localhost。这样当然不通因为Nginx和PHP-FPM是两个Pod它们各自有独立的网络命名空间必须通过Service名或Pod IP去访问。我直接给一个最小但完整的LNMP网络配置示例。先创建MySQL的Service和PodapiVersion: 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和NginxNginx配置里的fastcgi_pass直接写PHP-FPM的Service名而不是localhostfastcgi_pass php-fpm:9000;PHP代码里连接数据库的host也写MySQL的Service名而不是localhost$db new PDO(mysql:hostmysql;port3306;dbnametest, 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名看能否解析出ClusterIP4.3 NodePort外部访问不通NodePort在集群内部能通但集群外访问不了这类问题的原因通常不在K8s内部而是宿主机网络和防火墙层面的问题。首先检查服务端口是否真的绑定在节点上执行ss -tlnp | grep 30080。其次查看节点的安全组或防火墙规则确认30080端口是否对目标IP段放行。如果用了云厂商的负载均衡接LoadBalancer类型的Service还要在控制台检查后端实例的端口健康检查是否通过。4.4 K8s面试常问的网络题速查我整理几个常见的面试题和思路供参考K8s和Docker的区别Docker是容器运行时负责单个容器的创建和运行K8s是容器编排平台负责成百上千个容器的调度、伸缩、服务发现和故障恢复。两者不是替代关系K8s底层默认就用containerdDocker的后续替代品或Docker作为运行时简述K8s Pod之间的通信流程Pod之间通过CNI插件分配的IP直接通信跨节点则通过VXLAN隧道或BGP路由转发Service的ClusterIP能ping通吗不一定能ping通。ClusterIP是虚拟IP没有绑定真实网卡kube-proxy只维护iptables/IPVS转发规则ICMP包能不能通取决于节点和CNI的实现Flannel和Calico的主要区别Flannel使用VXLAN封包简单但性能有损耗不支持NetworkPolicyCalico使用BGP路由性能好支持NetworkPolicy5. 给新手的学习建议最后分享几条我用真金白银换来的经验。第一学习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这条路上基本就没有跨不过去的坎了。