写点什么

spring-cloud-kubernetes 的服务发现和轮询实战 (含熔断)

作者:程序员欣宸
  • 2022 年 4 月 19 日
  • 本文字数:15793 字

    阅读完需:约 52 分钟

spring-cloud-kubernetes的服务发现和轮询实战(含熔断)

欢迎访问我的 GitHub

这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos


  • 本文是《spring-cloud-kubernetes 实战系列》的第四篇,主要内容是在 kubernetes 上部署两个应用:Web-Service 和 Account-Service,通过 spring-cloud-kubernetes 提供的注册发现能力,实现 Web-Service 调用 Account-Service 提供的 http 服务;

全文概览

  • 本文由以下段落组成:


  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<Server> 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<Server> result = new ArrayList<Server>(); 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 上下载本次实战的源码,地址和链接信息如下表所示:

  • 这个 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 内容如下:

<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">    <modelVersion>4.0.0</modelVersion>    <parent>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-parent</artifactId>        <version>2.1.1.RELEASE</version>        <relativePath/> <!-- lookup parent from repository -->    </parent>    <groupId>com.bolingcavalry</groupId>    <artifactId>account-service</artifactId>    <version>0.0.1-SNAPSHOT</version>    <name>account-service</name>    <description>Demo project for Spring Cloud service provider run in kubernetes</description>
<properties> <java.version>1.8</java.version> <spring-boot.version>2.1.1.RELEASE</spring-boot.version> <maven-checkstyle-plugin.failsOnError>false</maven-checkstyle-plugin.failsOnError> <maven-checkstyle-plugin.failsOnViolation>false</maven-checkstyle-plugin.failsOnViolation> <maven-checkstyle-plugin.includeTestSourceDirectory>false</maven-checkstyle-plugin.includeTestSourceDirectory> <maven-compiler-plugin.version>3.5</maven-compiler-plugin.version> <maven-deploy-plugin.version>2.8.2</maven-deploy-plugin.version> <maven-failsafe-plugin.version>2.18.1</maven-failsafe-plugin.version> <maven-surefire-plugin.version>2.21.0</maven-surefire-plugin.version> <fabric8.maven.plugin.version>3.5.37</fabric8.maven.plugin.version> <springcloud.version>2.1.1.RELEASE</springcloud.version> </properties>
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <type>pom</type> <scope>import</scope> <version>${spring-boot.version}</version> </dependency> </dependencies> </dependencyManagement>
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <version>${springcloud.version}</version> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>${springcloud.version}</version> </dependency> </dependencies>
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>${spring-boot.version}</version> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin>
<plugin> <!--skip deploy --> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-deploy-plugin</artifactId> <version>${maven-deploy-plugin.version}</version> <configuration> <skip>true</skip> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>${maven-surefire-plugin.version}</version> <configuration> <skipTests>true</skipTests> <!-- Workaround for https://issues.apache.org/jira/browse/SUREFIRE-1588 --> <useSystemClassLoader>false</useSystemClassLoader> </configuration> </plugin> <plugin> <groupId>io.fabric8</groupId> <artifactId>fabric8-maven-plugin</artifactId> <version>${fabric8.maven.plugin.version}</version> <executions> <execution> <id>fmp</id> <goals> <goal>resource</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
<profiles> <profile> <id>kubernetes</id> <build> <plugins> <plugin> <groupId>io.fabric8</groupId> <artifactId>fabric8-maven-plugin</artifactId> <version>${fabric8.maven.plugin.version}</version> <executions> <execution> <id>fmp</id> <goals> <goal>resource</goal> <goal>build</goal> </goals> </execution> </executions> <configuration> <enricher> <config> <fmp-service> <type>NodePort</type> </fmp-service> </config> </enricher> </configuration> </plugin> </plugins> </build> </profile> </profiles></project>
复制代码


  • 由上面的 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 服务的调用方,它们会调用这个接口来确定当前服务是否正常:

@RestControllerpublic 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 deploymentsNAME              READY   UP-TO-DATE   AVAILABLE   AGEaccount-service   1/1     1            1           69m[root@minikube spring-cloud-k8s-account-service]# kubectl get servicesNAME              TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGEaccount-service   NodePort    10.105.157.201   <none>        8080:32596/TCP   69mkubernetes        ClusterIP   10.96.0.1        <none>        443/TCP          8d
复制代码


  1. minikube 的 service 命令可以得到指定服务的访问地址:

[root@minikube spring-cloud-k8s-account-service]# minikube service account-service --urlhttp://192.168.121.133:32596
复制代码


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

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

  • 现在 account-service 服务已经就绪,接下来是开发和部署 web-service 应用。

开发和部署 Web-Service 服务

  • Web-Service 服务是个 springboot 应用,用到了 spring-cloud-kubernetes 提供的注册发现能力,以轮询的方式访问指定服务的全部 pod:


  1. 通过 maven 创建一个 springboot 应用,artifactId 是 web-service,pom.xml 内容如下,要重点关注的是 spring-cloud-starter-kubernetes-ribbon 的依赖:

<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">    <modelVersion>4.0.0</modelVersion>    <parent>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-parent</artifactId>        <version>2.1.1.RELEASE</version>        <relativePath/> <!-- lookup parent from repository -->    </parent>    <groupId>com.bolingcavalry</groupId>    <artifactId>web-service</artifactId>    <version>0.0.1-SNAPSHOT</version>    <name>web-service</name>    <description>Demo project for Spring Cloud service consumer run in kubernetes</description>
<properties> <java.version>1.8</java.version> <spring-boot.version>2.1.1.RELEASE</spring-boot.version> <maven-checkstyle-plugin.failsOnError>false</maven-checkstyle-plugin.failsOnError> <maven-checkstyle-plugin.failsOnViolation>false</maven-checkstyle-plugin.failsOnViolation> <maven-checkstyle-plugin.includeTestSourceDirectory>false</maven-checkstyle-plugin.includeTestSourceDirectory> <maven-compiler-plugin.version>3.5</maven-compiler-plugin.version> <maven-deploy-plugin.version>2.8.2</maven-deploy-plugin.version> <maven-failsafe-plugin.version>2.18.1</maven-failsafe-plugin.version> <maven-surefire-plugin.version>2.21.0</maven-surefire-plugin.version> <fabric8.maven.plugin.version>3.5.37</fabric8.maven.plugin.version> <springcloud.kubernetes.version>1.0.1.RELEASE</springcloud.kubernetes.version> <springcloud.version>2.1.1.RELEASE</springcloud.version> </properties>
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <type>pom</type> <scope>import</scope> <version>${spring-boot.version}</version> </dependency> </dependencies> </dependencyManagement>
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-kubernetes-core</artifactId> <version>${springcloud.kubernetes.version}</version> </dependency>
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-kubernetes-discovery</artifactId> <version>${springcloud.kubernetes.version}</version> </dependency>
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-kubernetes-ribbon</artifactId> <version>${springcloud.kubernetes.version}</version> </dependency>
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-commons</artifactId> <version>${springcloud.version}</version> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <version>${springcloud.version}</version> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>${springcloud.version}</version> </dependency>
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> <version>${springcloud.version}</version> </dependency>
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> <version>${springcloud.version}</version> </dependency> </dependencies>
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>${spring-boot.version}</version> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin>
<plugin> <!--skip deploy --> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-deploy-plugin</artifactId> <version>${maven-deploy-plugin.version}</version> <configuration> <skip>true</skip> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>${maven-surefire-plugin.version}</version> <configuration> <skipTests>true</skipTests> <!-- Workaround for https://issues.apache.org/jira/browse/SUREFIRE-1588 --> <useSystemClassLoader>false</useSystemClassLoader> </configuration> </plugin> <plugin> <groupId>io.fabric8</groupId> <artifactId>fabric8-maven-plugin</artifactId> <version>${fabric8.maven.plugin.version}</version> <executions> <execution> <id>fmp</id> <goals> <goal>resource</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
<profiles> <profile> <id>kubernetes</id> <build> <plugins> <plugin> <groupId>io.fabric8</groupId> <artifactId>fabric8-maven-plugin</artifactId> <version>${fabric8.maven.plugin.version}</version> <executions> <execution> <id>fmp</id> <goals> <goal>resource</goal> <goal>build</goal> </goals> </execution> </executions> <configuration> <enricher> <config> <fmp-service> <type>NodePort</type> </fmp-service> </config> </enricher> </configuration> </plugin> </plugins> </build> </profile> </profiles></project>
复制代码


  1. application.yml 的内容如下,增加了熔断的配置:

spring:  application:    name: web-service
server: port: 8080
backend: ribbon: eureka: enabled: false client: enabled: true ServerListRefreshInterval: 5000
hystrix.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("<br>"); }
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 deploymentsNAME              READY   UP-TO-DATE   AVAILABLE   AGEaccount-service   1/1     1            1           109mweb-service       1/1     1            1           18m[root@minikube spring-cloud-k8s-web-service]# kubectl get svcNAME              TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGEaccount-service   NodePort    10.105.157.201   <none>        8080:32596/TCP   109mkubernetes        ClusterIP   10.96.0.1        <none>        443/TCP          8dweb-service       NodePort    10.99.211.179    <none>        8080:30519/TCP   18m
复制代码


  1. minikube 的 service 命令可以得到指定服务的访问地址:

[root@minikube spring-cloud-k8s-web-service]# minikube service web-service --urlhttp://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 上的注册发现能力正常:

扩容验证 ribbon 轮询能力

  • 虽然 web-service 可以正常调用 account-service 的服务,但始终访问的是一个 pod,接下来我们就对 account-service 的 pod 进行扩容,将数量调整为 2 个,看看 web-service 是否可以轮询调用每个 account-service 的 pod:

  1. 执行以下命令即可将 pod 数量调整为 2 个:

kubectl scale --replicas=2 deployment account-service
复制代码

2. 检查 account-service 的 pod,发现已经有两个了(account-service-5554576647-m29xr 和 account-service-5554576647-zwwml):

[root@minikube spring-cloud-k8s-web-service]# kubectl get pods
NAME 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
复制代码
  1. 用浏览器访问地址:http://192.168.121.133:30519/account ,如下图所示,account-sercice 返回的 hostname 已经变成了两种,和前面查到的 pod 的 name 一致,可见 web-service 的确是通过 ribbon 轮询访问了多个 account-service 的 pod:


验证熔断能力

接下来验证 web-service 配置的熔断服务是否可以生效:

  1. 执行以下命令将 account-service 的 deployment 删除:

kubectl delete deployment account-service
复制代码
  1. 再浏览器访问地址:http://192.168.121.133:30519/account ,如下图所示,页面上的"Fallback"是配置的熔断方法返回的内容,可见熔断配置已经生效:


3. 再回到 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 上,希望本文能给您一些参考。

欢迎关注 InfoQ:程序员欣宸

学习路上,你不孤单,欣宸原创一路相伴...

发布于: 2022 年 04 月 19 日阅读数: 31
用户头像

搜索"程序员欣宸",一起畅游Java宇宙 2018.04.19 加入

前腾讯、前阿里员工,从事Java后台工作,对Docker和Kubernetes充满热爱,所有文章均为作者原创,个人Github:https://github.com/zq2599/blog_demos

评论

发布
暂无评论
spring-cloud-kubernetes的服务发现和轮询实战(含熔断)_java_程序员欣宸_InfoQ写作社区