Статьи

12-факторные сервисы поддержки приложений в стиле Spring и Cloud Foundry

 [Эта статья была написана Джошом Лонгом.]

В Манифесте 12 Factor App подробно говорится о вспомогательных услугах . Служба поддержки — это, в основном, любая подключенная к сети служба, которую ваше приложение использует для своей работы. Это может быть экземпляр MongoDB, база данных PostgreSQL, бинарное хранилище, такое как Amazon S3, службы сбора метрик, такие как New Relic, очередь сообщений RabbitMQ или ActiveMQ, кэш на основе Memcached или Redis, служба FTP, служба электронной почты или что-то еще остальное. Различие заключается не столько в том, что сервис, сколько в том, как он выставляется и потребляется в приложении . К приложению оба являются прикрепленными ресурсами, доступ к которым осуществляется через URL-адрес или другой локатор / учетные данные, хранящиеся в конфигурации.

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

В этом посте мы рассмотрим, как среды Platform-as-a-Service (PaaS), такие как Cloud Foundry или Heroku, обычно предоставляют сервисы поддержки , и рассмотрим способы использования этих сервисов внутри приложения Spring. В наших примерах мы будем использовать Cloud Foundry, потому что он с открытым исходным кодом и легко запускается в любом центре обработки данных или размещается, хотя большая часть этого довольно проста для применения в Heroku.

Мой друг Эбби Грегори Кирнс собрал очень хороший общий обзор роли и ценности портфеля сервисных услуг Cloud Foundry .

PaaS, такой как Cloud Foundry, предоставляет вспомогательные сервисы в качестве переменных локальной среды процессов операционной системы. Переменные среды удобны, потому что они работают для всех языков и сред выполнения, и их легко переключать из одной среды в другую. Это намного проще, чем, например, попытаться запустить JNDI на локальном компьютере и продвигать переносимые сборки. В этом посте я собираюсь взглянуть на сервисы поддержки именно через призму Cloud Foundry. Имейте в виду, хотя этот подход специально предназначен для продвижения портативных сборок за пределамиоблачной среды. Весна сделана на заказ, чтобы быть переносной; внедрение зависимостей способствует отделению логики инициализации и получения (как, например, от службы поддержки) от места ее использования. Мы можем использовать Spring для написания кода, который обдумывает, javax.sql.DataSourcesа затем записывать конфигурацию в исходный код DataSourceиз правильного контекста и конфигурации, когда приложение перемещается из одной среды в другую.

Среда выполнения следующей версии Cloud Foundry (до сих пор упоминавшаяся в прессе как Диего ) — сначала Docker, а Docker — нативная. Разумеется, Docker упрощает контейнеризацию приложений, а интерфейс между контейнеризованным приложением и внешним миром намеренно минимален для продвижения переносимых приложений. Одним из ключевых входов в образе Docker, как вы уже догадались, являются переменные окружения! Наш приятель Крис Ричардсон (Chris Richardson) сделал несколько хороших постов как по упаковке, так и по созданию образов Docker на основе Spring Boot, а также по поддержке бэк-сервисов., Мы не будем рассматривать Docker в этом посте (хотя, следите за обновлениями!), Но важно его усвоить: переменные среды — это простой и гибкий способ извлечения информации о подключении к вспомогательным сервисам.

Простое весеннее загрузочное приложение, которое общается с JDBC DataSource

Вот простое приложение Spring Boot, которое вставляет и предоставляет несколько записей из DataSourcebean-компонента, который Spring Boot автоматически создаст для нас, потому что у нас есть встроенный драйвер базы данных H2 в CLASSPATH. Если весна загрузка не обнаруживает боб типа javax.sql.DataSourceи действительно обнаружить встроенный драйвер базы данных (H2, Derby, HSQL), он будет автоматически создать внедренный javax.sql.DataSourceбоб. В этом примере JPA используется для сопоставления записей с базой данных. Вот зависимости Maven:

ID группы ID артефакта
com.h2database h2
org.springframework.boot spring-boot-starter-data-jpa
org.springframework.boot spring-boot-starter-data-rest
org.springframework.boot spring-boot-starter-test
org.springframework.boot spring-boot-starter-actuator

