Рассмотрим техническое задание, которое включает в себя три основных пункта:

  1. настроить базовое автообновление конфигураций клиентов Spring Cloud Config Server.

  2. на сервере настроить принудительное обновление конфигураций клиентов, если состав хранимой на сервере конфигурации изменится каким-либо образом.

  3. при этом запрещается использовать технологию 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. Регистрация доступна по ссылке.

Комментарии (0)