Рассмотрим техническое задание, которое включает в себя три основных пункта:
настроить базовое автообновление конфигураций клиентов Spring Cloud Config Server.
на сервере настроить принудительное обновление конфигураций клиентов, если состав хранимой на сервере конфигурации изменится каким-либо образом.
при этом запрещается использовать технологию Spring Cloud Bus и связанную с ней передачу команд на обновление через Kafka или RabbitMQ.
Если вас удивил пункт 3 этого ТЗ, то вы не одиноки - меня он тоже очень удивил. Но это был реальный заказчик, очень негибкий, требовательный к условиям ТЗ и не склонный к переговорам. Учитывая степень его платежеспособности, спорить не хотелось, и иные варианты даже не обсуждались - словом, любой каприз за ваши деньги.
Если у вас нет таких нестандартных требований, вы можете сразу перейти к прекрасному руководству, которое уже публиковалось в блоге компании OTUS. Там есть практически все. Мы же настроим обновление именно для этого нестандартного случая, не слишком углубляясь в стандартные детали, и наоборот, более подробно описав специфические для этого ТЗ особенности настройки.
Итак, у нас есть клиент, обновлять его мы будем, используя возможности Spring Actuator, его использовать не запрещалось, тем более, что он может выполнять и прямые свои обязанности - сбор метрик, что также было нужно в проекте, как бывает и во многих других проектах. Чтобы это заработало, добавим в pom.xml проекта на Maven следующие зависимости:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<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-bootstrap</artifactId>
</dependency>
Здесь все понятно - защита Spring Boot приложения, сам Actuator, две зависимости для Spring Cloud Server клиента.
В клиенте нам нужен файл bootstrap.yml, который будет лежать в ресурсах:
spring:
application:
name: pyramid
config:
import: optional:configserver:http://localhost:8800
profiles:
active: local
cloud:
config:
fail-fast: true
enabled: true
username: configserver
password: configserver
security:
user:
name: actuator
password: actuator
roles: ACTUATOR_ADMIN
server:
port: 8082
#минимальная настройка Spring Actuator - ее нельзя переносить в config-server, потому что мы нуждаемся в обновлении через него по умолчанию
management:
endpoints:
enabled-by-default: true
web:
exposure:
include: "*"
Здесь я привожу настройку для случая локального dev стенда, на котором сервер и клиент лежат на localhost, для переноса на сервер следует внести собственные правки адресов, портов и credentials. Секцию management вы также можете изменить под собственные нужды, добавив или более точно и безопасно перенастроив endpoints, для демонстрации я просто открыл все endpoints и опубликовал web, чтобы не возиться. В продуктовом приложении эта секция была настроена под нужды приложения, подробности которого здесь не обсуждаются. Фактически, данная настройка делает две вещи - предоставляет настройки для подключения к серверу Spring Cloud Config Server и предоставляет Spring Actuator с включенной примитивной авторизацией для того, чтобы использовать его для автообновления конфигураций.
В демо авторизацию я сделал самую простую, вы можете использовать любую настройку, которая требуется для вашего продуктива:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.requestMatchers(EndpointRequest.toAnyEndpoint())
.hasRole("ACTUATOR_ADMIN")
.requestMatchers(PathRequest.toStaticResources().atCommonLocations())
.permitAll()
.antMatchers("/")
.permitAll()
.antMatchers("/**")
.authenticated()
.and()
.httpBasic()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
Ну вот, в общем-то и все самое главное о клиенте. Осталось добавить, что нужно разметить ваше приложение в нужных местах аннотациями @RefreshScope для передачи properties из конфигурации в код приложения там, где это вам нужно. Как это сделать, опять же можно посмотреть в статье, на которую я выше уже ссылался, или на мою статью, где этот вопрос также отчасти затронут для более редкого особенного случая. Полный код демо приложения можно посмотреть на github, в нем много не имеющего прямого отношения к данной статье (детали приложения с его специфическими нуждами), но все, что я здесь описал, вы найдете на своих местах.
Самое главное, что мы должны понять - без применения какого-либо механизма автообновления данный клиент будет забирать конфигурацию с сервера только при его загрузке или при кастомной инициации обновления. Если хранимая на сервере конфигурация будет изменена, клиент об этом ничего не узнает и будет использовать старые подгруженные значения до тех пор, пока мы не инициируем вручную запрос к клиенту на обновление конфигурации, в нашем случае мы используем для этого обращение к Spring Actuator, установленному на клиенте, по адресу https://client_url/actuator/refresh. Теоретически можно сделать и на самом клиенте bean с аннотацией @Scheduled, который будет опрашивать этот адрес с каким-то периодом, но это было бы совсем топорно, например, конфигурация будет загружаться заново независимо от того, были в ней обновления или нет, и клиент будет вхолостую выполнять массу совершенно не нужной работы в каждом периоде. Однако, если реализовать обращение к адресу Spring Actuator со стороны сервера и только при определенных условиях, то мы получим то, что требуется от нас в ТЗ. Поэтому так и поступим.
Во второй части статьи мы рассмотрим один из вариантов настройки сервера, продолжение в следующей статье.
Также хочу пригласить всех желающих на бесплатный урок от OTUS, в рамках которого будут рассмотрены аспекты - что это и зачем нужно. Как создавать аспекты в Java используя разные технологии, и как они используются в Spring. Регистрация доступна по ссылке.