Вот пример кода Java:

package demo;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import java.util.Arrays;

@SpringBootApplication
public class DemoApplication {

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

	@Bean
	CommandLineRunner seed(ReservationRepository rr) {
		return args -> Arrays.asList("Phil,Webb", "Josh,Long", "Dave,Syer", "Spencer,Gibb").stream()
			.map(s -> s.split(","))
			.forEach(namePair -> rr.save(new Reservation(namePair[0], namePair[1])));
	}
}

@RepositoryRestResource
interface ReservationRepository extends JpaRepository<Reservation, Long> {
}

@Entity
class Reservation {

	@Id
	@GeneratedValue
	private Long id;

	private String firstName, lastName;

	Reservation() {
	}

	public Reservation(String firstName, String lastName) {
		this.firstName = firstName;
		this.lastName = lastName;
	}

	public Long getId() {
		return id;
	}

	public String getFirstName() {
		return firstName;
	}

	public String getLastName() {
		return lastName;
	}
}

Создание и привязка сервисов поддержки в Cloud Foundry

Приложение работает локально, но давайте теперь обратимся к тому, чтобы оно работало на Cloud Foundry, где ему нужно будет рассказать, как подключиться к предоставляемой для него службе поддержки (экземпляру PostgreSQL).

Cloud Foundry выпускается во многих вариантах. Как и Heroku, вы можете запускать его на основе AWS, который доступен как Pivotal Web Services . Вы можете получить бесплатный пробный аккаунт. Вы можете использовать сжатый вариант Pivotal , Pivotal Cloud Foundry , и запустить его в своем центре обработки данных, если хотите. Кроме того, вы можете использовать биты с открытым исходным кодом и запустить их тоже. Или используйте любую из многочисленных других реализаций, таких как IBM и HP . В любом случае вы получите PaaS, который ведет себя в основном одинаково по отношению к вспомогательным сервисам.

Добавление резервного сервиса является декларативной функцией: просто создайте сервис, а затем привяжите его. Облако Foundry имеет MarketPlace команду: cf marketplace. В 80% случаев вы должны быть в состоянии выбрать один из вариантов в cf marketplaceвыходных данных. Как только вы выбрали сервис, создайте его экземпляр, например так:

cf create-service elephantsql turtle postgresql-db

Обратите особое внимание на то, что postgresql-dbэто имя службы поддержки, имя elephantsqlпоставщика услуг и имя планаturtle (бесплатного) уровня . Имея это в виду , вы можете привязать сервис к развернутому приложению. Вы можете использовать CLI или просто объявить зависимость службы поддержки в файле вашего приложения .cfmanifest.yml

Вот основные manifest.ymlдля всех наших образцов. Символы nameи hostпереходят из одного манифеста в другой, но в примерах для этого поста используется этот недавно созданный postgresql-dbсервис.

---
applications:
- name: simple-backing-services
	memory: 512M
	instances: 1
	host: simple-backing-services-${random-word}
	domain: cfapps.io
	path: target/simple.jar
	services:
		- postgresql-db
	env:
		SPRING_PROFILES_ACTIVE: cloud
		DEBUG: "true"
		debug: "true"

Теперь давайте рассмотрим несколько различных способов использования службы поддержки и рассмотрим некоторые их сильные и слабые стороны.

Мы будем использовать Spring Boot , как минимум, для всех этих примеров. Spring Boot предлагает модуль привода Spring Boot. Он предоставляет очень полезную информацию о приложении — метрики, дампы среды, список всех определенных bean-компонентов и т. Д. Мы будем использовать некоторые из этих конечных точек, чтобы получить представление о переменных среды и системных свойствах, отображаемых при запуске приложения Spring, и понимать такие вещи, как профиль, под которым работает Spring Boot. Добавьте Spring Boot Actuator в ваш Maven или Gradle билд. groupIdЭто org.springframework.bootи artifactIdесть spring-boot-starter-actuator. Вам не нужно указывать версию, если вы используете Spring Boot, сгенерированный из start.spring.ioили команду spring initCLI.

Авто-реконфигурация Cloud Foundry

