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

“我喜欢编写身份验证和授权代码。” 〜从来没有Java开发人员。 厌倦了一次又一次地建立相同的登录屏幕? 尝试使用Okta API进行托管身份验证,授权和多因素身份验证。

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

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

了解现代微服务架构

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

有一些关于微型尺寸的讨论。 有人说微服务是可以在单个冲刺中创建的软件。 其他人则说,如果微服务在逻辑上相关(例如,您不能混合使用苹果和橙子),则微服务的规模可能会更大。 我同意马丁·福勒Martin Fowler)的观点,认为尺寸并没有多大关系,它与款式息息相关。

微服务有许多优点:

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

但是,有一些缺点:

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

如今,通常应该首先避免使用微服务架构 。 经过一些迭代后,代码划分将变得更加清晰,项目的需求也将变得更加清晰。 在您的开发团队开始进行小型项目之前,处理微服务通常过于昂贵。

在Spring使用Docker构建微服务

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

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

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

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

更少的单词,更多的代码! 克隆本教程的资源库,并检出start分支。

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

pom.xml文件不是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。

学校服务微服务

school-service目录包含一个Spring Boot项目,该项目充当项目的持久层和业务规则。 在更复杂的情况下,您将拥有更多这样的服务。 该项目是使用始终出色的Spring Initializr创建的,并具有以下配置:

Spring微服务
  • 组– com.okta.developer.docker_microservices
  • 神器– school-service
  • 依赖关系– JPA,Web,Lombok,H2

您可以阅读PostgreSQL,Flyway和JSONB的Spring Boot以获得有关此项目的更多详细信息。 总而言之,它具有实体TeachingClassCourse, Student并使用TeachingClassServiceDBTeachingClassController通过REST API公开一些数据。 要测试它,请打开一个终端,导航到school-service目录,然后运行以下命令:

./mvnw spring-boot:run

该应用程序将从端口8081 (在文件school-service/src/main/resources/application.properties )启动,因此您应该能够导航到http://localhost: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
   }
]

基于Spring的School UI微服务

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

  • 组– com.okta.developer.docker_microservices
  • 神器– school-ui
  • 依存关系-网络,仇恨,胸腺,Lombok

UI是一个单独的网页,列出了数据库上可用的类。 为了获取信息,它通过文件school-ui/src/main/resources/application.properties的配置与school-service连接。

service.host=localhost:8081

SchoolController类具有查询服务的所有逻辑:

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=localhost:9090这样的环境变量来更改属性设置。 尽管如此,它仍必须手动定义。 如何拥有许多学校服务申请实例? 在当前阶段不可能。

启用school-service后 ,启动school-ui ,并在浏览器中浏览至http://localhost:8080

./mvnw spring-boot:run

您应该看到如下页面:

Spring微服务

使用Spring Cloud和Eureka构建发现服务器

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

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

返回start.spring.io并创建一个新项目,如下所示:

  • 组: com.okta.developer.docker_microservices
  • 神器: discovery
  • 依赖项: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 –应用程序的名称,发现服务也使用它来发现服务。 您会看到其他所有应用程序也都有一个应用程序名称。
  • server.port –服务器正在运行的端口。 Eureka服务器的默认端口8761
  • eureka.client.register-with-eureka –告诉Spring不要将自己注册到发现服务中。
  • eureka.client .fetch-registry –指示该实例不应从服务器获取发现信息。

现在,运行并访问http://localhost:8761

./mvnw spring-boot:run
Spring微服务

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

注意:如果在启动时收到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>

使用服务发现在微服务之间进行通信

首先,添加所需的依赖关系很重要。 将以下内容添加到pom.xml文件中(在school-serviceschool-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

不要忘了应用程序的名称从改变school-serviceschool-ui学校的UI项目。 注意第一行中有一种新的参数: {EUREKA_SERVER:http://localhost:8761/eureka} 。 这意味着“如果环境变量EUREKA_SERVER存在,请使用其值,否则请使用默认值。” 这在以后的步骤中将很有用。 ;)

你知道吗? 两个应用程序都准备好将自己注册到发现服务中。 您无需执行任何其他操作。 我们的主要目标是学校用户界面项目不需要知道学校服务在哪里 。 因此,您需要更改SchoolController (在school-ui项目中)以在其REST端点中使用school-service 。 您也可以在此类中删除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/");
        }
    }
}

现在,开始重新启动school-serviceschool-ui (并保持Discovery服务启动)。 再次快速浏览一下http://localhost:8761

Spring微服务

现在,您的服务正在与Discovery服务器共享信息。 您可以再次测试该应用程序,然后查看它是否可以正常运行。 只需在您喜欢的浏览器中转到http://localhost:8080

将配置服务器添加到您的微服务架构

尽管此配置有效,但最好删除项目源代码中任何配置值的痕迹。 首先,配置URL已从项目中删除,并由服务进行管理。 现在,您可以使用Spring Cloud Config对项目中的每个配置执行类似的操作。

首先,使用Spring Initializr和以下参数创建配置项目:

  • 组: com.okta.developer.docker_microservices
  • 工件: config
  • 依赖项:配置服务器,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=native表示Spring Cloud Config必须使用本机文件系统来获取配置。 通常使用Git存储库,但是为了简单起见,我们将坚持使用本机文件系统。
  • spring.cloud.config.server.native.searchLocations –包含配置文件的路径。 如果将其更改为硬盘驱动器上的特定文件夹,请确保并在其中创建school-ui.properties文件。

