当前位置: 首页 > news >正文

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-svcports:- 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: 2selector:matchLabels:app: ngx-deptemplate:depmetadata:labels:app: ngx-depspec:volumes:- name: ngx-conf-volconfigMap:name: ngx-confcontainers:- image: nginx:alpinename: nginxports:- containerPort: 80volumeMounts:- mountPath: /etc/nginx/conf.dname: 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-depports:- 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-depports:- 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-depports:- 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-depports:- 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-depports:- 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。

http://www.dtcms.com/a/403702.html

相关文章:

  • 网站建设有什么理论依据外包公司会抽取多少工资
  • 积加科技音视频一面
  • phpstudy如何搭建网站萝岗微信网站建设
  • Spring Boot 高级特性:从原理到企业级实战​
  • 个人网站用凡科建站好吗wordpress只允许中文评论
  • Qt常用控件之按钮
  • 祝贺网站改版建设教育网站的目的
  • 网站建设验收需要注意什么怎样下载网页上的视频
  • 分布式专题——20 Kafka快速入门
  • SSH公钥私钥!进阶!SSH与Git!
  • 网站必须兼容哪些浏览器中核正式员工年收入
  • 珠海网站品牌设计公司简介网络新闻专题做的最好的网站
  • keepalived服务器
  • AI写的超级好用的课堂互动系统
  • 山东建设机械协会网站课程网站建设的设计报告
  • 第四部分:Netty核心源码解析(下)
  • 攻克 大 Excel 上传难题:从异步处理到并发去重的全链路解决方案
  • 【双光相机配准】红外相机与可见光相机配准方案
  • 中国建设银行网站个人客户wordpress 主题显示
  • 开源超级终端PuTTY改进之:增加点对点网络协议IocHub,实现跨网段远程登录
  • 帮别人做网站如何备案wordpress video plugin
  • 118. 杨辉三角(dp)
  • 济宁网站开发招聘威海建设集团官方网站
  • 【QT】QPainter的使用
  • 北京代理网站备案成都市建设工程交易中心网站
  • PyTorch 数据处理工具箱与可视化工具
  • python的高阶函数
  • Python请求示例JD商品评论API接口,json数据返回
  • Json格式化处理碰到的问题
  • 驱动开发(4)|鲁班猫rk356x镜像编译,及启用SPI控制器驱动