Buildpack Cloud Foundry Java делает автоматическую перенастройку для вас. Из документов :

Автореконфигурация состоит из трех частей. Во-первых, он добавляет cloudпрофиль в список активных профилей Spring. Во- вторых он предоставляет все свойства предоставлены Cloud Foundry , как PropertySourceв ApplicationContext. Наконец, он переписывает определения бинов различных типов для автоматического соединения со службами, привязанными к приложению. Переписаны следующие типы:

Тип бобов Тип Обслуживания
javax.sql.DataSource Службы реляционных данных (например, ClearDB, ElephantSQL)
org.springframework.amqp.rabbit.connection.ConnectionFactory Сервис RabbitMQ (например, CloudAMQP)
org.springframework.data.mongodb.MongoDbFactory Монго Сервис (например, МонгоЛаб)
org.springframework.data.redis.connection.RedisConnectionFactory Служба Redis (например, Redis Cloud)
org.springframework.orm.hibernate3.AbstractSessionFactoryBean Службы реляционных данных (например, ClearDB, ElephantSQL)
org.springframework.orm.hibernate4.LocalSessionFactoryBean Службы реляционных данных (например, ClearDB, ElephantSQL)
org.springframework.orm.jpa.AbstractEntityManagerFactoryBean Службы реляционных данных (например, ClearDB, ElephantSQL)

В результате приложение, работающее на H2 локально, автоматически запускается на PostgreSQL на Cloud Foundry, при условии, что у вас есть служба поддержки, указывающая на экземпляр PostgreSQL, созданный и связанный с приложением, как мы это делали выше. Это очень удобно! Если ваши две цели — localhost и Cloud Foundry, это работает отлично.

Полезные Environmentсвойства для вашего приложения Spring

По умолчанию Java buildpack (который вы можете переопределить при выполнении cf pushили объявив его в вашем manifest.yml) добавляет Spring, Environment PropertySourceкоторый регистрирует множество свойств, которые начинаются с cloud.. Их можно увидеть, посетив /envконечную точку REST в вышеуказанном приложении, как только вы отправили приложение в Cloud Foundry. Вот некоторые результаты для моего приложения:

{
	...
	cloud.services.postgresql-db.connection.jdbcurl: "jdbc:postgresql://babar.elephantsql.com:5432/AUSER?user=AUSER&password=WOULDNTYOULIKETOKNOW",
	...
	cloud.services.postgresql-db.connection.uri: "postgres://AUSER:WOULDNTYOULIKETOKNOW@babar.elephantsql.com:5432/AUSER",
	cloud.services.postgresql-db.connection.scheme: "postgres",
	cloud.services.postgresql.connection.jdbcurl: "jdbc:postgresql://babar.elephantsql.com:5432/AUSER?user=AUSER&password=WOULDNTYOULIKETOKNOW",
	cloud.services.postgresql.connection.port: 5432,
	cloud.services.postgresql.connection.path: "AUSER",
	cloud.application.host: "0.0.0.0",
	cloud.services.postgresql-db.connection.password: "******",
	cloud.services.postgresql-db.connection.username: "AUSER",
	...
	cloud.application.application_name: "simple-backing-services",
	cloud.application.limits: {
		mem: 512,
		disk: 1024,
		fds: 16384
	},
	cloud.services.postgresql-db.id: "postgresql-db",
	cloud.application.application_uris: [
	"simple-backing-services-fattiest-teniafuge.cfapps.io",
	"simple-backing-services-unmummifying-prehnite.cfapps.io"
],
	cloud.application.instance_index: 0,
	...
}

Вы можете использовать эти свойства в Spring так же, как и любое другое свойство. Они также очень удобны, так как они предоставляют не только довольно стандартный URI соединения Heroku-esque ( cloud.services.postgresql-db.connection.uri), но также и тот, который может использоваться непосредственно в контексте JDBC cloud.services.postgresql.connection.jdbcurl. Пока вы используете этот (или разветвленный) сборочный пакет, вы будете пользоваться этими свойствами.