现在,您需要一些配置和适用于此示例。 Okta的配置如何? 让我们将school-ui放在授权层后面,并使用配置项目提供的属性值。

您可以注册一个永久免费的开发人员帐户 ,该帐户使您可以创建所需使用的尽可能多的用户和应用程序! 创建帐户后,在Okta的信息中心中创建一个新的Web应用程序(“ 应用程序” >“ 添加应用程序” ):

Spring微服务

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

Spring微服务

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

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

现在,运行config项目并检查其是否正确获取了配置数据:

./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

更改School UI以使用Spring Cloud Config和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包中创建一个新的SecurityConfiguration类:

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();
    }
}

更改您的SchoolController以便仅允许具有范围profile用户使用(每位经过身份验证的用户都拥有)。

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.properties移到该文件,因为Spring需要知道Eureka Server的位置以及如何搜索配置。 在上面的示例中,您启用了通过发现服务进行配置( spring.cloud.config.discovery.enabled )并指定了配置service-id

更改application.properties文件,使其仅具有一个OAuth 2.0属性:

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

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

<!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="https://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属性:

  • @{/logout} –返回在后端定义的注销URL
  • th:if="${#authorization.expression('isAuthenticated()')}" –仅在用户登录时打印HTML
  • @{//oauth2/authorization/okta} –这是Spring Security重定向到Okta的URL。 您也可以链接到/login ,但这只是呈现相同的链接,您必须单击它。
  • th:unless="${#authorization.expression('isAuthenticated()')}" –仅在用户注销后才在节点内打印HTML

现在,重新启动配置项目和school-ui。 如果导航到输入http://localhost:8080 ,则应该看到以下屏幕:

Spring微服务

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

Spring微服务

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

使用Docker打包Spring应用程序

Docker是一项了不起的技术,它允许创建类似于虚拟机的系统映像,但是共享与主机操作系统相同的内核。 此功能可以提高系统性能和启动时间。 此外,Docker提供了一个精巧的内置系统,该系统可确保一旦创建映像就可以; 它永远不会改变。 换句话说:不再有“它可以在我的机器上工作!”

提示:需要更深的Docker背景吗? 看看我们的《 Docker开发人员指南》

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

在每个项目的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>

每次运行./mvnw install时,此XML都会配置Dockerfile Maven插件以构建Docker映像。 将使用名称developer.okta.com/microservice-docker-${project.artifactId}创建每个图像,其中project.artifactIdproject.artifactId而异。

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

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"

Dockerfile遵循Spring Boot与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在找不到配置服务器时立即终止应用程序。 这将对下一步很有用。

添加Docker Compose以运行所有内容

创建一个名为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://discovery:8761/eureka 。 这将告诉您在哪里可以找到发现服务器。 Docker Compose在服务之间创建一个虚拟网络,每个服务使用的DNS名称就是其名称:这就是为什么可以将discovery用作主机名的原因。
  • Config服务将具有用于配置文件的卷。 该卷将映射到docker容器内的/var/config-data 。 同样,属性spring.cloud.config.server.native.searchLocations将被覆盖为相同的值。 您必须将文件school-ui.properties存储在卷映射上指定的同一文件夹中(在上面的示例中, 相对文件夹./config-data )。
  • school-ui项目的属性将restart: on-failure 。 这将Docker Compose设置为在应用程序失败后立即重新启动。 与failFast属性一起使用可以使应用程序继续尝试启动,直到DiscoveryConfig项目完全准备好为止。

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

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

school-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 {
    ...
}

现在,您应该能够在school-ui项目中运行./mvnw clean install

完成后,运行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

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

使用Spring配置文件来修改您的微服务的配置

现在,您已经到达了微服务之旅的最后阶段。 Spring Profiles是一个功能强大的工具。 使用配置文件,可以通过完全注入不同的依赖项或配置来修改程序行为。

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

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

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

现在,在您的config项目中,创建一个名为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运行配置项目,然后运行以下两个curl命令:

./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==

如您所见,即使文件school-ui-production具有两个属性, config项目也会显示三个属性(因为配置已合并)。

现在,您可以在docker-compose.yml中将school-ui服务docker-compose.yml为使用production配置文件:

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复制到您的config-data目录中。 然后关闭所有Docker容器并重新启动它们。

docker-compose down
docker-compose up -d

您应该在school-ui容器的日志中看到以下内容:

The following profiles are active: production

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

提示:如果要证明使用了okta-docker-production应用程序而不是okta-docker ,可以在Okta中停用okta-docker应用程序,并确认您仍然可以登录http://localhost:8080

了解有关微服务,Spring,Docker和现代应用程序安全性的更多信息

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

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

您可以在oktadeveloper / okta-spring-microservices-docker-example上的GitHub上找到本教程的完整源代码。

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

如果您对此帖子有任何疑问,请在下面发表评论。 您可以在Twitter上关注@oktadev以获取更多精彩内容!

Build Spring Microservices和Dockerize Them for Production''最初于2019年2月28日发布在Okta开发者博客上。

“我喜欢编写身份验证和授权代码。” 〜从来没有Java开发人员。 厌倦了一次又一次地建立相同的登录屏幕? 尝试使用Okta API进行托管身份验证,授权和多因素身份验证。

翻译自: https://www.javacodegeeks.com/2019/04/build-spring-microservices-dockerize-production.html

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值