第 5 章:搞懂 K8s Service:从 ClusterIP 到负载均衡

第 5 章:搞懂 K8s Service:从 ClusterIP 到负载均衡

上一章讲了 StatefulSet。本章讲 Service:Pod 的 IP 是临时的,Service 负责为 Pod 提供一个稳定的访问入口。

5.1 为什么需要 Service

Pod 的 IP 不是固定的。重建一次 IP 就变一次,这带来几个问题:

  • 前端调用后端时,没法写死 Pod IP
  • 后端有多个副本时,需要自己做负载均衡
  • Pod 扩缩容或滚动更新时,调用方要重新发现后端

Service 就是来解决这些问题的。它给一组 Pod 提供:

  1. 稳定的虚拟 IP(ClusterIP)
  2. 负载均衡
  3. 基于标签的服务发现
markdown 复制代码
             Service: web (ClusterIP 10.96.12.34)
                 │
      ┌─────────┼─────────┐
      ▼         ▼         ▼
  Pod IP 1   Pod IP 2   Pod IP 3
  10.244.1.2 10.244.2.5 10.244.1.9

调用方只需要访问 http://web:80,流量会被自动转发到后端的某个 Pod。完整的流量链路如下:

5.2 Service 的四种类型

K8s 内置了四种 Service 类型,覆盖从内网访问到公网暴露的不同场景。

5.2.1 ClusterIP(默认)

只在集群内部暴露一个虚拟 IP,外部无法直接访问。这是最常用、最安全的类型。

yaml 复制代码
apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  type: ClusterIP
  selector:
    app: web
  ports:
  - port: 80
    targetPort: 8080
字段 含义
port Service 本身暴露的端口
targetPort 后端 Pod 容器里的端口
selector 选择哪些 Pod 作为后端

集群内其他 Pod 可以通过 http://web:80 访问,请求会被转发到某个 Pod 的 8080 端口。

5.2.2 NodePort

在集群每个节点上打开一个固定端口,外部可以通过 节点IP:端口 访问。NodePort 范围默认是 30000-32767。

yaml 复制代码
apiVersion: v1
kind: Service
metadata:
  name: web-nodeport
spec:
  type: NodePort
  selector:
    app: web
  ports:
  - port: 80
    targetPort: 8080
    nodePort: 30080

在我们的 3 节点集群中,创建后可以通过任意节点访问:

bash 复制代码
curl http://192.168.0.201:30080
curl http://192.168.0.202:30080
curl http://192.168.0.203:30080

注意:NodePort 只是入门调试时使用,生产环境通常不会直接暴露端口,而是配合 Ingress 使用。

5.2.3 LoadBalancer

由云厂商的负载均衡器(如阿里云 SLB、AWS ELB)分配一个公网 IP,自动把流量转发到 NodePort。K8s 本身不实现 LoadBalancer,需要云环境支持。

yaml 复制代码
apiVersion: v1
kind: Service
metadata:
  name: web-lb
spec:
  type: LoadBalancer
  selector:
    app: web
  ports:
  - port: 80
    targetPort: 8080

在裸金属环境或私有云中,通常用 MetalLB 来模拟 LoadBalancer 行为。

5.2.4 ExternalName

把 Service 映射到一个外部域名,而不是 Pod。它不做负载均衡,只是做一个 DNS 别名。

yaml 复制代码
apiVersion: v1
kind: Service
metadata:
  name: db-external
spec:
  type: ExternalName
  externalName: mysql.corp.example.com

集群内访问 db-external 会解析为 mysql.corp.example.com,常用于临时对接外部数据库。

5.3 Endpoints 和 EndpointSlice

Service 本身并不直接保存 Pod IP 列表。它通过 selector 找到匹配的 Pod,然后由控制器生成 Endpoints 对象。

bash 复制代码
$ kubectl get endpoints web
NAME   ENDPOINTS                                   AGE
web    10.244.1.2:8080,10.244.2.5:8080,10.244.1.9:8080   1h

Endpoints 就是后端真实 Pod 的 IP 列表。当 Pod 变化时,Endpoints 会自动更新。

大规模集群中,一个 Service 可能有成百上千个后端。Endpoints 对象太大,更新效率低。因此 K8s 引入了 EndpointSlice,把后端分成多个切片。

bash 复制代码
$ kubectl get endpointslices
NAME        ADDRESSTYPE   PORTS   ENDPOINTS
web-abcde   IPv4          8080    10.244.1.2,10.244.2.5,10.244.1.9

日常运维中,我们更常看 kubectl get endpoints,但 K8s 内部实际使用 EndpointSlice。

5.4 kube-proxy 与 iptables/ipvs

Service 的虚拟 IP 是怎么工作的?这背后是 kube-proxy 在维护转发规则。

kube-proxy 运行在每个节点上,监听 Service 和 EndpointSlice 变化,然后在本机配置转发规则。它支持三种模式:

模式 原理 适用场景
userspace 早期模式,性能差,已淘汰 不推荐
iptables 默认模式,通过 iptables 规则转发 中小规模
ipvs 内核态负载均衡,性能更好 大规模集群

iptables 模式下,访问 10.96.12.34:80 会命中一条 DNAT 规则,被随机转发到某个后端 Pod:

bash 复制代码
$ sudo iptables -t nat -L KUBE-SERVICES -n | grep web
KUBE-SVC-XXXXX  tcp  --  0.0.0.0/0  10.96.12.34  tcp dpt:80

ipvs 模式则会在内核里维护一个虚拟服务器列表,转发效率更高,支持的调度算法也更多(rr、wrr、lc 等)。

要查看当前 kube-proxy 模式:

