14. Service

Service

为什么需要 Service?

在 Kubernetes 中:

  • Pod 的生命周期短暂:一旦被销毁,重建后 IP 会变化。
  • 其他应用如果直接依赖 Pod IP,就会出现“找不到服务”的问题。
  • 在传统架构中,我们用 Nginx + Consul / ZooKeeper / etcd 做服务发现与负载均衡。

在 Kubernetes 中,这个功能由 Service 统一提供。

Service = 一组 Pod 的抽象 + 访问策略 + 负载均衡入口

Service 根据 selector 自动维护后端 Pod 列表(Endpoints),从而实现前端与后端的解耦。

ubernetes 中三种 IP

IP 类型 说明 作用范围
Node IP 节点物理机(主机)的 IP 集群内外可访问
Pod IP 每个 Pod 的独立 IP 集群内部可访问,Pod 重建后会变化
Cluster IP Service 的虚拟 IP 集群内部可访问,不随 Pod 变化

Service 的使用

有一组 Pod,标签为 app=myapp,对外暴露端口 8080:

# myapp-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80

ClusterIP 类型:

# myapp-svc.yaml
apiVersion: v1
kind: Service
metadata:
  name: myservice
spec:
  selector:
    app: myapp
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
    name: myapp-http

NodePort Service:

# myapp-nodeport.yaml
apiVersion: v1
kind: Service
metadata:
  name: myapp-nodeport
spec:
  type: NodePort
  selector:
    app: myapp
  ports:
  - port: 80
    targetPort: 80

作用:

  • ClusterIP 会自动分配
  • selector 匹配的 Pod 会自动加入 Endpoints

验证:

# 创建应用
ubuntu@ubuntu:~/example/service$ kubectl apply -f ./myapp-deploy.yaml 
deployment.apps/myapp created
ubuntu@ubuntu:~/example/service$ kubectl get pods -n default -o wide
NAME                     READY   STATUS    RESTARTS   AGE   IP             NODE    NOMINATED NODE   READINESS GATES
myapp-5c8d88cd7d-58xhs   1/1     Running   0          12s   10.244.2.189   node1   <none>           <none>
myapp-5c8d88cd7d-gjrm8   1/1     Running   0          12s   10.244.1.164   node2   <none>           <none>
myapp-5c8d88cd7d-jj748   1/1     Running   0          12s   10.244.2.188   node1   <none>           <none>
# 创建svc
ubuntu@ubuntu:~/example/service$ kubectl apply -f ./myapp-svc.yaml 
service/myservice created
ubuntu@ubuntu:~/example/service$ kubectl get svc -n default
NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.96.0.1      <none>        443/TCP   75d
myservice    ClusterIP   10.107.155.4   <none>        80/TCP    18s
# 查看endPoint
ubuntu@ubuntu:~/example/service$ kubectl get endpoints myservice
Warning: v1 Endpoints is deprecated in v1.33+; use discovery.k8s.io/v1 EndpointSlice
NAME        ENDPOINTS                                               AGE
myservice   10.244.1.164:8080,10.244.2.188:8080,10.244.2.189:8080   93s
# 启动临时应用访问测试
ubuntu@ubuntu:~$ kubectl run curl --image=busybox -it --rm -- sh
# If you don't see a command prompt, try pressing enter.
/ # wget -qO- http://myservice
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
/ # exit
Session ended, resume using 'kubectl attach curl -c curl -i -t' command when the pod is running
pod "curl" deleted
# 可以开另外一个终端查看日志
ubuntu@ubuntu:~/example/service$ kubectl logs -f myapp-5c8d88cd7d-58xhs
10.244.2.190 - - [18/Nov/2025:05:45:29 +0000] "GET / HTTP/1.1" 200 615 "-" "Wget" "-"
# 可以观察到负载均衡的效果



