Статьи

MicroServices. Часть 2. Управление конфигурацией с помощью Spring Cloud Config и Vault

В MicroServices с использованием Spring Boot и Spring Cloud. Часть 1. Обзор мы кратко рассмотрели, что такое микро-сервисы и как мы можем использовать SpringBoot и SpringCloud для создания микро-сервисов.

В этом посте мы собираемся узнать:

  • Зачем нужны Spring Cloud Config и Vault ?
  • Создайте наш первый микро-сервис: каталог-сервис
  • Создать сервер Spring Cloud Config
  • Использование Vault для хранения конфиденциальных данных

Микроуслуги с использованием Spring Boot и Spring Cloud

Зачем нужны Spring Cloud Config и Vault?

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

В мире микросервисов может быть большое количество микросервисов, и несколько экземпляров этих микросервисов работают. Обновление свойств конфигурации и перезапуск всех этих экземпляров вручную или даже с помощью автоматических сценариев может оказаться невозможным. Spring Cloud Config решает эту проблему.

Мы можем создать сервер Spring Cloud Config, который предоставляет значения конфигурации для всех наших микросервисов. Мы можем использовать git , svn , database или Consul в качестве бэкэнда для хранения параметров конфигурации. Затем мы можем настроить расположение сервера Spring Cloud Config в нашем микросервисе, чтобы он загружал все свойства при запуске приложения. Кроме того, всякий раз, когда мы обновляем свойства, мы можем вызывать / обновлять конечную точку REST в нашем микросервисе, чтобы она перезагружала изменения конфигурации без необходимости перезапуска приложения.

В наших приложениях нам также необходимо настроить различные конфиденциальные данные, такие как учетные данные базы данных, ключи, токены и т. Д. Очевидно, что мы не хотим хранить их в виде простого текста. Лучшим подходом было бы хранить их в зашифрованном формате, а Spring Cloud Config Server предоставляет возможность шифровать и дешифровать данные. Еще лучше, мы должны использовать безопасные инструменты хранения данных, такие как Vault . Spring Cloud также обеспечивает интеграцию с Vault, поэтому мы можем хранить любые конфиденциальные свойства конфигурации в Vault.

Я уже написал несколько учебных пособий по Spring Cloud Config Server, к которым вы можете обратиться:

Создайте наш первый микро-сервис: каталог-сервис

Начнем с нашего первого микросервиса, то есть каталога-сервиса . Создайте приложение SpringBoot со стартерами в Интернете , JPA , MySQL , Actuator , DevTools , Lombok . Пока ничего необычного, типичное приложение SpringBoot.

Вы можете найти исходный код этой статьи по адресу https://github.com/sivaprasadreddy/spring-boot-microservices-series.

Во-первых, давайте реализуем конечную точку REST для предоставления данных о продуктах, а затем рефакторинг их для использования Cloud Config Server.

Мы собираемся использовать Docker и запускать MySQL как контейнер Docker.

докер-compose.yml

01
02
03
04
05
06
07
08
09
10
version: '3'
services:
  mysqldb:
     image: mysql:5.7
     container_name: mysqldb
     ports:
       - "3306:3306"
     environment:
       MYSQL_ROOT_PASSWORD: admin
       MYSQL_DATABASE: catalog

Настройте свойства источника данных в application.properties следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
server.port=8181
logging.level.com.sivalabs=debug
 
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/catalog?useSSL=false
spring.datasource.username=root
spring.datasource.password=admin
 
spring.datasource.initialization-mode=always
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
 
//expose all the Actuator endpoints
management.endpoints.web.exposure.include=*

Создайте сущность JPA Product.java следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
import lombok.Data;
import javax.persistence.*;
 
@Data
@Entity
@Table(name = "products")
public class Product {
    @Id @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @Column(nullable = false, unique = true)
    private String code;
    @Column(nullable = false)
    private String name;
    private String description;
    private double price;
}

Создайте репозиторий Spring Data JPA ProductRepository.java следующим образом:

1
2
3
4
5
6
7
import com.sivalabs.catalogservice.entities.Product;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
 
public interface ProductRepository extends JpaRepository<Product, Long> {
    Optional<Product> findByCode(String code);
}

Создайте ProductService, который сейчас просто делегирован в ProductRepository . Мы можем напрямую внедрить Repository в наши компоненты веб-слоя (Controllers), но в будущем может возникнуть бизнес-логика, которую я не люблю вставлять ни в Controller, ни в Repository.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import com.sivalabs.catalogservice.entities.Product;
import com.sivalabs.catalogservice.repositories.ProductRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
 
@Service
@Transactional
@Slf4j
public class ProductService {
    private final ProductRepository productRepository;
 
