Статьи

Создание микросервисов с использованием Spring Boot и Docker — Часть 2

Во второй половине этого руководства мы создадим Поиск сотрудников и другие службы, а затем развернем ваши микросервисы с помощью Docker. 

Создание службы поиска сотрудников

Теперь мы создадим крошечный микросервис, который на самом деле возвращает информацию о сотруднике на основе переданного идентификатора. Кроме того, он может вернуть всю информацию о сотруднике. Я предоставлю REST API и зарегистрирую этот микросервис на сервере Eureka, чтобы другие микросервисы могли его найти.

Мы выбираем EurekaClient из start.spring.io.

Давайте посмотрим 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.example</groupId>
   <artifactId>EmployeeSearchService</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <packaging>jar</packaging>
   <name>EmployeeSearchService</name>
   <description>Demo project for Spring Boot</description>
   <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>1.5.4.RELEASE</version>
      <relativePath />
      <!-- lookup parent from repository -->
   </parent>
   <properties>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
      <java.version>1.8</java.version>
      <spring-cloud.version>Dalston.SR1</spring-cloud.version>
   </properties>
   <dependencies>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-actuator</artifactId>
      </dependency>
      <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-config</artifactId>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-jersey</artifactId>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-test</artifactId>
         <scope>test</scope>
      </dependency>
      <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-eureka</artifactId>
      </dependency>
   </dependencies>
   <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>
   <build>
      <plugins>
         <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
         </plugin>
      </plugins>
   </build>
</project>

Давайте посмотрим на bootstrap.properties:

spring.application.name=EmployeeSearch
spring.cloud.config.uri=http://localhost:9090
eureka.client.serviceUrl.defaultZone:http://localhost:9091/eureka
server.port=8080
security.basic.enable: false   
management.security.enabled: false

Здесь я даю логическое имя службы  EmployeeSerach , все экземпляры этой службы, зарегистрированные с этим именем на сервере Eureka, это общее логическое имя для всех экземпляров Службы EmployeeSerach. также я даю URL-адрес сервера конфигурации (обратите внимание: когда мы развертываем его в Docker, нам нужно изменить локальный хост на IP-адрес контейнера Docker сервера Config, чтобы найти сервер конфигурации)

