摘要:理解 Service、EndpointSlice 与 CoreDNS 如何提供稳定访问和名称解析,掌握端口映射、DNS 搜索域、Headless Service 及服务发现排查。
1. 为什么需要 Service?
假设你部署了三个后端 Pod:
flowchart TB
N0["前端 Pod<br/>需要调用后端 API"]
N1["backend Service<br/>稳定的虚拟 IP 和服务名称"]
N2["Pod A<br/>10.244.1.10"]
N3["Pod B<br/>10.244.1.11"]
N4["Pod C<br/>10.244.2.15"]
N0 --> N1
N1 --> N2
N1 --> N3
N1 --> N4
如果前端直接访问 Pod A 的 IP,一旦 Pod A 被删除并创建新实例,原来的 IP 就可能失效。
Service 解决了这个问题:
- 提供相对稳定的服务访问入口。
- 根据标签选择后端 Pod。
- 将请求转发给符合条件的后端端点。
- 配合 DNS,让客户端通过服务名称访问。
前端可以直接请求:
curl http://backend:8080而不需要知道后端 Pod 的具体 IP。
2. Service 的工作原理
Service 通常通过标签选择器找到后端 Pod,并由控制平面维护相应的 EndpointSlice。
flowchart TB
N0["客户端 Pod<br/>请求 backend:8080"]
N1["CoreDNS<br/>将 backend 解析为 Service 的 ClusterIP"]
N2["Service ClusterIP:8080<br/>通过数据面规则转发流量"]
N3["EndpointSlice"]
N4["Pod A"]
N5["Pod B"]
N6["Pod C"]
N0 -->|"DNS 查询"| N1
N1 -->|"返回 ClusterIP"| N0
N0 -->|"访问 Service"| N2
N3 -.->|"端点信息"| N2
N2 -->|"转发流量"| N4
N2 -->|"转发流量"| N5
N2 -->|"转发流量"| N6
这里有一个重要区别:
- CoreDNS:负责名称解析。
- Service:提供稳定的服务抽象。
- EndpointSlice:记录服务关联的网络端点。
- kube-proxy 或其他兼容的数据面:负责实际的流量转发。
DNS 解析成功,并不代表后端一定可用;Service 也不是一个必须运行独立进程的代理服务器。
3. Service 配置与端口映射
apiVersion: v1kind: Servicemetadata: name: backendspec: type: ClusterIP selector: app: backend ports: - name: http port: 8080 targetPort: 80 protocol: TCP此时,Service 的 port 是 8080,而后端容器监听的 targetPort 是 80。
所以请求路径是:
flowchart TB
N0["客户端"]
N1["Service :8080"]
N2["Nginx Pod :80"]
N0 -->|"backend:8080"| N1
N1 -->|"转发到后端 :80"| N2
注意: ClusterIP 默认用于集群内部访问,外部电脑通常不能直接访问这个地址。
4. Service 的四种常见类型
| 类型 | 用途 | 典型访问方式 |
|---|---|---|
ClusterIP | 集群内部服务通信 | Service 虚拟 IP 或 DNS |
NodePort | 通过节点端口暴露服务 | 节点 IP:NodePort |
LoadBalancer | 通过负载均衡器对外暴露 | 外部负载均衡器地址 |
ExternalName | 将 Service DNS 名称映射到外部 DNS 名称 | CNAME 解析 |
1. ClusterIP
最常见的类型,适合前端调用后端、应用访问数据库等集群内部通信。
spec: type: ClusterIP不指定 type 时,默认就是 ClusterIP。
2. NodePort
NodePort 会在符合条件的节点上开放一个端口,默认端口范围通常为 30000–32767,但可以通过集群配置调整。
spec: type: NodePort ports: - port: 80 targetPort: 80 nodePort: 30080访问方式:
http:// 节点 IP:30080实际可达性还取决于节点防火墙、网络路由及集群数据面配置。
3. LoadBalancer
在支持 LoadBalancer 的云环境或已安装相应负载均衡实现的自建集群中,可以使用:
spec: type: LoadBalancer ports: - port: 80 targetPort: 80Kubernetes 会通过相应的实现申请或配置负载均衡入口。在裸机集群中,仅创建 LoadBalancer Service 不一定就能自动获得外部 IP,还需要配套的负载均衡实现。
4. ExternalName
例如,你希望在集群中通过 external-db 这个服务名称访问外部数据库域名:
apiVersion: v1kind: Servicemetadata: name: external-dbspec: type: ExternalName externalName: db.example.com这种类型主要提供 DNS 名称映射,不会像普通 ClusterIP Service 一样创建代理转发规则。
5. Service 如何发现后端 Pod?
主要依靠 selector。
例如:
selector: app: backend这表示 Service 会匹配带有 app=backend 标签的 Pod。
假设有以下三个 Pod:
| Pod | 标签 | 是否匹配 |
|---|---|---|
| Pod A | app=backend | 是 |
| Pod B | app=backend | 是 |
| Pod C | app=frontend | 否 |
那么 Service 只会把符合条件且可用于服务流量的端点纳入转发范围。
你可以查看:
kubectl get pods --show-labelskubectl get endpointsliceskubectl describe svc backend如果 Service 存在,但没有可用后端,优先检查:
- Service 的 selector 是否正确。
- Pod 是否已经创建。
- Pod 标签是否匹配。
- readinessProbe 是否通过。
- Service 的
targetPort是否正确。
EndpointSlice 是现代 Kubernetes 管理服务端点的主要 API 资源;旧的 Endpoints 资源仍可能出现在排障资料中,但不应把它当作所有场景下的首选。
6. Kubernetes Service 的 DNS 命名规则
Kubernetes 中最常见的 Service 完整域名格式是:
<service-name>.<namespace>.svc.<cluster-domain>假设:
- Service 名称:
backend - Namespace:
default - 集群域名:
cluster.local
那么完整域名就是:
backend.default.svc.cluster.local常见的访问方式有以下几种:
| 访问名称 | 适用场景 |
|---|---|
backend | 通常用于同一个 Namespace |
backend.default | 明确指定 Namespace |
backend.default.svc | 指定 Service 域名层级 |
backend.default.svc.cluster.local | 使用完整域名 |
这里的 cluster.local 是常见默认值,不是固定要求,实际集群可能使用其他域名。
1. 为什么不同 Namespace 可以有同名 Service?
例如:
flowchart TB
N0["Namespace: dev"]
N1["backend"]
N2["Namespace: prod"]
N3["backend"]
N0 --> N1
N2 --> N3
两个 Service 可以同名,因为 Namespace 提供了命名空间隔离。
在 dev Namespace 中,直接访问 backend 通常会解析到 dev 中的 Service;如果要访问生产环境的服务,可以写:
curl http://backend.prod.svc.cluster.local:8080这也是 Kubernetes 服务发现的重要基础。
7. CoreDNS 是什么?
CoreDNS 是 Kubernetes 中常用的集群 DNS 服务。它通常以 Deployment 的形式运行在集群中,并由对应的 Service 提供访问入口。
它主要负责:
- 解析 Kubernetes 集群内部的 Service 名称。
- 根据集群资源信息生成 DNS 响应。
- 按配置将外部域名查询转发给上游 DNS 服务器。
- 根据配置提供缓存等 DNS 功能。
查看 CoreDNS:
kubectl get pods -n kube-systemkubectl get svc -n kube-systemkubectl get deployment -n kube-system通常可以看到类似以下资源:
flowchart TB
N0["kube-system"]
N1["coredns-xxxxx"]
N2["coredns-yyyyy"]
N3["kube-dns"]
N0 --> N1
N0 --> N2
N0 --> N3
这里有一个容易混淆的名称:
即使实际 DNS 程序是 CoreDNS,Kubernetes 中对应的 Service 仍经常叫 kube-dns。
这是历史命名沿用,并不代表集群还在运行旧版 kube-dns 程序。
1. CoreDNS 的工作流程
flowchart TB
N0["Pod 发起 DNS 查询<br/>例如查询 backend.default"]
N1["Pod 的 DNS 配置<br/>nameserver 指向集群 DNS 服务地址"]
N2["CoreDNS<br/>匹配集群域名规则,查询 Kubernetes 服务信息"]
N3["返回集群资源对应的记录"]
N4["按配置转发或处理"]
N0 --> N1
N1 --> N2
N2 -->|"集群内部域名"| N3
N2 -->|"外部域名"| N4
CoreDNS 的具体处理流程由 Corefile 配置决定。例如,kubernetes 插件负责 Kubernetes 域名,forward 插件可以把外部查询转发给上游 DNS。
8. /etc/resolv.conf:Pod 如何知道 DNS 在哪里?
进入一个 Pod,执行:
cat /etc/resolv.conf典型内容可能类似:
search default.svc.cluster.local svc.cluster.local cluster.localnameserver 10.96.0.10options ndots:5这些字段非常重要。
| 字段 | 含义 |
|---|---|
search | 配置 DNS 查询时尝试使用的搜索域 |
nameserver | 指定 DNS 服务器地址 |
ndots | 控制何时优先尝试绝对域名查询 |
实际输出取决于 Pod 的 dnsPolicy、集群配置以及 DNS 域名设置。
1. Search:为什么只写 backend 也能访问?
假设 /etc/resolv.conf 中有:
search default.svc.cluster.local svc.cluster.local cluster.local当程序查询 backend 时,系统的名称解析机制可能依次尝试类似以下名称:
backend.default.svc.cluster.localbackend.svc.cluster.localbackend.cluster.localbackend具体顺序和尝试方式会受到解析器实现、ndots 和域名写法影响。
因此,你在同一个 Namespace 中通常可以直接执行:
curl http://backend:8080而不必手动写完整域名。
2. ndots:5 是什么意思?
ndots:5 表示:域名中至少有 5 个点时,解析器才会把它视为优先尝试绝对查询的名称;点数不足时,通常先尝试搜索域扩展。
例如:
backend只有 0 个点,解析器通常会先尝试搜索域。
而:
backend.default.svc.cluster.local包含 4 个点,在常见的 ndots:5 配置下,仍可能先尝试附加搜索域,再尝试绝对名称。
这可能导致额外的 DNS 查询,特别是在应用频繁访问外部域名时。
不过,ndots 的行为属于操作系统解析器的机制,不是 CoreDNS 自己决定的所有查询顺序。
9. Headless Service:DNS 直接返回 Pod 地址
普通 ClusterIP Service 通常通过 DNS 返回 Service 的虚拟 IP。
Headless Service 则不分配普通 ClusterIP,而是通过 DNS 暴露符合条件的后端地址。
创建方式:
apiVersion: v1kind: Servicemetadata: name: backend-headlessspec: clusterIP: None selector: app: backend ports: - port: 8080 targetPort: 80普通 Service 和 Headless Service 的区别:
| 对比 | 普通 ClusterIP | Headless Service |
|---|---|---|
| 是否有 ClusterIP | 有 | 没有普通 ClusterIP |
| DNS 常见结果 | Service 虚拟 IP | 符合条件的后端 Pod 地址 |
| 流量转发 | 通过 Service 数据面 | 客户端通常直接连接解析到的端点 |
| 常见用途 | 普通微服务 | StatefulSet、需要发现各实例的应用 |
Headless Service 特别适合 StatefulSet,因为每个实例可以拥有可预测的网络身份。
但需要注意:DNS 返回 Pod 地址并不保证 Pod 一定健康,也不保证客户端能够直接访问这些地址。
10. Service 与 DNS 故障排查
1. Service 请求失败
建议按照从上到下的顺序:
# 1. 检查 Podkubectl get pods -o wide
# 2. 检查 Servicekubectl get svc backendkubectl describe svc backend
# 3. 检查后端端点kubectl get endpointslices \ -l kubernetes.io/service-name=backend
# 4. 检查 DNSkubectl exec < 客户端 Pod> -- \ nslookup backend
# 5. 检查应用端口与日志kubectl logs < 后端 Pod>kubectl describe pod < 后端 Pod>常见故障:
| 现象 | 优先检查 |
|---|---|
| DNS 解析失败 | CoreDNS、Pod DNS 配置、服务名称 |
| Service 没有后端 | selector、Pod 标签、就绪状态 |
| DNS 正常但连接超时 | 网络策略、路由、数据面、防火墙 |
| 连接被拒绝 | 应用是否监听、targetPort 是否正确 |
| 只有部分请求失败 | 后端实例健康状况、端口和应用日志 |
2. DNS 专项排查
这是 K8s 网络中的经典问题。
假设:
curl http://10.96.120.30:8080可以访问,但:
curl http://backend:8080失败。
这时应该先区分是 DNS 解析失败,还是解析成功后的网络连接失败。
1. 第一步:检查 DNS 配置
kubectl exec <pod-name> -- cat /etc/resolv.conf确认 nameserver 是否指向正确的集群 DNS 地址,以及 search 域是否符合预期。
2. 第二步:检查 DNS 查询
如果容器中有 nslookup:
kubectl exec <pod-name> -- nslookup backend或者查询完整域名:
kubectl exec <pod-name> -- nslookup \ backend.default.svc.cluster.local3. 第三步:检查 Service 和端点
kubectl get svc backendkubectl get endpointslices \ -l kubernetes.io/service-name=backend确认 Service 存在,并且后端端点符合预期。
4. 第四步:检查 CoreDNS
kubectl get pods -n kube-systemkubectl logs -n kube-system \ -l k8s-app=kube-dns如果日志选择器不匹配实际标签,可以先执行:
kubectl get pods -n kube-system --show-labels再选择正确的标签查看日志。
5. 第五步:检查 DNS 网络连通性
如果查询超时,需要继续检查:
- Pod 是否能访问 DNS Service。
- CoreDNS Pod 是否正常运行。
- UDP/TCP 53 端口是否可达。
- NetworkPolicy 是否阻断 DNS。
- CNI、路由或节点防火墙是否影响 DNS 流量。
不要因为 DNS 解析失败就直接重启 CoreDNS。先通过查询结果和事件确定故障范围。
11. 知识检查清单
本节最重要的知识点:
- CoreDNS 负责集群 DNS 服务。
- Service DNS 名称 让应用不必依赖变化的 Pod IP。
/etc/resolv.conf决定 DNS 服务器、搜索域和部分查询行为。- Headless Service 可以让客户端发现具体的后端 Pod 地址。
- DNS 解析成功不代表应用一定可达,仍需检查 Service、端点和网络链路。