构建Spring微服务并对其进行Dockerize生产

在本文中,您将学习微服务架构以及如何使用Spring Boot实施微服务架构。 在使用该技术创建了一些项目之后,您将把工件部署为Docker容器并模拟一个容器协调器(例如Kubernetes)使用Docker撰写为了简化。 锦上添花的是使用Spring Profiles进行身份验证集成。 您将了解如何通过生产资料启用它。

但首先,让我们谈谈微服务。

Understand a Modern Microservice Architecture

与整体架构相反,微服务要求您将应用程序分成逻辑上相关的小片段。 这些程序段是独立的软件,例如,可以使用HTTP或消息与其他程序段进行通信。

There is some discussion of what size micro is. Some say a microservice is software that can be created in a single sprint; others say microservices can have bigger size if it is logically related (you can’t mix apples and oranges, for example). I agree with Martin Fowler and think size doesn’t matter that much, and it’s more related to the style.

微服务有许多优点:

  • 无高耦合风险-由于每个应用程序都处于不同的进程中,因此无法创建彼此对话的类。轻松缩放-如您所知,每项服务都是独立的软件。 因此,它可以按需扩展或缩小。 而且,由于代码是较小的比整体而言,它可能会启动得更快。多个堆栈-您可以为每项服务使用最佳的软件堆栈。 例如,当Python对您要构建的东西更好时,就不再需要使用Java。更少的合并和代码冲突-由于每个服务都是不同的存储库,因此更易于处理和审查提交。

但是,有一些缺点:

  • 你有一个新敌人-网络问题。 服务启动了吗? 如果服务中断,该怎么办?复杂的部署过程-OK CI / CD在这里,但是现在每种服务都有一个工作流程。 如果他们使用不同的堆栈,则可能甚至无法为每个堆栈复制工作流程。更复杂且难以理解的架构-这取决于您的设计方式,但请考虑以下事项:如果您不知道方法在做什么,则可以阅读其代码。 在微服务体系结构中,此方法可能在另一个项目中,甚至可能没有代码。

Nowadays, it’s commonly accepted that you should avoid a microservice architecture at first. After some iterations, the code division will become clearer as will the demands of your project. It is often too expensive to handle microservices until your development team starts into small projects.

Build Microservices in Spring with Docker

在本教程中,您将构建两个项目:服务(school-service)和UI(school_ui)。 该服务提供了持久层和业务逻辑,而UI提供了图形用户界面。 只需最少的配置即可连接它们。

初始设置后,我将讨论发现和配置服务。 两种服务都是任何大规模分布式体系结构的重要组成部分。 为了证明这一点,您将其与OAuth 2.0集成在一起,并使用配置项目来设置OAuth 2.0密钥。

最后,每个项目都将转换为Docker映像。 Docker Compose将用于模拟容器协调器因为Compose将使用服务之间的内部网络来管理每个容器。

最后,将介绍Spring配置文件以根据当前适当分配的环境来更改配置。 这样,您将拥有两个OAuth 2.0环境:一个用于开发,另一个用于生产。

更少的单词,更多的代码! 克隆本教程的资源库,然后查看开始科。

git clone -b start https://github.com/oktadeveloper/okta-spring-microservices-docker-example.git 

根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>com.okta.developer.docker_microservices</groupId>
    <artifactId>parent-pom</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>pom</packaging>
    <name>parent-project</name>
    <modules>
        <module>school-service</module>
        <module>school-ui</module>    
    </modules>
</project>

这称为汇总项目因为它汇总了子项目。 这对于在所有声明的模块上运行相同的Maven任务很有用。 这些模块不需要将根模块用作父模块。

有两个模块可用:学校服务和学校UI。

The School Service Microservice

The school-service directory contains a Spring Boot project that acts as the project’s persistence layer and business rules. In a more complex scenario, you would have more services like this. The project was created using the always excellent Spring Initializr with the following configuration:

Spring Initializr

  • 组-com.okta.developer.docker_microservices神器-学校服务依赖关系-JPA,Web,Lombok,H2

You can get more details about this project by reading Spring Boot with PostgreSQL, Flyway, and JSONB. To summarize, it has the entities TeachingClass, Course, Student and uses TeachingClassServiceDB and TeachingClassController to expose some data through a REST API. To test it, open a terminal, navigate to the school-service directory, and run the command below:

