Qingular

Endpoints 与服务发现

·CKAk8s练习

CKA Domain 3 — Kubernetes Endpoints/EndpointSlice 资源管理与服务发现机制

← 返回 CKA 练习目录

概述

Endpoints 是 Kubernetes 中跟踪 Service 后端 Pod IP 地址的资源。当 Service 有 Selector 时,K8s 自动创建和更新 Endpoints。EndpointSlice 是新一代的替代方案(K8s v1.21+),解决大规模集群中单个 Endpoints 资源的性能问题。


一、Endpoints 资源

自动管理

当 Service 定义 Selector 时,K8s 自动创建并维护 Endpoints:

apiVersion: v1
kind: Service
metadata:
  name: my-svc
spec:
  selector:
    app: nginx
  ports:
    - port: 80
      targetPort: 80
---
# K8s 自动创建的 Endpoints
apiVersion: v1
kind: Endpoints
metadata:
  name: my-svc          # 名称必须与 Service 一致
subsets:
  - addresses:
      - ip: 10.244.1.5
        nodeName: node-1
        targetRef:
          kind: Pod
          name: nginx-pod-1
          namespace: default
      - ip: 10.244.2.7
        nodeName: node-2
        targetRef:
          kind: Pod
          name: nginx-pod-2
          namespace: default
    ports:
      - port: 80
        protocol: TCP
# 查看 Endpoints
kubectl get endpoints
kubectl get endpoints my-svc
kubectl describe endpoints my-svc

# Endpoints 名称与 Service 名称一一对应
# 如果一个 Service 没有 Endpoints,说明没有匹配到 Pod
kubectl get svc,ep

手动创建 Endpoints(指向外部服务)

当需要 Service 指向外部服务(如外部数据库)时,可以创建无 Selector 的 Service 并手动绑定 Endpoints。

# 1. 创建无 Selector 的 Service
apiVersion: v1
kind: Service
metadata:
  name: external-mysql
spec:
  ports:
    - port: 3306
      targetPort: 3306
---
# 2. 手动创建同名 Endpoints
apiVersion: v1
kind: Endpoints
metadata:
  name: external-mysql    # 名称必须与 Service 一致
subsets:
  - addresses:
      - ip: 192.168.1.100    # 外部数据库的 IP
      - ip: 192.168.1.101
    ports:
      - port: 3306
# 验证
kubectl get svc external-mysql
kubectl get endpoints external-mysql

# 从 Pod 中测试
kubectl run test-db --image=busybox:1.28 --rm -it --restart=Never -- telnet external-mysql 3306

二、EndpointSlice(K8s v1.21+)

EndpointSlice 是 Endpoints 的替代方案,解决了单个 Endpoints 对象在大型集群中的性能瓶颈问题。

核心特性

  • 自动分片:每个 EndpointSlice 默认最多包含 100 个端点
  • 地址类型:支持 IPv4、IPv6、FQDN
  • 更高效的更新:只更新变化的分片而非整个 Endpoints
# 查看 EndpointSlice
kubectl get endpointslices
kubectl get endpointslices -o yaml

# 查看与 Service 的关联
kubectl describe endpointslices <slice-name>

EndpointSlice YAML 示例

apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
  name: my-svc-abc123
  labels:
    kubernetes.io/service-name: my-svc    # 必须标签,标识所属 Service
addressType: IPv4
endpoints:
  - addresses:
      - 10.244.1.5
    conditions:
      ready: true
      serving: true
      terminating: false
    nodeName: node-1
    targetRef:
      kind: Pod
      name: nginx-pod-1
      namespace: default
  - addresses:
      - 10.244.2.7
    conditions:
      ready: true
      serving: true
      terminating: false
    nodeName: node-2
  - addresses:
      - 10.244.3.9
    conditions:
      ready: false        # Pod 未就绪
      serving: false
      terminating: false
ports:
  - name: http
    protocol: TCP
    port: 80

Endpoints vs EndpointSlice

特性EndpointsEndpointSlice
API 版本v1discovery.k8s.io/v1
GA 阶段自始 GAK8s v1.21+ GA
单对象容量无限制(整体大对象)最多 100 个端点/分片
更新效率全量更新增量更新
多地址类型仅 IPv4IPv4、IPv6、FQDN
大规模集群性能下降推荐使用

三、无 Selector 的 Service 类型

除了手动绑定 Endpoints,还有以下无 Selector 的 Service 变体:

1. ExternalName Service

通过 DNS CNAME 将 Service 映射到外部域名。

