“我喜欢编写身份验证和授权代码。” 〜从来没有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创建的,并具有以下配置:
- 组–
com.okta.developer.docker_microservices
- 神器–
school-service
- 依赖关系– JPA,Web,Lombok,H2
您可以阅读PostgreSQL,Flyway和JSONB的Spring Boot以获得有关此项目的更多详细信息。 总而言之,它具有实体TeachingClass
, Course,
Student
并使用TeachingClassServiceDB
和TeachingClassController
通过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 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
上面的屏幕显示了准备注册新服务的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-service和school-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-service
于school-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@LoadBalancedpublic RestTemplate restTemplate() {return new RestTemplate();}@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {if(!registry.hasMappingForPattern("/static/**")) {registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/", "classpath:/static/js/");}}
}
现在,开始重新启动school-service和school-ui (并保持Discovery服务启动)。 再次快速浏览一下http://localhost:8761
:
现在,您的服务正在与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应用程序(“ 应用程序” >“ 添加应用程序” ):
并用以下值填写下一个表格:
该页面将为您返回一个应用程序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 {@Overrideprotected 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-uicloud:config:discovery:enabled: trueservice-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 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.artifactId
因project.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-uicloud:config:discovery:enabled: trueserviceId: CONFIGSERVERfailFast: 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-SNAPSHOTports:- 8761:8761config:image: developer.okta.com/microservice-docker-config:0.0.1-SNAPSHOTvolumes:- ./config-data:/var/config-dataenvironment:- JAVA_OPTS=-DEUREKA_SERVER=http://discovery:8761/eureka-Dspring.cloud.config.server.native.searchLocations=/var/config-datadepends_on:- discoveryports:- 8888:8888school-service:image: developer.okta.com/microservice-docker-school-service:0.0.1-SNAPSHOTenvironment:- JAVA_OPTS=-DEUREKA_SERVER=http://discovery:8761/eurekadepends_on:- discovery- configschool-ui:image: developer.okta.com/microservice-docker-school-ui:0.0.1-SNAPSHOTenvironment:- JAVA_OPTS=-DEUREKA_SERVER=http://discovery:8761/eurekarestart: on-failuredepends_on:- discovery- configports:- 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
属性一起使用可以使应用程序继续尝试启动,直到Discovery和Config项目完全准备好为止。
就是这样! 现在,构建图像:
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.propertiesokta.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-SNAPSHOTenvironment:- JAVA_OPTS=-DEUREKA_SERVER=http://discovery:8761/eureka-Dspring.profiles.active=productionrestart: on-failuredepends_on:- discovery- configports:- 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中学习有关微服务或现代应用程序开发的更多信息,建议您查看以下资源:
- 使用Spring Boot 2.0和OAuth 2.0构建并保护微服务
- 使用JHipster和OAuth 2.0开发微服务架构
- 使用Spring Boot为Microbrews构建微服务架构
- Spring Boot 2.1:出色的OIDC,OAuth 2.0和反应式API支持
- 使用Spring Boot和MongoDB构建一个反应式应用程序
如果您对此帖子有任何疑问,请在下面发表评论。 您可以在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