返回首页

2.3 Service 与服务发现:让应用稳定被访问

在 Kubernetes 里,Pod 是可以随时被替换的:扩缩容会变、滚动更新会变、节点故障迁移也会变。也正因为如此,Pod IP 不应该被当成稳定访问入口。Service 的出现,就是为了给这批不断变化的 Pod 提供一个稳定的网络抽象层。::cite[455]

你可以把它理解成:Pod 是后端实例集合,Service 是对外暴露的“稳定门牌号”。客户端不需要知道后端 Pod 有几个、IP 是什么,只要访问 Service 即可。与此同时,Kubernetes 会通过 EndpointSlice 和集群 DNS 持续维护“Service 名称 → 当前可用后端”的映射关系。::cite[455]::cite[454]::cite[283]

2.3.1 为什么 Pod IP 不适合直接访问

Pod 天生是 临时性资源。在 Deployment 控制下,某个时刻运行的 Pod 与下一刻运行的 Pod,名称、IP、所在节点都可能不同。即使业务逻辑没变,后端实例集合也会变化。::cite[455]

这会带来三个直接问题:

  • 客户端无法记住一个长期稳定的 Pod IP。
  • 滚动更新期间,旧 Pod 下线、新 Pod 上线,访问目标不断变化。
  • 扩缩容后,后端数量变化,客户端自己维护地址列表很麻烦。::cite[455]

所以,Kubernetes 的标准做法不是“让客户端直接追 Pod”,而是:

  1. 标签选择器 选出一组 Pod。
  2. Service 给这组 Pod 提供稳定的入口。
  3. EndpointSlice 记录当前可用后端。
  4. DNS 把 Service 名称解析到可访问地址。::cite[455]::cite[454]::cite[283]

2.3.2 Service 是什么

Service 是 Kubernetes API 中的一个对象,用来暴露一组网络应用后端。一个 Service 定义了两件事:

  • 哪些 Pod 是它的后端(通常通过 selector)
  • 这些后端应该如何被访问::cite[455]

最常见的 Service 写法如下:

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app.kubernetes.io/name: MyApp
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9376

这段 YAML 的意思是:

  • 把带有 app.kubernetes.io/name=MyApp 标签的 Pod 作为后端。
  • 对外暴露 80 端口。
  • 实际流量转发到 Pod 的 9376 端口。::cite[455]

最小可运行示例:Deployment + ClusterIP Service

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-deployment
  labels:
    app: web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: nginx
          image: nginx:1.27
          ports:
            - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: web-service
spec:
  selector:
    app: web
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 80

实操步骤

  1. 创建 Deployment 与 Service。
kubectl apply -f web-service-demo.yaml
  1. 查看 Service。
kubectl get svc web-service
  1. 查看后端 EndpointSlice。
kubectl get endpointslice -l kubernetes.io/service-name=web-service
  1. 在集群内测试访问。
kubectl run curl-test --image=busybox:1.36 -it --rm -- sh
wget -qO- http://web-service

2.3.3 Service 的类型:ClusterIP、NodePort、LoadBalancer、ExternalName

Kubernetes 提供了几种常见 Service 类型,它们不是互相平级替代,而是逐层扩展访问能力。::cite[455]

类型 访问范围 典型用途
ClusterIP 仅集群内部 微服务间调用、默认选择
NodePort 通过任意节点 IP + 端口访问 测试环境、无云 LB 场景
LoadBalancer 通过云厂商或外部负载均衡访问 对外提供正式服务
ExternalName 映射到外部 DNS 名称 把集群内访问统一到外部服务域名

1)ClusterIP:默认类型

如果不显式指定 type,Service 默认就是 ClusterIP。它会分配一个集群内部虚拟 IP,只能在集群内部访问。::cite[455]

apiVersion: v1
kind: Service
metadata:
  name: api-clusterip
spec:
  type: ClusterIP
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 80

这类 Service 最适合:

  • 前后端服务在同一集群内通信
  • 只希望被 Ingress / Gateway 间接暴露
  • 不直接暴露给外部用户

2)NodePort:节点端口暴露

