51自学网



全文概览

本文由以下段落组成:

  1. 环境信息
  2. 常见的SpringCloud注册发现服务一览
  3. 分析kubernetes上如何实现服务注册发现
  4. 本章实战源码下载链接
  5. 实战开发Account-Service服务(服务提供方)
  6. 实战开发Web-Service服务(服务消费方)
  7. 扩容验证ribbon轮询能力
  8. 验证熔断能力

环境信息

本次实战的环境和版本信息如下:

  1. 操作系统:CentOS Linux release 7.6.1810
  2. minikube:1.1.1
  3. Java:1.8.0_191
  4. Maven:3.6.0
  5. fabric8-maven-plugin插件:3.5.37
  6. spring-cloud-kubernetes:1.0.1.RELEASE

上面的linux、minikube、java、maven,请确保已准备好,linux环境下minikube的安装和启动请参考《Linux安装minikube指南 》。

常见的SpringCloud注册发现服务一览

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,用上其提供的服务。

分析kubernetes上如何实现服务注册发现

如果将上面的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,如下所示:

public List getUpdatedListOfServers() { //用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(); List result = new ArrayList(); 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(new Server(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(new Server(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 + "]"); } return result; } 

理论分析已经完成,接下来就开始实战吧

源码下载

如果您不打算写代码,也可以从GitHub上下载本次实战的源码,地址和链接信息如下表所示:

名称链接备注项目主页https://github.com/zq2599/blog_demos该项目在GitHub上的主页git仓库地址(https)https://github.com/zq2599/blog_demos.git该项目源码的仓库地址,https协议git仓库地址(ssh)git@github.com:zq2599/blog_demos.git该项目源码的仓库地址,ssh协议

这个git项目中有多个文件夹,本章的Account-Service源码在spring-cloud-k8s-account-service文件夹下,Web-Service源码在spring-cloud-k8s-web-service文件夹下,如下图红框所示:

下面是详细的编码过程;

开发和部署Account-Service服务

Account-Service服务是个很普通的springboot应用,和spring-cloud-kubernetes没有任何关系:

  1. 通过maven创建一个springboot应用,artifactId是account-service,pom.xml内容如下:
  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环境;

  1. application.yml内容如下,依旧很简单:
spring: application: name: account-service server: port: 8080 
  1. 对外提供服务的是AccountController ,方法getName返回了当前容器的hostname,方法health用于响应kubernetes的两个探针,方法ribbonPing用于响应使用了ribbon服务的调用方,它们会调用这个接口来确定当前服务是否正常:
@RestController public class AccountController { private static final Logger LOG = LoggerFactory.getLogger(AccountController.class); private final String hostName = System.getenv("HOSTNAME"); /** * 探针检查响应类 * @return */ @RequestMapping("/health") public String health() { return "OK"; } @RequestMapping("/") public String ribbonPing(){ LOG.info("ribbonPing of {}", hostName); return hostName; } /** * 返回hostname * @return 当前应用所在容器的hostname. */ @RequestMapping("/name") public String getName() { return this.hostName + ", " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()); } } 
  1. 将上述工程的源码放在minikube机器上,确保maven设置正常,然后在pom.xml文件所在目录执行以下命令,即可编译构建工程并部署到kubernetes上:
mvn clean install fabric8: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] <<< fabric8-maven-plugin:3.5.37:deploy (default-cli) < install @ account-service <<< [INFO] [INFO] [INFO] --- fabric8-maven-plugin:3.5.37:deploy (default-cli) @ account-service --- [INFO] F8: Using Kubernetes at https://192.168.121.133:8443/ in namespace default with manifest /usr/local/work/k8s/ribbon/spring-cloud-k8s-account-service/target/classes/META-INF/fabric8/kubernetes.yml [INFO] Using namespace: default [INFO] Updating a Service from kubernetes.yml [INFO] Updated Service: target/fabric8/applyJson/default/service-account-service.json [INFO] Using namespace: default [INFO] Updating Deployment from kubernetes.yml [INFO] Updated Deployment: target/fabric8/applyJson/default/deployment-account-service.json [INFO] F8: HINT: Use the command `kubectl get pods -w` to watch your pods start up [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 11.941 s [INFO] Finished at: 2019-06-16T19:00:51+08:00 [INFO] ------------------------------------------------------------------------ 
  1. 检查kubernetes上的部署和服务是否正常:
[root@minikube spring-cloud-k8s-account-service]# kubectl get deployments NAME READY UP-TO-DATE AVAILABLE AGE account-service 1/1 1 1 69m [root@minikube spring-cloud-k8s-account-service]# kubectl get services NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE account-service NodePort 10.105.157.201  8080:32596/TCP 69m kubernetes ClusterIP 10.96.0.1  443/TCP 8d 
  1. minikube的service命令可以得到指定服务的访问地址:
[root@minikube spring-cloud-k8s-account-service]# minikube service account-service --url http://192.168.121.133:32596 

