SpringCloud + kubernetes集群内的HTTP请求流程

背景

最初想到这个问题的原因是,编写代码的时候,有时候会用@Loadbalance的RestTemplate,有时候则直接用@Bean的方式获取,所以才会产生这个疑问,在学习过程中,逐步发现以Kubernetes为容器编排和SpringCloud作为服务治理的系统中,服务间的请求还涉及到了kube-proxy/kube-dns/iptables/eureka/ribbon/ingress等内容,因此逐个梳理一下,这些进程/组件在服务间调用过程中的作用到底是啥。

为便于理解,分两种场景,一步步的解释一次Http请求的过程。

  1. 来自客户端的一次http请求
  2. 来自集群内的一次http请求

场景一:来自客户端的一次http请求

Step1:客户端对域名www.xxx.com发出请求,本质上是一个TCP连接,所以需要通过客户端所属网络DNS获取域名机器的IP地址,然后建立连接,发出请求。
在这里插入图片描述
Step2:域名节点即为服务端的一台机器,在Kubernetes集群中就是其中的一个node,可以是master也可以不是,此时该机器需要有一个Service进程接受Client的请求,所以域名节点会有一个nginx
在这里插入图片描述

Step3:一般来说,此时nginx启动后,就能接收并响应Client的请求,不过在Kubernetes集群中,就需要将这个nginx运行在一个pod里,所以可以选择Deployment/Statefulset任意的一种方式,部署nginx镜像,同时通过hostnetwork的方式,监听主机的80端口,但是这种方式存在一个弊端:当我们有新服务或者原有nginx的路由配置需要变更,此时nginx需要重启,会导致服务中断,为解决这个问题,引入了Ingress,所以就变成了下面的样子。
在这里插入图片描述
Step4:在使用Ingress的情况下,nginx的配置修改就不需要再重启了,而是每次通过ingress-controller + ingress的方式来更新,Ingress是Kubernetes的资源类型之一,因此通过k8s-api-server可以查询到(在etcd中持久化),ingress-controller就可以通过定时拉取更新的方式感知到ingress的变化,当识别到更新时,还是通过k8s-api-server获取到nginx所在pod,然后进入pod去动态修改nginx的配置。需要注意的是:ingress是kubernetes资源,ingress-controller和nginx一样是运行的进程,早期的nginx是不支持ingress-controller,后来才出的nginx-ingress-controller,相当于将上述的ngxin + ingress-controller合并在一起,镜像启动后即可实现对ingress资源的监听和对nginx配置的刷新(你也可以选择traefik-ingress-controller作为网关)

一个相对比较详细的流程如下图:
在这里插入图片描述
集成ingress-controller的网关横向对比
在这里插入图片描述

Step5:请求已经顺利到达网关,下一步就是如何达到我们的Service了。在ingress-controller已经生效的情况下,通过配置ingress来指定路由和服务的关系,如果系统中使用了网关服务,可以直接在nginx-ingress-controller的配置文件直接配置backend,指向网关服务,这样相当于所有的流量经过nginx,都会默认请求到网关服务。如果没有这种场景,可以按照如下方式配置ingress。

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: test-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - http:
      paths:
      - path: /testpath
        backend:
          serviceName: test
          servicePort: 80

Step6:nginx是Kubernetes中的一个pod,网关或我们的其他服务也同样是pod,那么无论是通过nginx配置backend或ingress配置路由,都是pod和pod之间的请求转发(通信),是如何做到的?这里先mark一下,答案是通过Kubernetes的网络实现,这部分内容将在场景2中展开。

回顾一下“来自客户端的一次http请求”过程,主要包括以下几个阶段:

  1. http请求根据域名解析到达对应的主机(途中Nginx-ingress-controller所在机器)
  2. 该主机Nginx-ingress-controller是主机网络部署(yaml中hostnetwork=true),监听80
  3. Nginx收到请求后,通过ingress-controller过滤K8S集群中的ingress,根据ingress配置的rule查询是否匹配
  4. 如果匹配则转发请求到对应ingress配置的服务(在K8S集群内)
  5. 如果未匹配则直接转发至back-end(Nginx-ingress-controller的daemonSet yaml中配置)

常见的几个问题:

  • 问题1:Ingress的配置如何更新
    ingress-controller会定期通过k8s api server更新当前已有的ingress(不用重启服务的情况下修改路由规则)

  • 问题2:Nginx-ingress-controller和ingress、nginx、ingress-controller啥关系
    较早版本中ingress、nginx、ingress-controller是分离的,nginx接收请求,ingress-controller负责更新ingress,ingress配置独立的路由规则,后来版本将nginx和ingress-controller合并为Nginx-ingress-controller

  • 问题3:转发请求的时候,如何实现LB
    请求转发发生在识别到ingress或通过back-end转发,这两部分配置的均为K8S的Service(简称svc),LB通过svc到pod来实现(这部分内容将在场景2中展开)