如果设置 type: NodePort,Kubernetes 会在每个节点上开放同一个端口,把请求转发到 Service。默认范围是 30000-32767。::cite[455]

apiVersion: v1
kind: Service
metadata:
  name: api-nodeport
spec:
  type: NodePort
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 80
      nodePort: 30080

访问方式类似:

http://<NodeIP>:30080

NodePort 常用于:

  • 本地实验
  • 裸机环境
  • 临时对外调试

但它一般不是生产环境最优入口,因为:

  • 需要暴露节点 IP
  • 端口范围固定且不够友好
  • 通常还需要自己再配一层负载均衡

3)LoadBalancer:对外暴露的标准选择

如果集群所在环境支持外部负载均衡器,type: LoadBalancer 会自动为 Service 准备一个外部入口。::cite[455]

apiVersion: v1
kind: Service
metadata:
  name: api-loadbalancer
spec:
  type: LoadBalancer
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 80

查看外部地址:

kubectl get svc api-loadbalancer

典型特点:

  • 对用户最友好
  • 常用于公网或内网统一入口
  • 实际创建 LB 的能力取决于云厂商或接入组件::cite[455]

4)ExternalName:映射外部域名

ExternalName 很特别,它不代理流量,也不选 Pod,而是直接在集群 DNS 里返回一个 CNAME,把 Service 名称映射到外部 DNS 名称。::cite[455]

apiVersion: v1
kind: Service
metadata:
  name: external-db
  namespace: default
spec:
  type: ExternalName
  externalName: my.database.example.com

适合场景:

  • 把外部数据库、外部 API 统一封装成集群内 Service 名称
  • 迁移期把“集群外服务”和“集群内服务”的调用方式统一

但要注意:它是 DNS 级别映射,不是代理,因此某些依赖 Host 头或 TLS 域名校验的协议,使用时要特别小心。::cite[455]

2.3.4 Service 与 EndpointSlice 的关系

很多人只会看 kubectl get svc,但真正决定 Service 后端是谁的,是 EndpointSlice。官方把 EndpointSlice 作为 Service 后端网络端点的分片对象,它是现在推荐使用的后端信息来源。::cite[454]::cite[455]

关系可以这样理解

  • Service:声明“我要暴露谁、怎么暴露”。
  • EndpointSlice:记录“现在真正可用的后端地址有哪些”。

当 Service 使用 selector 时,控制平面会自动扫描匹配的 Pod,并维护对应的 EndpointSlice。只要 Pod 变化,EndpointSlice 也会更新。::cite[455]::cite[454]

查看某个 Service 的 EndpointSlice

kubectl get endpointslice -l kubernetes.io/service-name=web-service

手动维护后端:无 selector 的 Service

Service 不一定非得选 Pod。没有 selector 时,Kubernetes 不会自动创建 EndpointSlice,你需要自己补一个。::cite[455]

下面是一个“手动指定后端地址”的完整示例:

apiVersion: v1
kind: Service
metadata:
  name: external-backend-service
spec:
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 9376
---
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
  name: external-backend-service-1
  labels:
    kubernetes.io/service-name: external-backend-service
    endpointslice.kubernetes.io/managed-by: staff
addressType: IPv4
ports:
  - name: http
    protocol: TCP
    port: 9376
endpoints:
  - addresses:
      - 10.4.5.6
  - addresses:
      - 10.1.2.3

这类写法常见于:

  • 集群外数据库或中间件接入
  • 跨集群服务代理
  • 迁移阶段的兼容接入::cite[455]

2.3.5 集群内 DNS 与服务发现机制

Kubernetes 支持两种主要的服务发现方式:

  • 环境变量
  • DNS

但在现代集群里,DNS 几乎总是首选。::cite[455]::cite[283]

1)环境变量发现

当 Pod 被创建时,kubelet 会把当前活跃 Service 的地址信息注入成环境变量,例如:

  • REDIS_PRIMARY_SERVICE_HOST
  • REDIS_PRIMARY_SERVICE_PORT

这种方式的限制是:Service 必须先创建,后启动客户端 Pod,否则环境变量不会自动补上。::cite[455]