可见account-service的服务可以通过这个url访问:http://192.168.121.133:32596

  1. 用浏览器访问地址:http://192.168.121.133:32596/name ,如下图所示,可以正常访问account-service提供的服务:

  1. 现在account-service服务已经就绪,接下来是开发和部署web-service应用。
  2. 开发和部署Web-Service服务
  3. Web-Service服务是个springboot应用,用到了spring-cloud-kubernetes提供的注册发现能力,以轮询的方式访问指定服务的全部pod:
  4. 通过maven创建一个springboot应用,artifactId是web-service,pom.xml内容如下,要重点关注的是spring-cloud-starter-kubernetes-ribbon的依赖:
 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         
  1. application.yml的内容如下,增加了熔断的配置:
spring: application: name: web-serviceserver: port: 8080backend: ribbon: eureka: enabled: false client: enabled: true ServerListRefreshInterval: 5000hystrix.command.BackendCall.execution.isolation.thread.timeoutInMilliseconds: 5000hystrix.threadpool.BackendCallThread.coreSize: 5
  1. 创建一个ribbon的配置类RibbonConfiguration:
package com.bolingcavalry.webservice;import com.netflix.client.config.IClientConfig;import com.netflix.loadbalancer.AvailabilityFilteringRule;import com.netflix.loadbalancer.IPing;import com.netflix.loadbalancer.IRule;import com.netflix.loadbalancer.PingUrl;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;/** * @Description: ribbon配置类 * @author: willzhao E-mail: zq2599@gmail.com * @date: 2019/6/16 11:52 */public class RibbonConfiguration { @Autowired IClientConfig ribbonClientConfig; /** * 检查服务是否可用的实例, * 此地址返回的响应的返回码如果是200表示服务可用 * @param config * @return */ @Bean public IPing ribbonPing(IClientConfig config){ return new PingUrl(); } /** * 轮询规则 * @param config * @return */ @Bean public IRule ribbonRule(IClientConfig config){ return new AvailabilityFilteringRule(); }}
  1. 应用启动类如下,注意增加了服务发现、熔断、ribbon的配置,还定义了restTemplte实例,注意@LoadBalanced注解:
package com.bolingcavalry.webservice;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;import org.springframework.cloud.client.discovery.EnableDiscoveryClient;import org.springframework.cloud.client.loadbalancer.LoadBalanced;import org.springframework.cloud.netflix.ribbon.RibbonClient;import org.springframework.context.annotation.Bean;import org.springframework.web.client.RestTemplate;@SpringBootApplication@EnableDiscoveryClient@EnableCircuitBreaker@RibbonClient(name="account-service", configuration = RibbonConfiguration.class)public class WebServiceApplication { public static void main(String[] args) { SpringApplication.run(WebServiceApplication.class, args); } @LoadBalanced @Bean RestTemplate restTemplate(){ return new RestTemplate(); }}
  1. 远程调用account-service的http接口的逻辑被放进服务类AccountService中,注意URL中用的是服务名account-service:
