本文是《spring-cloud-kubernetes实战系列》的第四篇,主要内容是在kubernetes上部署两个应用:Web-Service和Account-Service,通过spring-cloud-kubernetes提供的注册发现能力,实现Web-Service调用Account-Service提供的http服务;
本文由以下段落组成:
本次实战的环境和版本信息如下:
上面的linux、minikube、java、maven,请确保已准备好,linux环境下minikube的安装和启动请参考 《Linux安装minikube指南 》。
SpringCloud环境最重要的功能是注册发现服务,因此将SpringCloud应用迁移到kubernetes环境时,开发者最关心的问题是在kubernetes上如何将自身服务暴露出去,以及如何调用其他微服务。
先看看普通SpringCloud环境下的注册发现,下图来自spring官方博客,地址是: https://spring.io/blog/2015/07/14/microservices-with-spring,
由上图可见,应用Account-Service将自己注册到Eureka,这样Web-Service用"account-service"就能在Eureka找到Account-Service服务的地址,然后顺利发送RestFul请求到Account-Service,用上其提供的服务。
如果将上面的Web-Service和Account-Service两个应用迁移到kubernetes上之后,注册发现机制变成了啥样呢?
第一种:沿用上图的方式,将Eureka也部署在kubernetes上,这样的架构和不用kubernetes时没有啥区别;
第二种,就是今天要实战的内容,使用spring-cloud-kubernetes框架,该框架可以调用kubernetes的原生能力来为现有SpringCloud应用提供服务,架构如下图所示:
上图表明,Web-Service应用在调用Account-Service应用的服务时,会用okhttp向API Server请求服务列表,API Server收到请求后会去etcd取数据返回给Web-Service应用,这样Web-Service就有了Account-Service的信息,可以向Account-Service的多个Pod轮询发起请求;
上图有个细节请注意:WebService应用并不是直接将请求发送给Account-Service在kubernetes创建的service,而是直接发送到具体的Pod上了,之所以具有这个能力,是因为spring-cloud-kubernetes框架通过service拿到了Account-Service对应的所有Pod信息(endpoint),此逻辑可以参考源码KubernetesServerList.java,如下所示:
publicListgetUpdatedListOfServers(){//用namespace和serviceId做条件,得到该服务对应的所有节点(endpoints)信息Endpoints endpoints=this.namespace!=null?this.client.endpoints().inNamespace(this.namespace).withName(this.serviceId).get():this.client.endpoints().withName(this.serviceId).get();Listresult=newArrayList();if(endpoints!=null){if(LOG.isDebugEnabled()){LOG.debug("Found ["+endpoints.getSubsets().size()+"] endpoints in namespace ["+this.namespace+"] for name ["+this.serviceId+"] and portName ["+this.portName+"]");}//遍历所有的endpoint,取出IP地址和端口,构建成Server实例,放入result集合中for(EndpointSubset subset:endpoints.getSubsets()){if(subset.getPorts().size()==1){EndpointPort port=subset.getPorts().get(FIRST);for(EndpointAddress address:subset.getAddresses()){result.add(newServer(address.getIp(),port.getPort()));}}else{for(EndpointPort port:subset.getPorts()){if(Utils.isNullOrEmpty(this.portName)||this.portName.endsWith(port.getName())){for(EndpointAddress address:subset.getAddresses()){result.add(newServer(address.getIp(),port.getPort()));}}}}}}else{LOG.warn("Did not find any endpoints in ribbon in namespace ["+this.namespace+"] for name ["+this.serviceId+"] and portName ["+this.portName+"]");}returnresult;}
理论分析已经完成,接下来就开始实战吧
如果您不打算写代码,也可以从GitHub上下载本次实战的源码,地址和链接信息如下表所示:
名称 | 链接 | 备注 |
---|---|---|
项目主页 | https://github.com/zq2599/blog_demos | 该项目在GitHub上的主页 |
git仓库地址(https) | https://github.com/zq2599/blog_demos.git | 该项目源码的仓库地址,https协议 |
git仓库地址(ssh) | [email protected]:zq2599/blog_demos.git | 该项目源码的仓库地址,ssh协议 |
这个git项目中有多个文件夹,本章的Account-Service源码在spring-cloud-k8s-account-service文件夹下,Web-Service源码在spring-cloud-k8s-web-service文件夹下,如下图红框所示:
下面是详细的编码过程;
Account-Service服务是个很普通的springboot应用,和spring-cloud-kubernetes没有任何关系:
4.0.0 org.springframework.boot spring-boot-starter-parent 2.1.1.RELEASE com.bolingcavalry account-service 0.0.1-SNAPSHOT account-service Demo project for Spring Cloud service provider run in kubernetes 1.8 2.1.1.RELEASE false false false 3.5 2.8.2 2.18.1 2.21.0 3.5.37 2.1.1.RELEASE org.springframework.boot spring-boot-dependencies pom import ${spring-boot.version} org.springframework.boot spring-boot-starter ${springcloud.version} org.springframework.boot spring-boot-starter-web ${springcloud.version} org.springframework.boot spring-boot-maven-plugin ${spring-boot.version} repackage org.apache.maven.plugins maven-deploy-plugin ${maven-deploy-plugin.version} true org.apache.maven.plugins maven-surefire-plugin ${maven-surefire-plugin.version} true false io.fabric8 fabric8-maven-plugin ${fabric8.maven.plugin.version} fmp resource kubernetes io.fabric8 fabric8-maven-plugin ${fabric8.maven.plugin.version} fmp resource build NodePort
由上面的pom.xml内容可见,account-service应用是个简单的web应用,和SpringCloud、spring-cloud-kubernetes都没有任何关系,和其他springboot唯一的不同就是用到了fabric8-maven-plugin插件,可以方便的将应用部署到kubernetes环境;
spring:application:name:account-serviceserver:port:8080
@RestControllerpublicclassAccountController{privatestaticfinalLogger LOG=LoggerFactory.getLogger(AccountController.class);privatefinalString hostName=System.getenv("HOSTNAME");/**
* 探针检查响应类
* @return
*/@RequestMapping("/health")publicStringhealth(){return"OK";}@RequestMapping("/")publicStringribbonPing(){LOG.info("ribbonPing of {}",hostName);returnhostName;}/**
* 返回hostname
* @return 当前应用所在容器的hostname.
*/@RequestMapping("/name")publicStringgetName(){returnthis.hostName+", "+newSimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(newDate());}}
mvn cleaninstallfabric8:deploy -Dfabric8.generator.from=fabric8/java-jboss-openjdk8-jdk -Pkubernetes
执行成功后控制台输出如下:
...[INFO]Installing /usr/local/work/k8s/ribbon/spring-cloud-k8s-account-service/target/classes/META-INF/fabric8/kubernetes.json to /root/.m2/repository/com/bolingcavalry/account-service/0.0.1-SNAPSHOT/account-service-0.0.1-SNAPSHOT-kubernetes.json[INFO][INFO]<<
[[email protected] spring-cloud-k8s-account-service]# kubectl get deploymentsNAME READY UP-TO-DATE AVAILABLE AGE
account-service 1/1 1 1 69m[[email protected] spring-cloud-k8s-account-service]# kubectl get servicesNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)AGE
account-service NodePort 10.105.157.2018080:32596/TCP 69m
kubernetes ClusterIP 10.96.0.1443/TCP 8d
[[email protected] spring-cloud-k8s-account-service]# minikube service account-service --urlhttp://192.168.121.133:32596
可见account-service的服务可以通过这个url访问: http://192.168.121.133:32596
Web-Service服务是个springboot应用,用到了spring-cloud-kubernetes提供的注册发现能力,以轮询的方式访问指定服务的全部pod:
4.0.0 org.springframework.boot spring-boot-starter-parent 2.1.1.RELEASE com.bolingcavalry web-service 0.0.1-SNAPSHOT web-service Demo project for Spring Cloud service consumer run in kubernetes 1.8 2.1.1.RELEASE false false false 3.5 2.8.2 2.18.1 2.21.0 3.5.37 1.0.1.RELEASE 2.1.1.RELEASE org.springframework.boot spring-boot-dependencies pom import ${spring-boot.version} org.springframework.cloud spring-cloud-kubernetes-core ${springcloud.kubernetes.version} org.springframework.cloud spring-cloud-kubernetes-discovery ${springcloud.kubernetes.version} org.springframework.cloud spring-cloud-starter-kubernetes-ribbon ${springcloud.kubernetes.version} org.springframework.cloud spring-cloud-commons ${springcloud.version} org.springframework.boot spring-boot-starter ${springcloud.version} org.springframework.boot spring-boot-starter-web ${springcloud.version} org.springframework.cloud spring-cloud-starter-netflix-ribbon ${springcloud.version} org.springframework.cloud spring-cloud-starter-netflix-hystrix ${springcloud.version} org.springframework.boot spring-boot-maven-plugin ${spring-boot.version} repackage org.apache.maven.plugins maven-deploy-plugin ${maven-deploy-plugin.version} true org.apache.maven.plugins maven-surefire-plugin ${maven-surefire-plugin.version} true false io.fabric8 fabric8-maven-plugin ${fabric8.maven.plugin.version} fmp resource kubernetes io.fabric8 fabric8-maven-plugin ${fabric8.maven.plugin.version} fmp resource build NodePort
spring:application:name:web-serviceserver:port:8080backend:ribbon:eureka:enabled:falseclient:enabled:trueServerListRefreshInterval:5000hystrix.command.BackendCall.execution.isolation.thread.timeoutInMilliseconds:5000hystrix.threadpool.BackendCallThread.coreSize:5
packagecom.bolingcavalry.webservice;importcom.netflix.client.config.IClientConfig;importcom.netflix.loadbalancer.AvailabilityFilteringRule;importcom.netflix.loadbalancer.IPing;importcom.netflix.loadbalancer.IRule;importcom.netflix.loadbalancer.PingUrl;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.context.annotation.Bean;/**
* @Description: ribbon配置类
* @author: willzhao E-mail: [email protected]
* @date: 2019/6/16 11:52
*/publicclassRibbonConfiguration{@AutowiredIClientConfig ribbonClientConfig;/**
* 检查服务是否可用的实例,
* 此地址返回的响应的返回码如果是200表示服务可用
* @param config
* @return
*/@BeanpublicIPingribbonPing(IClientConfig config){returnnewPingUrl();}/**
* 轮询规则
* @param config
* @return
*/@BeanpublicIRuleribbonRule(IClientConfig config){returnnewAvailabilityFilteringRule();}}
packagecom.bolingcavalry.webservice;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;importorg.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;importorg.springframework.cloud.client.discovery.EnableDiscoveryClient;importorg.springframework.cloud.client.loadbalancer.LoadBalanced;importorg.springframework.cloud.netflix.ribbon.RibbonClient;importorg.springframework.context.annotation.Bean;importorg.springframework.web.client.RestTemplate;@[email protected]@[email protected](name="account-service",configuration=RibbonConfiguration.class)publicclassWebServiceApplication{publicstaticvoidmain(String[]args){SpringApplication.run(WebServiceApplication.class,args);}@[email protected](){returnnewRestTemplate();}}
packagecom.bolingcavalry.webservice;importcom.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;importcom.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Service;importorg.springframework.web.client.RestTemplate;importjava.text.SimpleDateFormat;importjava.util.Date;/**
* @Description: 这里面封装了远程调用account-service提供服务的逻辑
* @author: willzhao E-mail: [email protected]
* @date: 2019/6/16 12:21
*/@ServicepublicclassAccountService{@AutowiredprivateRestTemplate restTemplate;@HystrixCommand(fallbackMethod="getFallbackName",commandProperties={@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="1000")})publicStringgetDataFromSpringCloudK8SProvider(){returnthis.restTemplate.getForObject("http://account-service/name",String.class);}/**
* 熔断时调用的方法
* @return
*/privateStringgetFallbackName(){return"Fallback"+", "+newSimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(newDate());}}
packagecom.bolingcavalry.webservice;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RequestParam;importorg.springframework.web.bind.annotation.RestController;/**
* @Description: 测试用的controller,会远程调用account-service的服务
* @author: willzhao E-mail: [email protected]
* @date: 2019/6/16 11:46
*/@RestControllerpublicclassWebServiceController{@AutowiredprivateAccountService accountService;/**
* 探针检查响应类
* @return
*/@RequestMapping("/health")publicStringhealth(){return"OK";}/**
* 远程调用account-service提供的服务
* @return 多次远程调返回的所有结果.
*/@RequestMapping("/account")publicStringaccount(){StringBuilder sbud=newStringBuilder();for(inti=0;i<10;i++){sbud.append(accountService.getDataFromSpringCloudK8SProvider()).append("
");}returnsbud.toString();}}
mvn cleaninstallfabric8:deploy -Dfabric8.generator.from=fabric8/java-jboss-openjdk8-jdk -Pkubernetes
执行成功后控制台输出如下:
...[INFO]Installing /usr/local/work/k8s/ribbon/spring-cloud-k8s-web-service/target/classes/META-INF/fabric8/kubernetes.json to /root/.m2/repository/com/bolingcavalry/web-service/0.0.1-SNAPSHOT/web-service-0.0.1-SNAPSHOT-kubernetes.json[INFO][INFO]<<
[[email protected] spring-cloud-k8s-web-service]# kubectl get deploymentsNAME READY UP-TO-DATE AVAILABLE AGE
account-service 1/1 1 1 109m
web-service 1/1 1 1 18m[[email protected] spring-cloud-k8s-web-service]# kubectl get svcNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)AGE
account-service NodePort 10.105.157.2018080:32596/TCP 109m
kubernetes ClusterIP 10.96.0.1443/TCP 8d
web-service NodePort 10.99.211.1798080:30519/TCP 18m
[[email protected] spring-cloud-k8s-web-service]# minikube service web-service --urlhttp://192.168.121.133:30519
可见web-service的服务可以通过这个url访问: http://192.168.121.133:30519
虽然web-service可以正常调用account-service的服务,但始终访问的是一个pod,接下来我们就对account-service的pod进行扩容,将数量调整为2个,看看web-service是否可以轮询调用每个account-service的pod:
kubectl scale --replicas=2 deployment account-service
[[email protected] spring-cloud-k8s-web-service]# kubectl get podsNAME READY STATUS RESTARTS AGE
account-service-5554576647-m29xr 1/1 Running 0 53m
account-service-5554576647-zwwml 1/1 Running 0 20s
web-service-6d775855c7-7lkvr 1/1 Running 0 29m
接下来验证web-service配置的熔断服务是否可以生效:
kubectl delete deployment account-service
mvn cleaninstallfabric8:deploy -Dfabric8.generator.from=fabric8/java-jboss-openjdk8-jdk -Pkubernetes
至此,spring-cloud-kubernetes的服务发现和轮询实战(含熔断)就全部完成了,利用API Server提供的信息,spring-cloud-kubernetes将原生的kubernetes服务带给了SpringCloud应用,帮助传统微服务更好的融合在kubernetes环境中,如果您也在考虑将应用迁移到kubernetes上,希望本文能给您一些参考。