1. 为什么服务发现是 K8s 里最容易被低估的一环刚接触 K8s 那会儿我对 Service 的理解长期停留在“给 Pod 套个固定 IP”这个层面。直到有一次在测试环境里前端 Pod 重启后一直报连接超时我查了半天才发现后端 Pod 的 IP 变了而前端配置里写的是硬编码 IP。那一刻我才真正意识到在 K8s 里Pod 的 IP 是“一次性”的随时会变而 Service 才是那个稳定的入口。Service 解决的核心问题就一个让一组动态变化的 Pod对外呈现一个稳定的访问入口。这个入口包含三样东西——固定的虚拟 IPClusterIP、固定的 DNS 名称、以及一套负载均衡规则。不管后面的 Pod 怎么扩缩容、怎么重启、怎么漂移调用方只需要认准这个入口就行。这篇文章适合谁看如果你已经能跑起一个 Deployment但对 Service 的几种类型总是记混或者遇到过“Service 创建了但访问不通”的情况那这篇内容就是写给你的。我会从设计思路讲到实操细节再到排查技巧尽量把我在实际项目里踩过的坑都摊开来说。2. Service 的整体设计与方案选型思路2.1 为什么不用 Pod IP 直接通信K8s 的 Pod 设计哲学是“ cattle, not pets ”意思是 Pod 是“牲口”不是“宠物”随时可以被替换。每次 Pod 重建IP 都会重新分配。如果服务之间直接用 Pod IP 通信那每次发布、每次扩缩容调用方都得跟着改配置这在微服务架构里是不可接受的。Service 的出现就是为了在 Pod 之上加一层抽象。它通过Label Selector动态匹配一组 Pod然后为这组 Pod 分配一个虚拟 IP。这个虚拟 IP 有几个特点它不属于任何网络接口只存在于 iptables 或 IPVS 规则里它在 Service 生命周期内保持不变它只在集群内部可路由。提示ClusterIP 是一个“虚拟”IP你在节点上用ping是 ping 不通的因为它没有绑定到任何真实网卡。这一点新手很容易误解。2.2 四种 Service 类型的适用场景K8s 提供了四种 Service 类型很多人一开始会搞混。我用一个表格来对比类型访问范围典型场景是否需要额外组件ClusterIP仅集群内部微服务间调用、数据库访问否NodePort集群外部通过节点 IP 端口测试环境暴露、临时对外否LoadBalancer集群外部通过云厂商 LB生产环境对外服务需要云厂商支持ExternalName集群内部映射到外部 DNS访问外部数据库、第三方 API否选型的逻辑其实很简单先问“谁要访问这个服务”。如果只有集群内的 Pod 访问ClusterIP 就够了如果需要从集群外访问再看有没有云厂商的 LB 支持有就用 LoadBalancer没有就用 NodePort 顶着。2.3 ClusterIP 的两种实现模式iptables 与 IPVSClusterIP 的负载均衡底层有两种实现方式这个在部署集群时就要决定后期切换成本不低。iptables 模式是默认方式。它的原理是在每个节点上写一堆 iptables 规则当流量到达 ClusterIP 时规则会把目标地址 DNAT 成某个 Pod IP。优点是成熟稳定不依赖额外内核模块缺点是规则数量随 Service 数量线性增长大规模集群下匹配效率会下降。IPVS 模式基于内核的 IPVS 模块使用哈希表存储转发规则支持多种调度算法轮询、最少连接、源地址哈希等。在 Service 数量超过 1000 的集群里IPVS 的性能优势非常明显。# 查看当前集群使用的代理模式 kubectl get configmap kube-proxy -n kube-system -o yaml | grep mode如果输出是空或者说明用的是 iptables如果是ipvs那就是 IPVS 模式。我在实际项目中只要集群规模预计会超过 500 个 Service都会在初始化时直接开启 IPVS。3. Service 核心细节解析与实操要点3.1 Label Selector 的匹配逻辑Service 和 Pod 之间的关联完全靠 Label Selector。这里有个容易踩的坑Selector 是“与”关系不是“或”关系。如果你写了两个 labelPod 必须同时满足这两个 label 才会被选中。apiVersion: v1 kind: Service metadata: name: my-service spec: selector: app: web tier: frontend ports: - protocol: TCP port: 80 targetPort: 8080上面这个 Service 只会选中同时带有appweb和tierfrontend的 Pod。如果你只想匹配appweb那就不要写tier。注意如果 Selector 写错了Service 的 Endpoints 列表会是空的访问时直接超时。排查时第一件事就是kubectl get endpoints service-name看有没有后端 Pod。3.2 port、targetPort、nodePort 三者的区别这三个端口是新手最容易混淆的地方我用一个生活类比来解释portService 自己的端口相当于“公司前台电话”。targetPortPod 容器实际监听的端口相当于“员工分机号”。nodePort节点上暴露的端口相当于“大楼门牌号”。外部流量访问 NodePort 时路径是节点IP:nodePort - Service:port - Pod:targetPort。ClusterIP 模式下没有 nodePort 这一层。spec: ports: - port: 80 # Service 端口 targetPort: 8080 # 容器端口 nodePort: 30080 # 节点端口范围默认 30000-32767targetPort 如果不写默认等于 port。但实际项目中我强烈建议显式写出 targetPort因为容器端口和 Service 端口往往不一致写清楚能避免很多沟通成本。3.3 Endpoints 与 EndpointSlice 的演进Service 本身不直接管理 Pod IP而是通过一个叫Endpoints的资源来记录后端 Pod 的地址列表。kube-controller-manager 里的 endpoints-controller 会监听 Service 和 Pod 的变化实时更新 Endpoints。但在大规模集群里一个 Service 可能对应上千个 PodEndpoints 对象会变得非常大任何一次更新都要全量推送性能很差。所以从 K8s 1.17 开始引入了EndpointSlice把一个 Endpoints 拆成多个切片每个切片最多 100 个端点更新时只推送变化的切片。# 查看 Endpoints kubectl get endpoints my-service # 查看 EndpointSlice1.21 默认启用 kubectl get endpointslices -l kubernetes.io/service-namemy-service我在一个管理 2000 Pod 的集群里实测过开启 EndpointSlice 后Service 变更的传播延迟从秒级降到了毫秒级kube-proxy 的 CPU 占用也明显下降。3.4 DNS 解析机制CoreDNS 的作用集群内的 Pod 访问 Service最常用的方式是用 DNS 名称。CoreDNS 是 K8s 默认的 DNS 服务器它为每个 Service 创建一条 A 记录格式是service-name.namespace.svc.cluster.local同一个 namespace 内的 Pod 可以直接用service-name访问跨 namespace 需要加上 namespace 前缀。# 在 Pod 内测试 DNS 解析 nslookup my-service nslookup my-service.default.svc.cluster.local提示如果 DNS 解析失败先检查 CoreDNS 的 Pod 是否正常运行再看 Pod 的/etc/resolv.conf里的 nameserver 是否指向 CoreDNS 的 ClusterIP。4. 完整实操从零搭建一个可访问的 Service4.1 创建 Deployment 作为后端先创建一个简单的 Deployment用 nginx 作为示例应用。apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deploy labels: app: nginx spec: replicas: 3 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.25 ports: - containerPort: 80kubectl apply -f nginx-deploy.yaml kubectl get pods -l appnginx -o wide这时候你应该能看到 3 个 Running 的 Pod每个都有自己的 IP。4.2 创建 ClusterIP ServiceapiVersion: v1 kind: Service metadata: name: nginx-svc spec: type: ClusterIP selector: app: nginx ports: - port: 80 targetPort: 80kubectl apply -f nginx-svc.yaml kubectl get svc nginx-svc kubectl get endpoints nginx-svcEndpoints 里应该能看到 3 个 Pod IP 和端口。如果为空说明 Selector 没匹配上回去检查 Pod 的 label。4.3 集群内验证访问启动一个临时 Pod 来测试kubectl run test-pod --imagebusybox:1.36 --rm -it --restartNever -- sh在 Pod 内执行wget -qO- http://nginx-svc wget -qO- http://nginx-svc.default.svc.cluster.local如果能看到 nginx 的欢迎页面说明 ClusterIP 工作正常。4.4 创建 NodePort Service 并外部访问apiVersion: v1 kind: Service metadata: name: nginx-nodeport spec: type: NodePort selector: app: nginx ports: - port: 80 targetPort: 80 nodePort: 30080kubectl apply -f nginx-nodeport.yaml kubectl get svc nginx-nodeport然后用任意节点的 IP 加 30080 端口访问。如果访问不通检查节点的防火墙规则是否放行了该端口。4.5 会话保持与负载均衡策略默认情况下Service 是轮询转发。如果需要会话保持可以设置sessionAffinityspec: sessionAffinity: ClientIP sessionAffinityConfig: clientIP: timeoutSeconds: 10800这样同一个客户端 IP 的请求会固定转发到同一个 Pod默认超时 3 小时。我在做需要登录态的服务时经常用这个配置但要注意它只基于 IP如果客户端在 NAT 后面所有用户会被当成同一个 IP。5. 常见问题与排查技巧实录5.1 Service 访问不通的排查路径这是我总结的一套排查顺序基本能覆盖 90% 的问题检查 Endpointskubectl get endpoints svc如果为空说明 Selector 没匹配到 Pod。检查 Pod 状态kubectl get pods -l selector确认 Pod 是 Running 且 Ready。检查端口确认 targetPort 和容器实际监听端口一致。检查 DNS在 Pod 内nslookup svc看能否解析。检查 kube-proxykubectl get pods -n kube-system | grep kube-proxy确认所有节点上的 kube-proxy 都正常。检查网络插件如果是 Calico、Flannel 等 CNI 插件确认其 Pod 状态正常。5.2 常见问题速查表现象可能原因解决方法Endpoints 为空Selector 不匹配检查 Pod label 和 Service selector访问超时防火墙未放行 nodePort开放节点防火墙端口DNS 解析失败CoreDNS 异常重启 CoreDNS Pod间歇性 502Pod 未 Ready 就被加入 Endpoints配置 readinessProbe只有部分节点能访问kube-proxy 未正常运行检查该节点 kube-proxy 日志ClusterIP ping 不通正常现象虚拟 IP 不响应 ICMP用 curl 或 wget 测试5.3 独家避坑技巧坑一readinessProbe 没配导致流量打到未就绪的 Pod。这是我在生产环境遇到过最频繁的问题。Pod 启动后进程虽然起来了但应用还没初始化完成这时候如果 Service 已经把流量转过来就会报错。解决办法是配置 readinessProbe只有探测通过后 Pod 才会被加入 Endpoints。坑二NodePort 端口冲突。如果你手动指定了 nodePort要确保这个端口在所有节点上都没被占用。我建议除非有特殊需求否则让 K8s 自动分配避免冲突。坑三跨 namespace 访问忘记加后缀。同一个 namespace 内可以直接用服务名但跨 namespace 必须用svc.namespace或者完整域名。我见过有同事排查了半天最后发现是 namespace 写错了。坑四sessionAffinity 在 NAT 环境下的陷阱。前面提到过ClientIP 模式下如果客户端在 NAT 后面所有请求会被当成同一个 IP导致负载均衡失效。这种场景下建议改用应用层的会话保持方案。6. 个人实操体会与扩展思路Service 这块内容我最大的体会是不要把它当成一个孤立的资源来看。它和 Deployment、Pod、CoreDNS、kube-proxy、CNI 插件都是联动的任何一个环节出问题都会表现为“访问不通”。所以排查时要有全局视角从 Endpoints 入手顺着链路一层层往下查。另外如果你的集群规模在增长建议尽早考虑这几点一是开启 IPVS 模式替代 iptables二是确保 EndpointSlice 已启用三是给关键服务配置 PodDisruptionBudget避免滚动更新时后端 Pod 全部不可用。后续如果要做更细粒度的流量管理比如灰度发布、流量镜像、熔断限流那就需要引入 Ingress Controller 或者 Service Mesh 了。但那是另一个话题Service 作为最基础的抽象层把这些搞扎实了上层的东西才好理解。
阅读完成 · 觉得有帮助?