最近一直在研究基于Kubernetes和SpringBoot的微服务架构,在研究过程中,逐渐意识到,一个优秀的微服务架构在最大化地做到高内聚、松耦合的同时,也必须要求架构内的微服务基于一定的规范进行设计。符合这些规范的微服务,才是是体系内的“优秀公民”,只有体系内的都是“优秀公民”,才能保障微服务架构的健康发展。
针对这一设计理念,我决定写几篇博文,来定义一下我认为的“优秀公民”,给后续搭建微服务提供一些范例。
SpringBoot由于其自带Servlet容器,可以独立运行,并且配置简单,容易上手,最重要的是基于JavaEE平台,使得SpringBoot非常适合开发微服务。所以本文决定以一个简单的SpringBoot应用为例,来演示一下,如何把SpringBoot以微服务的形式部署到Kubernetes集群里。
阅读本文您需要具备基本的Java、Docker和Kubernetes知识。
SpringBoot Hello World
首先,我们先创建一个最简单的SpringBoot应用。项目的源码结构如下:
src
--main
--java
--hello
-Application.java
-HelloController.java
pom.xml
HelloController.java是一个SpringMVC的Controller,内容如下:
package hello;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
@RestController
public class HelloController {
@RequestMapping("/")
public String index() {
return "Greetings from Spring Boot!";
}
}
可以看到我们以注解的方式声明了一个RestController,然后将URI(/)映射到index方法里,这样,只要有URI为“/”的请求,就会返回“Greetings from Spring Boot!”。
Application.java是程序的入口,内容如下:
package hello;
import java.util.Arrays;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public CommandLineRunner commandLineRunner(ApplicationContext ctx) {
return args -> {
System.out.println("Let's inspect the beans provided by Spring Boot:");
String[] beanNames = ctx.getBeanDefinitionNames();
Arrays.sort(beanNames);
for (String beanName : beanNames) {
System.out.println(beanName);
}
};
}
}
Application类里面包含两个方法,main方法是整个程序的入口,另外commandLineRunner方法被注解成了@Bean,程序在启动的时候,这个方法会被自动执行,将整个Application Context里面所有的bean都打印出来,这个方法是便于各位了解SpringBoot底层原理的,在生产环境中可以删除。
最后是根目录的Maven 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>
<groupId>org.springframework</groupId>
<artifactId>gs-spring-boot</artifactId>
<version>0.1.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<properties>
<java.version>1.8</java.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
SpringBoot提供了spring-boot-starter-parent、spring-boot-starter-web以及spring-boot-maven-plugin,这样就大大简化了SpringBoot项目的Maven配置,基于“约定优于配置”的理念,可以让开发人员快速上手,轻松开发出SpringBoot应用。
开发完成后,在项目根目录执行:
mvn package
如果一切正常的话,会在target目录里生成一个名为gs-spring-boot-0.1.0.jar的文件。我们可以这样运行这个jar:
cd target
java -jar ./gs-spring-boot-0.1.0.jar
在浏览器上访问:http://localhost:8080,可以看到如下输出:
这样,我们就完成了一个基本的SpringBoot应用。
构建Docker镜像
接下来,我们来构建一个Docker镜像,用于运行刚才我们开发的SpringBoot应用。
首先,我们要构建一个基础镜像,这个镜像包含了简单的操作系统,JDK环境等等。我们没有直接使用dockerhub上的Java8基础镜像,而是基于opensuse的基础镜像,之后在上面安装OpenJDK,原因是我们公司在生产环境使用SUSE Linux,运维人员对SUSE更熟悉,这也反映出在文章开始我提到的,一个微服务框架内的“优秀公民”要为了符合规范,做一些妥协,这样整个微服务体系才可以健康发展。
废话少说,直接上代码。构建基础镜像的Dockerfile如下:
FROM opensuse:latest
MAINTAINER "Feng Di <fengdidi@gmail.com>"
LABEL description="Base Image Java 8"
RUN zypper -n update && zypper -n install java-1_8_0-openjdk && mkdir /app
之后我们打成名为opensuse-java8的docker镜像:
docker build -t opensuse-java8:latest .
接下来,构建应用镜像的Dockerfile如下:
FROM opensuse-java8:latest
MAINTAINER "Feng Di <fengdidi@gmail.com>"
LABEL description="Spring Boot Image"
WORKDIR /app
COPY gs-spring-boot-0.1.0.jar /app/app.jar
EXPOSE 8080
CMD java -jar /app/app.jar
注意构建这个镜像时,需要将上一章编译出的gs-spring-boot-0.1.0.jar放到于上面Dockerfile相同目录下。
docker build -t springbootdemo:latest .
在Kubernetes上运行
这里我们使用Minikube在自己的开发机上跑一个K8S集群。
- minikube的详细信息请移步:https://kubernetes.io/docs/getting-started-guides/minikube/
- minikube在Mac上安装需要提前安装VirtualBox和Kubectl,
VirtualBox安装介质可在官网上下载:https://www.virtualbox.org/wiki/Downloads - 用于某些原因,minikube在国内运行需要做一些改造,具体请移步:https://www.cnblogs.com/sxwen/p/8421188.html
安装好minikube后,运行下面命令启动minikube:
minikube start
由于minikube里面的docker daemon与本机的不相同,这里我们需要先将刚才构建的镜像上传到minikube的docker daemon里面。首先将刚才构建的镜像导出到一个tar包里:
docker save springbootdemo:latest > springbootdemo.tar
之后切换到minikube的docker daemon并执行导入:
eval $(minikube docker-env)
docker load < springbootdemo.tar
创建一个名为springbootdemo.yaml的文件,这个文件容器编排的脚本
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: springbootdemo
spec:
replicas: 2 # tells deployment to run 2 pods matching the template
template: # create pods using pod definition in this template
metadata:
labels:
app: springbootdemo
spec:
containers:
- name: springbootdemo
image: springbootdemo:latest
imagePullPolicy: Never #just for minikube,do not use this in production!!!
ports:
- containerPort: 8080
注意这里因为要minikube使用本地构建的镜像,所以我在编排脚本里面加入了这一行:“imagePullPolicy: Never ”,意思是说让kubernetes在创建pod的时候不从dockerhub上pull镜像,而是直接使用本地的镜像,这个设置仅用于开发测试,这个在生产环境上千万不要这么用。
接下来,我们创建这个deployment,然后将这个服务暴露出去:
kubectl create -f springbootdemo.yaml
kubectl expose deployment springbootdemo --type="LoadBalancer"
minikube service springbootservice --url
此时我们执行“kubectl get pods”,如果刚才创建的pod正常启动了,我们可以执行如下命令查看这个服务对外暴露的IP和端口:
minikube service springbootservice --url
我这里的输出为:http://192.168.99.100:30009/
在浏览器上输入上述地址,可以看到我们的服务跑起来了:
服务扩容
在服务的运行过程中,有时我们可能会面临服务的消费者过多,对服务产生很大压力的场景。这个时候,我们就需要对这个服务做弹性扩容。
现在运行如下命令:
kubectl get pods
我们可以看到,现在springbootdemo这个服务,有两个实例在运行,下面我将它扩充到4个:
kubectl scale deployment springbootdemo --replicas=4
再次执行“kubectl get pods”,可以看到服务成功扩充到了4个节点。
总结
以上就是一个基于kubernetes和springboot的简单实例,在这个实例中,我们将一个springboot的应用以微服务的形式部署到了kubernetes集群中,并实现了对外暴露、对外服务。当然这仅仅是一个“Hello World”实例,kubernetes和springboot都是非常成熟非常优雅的框架,我相信将这两个技术结合使用构建微服务是一个不错的选择。