javascript
构建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創建的,并具有以下配置:
- 組– 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:8081SchoolController類具有查詢服務的所有邏輯:
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: truespring.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
總結
以上是生活随笔為你收集整理的构建Spring微服务并对其进行Dockerize生产的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 硝酸铵溶于水化学式 硝酸铵溶于水的化学方
- 下一篇: react 消息队列_具有AkkaRea