有关Spring Cloud Kubernetes(以下简称SCK)详见https://github.com/spring-cloud/spring-cloud-kubernetes,在本文中我们主要测试三个功能:

  • 使用Kubernetes服务发现配合Spring Cloud Ribbon做服务调用

  • 读取Kubernetes的ConfigMap配置并且支持修改后动态刷新

  • Spring Boot Actuator对Kubernates Pod信息的感知

编写测试程序

首先,我们来创建pom文件,注意几点:

  • Spring Boot版本不能太高

  • 引入了 Spring Boot Web以及Actuator两个模块,我们开发一个Web项目进行测试

  • 引入了 Spring Cloud的Ribbon模块,我们需要测试一下服务调用

  • 引入了spring-cloud-starter-kubernetes-all依赖,我们的主要测试对象

  • 额外引入了docker-maven-plugin插件用于帮助我们构建镜像

  • 设置了finalName

文件如下:

  1. <?xml version="1.0" encoding="UTF-8"?>

  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

  4. <modelVersion>4.0.0</modelVersion>

  5. <parent>

  6. <groupId>org.springframework.boot</groupId>

  7. <artifactId>spring-boot-starter-parent</artifactId>

  8. <version>2.0.9.RELEASE</version>

  9. <relativePath/>

  10. </parent>

  11. <groupId>me.josephzhu</groupId>

  12. <artifactId>springcloudk8sdemo</artifactId>

  13. <version>0.0.1-SNAPSHOT</version>

  14. <name>springcloudk8sdemo</name>

  15. <properties>

  16. <java.version>11</java.version>

  17. </properties>

  18. <dependencies>

  19. <dependency>

  20. <groupId>org.springframework.boot</groupId>

  21. <artifactId>spring-boot-starter-web</artifactId>

  22. </dependency>

  23. <dependency>

  24. <groupId>org.springframework.boot</groupId>

  25. <artifactId>spring-boot-starter-actuator</artifactId>

  26. </dependency>

  27. <dependency>

  28. <groupId>org.springframework.cloud</groupId>

  29. <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>

  30. </dependency>

  31. <dependency>

  32. <groupId>org.springframework.cloud</groupId>

  33. <artifactId>spring-cloud-starter-kubernetes-all</artifactId>

  34. <version>1.0.3.RELEASE</version>

  35. </dependency>

  36. <dependency>

  37. <groupId>org.projectlombok</groupId>

  38. <artifactId>lombok</artifactId>

  39. <optional>true</optional>

  40. </dependency>

  41. <dependency>

  42. <groupId>org.springframework.boot</groupId>

  43. <artifactId>spring-boot-starter-test</artifactId>

  44. <scope>test</scope>

  45. </dependency>

  46. </dependencies>

  47. <build>

  48. <finalName>k8sdemo</finalName>

  49. <plugins>

  50. <plugin>

  51. <groupId>org.springframework.boot</groupId>

  52. <artifactId>spring-boot-maven-plugin</artifactId>

  53. </plugin>

  54. <plugin>

  55. <groupId>com.spotify</groupId>

  56. <artifactId>docker-maven-plugin</artifactId>

  57. <version>1.0.0</version>

  58. <configuration>

  59. <imageName>zhuye/${project.artifactId}</imageName>

  60. <dockerDirectory>src/main/docker</dockerDirectory>

  61. <resources>

  62. <resource>

  63. <targetPath>/</targetPath>

  64. <directory>${project.build.directory}</directory>

  65. <include>${project.build.finalName}.jar</include>

  66. </resource>

  67. </resources>

  68. </configuration>

  69. </plugin>

  70. </plugins>

  71. </build>

  72. <dependencyManagement>

  73. <dependencies>

  74. <dependency>

  75. <groupId>org.springframework.cloud</groupId>

  76. <artifactId>spring-cloud-dependencies</artifactId>

  77. <version>Finchley.SR4</version>

  78. <type>pom</type>

  79. <scope>import</scope>

  80. </dependency>

  81. </dependencies>

  82. </dependencyManagement>

  83. </project>

接下去在src\main\docker目录下创建Dockerfile文件:

  1. FROM openjdk:11-jdk-slim

  2. VOLUME /tmp

  3. ADD k8sdemo.jar app.jar

  4. ENTRYPOINT exec java $JAVA_OPTS -jar /app.jar

值得注意的是,JVM参数我们希望从环境变量注入。

