首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Kubernetes 中的服务发现:从原理到实战

Kubernetes 中的服务发现:从原理到实战

原创
作者头像
闫同学
发布2025-09-14 13:37:59
发布2025-09-14 13:37:59
1650
举报

在微服务的世界里,最让人头疼的问题之一就是 “我怎么找到你?”

想象一下,你有几十上百个服务副本,每个 Pod 的 IP 地址都像是临时旅馆的房号——今天住 101,明天就可能换到 307。要是每个调用方都去记 Pod 的 IP,不仅累死人,还容易出错。

这时候,Kubernetes 伸出了一只温柔的手:服务发现(Service Discovery)。它告诉你,不用再管谁在哪,记住一个名字就行,剩下的我来搞定。

接下来,我们就一起来聊聊 Kubernetes 的服务发现机制,看看它是怎么帮我们解决这个“找人”的问题,并结合 Go 语言的代码示例,看看在实战中如何应用。

服务发现的意义

在传统架构里,运维人员可能要手工维护一份 IP 地址清单,或者通过配置文件告诉应用:“你要去找数据库,就连这个 192.168.1.23;要找 Redis,就去 192.168.1.45。”

但是在 Kubernetes 的世界里,Pod 会被频繁创建和销毁,IP 地址完全不固定。试想一下,如果还靠人来维护这种清单,那简直是灾难。

所以,服务发现的核心目标就是:屏蔽 Pod 动态变化,提供一个稳定的服务访问入口。

Kubernetes 提供的两种服务发现方式

环境变量

当 Pod 启动时,Kubelet 会自动把集群里现有的 Service 信息注入到 Pod 的环境变量里。

举个例子,如果有个叫 redis 的 Service,那么 Pod 内会有类似的环境变量:

代码语言:bash
复制
REDIS_SERVICE_HOST=10.0.0.15
REDIS_SERVICE_PORT=6379

在应用程序里,我们可以直接读取这些环境变量,然后建立连接。

优点:零配置、简单直接。

缺点:Pod 启动之后才创建的 Service,不会被感知。换句话说,这个方式“有点傻”,不太适合动态变化很频繁的场景。

DNS(推荐)

DNS 是 Kubernetes 服务发现的主流方式。它由 CoreDNS 组件负责。

每个 Service 都会被自动分配一个 DNS 名称,比如:

代码语言:shell
复制
my-service.my-namespace.svc.cluster.local

在 Pod 内,你只要直接访问 my-service:port,就能找到对应的服务。完全不需要知道背后有多少 Pod,也不需要记它们的 IP。

更神奇的是,如果你把 Service 定义成 Headless Service(即 ClusterIP: None),那么 DNS 会返回所有 Pod 的 IP。这种方式非常适合需要直连 Pod 的有状态服务(如 MySQL 主从集群)。

服务发现背后的原理

光说表面机制可能有点抽象,我们来扒一扒背后到底发生了什么:

  1. Service 是抽象,真正干活的是 Endpoints
  • 当你创建一个 Service 时,Kubernetes 会生成一个 Endpoints 对象,里面维护了所有匹配该 Service 的 Pod IP 列表。
  • kube-proxy 监听 Endpoints 的变化,然后更新节点上的 iptables/ipvs 规则,确保请求能正确转发。
  1. CoreDNS 的角色
  • CoreDNS 持续 watch API Server,动态更新 DNS 解析。
  • 当 Pod 上线或下线时,Endpoints 发生变化,CoreDNS 立刻更新对应的 A 记录。
  1. 负载均衡策略
  • Service 默认采用简单的轮询,把请求均匀分发到 Pod。
  • 如果要对有状态应用进行“定向寻址”,就需要借助 Headless Service + StatefulSet。

一句话总结:

👉 Service 就像一个前台,Endpoints 是员工名单,kube-proxy 则是前台的小秘书,负责把客人引导到对应的员工。

Go 开发者如何用到服务发现?

直接使用 Service 名称

在 Go 代码中,你只需用 Service 名称作为主机名即可:

代码语言:go
复制
conn, err := grpc.Dial("my-service:50051", grpc.WithInsecure())
if err != nil {
    log.Fatalf("did not connect: %v", err)
}

这里的 my-service 会自动解析成 Service 的 ClusterIP,流量再由 kube-proxy 转发到具体的 Pod。

使用 Headless Service 获取所有 Pod

有时候我们想要更多控制,比如写一个客户端负载均衡器。

代码语言:go
复制
addrs, _ := net.LookupHost("my-service")
for _, ip := range addrs {
    fmt.Println("Pod IP:", ip)
}

你可以拿到所有 Pod 的 IP,然后自己决定如何分配请求,比如按哈希取模,或者根据节点健康情况做选择。

直接 Watch Endpoints

如果你想实现更强大的服务发现(比如一个自研的服务治理框架),可以直接通过 client-go 监听 Endpoints 的变化:

代码语言:go
复制
config, _ := clientcmd.BuildConfigFromFlags("", "/root/.kube/config")
clientset, _ := kubernetes.NewForConfig(config)

watcher, _ := clientset.CoreV1().
    Endpoints("default").
    Watch(context.TODO(), metav1.ListOptions{
        FieldSelector: "metadata.name=my-service",
    })

for event := range watcher.ResultChan() {
    fmt.Println("Event:", event.Type, event.Object)
}

这样,你可以实时感知 Pod 的上下线,然后动态更新自己的连接池。

常见实战场景

  • 微服务调用:gRPC/HTTP 服务直接使用 Service 名称,扩容缩容完全无感知。
  • 数据库集群:Headless Service + StatefulSet,让应用直连特定的数据库节点。
  • 服务网格(Service Mesh):Istio、Linkerd 等高级组件,本质上也依赖 Kubernetes 的服务发现能力。

总结与注意点

  • Kubernetes 提供了 环境变量 + DNS 两种方式,推荐用 DNS。
  • Service 是抽象,Endpoints 维护真实的 Pod IP。
  • 对 Go 开发者来说,最常见的就是 直接用服务名当主机名
  • 如果需要更精细的控制,可以结合 Headless ServiceKubernetes API

注意事项

  1. 跨命名空间访问要写全称:my-service.other-ns.svc.cluster.local
  2. DNS 有缓存,可能导致短暂延迟。
  3. 有状态应用(数据库、消息队列)需要配合 StatefulSet 使用。

最后的比喻

如果把 Kubernetes 比作一座大城市,Pod 就是流动的摊贩,每天换地方摆摊。

而 Service 就像是统一的招牌:

  • 你只要记得“张三煎饼摊”,不用关心他今天在哪条街。
  • kube-proxy 是负责引路的小哥,CoreDNS 则是“城市地图”。

这就是 Kubernetes 服务发现的魅力——它让分布式世界的沟通,变得像打电话一样简单。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 服务发现的意义
  • Kubernetes 提供的两种服务发现方式
    • 环境变量
    • DNS(推荐)
  • 服务发现背后的原理
  • Go 开发者如何用到服务发现?
    • 直接使用 Service 名称
    • 使用 Headless Service 获取所有 Pod
    • 直接 Watch Endpoints
  • 常见实战场景
  • 总结与注意点
  • 最后的比喻
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档