Cloud Foundry предоставляет всю эту информацию в качестве стандартных, языковых и технологических переменных среды среды ( VCAP_SERVICESи VCAP_APPLICATION). Теоретически, вы должны иметь возможность написать приложение для любой реализации Cloud Foundry и нацелить эти переменные. Spring Boot также предоставляет автоконфигурацию для этих переменных, и этот подход работает независимо от того, используете вы вышеупомянутый Java buildpack или нет.

Spring Boot отображает эти переменные среды в набор свойств, доступных из Environmentабстракции Spring . Вот некоторые примеры вывода VCAP_*свойств, предоставляемых Spring Boot, а также /env:

{
	...
	vcap.application.start: "2015-01-27 09:58:13 +0000",
	vcap.application.application_version: "9e6ba76e-039f-4585-9573-8efa9f7e9b7e",
	vcap.application.application_uris[2]: "simple-backing-services-detersive-sterigma.cfapps.io",
	vcap.application.uris: "simple-backing-services-fattiest-teniafuge.cfapps.io,simple-backing-services-grottoed-distillment.cfapps.io,...",
	vcap.application.space_name: "joshlong",
	vcap.application.started_at: "2015-01-27 09:58:13 +0000",
	vcap.services.postgresql-db.tags: "Data Stores,Data Store,postgresql,relational,New Product",
	vcap.services.postgresql-db.credentials.uri: "postgres://AUSER:WOULDNTYOULIKETOKNOW@babar.elephantsql.com:5432/hqsugvxo",
	vcap.services.postgresql-db.tags[1]: "Data Store",
	vcap.services.postgresql-db.tags[4]: "New Product",
	vcap.application.application_name: "simple-backing-services",
	vcap.application.name: "simple-backing-services",
	vcap.application.uris[2]: "simple-backing-services-detersive-sterigma.cfapps.io",
	...
}

Я склонен немного полагаться на каждый подход. Свойства Spring Boot удобны тем, что они предоставляют индексированные свойства. vcap.application.application_uris[2]предоставляет способ индексировать в массив возможных маршрутов для этого приложения. Это идеально, если вы хотите сообщить своему запущенному приложению, каков его внешний адрес URI, например, если ему нужно установить обратные вызовы при запуске до того, как поступит какой-либо запрос. Он также предоставляет эквивалентные независимые от технологии URI, но не JDBC специфическая строка подключения. Итак, я бы использовал оба. Этот подход удобен, особенно в Spring Boot, потому что я могу быть явным в своей конфигурации и устанавливать свойства (например spring.datasource.*), чтобы инструктировать Spring Boot о том, как это настроить. Это полезно из-за явной явности или если у меня есть несколько вспомогательных служб одного типа (например, JDBCjavax.sql.DataSource) связаны с тем же приложением. В этом случае buildpack-пакет не будет знать, что делать, поэтому вам нужно быть явным и недвусмысленно указывать, какая ссылка на вспомогательную службу должна быть введена и где.

Использование Spring Profiles

Spring Boot, по умолчанию, загружается src/main/resources/application.(properties,yml). Также будут загружены файлы свойств профиля, имеющие вид формы src/main/resources/application-PROFILE.yml, где PROFILEуказано имя активного профиля Spring . Ранее мы видели, что наш manifest.ymlспециально активирует cloudпрофиль, устанавливая переменную окружения. Итак, предположим, что вы хотели иметь конфигурацию, которая была активирована только при работе в cloudпрофиле, и другую, которая была активирована, когда не было активировано определенного профиля — это называется defaultпрофилем. Вы можете создать три файла: src/main/resources/application-cloud.(properties,yml)которые будут активированы всякий раз, когда активирован cloudпрофиль, src/main/resources/application-default.(properties,yml)который будет активирован, когда никакой другой профиль специально не активирован, иsrc/main/resources/application.(properties,yml) который будет активирован для всех ситуаций, несмотря ни на что.

Образец src/main/resources/application.properties:

spring.jpa.generate-ddl=true

Образец src/main/resources/application-cloud.properties:

spring.datasource.url=${cloud.services.postgresql-db.connection.jdbcurl}

Образец src/main/resources/application-default.properties:

# empty in this case because I rely on the embedded H2 instance being created
# though you could point it to another, local,
# PostgresSQL instancefor dev workstation configuration

Использование PaaS-соединителей Spring Cloud

