K8s SpringCloud服务从VM迁移到k8s历程总结

背景

公司业务部署都是部署在虚拟机里边,所以推动到k8s中总会遇到一些“曲折“,比如 有些服务部署在k8s中 ,有些”钉子户”依然部署在vm,那么vm和k8s网络问题该怎么解决呢?接下来我们针对这些问题找出合理的解决办法

场景

SpringCloud 的注册中心是用Eureka来实现的,通过服务注册和服务发现获取服务列表,服务提供方B 注册自己的信息IP 和端口到 Eureka Server ,服务消费方从Eureka Server 拿到 服务提供方的 服务列表,从而通过负载均衡进行远程通信,这个时候我们可以针对这个服务调用推理出VM和k8s 的调用场景

  • vm里边的A服务访问vm 里边的B服务,原有的处理场景
  • k8s里边的A服务访问k8s里边B服务,k8s集群内部访问网络互通
  • k8s里边的A服务访问vm里边的B服务,运行A服务的容器所在宿主机和vm B服务机器IP属于一个网络段所以也可以访问
  • vm A服务访问 k8s B服务,eureka 返回给A服务的是B服务注册的IP,也就是运行在k8s的pod IP,因为pod IP 是虚拟IP,所以A服务和B服务因为IP的访问方式不通最后访问失败

解决方式

针对于vm访问k8s IP 不通问题找出合理的解决办法,首先就是考虑的是vm如何能够访问到k8s集群里边的pod,一种方式是通过上层应用层方案来解决 比如通过service 和 ingress 流量导入,另一种方式底层的网络层就是 vm 的网络和 k8s集群的网络ipvs 打通。

现在我们先来说一下第一种方式 通过应用层来解决如何访问到Pod里的应用,
我们知道Pod IP 是跟随 Pod的生命周期变化的 ,所以我们不会直接访问Pod IP而是通过service 访问到Pod,那先来回顾一下service 的类型和适用场景

  • /ClusterIP/ (default) – 将服务暴露在集群内部的IP,此类型仅支持在集群内服务。
  • /NodePort/ – 将服务暴露在所选定每一个Node的同一端口,集群外可以通过:方式访问服务。
  • /LoadBalancer/ – 在当前的集群中创建一个外部的负载均衡,并为服务(service)指派一个固定的、外部的需要公有云的支持 并且收费。
  • /ExternalName/ – 使用一个随意的名称(在规格中指定)来暴露服务,并会返回一个带有名称的CNAME记录。此类型不使用代理,这种类型只在kube-dns v1.7上才支持。

第一种仅支持集群内部通信 那么vm 仍然访问不了,这种方式不可行。
第二种 集群外可以访问,但是需要知道 NodeIP和 NodePort ,这种可行但不完美,可能改动会很大
第三种 收费并且需要公有云支持 所以不考虑

第四种 将服务通过 DNS CNAME 记录方式转发到指定的域名(通过 spec.externlName 设定)。需要 kube-dns 版本在 1.7 以上。现有的版本是1.3 所以暂不考虑

通过上边的介绍用 service 直接访问 Pod内部好像没有一个没有一个完美的方式,那么还有其他的方式么? 这个时候可能就会想到 service 的 service 也就是ingress ,我们通过ingress 的域名来进行访问。


这个时候回顾一下通过ingress 生成的域名 访问到 Pod 的步骤,ingress 可以理解为 nginx 配置文件(用的ingress controller 是 nginx),这个配置文件通过域名绑定(serviceName + service port )到路由的service,然后通过service 找到要绑定(Target Port + label )的Pod 。

因为我的迁移要做到对用户(开发者)是无感知的,所以不会修改任何配置和代码,那么生成的这个域名怎么能让请求方拿到了呢? 那答案只有在vm 里的服务访问k8s 服务的时候返回的不是IP而是生成的ingress域名,所以要做到这种方式只有改Eureka 源码。

实现方式

方案:zone-1000 的 application 信息都拷贝一份,再对副本做如下修改:

  1. zone-1000 -> zone-1
  2. hostName -> Ingress 域名
  3. instanceId -> instanceId+k8s
  4. port -> 80

首页我们确定了解决方式,就是获取服务列表的时候把IP 换成对应的ingress 域名,在场景描述里边前三种方式都是互通的 所以就没有必要也换成域名了,所以那就只有给k8s里边的服务换成域名,那这时候可能有个疑问 , 我们在不改变客户端情况下,Eureka是怎么确定这个服务就是k8s的呢 ?公司内部的所有服务发布方式都是通过内部的一个发布系统我们简称为“嫦娥“,所以我们在发布的时候动了手脚就是加了一个启动参数-Deureka.instance.metadata-map.zone 这个就是选择当前服务所要注册的zone区是哪个,关于Eureka zone区的原理可以google一下。

因为嫦娥知道这个服务要发布到k8s里还是虚拟机里所以用它来区分当前是虚拟机还是k8s以及获取服务所在的zone区,
ks8设置 -Deureka.instance.metadata-map.zone=zone-1000
vm设置 -Deureka.instance.metadata-map.zone=zone-1

所以这个时候我们知道了zone-1000里边的服务都是部署在k8s里边的 然后我们就可以修改源码了,目的是在返回给客户端服务注册IP的时候我们要做个转换,然后阅读源码返回注册服务列表类名方法
ResponseCacheImpl.getValue

这个时候我们要把返回所有应用列表信息开始遍历,首先循环的是应用列表,和每个应用列表的实例(instanceInfo)当条件满足以下
instanceInfo.getMetadata().get(“zone”).equals(“zone-1000”) ,这个获取的zone就是前面说的启动参数加入的zone,满足这个条件就知道这个服务是部署在k8s里的,然后要把这个部署在k8s的应用实例copy一份 放入到zone-1区 也是就 vm所在区,为什么是copy而不是删除旧的加入新的呢 ?如果删除了zone-1000里边的这个实例 那么k8s集群内的服务访问也变成了ingress 域名的形式了,比如上述场景中的第二条 这样的网络上并没有集群内部IP的方式效率高。在copy之后两个zone区都会有这个实例,由于前边我们设置了每个服务发布的时候所要访问zone区的范围,所以当前的zone区里边的请求服务优先访问当前zone区注册服务。这样vm访问k8s的问题就解决了。

然后我们针对这次的改动可以思考一下对之前的访问是否有所影响,比如上述场景中所描述的前三种场景,我们这次修改只有应用发布才能知道当应用要步入的zone区,简而言之就是重新发布部署才能解决vm和k8s网络通信问题,那比如之前没有发布过的应用怎么办呢 ?那它拿到的实例有可能是zone-1区的 也有可能是zone-1000,这个时候访问zone-1000肯定还是有问题,针对这种情况我们要借助SpringCloud全家桶中的ribbon来实现了,具体怎么实现大家不妨思考一下。

总结

这是从vm迁移到k8s中刚开始的问题 ,往后会有更多有挑战的问题等着我们来解决来踩坑,然后关于ingress域名生成都是指定统一规范 比如 a.env.eureka.com,不然没有规范eureka就不知道这个域名是多少了,所有当有了规范在实施起来就顺手成章了。

你可能感兴趣的:(K8s SpringCloud服务从VM迁移到k8s历程总结)