apiVersion: v1
kind: Service
metadata:
  name: external-api
spec:
  type: ExternalName
  externalName: api.example.com
  # 不需要 selector 和 ports
kubectl run test --image=busybox:1.28 --rm -it --restart=Never -- nslookup external-api
# 返回 CNAME 记录:api.example.com

2. 指向外部 IP 的 Service(无 Selector + 手动 Endpoints)

apiVersion: v1
kind: Service
metadata:
  name: external-ip-svc
spec:
  ports:
    - name: http
      port: 80
---
apiVersion: v1
kind: Endpoints
metadata:
  name: external-ip-svc
subsets:
  - addresses:
      - ip: 10.0.0.1
      - ip: 10.0.0.2
    ports:
      - port: 80

3. 验证无 Selector Service

# 没有 Selector 的 Service 不会自动创建 Endpoints
kubectl describe svc external-ip-svc
# 注意输出中没有 Endpoints 行(除非手动创建)

四、服务发现

Kubernetes 提供两种服务发现机制:环境变量DNS

1. 环境变量

K8s 在 Pod 启动时注入 Service 的环境变量。

# 查看 Pod 中的环境变量
kubectl exec <pod-name> -- env

# 输出包含(示例):
# MY_SVC_SERVICE_HOST=10.96.0.20
# MY_SVC_SERVICE_PORT=80
# MY_SVC_PORT=tcp://10.96.0.20:80
# MY_SVC_PORT_80_TCP=tcp://10.96.0.20:80
# MY_SVC_PORT_80_TCP_PROTO=tcp
# MY_SVC_PORT_80_TCP_PORT=80
# MY_SVC_PORT_80_TCP_ADDR=10.96.0.20

环境变量命名规则

<SERVICE_NAME>_SERVICE_HOST
<SERVICE_NAME>_SERVICE_PORT
# 验证环境变量
kubectl run env-test --image=busybox:1.28 --rm -it --restart=Never -- sh
# 在容器内
env | grep -i kubernetes
# KUBERNETES_SERVICE_HOST=10.96.0.1
# KUBERNETES_SERVICE_PORT=443

限制

  • 仅在 Pod 创建时注入,Pod 启动后新增的 Service 不会出现
  • 依赖于 Pod 和 Service 的创建顺序(推荐使用 DNS)

2. DNS 服务发现(推荐)

通过 CoreDNS 解析 Service 域名。

# 同命名空间访问
kubectl run dns-test --image=busybox:1.28 --rm -it --restart=Never -- nslookup my-svc

# 跨命名空间访问
kubectl run dns-test --image=busybox:1.28 --rm -it --restart=Never -- nslookup my-svc.other-ns

# 完整的 FQDN
kubectl run dns-test --image=busybox:1.28 --rm -it --restart=Never -- nslookup my-svc.default.svc.cluster.local

3. 环境变量 vs DNS 对比

特性环境变量DNS
创建顺序依赖是(Service 必须先于 Pod)
动态更新否(需重建 Pod)
跨命名空间不支持支持
推荐使用不推荐推荐

五、Endpoints 相关故障排查

# Debug: Service 没有 Endpoints
kubectl describe svc my-svc
# 在 Endpoints 字段看到 <none>

# 检查 Selector 是否匹配 Pod
kubectl get pods -l app=my-app
kubectl get svc my-svc -o yaml | grep selector

# 检查 Pod 是否就绪
kubectl get pods -l app=my-app
# 确保 STATUS 为 Running 且 READY 列为 1/1

# 手动测试 Endpoints 连通性
kubectl run test --image=busybox:1.28 --rm -it --restart=Never -- wget -qO- http://<endpoint-ip>:80

# 查看 EndpointSlice 的详细信息
kubectl get endpointslices -l kubernetes.io/service-name=my-svc -o yaml


🧪 完整操作实例:创建指向外部服务的手动 Endpoints

场景描述

创建一个没有 Selector 的 Service,手动配置 Endpoints 指向模拟的外部数据库 IP,然后验证 Pod 可以通过 Service 名称访问该"外部数据库"。

前置条件

  • 集群正常运行
  • kubectl 已配置好集群访问
  • 准备一个可达的外部 IP 用于测试(可使用集群节点 IP 或任意可达地址)

操作步骤

Step 1: 创建无 Selector 的 Service

# 创建一个名为 external-db 的 Service,不指定 selector
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
  name: external-db
spec:
  ports:
    - protocol: TCP
      port: 3306
      targetPort: 3306
EOF
# 预期输出:service/external-db created