./mvnw spring-boot:run

该应用程序将在端口上启动8081(如文件中所定义school-service / src / main / resources / application.properties),因此您应该可以导航到http://本地主机:8081并查看返回的数据。

> curl http://localhost:8081
[
   {
      "classId":13,
      "teacherName":"Profesor Jirafales",
      "teacherId":1,
      "courseName":"Mathematics",
      "courseId":3,
      "numberOfStudents":2,
      "year":1988
   },
   {
      "classId":14,
      "teacherName":"Profesor Jirafales",
      "teacherId":1,
      "courseName":"Spanish",
      "courseId":4,
      "numberOfStudents":2,
      "year":1988
   },
   {
      "classId":15,
      "teacherName":"Professor X",
      "teacherId":2,
      "courseName":"Dealing with unknown",
      "courseId":5,
      "numberOfStudents":2,
      "year":1995
   },
   {
      "classId":16,
      "teacherName":"Professor X",
      "teacherId":2,
      "courseName":"Dealing with unknown",
      "courseId":5,
      "numberOfStudents":1,
      "year":1996
   }
]

The Spring-Based School UI Microservice

顾名思义,学校UI是利用学校服务的用户界面。 它是使用Spring Initializr使用以下选项创建的:

  • 组-com.okta.developer.docker_microservices神器-学校UI依存关系-网络,Hateoas,Thymeleaf,龙目岛

UI是一个单独的网页,列出了数据库上可用的类。 要获取信息,它与学校服务通过文件中的配置school-ui / src / main / resources / application。properties。

service.host=localhost:8081

班级学校负责人类具有查询服务的所有逻辑:

package com.okta.developer.docker_microservices.ui.controller;

import com.okta.developer.docker_microservices.ui.dto.TeachingClassDto;
import org.springframework.beans.factory.annotation.*;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.*;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.ModelAndView;
import java.util.List;

@Controller
@RequestMapping("/")
public class SchoolController {
    private final RestTemplate restTemplate;
    private final String serviceHost;

    public SchoolController(RestTemplate restTemplate, @Value("${service.host}") String serviceHost) {
        this.restTemplate = restTemplate;
        this.serviceHost = serviceHost;
    }

    @RequestMapping("")
    public ModelAndView index() {
        return new ModelAndView("index");
    }

    @GetMapping("/classes")
    public ResponseEntity<List<TeachingClassDto>> listClasses(){
        return restTemplate
                .exchange("http://"+ serviceHost +"/class", HttpMethod.GET, null,
                        new ParameterizedTypeReference<List<TeachingClassDto>>() {});
    }
}

如您所见,该服务有一个硬编码的位置。 您可以使用这样的环境变量来更改属性设置-Dservice.host =本地主机:9090。 尽管如此,它仍必须手动定义。 有很多实例学校服务应用? 在当前阶段不可能。

用学校服务打开,开始学校UI,然后在浏览器中浏览至http://本地主机:8080:

./mvnw spring-boot:run

您应该看到如下页面:

School UI

Build a Discovery Server with Spring Cloud and Eureka

现在,您有了一个可以使用的服务,该应用程序使用两种服务将信息提供给最终用户。 怎么了 在现代应用程序中,开发人员(或操作)通常不知道应用程序可能部署在何处或端口上。 部署应该是自动化的,因此没有人关心关于服务器名称和物理位置。 (除非您在数据中心内工作。否则,希望您在意!)

但是,必须有一个工具来帮助服务发现其对应对象。 有许多解决方案可用,对于本教程,我们将使用尤里卡来自Netflix,因为它具有出色的Spring支持。

Go back to start.spring.io and create a new project as follows:

  • 组:com.okta.developer.docker_microservices工件:发现依赖项:Eureka Server

编辑主DiscoveryApplication.java类添加一个@EnableEurekaServer注解:

package com.okta.developer.docker_microservices.discovery;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class DiscoveryApplication {
    public static void main(String[] args) {
        SpringApplication.run(DiscoveryApplication.class, args);
    }
}

而且,您需要更新其application.properties文件,因此它运行在端口8761上,并且不会尝试自行注册。

spring.application.name=discovery-server
server.port=8761
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false