场景二:来自集群内的一次http请求

集群内服务间的请求,例如服务A和服务B相互请求,就需要A和B都知道对方的ip地址才行,因此需要服务注册发现,服务的注册发现,可以通过两种方式来实现,为了能够弄清楚集群内服务间请求过程,我们要分开讨论:

  1. 通过Kubernetes(kube-dns)作为服务注册发现实现集群内服务间通信
  2. 通过SpringCloud(Eureka)作为服务注册发现实现集群内服务间通信

在场景一种,一个http请求到达后,会通过kube-dns的方式将请求从nginx转发到网关(第一种方式),此时网关就可以选择上述的两种方式来实现集群内的通信。

场景二的第一种情况,通过Kubernetes(kube-dns)实现集群内服务间通信

集群内的服务A,请求服务B,发出一个http请求,过程如下:
Step1:服务B在pod中,而pod的IP是可变的,DNS是服务发现的最基本手段,服务A得预先知道服务B的名称,通过B的服务名称向服务B发送请求,通过DNS实现从服务名到IP的映射
在这里插入图片描述

Step2:从服务A向服务B的请求,首先通过DNS解析获取对应pod的IP,在一个pod中,我们可以看到/etc/resolve.conf文件配置了DNS,通过对比发现DNS的IP地址就是kube-dns的IP地址,也就是说,当我们通过B服务的service名称访问时,就会通过kube-dns获取到对应服务的IP地址,此时就可以通过IP地址建立连接,发送请求

Step3:通过比对我们发现,DNS的IP地址并不是kube-dns的pod的ip地址,而是kube-dns的Service的IP地址,而且我们使用nslookup可以看到,访问B服务名称返回的IP地址也不是B服务的pod的IP地址,而是B服务的Service的IP地址,也就是说,在请求发送给B服务的Service的IP地址后,又经过了一次转发过程。

在这里插入图片描述

Step4:Service本身用于解决服务发现时pod的IP可变的问题,解决的方法是通过建立如下的关系,当pod的IP发生改变时,更新从EndPoint到pod的映射关系,而Service到EndPoint的关系不变,Service的IP本身也不会改变,通过kubectl get svc、kubectl get endpoints可以看到他们的关系。因此请求的目标IP虽然是Service B,但最终请求还是会根据EndPoint指向pod B的IP地址。

在这里插入图片描述
Step5:当请求转发到Service B的IP地址后,是通过iptables进行了请求的转发,实现转发请求到pod B的IP,关于iptables,我们在kubernetes网络中也可以配置视同ipvs、eBPF等其他方式来实现,小规模集群的情况下iptables就可以了。

  1. 逻辑链路上看,向pod B的请求实际上是通过了Service B转发到了pod B
  2. 物理链路上看,向pod B的请求经过Kubernetes网络(这个不详细展开了,参考CNI概念),从node1到node2,然后经过iptables的处理转发给了pod B的IP

下面这个图解释着两种过程:
在这里插入图片描述
回顾一下“通过Kubernetes(kube-dns)实现集群内服务间通信”过程,主要包括以下几个阶段:

  1. pod A向pod B发送,先为pod B创建Service
  2. Service B创建完成后,会自动关联pod B并为其创建Endpoint B
  3. kube-proxy通过轮询api-server发现Service B并为其修改所有node上的iptables
  4. kube-dns通过轮询api-server发现Service B并为其增加DNS记录
  5. Service-Controller、Endpoint-Controller通过轮询api-server发现Service B,及时更新他们的变化
  6. 此时请求从pod A发出,pod A中通过resolve.conf配置的nameService查询kube-dns,获取B的Service IP
  7. 向B的Service IP发出请求,经过kubernetes网络(基于CNI),首先转发到Service B对应物理节点,对应物理节点通过iptables规则,经请求转发到pod B所在节点

常见的几个问题:

  • 问题1:为什么kube-dns可以返回服务B对应的Service的IP地址
    kube-dns在运行时,会不断通过api-server查询更新当前集群的所有Service资源,并根据Service资源更新Kubernetes集群内的Service和IP映射关系

  • 问题2:请求向Service发出后如何实现转发到对应的pod所在节点

  • 问题3:负载均衡在哪个阶段发生

场景二的第二种情况,通过SpringCloud(Eureka)实现集群内服务间通信

我们也可以选择不通过kube-dns转发请求,本文开头提到的@Loadbalance就是本节要讨论的方式。对照上述流程,本节其实对应的DNS角色就是Eureka,在使用过程中配合Ribbon共同完成功能。分工角色类似于:
在这里插入图片描述
每个微服务在使用Eureka会在本地启动一个EurekaDiscoveryClient,通过心跳的方式和EurekaServer保

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值