来看看代码,我们首先定义一个配置类:

  1. package me.josephzhu.springcloudk8sdemo;

  2. import lombok.Data;

  3. import org.springframework.boot.context.properties.ConfigurationProperties;

  4. import org.springframework.context.annotation.Configuration;

  5. @Configuration

  6. @ConfigurationProperties(prefix = "bean")

  7. @Data

  8. public class TestConfig {

  9. private String message;

  10. private String serviceName;

  11. }

有了SCK的帮助,配置可以从ConfigMap加载,之后我们会看到ConfigMap的配置方式。下面我们定义一个控制器扮演服务端的角色:

  1. package me.josephzhu.springcloudk8sdemo;

  2. import org.springframework.beans.factory.annotation.Autowired;

  3. import org.springframework.cloud.client.discovery.DiscoveryClient;

  4. import org.springframework.web.bind.annotation.GetMapping;

  5. import org.springframework.web.bind.annotation.RestController;

  6. import java.net.InetAddress;

  7. import java.net.UnknownHostException;

  8. import java.util.List;

  9. @RestController

  10. public class TestServer {

  11. @Autowired

  12. private DiscoveryClient discoveryClient;

  13. @GetMapping("servers")

  14. public List<String> servers() {

  15. return discoveryClient.getServices();

  16. }

  17. @GetMapping

  18. public String ip() throws UnknownHostException {

  19. return InetAddress.getLocalHost().getHostAddress();

  20. }

  21. }

可以看到这里定义了两个接口:

  • servers 用于返回服务发现找到的所有服务(K8S的服务)

  • 根路径返回了当前节点的IP地址

接下去定义另一个控制器扮演客户端的角色:

  1. package me.josephzhu.springcloudk8sdemo;

  2. import lombok.extern.slf4j.Slf4j;

  3. import org.springframework.beans.factory.annotation.Autowired;

  4. import org.springframework.web.bind.annotation.GetMapping;

  5. import org.springframework.web.bind.annotation.RestController;

  6. import org.springframework.web.client.RestTemplate;

  7. import java.net.InetAddress;

  8. import java.net.UnknownHostException;

  9. @RestController

  10. @Slf4j

  11. public class TestClient {

  12. @Autowired

  13. private RestTemplate restTemplate;

  14. @Autowired

  15. private TestConfig testConfig;

  16. @GetMapping("client")

  17. public String client() throws UnknownHostException {

  18. String ip = InetAddress.getLocalHost().getHostAddress();

  19. String response = restTemplate.getForObject("http://"+testConfig.getServiceName()+"/", String.class);

  20. return String.format("%s -> %s", ip, response);

  21. }

  22. }

这里就一个接口client接口,访问后通过RestTemplate来访问服务端根路径的接口,然后输出了客户端和服务端的IP地址。

然后我们定义一个全局的异常处理器,在出错的时候我们直接看到是什么错:

  1. package me.josephzhu.springcloudk8sdemo;

  2. import lombok.extern.slf4j.Slf4j;

  3. import org.springframework.web.bind.annotation.ExceptionHandler;

  4. import org.springframework.web.bind.annotation.RestControllerAdvice;

  5. @RestControllerAdvice

  6. @Slf4j

  7. public class GlobalAdvice {

  8. @ExceptionHandler(Exception.class)

  9. public String exception(Exception ex){

  10. log.error("error:", ex);

  11. return ex.toString();

  12. }

  13. }

最后我们定义启动程序:

  1. package me.josephzhu.springcloudk8sdemo;

  2. import lombok.extern.slf4j.Slf4j;

  3. import org.springframework.beans.factory.annotation.Autowired;

  4. import org.springframework.boot.SpringApplication;

  5. import org.springframework.boot.autoconfigure.SpringBootApplication;

  6. import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

  7. import org.springframework.cloud.client.loadbalancer.LoadBalanced;

  8. import org.springframework.cloud.netflix.ribbon.RibbonClient;

  9. import org.springframework.context.annotation.Bean;

  10. import org.springframework.scheduling.annotation.EnableScheduling;

  11. import org.springframework.scheduling.annotation.Scheduled;

  12. import org.springframework.web.client.RestTemplate;

  13. import java.lang.management.ManagementFactory;

  14. import java.util.stream.Collectors;

  15. @SpringBootApplication

  16. @EnableDiscoveryClient

  17. @EnableScheduling

  18. @Slf4j

  19. @RibbonClient(name = "k8sdemo")

  20. public class Springcloudk8sdemoApplication {

  21. public static void main(String[] args) {

  22. log.info("jvm:{}",

  23. ManagementFactory.getRuntimeMXBean().getInputArguments().stream().collect(Collectors.joining(" ")));

  24. SpringApplication.run(Springcloudk8sdemoApplication.class, args);

  25. }

  26. @Autowired

  27. private TestConfig testConfig;

  28. @Scheduled(fixedDelay = 5000)

  29. public void hello() {

  30. log.info("config:{}", testConfig);

  31. }

  32. @LoadBalanced

  33. @Bean

  34. RestTemplate restTemplate() {

  35. return new RestTemplate();

  36. }

  37. }