让我们定义每个属性:

  • spring.application.name-应用程序的名称,发现服务还使用该名称来发现服务。 您会看到其他所有应用程序也都有一个应用程序名称。服务器端口-服务器正在运行的端口。8761是Eureka服务器的默认端口。eureka.client.with-eureka注册 - Tells Spring not to register itself into the 发现y service.eureka.client .fetch-registry - Indicates this instance should not fetch 发现y information from the server.

现在,运行并访问http://本地主机:8761。


./mvnw spring-boot:run

Empty Eureka Service

上面的屏幕显示了准备注册新服务的Eureka服务器。 现在,该改变了学校服务和学校UI使用它。

注意:如果您收到ClassNotFoundException:javax.xml.bind.JAXBContext启动时发生错误,这是因为您在Java 11上运行。您可以将JAXB依赖项添加到您的pom.xml解决这个问题。

<dependency>
  <groupId>javax.xml.bind</groupId>
  <artifactId>jaxb-api</artifactId>
  <version>2.3.1</version>
</dependency>
<dependency>
  <groupId>org.glassfish.jaxb</groupId>
  <artifactId>jaxb-runtime</artifactId>
  <version>2.3.2</version>
</dependency>

Use Service Discovery to Communicate Between Microservices

首先,添加必需的依赖关系很重要。 将以下两者都添加pom.xml文件(在学校服务和学校UI项目):

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

该模块是Spring Cloud计划的一部分,因此,需要一个新的依存关系管理节点,如下所示(不要忘记将其添加到两个项目中):

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

现在,您需要配置两个应用程序以向Eureka注册。

在里面application.properties两个项目的文件,添加以下行:

eureka.client.serviceUrl.defaultZone=${EUREKA_SERVER:http://localhost:8761/eureka}
spring.application.name=school-service

不要忘记更改应用程序名称学校服务至学校UI在里面学校UI project. Notice there is a new kind of parameter在里面first line: {EUREKA_SERVER:http:// localhost:8761 / eureka}。 这表示“如果环境变量EUREKA_SERVER存在,请使用其值,否则请使用默认值。” 这在以后的步骤中将很有用。

你知道吗? 两个应用程序都准备好将自己注册到发现服务中。 您无需执行任何其他操作。 我们的主要目标是学校UI项目不需要知道哪里学校服务是。 因此,您需要进行更改学校负责人(在里面学校UI项目)使用学校服务在其REST端点中。 您也可以删除serviceHost此类中的变量。

package com.okta.developer.docker_microservices.ui.controller;

import com.okta.developer.docker_microservices.ui.dto.TeachingClassDto;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.ModelAndView;

import java.util.List;

@Controller
@RequestMapping("/")
public class SchoolController {
    private final RestTemplate restTemplate;

    public SchoolController(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    @RequestMapping("")
    public ModelAndView index() {
        return new ModelAndView("index");
    }

    @GetMapping("/classes")
    public ResponseEntity<List<TeachingClassDto>> listClasses() {
        return restTemplate
                .exchange("http://school-service/classes", HttpMethod.GET, null,
                        new ParameterizedTypeReference<List<TeachingClassDto>>() {});
    }
}

在集成Eureka之前,您需要进行配置,指出学校服务是。 现在,您已将服务调用更改为使用其他服务使用的名称:无端口,无主机名。 您需要的服务就在某处,您不需要知道在哪里。

的学校服务可能有个的多个实例,最好在这些实例之间进行负载平衡。 幸运的是,Spring有一个简单的解决方案:RestTemplate创建bean,添加@LoadBalanced注释如下。 每当您向服务器提出问题时,Spring都会管理多个实例调用。

package com.okta.developer.docker_microservices.ui;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.config.annotation.*;

@SpringBootApplication
public class UIWebApplication implements WebMvcConfigurer {

    public static void main(String[] args) {
        SpringApplication.run(UIWebApplication.class, args);
    }

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        if(!registry.hasMappingForPattern("/static/**")) {
            registry.addResourceHandler("/static/**")
                    .addResourceLocations("classpath:/static/", "classpath:/static/js/");
        }
    }
}

现在,开始重启学校服务和学校UI(并保持发现服务正常运行)。 快速浏览http://本地主机:8761再次:

Populated Eureka Service

现在,您的服务正在与Discovery服务器共享信息。 您可以再次测试该应用程序,并看到它可以照常工作。 只是去http://本地主机:8080在您喜欢的浏览器中。

Add a Configuration Server to Your Microservices Architecture

While this configuration works, it’s even better to remove any trace of configuration values in the project’s source code. First, the configuration URL was removed from the project and became managed by a service. Now, you can do a similar thing for every configuration on the project using Spring Cloud Config.

First, create the configuration project using Spring Initializr and the following parameters:

  • 组:com.okta.developer.docker_microservices工件:配置依赖项:配置服务器,Eureka发现

在主类中,添加@EnableConfigServer:

package com.okta.developer.docker_microservices.config;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;

@SpringBootApplication
@EnableConfigServer
public class ConfigApplication {
    ...
}

将以下属性和值添加到项目的application.properties:

spring.application.name=CONFIGSERVER
server.port=8888
spring.profiles.active=native
spring.cloud.config.server.native.searchLocations=.
eureka.client.serviceUrl.defaultZone=${EUREKA_SERVER:http://localhost:8761/eureka}

有关属性的一些解释:

  • spring.profiles.active =本地-表示Spring Cloud Config必须使用本机文件系统来获取配置。 通常使用Git存储库,但是为了简单起见,我们将坚持使用本机文件系统。spring.cloud.config.server.native.searchLocations-包含配置文件的路径。 如果将其更改为硬盘驱动器上的特定文件夹,请确保并创建school-ui.properties文件中。

Now, you need something to configure and apply to this example. How about Okta’s configuration? Let’s put our school-ui behind an authorization layer and use the property values provided by the configuration project.

You can register for a free-forever developer account that will enable you to create as many user and applications you need to use! After creating your account, create a new Web Application in Okta’s dashboard (Applications > Add Application):

New web application

并用以下值填写下一个表格:

New web application, Step 2

该页面将为您返回一个应用程序ID和一个密钥。 保持安全并创建一个名为school-ui.properties在的根文件夹中配置具有以下内容的项目。 不要忘记填充变量值:

okta.oauth2.issuer=https://{yourOktaDomain}/oauth2/default
okta.oauth2.clientId={yourClientId}
okta.oauth2.clientSecret={yourClientSecret}

现在,运行配置 project and check if its getting the 配置uration data properly:

./mvnw spring-boot:run
> curl http://localhost:8888/school-ui.properties
okta.oauth2.clientId: YOUR_CLIENT_ID
okta.oauth2.clientSecret: YOUR_CLIENT_SECRET
okta.oauth2.issuer: https://YOUR_DOMAIN/oauth2/default

Change School UI to Use Spring Cloud Config and OAuth 2.0

现在,您需要对Spring UI项目进行一些更改。

首先,你需要改变school-ui / pom.xml并添加一些新的依赖项:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
    <groupId>com.okta.spring</groupId>
    <artifactId>okta-spring-boot-starter</artifactId>
    <version>1.1.0</version>
</dependency>
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>

创建一个新的安全配置中的课程com.okta ... ui.config包:

package com.okta.developer.docker_microservices.ui;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter {

   @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/").permitAll()
                .anyRequest().authenticated()
            .and()
                .logout().logoutSuccessUrl("/")
            .and()
                .oauth2Login();
    }
}

改变你的学校负责人因此,只有具有范围的用户轮廓将被允许​​(每个经过身份验证的用户都拥有)。

import org.springframework.security.access.prepost.PreAuthorize;

....

@GetMapping("/classes")
@PreAuthorize("hasAuthority('SCOPE_profile')")
public ResponseEntity<List<TeachingClassDto>> listClasses(){
    return restTemplate
        .exchange("http://school-service/class", HttpMethod.GET, null,
                new ParameterizedTypeReference<List<TeachingClassDto>>() {});
}

一些配置需要在项目启动时定义。 Spring提供了一个聪明的解决方案来正确定位并提取配置数据之前上下文启动。 您需要创建一个文件src / main / resources / bootstrap.yml像这样:

eureka:
  client:
    serviceUrl:
      defaultZone: ${EUREKA_SERVER:http://localhost:8761/eureka}
spring:
  application:
    name: school-ui
  cloud:
    config:
      discovery:
        enabled: true
        service-id: CONFIGSERVER

引导文件会创建一个预引导的Spring Application Context,用于在实际应用程序启动之前提取配置。 您需要将所有属性从application。propertiesSpring需要知道您的Eureka Server的位置以及如何搜索配置。 在上面的示例中,您启用了通过发现服务进行配置(spring。cloud。config。discovery。enabled)并指定配置服务编号。

改变你的application.properties文件,因此它只有一个OAuth 2.0属性:

okta.oauth2.redirect-uri=/authorization-code/callback

最后要修改的文件是src / main / resources / templates / index.html。 对其进行调整,以在用户未通过身份验证时显示登录按钮,在用户登录时显示注销按钮。

<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">

    <title>Hello, world!</title>
</head>
<body>
<nav class="navbar navbar-default">
    <form method="post" th:action="@{/logout}" th:if="${#authorization.expression('isAuthenticated()')}" class="navbar-form navbar-right">
        <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
        <button id="logout-button" type="submit" class="btn btn-danger">Logout</button>
    </form>
    <form method="get" th:action="@{/oauth2/authorization/okta}" th:unless="${#authorization.expression('isAuthenticated()')}">
        <button id="login-button" class="btn btn-primary" type="submit">Login</button>
    </form>
</nav>

<div id="content" th:if="${#authorization.expression('isAuthenticated()')}">
    <h1>School classes</h1>

    <table id="classes">
        <thead>
        <tr>
            <th>Course</th>
            <th>Teacher</th>
            <th>Year</th>
            <th>Number of students</th>
        </tr>
        </thead>
        <tbody>

        </tbody>
    </table>

    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->

    <script src="http://code.jquery.com/jquery-3.3.1.min.js" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
    <script src="static/js/school_classes.js"></script>
</div>

</body>
</html>

您应该在此HTML中了解一些Thymeleaf属性:

  • @{/登出}-返回在后端定义的注销URLth:if =“ $ {#authorization.expression('isAuthenticated()')}”-仅在以下情况下打印HTML:登录@ {// oauth2 / authorization / okta}-这是Spring Security重定向到Okta的URL。 您可以链接到/登录同样,但这只是呈现相同的链接,您必须单击它。th:unless =“ $ {#authorization.expression('isAuthenticated()')}”-仅当用户为已登出

现在,重新启动配置项目和school-ui。 如果您导航到键入http://本地主机:8080,您应该会看到以下屏幕:

Log in

登录后,屏幕应显示如下:

Log off

恭喜,您已经使用Spring Cloud config和Eureka创建了微服务架构来进行服务发现! 现在,让我们更进一步,并对每个服务进行Dockerize。

Use Docker to Package Your Spring Apps

docker is a marvelous technology that allows creating system images similar to Virtual Machines, but that shares the same Kernel of the host operating system. This feature increases system performance and startup time. Also, Docker provided an ingenious built system that guarantees once an image is created; it won’t be changed, ever. In other words: no more “it works on my machine!”

ŤIP: Need a deeper Docker background? Have a look at our Developer’s Guide Ťo Docker.

您需要为每个项目创建一个Docker映像。 每个映像应具有相同的Maven配置,并且Docker文件每个项目的根文件夹中的内容(例如,school-ui/Docker文件)。

在每个项目的pom中,添加dockerfile-maven-plugin:

<plugins>
    ...
    <plugin>
        <groupId>com.spotify</groupId>
        <artifactId>dockerfile-maven-plugin</artifactId>
        <version>1.4.9</version>
        <executions>
            <execution>
                <id>default</id>
                <goals>
                    <goal>build</goal>
                    <goal>push</goal>
                </goals>
            </execution>
        </executions>
        <configuration>
            <repository>developer.okta.com/microservice-docker-${project.artifactId}</repository>
            <tag>${project.version}</tag>
            <buildArgs>
                <JAR_FILE>${project.build.finalName}.jar</JAR_FILE>
            </buildArgs>
        </configuration>
    </plugin>
</plugins>

This XML configures the Dockerfile Maven plugin to build a Docker image every time you run ./mvnw install. Each image will be created with the name developer.okta.com/microservice-docker-${project.artifactId} where project.artifactId varies by project.

创建一个Docker文件每个项目的根目录中的文件。

FROM openjdk:8-jdk-alpine
VOLUME /tmp
ADD target/*.jar app.jar
ENV JAVA_OPTS="
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar"]

The Dockerfile follows what is recommended by Spring Boot with Docker.

现在,改变school-ui / src / main / resources / bootstrap.yml添加一个新的failFast设置:

eureka:
  client:
    serviceUrl:
      defaultZone: ${EUREKA_SERVER:http://localhost:8761/eureka}
spring:
  application:
    name: school-ui
  cloud:
    config:
      discovery:
        enabled: true
        serviceId: CONFIGSERVER
      failFast: true

的spring.cloud.failFast:true设置告诉Spring Cloud Config在找不到配置服务器时立即终止应用程序。 这将对下一步很有用。

Add Docker Compose to Run Everything

创建一个名为docker-compose.yml定义每个项目的开始方式:

version: '3'
services:
  discovery:
    image: developer.okta.com/microservice-docker-discovery:0.0.1-SNAPSHOT
    ports:
      - 8761:8761
  config:
    image: developer.okta.com/microservice-docker-config:0.0.1-SNAPSHOT
    volumes:
      - ./config-data:/var/config-data
    environment:
      - JAVA_OPTS=
         -DEUREKA_SERVER=http://discovery:8761/eureka
         -Dspring.cloud.config.server.native.searchLocations=/var/config-data
    depends_on:
      - discovery
    ports:
      - 8888:8888
  school-service:
    image: developer.okta.com/microservice-docker-school-service:0.0.1-SNAPSHOT
    environment:
      - JAVA_OPTS=
        -DEUREKA_SERVER=http://discovery:8761/eureka
    depends_on:
      - discovery
      - config
  school-ui:
    image: developer.okta.com/microservice-docker-school-ui:0.0.1-SNAPSHOT
    environment:
      - JAVA_OPTS=
        -DEUREKA_SERVER=http://discovery:8761/eureka
    restart: on-failure
    depends_on:
      - discovery
      - config
    ports:
      - 8080:8080

如您所见,每个项目现在都是Docker中已声明的服务,将文件组成。 它将暴露其端口和其他一些属性。

  • 除了所有项目发现将具有可变值-DEUREKA_SERVER=http://发现:8761/eureka。 这将告诉您在哪里可以找到发现服务器。 Docker Compose在服务之间创建了一个虚拟网络,每个服务使用的DNS名称就是其名称:这就是为什么可以使用发现作为主机名。设定档服务将具有用于配置文件的卷。 该卷将映射到/ var / config-data在docker容器中。 还有,财产spring.cloud.config.server.native.searchLocations将被覆盖为相同的值。 您必须存储文件学校UI.properties在卷映射上指定的同一文件夹中(在上面的示例中,相对的夹./config-data)。的学校UI项目将拥有财产重新启动:失败。 这将Docker Compose设置为在应用程序失败后立即重启。 与一起使用failFast属性允许应用程序继续尝试启动,直到发现和设定档项目已完全准备就绪。

就是这样! 现在,构建图像:

cd config && ./mvnw clean install
cd ../discovery && ./mvnw clean install
cd .. && ./mvnw clean install

最后一个命令可能会失败,并在学校UI项目:

java.lang.IllegalStateException: Failed to load ApplicationContext
Caused by: java.lang.IllegalStateException: No instances found of configserver (CONFIGSERVER)

要解决此问题,请创建一个school-ui / src / test / resources / test.properties文件并添加属性,以便Okta的配置通过,并且在测试时不使用发现或配置服务器。

okta.oauth2.issuer=https://{yourOktaDomain}/oauth2/default
okta.oauth2.clientId=TEST
spring.cloud.discovery.enabled=false
spring.cloud.config.discovery.enabled = false
spring.cloud.config.enabled = false

然后修改UIWebApplicationTests.java加载此文件以获取测试属性:

import org.springframework.test.context.TestPropertySource;

...
@TestPropertySource(locations="classpath:test.properties")
public class UIWebApplicationTests {
    ...
}

现在您应该可以运行了./mvnw全新安装在里面学校UI项目。

完成后,运行Docker Compose以启动所有容器(在同一目录中,docker-compose.yml是)。

docker-compose up -d
Starting okta-microservice-docker-post-final_discovery_1 ... done
Starting okta-microservice-docker-post-final_config_1 ... done
Starting okta-microservice-docker-post-final_school-ui_1 ... done
Starting okta-microservice-docker-post-final_school-service_1 ... done

现在,您应该能够像以前一样浏览该应用程序。

Use Spring Profiles to Modify Your Microservices’ Configuration

Now you’ve reached the last stage of today’s journey through microservices. Spring Profiles is a powerful tool. Using profiles, it is possible to modify program behavior by injecting different dependencies or configurations completely.

假设您有一个结构良好的软件,其持久层与业务逻辑分离。 例如,您还提供对MySQL和PostgreSQL的支持。 每个数据库可能有不同的数据访问类,这些数据访问类仅由定义的概要文件加载。

另一个用例是配置:不同的配置文件可能具有不同的配置。 以身份验证为例。 您的测试环境会进行身份验证吗? 如果是这样,则不应使用与生产相同的用户目录。

将您的配置项目更改为在Okta中有两个应用程序:一个默认(用于开发),另一个用于生产。 在Okta网站上创建一个新的Web应用程序,并将其命名为“ okta-docker-production”。

现在,在您的配置项目,创建一个名为school-ui-production.properties。 你已经拥有了school-ui.properties,每个School UI实例都将使用它。 在文件末尾添加环境时,Spring会合并两个文件,并优先于最特定的文件。 使用生产应用的客户ID和密码保存文件,如下所示:

school-ui-production.properties

okta.oauth2.clientId={YOUR_PRODUCTION_CLIENT_ID}
okta.oauth2.clientSecret={YOUR_PRODUCTION_CLIENT_SECRET}

现在,使用Maven运行配置项目,然后运行以下两个卷曲命令:

./mvnw spring-boot:run

> curl http://localhost:8888/school-ui.properties

okta.oauth2.issuer: https://{yourOktaDomain}/oauth2/default
okta.oauth2.clientId: ==YOUR DEV CLIENT ID HERE==
okta.oauth2.clientSecret: ==YOUR DEV CLIENT SECRET HERE==

> curl http://localhost:8888/school-ui-production.properties
okta.oauth2.issuer: https://{yourOktaDomain}/oauth2/default
okta.oauth2.clientId: ==YOUR PROD CLIENT ID HERE==
okta.oauth2.clientSecret: ==YOUR PROD CLIENT SECRET HERE==

如您所见,即使文件学校用户界面制作有两个属性,配置 project displays three (since the 配置urations are merged).

现在,您可以更改学校UI服务于docker-compose.yml使用生产轮廓:

school-ui:
  image: developer.okta.com/microservice-docker-school-ui:0.0.1-SNAPSHOT
  environment:
    - JAVA_OPTS=
      -DEUREKA_SERVER=http://discovery:8761/eureka
      -Dspring.profiles.active=production
  restart: on-failure
  depends_on:
    - discovery
    - config
  ports:
    - 8080:8080

您还需要复制school-ui-production.properties给你配置数据目录。 然后关闭所有Docker容器并重新启动它们。

docker-compose down
docker-compose up -d

您应该在以下日志中看到以下内容:学校UI容器:

The following profiles are active: production

而已! 现在,您可以在生产配置文件中运行微服务架构。 头晕!

小费:如果你想证明你的okta-docker-production应用已使用但未使用okta-docker,您可以停用okta-dockerOkta中的应用程序,并确认您仍然可以登录http://本地主机:8080。

Learn More About Microservices, Spring, Docker, and Modern Application Security

在这篇文章中,您了解了有关微服务以及如何部署它们的更多信息,以及:

  • 什么是微服务?服务应如何发现其依赖关系而无需事先知道它们的位置。如何以信息的中心点维护分布式配置。 该配置可以管理一个或多个应用程序和环境。如何使用Spring Cloud Config配置OAuth 2.0。如何使用Docker和Docker Compose部署微服务如何使用Spring Profiles在生产环境中进行部署。

如果您有兴趣在Spring学习更多有关微服务或现代应用程序开发的知识,建议您查看以下资源:

If you have any questions about this post, please leave a comment below. You can follow @oktadev on Twitter for more awesome content!

from: https://dev.to//oktadev/build-spring-microservices-and-dockerize-them-for-production-1i4g

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值