2)DNS 发现:最常用方式

集群 DNS 会为 Service 自动创建 DNS 记录。官方文档说明,Pod 可以通过名字而不是 IP 去访问 Service。默认情况下,Pod 的 DNS 搜索路径会包含当前命名空间和集群默认域。::cite[283]

一个 Service 的完整域名一般长这样:

<service-name>.<namespace>.svc.cluster.local

例如:

web-service.default.svc.cluster.local

同命名空间下的简写

如果客户端 Pod 和目标 Service 在同一个命名空间里,通常直接写服务名就可以:

http://web-service

这是因为 Pod 的 DNS 搜索列表会优先补当前命名空间。::cite[283]

跨命名空间访问

如果要跨命名空间调用,建议至少写成:

http://web-service.backend

或者写完整 FQDN:

http://web-service.backend.svc.cluster.local

头服务(Headless Service)与 DNS

如果你把 .spec.clusterIP 设为 None,就创建了 Headless Service。此时 Kubernetes 不再给它分配虚拟 IP,而是直接通过 DNS 返回后端 Pod 的地址集合。::cite[455]::cite[283]

这类 Service 常用于:

  • StatefulSet
  • 需要直接感知每个后端实例的场景
  • 自己做客户端负载均衡的组件

示例:

apiVersion: v1
kind: Service
metadata:
  name: web-headless
spec:
  clusterIP: None
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 80

2.3.6 实操:验证 DNS 服务发现

启动测试容器

kubectl run dns-test --image=busybox:1.36 -it --rm -- sh

在容器中执行 DNS 查询

nslookup web-service
nslookup web-service.default.svc.cluster.local

访问 Service

wget -qO- http://web-service

验证 Headless Service

nslookup web-headless

观察重点:普通 Service 通常返回一个稳定入口;Headless Service 更倾向于暴露后端 Pod 地址集合。::cite[455]::cite[283]

2.3.7 常见注意事项

1)Service 选不中 Pod,通常是标签问题

最常见故障不是网络,而是 selector 和 Pod labels 对不上。先看:

kubectl get pods --show-labels
kubectl describe svc web-service

2)Readiness Probe 会影响 Service 后端列表

如果 Pod 的 readinessProbe 失败,Kubernetes 会把该 Pod 从 Service 对应的后端地址中移除。也就是说,“Pod 还活着”并不等于“Pod 还能接流量”。::cite[373]::cite[455]

3)无 selector 的 Service 不会自动生成 EndpointSlice

这是非常容易忽略的点。没写 selector,就需要你自己创建 EndpointSlice。::cite[455]

4)ExternalName 不等于反向代理

它只是返回 CNAME,不会做转发,也不会帮你处理应用层协议兼容问题。::cite[455]

5)尽量优先使用 DNS,而不是环境变量

环境变量方式对创建顺序敏感;DNS 更灵活,也更符合云原生服务发现模式。::cite[455]::cite[283]

2.3.8 本章小结

这一章最重要的理解有五点:

  1. Pod IP 是易变的,不能作为稳定访问入口。
  2. Service 提供稳定入口,EndpointSlice 维护实时后端。
  3. ClusterIP 解决集群内访问,NodePort / LoadBalancer 解决对外访问,ExternalName 解决外部域名映射。
  4. 集群 DNS 是 Kubernetes 服务发现的主流方式。
  5. 标签选择器、Readiness Probe、EndpointSlice 三者共同决定“流量到底会打到谁”。

掌握 Service 后,你会发现 Kubernetes 网络模型一下子清晰很多:

  • Pod 负责跑实例
  • Deployment 负责管副本和发布
  • Service 负责稳定入口
  • DNS 负责名字解析

这几层拼起来,才是微服务在 Kubernetes 中可持续运转的基础。


📝 版权声明:本文为原创技术博客,转载请注明出处。

如文章中存在错误或不准确之处,欢迎在评论区指正,感谢您的阅读与支持!

上一篇

4.5 NetworkPolicy

下一篇

6.1:RBAC 权限体系