在这个启动程序中我们做了几件事情:

  • 定义了一个定时器,5秒一次输出配置(随后用于观察ConfigMap配置动态刷新)

  • 定义了RestTemplate和Ribbon配合使用

  • 在启动的时候输出下JVM参数,以便证明JVM参数(通过环境变量)注入成功

配置文件方面,首先是application.yaml:

  1. spring:

  2. application:

  3. name: k8sdemo

  4. cloud:

  5. kubernetes:

  6. reload:

  7. enabled: true

  8. config:

  9. sources:

  10. - name: ${spring.application.name}

干了三件事情:

  • 定义应用程序名称

  • 指定ConfigMap名称为应用程序名,也就是k8sdemo

  • 启用ConfigMap配置自动刷新(见下图,默认是event方式)

再定义一个bootstrap.yaml用于打开actuator的一些端点:

  1. management:

  2. endpoint:

  3. restart:

  4. enabled: true

  5. health:

  6. enabled: true

  7. info:

  8. enabled: true

整个代码源码参见 https://github.com/JosephZhu1983/SpringCloudK8S

配置阿里云K8S集群

集群购买过程我就略去了,这些选项都可以勾上,Ingress特别记得需要,我们之后要在公网上进行测试。

差不多30秒就有了一个K8S集群,这鬼东西要自己从头搭建一套高可用的没一天搞不下来,这里可以看到我买了一个3节点的托管版K8S,所谓托管版也就是K8S的管理节点我们直接用阿里云自己的,只需要买工作节点,省钱省心。

买好后记得配置下kubeconfig,这样才能通过kubectl访问集群。

注意下,阿里云给出的配置别一股脑直接复制覆盖了原来的配置(比如你可能还有本地集群),也别直接粘贴到文件的最后,文件是有格式的,你需要把cluster、context和user三个配置分别复制到对应的地方。

构建镜像

我们知道在K8S部署程序不像虚拟机,唯一的交付是镜像,因此我们需要把镜像上传到阿里云。首先,本地构建镜像:

  1. mvn package docker:build -DskipTests

完成后查看镜像:

然后在阿里云开通镜像服务,创建自己的仓库:

根据里面的说明,给镜像打上标签后推送镜像到仓库:

  1. docker login --username=【你的账号】 registry.cn-shanghai.aliyuncs.com

  2. docker tag 80026bb476ce registry.cn-shanghai.aliyuncs.com/zhuyedocker/test:v6

  3. docker push registry.cn-shanghai.aliyuncs.com/zhuyedocker/test:v6

完成后在镜像仓库查看镜像:

部署应用

通过镜像创建无状态应用:

创建的时候注意下面几点:

  • 选择正确的镜像和Tag

  • 我这里给予一个应用1C CPU 1.4G内存的配置

  • 端口和应用一致,设置为8080

  • 通过环境变量注入额外的JVM参数:-server -XX:+UseContainerSupport -XX:MaxRAMPercentage=50.0 -XX:InitialRAMPercentage=50.0 -XX:MinRAMPercentage=50.0 -XX:MaxMetaspaceSize=256M -XX:ThreadStackSize=256 -XX:+DisableExplicitGC -XX:+AlwaysPreTouch

这里我配置了JVM动态根据容器的资源限制来设置堆内存大小(此特性在部分版本的JDK8上支持,在9以后都支持),这比直接设置死Xms和Xmx好很多(设置死的话不方便进行扩容),这里我设置了50%,不建议设置更高(比如如果是2GB的内存限制,给堆设置为1.5GB显然是不合适的),毕竟Java进程所使用的内存除了堆之外还有堆外、线程栈(线程数*ThreadStackSize)、元数据区等,而且容器本身也有开销。

