新闻详情

新闻详情

首页 / 资讯中心 / 详情

【K8S 运维实战】09-服务暴露Service与Ingress

发布时间:2026/7/23 9:04:27
【K8S 运维实战】09-服务暴露Service与Ingress
服务暴露:Service、Ingress 与流量治理一句话定位:ClusterIP/NodePort/LoadBalancer 怎么选 Ingress 生产配置 金丝雀实操。写在前面新人最常问的两个问题:我的 Service 有 ClusterIP 但访问不通,为什么?“和Ingress 配了但 404,到底哪层出了问题?”。这两个问题背后,是对 K8s 流量链路缺少整体认知。K8s 的流量从外到内经过的层次:外部 DNS → LoadBalancer(云厂商) → Ingress Controller → Service(虚拟 IP) → Endpoints(后端 Pod IP)。每一层都可能出问题,排查时必须知道卡在哪一层。这篇从 Service 四种类型讲起,覆盖 kube-proxy 的 iptables/ipvs 模式、Ingress 控制器选型、生产级 Ingress-Nginx 配置、金丝雀与 A/B 测试。读完你应该能画出整个流量链路图。核心问题ClusterIP / NodePort / LoadBalancer / ExternalName 什么时候用哪个?kube-proxy 的 iptables 模式和 ipvs 模式有什么区别?怎么切换?Endpoints 和 EndpointSlice 是什么,为什么 Service 访问不通要先看它?Ingress-Nginx / Traefik / APISIX 怎么选?生产怎么配?怎么用 Ingress 做金丝雀和 A/B 测试?一、原理剖析1.1 Service 的本质:一个稳定的虚拟 IPService 的核心作用是给一组 Pod(后端)提供一个稳定的访问入口。Pod 是会生灭的,IP 会变,但 Service 的 ClusterIP 和 DNS 名是固定的:┌──────────────────────────────────────────────────────────────┐ │ Service webapp (ClusterIP: 10.96.10.10) │ │ DNS: webapp.default.svc.cluster.local │ │ selector: appwebapp │ │ │ │ Endpoints(自动维护,跟随 Pod 变化): │ │ - 10.244.1.5:8080 ← Pod webapp-xxx-aaa │ │ - 10.244.2.7:8080 ← Pod webapp-xxx-bbb │ │ - 10.244.3.9:8080 ← Pod webapp-xxx-ccc │ └──────────────────────────────────────────────────────────────┘ 客户端访问 webapp.default.svc.cluster.local:8080 → kube-proxy 在每个节点上维护的规则把流量 NAT 到某个 Pod IP → 实际命中 10.244.2.7:8080Service 本身不转发流量,它只是配置(kube-proxy 读这些配置在节点上写 iptables/ipvs 规则)。所以Service 访问不通时,要查的是 Endpoints 有没有后端、kube-proxy 规则有没有生效。1.2 四种 Service 类型┌──────────────────┬───────────────────────────────────────────────┐ │ ClusterIP │ 集群内访问的虚拟 IP(默认)。外部不可达。 │ │ (默认) │ 适合:内部服务互调 │ ├──────────────────┼───────────────────────────────────────────────┤ │ NodePort │ 在每个节点开一个端口(30000-32767)转发到 Service│ │ │ 适合:测试、或自建 LB 后端 │ ├──────────────────┼───────────────────────────────────────────────┤ │ LoadBalancer │ 自动调用云厂商 API 创建外部 LB(阿里云/AWS/GCP)│ │ │ 适合:云上对外暴露服务 │ ├──────────────────┼───────────────────────────────────────────────┤ │ ExternalName │ CNAME 到外部域名,不走 Pod,纯 DNS 重定向 │ │ │ 适合:把外部服务伪装成集群内服务 │ └──────────────────┴───────────────────────────────────────────────┘层级关系(外层包含内层):也直接转发到LoadBalancer外部 IP 端口NodePort节点 IP:30000-32767ClusterIP集群内虚拟 IPEndpoints后端 Pod IP:PortLoadBalancer 类型的 Service 会同时创建 NodePort 和 ClusterIP,云厂商的 LB 把流量打到节点的 NodePort,再转到 ClusterIP,再到 Pod。1.3 kube-proxy:iptables vs ipvskube-proxy 有两种主流模式(1.30 默认 ipvs,但很多托管集群仍用 iptables):iptables 模式: - 每个 Service 在 iptables 里生成多条规则 - 随机选后端(iptables 的 --random) - 规则数随 Service×Pod 数线性增长(O(n)) - 大集群(1000 Service)性能下降明显 ipvs 模式: - 用内核 IPVS 模块,基于 hash 表 - 支持多种调度:rr(轮询)/lc(最少连接)/sh(源地址哈希) - 规则查找 O(1),大集群性能稳定 - 适合:超过 1000 Service 或需要复杂调度算法切换方法:# 看 kube-proxy 当前模式kubectl get pods-nkube-system-lk8s-appkube-proxy kubectl logs-nkube-system kube-proxy-xxx|grepUsing iptables\|Using ipvs# 改 ConfigMapkubectl edit cm kube-proxy-nkube-system# 找到 config.conf,修改:# mode: ipvs# ipvs:# scheduler: rr # 可选 rr|lc|dh|sh|sed|wlc# 重启 kube-proxykubectl rollout restart ds kube-proxy-nkube-system1.4 Endpoints 与 EndpointSliceService 通过 selector 选 Pod,但真正记录哪些 Pod 能收流量的是 Endpoints(老)或 EndpointSlice(1.21 默认):Service spec.selector ──选── Pod labels ↓ kube-controller-manager 自动维护: Endpoints(老,单对象,所有 IP 一个列表) EndpointSlice(新,每 100 个 IP 一片,可扩展) ↓ 内容: addresses: [10.244.1.5, 10.244.2.7, 10.244.3.9] ports: [{name: http, port: 8080}] conditions: readytrue ← Pod Ready 才进 Endpoints排错关键:Service 访问不通时,先看 Endpoints 有没有内容:kubectl get endpointssvc-nns# ENDPOINTS 列如果是 none,说明没有 Pod 被 selector 选中,或 Pod 都不 Ready1.5 Ingress 与 Ingress ControllerService 是 L4(TCP/UDP),Ingress 是 L7(HTTP/HTTPS)。Ingress 是 API 对象(规则),Ingress Controller 是真正执行转发的 Pod(nginx/traefik/haproxy):外部流量 ↓ ┌─────────────────────────────┐ │ Ingress Controller(nginx) │ ← 一个 LoadBalancer 类型的 Service 把流量引到这 │ - 读取 Ingress 规则 │ │ - 按域名/路径转发 │ └─────────────────────────────┘ ↓ 按规则转发到 Service A Service B Service C (backend) (backend) (backend)主流 Ingress Controller 对比:控制器优势劣势适用场景Ingress-Nginx社区最主流、文档全、annotation 丰富大集群 reload 性能一般通用 Web/APITraefik配置热更新无 reload、自动 ACME高级功能靠 CRD(IngressRoute)云原生优先APISIX插件生态强、动态路由、限流熔断学习曲线陡API 网关场景Higress阿里开源、Wasm 插件、国内文档好社区较小国内云原生生产里 Ingress-Nginx 是最稳妥的选择——生态成熟、问题好查、annotation 生态丰富。二、实战操作2.1 环境准备kubectl create ns traffic-demo# 安装 Ingress-Nginx(裸金属/本地用 NodePort 模式)helm repoaddingress-nginx https://kubernetes.github.io/ingress-nginx helm repo update helminstallingress-nginx ingress-nginx/ingress-nginx\--namespaceingress-nginx\--create-namespace\--setcontroller.service.typeNodePort\--setcontroller.service.nodePorts.http30080\--setcontroller.service.nodePorts.https30443\--version4.10.1# 等待就绪kubectlwait--namespaceingress-nginx\--forconditionready pod\--selectorapp.kubernetes.io/instanceingress-nginx\--timeout120s2.2 Service 四种类型实战# svc-demo.yaml---apiVersion:v1kind:Deploymentmetadata:name:webappnamespace:traffic-demospec:replicas:3selector:matchLabels:app:webapptemplate:metadata:labels:app:webappspec:containers:-name:appimage:nginxdemos/hello:plain-textports:-containerPort:80readinessProbe:httpGet:path:/port:80---# ClusterIP(默认)apiVersion:v1kind:Servicemetadata:name:webapp-cipnamespace:traffic-demospec:type:ClusterIPselector:app:webappports:-port:80targetPort:80---# NodePort(每个节点开 31080)apiVersion:v1kind:Servicemetadata:name:webapp-npnamespace:traffic-demospec:type:NodePortselector:app:webappports:-port:80targetPort:80nodePort:31080---# Headless(无 ClusterIP,直接返回 Pod IP)apiVersion:v1kind:Servicemetadata:name:webapp-headlessnamespace:traffic-demospec:clusterIP:None# Headless 标志selector:app:webappports:-port:80---# ExternalName(CNAME 到外部域名)apiVersion:v1kind:Servicemetadata:name:external-dbnamespace:traffic-demospec:type:ExternalNameexternalName:mydb.rds.aliyuncs.comkubectl apply-fsvc-demo.yaml# 验证 Endpointskubectl get endpoints-ntraffic-demo# Headless 的 DNS 行为(返回所有 Pod IP)kubectl run dns-test--rm-it--imagebusybox:1.36\--restartNever --nslookupwebapp-headless.traffic-demo.svc.cluster.local# ExternalName 的 DNS 行为(返回 CNAME)kubectl run dns-test--rm-it--imagebusybox:1.36\--restartNever --nslookupexternal-db.traffic-demo.svc.cluster.local2.3 Ingress-Nginx 生产配置模板# ingress-prod.yaml---apiVersion:networking.k8s.io/v1kind:Ingressmetadata:name:webapp-ingressnamespace:traffic-demoannotations:kubernetes.io/ingress.class:nginxcert-manager.io/cluster-issuer:letsencrypt-prod# 上游超时(长连接/慢接口)nginx.ingress.kubernetes.io/proxy-connect-timeout:10nginx.ingress.kubernetes.io/proxy-send-timeout:60nginx.ingress.kubernetes.io/proxy-read-timeout:60# 请求体大小限制(文件上传)nginx.ingress.kubernetes.io/proxy-body-size:10m# 限流(每秒 10 个请求,突发 20)nginx.ingress.kubernetes.io/limit-rps:10nginx.ingress.kubernetes.io/limit-burst:20# 跨域nginx.ingress.kubernetes.io/enable-cors:truenginx.ingress.kubernetes.io/cors-allow-origin:https://app.example.com# 客户端真实 IP 透传nginx.ingress.kubernetes.io/use-forwarded-headers:true# 上游 keepalive 连接复用nginx.ingress.kubernetes.io/upstream-keepalive-connections:100nginx.ingress.kubernetes.io/upstream-keepalive-timeout:60# WebSocket 支持nginx.ingress.kubernetes.io/configuration-snippet:|proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade;spec:ingressClassName:nginxtls:-hosts:-webapp.example.comsecretName:webapp-tlsrules:-host:webapp.example.comhttp:paths:-path:/pathType:Prefixbackend:service:name:webapp-cipport:number:80-path:/apipathType:Prefixbackend:service:name:api-svcport:number:8080kubectl apply-fingress-prod.yaml kubectl get ingress-ntraffic-demo# 测试(假设节点 IP 是 192.168.1.10)curl-HHost: webapp.example.comhttp://192.168.1.10:30080/2.4 金丝雀发布(基于 Ingress-Nginx)Ingress-Nginx 用canaryannotation 实现金丝雀,不需要额外部署:# canary-ingress.yaml---# 主 Ingress(100% 流量)apiVersion:networking.k8s.io/v1kind:Ingressmetadata:name:webapp-mainnamespace:traffic-demospec:ingressClassName:nginxrules:-host:webapp.example.comhttp:paths:-path:/pathType:Prefixbackend:service:name:webapp-stableport:number:80---# 金丝雀 Ingress(10% 流量)apiVersion:networking.k8s.io/v1kind:Ingressmetadata:name:webapp-canarynamespace:traffic-demoannotations:nginx.ingress.kubernetes.io/canary:truenginx.ingress.kubernetes.io/canary-weight:10# 10% 流量到 canaryspec:ingressClassName:nginxrules:-host:webapp.example.comhttp:paths:-path:/pathType:Prefixbackend:service:name:webapp-canaryport:number:80kubectl apply-fcanary-ingress.yaml# 推进金丝雀:30% → 50% → 100%kubectl annotate ingress webapp-canary-ntraffic-demo\nginx.ingress.kubernetes.io/canary-weight30--overwrite# 验证:发 100 个请求,看流量分布foriin$(seq1100);docurl-s-HHost: webapp.example.comhttp://192.168.1.10:30080/|grepServerdone|sort|uniq-c2.5 A/B 测试(基于 Header/Cookie)不用权重,按用户特征分流:# ab-test-ingress.yamlapiVersion:networking.k8s.io/v1kind:Ingressmetadata:name:webapp-abnamespace:traffic-demoannotations:nginx.ingress.kubernetes.io/canary:true# 按 Header 分流:带 X-Canary: true 的走 canarynginx.ingress.kubernetes.io/canary-by-header:X-Canarynginx.ingress.kubernetes.io/canary-by-header-value:true# 也可以按 Cookie:# nginx.ingress.kubernetes.io/canary-by-cookie: canaryspec:ingressClassName:nginxrules:-host:webapp.example.comhttp:paths:-path:/pathType:Prefixbackend:service:name:webapp-canaryport:number:80kubectl apply-fab-test-ingress.yaml# 测试:带 Header 的走 canarycurl-HHost: webapp.example.com-HX-Canary: truehttp://192.168.1.10:30080/# 不带 Header 的走 stablecurl-HHost: webapp.example.comhttp://192.168.1.10:30080/2.6 Service 访问不通排查脚本#!/bin/bash# svc-debug.sh service-name namespaceSVC$1NS${2:-default}echo 1. Service 是否存在 kubectl get svc$SVC-n$NS-owideecho 2. Endpoints 有没有后端 EP$(kubectl get endpoints$SVC-n$NS-ojsonpath{.subsets[*].addresses[*].ip}2/dev/null)if[-z$EP];thenecho❌ Endpoints 为空!检查 selector 和 Pod 是否 Readyecho--- Service selector ---kubectl get svc$SVC-n$NS-ojsonpath{.spec.selector}{\n}echo--- 匹配的 Pod(看是否 Ready)---SELECTOR$(kubectl get svc$SVC-n$NS-ojsonpath{.spec.selector}|jq-rto_entries|map(\(.key)\(.value))|join(,)) kubectl get pods -n $NS -l $SELECTOR -o wide else echo ✅ Endpoints: $EP fi echo 3. Service 的 ClusterIP CIP$(kubectl get svc $SVC -n $NS -o jsonpath{.spec.clusterIP})echoClusterIP:$CIP(ping 不通是正常的,要 curl 端口)echo 4. 从一个 Pod 内部测试 kubectl run svc-test--rm-it--imagecurlimages/curl:8.6.0\--restartNever --\curl-svhttp://${SVC}.${NS}.svc.cluster.local21|head-20chmodx svc-debug.sh ./svc-debug.sh webapp-cip traffic-demo三、踩坑与排查踩坑 1:Service 有 ClusterIP 但 curl 不通,Endpoints 是空的现象:kubectl get svc有 ClusterIP,但curl clusterIP:port超时,kubectl get endpoints显示none。原因:Service 的 selector 和 Pod 的 label 对不上(拼写错、命名空间错)。或 Pod 都不 Ready(readinessProbe 不过的 Pod 不会进 Endpoints)。解决:# 对比 selectorkubectl get svcsvc-ojsonpath{.spec.selector}kubectl get pods --show-labels|grepapp# 看 Pod 是否 Readykubectl get pods-lselector# Ready0/1 的 Pod 不进 Endpoints踩坑 2:Headless Service 解析出多个 IP,客户端只连第一个现象:Headless Service 用于 StatefulSet,DNS 返回 3 个 IP,但客户端只连第一个。原因:这是 Headless 的正常行为——DNS 返回所有 Pod IP,但客户端的 DNS 解析默认取第一个。轮询要靠客户端实现(比如 gRPC 的 round_robin 策略)。解决:对 StatefulSet 用 Pod 名 DNS,而不是 Service DNS:# 直接访问某个 Pod(稳定)webapp-0.webapp-headless.traffic-demo.svc.cluster.local踩坑 3:Ingress 配置了但 404现象:Ingress 资源创建了,访问域名返回 404。原因:Ingress 没指定ingressClassName,或和 controller 不匹配;Host 头不对(Ingress 按 host 路由,curl 时没带-H Host: xxx);Ingress-Nginx controller 还没同步(看 controller 日志的 “sync” 动作)。解决:# 确认 ingressClassNamekubectl get ingressname-ojsonpath{.spec.ingressClassName}# 确认 controller 存在kubectl get ingressclass# 看 controller 是否同步了kubectl logs-ningress-nginx-lapp.kubernetes.io/nameingress-nginx|grep-isync\|error# 测试时一定要带 Hostcurl-HHost: webapp.example.comhttp://controller-ip/踩坑 4:NodePort 在云厂商 LB 后面,健康检查失败现象:阿里云/腾讯云的 SLB 后端节点健康检查失败,NodePort 实际是通的。原因:云厂商 LB 健康检查默认打节点的某个端口(比如 80),但你的 NodePort 是 31080。需要配置 LB 的健康检查端口为 NodePort,或在节点上跑一个健康检查服务。解决:用LoadBalancer类型 Service 代替 NodePort,让 controller 自动配置健康检查;或在 LB 控制台手动改健康检查端口。踩坑 5:ExternalName Service 不生效,还是访问到旧地址现象:改了 ExternalName 的externalName,应用还连旧地址。原因:CoreDNS 的缓存。ExternalName 是 CNAME 记录,客户端 DNS 缓存可能很长;CoreDNS 本身也有缓存。解决:# 看 CoreDNS 配置的缓存时间kubectl get cm coredns-nkube-system-ojsonpath{.data.Corefile}|grepcache# 默认 cache 30,即缓存 30s# 应用层可能有自己的 DNS 缓存(Java 的 JVM 默认永久缓存!):# Java 应用要加 -Dnetworkaddress.cache.ttl30踩坑 6:ipvs 模式下 kube-proxy 规则不生效现象:切到 ipvs 模式后,某些 Service 访问不通。原因:节点内核没加载 ipvs 模块,或 scheduler 配置不对。解决:# 在节点上检查 ipvs 模块lsmod|grep-Eip_vs|nf_conntrack# 没有就加载:modprobe ip_vs ip_vs_rr ip_vs_wrr ip_vs_sh nf_conntrack# 永久生效:cat/etc/modules-load.d/ipvs.confEOF ip_vs ip_vs_rr ip_vs_wrr ip_vs_sh nf_conntrack EOF# 看 kube-proxy 是否正常kubectl logs-nkube-system kube-proxy-xxx|grep-ierror\|ipvs四、最佳实践Service 选型内部服务一律 ClusterIP:不要用 NodePort 暴露内部服务,端口管理混乱。对外服务用 LoadBalancer(云上)或 Ingress(裸金属):NodePort 仅用于测试。StatefulSet 用 Headless Service:稳定 DNS 名直达 Pod。ExternalName 谨慎用:DNS 缓存可能导致切换延迟,重要场景用 EndpointSlice 手动维护外部 IP。kube-proxy 模式大集群切 ipvs:超过 500 Service 就该切,性能差异明显。ipvs scheduler 用 rr 就够:最少连接(lc)在长连接场景反而可能不均。切换前在测试环境验证:有些应用对连接复用敏感,切换可能短时抖动。Ingress 配置生产必加超时和限流:proxy-read-timeout和limit-rps是保护后端的关键。TLS 用 cert-manager 自动签发:别手动管理证书。客户端真实 IP 透传:开启use-forwarded-headers,否则后端拿到的都是 controller IP。配置变更要 reload,大集群注意性能:Ingress-Nginx 配置变更会触发 nginx reload,大集群用--enable-dynamic-configuration减少 reload。金丝雀与 A/B金丝雀用canary-weight:5% → 25% → 50% → 100%,逐步推进。A/B 测试用canary-by-header:按用户特征分流,比按权重更可控。金丝雀要监控错误率:canary 和 stable 的错误率对比,差异大立即回滚。五、小结K8s 流量治理是分层的:Service 管 L4(虚拟 IP 负载均衡),Ingress 管 L7(域名/路径路由)。理解这两层的分工,排错时就能快速定位卡在哪一层。Service 访问不通的排查主线是:看 Endpoints 有没有后端 → 看 kube-proxy 规则 → 看节点网络。Ingress 配置不生效的主线是:看 ingressClassName → 看 controller 同步日志 → 带 Host 头测试。生产 Ingress 推荐用 Ingress-Nginx,它的 annotation 生态(超时、限流、金丝雀)是所有 controller 里最丰富的。金丝雀发布优先用canary-weight,A/B 测试用canary-by-header,这两种方式都比双 Deployment 更轻量。下一篇讲配置与密钥,我们会把 ConfigMap 热更新的坑和 Secret 的安全问题讲透。思考题一个 ClusterIP 类型的 Service,客户端在 Pod A 里curl service-name能通,在 Pod B 里不通。可能的原因有哪些?Ingress-Nginx 的金丝雀(canary-weight)和用两个 Deployment 一个 Service 的金丝雀,实现机制有什么本质区别?各有什么优劣?ipvs 模式的schedulerlc(最少连接),在什么场景下比rr(轮询)更合适?什么场景下反而更差?延伸阅读Service 官方文档Ingress 官方文档Ingress-Nginx 用户指南kube-proxy iptables vs ipvs 对比EndpointSlice 设计
网站建设 高端定制 企业官网