# 验证endpoint
ubuntu@ubuntu:~/example/service$ kubectl apply -f ./myapp-nodeport.yaml
service/myapp-nodeport created
# 查看端口:
ubuntu@ubuntu:~/example/service$ kubectl get svc myapp-nodeport 
NAME             TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
myapp-nodeport   NodePort   10.106.46.35   <none>        80:31770/TCP   63s
# 打开浏览器 输入 http://192.168.236.101:31770/
# 这个IP是 物理机的IP,  NodePort 的作用就是 在每个节点上暴露同一个端口, 所以 101,102,103 都能访问。

Service 的内部机制:kube-proxy

每个 Node 上都运行 kube-proxy,它负责监听 Service 和 Endpoints,并同步规则。

1)iptables 模式(默认,适合中小规模)

kube-proxy:

  • 监听 Service 和 Endpoints 的变化
  • 生成 iptables 规则(DNAT)
  • 默认随机选择一个后端 Pod

特点:

  • 成熟稳定
  • 但大量规则时性能下降

2)ipvs 模式(推荐,适合大规模)
优点:

  • 内核级负载均衡
  • 使用哈希表,性能远高于 iptables
  • 支持更多调度策略,如:
    • rr(轮询)
    • lc(最少连接)
    • sh(源地址哈希)
    • sed(最短期望延迟)

开启方式(kube-proxy 配置):

--proxy-mode=ipvs
--ipvs-scheduler=rr

Session Affinity(会话保持)

Service 提供简单的基于 ClientIP 的会话亲和性:

spec:
  sessionAffinity: ClientIP

用于保证同一个客户端永远访问同一个 Pod。

Service 类型

  1. ClusterIP(默认)

仅在集群内部访问:

ServiceIP:Port -> PodIP:TargetPort
  1. NodePort

在每个 Node 上开放一个端口(30000~32767),通过该端口访问 Service。

示例:

apiVersion: v1
kind: Service
metadata:
  name: myservice
spec:
  selector:
    app: myapp
  type: NodePort
  ports:
  - port: 80
    targetPort: 80

访问方式:

NodeIP:NodePort
  1. LoadBalancer

云厂商自动创建一个公网 LB,与 NodePort 结合使用。

  1. ExternalName [例如云服务的中间件可以用这种]

用于集群外服务,返回 CNAME,不走 kube-proxy。

示例:

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

使用 Endpoints 引入外部服务

当使用 clusterIP=None 且与 Service 同名时,可以自定义 Endpoints:

Service:

apiVersion: v1
kind: Service
metadata:
  name: etcd-k8s
spec:
  clusterIP: None
  ports:
  - port: 2379

Endpoints:

apiVersion: v1
kind: Endpoints
metadata:
  name: etcd-k8s
subsets:
- addresses:
  - ip: 10.151.30.57
  ports:
  - port: 2379

Service 与 externalIPs

externalIPs 是 Service 的一个可选属性,用于暴露服务到集群外的指定 IP。

当某个外部 IP 可以路由到集群 Node 时,可以通过 Service 的 externalIPs 将流量路由到对应 Service。

注意:

  • externalIPs 不由 Kubernetes 管理,需要集群管理员保证外部 IP 的路由正确。
  • Service 仍然通过 Endpoints 或 EndpointSlice 将流量转发到后端 Pod。
# my-app-external.yaml
apiVersion: v1
kind: Service
metadata:
  name: my-app-external
spec:
  selector:
    app: myapp
  ports:
  - port: 80
    targetPort: 80
  externalIPs:
  - 192.168.236.101 # 集群上某个node

验证:

# 删掉之前的
ubuntu@ubuntu:~/example/service$ kubectl delete -f ./myapp-nodeport.yaml 
service "myapp-nodeport" deleted
ubuntu@ubuntu:~/example/service$ kubectl apply -f ./my-app-external.yaml 
service/my-app-external created
# 查看信息
ubuntu@ubuntu:~/example/service$ kubectl get svc my-app-external
NAME              TYPE        CLUSTER-IP     EXTERNAL-IP     PORT(S)   AGE
my-app-external   ClusterIP   10.108.2.244   192.168.1.101   80/TCP    74s
# 浏览器测试访问
# 只有  http://192.168.236.101 可以访问,其他均不可以