    @Autowired
    public ProductService(ProductRepository productRepository) {
        this.productRepository = productRepository;
    }
 
    public List<Product> findAllProducts() {
        return productRepository.findAll();
    }
 
    public Optional<Product> findProductByCode(String code) {
        return productRepository.findByCode(code);
    }
}

Наконец, создайте наш REST-контроллер ProductController.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import com.sivalabs.catalogservice.entities.Product;
import com.sivalabs.catalogservice.exceptions.ProductNotFoundException;
import com.sivalabs.catalogservice.services.ProductService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
import java.util.List;
 
@RestController
@RequestMapping("/api/products")
@Slf4j
public class ProductController {
 
    private final ProductService productService;
 
    @Autowired
    public ProductController(ProductService productService) {
        this.productService = productService;
    }
 
    @GetMapping("")
    public List<Product> allProducts() {
        return productService.findAllProducts();
    }
 
    @GetMapping("/{code}")
    public Product productByCode(@PathVariable String code) {
        return productService.findProductByCode(code)
                .orElseThrow(() -> new ProductNotFoundException("Product with code ["+code+"] doesn't exist"));
    }
}

Создайте исключение ProductNotFoundException, расширяющее RuntimeException, и аннотируйте его с помощью @ResponseStatus (HttpStatus.NOT_FOUND) .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
 
@ResponseStatus(HttpStatus.NOT_FOUND)
public class ProductNotFoundException extends RuntimeException {
    public ProductNotFoundException() {
    }
 
    public ProductNotFoundException(String message) {
        super(message);
    }
 
    public ProductNotFoundException(String message, Throwable cause) {
        super(message, cause);
    }
 
    public ProductNotFoundException(Throwable cause) {
        super(cause);
    }
}

Давайте добавим несколько примеров продуктов в нашу базу данных.

SRC / главная / ресурсы / data.sql

1
2
3
4
5
6
7
DELETE FROM products;
 
insert into products(id, code, name, description, price) VALUES
(1, 'P001', 'Product 1', 'Product 1 description', 25),
(2, 'P002', 'Product 2', 'Product 2 description', 32),
(3, 'P003', 'Product 3', 'Product 3 description', 50)
;

Хорошо, теперь мы можем запустить наше приложение SpringBoot и нажать http: // localhost: 8181 / api / products. Вы должны увидеть ответ JSON с информацией о продуктах.

Создать сервер Spring Cloud Config

мы собираемся создать Spring Cloud Config Server, используя Git backend. Spring Cloud Config Server — это не что иное, как проект SpringBoot. Создайте проект SpringBoot с помощью Config Server startter.

Сконфигурируйте местоположение репозитория git, в котором мы будем хранить все наши файлы конфигурации в файле application.properties .

1
2
3
4
5
6
7
spring.config.name=configserver
server.port=8888
 
spring.cloud.config.server.git.uri=https://github.com/sivaprasadreddy/microservices-config-repo
spring.cloud.config.server.git.clone-on-start=true
 
management.endpoints.web.exposure.include=*

Теперь аннотируйте класс точки входа с помощью @EnableConfigServer .

01
02
03
04
05
06
07
08
09
10
11
12
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
 
@EnableConfigServer
@SpringBootApplication
public class ConfigServerApplication {
 
    public static void main(String[] args) {
        SpringApplication.run(ConfigServerApplication.class, args);
    }
}

Вот и все. Это все, что вам нужно сделать для создания Spring Cloud Config Server, и вам просто нужно добавить специфичные для приложения файлы конфигурации в репозиторий git.

Если вы мысленно подготовили написать кучу кода для создания Spring Cloud Config Server, извините, что разочаровал вас :-).

Рефакторинг каталога-сервиса для использования Config Server

Наш каталог-сервис станет клиентом для Config Server. Итак, давайте добавим клиент Config Client в каталог-сервис, который добавит следующую зависимость.

1
2
3
4
<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-config</artifactId>
</dependency>

Убедитесь, что вы также добавили спецификацию spring-cloud-dependencies и в раздел <properties> .

При использовании Spring Cloud Config Server процесс загрузки свойств происходит в несколько этапов, сначала загружая bootstrap.properties/ yml, а затем с сервера конфигурации.

Итак, давайте переименуем application.properties в bootstrap.properties и обновим его, чтобы он имел следующие свойства.

1
2
3
4
spring.application.name=catalog-service
server.port=8181
management.endpoints.web.exposure.include=*
spring.cloud.config.uri=http://localhost:8888

Здесь мы настроили расположение нашего сервера Config и дали имя в качестве службы каталогов нашему приложению, используя spring.application.name .

