Endpoints 与服务发现
CKA Domain 3 — Kubernetes Endpoints/EndpointSlice 资源管理与服务发现机制
概述
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
| 特性 | Endpoints | EndpointSlice |
|---|---|---|
| API 版本 | v1 | discovery.k8s.io/v1 |
| GA 阶段 | 自始 GA | K8s v1.21+ GA |
| 单对象容量 | 无限制(整体大对象) | 最多 100 个端点/分片 |
| 更新效率 | 全量更新 | 增量更新 |
| 多地址类型 | 仅 IPv4 | IPv4、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
考试提示
-
手动 Endpoints 的 Service 必须没有 Selector,否则 Endpoints 会被自动管理覆盖
-
Endpoints 名称必须与 Service 名称完全一致(包括命名空间)
-
手动 Endpoints 中的
targetRef字段可选(指向外部 IP 时不需要) -
CKA 中常考场景:集群 Pod 通过 Service 名称访问外部数据库(如 AWS RDS、Azure SQL)
-
EndpointSlice 是 Endpoints 的替代方案(K8s v1.21+ GA),大规模集群推荐使用
-
注意
kubectl create service命令无法创建无 Selector 的 Service,必须使用 YAML -
使用
nc -zv或telnet测试端口连通性比curl更适合非 HTTP 服务(如数据库) -
https://kubernetes.io/docs/concepts/services-networking/service/#services-without-selectors
-
https://kubernetes.io/docs/concepts/services-networking/endpoint-slices/
-
https://kubernetes.io/docs/tasks/administer-cluster/enabling-endpoint-slices/
-
https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/
-
https://kubernetes.io/docs/concepts/services-networking/service/#environment-variables