K8s学习12--- Service详解Ingress介绍和使用先问个扎心的问题你用了 Kubernetes 一段时间之后是不是也遇到过这种情况——Pod 怎么访问都行Service 勉强也会写但只要业务要暴露到公网或者想按域名、按路径分流量就开始发懵了我在刚接触 K8s 的那几个月就是这个状态。NodePort 开了一堆端口安全组规则改来改去最后发现流量入口乱成一锅粥。那时候才意识到Service 不只是“给 Pod 提供一个稳定 IP”那么简单。它背后是一整套流量转发机制而真正把外部流量优雅地导进集群、还能按规则分发到不同服务的是另一个核心组件——Ingress。这篇文章我就想把自己重新梳理这部分知识时的一些心得写出来包括 Service 的底层逻辑、Ingress 的原理和实操配置以及那些网上教程一般不讲的排查技巧和经验。目标是让看这篇文章的你能一次性把流量入口这件事想明白而不是死记一堆 YAML。1. 为什么说 Service 和 Ingress 是 K8s 流量的双引擎1.1 没有 ServicePod 就像没有固定电话的员工先说说 Service 解决的根本问题。K8s 里 Pod 是随时可能被调度、重启、扩缩容的每次变化它的 IP 都会变。如果你直接让外部请求访问 Pod IP那等于你每天都要重新记一次同事的手机号完全没法玩。Service 做的就是这件事——提供一个长期稳定的虚拟 IPClusterIP并且把请求转发给背后的真实 Pod。这种“稳定入口 动态后端”的机制是整个集群网络的基础。我在理解 Service 的时候脑子里的类比是“公司前台”。前台电话是固定的你打过去不用管接电话的到底是谁可能是销售部小王也可能是售后部小李。Service 就是那个前台它背后的 Endpoints 就是当前在岗的 Pod 列表。你只要记住前台的号码Pod 怎么换人都影响不到外面。那 Service 是怎么做到“知道后面有哪些 Pod 在岗”的呢靠的是 Label Selector。你定义 Service 的时候写上selector: app: nginx它就会自动筛选出所有带这个标签的 Pod并把它们的 IP 和端口维护成一份实时更新的后端列表。这个过程是由控制平面配合 kube-proxy 完成的虽然你只是写了一段 YAML但背后已经有一整套机制在运转。1.2 Ingress 解决的问题是 Service 给不了的Service 解决了“访问谁”的问题但你自己用过就知道服务一多问题就来了。比如你集群里有三个应用一个是用户前端、一个是用户后台、一个是管理后台。如果用 NodePort 暴露每个服务都会占用一个几千的随机端口你开防火墙规则时得逐个放行如果用 LoadBalancer每个服务都要买一个 LB 实例成本直接起飞。更重要的是你没法做到“同一个端口、不同域名转到不同服务”。比如用户前端和后端都想要 80 端口Service 层面做不到这种基于域名和路径的灵活路由。Ingress 的意义就在这里。它像是集群门口的“保安分流员”你告诉它请求api.example.com转到 Service-A请求admin.example.com转到 Service-B它就能照着规则把流量分得明明白白。而且所有规则都通过一份 YAML 维护更新、回滚都非常方便作为入口再合适不过。我自己的结论是Service 负责“稳定寻址”Ingress 负责“智能路由”。两者不是叠加的关系而是分工的关系。理解到这一步后面所有细节都能串起来了。2. Service 的四种类型到底该怎么选2.1 ClusterIP 和它的底层转发真相ClusterIP 是 Service 的默认类型它给 Service 分配一个集群内部的虚拟 IP只能在集群内部访问。如果你还没有对外暴露需求只想让集群里的 Pod 之间互相通信用 ClusterIP 就够了。但有意思的是ClusterIP 并不是真实存在的物理 IP它更像一个“虚拟身份证”。真正干活的是 kube-proxy 写在节点上的 iptables 规则或 IPVS 规则。请求到达这个 ClusterIP 时节点内核会按照规则把包随机转发到某个后端 Pod 上。我实测下来IPVS 模式在高并发场景下的表现比 iptables 模式好很多。因为 IPVS 采用了更高效的数据结构和哈希算法调度开销更小而且支持更多负载均衡算法比如轮询、最少连接等。如果你要给业务集群做调优建议把 kube-proxy 的 mode 从 iptables 切到 ipvs。这一步在大多数云厂商的托管集群里已经是默认项但如果是自建的就需要你确认一下。2.2 NodePort、LoadBalancer 和 ExternalName 的选择逻辑为什么不能让所有服务都用 NodePort因为 NodePort 会在每台节点上开一个端口默认范围是 30000-32767。服务一多端口分配和管理都是噩梦。而且 NodePort 本身没有健康检查、没有智能负载均衡它只是把流量“硬塞”给某个后端谈不上高级路由。从上手难度来看NodePort 最适合本地开发调试LoadBalancer 适合云上单应用暴露ExternalName 则是个特殊玩法——它不指向任何 Pod而是将 Service 映射为一个外部的 DNS 域名。比如你需要让集群内 Pod 访问外部数据库但又不想写死 IP就可以用 ExternalName 做一层 CNAME 转发。举个例子如果你的数据库在另一套环境域名是db.internal.example.com你可以定义一个 ExternalName Service集群里直接访问my-db这个名字kube-proxy 会自动把请求 CNAME 到db.internal.example.com。这样做的好处是代码里不需要存实际地址环境切换时只要改 Service 定义就行。3. Ingress 的核心原理和运作逻辑3.1 Ingress Controller、IngressClass 和 Ingress 三者别搞混这是初学 Ingress 时最容易混淆的三兄弟。Ingress 本身只是一份声明式规则它告诉集群“什么域名/路径转发到什么 Service”。但这份规则本身不会执行真正干活的是 Ingress Controller。Ingress Controller 是一个常驻的应用程序它会持续监听 Ingress 资源的变化然后把规则转换成自身负载均衡器的配置比如 Nginx 的 server 块配置。没有 Controller你写一万个 Ingress 也没用。而 IngressClass 是用来区分不同 Ingress Controller 的。比如你的集群里同时跑着 Nginx Ingress 和 Traefik每个 Ingress 资源就可以通过ingressClassName指定让谁来处理。如果只有一个 Controller你可以在 ingress-controller 启动参数里加--ingress-classnginx然后所有 Ingress 默认匹配到它。这三者的关系我的理解是Ingress 是“需求规格书”IngressClass 是“指定哪个施工队接单”Ingress Controller 是“真正施工的工程队”。弄明白这一点后面配置就顺理成章了。3.2 一个请求从域名到 Pod 的完整链路我习惯把 Ingress 的链路拆成五个节点这样排查问题时思路特别清晰用户在浏览器输入app.example.comDNS 解析到 Ingress Controller 对外的入口地址可以是 LB、NodePort 或者云上的负载均衡器。请求到达 Ingress Controller 的监听端口通常是 80 或 443。Controller 根据请求的 Host 头和路径去匹配当前监听的所有 Ingress 规则。命中了某条规则后Controller 将请求反向代理到对应的 Service 的 ClusterIP 上。Service 再通过 kube-proxy 的转发规则把请求最终发送给某个后端 Pod。这条链路里最容易被忽略的是 DNS 解析这第一步。很多人写好了 Ingress 规则本机 curl 也能通但外部就是访问不了一查发现是域名还没有解析到集群入口的地址。记住一句话Ingress 只管 4 层以下的请求转发DNS 归属你的运维体系管。4. Ingress 实操从安装到配置一个完整的路由规则4.1 以某主流 Ingress Controller 为例做部署我这里用的示例是 Nginx Ingress Controller它也是目前生产环境里使用最广的选择之一。官方提供了多种安装方式最简单的是直接用kubectl apply -f拉取官方部署清单但我一般会做两处调整一是把镜像版本固定成自己验证过的一个稳定版本二是把副本数调成至少 2 个保证高可用。kubectl apply -f https://raw.githubusercontent.com/nginxinc/kubernetes-ingress/main/deployments/static/mandatory.yaml注意如果安装之后迟迟看不到 ingress-nginx 的 Service 拿到外部地址先别急着怀疑配置。很多 Kubernetes 集群要专门给云上 LB 类型的 Service 装一个额外的组件比如某云环境下的 cloud-controller-manager。如果是裸金属环境就得考虑把 ingress-nginx 的 Service 类型改为 NodePort再在前面叠一层自建的负载均衡或 DNS 轮询。4.2 编写第一条 Ingress 规则假设部署一个前端服务和一个后端服务域名分别是app.example.com和api.example.com。你只需要维护以下这份 Ingress 资源apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: example-ingress namespace: prod spec: ingressClassName: nginx rules: - host: app.example.com http: paths: - path: / pathType: Prefix backend: service: name: frontend-service port: number: 80 - host: api.example.com http: paths: - path: / pathType: Prefix backend: service: name: backend-service port: number: 8080这里有几个关键点我一开始踩过坑必须提醒你pathType有两种常用值Prefix和Exact。Prefix是按路径前缀匹配/api会匹配/api/v1、/api/v2Exact则是精确匹配多以根路径或特殊路由为主。如果你想让/foo只匹配/foo那就要用Exact。networking.k8s.io/v1里的写法跟前些版本相比已经变了老版本的kubernetes.io/ingress.class注解在 v1 里要改成spec.ingressClassName不然规则可能不生效。服务端口填的必须是 Service 的port而不是后端 Pod 的targetPort。好多新手在这里填错规则写好了却转发不进去。4.3 给 Ingress 加 HTTPS、加路径重写、加安全头实际生产中裸 HTTP 是不行的。一般至少要配 HTTPS。有两种方式一种是在边缘网关层做终止一种是让 Ingress Controller 直接担任 TLS 终止点。我通常采用第二种直接在 Ingress 里配置证书apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: tls-example namespace: prod spec: tls: - hosts: - app.example.com secretName: app-tls-secret rules: - host: app.example.com http: paths: - path: / pathType: Prefix backend: service: name: frontend-service port: number: 80证书本身得先存成 Secretkubectl create secret tls app-tls-secret --certapp.crt --keyapp.key -n prod如果你用的是自动签发方案比如某云厂商的托管证书服务那么在 Ingress 注解里指定证书 ID 就行不需要手动维护 Secret。再说路径重写。这是个非常高频的需求前端服务收到的请求路径里不希望带着 API 前缀而后端接口定义又确实有前缀。比如请求/api/users希望后端实际收到的是/users那就在 Ingress 上加一条注解metadata: annotations: nginx.ingress.kubernetes.io/rewrite-target: /$2配合路径规则spec: rules: - host: api.example.com http: paths: - path: /api(/|$)(.*) pathType: ImplementationSpecific backend: service: name: backend-service port: number: 8080这条注解的意思是把匹配到的请求重写之后再转发给后端。我第一次用的时候没写/$2结果后端收到的还是带/api的路径排查了挺久才发现问题出在这。安全头也值得顺手加上。比如强制跳转 HTTPSnginx.ingress.kubernetes.io/ssl-redirect: true限制单 IP 访问频率nginx.ingress.kubernetes.io/limit-rps: 5这些注解看着不起眼但在安全评审和限流场景里就是实打实的刚需。5. 常见问题与排查技巧实录5.1 Service 无法访问的经典问题我先说一个出现频率最高的场景Service 指向的 Pod 明明在运行kubectl get endpoints却显示为空或者访问不通。九成是 Label Selector 和 Pod 的标签对不上。比如 Service 写的是appnginxPod 的标签却是namenginx那 Service 的 Endpoints 会一直空着。排查命令很简单三板斧kubectl get svc -n namespace kubectl get endpoints -n namespace kubectl describe svc service-name -n namespace你先看 Endpoints 有没有 IP没有的话再kubectl get pod --show-labels -n namespace对比标签。这个流程走完九成问题都能定位。还有一种情况是 ClusterIP 本身访问不到不是 Service 定义错这时候要看 kube-proxy 是否在正常工作kubectl logs -n kube-system -l k8s-appkube-proxy如果 kube-proxy 好着再看节点上的 iptables 规则iptables -L -n -t nat | grep cluster-ip没有规则说明 kube-proxy 没能同步 Service多半是 apiserver 到 kube-proxy 的连接有问题或者 kube-proxy 权限被改了。这类问题在自建集群里特别常见。5.2 Ingress 规则不生效先别怀疑人生我在生产环境里遇到过三次 Ingress 规则“突然不生效”其中两次最后发现是 Ingress 资源根本没被 Controller 加载。原因是资源的ingressClassName和 Controller 启动参数里指定的--ingress-class不一致。检查方法很直接kubectl get ingress -n prod -o yaml kubectl logs -n ingress-nginx ingress-controller-podController 不会为不匹配的 Ingress 打印明显错误只在日志里静默跳过。这个坑非常隐蔽一定要先核对 class 名称。第二个常见问题是改了 Ingress 配置后半天不生效。一般的原因是 Controller 默认每 30 秒同步一次配置。你不要趴在那儿等直接用kubectl apply -f new-ingress.yaml kubectl rollout restart deployment -n ingress-nginx ingress-nginx-controller如果确实需要快速生效重启 Controller 是最立竿见影的。但这不是长久之计生产环境建议把 Controller 的syncPeriod调短一点。第三个问题是 HTTPS 证书更新后客户端还是拿到旧证书。这是因为 Ingress Controller 把证书缓存到了本地更新 Secret 后需要 reload 才生效。Nginx Ingress 的默认行为里有一个nginx.ingress.kubernetes.io/proxy-next-upstream之类的配置但证书缓存属于另一套逻辑。我一般更新证书后直接删掉 Controller Pod让它重新消费一次 Secret比等它自动 reload 快得多。5.3 会话保持失效原来不是 Ingress 的锅做过支付类或者登录类业务的都知道最怕 Session 频繁跳变。某天线上突然很多用户反馈“明明登录了一刷新又要重新登录”第一反应是不是 Nginx Ingress 的 sticky session 没配置好其实不一定。如果你的请求经过了多个副本的 Ingress Controller比如水平扩展了 3 个 Pod而每个 Controller 实例之间的会话保持配置不一致或者刚才提到的 IPVS / iptables 转发链路有多个代理回源 IP 一直在变都会导致会话失效。解决思路有两层一是在 Ingress 上开启 sticky sessionnginx.ingress.kubernetes.io/affinity: cookie二是在应用层把 Session 存储迁移到 Redis 或其他分布式存储让任何副本都能读到会话数据。我踩过的坑是只开了 cookie 亲和却发现用户访问时第一次请求到了 Pod-A第二次因为 DNS 轮询或 LB 分发又到了 Pod-B。最终发现是 Nginx Ingress 的 cookie 范围默认作用在域名上而前端域名和服务端域名不是同一个导致 cookie 没有被携带。把 cookie 的域名配置调成二级域名统一之后问题才彻底解决。5.4 常见问题速查表现象可能原因排查/解决Service 的 Endpoints 为空标签选择器不匹配或者 Pod 未就绪kubectl get endpoints对比标签查看 Pod readinessClusterIP 访问不通kube-proxy 未同步或 iptables 规则丢失查 kube-proxy 日志iptables -L -n -t nat检查规则Ingress 规则不生效IngressClass 名不匹配或 Controller 未加载核对ingressClassName看 Controller 日志证书不生效Secret 内容不对或 Controller 缓存未刷新kubectl describe secret验证重启 Controller路径重写不生效rewrite-target 注解没写对检查注解路径表达式确认ImplementationSpecific路径类型会话保持失效多副本 Ingress Controller 策略不一致统一注解配置应用层迁移分布式会话HTTPS 跳转失败没有配置 ssl-redirect 注解或 TLS 未配置添加nginx.ingress.kubernetes.io/ssl-redirect: true收到 502/504后端 Service 端口没通或 Pod 负载过高kubectl exec进 Pod 自测端口查后端应用日志6. 生产环境里还要想清楚的几个进阶话题6.1 Ingress Controller 本身怎么保证高可用很多人写 Ingress 规则写得飞起但没想过 Ingress Controller 要是挂了整个集群入口就瘫了。Controller 本来也是跑在集群里的一组 Pod可以被 PDBPodDisruptionBudget保护并且通过反亲和性把副本尽量分布到不同节点上。我一般会在安装 Ingress Controller 时做三件事人肉确保入口稳定把副本数设为至少 3多副本均衡承载。给 Controller 的 Pod 加上topologySpreadConstraints让它尽量均匀分布到不同可用区。在前面套一个云负载均衡器并把后端指向 Ingress Controller 的 NodePort 或 LB Service。负载均衡器负责做健康检查一旦某个节点 Controller 挂了自动把流量摘掉。如果你用的裸金属自建集群没有云 LB 可以用那可以用 Keepalived 给多个 Controller 节点漂移一个 VIP由 VIP 对外提供服务。这样至少不会因为单点故障导致入口全断。6.2 多环境复用同一套 Ingress 规则怎么隔离我见过很多团队搞“一套 Ingress 写到头”结果开发环境、测试环境、生产环境混在一个集群里域名冲突、证书混乱出了事故都不知道改的是哪份配置。这个问题还不能单纯靠加命名空间来解决因为 Ingress 规则里的 host 是全局的。我的做法是维护一个变量化的模板用不同的域名后缀来区分环境。比如开发环境dev.example.com测试环境test.example.com生产环境www.example.com然后用 CI/CD 模板引擎在部署流程里动态渲染 Ingress 的 host。这样不同环境虽然共用同一套 Ingress 逻辑但实际互不干扰也不用担心误改生产规则。还有一个细节是证书管理。环境多了之后开发环境的证书很多是自签的可以选择性跳过校验但生产环境必须强制校验这种差异建议放到 CI/CD 流水线里去控制而不是写在应用代码里。6.3 流量路径上的安全加固Ingress 暴露到公网之后攻击面也跟着变大。我先说三个靠谱且容易落地的点TSL 证书过期管理自动化。不要等监控报警才发现证书过期。可以用 cert-manager 之类的工具自动签发和续期配合 Ingress 注解自动挂载证书整个流程可以做到全自动。开启请求体大小限制。有些后端接口没设限攻击者往费内存的接口疯狂上传大文件很容易把应用实例打挂。通过 Ingress 注解就能限制nginx.ingress.kubernetes.io/proxy-body-size: 10m拒绝非法 Host 头。有些扫描器会带着随机域名来探测如果不加限制可能被引导到默认后端如果你配置过默认后端的话或者产生大量无意义的日志流量。可以在 Controller 的配置里设置allow-backend-server-header: false同时不要轻易配置没有 host 的默认路由。我在多个团队的集群里都遇到过因为默认后端被刷爆的情况最好别给没有 host 匹配的请求留下的自动转发通道宁可让它返回 404。6.4 灰度发布和流量治理Ingress 本身不带灰度能力但它可以配合 Service 实现简单的“基于版本权重”的切换。思路就是线上 Service 和后端 Pod 打同样的标签新的版本先起一套独立标签的 Deployment再用 Ingress 的路径规则把一小部分流量切到新版本。比如在 Nginx Ingress 里可以通过自定义负载均衡比如使用 LUA 脚本实现按请求头流量切分或者干脆在 Ingress 规则上用多个 path 指向不同版本。但这种方式肉眼运维成本高长期并不推荐。如果团队短期想要一个快速灰度方案又不想引入额外的服务网格比如 Linkerd 或 Istio可以试试用 Flagger 这类渐进式交付工具它会把 Ingress Controller 作为流量管理器直接实现按百分比发布和自动回滚。我个人的建议是如果业务对灰度发布是强需求比如每天发版好几次的互联网业务直接上服务网格更省心但如果只是偶尔做一次小版本测试用 Ingress 配合 Deployment 副本数调整也够用了。7. 从 Service 到 Ingress我学到的思路总结个人体会回想我自己摸爬滚打过来的路径最值钱的不是记住了多少 YAML 字段而是建立了一套关于流量的思考方式。Service 和 Ingress 本质上是一层又一层的抽象每一层都有自己的职责边界。当你拿到一个“访问不通”的问题时先别急着去翻文档沿着请求链路一层层排查从 DNS 到 Ingress Controller再到 Service最后到 Pod问题必然落在其中一个环节上。还有一个深切的体会K8s 的文档和网络资源虽然多但有个很大的问题就是信息过时得特别快。当年版本还是extensions/v1beta1的 Ingress 写法今天看到的多半已经废弃了。每次搭建环境时最好先去官方文档核对当前版本里的 API 版本和字段定义再抄网上的配置。学基础设施这块永远不能犯“我上次用的版本就是标准”的傲慢。如果你正在学 K8s 的 Service 和 Ingress我的建议是不要停留在能跑通阶段试着在你的测试环境里模拟这些场景多个服务共享同一域名前缀、某个路径需要特别重写、证书过期自动更换、Ingress Controller 单点故障模拟。跑通这几个场景之后你对集群网络的理解会有一个质的飞跃。这个内容后续还可以继续往外扩展比如基于 Istio 的服务网格做更细粒度的流量治理或者结合可观测性工具追踪一条请求从入口到容器的完整链路但万变不离其宗底层这层流量转发的逻辑搞透它比什么都管用。