Все эти варианты до сих пор используют преимущества Environmentабстракции. Без сомнения, они гораздо проще, чем вручную выбирать структуру JSON в VCAP_SERVICESпеременной, но мы можем добиться большего. Как сказано в документации для проекта Spring Cloud Connectors :

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

Давайте посмотрим на наш пересмотренный пример:

package demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.java.AbstractCloudConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.sql.DataSource;

@SpringBootApplication
public class DemoApplication {

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

    @Configuration
    @Profile("cloud")
    public static class DataSourceConfig extends AbstractCloudConfig {

        @Bean
        DataSource reservationsPostgreSqlDb() {
            return connectionFactory().dataSource("postgresql-db");
        }
    }

}

@RepositoryRestResource
interface ReservationRepository extends JpaRepository<Reservation, Long> {
}

@Entity
class Reservation {
    @Id
    @GeneratedValue
    private Long id;

    public Long getId() {
        return id;
    }

    private String firstName, lastName;

    Reservation() {
    }

    public Reservation(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }
}

Вместо того, чтобы конфигурировать javax.sql.DataSourceиспользование свойств и Spring Boot, мы явно производим объект правильного типа. Существуют другие методы для других вспомогательных служб, таких как MongoDB , Redis , SendGrid и т. Д., И вы можете легко предоставить свой собственный. Мы используем плагин Spring Cloud Connectors для Cloud Foundry, хотя нет никаких причин, по которым вы не могли бы использовать плагин Spring Cloud Connectors для Heroku или альтернативу на основе свойств для локальных приложений. Тогда ваше приложение будет одинаковым в разных средах, и отличается только внешняя конфигурация.

Использование конфигурации Java @Beanсек

До сих пор мы полагались на стандартные значения по умолчанию, предоставляемые платформой или платформой, но вам не нужно отказываться от какого-либо контроля. Например, вы можете явно определить компонент в конфигурации XML или Java, используя значения из среды. Вы можете сделать это, если ваше приложение хочет использовать пользовательский пул соединений или каким-то другим образом настроить конфигурацию службы поддержки. Вы также можете сделать это, если у платформы и фреймворка нет автоматической поддержки для службы поддержки, которую вы пытаетесь использовать.

	@Bean
	@Profile("cloud")
	DataSource dataSource(
			@Value("${cloud.services.postgresql-db.connection.jdbcurl}") String jdbcUrl) {
		try {
			return new SimpleDriverDataSource(
				org.postgresql.Driver.class.newInstance() , jdbcUrl);
		}
		catch (Exception e) {
			throw new RuntimeException(e) ;
		}
	}

Куда пойти отсюда?

До сих пор мы рассматривали только те сервисы, которые предоставляет Cloud Foundry. Если вы хотите использовать услугу, которая у вас есть вне PaaS, достаточно легко обращаться с ней, как с любой другой службой поддержки, используя предоставляемые пользователем услуги Cloud Foundry . Этот механизм — просто причудливый способ рассказать Cloud Foundry о локаторе и учетных данных для пользовательского сервиса, с которым ваши приложения должны взаимодействовать. После того, как вы это сделаете, приложения и службы Cloud Foundry могут привязать этот сервис к своим приложениям и использовать его, как обычно. Предоставляемые пользователями сервисы являются идеальными или сервисами, такими как фиксированный экземпляр Oracle, которыми вы не собираетесь управлять Cloud Foundry. Cloud Foundry не добавляет новые экземпляры, не удаляет их и не контролирует авторизацию.

Если вы хотите, чтобы Cloud Foundry управлял сервисом, вам необходимо адаптировать его к Cloud Foundry с помощью API брокера сервисов . Это более важно при развертывании Cloud Foundry в вашей собственной среде и требует прав администратора (например, которые вы не будете иметь в размещенном Pivotal Cloud Foundry). API сервисного брокера представляет собой набор хорошо известных обратных вызовов REST, о которых необходимо знать Cloud Foundry. Достаточно легко реализовать свой собственный брокер пользовательских сервисов, и даже есть этот удобный проект на основе Spring Boot и соответствующий пример .

Проверьте примеры тоже!