2022年2月24日,Go微服务框架rpcx作者晁岳攀(鸟窝)在2022云原生超级英雄会直播中,分享了如何在云原生环境下进行微服务发现。在没有Service Mesh或Istio框架的情况下,如何实现微服务发现?如何充分利用云原生的机制来实现微服务发现扩缩容或者故障容灾的形式?
基于环境变量的方式
传统的微服务发现平台,比如阿里的nacos、etcd、zookeeper等会把服务端注册到服务发现平台上,客户端去发现节点。但是在云原生的环境下,Pod并不会固定在某一个节点上,因为K8s会进行调度:扩缩容的时候会进行调度、有故障的时候也可能会被调度。这种情况下客户端如何发现服务端的Pod,进行相应的服务调用?在K8s的环境下,服务发现之后,定义了相应的Pod再定义服务。通过环境变化定义一组环境变量,再通过环境变量就可以发现服务的VIP和调用的端口,这样客户端就可以发现服务进行调用。
下图所展示的是定义了一个服务端的应用rpcx-sever-demo。以rpcx传统的微服务框架如何在云原生环境下进行服务发现来举例,图中显示服务端应用的副本是3,在发布Pod或者应用的时候,会取三个Pod提供服务。使用的镜像是比较简单的服务端测试程序,用来实现基本乘法,定义其端口为8972,继而使用命令把应用发布出去。
因为是使用环境变量的方式,所以接下来必须定义一个服务。下图服务的名称是rpcx-server-demo-service,使用的应用是上述定义的rpcx-server-demo应用,服务的端口是9981,集群内的其他容器组可以通过9981去访问Service,然后Service会把请求转到相应的port 8972的端口上,通过命令就可以把这个服务发布出去。
发布了服务端的应用和Service之后,可以检验一下相应的Pod和Service是否已经正常工作。通过命令get pods,显示有三个副本已经创建出来且运行正常,都是running的状态。下图中可以看到已经发布出来的服务以及服务的集群IP地址,通过这个IP地址和端口号就可以去访问了。
客户端如何使用环境变量查看IP地址和端口号?首先,把客户端的程序发布出去,在这里假设程序已经写好,然后写一个配置文件发布客户端,在这个客户端发布之后校验是否为正常发布。通过命令校验服务端相应的service的环境变量,使用printtenv命令可以打印出来相应环境容积内的环境变量。下图中只把rpcx这个环境变量输出校验。相应的环变量不需要手工去配置已经自动创建出来几个环境变量。通过其中一个环境变量或者主机名、端口就可以知道相应的端口号和class的IP地址。在客户端可以查询环境变量得到的相应的主机名和端口,得到了相应访问的classIP地址和端口就可以直接访问服务端了。
接下来是rpcx的配置。下图中服务的class IP和端口写出来后续都是rpcx的正常调用方法,调用的是算术乘法服务。把参数10和20填进去就可以不断循环调用这个服务,也可以查看log日志。通过环境变量的方式得到Pod形成的Service地址,Service会把相应的请求转到对应的Pod上。
但是会有一个问题:客户端调用的时候把所有的请求都发在了其中一个Pod上。在下图中可以看到刚刚发布的三个服务端Pod,只有第二个Pod被调用了。
因为rpcx框架是创建一个长连接,和服务端的长连接进行通讯,在长连接上不断把服务调用的请求发过去,然后在服务端的连接上收到这个请求。因此若创建了一个连接,这个连接在不中断或没有异常情况下,永远只和其中一个Pod进行通讯。所有的请求都是发在同一个Pod上,其他两个Pod处于空闲状态,在这种情况下服务端没有负载均衡的能力,这是它的一个缺点。
基于DNS的方式
K8s会为Services和Pods生成相应的DNS记录,通过DNS记录可以查到相应的Service和Pod。Service可以根据其名称去查询,Pod根据IP地址生成域名。在微服务情况下客户端想查某一个Service下面所有Pod的信息,只需要使用Service的域名,查出一组相应的Pod记录就可以实现负载均衡的能力。
直接使用服务的名称也可以访问。如下图演示,执行三次client端容器的ping服务,访问的分别是0.3、0.3、0.5,因此可以发现Pod的地址是随机的,不会固定在同一个地址上。
rpcx本身支持DNS的方式,如下图所示只需要把DNS的名称传进去,得到Pod的IP地址,之后再得到Pod的对外容器端口“8972”就可以进行调用了。服务器会定时查询DNS,如果发现DNS下面的节点有变化,比如其中一个Pod被下掉或者被迁移到其他节点上,可以马上获取到最新的节点信息。
但DNS有一个限制,消息的大小是固定的。如果Pod非常多,有几十、上百个,服务端只能查询出来部分Pod,且每次结果可能都不一样,这会导致漏掉一些服务端或服务端总是来回变化。但在服务端比较少量的情况下仍可以通过DNS的方式,这种调用方式本身是没有问题的。
通过k8s的API客户端从POD内读取服务的endpoints
从Pod内访问K8s的信息需要把权限设置好,允许Pod内能查询相关信息。下图所示通过“getPodsIPs”命令首先查询服务对应的Pod有哪些,利用查询出来的节点,为每一个endpoint设置一个链接,通过一些负载均衡的方式,比如轮巡、随机等方式去访问也能实现负载均衡。图中运行的程序实现了第一次查询,后续还需要启动一个定时任务查询,如果有变化,会通知rpcx client重新生成endpoint的信息来保证后续调用使用最新的服务器信息。
下图演示的是监控Pod IP的方式。把“app=rpcx-server-demo2”做一次筛选,得到要调用的相应服务的Pod,继而得到相应Pod的IP地址列表。其中可以发布定时任务:每隔一段时间,比如每隔10秒进行一次查询更新,这是一种比较好的查询服务端的方式,它不依赖于其他第三方应用,完全利用K8s自身的功能查询。唯一需要注意的是必须配置一些策略允许调用,否则会显示没有权限访问。
基于第三方服务发现平台的方式
传统的框架有etcd、consul、zookeeper,rpcx使用的redis,一些大厂的服务发现平台也各有不同,如阿里nacos,腾讯polaris,微博vintage等等。其他一些公司也有基于自己具体情况开发的服务发现平台,把服务端的信息注册到平台上,客户端访问相应的节点得到服务端的信息,就能发现这些服务端,然后再根据一些算法就可以访问这些服务并实现负载均衡。
在传统的方式下,上述内容可以操作,但是在K8s情况下服务端的节点都是一个个Pod,这些Pod需要把自己的信息注册到这些平台之上,如果某些Pod被迁移到其他的节点上,需要把自己新的信息注册到新的节点,在这种情况下应该怎么去做?
以nacos为例,在服务端程序启动的时候要把服务平台的信息通过环境变量的方式告诉服务端程序。图中把nacos的主机名、端口写出来,服务端通过环境变量就可以知道到去哪个nacos平台注册。服务端还需要把自己的Pod信息注册上去,因为在这种情况下其他rpcx的client就可以得到Pod的信息,通过这种方式得到机器的Pod IP地址放在环境变量里。
下图中程序得到了nacos的主机、IP地址和端口,就可以生成nacos的配置信息。在K8s情况下,通过环境变量传进来之后插件就可以自动把Pod信息注册到nacos上。
如下图所示,客户端也需要把nacos的地址传进来。图中定义了两个环境变量,由此得到第三方服务发现平台也就是nacos的信息,接着利用rpcx的nacos插件去访问nacos平台,通过这种方式就可以得到相应服务对应的各个Pod的信息。得到每个Pod信息之后,为每一个Pod建立连接,通过指定的负载均衡的方法就可以访问调用这些服务。因为图示情况下部署的是测试环境,可以访问外网nacos的测试服务器,在实际操作时首先要保证客户端和服务端可以访问服务发现的平台,这是非云原生环境下的传统服务发现方式。
在云原生的环境下,可以通过上述四种方式实现微服务发现。关于微服务发现的实践,大家有任何想法欢迎评论区留言交流。
云原生志愿者计划正在招募
精准把握技术趋势,深度学习新技术、新实践
扫描图片二维码立即申请加入