Build MicroService With Spring Cloud And Rancher
分享人:郑云龙
时间:2016–7–20
睿云智合持续交付产品负责人,在敏捷和DevOps领域有丰富经验的实践,过去作为敏捷和DevOps技术教练向多家大型企业提供咨询和培训服务。
1, Aganda
今天给大家分享的内容是:《Build MicroService With Spring Cloud And Rancher》主要内容:我们将演示如何通过Spring Cloud和Rancher构建一个具有弹性的微服务应用。并且在这个过程中当中我们主要遇到的问题,以及最后如何解决。
对于一个最简单的微服务应用而言,一个最基本的结构如上所示:
- 我们需要一个服务发现和注册服务帮助我们管理和发现新的服务;
- 当我们从一个单体应用到几个,几十个微服务时,服务的配置也成了一个非常棘手的问题,统一的配置管理必不可少;
- 为了简化我们的客户端调用我们可能需要一个轻量级API Gateway来统一代理我们的所有请求,同时可以做统一的安全控制;
- 同时在微服务下我们应该遵循Build For Failure的原则,当服务发生失败时我们能够隔离这些失败的服务,从而需要引入Circuit Breaker熔断器;
- 除此之外,同一个服务可能有一个到多个实例,当我们访问服务时,我们还需要一个负载均衡器,帮助我们合理的分发我们的API请求。
2, Build MicroService With Spring Cloud
而Spring Cloud项目正是帮助我们解决以上问题的利器,让我们可以更加专注于我们服务本身业务逻辑的开发,而将服务治理所需的工作都统统交给框架来完成。
- Eureka提供了统一的服务发现和注册能力;
- Ribbon和Hystrix则在服务和服务之间引入了负载均衡以及熔断的能力;
- Zuul则作为一个轻量级的路由和代码服务让我们可以快速建立一个API Gateway的能力。
3, Service Discovery With Spring Cloud Netflix: Eureka
以Eureka提供的服务注册和发现能力为例,作为一个微服务实例,在启动时我们将会主动向Eureka Server进行注册。Eureka Server服务维护当前服务的实例列表以及状态健康检查。 而作为服务的使用者,我们首先需要向eureka server请求当前的服务实例,之后再将请求发送给API的真正提供者。
4, Client Side Load Balance: Ribbon
同时Spring Cloud的Ribbon项目还提供了一个客户端的负载均衡器,可以根据当前服务的运行状态来讲请求发送到适合的实例。
5, Quick Start Demo
这里我们展示一下Spring Cloud是如何帮助我们快速搭建一个微服务架构的应用的。
这里我们以一个最简单的spring cloud应用为例,这里我们包含3个主要服务:Eureka Server, Hello Server以及Hello Client。
首先基于Spring Boot我们可以@EnableEurekaServer可以快速创建一个EurekaServer的服务,基本不需要任何代码量。
同时我们基于Spring Boot创建另外一个项目Hello Sever,提供一个非常简单的API返回一串字符。在application.yml当中我们配置该服务的名称为HelloServer, 端口为7111,并且配置eureka server的地址,在应用启动时,将会主动注册服务相关信息。
同样我们建立一个Hello Client的微服务实例,这里唯一的区别是我们使用Spring Cloud的FeignClient声明了一个接口,该注解告诉Spring Cloud当调用HelloClient的hello方法时,需要向HelloServer的实例的”/”地址发送一个GET请求,并且返回结果是一个字符串。
同样的我们在application.yml文件中也配置了eureka相关的信息,为Hello Client建立一个Rest API,该API实际调用的是Hello Sever提供的服务。
依次启动Eureka Server, Hello Server, Hello Client. 我们得到了一个最简单的微服务架构的应用,我们将会得到如上的运行结果。当访问Hello Client的Rest API时,实际请求转发到了Hello Server并且得到相应的结果返回给用户。
这个是如何工作的呢? 我们简单解释一下,和刚才图一样:
1 Hello Server向Eureka Server进行注册名为HelloServer的服务实例。
2 Eureka Server得到请求后保存了HelloServer的实例列表信息(IP地址以及其他状态信息)。
3 Hello Client请求http://localhost:7211时,调用了HelloClient的hello()方法,该方法的@FeginClient(“HelloServer”)注解告诉Hello Client我们需要先向Eureka Server请求HelloServer的服务实例列表,之后再根据返回的实例列表将请求发送给HelloServer其中一个实例。
这里需要注意Spirng Cloud中的一个配置项eureka.instance.preferIpAddress默认情况下该配置为True. 表示Spring Cloud优先使用HelloServer注册的IP地址,如果访问不成功我们则使用HelloServer的名称作为默认的域名发送请求。同理当eureka.instance.preferIpAddress为false时,Spring Cloud则直接使用默认的spring.application.name也就是HelloServer作为域名发送请求。
所以对于部署基于Spring Cloud的微服务应用时,我们对于网络环境的基本要求是:1, 各个服务实例能够访问eureka-server;
2, 各个服务实例之间能够通过IP地址或者DNS域名相互访问。
6, Rancher: A Complete Platform for Running Containers
Rancher作为一Production目标的容器管理平台,当然是我们去运行,管理和维护微服务的首选运行平台之一。 当我们把我们所有的服务制作为Dokcer镜像之后,我们可以通过docker-compose.yml来编排我们的服务,如上Client-Service以及Hello-Service都与Eureka相连,并且设置eureka的访问地址为eureka-server,与我们application.yml中配置的eureka url相一致。
通过Rancher启动docker-compose.yml之后,我们可以在eureka-server页面上看到相关的服务实例信息。
通过Rancher我们可以非常方便的对我们的微服务应用进行管理,同时提供scale相关的能力支持。 这个时候重新尝试访问我们的Server服务, 一切正常。得到了相应的API返回值。
我们再尝试访问我们的Client服务呢。。。出错了。。。
7, Rancher Network Service
我们来分析一下出错的原因:
首先我们的服务和服务是分布式部署在不同的Rancher Agent上的。Rancher使用managed network来完成跨主机的容器与容器之间的相互连接。同时Docker也有自己默认的的bridge network。 但是bridge network只能保证同一个Host上的容器是互通的。 而client以及server在启动时,获取的默认网络地址则是birdge network的ip地址。 所以当访问client的rest api时,client向eureka获取的IP地址是不可访问的,之后又尝试了spring.application.name也就是http://HelloServer:7111同样出错,因为HelloServer是没有任何DNS服务能够解析的。所以得到了我们的错误页面。如下图所示:
再直观一点的情况如下图所示:
所以在该模式下,我们的各个服务虽然都能向eureka server注册相关信息,但是服务和服务之间是不能相互访问的。
如何解决?
8, Rancher Internal DNS Services
首先:Rancher提供的内部DNS服务,在容器中我们可以通过<service_name>或者<service_name>.<stack_name>来访问其他的服务容器实例。
那么我们假如启动硬编码client以及server时设置hostname的配置,比如对于client 我们配置eureka.instance.hostname=client-service,对于server我们配置eureka.instance.hostname=hello-service。 这样当服务和服务之间相互访问时,则会使用我们配置的eureka.instance.hostname作为域名进行访问。 但是问题在于client-service和hello-service的名字实际上是和docker-compose相对应的。 如果docker-compose.yml修改了各个服务的名字。 那服务和服务之间同样是不可相互访问的。
9, Rancher Metadata Services
如何解决?
Rancher Metadata Service. 通过metadata service我们可以通过使用HTTP API的方式来获取到我们的服务以及容器的所有信息。
举例来说,通过在容器内访问:http://rancher-metadata/latest/self/container/service_name我们便可以获取到该容器的server_name,除此之外,包括容器的健康状态,hostname,primary_ip以及其他等等信息我们都可以通过该方式来进行获取。
10, Solution And Summary
综合以上问题以及Rancher平台所提供的能力,在Dockerfile中我们使用单独的shell文件作为启动脚本,在该启动脚本中我们首先调用rancher的metadata service获取该container的service_name, 并且将该service_name作为eureka.instance.hostname的值作为jar包的启动参数即可。
在这个案例中,我们使用Spring Cloud快速搭建了一个简单应用。 同时利用Racnher提供的容器管理能力,以及DNS Service和Metadata Service解决在容器分布式部署状态下的一些主要问题。希望能对大家有一定的参考。
关于本次分享的源码已经发布在github: https://github.com/yunlzheng/spring-cloud-with-rancher.git