Service :微服务通信、负载、故障难题的解决方案

一、引言

在前面我们了解了 Deployment 和 DaemonSet 这两个 API 对象,它们都是在线业务, 只是以不同的策略部署应用,Deployment 创建任意多个实例,Daemon 为每个节点创建一个实例。 这两个 API 对象可以部署多种形式的应用,而在云原生时代,微服务无疑是应用的主流形态。 为了更好地支持微服务以及服务网格这样的应用架构,Kubernetes 又专门定义了一个新的对象:Service,它是集群内部的负载均衡机制,用来解决服务发现的关键问题。

二、为什么需要Service

有了 Deployment 和 DaemonSet,我们在集群里发布应用程序的工作轻松了很多。借助 Kubernetes 强大的自动化运维能力,我们可以把应用的更新上线频率由以前的月、周级别提 升到天、小时级别,让服务质量更上一层楼。

不过,在应用程序快速版本迭代的同时,另一个问题也逐渐显现出来了,就是"服务发现"。

在 Kubernetes 集群里 Pod 的生命周期是比较"短暂"的,虽然 Deployment 和 DaemonSet 可 以维持 Pod 总体数量的稳定,但在运行过程中,难免会有 Pod 销毁又重建,这就会导致 Pod 集合处于动态的变化之中。 这种"动态稳定"对于现在流行的微服务架构来说是非常致命的,试想一下,后台 Pod 的 IP 地 址老是变来变去,客户端该怎么访问呢?如果不处理好这个问题,Deployment 和 DaemonSet 把 Pod 管理得再完善也是没有价值的。

其实,这个问题也并不是什么难事,业内早就有解决方案来针对这样"不稳定"的后端服务,那 就是"负载均衡",典型的应用有 LVS、Nginx 等等。它们在前端与后端之间加入了一个"中间 层",屏蔽后端的变化,为前端提供一个稳定的服务。Kubernetes 就按照这个思路,定义了新的 API 对 象:Service。

Service 的工作原理和 LVS、Nginx 差不多,Kubernetes 会给它分配一 个静态 IP 地址,然后它再去自动管理、维护后面动态变化的 Pod 集合,当客户端访问 Service,它就根据某种策略,把流量转发给后面的某个 Pod。

下面的这张图来自 Kubernetes官网文档,比较清楚地展示了 Service 的工作原理:

这里 Service 使用了 iptables 技术,每个节点上的 kube-proxy 组件自动维护 iptables 规则,客户不再关心 Pod 的具体地址,只要访问 Service 的固定 IP 地址,Service 就 会根据 iptables 规则转发请求给它管理的多个 Pod,是典型的负载均衡架构。

看到这里我们也就明白了为什么需要Service,回想一下微服务通常以 Pod 为单位部署,但 Pod 有个 "致命缺点"------生命周期不稳定,扩容/缩容,重启,Pod 的 IP 地址都可能变化,一旦 Pod IP 变动,调用就会失败,整个微服务链路都会断。而 Service 的核心作用,就是为这些 "不稳定的 Pod" 提供一个稳定的访问入口,就像给一群动态变化 的 Pod 挂了个 "固定门牌",不管 Pod 怎么变,调用方只要访问这个 "门牌" 就行。

简单说:Service = 稳定 IP + 自动负载均衡,是 微服务在 K8S 中实现 "服务发现" 和 "负载均衡 " 的关键组件

三、使用 YAML 描述 Service

我们先来看一下Service YAML:

复制代码
apiVersion: v1
kind: Service
metadata:
  name: ngx-svc
spec:
  selector:  # Service的标签选择器,和Pod标签匹配
    app: ngx-svc
  ports:
  - port: 80  # Service的端口
    targetPort: 80  # Pod中Java服务的端口

Service 的定义非常简单,在"spec"里只有两个关键字段,selector 和 ports。

selector 和 Deployment/DaemonSet 里的作用是一样的,用来过滤出要代理的那些 Pod。 因为我们指定要代理 Deployment,所以 Kubernetes 就为我们自动填上了 ngx-dep 的标签, 会选择这个 Deployment 对象部署的所有 Pod。