Теперь нам нужно добавить все свойства нашего каталога-сервиса в catalog-service.properties и зафиксировать / отправить его в наше git repo microservices-config-repo .

microservices-конфигурации-репо / catalog-service.properties

01
02
03
04
05
06
07
08
09
10
logging.level.com.sivalabs=debug
 
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/catalog?useSSL=false
spring.datasource.username=root
spring.datasource.password=admin
 
spring.datasource.initialization-mode=always
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

Вы также можете добавить отдельные файлы конфигурации для разных файлов, таких как catalog-service-qa.properties , catalog-service-prod.properties и т. Д.

Теперь сначала запустите приложение Config Server, а затем приложение службы каталогов. Это должно работать нормально. Вы можете проверить журналы консоли, что служба каталогов извлекает свойства с сервера конфигурации http: // localhost: 8888 / при запуске.

Сейчас мы немного приближаемся к нашей цели, но мы все еще храним учетные данные в виде простого текста. Давайте переместим конфиденциальные свойства конфигурации в Vault.

Использование Vault для хранения конфиденциальных данных

Vault — это инструмент для безопасного хранения и доступа к секретам. Вы можете прочитать больше о Vault здесь https://www.vaultproject.io/intro/index.html . Vault поставляется в виде одного двоичного файла, который можно загрузить по адресу https://www.vaultproject.io/downloads.html .

Теперь запустите Vault в режиме разработки с помощью следующей команды:

$ vault server -dev

В консоли вы можете увидеть информацию о том, как использовать Vault и Root токен .
Откройте новое окно терминала, установите переменную среды VAULT_ADDR .

$ export VAULT_ADDR = ‘http: //127.0.0.1: 8200 ′

ПРИМЕЧАНИЕ. Режим разработки хранилища предназначен только для разработки и не предназначен для производственного использования.

Мы можем записать секретные данные в хранилище, используя секретную запись / хранилище ключей ключ1 = значение1 ключ2 = значение2
Мы также можем поместить все наши секреты в файл JSON и записать из него файл. Давайте создадим файл JSON с учетными данными базы данных MySQL и запишем в Vault.

Каталог-сервис-credentials.json

1
2
3
4
{
    "spring.datasource.username": "root",
    "spring.datasource.password": "admin"
}

$ vault write secret / catalog-service @ catalog-service-credentials.json

Вы можете проверить значения, запустив хранилище read secret / catalog-service .

Мы можем автоматизировать весь этот процесс настройки Vault и инициализации с использованием секретов с помощью Docker. Пожалуйста, посмотрите на репозиторий исходных текстов на github, чтобы узнать, как это сделать. Один из способов сделать это.

Теперь, когда Vault настроен и инициализирован с секретами. Давайте проведем рефакторинг каталога-сервиса для использования Vault.

Добавьте Vault Configuration starter в каталог-сервис, который добавит следующую зависимость.

1
2
3
4
<dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-vault-config</artifactId>
</dependency>

Удалите следующие учетные данные из microservices-config-repo / catalog-service.properties и зафиксируйте их.

1
2
spring.datasource.username=root
spring.datasource.password=admin

Добавьте свойства конфигурации Vault в bootstrap.properties .

1
2
3
4
5
spring.cloud.vault.host=localhost
spring.cloud.vault.port=8200
spring.cloud.vault.scheme=http
spring.cloud.vault.authentication=token
spring.cloud.vault.token=934f9eae-31ff-a8ef-e1ca-4bea9e07aa09

Мы настроили свойства Vault с использованием аутентификации на основе токенов и настроили Root Taken, который печатается в журнале консоли при запуске сервера хранилища.

У нас все готово. Мы перенесли свойства сервиса на внешний сервер конфигурации и конфиденциальные данные в Vault.

Теперь запустите Config Server и каталог-сервис, и он должен работать нормально.

Резюме

В этом посте мы узнали, как использовать Spring Cloud Config для вывода свойств конфигурации и Vault для хранения секретов. Вы можете использовать Spring Cloud Bus для автоматического обновления изменений конфигурации, как описано в Учебных пособиях Spring Cloud — Автоматическое обновление изменений конфигурации с использованием Spring Cloud Bus .

Вы можете найти исходный код этой статьи по адресу https://github.com/sivaprasadreddy/spring-boot-microservices-series

В следующей статье мы рассмотрим, как использовать Netflix Eureka для Service Registry и Service Discovery .

Опубликовано на Java Code Geeks с разрешения Сивы Редди, партнера нашей программы JCG . См. Оригинальную статью здесь: MicroServices. Часть 2. Управление конфигурацией с помощью Spring Cloud Config и Vault.

Мнения, высказанные участниками Java Code Geeks, являются их собственными.