также я упоминаю расположение сервера Eureka (обратите внимание: когда мы развертываем его в докере, мы должны изменить локальный хост на IP-адрес контейнера Docker для Eureka, чтобы найти сервер Eureka,

Теперь создайте файл контроллера и сервиса

package com.example.EmployeeSearchService.service;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.springframework.stereotype.Service;

import com.example.EmployeeSearchService.domain.model.Employee;

@Service
public class EmployeeSearchService {

 private static Map < Long, Employee > EmployeeRepsitory = null;

 static {

  Stream < String > employeeStream = Stream.of("1,Shamik  Mitra,Java,Architect", "2,Samir  Mitra,C++,Manager",
   "3,Swastika  Mitra,AI,Sr.Architect");

  EmployeeRepsitory = employeeStream.map(employeeStr -> {
   String[] info = employeeStr.split(",");
   return createEmployee(new Long(info[0]), info[1], info[2], info[3]);
  }).collect(Collectors.toMap(Employee::getEmployeeId, emp -> emp));

 }

 private static Employee createEmployee(Long id, String name, String practiceArea, String designation) {
  Employee emp = new Employee();
  emp.setEmployeeId(id);
  emp.setName(name);
  emp.setPracticeArea(practiceArea);
  emp.setDesignation(designation);
  emp.setCompanyInfo("Cognizant");
  return emp;
 }

 public Employee findById(Long id) {
  return EmployeeRepsitory.get(id);
 }

 public Collection < Employee > findAll() {
  return EmployeeRepsitory.values();
 }

}

Файл контроллера,

package com.example.EmployeeSearchService.controller;

import java.util.Collection;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.example.EmployeeSearchService.domain.model.Employee;
import com.example.EmployeeSearchService.service.EmployeeSearchService;

@RefreshScope
@RestController
public class EmployeeSearchController {

 @Autowired
 EmployeeSearchService employeeSearchService;

 @RequestMapping("/employee/find/{id}")
 public Employee findById(@PathVariable Long id) {
  return employeeSearchService.findById(id);
 }

 @RequestMapping("/employee/findall")
 public Collection < Employee > findAll() {
  return employeeSearchService.findAll();
 }
}
package com.example.EmployeeSearchService.domain.model;

public class Employee {
 private Long employeeId;
 private String name;
 private String practiceArea;
 private String designation;
 private String companyInfo;
 public Long getEmployeeId() {
  return employeeId;
 }
 public void setEmployeeId(Long employeeId) {
  this.employeeId = employeeId;
 }
 public String getName() {
  return name;
 }
 public void setName(String name) {
  this.name = name;
 }
 public String getPracticeArea() {
  return practiceArea;
 }
 public void setPracticeArea(String practiceArea) {
  this.practiceArea = practiceArea;
 }
 public String getDesignation() {
  return designation;
 }
 public void setDesignation(String designation) {
  this.designation = designation;
 }
 public String getCompanyInfo() {
  return companyInfo;
 }
 public void setCompanyInfo(String companyInfo) {
  this.companyInfo = companyInfo;
 }
 @Override
 public String toString() {
  return "Employee [employeeId=" + employeeId + ", name=" + name + ", practiceArea=" + practiceArea + ", designation=" + designation + ", companyInfo=" + companyInfo + "]";
 }




}

Ничего особенного, я просто создаю несколько сотрудников и сопоставляю их с URL-адресом отдыха.

давайте посмотрим файл Spring Boot сейчас

package com.example.EmployeeSearchService;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@EnableDiscoveryClient
@SpringBootApplication
public class EmployeeSearchServiceApplication {

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

Здесь я использую @ EnableDiscoveryClient, чтобы зарегистрировать этот сервис в качестве клиента eureka.

Теперь, если я нажму на этот http: // localhost: 8080 / employee / find / 1, я могу увидеть следующий вывод

{
   "employeeId":1,
   "name":"Shamik  Mitra",
   "practiceArea":"Java",
   "designation":"Architect",
   "companyInfo":"Cognizant"
}

Создание службы DashBoard для сотрудников

Теперь я создам другую службу, которая использует службу поиска сотрудников для получения информации о сотрудниках для связи со службой поиска сотрудников. Я буду использовать  клиента Feign,  а также использовать  Hystrix  в качестве автоматического выключателя, поэтому, если служба поиска сотрудников не работает, она может предоставлять данные по умолчанию.

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.example</groupId>
   <artifactId>EmployeeDashBoardService</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <packaging>jar</packaging>
   <name>EmployeeDashBoardService</name>
   <description>Demo project for Spring Boot</description>
   <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>1.5.4.RELEASE</version>
      <relativePath />
      <!-- lookup parent from repository -->
   </parent>
   <properties>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
      <java.version>1.8</java.version>
      <spring-cloud.version>Dalston.SR1</spring-cloud.version>
   </properties>
   <dependencies>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-actuator</artifactId>
      </dependency>
      <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-config</artifactId>
      </dependency>
      <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-eureka</artifactId>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-jersey</artifactId>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-test</artifactId>
         <scope>test</scope>
      </dependency>
      <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-feign</artifactId>
      </dependency>
      <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-ribbon</artifactId>
      </dependency>
      <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-hystrix</artifactId>
      </dependency>
   </dependencies>
   <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>
   <build>
      <plugins>
         <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
         </plugin>
      </plugins>
   </build>
</project>

bootstrap.properties:

spring.application.name=EmployeeDashBoard
spring.cloud.config.uri=http://localhost:9090
eureka.client.serviceUrl.defaultZone:http://localhost:9091/eureka
server.port=8081
security.basic.enable: false   
management.security.enabled: false 

Симулировать клиента:

package com.example.EmployeeDashBoardService.controller;

import java.util.Collection;

import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

import com.example.EmployeeDashBoardService.domain.model.EmployeeInfo;



@FeignClient(name = "EmployeeSearch")
@RibbonClient(name = "EmployeeSearch")
public interface EmployeeServiceProxy {

 @RequestMapping("/employee/find/{id}")
 public EmployeeInfo findById(@PathVariable(value = "id") Long id);

 @RequestMapping("/employee/findall")
 public Collection < EmployeeInfo > findAll();

}

контроллер:

package com.example.EmployeeDashBoardService.controller;

import java.util.Collection;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.example.EmployeeDashBoardService.domain.model.EmployeeInfo;

@RefreshScope
@RestController
public class FeignEmployeeInfoController {

 @Autowired
 EmployeeServiceProxy proxyService;

 @RequestMapping("/dashboard/feign/{myself}")
 public EmployeeInfo findme(@PathVariable Long myself) {
  return proxyService.findById(myself);

 }

 @RequestMapping("/dashboard/feign/peers")
 public Collection < EmployeeInfo > findPeers() {
  return proxyService.findAll();
 }

}

Пружинная загрузка стартера

package com.example.EmployeeDashBoardService;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication
public class EmployeeDashBoardService {

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

 @Bean
 public RestTemplate restTemplate(RestTemplateBuilder builder) {
  return builder.build();
 }
}

Мы готовы сейчас. Если я нажму на URL http: // localhost: 8081 / dashboard / feign / 1, я увижу следующий ответ:

{
   "employeeId":1,
   "name":"Shamik  Mitra",
   "practiceArea":"Java",
   "designation":"Architect",
   "companyInfo":"Cognizant"
}

Создание службы шлюза

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>
   <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-dependencies</artifactId>
      <version>1.5.6.RELEASE</version>
      <relativePath>../../spring-boot-dependencies</relativePath>
   </parent>
   <artifactId>spring-boot-starter-parent</artifactId>
   <packaging>pom</packaging>
   <name>Spring Boot Starter Parent</name>
   <description>Parent pom providing dependency and plugin management for applications
built with Maven</description>
   <url>http://projects.spring.io/spring-boot/</url>
   <organization>
      <name>Pivotal Software, Inc.</name>
      <url>http://www.spring.io</url>
   </organization>
   <properties>
      <java.version>1.6</java.version>
      <resource.delimiter>@</resource.delimiter>
      <!-- delimiter that doesn't clash with Spring ${} placeholders -->
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
      <maven.compiler.source>${java.version}</maven.compiler.source>
      <maven.compiler.target>${java.version}</maven.compiler.target>
   </properties>
   <dependencyManagement>
      <dependencies>
         <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
            <exclusions>
               <exclusion>
                  <groupId>commons-logging</groupId>
                  <artifactId>commons-logging</artifactId>
               </exclusion>
            </exclusions>
         </dependency>
      </dependencies>
   </dependencyManagement>
   <build>
      <!-- Turn on filtering by default for application properties -->
      <resources>
         <resource>
            <directory>${basedir}/src/main/resources</directory>
            <filtering>true</filtering>
            <includes>
               <include>**/application*.yml</include>
               <include>**/application*.yaml</include>
               <include>**/application*.properties</include>
            </includes>
         </resource>
         <resource>
            <directory>${basedir}/src/main/resources</directory>
            <excludes>
               <exclude>**/application*.yml</exclude>
               <exclude>**/application*.yaml</exclude>
               <exclude>**/application*.properties</exclude>
            </excludes>
         </resource>
      </resources>
      <pluginManagement>
         <plugins>
            <!-- Apply more sensible defaults for user projects -->
            <plugin>
               <groupId>org.apache.maven.plugins</groupId>
               <artifactId>maven-failsafe-plugin</artifactId>
               <executions>
                  <execution>
                     <goals>
                        <goal>integration-test</goal>
                        <goal>verify</goal>
                     </goals>
                  </execution>
               </executions>
            </plugin>
            <plugin>
               <groupId>org.apache.maven.plugins</groupId>
               <artifactId>maven-jar-plugin</artifactId>
               <configuration>
                  <archive>
                     <manifest>
                        <mainClass>${start-class}</mainClass>
                        <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
                     </manifest>
                  </archive>
               </configuration>
            </plugin>
            <plugin>
               <groupId>org.apache.maven.plugins</groupId>
               <artifactId>maven-surefire-plugin</artifactId>
               <configuration>
                  <includes>
                     <include>**/*Tests.java</include>
                     <include>**/*Test.java</include>
                  </includes>
                  <excludes>
                     <exclude>**/Abstract*.java</exclude>
                  </excludes>
               </configuration>
            </plugin>
            <plugin>
               <groupId>org.apache.maven.plugins</groupId>
               <artifactId>maven-war-plugin</artifactId>
               <configuration>
                  <failOnMissingWebXml>false</failOnMissingWebXml>
                  <archive>
                     <manifest>
                        <mainClass>${start-class}</mainClass>
                        <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
                     </manifest>
                  </archive>
               </configuration>
            </plugin>
            <plugin>
               <groupId>org.codehaus.mojo</groupId>
               <artifactId>exec-maven-plugin</artifactId>
               <configuration>
                  <mainClass>${start-class}</mainClass>
               </configuration>
            </plugin>
            <plugin>
               <groupId>org.apache.maven.plugins</groupId>
               <artifactId>maven-resources-plugin</artifactId>
               <version>2.6</version>
               <configuration>
                  <delimiters>
                     <delimiter>${resource.delimiter}</delimiter>
                  </delimiters>
                  <useDefaultDelimiters>false</useDefaultDelimiters>
               </configuration>
            </plugin>
            <plugin>
               <groupId>pl.project13.maven</groupId>
               <artifactId>git-commit-id-plugin</artifactId>
               <executions>
                  <execution>
                     <goals>
                        <goal>revision</goal>
                     </goals>
                  </execution>
               </executions>
               <configuration>
                  <verbose>true</verbose>
                  <dateFormat>yyyy-MM-dd'T'HH:mm:ssZ</dateFormat>
                  <generateGitPropertiesFile>true</generateGitPropertiesFile>
                  <generateGitPropertiesFilename>${project.build.outputDirectory}/git.properties</generateGitPropertiesFilename>
               </configuration>
            </plugin>
            <!-- Support our own plugin -->
            <plugin>
               <groupId>org.springframework.boot</groupId>
               <artifactId>spring-boot-maven-plugin</artifactId>
               <executions>
                  <execution>
                     <goals>
                        <goal>repackage</goal>
                     </goals>
                  </execution>
               </executions>
               <configuration>
                  <mainClass>${start-class}</mainClass>
               </configuration>
            </plugin>
            <!-- Support shade packaging (if the user does not want to use our plugin) -->
            <plugin>
               <groupId>org.apache.maven.plugins</groupId>
               <artifactId>maven-shade-plugin</artifactId>
               <dependencies>
                  <dependency>
                     <groupId>org.springframework.boot</groupId>
                     <artifactId>spring-boot-maven-plugin</artifactId>
                     <version>1.5.6.RELEASE</version>
                  </dependency>
               </dependencies>
               <configuration>
                  <keepDependenciesWithProvidedScope>true</keepDependenciesWithProvidedScope>
                  <createDependencyReducedPom>true</createDependencyReducedPom>
                  <filters>
                     <filter>
                        <artifact>*:*</artifact>
                        <excludes>
                           <exclude>META-INF/*.SF</exclude>
                           <exclude>META-INF/*.DSA</exclude>
                           <exclude>META-INF/*.RSA</exclude>
                        </excludes>
                     </filter>
                  </filters>
               </configuration>
               <executions>
                  <execution>
                     <phase>package</phase>
                     <goals>
                        <goal>shade</goal>
                     </goals>
                     <configuration>
                        <transformers>
                           <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                              <resource>META-INF/spring.handlers</resource>
                           </transformer>
                           <transformer implementation="org.springframework.boot.maven.PropertiesMergingResourceTransformer">
                              <resource>META-INF/spring.factories</resource>
                           </transformer>
                           <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                              <resource>META-INF/spring.schemas</resource>
                           </transformer>
                           <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
                           <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                              <mainClass>${start-class}</mainClass>
                           </transformer>
                        </transformers>
                     </configuration>
                  </execution>
               </executions>
            </plugin>
         </plugins>
      </pluginManagement>
   </build>
</project>

bootstrap.properties:

spring.application.name=EmployeeAPIGateway
eureka.client.serviceUrl.defaultZone:http://localhost:9091/eureka
server.port=8084
security.basic.enable: false   
management.security.enabled: false 
zuul.routes.employeeUI.serviceId=EmployeeDashBoard
zuul.host.socket-timeout-millis=30000

Здесь обратите внимание на свойство  zuul.routes.employeeUI.serviceId = EmployeeDashBoard . При этом мы сообщаем Zuul, что любой URL, содержащий employeeUI, должен перенаправляться в сервис EmployeeDashboard.

Файл Spring Boot:

package com.example.EmployeeZuulService;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

@EnableZuulProxy
@EnableDiscoveryClient
@SpringBootApplication
public class EmployeeZuulServiceApplication {

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

Теперь, если я запускаю службу и нажимаю http: // localhost: 8084 / employeeUI / dashboard / feign / 1, это дает нам следующий ответ:

{
   "employeeId":1,
   "name":"Shamik  Mitra",
   "practiceArea":"Java",
   "designation":"Architect",
   "companyInfo":"Cognizant"
}

Развертывание в Docker-контейнерах

Хватит кодировать сейчас. Давайте посмотрим, как работает наше приложение. К этому времени все наши сервисы уже готовы и работают на локальной машине. Но мы не хотим, чтобы наш код выполнялся только в вашей локальной настройке. Скорее, мы хотим видеть, как он работает с летающими цветами в производстве (Мы любим наш код как нашего ребенка и хотим, чтобы он всегда был успешным). Но так как мы отправляем наших детей в школу или направляем их на правильный путь к успеху, нам также необходимо направлять наше заявление. Итак, давайте отправимся в   мир DevOps и попытаемся дать нашему исходному коду правильный путь к производству. 

Добро пожаловать в мир докеров

Докер не нуждается в представлении. Если вы чувствуете, что вам все еще нужен гид, не стесняйтесь заглянуть сюда по адресу  https://docs.docker.com/get-started/ .

В дальнейшем я предполагаю, что на вашей машине установлен Docker CE. Концепции, которые мы будем использовать здесь для развертывания, следующие:

  1. Dockerfile : это текстовый документ, который содержит все инструкции, необходимые для создания образа Docker. Используя набор инструкций Dockerfile, мы можем написать шаги, которые будут копировать файлы, выполнять установку и т. Д. Для получения дополнительных ссылок посетите страницу  https://docs.docker.com/engine/reference/builder/ .

  2. Docker Compose : это инструмент, который может создавать и создавать несколько контейнеров. Это помогает создать необходимую среду с помощью одной команды. 

Как показано на диаграмме архитектуры микросервиса, мы будем создавать отдельный контейнер для каждого сервиса. Ниже приведен список контейнеров для нашего примера:

  1. Config Server

  2. EmployeeService

  3. Служба поддержки сотрудников

  4. Сервисная панель сотрудников

  5. Шлюз Сервис

Конфигурация Docker для сервера конфигурации

Контейнер должен содержать файл jar сервера конфигурации. Здесь мы выберем файл jar с локальной машины. В реальном сценарии мы должны отправить файл jar в систему Artifact Repository Manager, такую ​​как Nexus или Artifactory, и контейнер должен загрузить файл из менеджера репозитория. 

Сервер конфигурации должен быть доступен через порт 8888 согласно bootstrap.properties.

Как упоминалось выше, у нас будет сервер конфигурации, который будет считывать конфигурацию из местоположения файла, поэтому мы позаботимся о том, чтобы эти файлы свойств можно было извлечь, даже если контейнер отключится. 

Создайте папку с именем config-repo, которая будет содержать необходимый файл свойств. Мы позаботимся о следующем для контейнера Config Server. 

 # mkdir config-repo
 # cd config-repo
 # echo "service.employyesearch.serviceId=EmployeeSearch" > EmployeeDashBoard.properties
 # echo "user.role=Dev" > EmployeeSearch.properties

Вернитесь в родительскую папку и создайте файл Docker с именем Dockerfile. Этот Dockerfile создаст наш базовый образ, который содержит Java.

 # cd ../
 # vi Dockerfile

Поместите в содержание ниже:

FROM alpine:edge
MAINTAINER javaonfly
RUN apk add --no-cache openjdk8

ОТ : Это ключевое слово, говорит Docker использовать заданное изображение с его тегом в качестве основы сборки. 

MAINTAINER : MAINTAINER является автором изображения

RUN : эта команда установит openjdk8 в систему.

Выполните приведенную ниже команду, чтобы создать базовый образ Docker:

 docker build --tag=alpine-jdk:base --rm=true . 

После успешного создания базового образа настало время создать образ Docker для сервера конфигурации. 

Создайте папку с именем files и поместите файл jar сервера конфигурации в каталог. Затем создайте файл с именем Dockerfile-configserver со следующим содержимым:

FROM alpine-jdk:base
MAINTAINER javaonfly
COPY files/MicroserviceConfigServer.jar /opt/lib/
RUN mkdir /var/lib/config-repo
COPY config-repo /var/lib/config-repo
ENTRYPOINT ["/usr/bin/java"]
CMD ["-jar", "/opt/lib/MicroserviceConfigServer.jar"]
VOLUME /var/lib/config-repo
EXPOSE 9090

Здесь мы упомянули создание изображения из ранее созданного изображения alpine-jdk. Мы скопируем jar-файл employeeconfigserver.jar в папку / opt / lib, а также скопируем config-repo в каталог / root. Когда контейнер запускается, мы хотим, чтобы сервер конфигурации запускался, поэтому ENTRYPOINT и CMD настроены на выполнение команды Java. Нам нужно смонтировать том, чтобы поделиться файлами конфигурации снаружи контейнера; команда VOLUME помогает нам достичь этого. Конфиг-сервер должен быть доступен для внешнего мира с портом 9090; вот почему у нас есть EXPOSE 9090. 

Теперь давайте создадим образ Docker и отметим его как config-server:

# docker build --file=Dockerfile-configserver --tag=config-server:latest --rm=true .

Теперь давайте создадим том Docker:

# docker volume create --name=config-repo
# docker run --name=config-server --publish=9090:9090 --volume=config-repo:/var/lib/config-repo config-server:latest

Уф! Как только мы запустим указанную выше команду, мы сможем увидеть, что Docker-контейнер работает и работает. Если мы перейдем в браузер и нажмем на URL  http: // localhost: 9090 / config / default / , мы также сможем получить доступ к свойствам. 

EurekaServer

Точно так же нам нужно создать файл Docker для EurekaServer, который будет работать на порту 9091. Файл Docker для Eureka Server должен выглядеть следующим образом:

FROM alpine-jdk:base
MAINTAINER javaonfly
COPY files/MicroserviceEurekaServer.jar /opt/lib/
ENTRYPOINT ["/usr/bin/java"]
CMD ["-jar", "/opt/lib/MicroserviceEurekaServer.jar"]
EXPOSE 9091

Чтобы построить образ, используйте эту команду:

docker build --file=Dockerfile-EurekaServer --tag=eureka-server:latest --rm=true .
docker run --name=eureka-server --publish=9091:9091 eureka-server:latest

Microservices

Теперь пришло время развернуть наши актуальные микросервисы. Шаги должны быть похожими; единственное, что нам нужно помнить, это то, что наши микросервисы зависят от ConfigServer и EurekaServer, поэтому нам всегда нужно убедиться, что перед запуском наших микросервисов два указанных выше запущены и работают. Существуют зависимости между контейнерами, поэтому пришло время изучить Docker Compose. Это прекрасный способ убедиться, что контейнеры создаются для поддержания определенного порядка. 

Чтобы сделать это, мы должны написать Dockerfile для остальных контейнеров. Ниже находится Dockerfile:

Dockerfile-EmployeeSearch. 
================================
FROM alpine-jdk:base
MAINTAINER javaonfly
RUN apk --no-cache add netcat-openbsd
COPY files/EmployeeSearchService.jar /opt/lib/
COPY EmployeeSearch-entrypoint.sh /opt/bin/EmployeeSearch-entrypoint.sh
RUN chmod 755 /opt/bin/EmployeeSearch-entrypoint.sh
EXPOSE 8080

Dockerfile-EmployeeDashboard
====================================
FROM alpine-jdk:base
MAINTAINER javaonfly
RUN apk --no-cache add netcat-openbsd
COPY files/EmployeeDashBoardService.jar /opt/lib/
COPY EmployeeDashBoard-entrypoint.sh /opt/bin/EmployeeDashBoard-entrypoint.sh
RUN chmod 755 /opt/bin/EmployeeDashBoard-entrypoint.sh
EXPOSE 8080

Dockerfile-ZuulServer
=========================================
FROM alpine-jdk:base
MAINTAINER javaonfly
COPY files/EmployeeZuulService.jar /opt/lib/
ENTRYPOINT ["/usr/bin/java"]
CMD ["-jar", "/opt/lib/EmployeeZuulService.jar"]
EXPOSE 8084

Стоит заметить, что я создал два сценария оболочки для сервисов Dashboard Employee и Employee. Он инструктирует Docker о том, что не следует запускать сервис Dashboard Employee и Employee до запуска сервера Config и сервера Eureka.

Employee dashBoard Script
==================================
#!/bin/sh

while ! nc -z config-server 9090 ; do
    echo "Waiting for the Config Server"
    sleep 3
done
while ! nc -z eureka-server 9091 ; do
    echo "Waiting for the Eureka Server"
    sleep 3
done

java -jar /opt/lib/EmployeeDashBoardService.jar
==================================
Employee service Script
==================================
#!/bin/sh

while ! nc -z config-server 9090 ; do
    echo "Waiting for the Config Server"
    sleep 3
done
while ! nc -z eureka-server 9091 ; do
    echo "Waiting for the Eureka Server"
    sleep 3
done

java -jar /opt/lib/EmployeeSearchService.jar

Теперь давайте создадим файл с именем docker-compose.yml, который будет использовать все эти файлы Docker для порождения наших необходимых сред. Это также обеспечит поддержание правильного порядка в порождаемых контейнерах и их взаимосвязь. 

version: '2.2'
services:
    config-server:
        container_name: config-server
        build:
            context: .
            dockerfile: Dockerfile-configserver
        image: config-server:latest
        expose:
            - 9090
        ports:
            - 9090:9090
        networks:
            - emp-network
        volumes:
            - config-repo:/var/lib/config-repo

    eureka-server:
        container_name: eureka-server
        build:
            context: .
            dockerfile: Dockerfile-EurekaServer
        image: eureka-server:latest
        expose:
            - 9091
        ports:
            - 9091:9091
        networks:
            - emp-network

    EmployeeSearchService:
        container_name: EmployeeSearch
        build:
            context: .
            dockerfile: Dockerfile-EmployeeSearch
        image: employeesearch:latest
        environment:
            SPRING_APPLICATION_JSON: '{"spring": {"cloud": {"config": {"uri": "http://config-server:9090"}}}}'

        entrypoint: /opt/bin/EmployeeSearch-entrypoint.sh
        expose:
            - 8080
        ports:
            - 8080:8080
        networks:
            - emp-network
        links:
            - config-server:config-server
            - eureka-server:eureka-server
        depends_on:
            - config-server
            - eureka-server
        logging:
            driver: json-file
    EmployeeDashboardService:
        container_name: EmployeeDashboard
        build:
            context: .
            dockerfile: Dockerfile-EmployeeDashboard
        image: employeedashboard:latest
        environment:
            SPRING_APPLICATION_JSON: '{"spring": {"cloud": {"config": {"uri": "http://config-server:9090"}}}}'

        entrypoint: /opt/bin/EmployeeDashBoard-entrypoint.sh
        expose:
            - 8081
        ports:
            - 8081:8081
        networks:
            - emp-network
        links:
            - config-server:config-server
            - eureka-server:eureka-server
        depends_on:
            - config-server
            - eureka-server
        logging:
            driver: json-file
    ZuulServer:
        container_name: ZuulServer
        build:
            context: .
            dockerfile: Dockerfile-ZuulServer
        image: zuulserver:latest
        expose:
            - 8084
        ports:
            - 8084:8084
        networks:
            - emp-network
        links:
            - eureka-server:eureka-server
        depends_on:
            - eureka-server
        logging:
            driver: json-file
networks:
    emp-network:
        driver: bridge
volumes:
    config-repo:
        external: true

В файле Docker compose ниже есть несколько важных записей:

  1. версия : обязательное поле, в котором нам нужно сохранить версию формата Docker Compose.

  2. services : каждая запись определяет контейнер, который нам нужен. 

    1. build : если упомянуто, то Docker Compose должен создать образ из заданного Dockerfile. 

    2. изображение : имя изображения, которое будет создано.

    3. сети : название сети, которая будет использоваться. Это имя должно присутствовать в разделе сети. 

    4. ссылки : это создаст внутреннюю ссылку между сервисом и упомянутым сервисом. Здесь службе EmployeeSearch необходим доступ к серверу конфигурации и Eureka.

    5. зависит : это необходимо для поддержания порядка. Контейнер EmployeeSearch зависит от сервера Eureka и Config. Следовательно, Docker гарантирует, что контейнеры Eureka и Config Server будут порождаться до появления контейнера EmployeeSearch. 

После создания файла давайте создадим наши изображения, создадим необходимые контейнеры и начнем с одной команды:

 docker-compose up --build 

Чтобы остановить всю среду, мы можем использовать эту команду:

 docker-compose down 

С полной документацией по Docker Compose можно ознакомиться по адресу 
https://docs.docker.com/compose/ .

Подводя итог, можно сказать, что написание файлов Dockerfile и Docker Compose является однократным действием, но оно позволяет вам создавать целую среду по требованию в любое время. 

Заключение

Это полное руководство о том, как встраивать различные компоненты в микросервисы и развертывать их в Docker. В производстве должен быть задействован CI / CD, поэтому вам не нужно знать все команды Docker для создания образов, но как разработчику полного стека важно узнать, как можно создавать и создавать образ в Docker.