从这里你也可以看到,Kubernetes 的这个标签机制虽然很简单,却非常强大有效,很轻松就 关联上了 Deployment 的 Pod。 ports 就很好理解了,里面的两个字段分别表示外部端口、内部端口,在这里 就是内外部都使用 80 端口。在这里也可以把 ports 改成"8080"等其他的端口,这样外部服务看到的就是 Service 给出的端口,而不会知道 Pod 的真正服务端口。

为了直观的看清楚 Service 与它引用的 Pod 的关系,我们还是用一张图来展示一下:

四、Kubernetes 里使用 Service

首先,我们创建一个 ConfigMap,定义一个 Nginx 的配置片段,它会输出服务器的地址、主 机名、请求的 URI 等基本信息:

复制代码
apiVersion: v1
kind: ConfigMap
metadata:
  name: ngx-conf
data:
  default.conf: |
    server {
      listen 80;
      location / {
        default_type text/plain;
        return 200 'srv: $server_addr:$server_port\nhost: $hostname\nuri: $request_method $request_uri';
      }
    }

使用命令进行创建

复制代码
kubectl apply -f ngx-conf.ymal

然后我们在 Deployment 的"template.volumes"里定义存储卷,再用"volumeMounts"把配置 文件加载进 Nginx 容器里:

复制代码
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ngx-dep
spec:
  replicas: 2
  selector:
    matchLabels:
      app: ngx-dep
  template:dep
    metadata:
      labels:
        app: ngx-dep
    spec:
      volumes:
      - name: ngx-conf-vol
        configMap:
          name: ngx-conf
      containers:
      - image: nginx:alpine
        name: nginx
        ports:
        - containerPort: 80
        volumeMounts:
        - mountPath: /etc/nginx/conf.d
          name: ngx-conf-vol

使用命令进行创建:

复制代码
kubectl apply -f ngx-dep.yaml

我们可以清楚的看到我们部署的两个ngxin deployment已经启动。

部署这个 Deployment 之后,我们就可以创建 Service 对象了,用的还是 kubectl apply:

复制代码
apiVersion: v1
kind: Service
metadata:
  name: ngx-svc
spec:
  selector:  # Service的标签选择器,和Pod标签匹配
    app: ngx-dep
  ports:
  - port: 80  # Service的端口
    targetPort: 80

可以看到,Kubernetes 为 Service 对象自动分配了一个 IP 地址"10.108.220.201",这个地址 段是独立于 Pod 地址段的。而且 Service 对象的 IP 地址还 有一个特点,它是一个"虚地址",不存在实体,只能用来转发流量。

想要看 Service 代理了哪些后端的 Pod,你可以用 kubectl describe 命令:

这里显示 Service 对象管理了两个 endpoint,分别是"10.224.154.194:80"和"10.224.154.197:80",初 步判断与 Service、Deployment 的定义相符,那么这两个 IP 地址是不是 Nginx Pod 的实际地 址呢?

我们还是用 kubectl get pod 来看一下,加上参数 -o wide:

把 Pod 的地址与 Service 的信息做个对比,我们就能够验证 Service 确实用一个静态 IP 地址 代理了两个 Pod 的动态 IP 地址,同时我们看到两个pod分别运行在不同的节点。

看到这里相信大家就明白了Service 怎么知道哪些 Pod 的 IP 是需要转发的,答案是Endpoints。

K8S 会自动维护一个和 Service 同名的 Endpoints 资源,它的作用就是记录所有符合 Service 标签选择 器的 Pod 的 IP 和端口。当 Pod 新增或删除时,Endpoints 会自动更新;当你访问 Service 时,Service 会先查 Endpoints,再把请求转发到 Endpoints 中的某个 Pod 上。

4.1 测试 Service 的负载均衡

因为 Service、 Pod 的 IP 地址都是 Kubernetes 集群的内部网段,所以我们需要用 kubectl exec 进入到 Pod 内部,再用 curl 等工具来访问 Service:

创建一个临时的 busybox 容器,循环向 Service 发送请求,观察返回的主机名变化(验证负载均衡):

复制代码
kc run -it --rm --image=busybox:1.35 test-lb -- sh

在容器内部执行循环请求:

复制代码
while true; do wget -qO- http://ngx-svc; sleep 1; done

预期结果 :每次返回的 host 字段会在两个 Pod 的名称之间切换,证明负载均衡生效。

我们再试着删除一个 Pod,看看 Service 是否会更新后端 Pod 的信息,实现自动化的服务发现:

由于 Pod 被 Deployment 对象管理,删除后会自动重建,而 Service 又会通过 controllermanager 实时监控 Pod 的变化情况,所以就会立即更新它代理的 IP 地址。通过截图你就可以看到有一个 IP 地址"10.224.154.194"消失了,换成了新的"10.224.154.196",它就是新创建的 Pod。

五、Service 的不同类型

由于 Service 是一种负载均衡技术,所以它不仅能够管理 Kubernetes 集群内部的服务,还能 够担当向集群外部暴露服务的重任。 Service 对象有一个关键字段"type",表示 Service 是哪种类型的负载均衡。前面我们看到的 用法都是对集群内部 Pod 的负载均衡,所以这个字段的值就是默认的"ClusterIP",Service 的 静态 IP 地址只能在集群内访问。

除了"ClusterIP",Service 还支持其他三种类型,分别是"ExternalName""LoadBalancer" "NodePort"。接下来就分别介绍一下他们。

5.1 ClusterIP:集群内访问,微服务间调用首选

特点:K8S 会给 Service 分配一个集群内部的虚拟 IP(仅集群内可访问),只能在集群中的 Pod 或节点上访问。

适用场景:微服务之间的内部调用,比如订单服务调用支付服务,两者都在 K8S 集群内,用 ClusterIP 最安全。

复制代码
apiVersion: v1
kind: Service
metadata:
  name: ngx-svc
spec:
  type: ClusterIP  # 类型为ClusterIP(默认类型,可省略)
  selector:  # Service的标签选择器,和Pod标签匹配
    app: ngx-dep
  ports:
  - port: 80  # Service的端口
    targetPort: 80

5.2 NodePort:开发测试用,外部直接访问

特点:在 ClusterIP 的基础上,在集群的每个节点上开放一个 "固定端口"(比如 30080),外部可以 通过 "节点 IP:NodePort" 访问服务。

适用场景:开发或测试环境中,需要从本地电脑访问 K8S 中的 Java 服务(比如调试接口),不用复杂 的配置。 注意:NodePort 的端口范围默认是 30000-32767,不能随便选。 示例配置:

复制代码
apiVersion: v1
kind: Service
metadata:
  name: ngx-svc-nodeport
spec:
  type: NodePort  
  selector:  # Service的标签选择器,和Pod标签匹配
    app: ngx-dep
  ports:
  - port: 80  # Service的端口
    targetPort: 80

通过上图就会看到"TYPE"变成了"NodePort",而在"PORT"列里的端口信息也不一样,除了集群内部使 用的"80"端口,还多出了一个"30950"端口,这就是 Kubernetes 在节点上为 Service 创建的专 用映射端口。因为这个端口号属于节点,外部能够直接访问,所以现在我们就可以不用登录集群节点或者进 入 Pod 内部,直接在集群外使用任意一个节点的 IP 地址,就能够访问 Service 和它代理的后 端服务了。使用集群中任何一个节点IP + port就能调用服务。

为了更好理解NodePort 与 Service、Deployment 的对应关系,因此画成了图,你看了应该就能更好地明白它 的工作原理

5.3 LoadBalancer:生产环境首选,公网访问 + 自动负载

特点:借助云服务商(阿里云、AWS 等)的负载均衡器,为 Service 分配一个公网 IP,外部请求先到 云负载均衡器,再转发到 Service 和 Pod。

适用场景:生产环境中,需要对外暴露的 Java 服务(比如用户端访问的 API 网关、前端调用的后端服务),稳定性和可用性要求高。示例配置:

复制代码
apiVersion: v1
kind: Service
metadata:
  name: ngx-svc-balancer
spec:
  type: LoadBalancer  
  selector:  # Service的标签选择器,和Pod标签匹配
    app: ngx-dep
  ports:
  - port: 80  # Service的端口
    targetPort: 80

配置后,云服务商(比如阿里云)会自动创建一个 SLB 负载均衡器,并分配公网 IP。外部用户访问这 个公网 IP,就能通过 API 网关调用后端的 Java 微服务,负载均衡由云 SLB 和 Service 共同实现。

5.4 ExternalName:访问集群外服务,不用硬编码 IP

特点:把 Service 映射到一个外部的 DNS 名称(比如mysql.abc.com),而不是集群内的 Pod。当访 问这个 Service 时,K8S 会自动把请求转发到外部的 DNS 地址。

适用场景:Java 服务需要调用集群外的服务,比如访问云数据库(MySQL、Redis)或第三方 API,- 不想在代码中硬编码 IP(避免 IP 变动导致服务不可用)。 示例配置:

复制代码
apiVersion: v1
kind: Service
metadata:
  name: ngx-svc-balancer
spec:
  type: ExternalName  
  selector:  # Service的标签选择器,和Pod标签匹配
    app: ngx-dep
  ports:
  - port: 80  # Service的端口
    targetPort: 80

调用方式:直接用 Service 名称访问,比如jdbc:mysql://external-mysql-svc:3306/ order_db,K8S 会把external-mysql-svc解析为mysql.abc.com,后续即使外部 MySQL 的 IP 变了 ,只要 DNS 不变,代码就不用改。

六、小结

对于开发者来说,K8S Service 不是 "可选组件",而是 "必用组件"------ 它解决了 Pod 动态 IP 的痛点,实现了微服务的自动负载均衡,还能灵活对接内部和外部服务。

最后快速回顾几个重点:

  • Service 的核心:为 Pod 提供稳定访问入口,本质是 "稳定 IP + Endpoints + 负载均衡";
  • 4 种类型的选择:内部调用用 ClusterIP,开发测试用 NodePort,生产公网用 LoadBalancer,访问外 部服务用 ExternalName;

Tips: 为了大家快速高效的学习,已经将文章提交到了git仓库,涵盖后端大部分技术,以及后端学习路线,仓库内容会持续更新,建议 Star 收藏 以便随时查看https://gitee.com/bxlj/java-article

相关推荐
阿里云云原生7 小时前
不改代码也能监控 AI Agent?揭秘 OBI 如何在内核层精准解析 GenAI 语义流量
云原生·agent
众人皆醒我独醉7 小时前
Pod 一直 Terminating,死活删不掉?三个凶手,每一个你都意想不到
面试·kubernetes
飞翔沫沫情9 小时前
K8s Alloy 采集 Pod 控制台日志
云原生·容器·kubernetes
潮起鲸落入海14 小时前
Kubernetes Metric Server, Quota and Limits
容器·kubernetes
梦想的颜色14 小时前
Docker 容器化本地部署 Claude Code 完整硬核教程|隔离沙盒 + 国产模型直连 + 持久化 Skill
运维·容器·ai 工程化·沙盒隔离·docker 本地部署·claude code硬核实战·国内国产模型接入
摇滚侠15 小时前
云原生 Java 架构师的第一课 K8s+Docker+KubeSphere+DevOps 19-25
java·云原生·kubernetes
渣渣盟15 小时前
Docker 运维常用命令手册(含扩容与实战)
运维·docker·容器
我叫张小白。16 小时前
一个微服务电商+社区项目(瓷韵app)的技术深度复盘
docker·微服务·云原生·架构
咕噜咕噜的猪大侠17 小时前
《K8s 核心知识梳理:Deployment 滚动更新、JobCronJob、DaemonSet 和 Service》
云原生·容器·kubernetes
虚心的百褶裙17 小时前
远程调用服务架构设计及zookeeper技术详解(下篇)
分布式·zookeeper·云原生