基于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 <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后,运行下面命令启动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都是非常成熟非常优雅的框架,我相信将这两个技术结合使用构建微服务是一个不错的选择。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容

  • nginx初学者指南 本文为选译的官方文档,原文链接请点击此处跳转 启动,停止和重载配置配置文件的组成结构发布静态...
    默浑秩阅读 367评论 1 2
  • 4月晨读- 如何教好大班(英语)课 Day 4 欢迎来到小静的晨读会,今天为大家分享的是《如何教好大班英语课》之 ...
    叶小静Stamy阅读 378评论 0 0
  • os.listdir()用法描述 os.listdir() 方法用于返回指定的文件夹包含的文件或文件夹的名字的列表...
    shaoxiangjun阅读 14,906评论 1 0
  • 因为喜欢而去做,做到了自然就更喜欢 1月份班级开年主题“找回初心”,我们重新定义了加入007写作的初心,也坚定了自...
    思学101阅读 329评论 2 1
  • 黑乎乎的世界里,伸手不见五指的黑,黑到了灵魂的深处,我拼命地眨眨眼睛,想把这黑眨掉,越眨就越黑,可能是太用力了...
    追梦1981阅读 424评论 0 1