ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Kubernetes Service与Ingress:分层流量模型、原理与排障实践

2026/10/1 3:45:17 拓冰建站 浏览量
Kubernetes Service与Ingress:分层流量模型、原理与排障实践 聊Kubernetes的流量负载几乎每个刚接触K8s的人都会被Service和Ingress卡住。我不止一次在群里看到有人问“我已经建了Service为什么外部还是访问不了”或者“Ingress到底算不算Service的一种”这些问题背后其实是还没理清K8s里流量分层的设计思路。Service解决的是集群内部的服务发现和负载均衡Ingress解决的是集群外部HTTP(S)流量的统一入口和路由转发。二者功能上有关联但定位完全不同。这篇文章我就从这两层模型出发把原理、配置、排障和实际运维中容易踩的坑一次讲清楚。无论你是刚入门K8s的开发还是在准备CKA又或者正想把业务从裸机搬到K8s上这篇都能当一份比较完整的参考。1. 为什么K8s要单独设计一套流量模型1.1 传统负载均衡在容器环境里为什么失效在部署在虚拟机或物理机上的传统架构里服务的IP和端口相对固定负载均衡器的配置也基本一劳永逸。但容器环境完全不是这个逻辑。Pod在K8s里是被当成“临时资源”来用的副本销毁、节点故障、水平扩容、滚动更新都会导致Pod的IP频繁变化。如果还用传统方式把后端地址写死在负载均衡配置里那么每次Pod重建后都要手动改配置完全没法自动化。用一句生活化的类比来理解Pod IP就像酒店客人的临时房号客人退房后再入住可能换房间Service则像酒店的固定前台总机不管住客怎么变你只需要拨打同一个总机号码前台会帮你转接到当前入住的房间。这就是K8s引入Service的核心原因——为动态变化的Pod提供一套稳定的访问入口。1.2 Service与Ingress的分层逻辑想不被绕晕必须先分清它们所处的协议层。Service工作在四层主要管TCP/UDP的流量分发。它通过标签选择器找到一组Pod再把到达Service的请求均匀转发到这些Pod上。Service本身不关心请求里面是什么路径、什么域名、是不是HTTP它只关心“把这包数据送到哪个Pod”。Ingress工作在七层专门处理HTTP和HTTPS请求。它会根据请求的域名host、URL路径path、请求方法等七层信息把流量路由到不同的Service上。也就是说Ingress做的是“先看请求内容再决定交给哪个Service”相当于集群入口处的智能分拣员。这种分层带来的好处很明显底层Service负责稳定的四层转发上层Ingress负责灵活的业务路由。你在同一个Ingress下可以按域名把流量分给前端service按路径把流量分给后端service还可以挂上TLS证书做HTTPS终止这些都在业务层完成不需要给每个Service单独暴露公网端口。1.3 流量负载模型的整体面貌把两层合起来看K8s的流量路径大致是外部请求先到达Ingress ControllerIngress Controller根据规则转发到对应Service的ClusterIPService再经过kube-proxy转发到某个Pod。在某些简单场景下也可以不走Ingress直接用NodePort或LoadBalancer类型的Service把流量接入集群。后面第4部分我会专门把这条链路中的每一跳拆开讲。还要注意一点Service也承担负载均衡职责。即使没有Ingress只要Service后面挂了多个Pod它就会做四层轮询转发。而Ingress除了路由也可以配合Canary注解做灰度发布。理解了这个模型你在设计集群网络时就不会再纠结“该用什么暴露服务”的问题。2. Service集群内流量分发的核心枢纽2.1 Service工作原理Selector机制与EndpointsService本身并不是一个运行中的进程它更像一条规则或者一个转发目标。Service通过spec.selector里的标签选择器动态圈定一组Pod。K8s的控制面会持续观察这组Pod的变化并维护一个名叫Endpoints的资源对象里面记录当前所有匹配Pod的IP和端口。数据面转发时kube-proxy会读取Service和Endpoints的更新把发往Service IP的流量改写并转发到具体的Pod IP。我建议你先学会用命令看这个对应关系排障时特别有用kubectl get svc webapp-svc kubectl get endpoints webapp-svc kubectl describe svc webapp-svc如果Endpoints里的Pod IP列表是空的说明Service的selector没有匹配到任何Pod这时候再怎么调转发都白搭。我见过很多新手创建了Service后访问失败最后定位到的问题是Pod的标签和selector对不上比如Pod写的是appwebappService写的是app: webapp-v1。排查Service问题第一步永远是看Endpoints。2.2 三种Service类型的选型对比Service根据暴露范围不同分成几种类型项目里最常用的就是ClusterIP、NodePort和LoadBalancer。Service类型访问范围实现方式典型场景ClusterIP集群内部分配虚拟IP仅集群内可路由服务间调用、Ingress后端NodePort集群外可通过节点IP访问在每个节点开放固定端口转发到ClusterIP临时调试、小规模暴露、裸机集群LoadBalancer集群外通过云负载均衡访问云厂商LB绑定到NodePort或直接转发到Pod云环境暴露服务、对接公有云ExternalName集群内外均可DNS层CNAME转发把集群外域名映射为集群内访问名ClusterIP是默认类型也是其他类型的基础。NodePort等于在ClusterIP之上在每个节点额外开一个端口LoadBalancer又是在NodePort之上让云厂商的负载均衡器把公网流量转发到节点端口。很多云上集群的LoadBalancer底层实际上还是NodePort只是被云控制器自动串联起来了。选型时我一般遵循这样的原则集群内部服务调用用ClusterIP带公网入口的线上业务优先考虑Ingress加ClusterIP的组合如果业务在裸金属环境且没有额外LB设备就考虑NodePort配合externalTrafficPolicy只有确实需要云厂商LB能力时比如自动创建SLB、EIP才直接创建LoadBalancer类型Service。2.3 会话保持与转发模式的细节Service默认是随机或轮询转发但对需要保持用户会话的应用比如WebSocket、登录状态你希望同一个用户IP的请求始终打到同一个Pod上。这时需要配置sessionAffinityspec: sessionAffinity: ClientIP sessionAffinityConfig: clientIP: timeoutSeconds: 10800设置成ClientIP后Service会尽量把来自同一个源IP的请求转发到同一个Pod。不过要注意如果Pod数量变化或者Pod被重建会话还是会断。会话保持能缓解问题但不能完全替代业务侧的状态共享方案比如Redis保存Session。kube-proxy的转发模式也值得关注。老版本默认使用iptables模式每个连接会遍历规则并发高时CPU开销较大。新版本很多集群默认启用IPVS模式用hash表的转发逻辑性能和可靠性都更好。想确认当前模式可以查看kube-proxy容器的启动参数或者直接看节点上的ipvs规则ipvsadm -L -n如果输出里头没有对应的Service条目而你的集群用的是iptables模式就要去查iptables规则。这个细节在排查“Service不通”时非常关键。2.4 一个完整的Service示例下面这份YAML是我平时最常用的Web服务暴露方式apiVersion: v1 kind: Service metadata: name: webapp-svc spec: type: NodePort selector: app: webapp ports: - port: 80 protocol: TCP targetPort: 8080 nodePort: 30080这里port是Service对外提供服务的端口targetPort是容器里业务进程监听的端口。请记住这个区别很多人配置错误都是把两个端口填反了。比如你的容器用8080监听而Service的port写成8080、targetPort写成80流量转发过去就找不到进程。由于Pod可能分布在多个节点上NodePort会在所有节点上监听同一个端口。所以访问方式是http://任意节点IP:30080而不是某个特定IP。生产环境中节点数量多了这个特性反而容易把端口暴露面扩大需要靠安全组或防火墙控制入口。2.5 特殊类型的ServiceHeadless Service还有一种情况值得单独提一下就是headless service。配置时把clusterIP设为NoneService就不分配ClusterIPDNS解析会直接返回后端所有Pod的IP列表。这对有状态应用如数据库、消息队列特别有用因为应用可以自己感知每个Pod的真实地址做集群成员发现。如果你在跑高可用数据库或者需要自定义负载均衡策略的服务headless service通常比普通Service更好使。3. Ingress七层入口与统一路由3.1 先分清Ingress资源和Ingress Controller新人最常见的误区是以为创建一个Ingress资源后流量就能自动按规则转发。实际上Ingress只是一份声明式的路由规则真正干活的是Ingress Controller。Controller通常以Pod的形式运行在集群里它会持续监听Ingress资源的变化并把这些规则转换成具体的反向代理配置比如nginx.conf、Envoy路由规则等。所以使用Ingress的第一步是部署一个Controller。最常用的是ingress-nginx如果追求性能和可观测性可以选择基于Envoy实现的Emissary或Kong云计算平台通常也提供托管型Ingress Controller比如阿里云、腾讯云都有自己的组件。社区里最主流、文档最全的还是ingress-nginx下面的例子都以它为准。部署完成后还要确保Ingress资源里指定了正确的ingressClassName否则Controller会无视你的规则。从K8s 1.19开始推荐用spec.ingressClassName字段指定而不是旧的annotations写法spec: ingressClassName: nginx3.2 host与path路由规则怎么配一个Ingress可以有多个规则每条规则可以基于不同域名或路径转发。例如让同一个公网入口同时处理两个域名apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: multi-host-ingress spec: ingressClassName: nginx rules: - host: shop.example.com http: paths: - pathType: Prefix path: / backend: service: name: shop-svc port: number: 80 - host: blog.example.com http: paths: - pathType: Prefix path: / backend: service: name: blog-svc port: number: 80这里有两个关键概念。pathType为Prefix表示前缀匹配为Exact表示精确匹配。后端service块里不需要写IP只需要写Service名字和端口Ingress Controller会自动从服务发现机制里拿到后端地址。如果你需要把/api开头的请求都导到一个网关服务前端静态资源导到另一个服务可以用不同的path和同一个host来做。匹配顺序也需要注意。在ingress-nginx里路径最长匹配优先。例如同时存在/和/api两个path时请求/api/users会先匹配到/api那条规则而不是/. 想验证路由是否生效可以进到Ingress Controller Pod里查看生成的nginx配置文件kubectl exec -n ingress-nginx deploy/ingress-nginx-controller -- cat /etc/nginx/nginx.conf | grep server_name -A 203.3 HTTPS与TLS证书配置Ingress最常见的生产用途之一就是帮你在入口层终止HTTPS。你只需要在K8s里创建一个包含证书和私钥的Secret然后在Ingress里引用即可apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: secure-ingress spec: ingressClassName: nginx tls: - hosts: - shop.example.com secretName: shop-tls-secret rules: - host: shop.example.com http: paths: - pathType: Prefix path: / backend: service: name: shop-svc port: number: 80创建Secret时要注意证书必须用PEM格式。如果是证书链要把中间证书和站点证书合并到同一个tls.crt里。用cert-manager这个项目的话可以自动申请和续期证书写个Certificate资源声明域名controller会定期检查证书过期时间。我在生产环境里一直用cert-managerLets Encrypt证书续期几乎不用人工介入。TLS配置还有一个细节想让HTTP请求自动跳转到HTTPS一般通过全局配置或者annotation实现比如nginx.ingress.kubernetes.io/ssl-redirect: true。如果某些路径比如API回调需要强制HTTP而不是跳转HTTPS可以单独覆盖这个annotation。3.4 金丝雀发布利用Ingress按比例分流Ingress可以做很实用的灰度发布。以ingress-nginx为例你可以在主Ingress之外再创建一个带Canary注解的Ingress。例如让新版本服务接收20%的流量apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: new-app-ingress annotations: nginx.ingress.kubernetes.io/canary: true nginx.ingress.kubernetes.io/canary-weight: 20 spec: ingressClassName: nginx rules: - host: shop.example.com http: paths: - pathType: Prefix path: / backend: service: name: shop-svc-new port: number: 80这样流量会按20%比例分到新的Service剩下80%继续走旧Service。灰度期间如果看到异常率升高直接把canary注解删掉或者将canary-weight改成0就能立即把流量收回旧版不用重新改主Ingress。这个方案比修改Deployment副本数要精细得多非常适合线上验证新版本。还可以按请求头分流比如只有带特定Header的用户才会走新版本适合内部预发布验证。对应注解是canary-by-header。这些高级功能都是Controller实现的所以在换Ingress Controller时同一套annotation不一定通用迁移前要重点检查。3.5 Ingress版本与兼容性注意点Ingress API从networking.k8s.io/v1beta1演进到networking.k8s.io/v1字段有一些变化。如果是新集群直接用v1老集群在用v1beta1的话建议尽早迁移。最明显的变化是backend字段写法老版本是backend: serviceName: my-svc servicePort: 80新版本是backend: service: name: my-svc port: number: 80升级过程中很多存量Ingress会报校验错误最好先用kubectl convert转换再apply。我见过有人直接从网上复制老配置到新集群结果Ingress创建成功但流量全都404。出现这种情况第一反应应该检查Ingress API版本和Controller版本是否匹配。4. 一条请求从外部到Pod的完整链路4.1 逐层拆解一次HTTP请求很多人配置都能成功但要把整个链路说清楚时又含糊了。我在这里把一次从公网访问服务的完整路径拆成六跳第一跳用户在浏览器输入域名经过DNS解析到负载均衡器或节点IP。如果用了云服务商这一跳会绑定公网IP。第二跳流量到达Ingress Controller Pod它根据Host头比如shop.example.com和URL路径找到匹配的Ingress规则。第三跳Controller根据规则里的backend把请求转发到对应Service的ClusterIP和端口。第四跳Service所在的节点上kube-proxy会处理这次转发根据负载均衡策略把流量送到某个后端Pod的IP与targetPort。第五跳Pod里的业务容器比如Nginx、Java应用收到请求并处理。第六跳响应数据按原路径返回。这里有个常见的认知误区Ingress Controller转发请求给Service时是否还会再做一次负载均衡答案是会。不过这部分行为在不同Controller里实现略有差异。ingress-nginx默认情况下会直接把后端指向Endpoints里的Pod IP而不是ClusterIP这样更多是为了保留客户端真实IP并绕过kube-proxy的性能损耗。如果你在Service层面配置了会话保持这里可能会失效因为Controller跳过了Service负载均衡逻辑。设计高可用方案时要清楚这一点会话保持建议放在Ingress层配置或者让业务层自己处理Session。4.2 什么时候只需要Service什么时候必须加Ingress对于集群内部服务之间的调用比如订单服务调用用户服务直接用ClusterIP类型的Service就够了。请求不经过任何入口集群内DNS解析到ClusterIP就能互通。对于需要被外部访问的业务如果你的需求只是简单暴露一个TCP端口比如数据库、Redis、MQ用NodePort或LoadBalancer类型的Service反而最省事。Ingress主要用于HTTP/HTTPS流量它不支持直接转发MySQL、Redis这类原生TCP长连接虽然ingress-nginx现在也支持TCP/UDP配置但配置复杂度明显高不如直接NodePort。对于典型的Web业务我的建议是统一使用Ingress作为入口后端服务全部用ClusterIP不对外暴露NodePort。这样做的好处有三个一是入口统一安全策略和TLS证书只需要管一个地方二是端口管理简单不用记住一堆nodePort三是可以做域名路由、灰度、限流等高级策略。如果你的业务部门多、服务多这种模式能省很多事。4.3 云环境下的LoadBalancer与Ingress配合在公有云上Ingress Controller本身通常也会配一个LoadBalancer类型的Service来接入流量。也就是说云厂商负载均衡器监听公网IP把流量转发到Ingress Controller的PodController再按规则转到集群内部。如果直接用LoadBalancer类型Service暴露每一个后端服务那么每有一个服务就要创建一个云负载均衡器费用高、管理难。正确做法是尽量把云LB资源收敛到Ingress层。多个域名、多套TLS证书、多条路由规则都挂在同一个LB后面整体成本会低很多。在云上使用托管K8s时还要注意Ingress Controller的Service带宽、安全组放通等细节否则容易出现“Controller部署好了但公网访问不通”的诡异问题。4.4 时延与性能考量Ingress多引入了一层转发时延确实会略高一点。但正常配置下这个增量通常在几毫秒以内对绝大多数业务无感。如果发现时延异常高优先检查后端Pod的负载情况以及K8s网络插件比如Calico、Cilium的转发路径而不是第一时间怀疑Ingress。另外性能瓶颈往往出现在TLS握手和连接复用上。可以开启Controller的HTTP/2支持并调整keep-alive长连接参数减少频繁握手。对于超高并发场景还可以考虑把Ingress Controller单独部署到专用节点池避免和业务Pod争抢资源。5. 常见问题与排查技巧实录5.1 Service访问不通从哪里开始查我处理过很多Service不通的工单把排查顺序整理成一套思路按顺序走基本能定位问题。先看Service是否正常kubectl get svc kubectl describe svc service-name重点看Endpoints是否为空。如果为空说明selector没匹配到Pod如果不为空跳下一步。再检查Pod本身是否Readykubectl get pods -l selector kubectl logs pod-namePod没Ready的常见原因是镜像拉取失败、启动命令错误、Liveness探针失败等。然后尝试在集群内直接访问ClusterIPkubectl run test-pod --imagebusybox --rm -it -- wget -O- http://cluster-ip:port如果集群内能通、集群外不通问题往往出在NodePort层比如安全组没放行端口、节点防火墙拦截、或者Service类型不是NodePort/LoadBalancer。如果NodePort通但Ingress不通问题十有八九在Ingress规则匹配上。5.2 Ingress规则不生效的检查清单当你访问域名返回404或者503时按这个清单逐项排查更有方向Ingress Controller是否运行查看ingress-nginx命名空间下的Deployment和Pod状态。Ingress资源是否关联正确的ingressClassName。域名解析是否指向了Ingress Controller的入口地址云LB IP或节点IP。后端Service名称和端口是否与Ingress配置完全一致少了任何一项都会转发失败。路径匹配是不是被更长的路径规则抢走了尤其存在/、/api这些混合配置时。TLS配置的Secret是否存在证书过期会导致握手失败。如果配置了rewrite-target路径重写后的后端Path是否与业务路由匹配。排查时可以开启Ingress Controller的访问日志配置到日志系统后一眼就能看到请求是否成功转发到后端。ingress-nginx还支持在pod级别实时看日志kubectl logs -f -n ingress-nginx deploy/ingress-nginx-controller很多404不是Controller没收到请求而是请求被转发到了错误的后端。日志里会直接显示upstream地址和状态码比猜配置高效得多。5.3 kube-proxy与转发链路相关的坑节点上kube-proxy是Service转发的执行者。它的Pod如果异常Service依然显示存在但流量就是不通。检查方法kubectl get pods -n kube-system | grep kube-proxy kubectl logs -n kube-system kube-proxy-xxxxxkube-proxy使用iptables或IPVS模式时规则是异步更新的。在Pod频繁创建删除时偶尔会出现规则残留。遇到Service时通时不通可以试着清掉对应的iptables规则或者重启kube-proxy。不过更推荐的还是采用低频率变更Pod的方式避免短时间大量重建触发底层规则的混沌状态。另一个坑是节点端口被占用。NodePort默认范围在30000-32767如果分配的端口已被节点上的进程监听Service会显示创建成功但外部访问不了。提前查一下端口占用情况或者直接指定一个冷门端口能少踩很多坑。5.4 externalTrafficPolicy为什么有的节点访问不通Service的externalTrafficPolicy有Cluster和Local两个选项。默认是Cluster意思是无论请求落在哪个节点都会把流量转发到任意节点上的Pod可能产生额外的一跳但源IP会被SNAT成节点IP。Local模式则要求流量只转发到本节点上的Pod保留客户端源IP但会带来负载不均某个节点没有对应Pod时落在它上面的请求会被直接丢弃。LoadBalancer类型的Service在云环境经常用到Local模式因为这些LB的健康检查只发给节点端口。如果后端Pod不在这个节点上健康检查就会失败。所以使用Local模式时最好配合Pod反亲和性让每个节点都有Pod或者接受偶尔的流量不均现象。调试时可以配合curl验证源IPcurl http://node-ip:node-port/some-route然后看后端业务日志里记录的客户端IP。如果显示的是节点IP而不是真实客户端IP说明当前externalTrafficPolicy是ClusterIP被伪装了。要拿到真实IP要么改成Local要么在Ingress层开启转发策略。5.5 多个Ingress Controller共存时的冲突处理一个项目里可能同时跑着nginx-ingress和traefik或者同时有内网入口和外网入口。这时必须明确指定每一条Ingress归属于哪个Controller。用ingressClassName就可以做到隔离。如果两条Ingress定义了相同host和path却属于不同Controller那么每个Controller各自处理自己的规则互不影响。但如果规则都指向同一个Controller而出现重复只有一条会生效另一条会报冲突或被忽略。如果希望某个Ingress只处理部分请求可以用Controller自带的scope限制参数比如只监听某个命名空间的Ingress资源。这类细节在大规模多团队共享集群时特别重要否则团队A创建的规则会莫名影响团队B的入口。5.6 服务治理层面的常见疏漏除了基础流量不通我还常看到治理层面的疏漏。第一个是忘记设置Pod的资源requests/limits导致Pod在流量高峰被节点驱逐Service后面一直有“重建-启动-被驱逐”的循环。第二个是没给Service配置合适的健康检查ReadinessProbe失败会让Endpoint被自动摘除有可能不是Service坏了是探针配置不合理。第三个是Pod内多容器时只暴露了一个端口实际业务进程监听的端口和探针不一致导致服务“看起来挂但实际在跑”。先看容器启动命令确认监听端口再用kubectl exec进容器本地访问能快速排除这类问题。6. 实操心得与后续演进6.1 我在项目里踩过的几个真实教训这几年在多个K8s集群里摸爬滚打有几个踩过的坑现在印象都很深。第一次给别人搭环境Service创建好了NodePort也暴露了但外网就是访问不了。最后排查发现云安全组压根没放行30080端口而我自己又一直用内网机器测试所以完全没察觉。因此我后来养成了条件反射外部访问不通时第一件事就是检查安全组和防火墙而不是反复看YAML。还有一次是Ingress的HTTPS证书过期用户访问时不报证书过期而是报连接被重置。原因是部分客户端不展示证书告警直接中断握手。现在我在生产环境会专门加一套证书过期监控cert-manager的证书对象自带状态配合Prometheus的cert-manager指标做告警非常稳。另一个教训是关于Ingress路径重写。曾经有个前端项目把静态资源放在/static下但Controller里rewrite-target配置成了/导致请求/static/app.js被重写后变成/app.js全站样式和脚本全丢。这个问题表面看是路由404实际是路径重写规则没有吃透。现在遇到rewrite配置我一定会在本地小流量验证后再切全量。6.2 从Ingress走向Gateway API如果现在才开始新项目或者新集群我建议目光可以稍微往Gateway API移一点。Gateway API是K8s社区正在推进的下一代流量管理API目标是取代Ingress并为Service Mesh、南北向和东西向流量提供统一标准。它把Ingress里由注解承担的高级能力如超时、重试、灰度、TLS策略变成标准字段可移植性更强。不过现阶段Ingress依然是生态和应用范围最广的方案。不要因为追新就盲目重写基础设施我自己的建议是小规模新集群可以尝试Gateway API但现有Ingress存量业务除非有明确痛点否则维持现状更稳。毕竟基础设施的稳定性比技术指标本身更重要。6.3 一些避免焦虑的实用建议最后分享一点个人习惯接触K8s越久越觉得网络这块是最容易出现“配置看起来都对但就是不通”的领域。遇到问题时别急着怀疑大前提先把链路拆开从DNS到Ingress Controller、再到Service、最后到Pod逐层用curl和kubectl验证。给每个步骤都做一次二分定位通常比盯着YAML猜测高效得多。生产环境的变更也要坚持“小范围验证再全量”Ingress的灰度能力不只是给业务用的运维自己调整路由规则时同样能派上用场。Kubernetes的Service与Ingress并不复杂复杂的是应用场景里各种细小的叠加。把这篇里的原理和排查思路理顺后你会发现集群里的流量负载、对外暴露、灰度发布这些事情其实都是可以一步一步推理出来的。