Categories
程式開發

使用Spring Boot和Docker构建微服务架构(三)


【编者的话】 这篇也是我多年前发表在DockOne社区的文章“,对微服务架构以及容器化概念作一个概述,利用工具进行设置,深入探讨如何使用Docker工作,然后搭建我们的第一个容器,接着构建一个Employee微服务,并将添加一些额外的服务/容器,并且更新容器,采用Docker Compose以及使用HAProxy容器进行负载均衡。现在回顾这篇处于微服务及容器技术洪荒时代的文章,还是挺有意思的,现在从故纸堆里拿出来给大家作为参考吧,由于文章比较长,所以分为四篇来讲,本篇是第三篇。

第三篇 构建你的第一个微服务以及容器的连接

本篇构建微服务的步骤如下:

● 建立一个新的Spring Boot工程

● 定义我们的员工对象

● 持久连接

● 公开Web服务

● 定义一个Docker容器来运行我们的微服务,包括连接到我们在第二篇中创建的Mongo容器

● 在稍早设置的Docker Machine中运行我们的容器

建立我们的Spring Boot工程

我们将在我们的工程根目录下创建一个文件夹(生产环境中是在一个单独的版本库中)来承载我们的服务,在这个文件夹中,我们将创建build.grade文件。Spring Boot的优势就是在于对于依赖定义非常纯粹,如魔法般地带来了大量的互操作性。我们的build.grade文件看起来如下:

buildscript {

  repositories {

    jcenter()

  }

  dependencies { 

   classpath ‘org.springframework.boot:spring-boot-gradle-plugin:1.2.0.RELEASE’

  }

}

apply plugin: ‘spring-boot’

 

repositories { jcenter()

}

dependencies {

  compile “org.springframework.boot:spring-boot-starter-actuator”

  compile “org.springframework.boot:spring-boot-starter-web”

}

现在,正如我们提到的,我们将使用IDE,具体地说就是Spring Tool Suite(STS),所以我们将增加对于Gradle的支持。首先,打开STS,接着在打开的Dashboard页面上点击“IDE Extensions”的链接:

使用Spring Boot和Docker构建微服务架构(三) 1

在屏幕上选择“Grade Support”,点击“Install”,按照提示完成安装,其中包括重启STS:

使用Spring Boot和Docker构建微服务架构(三) 2

重启之后,选择“Import project…”,接着选择Gradle project(现在可用了),指向你的目录,然后点击“Build Model”——这将会用“Employee”来产生项目列表——选择这个列表并且点击“Finish”,这将会导入一个简单的build.grade到我们启动的工程。一旦工程被导入之后,第一个将是Spring Boot配置类,在这个场景中,我们将按照他们的服务来命名,所以在这种情况下配置类将会命名为EmployeeBoot。Spring大量使用了注解和反射,所以最小的配置也是需要的(忽略导入):

@Configuration

@EnableAutoConfiguration

@ComponentScan

public class EmployeeBoot {

 

       public static void main(String[] args) {

            SpringApplication.run(EmployeeBoot.class);

        }

     

}

我们的Employee类

接下来,我们将使得我们的POJO类来持有员工信息,我们目前保持最少的字段,有必要的话我们将增加:

Employee.Class

@Document(collection=”employees”)

public class Employee {

 

    @Id

    private String id; 

     

    private String email;

    private String fullName;

    private String managerEmail;

 

 // getters and setters omitted for brevity

}

注意@Document这个注解——这会将这个对象连结为一个Mongo文档,并为该Employee“文档”在哪里存储指定了集合名字。

接下来我们将定义一个Spring持久类来读取这些Employee:

EmployeeRepository.class:

public interface EmployeeRepository extends

MongoRepository {

 

}

Spring框架的一个优美之处就是在于所有你需要写的——比如扩展自MongoRepository的接口甚至不需要手动编写实现代码。两个通用的参数中第一个表示需要持久化的对象类型(Employee),第二个表示唯一的标识符类型(String类型)。Spring框架将会自动提供所声明的功能95%的实现。接下来的问题就是:Spring如何知道数据库的位置?Spring默认情况下将会寻找localhost:27017,这显然是不会工作的,所以我们需要直接设置位置新型。我们可以实现自己的Mongo模板Bean,但是幸运的是Spring允许我们通过Java属性表传递连接信息。我们可以定义一个属性文件或者在命令行上传递进来。我们选择了后者因为当稍后构建我们的容器时命令行方式非常方便。最后我们需要创建的是一个或者两个Rest端点并确保它们可以工作。我们将构建一个快速的Spring Controller,然后我们就可以测试了。

EmployeeController.java

@RestController

@RequestMapping(“/employee”)

public class EmployeeController {

     

    @Autowired

    EmployeeRepository employeeRepository;

 

    @RequestMapping(method = RequestMethod.POST)

    public Employee create(@RequestBody Employee employee){

         

        Employee result = employeeRepository.save(employee);

        return result;

    }

     

    @RequestMapping(method = RequestMethod.GET, value=”/{employeeId}”)

    public Employee get(@PathVariable String employeeId){

        return employeeRepository.findOne(employeeId);

    }

     

     

}

