[PostgreSQL, Kotlin, Микросервисы, Kubernetes] Как мы в 2020 году изобретали процесс разработки, отладки и доставки в прод изменений базы данных
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
На дворе 2020 год и фоновым шумом вы уже привыкли слышать: «Кубернетес — это ответ!», «Микросервисы!», «Сервис меш!», «Сесурити полиси!». Все вокруг бегут в светлое будущее.
Подходы в том, что касается баз данных, в нашей компании более консервативны, чем в прикладных приложениях. Крутится база данных у нас не в кубернетесе, а на железе или в виртуалке. Для изменений базы данных процессинга платежных сервисов у нас есть устоявшийся процесс, который включает в себя множество автоматических проверок, большое ревью и релиз с участием DBA. Количество проверок и привлекаемых людей в этом случае негативно влияет на time-to-market. С другой стороны, он отлажен и позволяет надежно вносить изменения в продакшен, минимизируя вероятность что-то сломать. А если что-то сломалось, то нужные люди уже включены в процесс починки. Этот подход делает работу основного сервиса компании стабильнее.
Большинство новых реляционных баз данных для микросервисов мы заводим на PostgreSQL. Отлаженный процесс для Oracle хоть и надёжный, но несет с собой избыточную сложность для маленьких БД. Тащить тяжёлые процессы из прошлого в светлое будущее никто не хочет. Проработкой процесса для светлого будущего заранее никто не занялся. В итоге получили отсутствие стандарта и разножопицу.
оригинал
Если хотите узнать, к каким проблемам это привело и как мы их порешали, — добро пожаловать под кат.
Проблемы, которые мы решали
Нет единых стандартов версионирования
В лучшем случае это DDL SQL-файлы, которые лежат где-то в директории db в репозитории с микросервисом. Совсем плохо, если это просто текущее состояние БД, разное на тесте и на проде, и эталонных скриптов схемы БД нет.
В ходе отладки ушатываем тестовую базу
«Я сейчас немного тестовую БД пошатаю, не пугайтесь там» — и пошел отлаживать на тестовой базе данных только что написанный код изменения схемы. Иногда долго, и всё это время тестовый контур не работает.
При этом может поломаться тестовый контур в той части, где другие микросервисы взаимодействуют с микросервисом, чью базу ушатал разработчик.
Методы DAO не покрываются тестами, не проверяются в CI
При разработке и отладке методы DAO вызываются через дергание за внешние ручки несколькими слоями выше. Это подвергает проверке целые сценарии бизнес-логики вместо конкретного взаимодействия микросервиса и базы данных.
Гарантии, что ничего не развалится в будущем, нет. Страдает качество и поддерживаемость микросервиса.
Неизоморфность сред
Если в тестовый и продакшен контуры изменения поставляются по-разному, то нельзя быть уверенным, что оно будет работать одинаково. Особенно когда на тесте по факту проводится разработка и отладка.
Объекты на тесте могут быть созданы из-под учетки разработчика или приложения. Гранты накидываются как попало, обычно grant all privileges. Гранты приложению выдаются по принципу «вижу ошибку в логе — даю грант». Часто при релизе забывают про гранты. Иногда после релиза смок-тестирование не покрывает всю новую функциональность и отсутствие гранта выстреливает не сразу.
Тяжелый и ломучий процесс наката в продакшен
Накат в прод сделали ручным, но по аналогии с процессом для Oracle, через согласование DBA, релиз-менеджеров и накат релиз-инженерами.
Это замедляет релиз. А в случае проблем увеличивает даунтайм, усложняя доступ разработчика к БД. Скрипты exec.sql и rollback.sql часто не проверялись на тесте, потому что стандарта патчсетирования для не-Oracle нет, а на тест катилось как попало.
Поэтому бывает такое, что в некритичные сервисы разработчики катят изменения без этого процесса вообще.
Как можно делать, чтобы было хорошо
Отладка на локальной БД в докер-контейнере
Для кого-то могут показаться очевидными вообще все технические решения, описанные в статье. Но почему-то из года в год я вижу людей, которые с энтузиазмом наступают на одни и те же грабли.
Вот вы же не лезете на тестовый сервер по ssh, чтобы писать и дебажить код приложения? Я считаю, что разрабатывать и отлаживать код базы данных на тестовом инстансе БД — так же абсурдно. Есть исключения, бывает, что поднять локально базу данных очень сложно. Но обычно, если мы говорим о чем-то легковесном и не-легаси, то поднять локально базу и накатить на нее последовательно все миграции не составляет большого труда. Взамен вы получите стабильный инстанс под боком, который не ушатает другой разработчик, до которого не пропадут доступы и на котором вы имеете нужные для разработки права.
Приведу пример, насколько просто поднять локально БД:
Пишем двухстрочный Dockerfile:
FROM postgres:12.3
ADD init.sql /docker-entrypoint-initdb.d/
В init.sql делаем «чистую» БД, которую рассчитываем получить и на тесте, и в проде. Она должна содержать:
- Пользователя-владельца схемы и саму схему.
- Пользователя приложения с грантом на использование схемы.
- Требуемые EXTENSIONs
Пример init.sql
SPL
create role my_awesome_service
with login password *** NOSUPERUSER inherit CREATEDB CREATEROLE NOREPLICATION;
create tablespace my_awesome_service owner my_awesome_service location '/u01/postgres/my_awesome_service_data';
create schema my_awesome_service authorization my_awesome_service;
grant all on schema my_awesome_service to my_awesome_service;
grant usage on schema my_awesome_service to my_awesome_service;
alter role my_awesome_service set search_path to my_awesome_service,pg_catalog, public;
create user my_awesome_service_app with LOGIN password *** NOSUPERUSER inherit NOREPLICATION;
grant usage on schema my_awesome_service to my_awesome_service_app;
create extension if not exists "uuid-ossp";
Для удобства можно добавить в Makefile таску db, которая (пере)запустит контейнер с базой и оттопырит порт для соединения:
db:
docker container rm -f my_awesome_service_db || true
docker build -t my_awesome_service_db docker/db/.
docker run -d --name my_awesome_service_db -p 5433:5432 my_awesome_service_db
Версионирование changeset‘ов с помощью чего-то стандартного для индустрии
Тоже выглядит очевидно: нужно писать миграции и содержать их в системе контроля версий. Но очень часто я вижу «голые» sql-скрипты, без какой-либо обвязки. И это значит, что нет никакого контроля наката и отката, кем, что и когда было накачено. Нет даже гарантии, что ваши SQL-скрипты могут быть выполнены на тестовой и продовой БД, так как ее структура могла измениться.
В общем, нужен контроль. Системы миграции как раз про контроль.
Не будем вдаваться в сравнение разных систем версионирования схем БД. FlyWay vs Liquibase — не тема этой статьи. Мы выбрали Liquibase.
Мы версионируем:
- DDL-структуру объектов бд (create table).
- DML-содержимое таблиц-справочников (insert, update).
- DCL-гранты для УЗ Приложения (grant select, insert on ...).
Запуская и отлаживая микросервис на локальной БД, разработчик столкнется с необходимостью позаботиться о грантах. Единственный легальный способ для него — завести DCL-скрипт в ченджсет. Это гарантирует нам, что гранты доедут до прода.
Пример sql-патчсета
SPL
0_ddl.sql:
create table my_awesome_service.ref_customer_type
(
customer_type_code varchar not null,
customer_type_description varchar not null,
constraint ref_customer_type_pk primary key (customer_type_code)
);
alter table my_awesome_service.ref_customer_type
add constraint customer_type_code_ck check ( (customer_type_code)::text = upper((customer_type_code)::text) );
1_dcl.sql:
grant select on all tables in schema my_awesome_service to ru_svc_qw_my_awesome_service_app;
grant insert, update on my_awesome_service.some_entity to ru_svc_qw_my_awesome_service_app;
2_dml_refs.sql:
insert into my_awesome_service.ref_customer_type (customer_type_code, customer_type_description)
values ('INDIVIDUAL', 'Физ. лицо');
insert into my_awesome_service.ref_customer_type (customer_type_code, customer_type_description)
values ('LEGAL_ENTITY', 'Юр. лицо');
insert into my_awesome_service.ref_customer_type (customer_type_code, customer_type_description)
values ('FOREIGN_AGENCY', 'Иностранное юр. лицо');
Fixtures. Данные для тестов или отладки идут отдельным ченжсетом с контекстом dev
3_dml_dev.sql:
insert into my_awesome_service.some_entity_state (state_type_code, state_data, some_entity_id)
values ('BINDING_IN_PROGRESS', '{}', 1);
rollback.sql:
drop table my_awesome_service.ref_customer_type;
Пример changeset.yaml
SPL
databaseChangeLog:
- changeSet:
id: 1
author: "mr.awesome"
changes:
- sqlFile:
path: db/changesets/001_init/0_ddl.sql
- sqlFile:
path: db/changesets/001_init/1_dcl.sql
- sqlFile:
path: db/changesets/001_init/2_dml_refs.sql
rollback:
sqlFile:
path: db/changesets/001_init/rollback.sql
- changeSet:
id: 2
author: "mr.awesome"
context: dev
changes:
- sqlFile:
path: db/changesets/001_init/3_dml_dev.sql
Liquibase создает на БД таблицу databasechangelog, где отмечает накаченные ченджсеты.
Автоматически вычисляет, сколько ченджсетов нужно докатить до БД.
Есть maven и gradle plugin с возможностью сгенерировать из нескольких ченджсетов скрипт, который нужно докатить до БД.
Интеграция системы миграций БД в фазу запуска приложения
Здесь мог бы быть любой адаптер системы контроля миграций и фреймворка, на котором построено ваше приложение. Со многими фреймворками он идёт в комплекте с ORM. Например, Ruby-On-Rails, Yii2, Nest.JS.
Этот механизм нужен, чтобы катить миграции при старте контекста приложения.
Например:
- На тестовой БД патчсеты 001, 002, 003.
- Погромист наразрабатывал патчсеты 004, 005 и не деплоил приложение в тест.
- Деплоим в тест. Докатываются патчсеты 004, 005.
Если не накатываются — приложение не стартует. Rolling update не убивает старые поды.
В нашем стеке JVM + Spring, и мы не используем ORM. Поэтому нам потребовалась интеграция Spring-Liquibase.
У нас в компании есть важное требование безопасности: пользователь приложения должен иметь ограниченный набор грантов и точно не должен иметь доступ уровня владельца схемы. С помощью Spring-Liquibase есть возможность катить миграции от имени пользователя-владельца схемы. При этом пул соединений прикладного уровня приложения не имеет доступа к DataSource'у Liquibase. Поэтому приложение не получит доступ из-под пользователя-владельца схемы.
Пример application-testing.yaml
SPL
spring:
liquibase:
enabled: true
database-change-log-lock-table: "databasechangeloglock"
database-change-log-table: "databasechangelog"
user: ${secret.liquibase.user:}
password: ${secret.liquibase.password:}
url: "jdbc:postgresql://my.test.db:5432/my_awesome_service?currentSchema=my_awesome_service"
DAO тесты на CI-этапе verify
В нашей компании есть такой CI-этап — verify. На этом этапе происходит проверка изменений на соответствие внутренним стандартам качества. Для микросервисов это обычно прогон линтера для проверки кодстайла и на наличие багов, прогон unit-тестов и запуск приложения с поднятием контекста. Теперь на этапе verify можно проверить миграции БД и взаимодействие DAO-слоя приложения с БД.
Поднятие контейнера с БД и накат патчсетов увеличивает время старта Spring-контекста на 1,5-10 сек, в зависимости от мощности рабочей машины и количества патчсетов.
Это не совсем unit-тесты, это тесты интеграции DAO-слоя приложения с базой данных.
Называя БД частью микросервиса, мы говорим, что это тестирование интеграции двух частей одного микросервиса. Без внешних зависимостей. Таким образом эти тесты стабильны и могут выполняться на этапе verify. Они фиксируют контракт микросервиса и БД, обеспечивая уверенность при будущих доработках.
А еще это удобный способ отладки DAO. Вместо того, чтобы вызывать RestController, имитируя поведения пользователя в каком-то бизнес-сценарии, сразу вызываем DAO с нужными аргументами.
Пример DAO-теста
SPL
@Test
@Transactional
@Rollback
fun `create cheque positive flow`() {
jdbcTemplate.update(
"insert into my_awesome_service.some_entity(inn, registration_source_code)" +
"values (:inn, 'QIWICOM') returning some_entity_id",
MapSqlParameterSource().addValue("inn", "526317984689")
)
val insertedCheque = chequeDao.addCheque(cheque)
val resultCheque = jdbcTemplate.queryForObject(
"select cheque_id from my_awesome_service.cheque " +
"order by cheque_id desc limit 1", MapSqlParameterSource(), Long::class.java
)
Assert.assertTrue(insertedCheque.isRight())
Assert.assertEquals(insertedCheque, Right(resultCheque))
}
Есть две сопутствующие задачи для прогона этих тестов в пайплайне на verify:
- На билдагенте может быть потенциально занят стандартный порт PostgreSQL 5432 или любой статичный. Мало ли, кто-то не потушил контейнер с базой после завершения тестов.
- Из этого вторая задача: нужно тушить контейнер после завершения тестов.
Эти две задачи решает библиотека TestContainers. Она использует существующий докер образ для поднятия контейнера с базой данных в состоянии init.sql.
Пример использования TestContainers
SPL
@TestConfiguration
public class DatabaseConfiguration {
@Bean
GenericContainer postgreSQLContainer() {
GenericContainer container = new GenericContainer("my_awesome_service_db")
.withExposedPorts(5432);
container.start();
return container;
}
@Bean
@Primary
public DataSource onlineDbPoolDataSource(GenericContainer postgreSQLContainer) {
return DataSourceBuilder.create()
.driverClassName("org.postgresql.Driver")
.url("jdbc:postgresql://localhost:"
+ postgreSQLContainer.getMappedPort(5432)
+ "/postgres")
.username("my_awesome_service_app")
.password("my_awesome_service_app_pwd")
.build();
}
@Bean
@LiquibaseDataSource
public DataSource liquibaseDataSource(GenericContainer postgreSQLContainer) {
return DataSourceBuilder.create()
.driverClassName("org.postgresql.Driver")
.url("jdbc:postgresql://localhost:"
+ postgreSQLContainer.getMappedPort(5432)
+ "/postgres")
.username("my_awesome_service")
.password("my_awesome_service_app_pwd")
.build();
}
С разработкой и отладкой разобрались. Теперь нужно доставить изменения схемы БД в продакшен.
Kubernetes — это ответ! А какой был ваш вопрос?
Итак, вам надо автоматизировать какой-то CI/CD-процесс. У нас есть обкатанный подход на тимсити. Казалось бы, где тут повод для еще одной статьи?
А повод есть. Кроме обкатанного подхода, есть и поднадоевшие проблемки большой компании.
- Билдагентов тимсити на всех не хватает.
- Лицензия стоит денег.
- Настройки виртуалок билдагентов делаются по старинке, через репозитории с конфигами и puppet.
- Доступы с билдагентов до целевых сетей пропиливать надо по старинке.
- Логины-пароли для наката изменений на базу тоже хранятся по старинке.
И во всем этом «по старинке» проблема — все бегут в светлое будущее, а поддержка легаси… ну вы знаете. Работает и ладно. Не работает — займемся потом. Когда-нибудь. Не сегодня.
Допустим, вы уже одной ногой по колено в светлом будущем и кубернетес-инфраструктура у вас уже есть. Есть даже возможность сгенерировать еще один микросервис, который сразу заведется в этой инфраструктуре, подхватит нужный конфиг и секреты, будет иметь нужные доступы, зарегистрируется в service mesh инфраструктуре. И всё это счастье может получить рядовой разработчик, без привлечения человека с ролью *OPS. Вспоминаем, что в кубернетесе есть тип ворклоада Job, как раз предназначенный для каких-то сервисных работ. Ну и погнали делать приложение на Kotlin+Spring-Liquibase, стараясь максимально переиспользовать существующую в компании инфраструктуру для микросервисов на JVM в кубере.
Переиспользуем следующие аспекты:
- Генерация проекта.
- Деплой.
- Доставку конфигов и секретов.
- Доступы.
- Логирование и доставка логов в ELK.
Получаем такой пайплайн:
Кликабельно
Теперь мы имеем:
- Версионирование ченджсетов.
- Проверяем их на выполнимость update → rollback.
- Пишем тесты на DAO. Бывает даже следуем TDD: запускаем отладку DAO с помощью тестов. Тесты выполняются на свежеподнятой БД в TestContainers.
- Запускаем локально БД в докере на стандартном порту. Проводим отладку, смотрим, что осталось в БД. При необходимости можем управлять локальной БД вручную.
- Накатываем в тест и проводим авторелиз патчсетов стандартным пайплайном в teamcity, по аналогии с микросервисами. Пайплайн является дочерним для микросервиса, которому принадлежит БД.
- Не храним креды от БД в тимсити. И не заботимся о доступах с виртуалок-билдагентов.
Знаю, что для многих это всё не откровение. Но раз уж вы дочитали, будем рады рассказу о вашем опыте в комментах =)
===========
Источник:
habr.com
===========
Похожие новости:
- [Разработка веб-сайтов, JavaScript, Тестирование веб-сервисов] Эффективное тестирование верстки
- [Open source, PostgreSQL, Администрирование баз данных] Знакомство с pg_probackup. Первая часть
- [Разработка веб-сайтов] [HTML, CSS, JavaScript] Знакомство с Rome — компилятор, сборщик, линтер, тесты в одном флаконе
- [Алгоритмы, Node.JS, API, Финансы в IT] Поиски фундаментальных данных для акций через API Financial Modeling Prep
- [*nix, Информационная безопасность, Облачные сервисы, Серверное администрирование, Сетевые технологии] Как мы выбирали VPN-протокол и сервер настраивали
- [API, JavaScript, Финансы в IT] Отслеживаем состояние своего портфеля у брокера «Тинькофф Инвестиции» через Google Таблицы
- [GitHub, LaTeX] GitHub Actions и LaTeX: поднимаем, заливаем
- [Java] Очередь отложенных событий delayedQueue
- [IT-инфраструктура, Kubernetes, Openshift] Kubernetes на собственной инфраструктуре: «за» и «против» приватных облаков
- [Информационная безопасность, Виртуализация, Финансы в IT] Как подружить ГОСТ Р 57580 и контейнерную виртуализацию. Ответ Центробанка (и наши соображения на этот счет)
Теги для поиска: #_postgresql, #_kotlin, #_mikroservisy (Микросервисы), #_kubernetes, #_teamcity, #_kubernetes, #_ci/cd, #_spring, #_kotlin, #_liquibase, #_docker, #_postgresql, #_java, #_dao, #_testing, #_blog_kompanii_qiwi (
Блог компании QIWI
), #_postgresql, #_kotlin, #_mikroservisy (
Микросервисы
), #_kubernetes
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 23-Ноя 02:06
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
На дворе 2020 год и фоновым шумом вы уже привыкли слышать: «Кубернетес — это ответ!», «Микросервисы!», «Сервис меш!», «Сесурити полиси!». Все вокруг бегут в светлое будущее. Подходы в том, что касается баз данных, в нашей компании более консервативны, чем в прикладных приложениях. Крутится база данных у нас не в кубернетесе, а на железе или в виртуалке. Для изменений базы данных процессинга платежных сервисов у нас есть устоявшийся процесс, который включает в себя множество автоматических проверок, большое ревью и релиз с участием DBA. Количество проверок и привлекаемых людей в этом случае негативно влияет на time-to-market. С другой стороны, он отлажен и позволяет надежно вносить изменения в продакшен, минимизируя вероятность что-то сломать. А если что-то сломалось, то нужные люди уже включены в процесс починки. Этот подход делает работу основного сервиса компании стабильнее. Большинство новых реляционных баз данных для микросервисов мы заводим на PostgreSQL. Отлаженный процесс для Oracle хоть и надёжный, но несет с собой избыточную сложность для маленьких БД. Тащить тяжёлые процессы из прошлого в светлое будущее никто не хочет. Проработкой процесса для светлого будущего заранее никто не занялся. В итоге получили отсутствие стандарта и разножопицу. оригинал Если хотите узнать, к каким проблемам это привело и как мы их порешали, — добро пожаловать под кат. Проблемы, которые мы решали Нет единых стандартов версионирования В лучшем случае это DDL SQL-файлы, которые лежат где-то в директории db в репозитории с микросервисом. Совсем плохо, если это просто текущее состояние БД, разное на тесте и на проде, и эталонных скриптов схемы БД нет. В ходе отладки ушатываем тестовую базу «Я сейчас немного тестовую БД пошатаю, не пугайтесь там» — и пошел отлаживать на тестовой базе данных только что написанный код изменения схемы. Иногда долго, и всё это время тестовый контур не работает. При этом может поломаться тестовый контур в той части, где другие микросервисы взаимодействуют с микросервисом, чью базу ушатал разработчик. Методы DAO не покрываются тестами, не проверяются в CI При разработке и отладке методы DAO вызываются через дергание за внешние ручки несколькими слоями выше. Это подвергает проверке целые сценарии бизнес-логики вместо конкретного взаимодействия микросервиса и базы данных. Гарантии, что ничего не развалится в будущем, нет. Страдает качество и поддерживаемость микросервиса. Неизоморфность сред Если в тестовый и продакшен контуры изменения поставляются по-разному, то нельзя быть уверенным, что оно будет работать одинаково. Особенно когда на тесте по факту проводится разработка и отладка. Объекты на тесте могут быть созданы из-под учетки разработчика или приложения. Гранты накидываются как попало, обычно grant all privileges. Гранты приложению выдаются по принципу «вижу ошибку в логе — даю грант». Часто при релизе забывают про гранты. Иногда после релиза смок-тестирование не покрывает всю новую функциональность и отсутствие гранта выстреливает не сразу. Тяжелый и ломучий процесс наката в продакшен Накат в прод сделали ручным, но по аналогии с процессом для Oracle, через согласование DBA, релиз-менеджеров и накат релиз-инженерами. Это замедляет релиз. А в случае проблем увеличивает даунтайм, усложняя доступ разработчика к БД. Скрипты exec.sql и rollback.sql часто не проверялись на тесте, потому что стандарта патчсетирования для не-Oracle нет, а на тест катилось как попало. Поэтому бывает такое, что в некритичные сервисы разработчики катят изменения без этого процесса вообще. Как можно делать, чтобы было хорошо Отладка на локальной БД в докер-контейнере Для кого-то могут показаться очевидными вообще все технические решения, описанные в статье. Но почему-то из года в год я вижу людей, которые с энтузиазмом наступают на одни и те же грабли. Вот вы же не лезете на тестовый сервер по ssh, чтобы писать и дебажить код приложения? Я считаю, что разрабатывать и отлаживать код базы данных на тестовом инстансе БД — так же абсурдно. Есть исключения, бывает, что поднять локально базу данных очень сложно. Но обычно, если мы говорим о чем-то легковесном и не-легаси, то поднять локально базу и накатить на нее последовательно все миграции не составляет большого труда. Взамен вы получите стабильный инстанс под боком, который не ушатает другой разработчик, до которого не пропадут доступы и на котором вы имеете нужные для разработки права. Приведу пример, насколько просто поднять локально БД: Пишем двухстрочный Dockerfile: FROM postgres:12.3
ADD init.sql /docker-entrypoint-initdb.d/ В init.sql делаем «чистую» БД, которую рассчитываем получить и на тесте, и в проде. Она должна содержать:
Пример init.sqlSPLcreate role my_awesome_service
with login password *** NOSUPERUSER inherit CREATEDB CREATEROLE NOREPLICATION; create tablespace my_awesome_service owner my_awesome_service location '/u01/postgres/my_awesome_service_data'; create schema my_awesome_service authorization my_awesome_service; grant all on schema my_awesome_service to my_awesome_service; grant usage on schema my_awesome_service to my_awesome_service; alter role my_awesome_service set search_path to my_awesome_service,pg_catalog, public; create user my_awesome_service_app with LOGIN password *** NOSUPERUSER inherit NOREPLICATION; grant usage on schema my_awesome_service to my_awesome_service_app; create extension if not exists "uuid-ossp"; Для удобства можно добавить в Makefile таску db, которая (пере)запустит контейнер с базой и оттопырит порт для соединения: db:
docker container rm -f my_awesome_service_db || true docker build -t my_awesome_service_db docker/db/. docker run -d --name my_awesome_service_db -p 5433:5432 my_awesome_service_db Версионирование changeset‘ов с помощью чего-то стандартного для индустрии Тоже выглядит очевидно: нужно писать миграции и содержать их в системе контроля версий. Но очень часто я вижу «голые» sql-скрипты, без какой-либо обвязки. И это значит, что нет никакого контроля наката и отката, кем, что и когда было накачено. Нет даже гарантии, что ваши SQL-скрипты могут быть выполнены на тестовой и продовой БД, так как ее структура могла измениться. В общем, нужен контроль. Системы миграции как раз про контроль. Не будем вдаваться в сравнение разных систем версионирования схем БД. FlyWay vs Liquibase — не тема этой статьи. Мы выбрали Liquibase. Мы версионируем:
Запуская и отлаживая микросервис на локальной БД, разработчик столкнется с необходимостью позаботиться о грантах. Единственный легальный способ для него — завести DCL-скрипт в ченджсет. Это гарантирует нам, что гранты доедут до прода. Пример sql-патчсетаSPL0_ddl.sql:
create table my_awesome_service.ref_customer_type
( customer_type_code varchar not null, customer_type_description varchar not null, constraint ref_customer_type_pk primary key (customer_type_code) ); alter table my_awesome_service.ref_customer_type add constraint customer_type_code_ck check ( (customer_type_code)::text = upper((customer_type_code)::text) ); 1_dcl.sql: grant select on all tables in schema my_awesome_service to ru_svc_qw_my_awesome_service_app;
grant insert, update on my_awesome_service.some_entity to ru_svc_qw_my_awesome_service_app; 2_dml_refs.sql: insert into my_awesome_service.ref_customer_type (customer_type_code, customer_type_description)
values ('INDIVIDUAL', 'Физ. лицо'); insert into my_awesome_service.ref_customer_type (customer_type_code, customer_type_description) values ('LEGAL_ENTITY', 'Юр. лицо'); insert into my_awesome_service.ref_customer_type (customer_type_code, customer_type_description) values ('FOREIGN_AGENCY', 'Иностранное юр. лицо'); Fixtures. Данные для тестов или отладки идут отдельным ченжсетом с контекстом dev 3_dml_dev.sql: insert into my_awesome_service.some_entity_state (state_type_code, state_data, some_entity_id)
values ('BINDING_IN_PROGRESS', '{}', 1); rollback.sql: drop table my_awesome_service.ref_customer_type;
Пример changeset.yamlSPLdatabaseChangeLog:
- changeSet: id: 1 author: "mr.awesome" changes: - sqlFile: path: db/changesets/001_init/0_ddl.sql - sqlFile: path: db/changesets/001_init/1_dcl.sql - sqlFile: path: db/changesets/001_init/2_dml_refs.sql rollback: sqlFile: path: db/changesets/001_init/rollback.sql - changeSet: id: 2 author: "mr.awesome" context: dev changes: - sqlFile: path: db/changesets/001_init/3_dml_dev.sql Liquibase создает на БД таблицу databasechangelog, где отмечает накаченные ченджсеты. Автоматически вычисляет, сколько ченджсетов нужно докатить до БД. Есть maven и gradle plugin с возможностью сгенерировать из нескольких ченджсетов скрипт, который нужно докатить до БД. Интеграция системы миграций БД в фазу запуска приложения Здесь мог бы быть любой адаптер системы контроля миграций и фреймворка, на котором построено ваше приложение. Со многими фреймворками он идёт в комплекте с ORM. Например, Ruby-On-Rails, Yii2, Nest.JS. Этот механизм нужен, чтобы катить миграции при старте контекста приложения. Например:
Если не накатываются — приложение не стартует. Rolling update не убивает старые поды. В нашем стеке JVM + Spring, и мы не используем ORM. Поэтому нам потребовалась интеграция Spring-Liquibase. У нас в компании есть важное требование безопасности: пользователь приложения должен иметь ограниченный набор грантов и точно не должен иметь доступ уровня владельца схемы. С помощью Spring-Liquibase есть возможность катить миграции от имени пользователя-владельца схемы. При этом пул соединений прикладного уровня приложения не имеет доступа к DataSource'у Liquibase. Поэтому приложение не получит доступ из-под пользователя-владельца схемы. Пример application-testing.yamlSPLspring:
liquibase: enabled: true database-change-log-lock-table: "databasechangeloglock" database-change-log-table: "databasechangelog" user: ${secret.liquibase.user:} password: ${secret.liquibase.password:} url: "jdbc:postgresql://my.test.db:5432/my_awesome_service?currentSchema=my_awesome_service" DAO тесты на CI-этапе verify В нашей компании есть такой CI-этап — verify. На этом этапе происходит проверка изменений на соответствие внутренним стандартам качества. Для микросервисов это обычно прогон линтера для проверки кодстайла и на наличие багов, прогон unit-тестов и запуск приложения с поднятием контекста. Теперь на этапе verify можно проверить миграции БД и взаимодействие DAO-слоя приложения с БД. Поднятие контейнера с БД и накат патчсетов увеличивает время старта Spring-контекста на 1,5-10 сек, в зависимости от мощности рабочей машины и количества патчсетов. Это не совсем unit-тесты, это тесты интеграции DAO-слоя приложения с базой данных. Называя БД частью микросервиса, мы говорим, что это тестирование интеграции двух частей одного микросервиса. Без внешних зависимостей. Таким образом эти тесты стабильны и могут выполняться на этапе verify. Они фиксируют контракт микросервиса и БД, обеспечивая уверенность при будущих доработках. А еще это удобный способ отладки DAO. Вместо того, чтобы вызывать RestController, имитируя поведения пользователя в каком-то бизнес-сценарии, сразу вызываем DAO с нужными аргументами. Пример DAO-тестаSPL@Test
@Transactional @Rollback fun `create cheque positive flow`() { jdbcTemplate.update( "insert into my_awesome_service.some_entity(inn, registration_source_code)" + "values (:inn, 'QIWICOM') returning some_entity_id", MapSqlParameterSource().addValue("inn", "526317984689") ) val insertedCheque = chequeDao.addCheque(cheque) val resultCheque = jdbcTemplate.queryForObject( "select cheque_id from my_awesome_service.cheque " + "order by cheque_id desc limit 1", MapSqlParameterSource(), Long::class.java ) Assert.assertTrue(insertedCheque.isRight()) Assert.assertEquals(insertedCheque, Right(resultCheque)) } Есть две сопутствующие задачи для прогона этих тестов в пайплайне на verify:
Эти две задачи решает библиотека TestContainers. Она использует существующий докер образ для поднятия контейнера с базой данных в состоянии init.sql. Пример использования TestContainersSPL@TestConfiguration
public class DatabaseConfiguration { @Bean GenericContainer postgreSQLContainer() { GenericContainer container = new GenericContainer("my_awesome_service_db") .withExposedPorts(5432); container.start(); return container; } @Bean @Primary public DataSource onlineDbPoolDataSource(GenericContainer postgreSQLContainer) { return DataSourceBuilder.create() .driverClassName("org.postgresql.Driver") .url("jdbc:postgresql://localhost:" + postgreSQLContainer.getMappedPort(5432) + "/postgres") .username("my_awesome_service_app") .password("my_awesome_service_app_pwd") .build(); } @Bean @LiquibaseDataSource public DataSource liquibaseDataSource(GenericContainer postgreSQLContainer) { return DataSourceBuilder.create() .driverClassName("org.postgresql.Driver") .url("jdbc:postgresql://localhost:" + postgreSQLContainer.getMappedPort(5432) + "/postgres") .username("my_awesome_service") .password("my_awesome_service_app_pwd") .build(); } С разработкой и отладкой разобрались. Теперь нужно доставить изменения схемы БД в продакшен. Kubernetes — это ответ! А какой был ваш вопрос? Итак, вам надо автоматизировать какой-то CI/CD-процесс. У нас есть обкатанный подход на тимсити. Казалось бы, где тут повод для еще одной статьи? А повод есть. Кроме обкатанного подхода, есть и поднадоевшие проблемки большой компании.
И во всем этом «по старинке» проблема — все бегут в светлое будущее, а поддержка легаси… ну вы знаете. Работает и ладно. Не работает — займемся потом. Когда-нибудь. Не сегодня. Допустим, вы уже одной ногой по колено в светлом будущем и кубернетес-инфраструктура у вас уже есть. Есть даже возможность сгенерировать еще один микросервис, который сразу заведется в этой инфраструктуре, подхватит нужный конфиг и секреты, будет иметь нужные доступы, зарегистрируется в service mesh инфраструктуре. И всё это счастье может получить рядовой разработчик, без привлечения человека с ролью *OPS. Вспоминаем, что в кубернетесе есть тип ворклоада Job, как раз предназначенный для каких-то сервисных работ. Ну и погнали делать приложение на Kotlin+Spring-Liquibase, стараясь максимально переиспользовать существующую в компании инфраструктуру для микросервисов на JVM в кубере. Переиспользуем следующие аспекты:
Получаем такой пайплайн: Кликабельно Теперь мы имеем:
Знаю, что для многих это всё не откровение. Но раз уж вы дочитали, будем рады рассказу о вашем опыте в комментах =) =========== Источник: habr.com =========== Похожие новости:
Блог компании QIWI ), #_postgresql, #_kotlin, #_mikroservisy ( Микросервисы ), #_kubernetes |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 23-Ноя 02:06
Часовой пояс: UTC + 5