bash 复制代码
$ kubectl get configmap kube-proxy -n kube-system -o yaml | grep mode
    mode: "iptables"

5.5 CoreDNS 与集群内 DNS

K8s 集群内有内置 DNS 服务,默认由 CoreDNS 提供。每个 Service 都会自动生成一个 DNS 记录:

xml 复制代码
<Service 名>.<namespace>.svc.cluster.local

同一个命名空间内,可以直接用 Service 名访问:

bash 复制代码
curl http://web

跨命名空间需要加上命名空间:

bash 复制代码
curl http://web.dev
# 或完整域名
curl http://web.dev.svc.cluster.local

Headless Service(即 clusterIP: None)的 DNS 解析方式不同,它会直接返回后端 Pod 的 IP 列表,而不是返回一个虚拟 IP。

验证 DNS 是否正常工作:

bash 复制代码
$ kubectl run -it --rm debug --image=busybox:1.28 --restart=Never -- nslookup web
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local

Name:      web
Address 1: 10.96.12.34

5.6 selector 与 label 不匹配排错

这是 Service 最常见的故障之一。创建了 Service,但访问时返回 Connection refusedno endpoints

常见原因:

  1. 标签不匹配 :Service 的 selector 和 Pod 的 labels 不一致
  2. 端口写错targetPort 和容器实际监听端口不一致
  3. Pod 没 Ready:只有 Ready 的 Pod 才会加入 Endpoints

排查步骤:

bash 复制代码
# 查看 Service 的 selector
$ kubectl get svc web -o yaml | grep selector -A 5

# 查看 Pod 的 labels
$ kubectl get pod -l app=web --show-labels

# 查看 Endpoints 是否为空
$ kubectl get endpoints web
NAME   ENDPOINTS   AGE
web    <none>      5m

如果 Endpoints 是空的,说明 selector 没匹配到 Pod,或者 Pod 都没 Ready。

5.7 实战:创建 Service 并验证

假设我们有一个 Deployment 运行了 3 个 Nginx 副本:

yaml 复制代码
# web-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
      - name: nginx
        image: nginx:alpine
        ports:
        - containerPort: 80

给它配一个 ClusterIP Service:

yaml 复制代码
# web-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  selector:
    app: web
  ports:
  - port: 80
    targetPort: 80

应用并验证:

bash 复制代码
$ kubectl apply -f web-deployment.yaml -f web-service.yaml

$ kubectl get svc web
NAME   TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
web    ClusterIP   10.96.12.34    <none>        80/TCP    1m

$ kubectl get endpoints web
NAME   ENDPOINTS                                    AGE
web    10.244.1.2:80,10.244.2.5:80,10.244.1.9:80    1m

在集群内 curl 测试:

bash 复制代码
$ kubectl run -it --rm test --image=nginx:alpine --restart=Never -- curl http://web

多次执行,流量会轮询到不同的后端 Pod。

5.8 NodePort 实战与访问

把 Service 类型改成 NodePort,并指定端口 30080:

yaml 复制代码
apiVersion: v1
kind: Service
metadata:
  name: web-nodeport
spec:
  type: NodePort
  selector:
    app: web
  ports:
  - port: 80
    targetPort: 80
    nodePort: 30080

创建后,在任意节点都能访问:

bash 复制代码
$ curl http://192.168.0.201:30080
$ curl http://192.168.0.202:30080
$ curl http://192.168.0.203:30080

注意:如果集群有防火墙或安全组,需要放行 30080 端口。

5.9 本章小结

  • Service 为 Pod 提供稳定的访问入口和负载均衡
  • 四种类型:ClusterIP、NodePort、LoadBalancer、ExternalName
  • Endpoints 和 EndpointSlice 保存后端 Pod 的 IP 列表
  • kube-proxy 通过 iptables 或 ipvs 实现转发
  • CoreDNS 提供集群内 DNS 解析,Service 名可直接访问
  • 排错核心:看 selector 是否匹配、targetPort 是否对、Endpoints 是否非空

5.10 课后练习

  1. 创建 3 副本 Nginx Deployment,并给它添加 ClusterIP Service,验证 Endpoints 是否包含 3 个 Pod IP。
  2. 把 Service 改成 NodePort,指定 30080,然后从集群外通过 192.168.0.201:30080 访问。
  3. 故意把 Service 的 selector 写成 app: webxxx,观察 Endpoints 是否变空,并理解原因。
  4. nslookup 测试 Service 的 DNS 解析,对比同一命名空间和跨命名空间的域名格式。
相关推荐
SLD_Allen2 小时前
DRA、MIG与GPU共享的云原生AI实践
人工智能·云原生
yunwei372 小时前
eBPF 入门实践教程第五十篇:使用 TCX Link 实现可组合的流量控制
linux·云原生·开源
qq_452396232 小时前
第一篇:《服务网格是什么?为什么云原生需要它?》
云原生
逐光老顽童2 小时前
第 2 章:彻底搞懂 K8s Pod——从 YAML 到调度全流程
分布式·云原生
逐光老顽童2 小时前
第 1 章:Kubernetes 核心概念总览——Pod、Deployment、Service 一次搞懂
分布式·云原生
花生了什么事o3 小时前
Docker 部署 SpringBoot:从镜像构建到服务器运行
服务器·spring boot·docker
Eloudy3 小时前
国内加速 docker pull 下载 docker images,特别是 nvidia 的cuda image
docker·gpu·rdma
Chief_fly3 小时前
ARM 麒麟系统服务器构建docker镜像
运维·服务器·docker
人间凡尔赛3 小时前
2026 云原生后端架构演进:事件驱动、虚拟线程与 AI Agent 内嵌,三驾马车如何重塑技术栈
后端·云原生·架构