Endpoints (废弃)

Warning: v1 Endpoints is deprecated in v1.33+; use discovery.k8s.io/v1 EndpointSlice

Endpoints 是 Kubernetes 的资源对象,用来存储一个 Service 对应的后端 Pod 的 IP 地址及端口。

自动创建:当 Service 有 selector 且 Pod 就绪时,Kubernetes 会自动创建 Endpoints。

更新机制:

  • Pod 被创建/销毁/重启 → Endpoints 自动同步更新。
  • Pod readiness probe 未就绪 → 不会加入 Endpoints。

结构

subsets:
  - addresses:
      - ip: 10.244.1.5
    ports:
      - port: 80

不足:

  • 单个 Endpoints 对象有最大数量限制(默认 1000 个端点)。
  • Pod 数量巨大时(上千),更新 Pod 会导致 Endpoints 对象很大 → 集群性能下降。

EndpointSlice

和endpoint 一样,一般情况不需要手动创建,创建 EndpointSlice 主要出现在 Service 没有 selector 或者要代理外部服务 的场景。这里主要聊一下概念。

EndpointSlice 是 Endpoints 的可扩展替代方案,设计目标:

  • 缓解大规模 Pod 的性能问题。
  • 支持拓扑信息(节点、可用区等)。
  • 支持就绪、服务中、终止状态。

特点:

  • 默认每个 EndpointSlice 最多 100 个端点(可通过 kube-controller-manager --max-endpoints-per-slice 调整)。
  • Pod 数量多时,会创建多个 EndpointSlice → 每个更新只影响相关 slice → 降低网络和 API 负载。
  • kube-proxy 会 watch 所有 EndpointSlice 来更新 iptables 或 IPVS 规则。

端点状态字段:

  • ready:Pod 就绪状态。
  • serving:Pod 服务状态,不考虑终止。
  • terminating:Pod 终止中状态。

标签管理:

endpointslice.kubernetes.io/managed-by 标识控制器管理的对象(默认:endpointslice-controller.k8s.io)。

地址类型:

IPv4、IPv6、FQDN

示例:

apiVersion: v1
kind: EndpointSlice
metadata:
  name: httpbin-xyz
  labels:
    endpointslice.kubernetes.io/managed-by: endpointslice-controller.k8s.io
addressType: IPv4
ports:
  - name: http
    port: 80
endpoints:
  - addresses:
      - 10.244.1.5
    conditions:
      ready: true
      serving: true
      terminating: false
spec:
  selector:
    app: myapp
  ports:
  - port: 80
    targetPort: 80

Service → Endpoints / EndpointSlice 的关系

Service (ClusterIP / NodePort / LoadBalancer)
        │ selector
        ▼
   Endpoints 或 EndpointSlice
        │ 各个 Pod IP + 端口
        ▼
       Pod

核心作用:Service 抽象 Pod 集合,对客户端隐藏 Pod 的 IP 变动。

流量转发:kube-proxy 通过 iptables 或 IPVS 将请求分发到 Pod。

可扩展性

  • 少量 Pod → Endpoints 足够
  • 大量 Pod → EndpointSlice 更高效

小结 / 建议

资源 用途 适用场景
Service 流量入口,抽象 Pod 集合 集群内部访问 / 外部访问
Endpoints 存储 Service 对应 Pod 的 IP(单对象) Pod 数量较少 (<1000),频繁更新量不大
EndpointSlice 分片存储 Pod IP,可扩展、支持拓扑信息 大规模集群、多副本 Pod (>1000)
externalIPs 外部 IP 直接访问 Service 需指定外部路由的场景
posted @ 2025-11-18 16:18  beamsoflight  阅读(16)  评论(0)    收藏  举报