最开始的@RestController和@RequestMapping这两个类级别的注解告诉Spring框架需要公开这是一个接收JSON的Rest服务,并且公开了URI路径是/employee。@Autowired注解告诉Spring框架采用上面我们定义的Repository接口的自动生成实现代码并将其注入到这个Controller中。现在到了特定的操作——方法级别的@RequestMapping注解表明这个方法将会基于HTTP动词来使用(在本例中是POST和GET),另外对于GET操作,我们指明了一个URL路径{employee},比如使用/employee/abcd1234来寻找一个员工并且返回该值。

现在我们有了足够多的准备工作可以来测试了,第一点,我们需要编译和运行我们的Spring Boot应用,在Eclipse中有很多方法可以做到这一点,但是我们将从命令行开始,并以我们的方式工作。在你的Employee目录中,敲入:gradle build,这个命令将会编译Spring Boot应用到build/lib/Employee.jar,这个jar包包含了运行这个应用锁需要的所有东西,包括一个嵌入式的Servlet容器(默认情况下是Tomcat)。

在我们运行和测试这个应用之前,我们需要稍作回顾——我们的Mongo服务又在哪里?观察“docker ps”命令的输出,我们记得虚拟机的32777端口映射到了Mongo容器的27017端口,虚拟机的IP地址是192.126.99.100。如前所述,我们可以通过传递一个环境变量属性的方式来把连接属性传递给Spring,所以运行这个应用的命令行如下:

java -Dspring.data.mongodb.uri=mongodb://192.168.99.100:32777/micros -jar build/libs/Employee.jar

一旦应用启动之后(应该在1~4秒),你可以使用选项中的Rest工具来打开Web服务。

使用Spring Boot和Docker构建微服务架构(三) 3

不要忘了在HTTP头部包含值为“application/json”的“Content-Type”,你应该收到如下的响应(id的值取决于你自己的情况):

{

  “id”: “55fb2f1930e07c6c844b02ff”,

  “email”: “[email protected]”,

  “fullName”: “Daniel Greene”,

  “managerEmail”: null

}

你可以通过如下的调用测试我们的GET方法:

http://localhost:8080/employee/55fb2f1930e07c6c844b02ff

你应该得到相同的文档,万岁!我们的服务起作用了!

将Boot应用导入容器

现在我们需要构建我们的第一个容器来运行我们的Employee微服务。我们的途径是定义一个Dockerfile,这个文件将会定义如何生成一个镜像并放入我们的精益求精、短小精悍的微服务中。让我们阅读这个文件并逐步解析(参考第三篇第三步请跳到这儿):

FROM java:8

VOLUME /tmp

ADD build/libs/Employee.jar app.jar

EXPOSE 8080

RUN bash -c ‘touch /app.jar’

ENTRYPOINT [“java”,”-Dspring.data.mongodb.uri=mongodb://mongodb/micros”, “-Djava.security.egd=file:/dev/./urandom”,”-jar”,”/app.jar”]

● 我们从一个包含了安装了Java 8的标准镜像(镜像名字为“Java”,标签为“8”)开始构建过程

● 我们接着定义了一个名为/tmp的卷

● 接着将本地文件系统的一个文件添加进来,并且重命名为“app.jar”,重命名不是必须的,只是一个可用的可选项

● 我们声明想要公开容器的8080端口

● 在容器内运行一个touch命令,这样可以确保app.jar文件的修改日期

● ENTRYPOINT命令定义了容器启动时需要运行的内容——我们运行Java,设置我们的Spring Mongo属性,还有快速的附加属性来加速Tomcat启动时间,然后指向我们的jar包。

现在我们通过运行如下命令构建镜像:

docker build -t microservicedemo/employee . 

我们可以敲入docker image看到结果

 REPOSITORY                  TAG             IMAGE ID            CREATED             VIRTUAL SIZE

microservicedemo/employee   latest          364ffd8162b9        15 minutes ago      846.6 MB

接下来的问题就是“我们的服务容器如何与Mongo容器交互”,为此,我们引入了容器的link机制。当你运行一个新的容器时,你可以传入一个可选的-link参数指定一个运行中的容器的名字,这样新的容器就可以与这个容器通讯了,所以我们的命令是:

docker run -P -d –name employee –link mongodb microservicedemo/employee

我们启动了一个新的容器,公开了端口(-P),以后台方式运行(-d),命名为employee(—name),接着将这个新的容器连接到了一个名为“mongodb”的容器(-link),连接过程如下:

● 在employee容器的host文件添加一个条目指向MongoDB容器的运行位置

● 在employee容器内添加一些环境变量来协助其他必要的编程访问,可以运行如下命令查看:

docker exec employee bash -c ‘env | grep MONGODB’ 

● 允许容器通过公开的端口来直接通讯,这样就不需要担心主机的部分映射了。如果你还记得上述的内容,我们设置了Spring Mongo到MongoDB的URL作为主机名(mongodb://mongodb/micros“),所以有了host文件条目和运行在默认端口的Mongo,Boot应用容器可以连上数据库了

在容器运行时我们可以来执行相同的Web服务,只是这次是作为容器来运行的(对于我来说,容器的8080端口会被映射到虚拟机的32772端口):

使用Spring Boot和Docker构建微服务架构(三) 4

本篇我们已经取得了很大的进展,我们有了两个容器可以工作并且可以互相通讯,接下来的第四篇我们将添加一些额外的服务/容器,可以观察到构建更新的进度并与CI工具集成工作。