我这里展示的是编辑界面,创建界面略有不同但是类似:

创建应用的时候你可以把Service和Ingress一并创建。

完成后可以进入应用详情看到2个节点状态都是运行中:

测试应用启动情况

来到Ingress界面可以看到我们的公网Ingress记录,可以直接点击访问:

根节点输出的是IP,在之前的截图中我们可以看到服务运行在1.13和0.137两个IP上:

多刷新几次浏览器可以看到负载均衡的效果。

访问services可以查看到所有K8S的服务:

访问actuator/info可以看到有关K8S的详情(感谢SCK),显然我们代码里获取到的IIP是PodIP:

测试读取K8S配置

接下去我们来到配置项来配置ConfigMap:这里配置项的名称需要和配置文件中的对应起来,也就是k8sdemo。然后配置项的Key需要和代码中的对应:

我们来看看应用的日志:

  1. 2019-10-03 11:30:33.442 INFO 1 --- [pool-1-thread-1] m.j.s.Springcloudk8sdemoApplication : config:TestConfig(message=8888, serviceName=k8sdemo-svc)

的确正确获取到了配置,我们修改下配置项bean.message为9999,随后再来看看日志:可以看到程序发现了配置的变更,刷新了上下文,然后获取到了最新的配置。

测试通过K8S服务发现进行服务调用:

访问client接口可以看到1.13正常从0.137获取到了数据:多刷新几次:

我们访问到应用的负载均衡是由Ingress实现的,应用访问服务端的负载均衡是由Ribbon实现的。

查看JVM内存情况

还记得吗,我们在创建应用的时候给的内存是1.4GB,然后我们设置了JVM使用50%的内存(初始和最大都是50%),现在我们来看看是不是这样。

首先来看看pod的情况:

然后执行如下命令在Pod内运行jinfo

  1. kubectl exec k8sdemo-7b44d9fbff-c4jkf -- jinfo 1

可以看到如下结果,初始和最大堆是700M左右,说明参数起作用了:

小结

本文我们简单展示了一下Spring Cloud Kubernetes的使用,以及如何通过阿里云的K8S集群来部署我们的微服务,我们看到:

  • 如何通过SCK来读取ConfigMap的配置,支持动态刷新

  • 如何通过SCK来使用K8S的服务发现进行服务调用

  • JVM内存参数设置问题

  • 如何把镜像推到阿里云并且在阿里云的K8S跑起来我们的镜像

有关K8S和基于Spring Boot/Spring Cloud的微服务结合使用,有几点需要注意:

  • Spring Cloud 有自己的服务注册中心,比如Eureka。如果你希望统一使用K8S做服务发现,那么可以使用Spring Cloud Kubernetes。如果你希望使用Eureka作为服务发现,那么服务之间调用都建议通过Feign或Ribbon调用,而不是使用K8S的Service域名或Ingress调用,两套服务发现体系混用的话比较混乱而且有协同性问题。

  • 在K8S而不是VM中部署应用,最主要的区别是不能认为服务的IP是固定的,因为Pod随时可能重新调度,对于某些框架,需要依赖有状态的应用IP,比如XXL Job这可能是一个问题,需要改造。

  • Pod的生命周期和VM不同,考虑各种日志和OOM Dump的收集和保留问题。

  • 应用无故重启,考虑健康检测、资源不足等问题,在K8S部署应用需要观察应用的重启问题,合理设置reques和limit配置以及JVM参数(比如-XX:+UseContainerSupport -XX:MaxRAMPercentage=50.0 -XX:InitialRAMPercentage=50.0 -XX:MinRAMPercentage=50.0),审查健康检测的配置是否合理。

