基于Kubernetes和Springboot构建微服务

最近一直在研究基于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.png

 

这样,我们就完成了一个基本的SpringBoot应用。

构建Docker镜像

接下来,我们来构建一个Docker镜像,用于运行刚才我们开发的SpringBoot应用。
首先,我们要构建一个基础镜像,这个镜像包含了简单的操作系统,JDK环境等等。我们没有直接使用dockerhub上的Java8基础镜像,而是基于opensuse的基础镜像,之后在上面安装OpenJDK,原因是我们公司在生产环境使用SUSE Linux,运维人员对SUSE更熟悉,这也反映出在文章开始我提到的,一个微服务框架内的“优秀公民”要为了符合规范,做一些妥协,这样整个微服务体系才可以健康发展。
废话少说,直接上代码。构建基础镜像的Dockerfile如下:

 

FROM opensuse:latest
MAINTAINER "Feng Di <[email protected]>"
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 <[email protected]>"
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后,运行下面命令启动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/
在浏览器上输入上述地址,可以看到我们的服务跑起来了:

 

minikube.png

服务扩容

在服务的运行过程中,有时我们可能会面临服务的消费者过多,对服务产生很大压力的场景。这个时候,我们就需要对这个服务做弹性扩容。
现在运行如下命令:

 

kubectl get pods

我们可以看到,现在springbootdemo这个服务,有两个实例在运行,下面我将它扩充到4个:

 

kubectl scale deployment springbootdemo --replicas=4

再次执行“kubectl get pods”,可以看到服务成功扩充到了4个节点。

总结

以上就是一个基于kubernetes和springboot的简单实例,在这个实例中,我们将一个springboot的应用以微服务的形式部署到了kubernetes集群中,并实现了对外暴露、对外服务。当然这仅仅是一个“Hello World”实例,kubernetes和springboot都是非常成熟非常优雅的框架,我相信将这两个技术结合使用构建微服务是一个不错的选择。


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章