package com.bolingcavalry.webservice;import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.web.client.RestTemplate;import java.text.SimpleDateFormat;import java.util.Date;/** * @Description: 这里面封装了远程调用account-service提供服务的逻辑 * @author: willzhao E-mail: zq2599@gmail.com * @date: 2019/6/16 12:21 */@Servicepublic class AccountService { @Autowired private RestTemplate restTemplate; @HystrixCommand(fallbackMethod = "getFallbackName" ,commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000") }) public String getDataFromSpringCloudK8SProvider(){ return this.restTemplate.getForObject("http://account-service/name", String.class); } /** * 熔断时调用的方法 * @return */ private String getFallbackName() { return "Fallback" + ", " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()); }}
  1. 最后是响应web请求的WebServiceController类,这里面调用了AccountService的服务,这样我们从web发起请求后,web-service就会远程调用account-service的服务:
package com.bolingcavalry.webservice;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;/** * @Description: 测试用的controller,会远程调用account-service的服务 * @author: willzhao E-mail: zq2599@gmail.com * @date: 2019/6/16 11:46 */@RestControllerpublic class WebServiceController { @Autowired private AccountService accountService; /** * 探针检查响应类 * @return */ @RequestMapping("/health") public String health() { return "OK"; } /** * 远程调用account-service提供的服务 * @return 多次远程调返回的所有结果. */ @RequestMapping("/account") public String account() { StringBuilder sbud = new StringBuilder(); for(int i=0;i<10;i++){ sbud.append(accountService.getDataFromSpringCloudK8SProvider()) .append("
"); } return sbud.toString(); }}
  1. 将上述工程的源码放在minikube机器上,确保maven设置正常,然后在pom.xml文件所在目录执行以下命令,即可编译构建工程并部署到kubernetes上:
mvn clean install fabric8: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] <<< fabric8-maven-plugin:3.5.37:deploy (default-cli) < install @ web-service <<< [INFO] [INFO] [INFO] --- fabric8-maven-plugin:3.5.37:deploy (default-cli) @ web-service --- [INFO] F8: Using Kubernetes at https://192.168.121.133:8443/ in namespace default with manifest /usr/local/work/k8s/ribbon/spring-cloud-k8s-web-service/target/classes/META-INF/fabric8/kubernetes.yml [INFO] Using namespace: default [INFO] Creating a Service from kubernetes.yml namespace default name web-service [INFO] Created Service: target/fabric8/applyJson/default/service-web-service.json [INFO] Using namespace: default [INFO] Creating a Deployment from kubernetes.yml namespace default name web-service [INFO] Created Deployment: target/fabric8/applyJson/default/deployment-web-service.json [INFO] F8: HINT: Use the command `kubectl get pods -w` to watch your pods start up [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 12.792 s [INFO] Finished at: 2019-06-16T19:24:21+08:00 [INFO] ------------------------------------------------------------------------ 
  1. 检查kubernetes上的部署和服务是否正常:
[root@minikube spring-cloud-k8s-web-service]# kubectl get deployments NAME READY UP-TO-DATE AVAILABLE AGE account-service 1/1 1 1 109m web-service 1/1 1 1 18m [root@minikube spring-cloud-k8s-web-service]# kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE account-service NodePort 10.105.157.201  8080:32596/TCP 109m kubernetes ClusterIP 10.96.0.1  443/TCP 8d web-service NodePort 10.99.211.179  8080:30519/TCP 18m 
  1. minikube的service命令可以得到指定服务的访问地址:
[root@minikube spring-cloud-k8s-web-service]# minikube service web-service --url http://192.168.121.133:30519 

可见web-service的服务可以通过这个url访问:http://192.168.121.133:30519

  1. 用浏览器访问地址:http://192.168.121.133:30519/account ,如下图所示,页面上展示的内容都是web-service调用了account-service的接口返回的,证明kubernetes上的注册发现能力正常:

  1. 扩容验证ribbon轮询能力
  2. 虽然web-service可以正常调用account-service的服务,但始终访问的是一个pod,接下来我们就对account-service的pod进行扩容,将数量调整为2个,看看web-service是否可以轮询调用每个account-service的pod:
  3. 执行以下命令即可将pod数量调整为2个:
kubectl scale --replicas=2 deployment account-service
  1. 检查account-service的pod,发现已经有两个了(account-service-5554576647-m29xr和account-service-5554576647-zwwml):
[root@minikube spring-cloud-k8s-web-service]# kubectl get podsNAME READY STATUS RESTARTS AGEaccount-service-5554576647-m29xr 1/1 Running 0 53maccount-service-5554576647-zwwml 1/1 Running 0 20sweb-service-6d775855c7-7lkvr 1/1 Running 0 29m
  1. 用浏览器访问地址:http://192.168.121.133:30519/account ,如下图所示,account-sercice返回的hostname已经变成了两种,和前面查到的pod的name一致,可见web-service的确是通过ribbon轮询访问了多个account-service的pod:

  1. 验证熔断能力
  2. 接下来验证web-service配置的熔断服务是否可以生效:
  3. 执行以下命令将account-service的deployment删除:
kubectl delete deployment account-service
  1. 再浏览器访问地址:http://192.168.121.133:30519/account ,如下图所示,页面上的&#8221;Fallback&#8221;是配置的熔断方法返回的内容,可见熔断配置已经生效:

  1. 再回到web-service的pom.xml所在位置执行以下命令,这样会重新构建部署一次web-service服务:
mvn clean install fabric8:deploy -Dfabric8.generator.from=fabric8/java-jboss-openjdk8-jdk -Pkubernetes
  1. 再浏览器访问地址:http://192.168.121.133:30519/account ,如下图所示,服务成功恢复:

至此,spring-cloud-kubernetes的服务发现和轮询实战(含熔断)就全部完成了,利用API Server提供的信息,spring-cloud-kubernetes将原生的kubernetes服务带给了SpringCloud应用,帮助传统微服务更好的融合在kubernetes环境中,如果您也在考虑将应用迁移到kubernetes上,希望本文能给您一些参考。

结语

最后我把这些实际遇到的常见问题的答案总结了一下,由于文字过多,就单独做了一个文档。以及准备了一些更多关于Kafka、Mysql、Tomcat、Docker、Spring、MyBatis、Nginx、Netty、Dubbo、Redis、Netty、Spring cloud、分布式、高并发、性能调优、微服务等架构面试题和架构资料,如有需要的朋友的可以点击免费领取,也可以关注我以后还会有更多干货分享!

标签:

猜你喜欢

  • 高中地理学习方法归纳总结 有哪些高效学习法
  • 太阳系行星数据表
  • ​象形和指事
  • 物理常用单位换算表
  • 古代最安稳的代步工具:牛车
  • 会意字
  • 細說漢字:輿
  • 細說漢字:輯
  • 細說漢字:輦
  • “木侯”什么意思(拼音,读音等)
  • 相关推荐