Kubernetes 系列【18】服务:基于 DNS 的服务发现机制
文章目录
- 1. Pod 之间互相通信
- 1.1 四种服务的访问入口
- 1.2 标准选型:ClusterIP 服务
- 1.3 硬编码虚拟 IP 的致命缺点
- 1.4 生产最佳实践
- 2. 服务发现机制
- 2.1 环境变量服务发现
- 2.1.1 变量命名规则
- 2.1.2 致命缺陷(生产禁用)
- 2.1.3 实操演示:环境变量服务发现
- 2.2 DNS 服务发现(生产标准推荐)
- 2.2.1 DNS 基础
- 2.2.2 域名访问规则
- 2.2.3 DNS SRV 记录
- 2.2.3 ExternalName 服务特殊说明
- 3. 案例演示
- 3.1 创建客户端 Pod
- 3.2 创建服务端 Pod
- 3.3 测试验证
- 3.3.1 进入 Pod 内部
- 3.3.2 使用虚拟 IP 访问
- 3.3.3 使用短服务名访问
- 2.2.5 使用 DNS 服务域名访问
- 2.2.6 使用 DNS SRV 记录访问
- 3.4 访问方式总结:
1. Pod 之间互相通信
1.1 四种服务的访问入口
在Kubernetes中,不同类型的Service对应不同的访问入口与流量场景。我们先回顾四种Service类型的默认访问入口:
| Service 类型 | 默认访问入口 |
|---|---|
ClusterIP(默认) | 集群内部虚拟 ClusterIP |
NodePort | ClusterIP + 节点宿主机端口 |
LoadBalancer | ClusterIP + 云厂商负载均衡公网 IP |
ExternalName | 无 ClusterIP,CNAME映射外部域名 |
1.2 标准选型:ClusterIP 服务
集群内部业务Pod主动发起请求、调用另一业务Pod,属于服务内网互相调用场景,也是Kubernetes最核心、最普遍的通信方式。
该场景仅存在集群内部流量、无需外网暴露,因此生产环境统一首选ClusterIP类型Service。
典型适用场景:
- 前后端业务微服务互相调用
- 业务服务访问集群内部中间件(
MySQL、Redis、Kafka) gRPC微服务内网互通、多端口精细化调用
ClusterIP只会生成集群内网虚拟IP,外网无法直接接入,天然具备良好的隔离性与安全性,是内网服务的标准选型。
1.3 硬编码虚拟 IP 的致命缺点
如果Pod之间使用ClusterIP中的虚拟IP进行通信的话,直接在代码 / 配置里写死ClusterIP地址访问服务,存在以下致命问题:
- ClusterIP 不固定:
Service删除重建后IP会重新分配,一旦变更,所有硬编码IP的业务直接访问失败。 - 可读性差、不易维护:纯数字
IP无语义,无法直观对应业务服务名称,排查问题成本高。 - 无法使用 SRV 记录能力:多端口
Service场景下,不能借助命名端口自动获取端口信息,需要手动维护多组地址端口。 - 多环境迁移繁琐:开发、测试、生产集群内同一服务的
ClusterIP各不相同,硬编码需要维护多套配置。 - 违背 Service 抽象设计理念:
Service提供稳定域名层屏蔽底层变化,直连IP等于舍弃这一层抽象。 - 不利于后续架构演进:服务网格(
Istio)、灰度发布、流量管控等组件大多基于服务域名配置策略,静态IP难以适配。 - 故障定位难度提升:日志只打印
IP,需要额外对照表才能关联到具体Service。
1.4 生产最佳实践
生产环境严格禁止硬编码ClusterIP虚拟IP,统一使用Service域名 +DNS服务发现实现内网通信,依托K8s原生DNS能力,实现服务无感变更、稳定寻址、可扩展、可治理的云原生通信架构。
2. 服务发现机制
Kubernetes集群中,Pod想要主动访问Service服务,必须依靠服务发现机制获取Service地址与端口。K8s原生提供两种服务发现方式:环境变量服务发现、DNS服务发现,其中DNS为生产唯一推荐方案。
| 服务发现方式 | 优点 | 致命缺点 | 生产适用性 |
|---|---|---|---|
| 环境变量 | 无需DNS组件、解析速度快 | 强时序依赖、无法动态更新、跨NS访问繁琐 | ❌ 完全禁用 |
| DNS | 动态更新、无时序限制、支持跨NS、可读性强 | 依赖CoreDNS组件 | ✅ 全局唯一推荐 |
2.1 环境变量服务发现
环境变量是K8s最基础、最原始的服务发现方式。Pod启动初始化阶段,节点上的kubelet组件会自动扫描当前命名空间下所有已存在的活跃 Service,并将Service的集群IP、端口等信息注入为Pod环境变量。
2.1.1 变量命名规则
环境变量遵循固定大写命名规则:
服务名_SERVICE_HOST(存储Service集群IP)服务名_SERVICE_PORT(存储Service业务端口)。
示例:命名空间下存在名为redis-primary的Service,集群IP为10.0.0.11、端口6379,Pod内会自动注入以下环境变量:
REDIS_PRIMARY_SERVICE_HOST=10.0.0.11REDIS_PRIMARY_SERVICE_PORT=63792.1.2 致命缺陷(生产禁用)
该机制存在无法规避的时序漏洞:Service 必须提前于 Pod 创建完成。如果先启动Pod、后创建Service,kubelet已完成Pod初始化,不会二次注入环境变量,Pod将永远无法识别该Service。
同时该方式不支持动态更新、不支持跨命名空间便捷访问、无域名可读性,完全无法适配动态扩缩容、滚动更新的云原生场景。
结论:仅作原理了解,生产环境完全弃用。
2.1.3 实操演示:环境变量服务发现
这里我们实操验证环境变量注入的时序 BUG,彻底看懂为什么不能生产用。
1)先创建Service,再启动Pod(能正常注入)
# 先创建服务kubectl expose deployment nginx-demo--port=80--name=svc-env-demo# 临时启动测试Podkubectl run test-env1--image=busybox--rm-it--sh在Pod内查看环境变量:
env|grepSVC_ENV_DEMO结果:可以正常看到SVC_ENV_DEMO_SERVICE_HOST、SVC_ENV_DEMO_SERVICE_PORT。
2)复现致命问题:先启动Pod,后创建Service
# 先启动Podkubectl run test-env2--image=busybox--rm-it--sh# 新开终端、后创建服务kubectl expose deployment nginx-demo--port=80--name=svc-env-late回到test-env2容器内查询:
env|grepSVC_ENV_LATE结果为空,永远不会补发环境变量。
这就是生产绝对禁用的核心原因:服务动态创建、动态扩容场景完全不兼容。
2.2 DNS 服务发现(生产标准推荐)
DNS服务发现是K8s官方标准、生产唯一通用的服务发现方案。集群部署CoreDNS组件后,所有Pod会默认使用集群内置DNS服务器,通过域名解析自动获取Service地址,全程动态感知资源变更,无时序限制、无配置硬编码。
2.2.1 DNS 基础
DNS(Domain Name System,域名系统),可以理解成互联网/集群内部的「电话簿」。计算机网络通信只能识别一串数字IP地址,人类很难记忆大量IP,DNS把好记的域名翻译成机器能识别的IP地址。
举个外网例子:
- 你在浏览器输入
www.baidu.com DNS查询,翻译成180.101.49.11- 客户端使用IP建立网络连接
DNS服务器保存一条条映射数据,每一条称为一条DNS标准记录。
常用标准记录清单:
| 记录类型 | 作用说明 | 关键特点 & 补充信息 | 示例 |
|---|---|---|---|
| A 记录(最常见) | 域名解析到 IPv4 地址 | 仅返回IPv4地址 | baidu.com → 180.101.49.11 |
| AAAA 记录 | 域名解析到 IPv6 地址 | 仅返回IPv6地址 | - |
| CNAME 别名记录 | 将一个域名指向另一个域名(不能直接指向IP) | 域名跳转,目标只能填写域名 | shturl.cc/n → shturl.cc/GyF8 |
| SRV 记录(重点) | 服务定位记录,通过DNS查找服务地址+端口 | 标准:RFC 2782,非K8s自定义 返回内容:优先级、权重、端口号、目标域名 A记录只返回IP;SRV附带端口与调度权重信息,用于发现服务 | - |
| MX 记录 | 邮件解析专用 | 指定域名对应的邮件服务器地址 | - |
| TXT 记录 | 存储自定义文本信息 | 常用于域名所有权验证、DKIM邮件签名等 | - |
Kubernetes集群内也运行DNS服务,默认组件为CoreDNS。集群内Pod不再需要硬编码各个服务的IP,只需要填写Service域名,请求发送给CoreDNS,解析得到Service 的ClusterIP,完成通信。
2.2.2 域名访问规则
K8s内置标准化域名体系,区分同命名空间、跨命名空间访问,适配所有业务场景:
同命名空间访问:直接使用
Service短名称,示例:my-service跨命名空间访问:服务名 + 命名空间,示例:
my-service.my-ns完整 FQDN 域名(全局通用):
my-service.my-ns.svc.cluster.local,集群内所有Pod均可解析,无命名空间限制
2.2.3 DNS SRV 记录
普通A/AAAA记录只能拿到IP,没有端口,很多服务不跑标准默认端口,光有IP无法连接。
SRV(Service Location Record)服务定位记录,可以通过DNS,直接查到「某个服务 = 域名:端口」,同时携带调度策略。
针对配置了命名端口的Service,K8s会自动生成SRV解析记录,不仅可以解析服务IP,还能精准获取对应端口号,适配多端口Service精准访问场景。
SRV域名固定格式:
_{端口名称}._{协议}.{服务名}.{命名空间}_http._tcp.my-service.my-ns解析:
_http:ports里的name名称_tcp:协议protocolmy-service:Service名称my-ns:命名空间
假设业务Pod想要调用http接口:
- 普通方式地址:
my-service.my-ns.svc.cluster.local:9090(端口写死) SRV方式:程序解析_http._tcp.my-service.my-ns,自动获取端口
多端口Service YAML示例:
apiVersion:v1kind:Servicemetadata:name:my-servicenamespace:my-nsspec:selector:app:my-appports:-name:http# ✅ 端口命名,生成SRV记录port:80targetPort:8080protocol:TCP-name:grpc# ✅ 第二个命名端口port:9090targetPort:9090protocol:TCP解析格式示例:查询_http._tcp.my-service.my-ns,可同时返回服务IP、业务端口、协议类型,实现精细化服务发现。
2.2.3 ExternalName 服务特殊说明
ExternalName类型Service无ClusterIP、无后端端点、无流量转发能力,不支持环境变量发现、不支持四层流量转发,仅能通过 DNS 服务发现访问,完全依赖CoreDNS的CNAME域名映射能力实现服务调用。
3. 案例演示
3.1 创建客户端 Pod
创建一个测试的nginx-client,模拟需要调用其他Pod进行业务操作:
apiVersion:apps/v1kind:Deploymentmetadata:name:nginx-clientnamespace:defaultspec:replicas:1selector:matchLabels:app:nginx-clienttemplate:metadata:labels:app:nginx-clientspec:containers:-image:'nginx:latest'imagePullPolicy:Alwaysname:nginxports:-containerPort:80protocol:TCP3.2 创建服务端 Pod
基于之前 Kubernetes 系列【16】服务:ClusterIP 核心原理与演示案例 中部署的服务,模拟被其他Pod调用。
查看服务信息:
[root@iZ2ze60mcbemirrq6qZ ~]# kubectl get svc nginx-svc -o wideNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR nginx-svc ClusterIP 10.8.173.136 <none>8080/TCP 23h app=nginx-web3.3 测试验证
提示:不要直接在宿主机服务器进行访问操作,K8s 集群内域名 *.svc.cluster.local 由 CoreDNS 负责解析,容器 /etc/resolv.conf 的 nameserver 指向集群内部 CoreDNS Service IP ,宿主机的 /etc/resolv.conf 是操作系统默认 DNS(运营商 DNS、阿里云 DNS 等),外部公共 DNS 完全不认识 svc.cluster.local 这个域名后缀
请求发出去后,外部 DNS 直接返回:域名不存在 ❌
简单一句话:
*.svc.cluster.local 是集群私有域名,仅集群 DNS(CoreDNS)能够识别
3.3.1 进入 Pod 内部
方式1直接进入Deployment下任意Pod:
kubectlexec-itdeploy/nginx-client --sh方式2先查Pod名称,精准进入单个Pod:
# 列出podkubectl get pods# 进入指定podkubectlexec-itnginx-client-xxxxxx-xxxxx --sh3.3.2 使用虚拟 IP 访问
可直接通过虚拟IP访问:
/# curl 10.8.173.136:8080<!DOCTYPE html><html><head><title>Welcome to nginx!</title><style>html{color-scheme: light dark;}body{width: 35em;margin:0auto;font-family: Tahoma, Verdana, Arial, sans-serif;}</style>3.3.3 使用短服务名访问
在同一命名空间Pod内部,不需要填写完整长域名,可以直接使用Service名称即可访问服务,依托CoreDNS+Pod DNS搜索域 共同实现。
查看/etc/resolv.conf:
nameserver10.96.0.10 search default.svc.cluster.local svc.cluster.local cluster.local options ndots:5关键字段:
nameserver 10.96.0.10:指向集群CoreDNS的固定Service IP,所有域名请求发送给集群内部DNS。search搜索域列表:程序发起域名查询nginx-svc,操作系统DNS解析器会自动依次拼接搜索域尝试解析,拼接第一个nginx-svc.default.svc.cluster.local,命中,查询A记录,返回Service地址,终止后续搜索匹配ndots:5:域名中点.数量≥5,直接当作完整域名查询,不追加搜索域,优化解析逻辑。
访问示例:
/# curl nginx-svc:8080<!DOCTYPE html><html><head><title>Welcome to nginx!</title><style>2.2.5 使用 DNS 服务域名访问
核心特性:
- 通用不受命名空间限制:不管客户端
Pod运行在哪一个命名空间,都可以使用这条完整域名访问服务。 - 查询的是
A记录:CoreDNS返回IP地址。 - 所有常规客户端天然兼容:
curl、RestTemplate、WebClient、OpenFeign、Java程序均可直接使用,底层只依赖标准A记录解析,不需要支持SRV。
DNS服务域名完整格式:
<service-name>.<namespace>.svc.cluster.local分段拆解
| 片段 | 含义 | 说明 |
|---|---|---|
| service-name | Service 的资源名称 | kubectl get svc展示的名字,本例:nginx-svc |
| namespace | 命名空间名称 | 服务所在ns,本例:default |
| svc | 固定关键字 | 代表这条域名对应Service 资源 |
| cluster.local | 集群域名后缀 | K8s集群默认根域名,全局固定 |
访问示例:
# Pod 内任意命名空间均可执行/# curl nginx-svc.default.svc.cluster.local:8080<!DOCTYPE html><html><head><title>Welcome to nginx!</title><style>2.2.6 使用 DNS SRV 记录访问
前提条件:Service端口必须配置ports.name,否则CoreDNS不会生成SRV记录
K8s SRV域名标准格式:
_<端口名称>._<协议>.<service-name>.<ns>.svc.cluster.local字段拆解
| 片段 | 说明 |
|---|---|
| _端口名称 | spec.ports[].name 自定义名称,必须以下划线开头 |
| _协议 | _tcp / _udp |
| service-name | Service名称 |
| ns | 命名空间 |
| svc.cluster.local | K8s集群域名后缀 |
用nslookup查询:
/# nslookup -type=SRV _http-web._tcp.nginx-svc.default.svc.cluster.localServer:10.96.0.10 Address:10.96.0.10:53 _http-web._tcp.nginx-svc.default.svc.cluster.localservice=01008080nginx-svc.default.svc.cluster.local不能直接curl SRV域名:
# ❌ 错误写法,一定会报域名解析失败curl_http._tcp.nginx-svc.default.svc.cluster.local:8080原因:
_http._tcp.xxx这条域名只有SRV记录,没有A记录。- 操作系统 /
JDK默认域名解析只会查询A记录,找不到记录直接抛出UnknownHostException。
3.4 访问方式总结:
| 访问方式 | 访问示例 | DNS记录类型 | 跨Namespace可用 | 是否可直接curl访问 | 核心前提 | 适用客户端 | 关键说明 |
|---|---|---|---|---|---|---|---|
| 1. ClusterIP虚拟IP访问 | curl 10.8.173.136:8080 | 无DNS,直接IP | ✅ | ✅ | 知道Service固定ClusterIP | 全部客户端 | IP硬编码,不推荐生产;ClusterIP仅集群内部可达,集群重建IP会变化 |
| 2. 短服务名访问 | curl nginx-svc:8080 | A记录 | ❌(仅限同namespace) | ✅ | Pod与Service同命名空间;依赖resolv.conf search搜索域 | 全部客户端 | CoreDNS自动补齐完整域名;跨命名空间不能使用短名称 |
| 3. 标准完整DNS域名访问 | curl nginx-svc.default.svc.cluster.local:8080 | A记录 | ✅ | ✅ | Service资源正常存在,Endpoints不为空 | 全部客户端(curl、RestTemplate、OpenFeign、WebClient等) | 生产微服务主流推荐方式,通用稳定,基于标准A记录解析 |
| 4. SRV记录查询 | 查询:nslookup -type=SRV _http-web._tcp.nginx-svc.default.svc.cluster.local❌不可直接 curl _http-web._tcp.nginx-svc.default.svc.cluster.local | SRV记录 | ✅ | ❌ | Service端口配置ports.name;CoreDNS生成SRV记录 | 仅gRPC原生支持;普通HTTP客户端需自行编码解析 | SRV仅为服务发现元数据(携带优先级、权重、端口);该域名无A记录,JDK/操作系统标准解析器无法直接访问 |