# 验证 Service 没有 Selector 且没有 Endpoints
kubectl get svc external-db
# NAME           TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
# external-db    ClusterIP   10.96.200.10    <none>        3306/TCP   5s

kubectl describe svc external-db
# ... 注意输出中 Endpoints 字段为 <none>,且没有 Selector 行

Step 2: 手动创建同名 Endpoints 指向外部 IP

# 手动创建 Endpoints,名称必须与 Service 完全一致
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Endpoints
metadata:
  name: external-db    # 名称必须与 Service 一致
subsets:
  - addresses:
      - ip: 192.168.1.100    # 模拟外部数据库 IP
    ports:
      - port: 3306
EOF
# 预期输出:endpoints/external-db created

# 验证 Endpoints 已绑定
kubectl get endpoints external-db
# NAME           ENDPOINTS                    AGE
# external-db    192.168.1.100:3306           5s

kubectl describe svc external-db
# ... Endpoints 字段已显示为 192.168.1.100:3306

Step 3: 创建测试 Pod 验证服务发现

# 创建一个测试 Pod
kubectl run test-app --image=busybox:1.28 --rm -it --restart=Never -- sh
# 注意:在容器内执行以下命令

# 在测试 Pod 的 shell 中:
# 1. 检查 DNS 解析
nslookup external-db
# 预期输出:
# Server:    10.96.0.10
# Address:   10.96.0.10:53
# Name:      external-db.default.svc.cluster.local
# Address:   10.96.200.10      # Service 的 ClusterIP

# 2. 通过 Service 名称连接外部服务
# (如果 192.168.1.100:3306 上有实际服务,可用以下命令测试)
# nc -zv external-db 3306
# telnet external-db 3306

# 3. 查看环境变量
env | grep EXTERNAL
# 预期输出:
# EXTERNAL_DB_SERVICE_HOST=10.96.200.10
# EXTERNAL_DB_SERVICE_PORT=3306
# EXTERNAL_DB_PORT=tcp://10.96.200.10:3306
# ...

# 4. 查看 /etc/resolv.conf
cat /etc/resolv.conf
# 预期输出:
# nameserver 10.96.0.10
# search default.svc.cluster.local svc.cluster.local cluster.local
# options ndots:5

# 按 Ctrl+D 退出容器

Step 4: 对比自动 Endpoints(创建有 Selector 的 Service)

# 创建自动 Endpoints 的 Service 作对比
kubectl create deployment test-nginx --image=nginx:1.25 --replicas=2
kubectl expose deployment test-nginx --name=auto-svc --port=80 --target-port=80

# 查看自动创建的 Endpoints
kubectl get endpoints auto-svc
# NAME        ENDPOINTS                           AGE
# auto-svc    10.244.1.5:80,10.244.2.7:80         10s

kubectl describe endpoints auto-svc
# 输出中包含 targetRef,引用了具体的 Pod 名称

# 对比手动创建的 Endpoints
kubectl describe endpoints external-db
# 输出中没有 targetRef,因为指向的是外部地址而非 Pod

Step 5: 添加多个外部地址(高可用场景)

# 更新 Endpoints,添加多个外部地址实现简单的负载均衡
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Endpoints
metadata:
  name: external-db
subsets:
  - addresses:
      - ip: 192.168.1.100    # 主数据库
      - ip: 192.168.1.101    # 备数据库
    ports:
      - port: 3306
EOF
# 预期输出:endpoints/external-db configured

# 验证多个地址
kubectl get endpoints external-db
# NAME           ENDPOINTS                                    AGE
# external-db    192.168.1.100:3306,192.168.1.101:3306       1m

# 从 Pod 中验证 DNS 解析依然指向 Service ClusterIP
kubectl run test-app2 --image=busybox:1.28 --rm -it --restart=Never -- nslookup external-db
# 预期输出:DNS 仍返回 Service ClusterIP,kube-proxy 负责在两个后端间负载均衡

验证结果

# 查看所有 Service 及其 Endpoints
kubectl get svc,ep

# 检查无 Selector Service 的完整状态
kubectl describe svc external-db
# 确认输出包含:
# Type:              ClusterIP
# IP:                10.96.200.10
# Port:              <unset>  3306/TCP
# Endpoints:         192.168.1.100:3306,192.168.1.101:3306
# Session Affinity:  None
# 注意:不显示 Selector 字段

# 清理资源
kubectl delete svc external-db auto-svc
kubectl delete endpoints external-db
kubectl delete deployment test-nginx

考试提示