朱晔和你聊Spring系列S1E11:小测Spring Cloud Kubernetes @ 阿里云K8S相关推荐

  1. 【微信小程序控制硬件⑧ 】微信小程序以 websocket 连接阿里云IOT物联网平台mqtt服务器,封装起来使用就是这么简单!(附带Demo)

    [微信小程序控制硬件第1篇 ] 全网首发,借助 emq 消息服务器带你如何搭建微信小程序的mqtt服务器,轻松控制智能硬件! [微信小程序控制硬件第2篇 ] 开始微信小程序之旅,导入小程序Mqtt客户 ...

  2. 朱晔和你聊Spring系列S1E9:聊聊Spring的那些注解

    本文我们来梳理一下Spring的那些注解,如下图所示,大概从几方面列出了Spring的一些注解: 如果此图看不清楚也没事,请运行下面的代码输出所有的结果. Spring目前的趋势是使用注解结合Java ...

  3. 微末--Spring系列之初入Spring(一)

    小宋最近写的博客会比较多,可能又爱上了写博客. 这篇博客我会带大家加深对Spring的理解,不会有什么代码,有些问题是我之前遇到过有记录的,有些是在使用 Spring 的过程中也并没有注意,临时查阅了 ...

  4. Spring系列(九)- Spring Web MVC 框架

    文章目录 MVC设计模式简介 Spring MVC 工作流程 Spring MVC接口 需求的配置 Spring MVC视图解析器 Controller 注解类型 Spring MVC的转发与重定向 ...

  5. 倾囊相授:小编是如何从阿里云转战达摩院并成功上岸de?

    点赞再看,养成习惯! 目录 前言 正文 结语 参考文献 前言 PS: 从动笔到写完,花了5天,还是虎头蛇尾,请多多担待~~ 一直想写,却一直拖着,拖到现在都快10天了,才开始动笔.害,拖延症真的是小编 ...

  6. 今天的这个小成绩,需要向阿里云的朋友报告一下!

    今天,想向大家报告一个最新的小成绩: 在数据库领域的权威评选--Gartner全球数据库魔力象限评比中,阿里云成功进入"挑战者"象限,连续两年作为唯一的中国企业入选. 最新评选表明 ...

  7. 微信小程序图片/视频直传阿里云服务器OSS

    阿里云官方文档: https://help.aliyun.com/document_detail/31925.html?spm=a2c4g.11186623.6.634.AMs4Fj 第一步(需要由后 ...

  8. 部署小程序到服务器(阿里云)

    选择购买服务器(阿里云) 先了解 独享云虚拟主机.共享云虚拟主机.云服务器 ECS 的区别? 根据自己需要,因为我的小程序是要商用,我选择的云服务器ESC. 购买流程 云服务器ESC购买链接 选择计费 ...

  9. 小程序wx.request访问阿里云公共资源返回403的独家解决方法

    文章目录 1.问题描述 2.问题解决 3.结语 1.问题描述   在我已经在小程序管理后台设置好request合法域名的前提下:   微信小程序小程序后端使用wx.request访问阿里云公共资源返回 ...

最新文章

  1. AI口语翻译,遇到的可不止口齿不清…….
  2. Linux redis安装教程,Linux 下redis5.0.0安装教程详解
  3. Jenkins CLI命令行工具,助你轻松管理 Jenkins
  4. C++编程(一):匈牙利命名法
  5. 微信公众平台如何获得openid
  6. 关于“WARN: SQL Error: 1366, SQLState: HY000”错误的解决方案
  7. 看懂这些代码,我哭着笑了
  8. 使用Safari浏览器自带工具,查看页面中 css 样式的引用~
  9. CMatrix类 矩阵类 C++
  10. js控制Windows Media Player
  11. IT行业未来发展前景如何?
  12. Gateway过滤器详解
  13. delphi阿里云短信(支持SendSms短信发送、SendBatchSms短信批量发送、QuerySendDetails查询短信发送记录),D7~XE10可用
  14. 编译 nginx + http-flv 模块
  15. 百度富文本编辑器修改图片上传尺寸
  16. 什么是天气预报 API 接口?如何获取天气预报 API?
  17. 国外问卷调查好做吗?为大家分享干货!
  18. 今日,首款国产超小体积5G通信模组商用!
  19. cnpm i 与 npm i
  20. Arduino安装esp32 SDK(Windows)问题:AzureIoT: no headers files解决

热门文章

  1. JS案例:接口加解密与防重放
  2. Linux系统分析第一次实验报告
  3. len函数python返回值类型_简单介绍Python中的len()函数的使用
  4. Chrome浏览器js调试(console等)
  5. Android工具类获取上下文对象
  6. 蘑菇游戏_让熊动起来
  7. 除自身以外数组的乘积-左右累乘-c++
  8. notepad++常用设置
  9. 火狐浏览器打开空白,点击页面没反应
  10. 你公司适合做哪个体系的认证?看完再也不怕搞不清!