Каждому твоему разработчику нужна база, похожая на продакшн, но она очень тяжелая, чтобы каждое утро выкачивать её к разработчику в его локальную среду. Теперь есть решение - поможет aldaas.

базы с гигабайтами данных как сервис
базы с гигабайтами данных как сервис

Содержание
- Предисловие
- Как работает aldaas
- Настройка инфраструктуры для aldaas
- Вывод

Предисловие

Придя со стартапа на 4-8 человек в команду побольше, я начал погружаться в CI/CD для монорепы, в каждой ветке мы хотели поднимать все окружение полностью с помощью docker, около 15 сервисов, очереди и... БД. Полагаю почти у всех такая проблема, всегда есть боевая база данных и вторая для всех-всех-всех.

Картина маслом
Картина маслом

Сначала, конечно, есть предложение, а давайте «пустые» БД поднимать и накатывать на них схему, и вот, пожалуйста, можно уже миграции катать, и все вроде хорошо.

Решение на коленке
Решение на коленке

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

И тут ты начинаешь искать готовые решения или изобретать велосипеды. Не очень помню удалось ли мне что-то нагуглить в 2018 году, но, видимо, ничего подходящего не обнаружив, я, вооруженный rancher 1.x, собрал свой saas с docker и zfs, решение можно было масштабировать добавлением нод в кластер rancher, соединение было по tcp напрямую в открытый порт контейнера, поэтому все это нужно было разворачивать в закрытом контуре в одной сети со станциями разработчиков и ранерами для CI/CD.

Решение тогда нас очень ускорило, каждый разработчик мог ломать свою базу данных и никак не влиять на соседей, миграции стали работать как должны. В один день мы поймали очень долгую неоптимизированную миграцию, и наш DBA (дядя Женя, который круче всех умел в sql на тот момент) помог оптимизировать её, и вместо 15 минут на машине разработки и в CI/CD, она прошла меньше минуты. При этом данные в saas базах уже были пожаты в 10 раз, к тому же мы выделяли по 1 гигабайту для каждой saas БД, специально отлавливая медленные запросы, на боевой БД миграция могла бы обрабатываться несколько часов, катастрофа предотвращена в зачатке (аплодисменты). Ну и конечно было удобно, что базы можно было выкидывать и сразу брать новые, каждое утро новый слепок с продакшн был доступен всем желающим, а кто хочет, может играться со своей копией хоть целый месяц, zfs тогда позволял оптимально использовать пространство на воркерах. Ну и самое важное, БД размером в 10+ гигов выкачивать к себе не нужно, на стороне клиента только легкий прокси сервер.

После я ушел в другую компанию, а сервисом еще долгое время пользовались на старом месте работы (а может и сейчас он работает), сегодня в 2022-24 я пришел в новую команду и опять наблюдаю проблему с единственной БД для разработки и staging окружения, опять проблемы миграции и аварий с sql. Но уже с новыми знаниями kubernetes и новым уровнем лени я написал aldaas - App with Large Data aa service. ZFS заменил на ceph c глубокой интеграцией в kubernetes благодаря стараниям rook.io, серверную часть писать не хотелось и я сделал ставку на argo workflows+events. Про настройку rook.io и argo в этой статье я тоже кратко расскажу. Также хотелось бы не зависеть от различных VPN и доставлять доступы до базы в любую инфраструктуру или окружение, не сильно беспокоясь об утечках. На глаза попался tcp-over-websocket, да, есть проблема в разрыве соединений, но я подумал - это не баг, а фича, приложение должно нормально обрабатывать реконнект к БД. Так, имея единую точку входа через nginx по websocket’ам, можно коннектиться к разным БД по разным path в урле.

Как работает aldaas

  1. Подготовка БД снапшотов

argo events слушает minio/s3 сервер на наличие новых бекапов, как только новый бекап загрузился argo event запускает workflow, а в нем:

  • создаётся новый диск в Ceph rbd

  • поднимается чистая БД с примонтированым диском ceph

  • в БД загружается бекап (самая долгая задача)

  • останавливается БД

  • создается Ceph snapshot от диска силами kubernetes volume snapshot

  • потом из снапшота снова поднимается диск с проверочной БД, и выполняется тестовая команда

  • останавливается проверочная БД, и удаляется снапшот диск

  • на снапшот ставится лейбл status=ready

  1. Удаление старых снапшотов

Argo по крону удаляет все старые снапшоты (и их workflow) с лейблом status=ready, кроме самого нового (команда в одну строку)

  1. Заказ БД для клиентов

есть argo workflow, который как раз запускают клиенты

  • ищет самый свежий снапшот с лейблом status=ready

  • создаёт из снапшота диск и поднимает pod с этим диском, в котором бд, прокси и TTL киллер (для выключения простаивающей БД)

  • поднимается сервис и ingress с уникальным path (и токеном)

На клиентской стороне простой скрипт, который либо проверяет есть ли что-то на локальной FS для подключения к уже запущенной БД (с прошлого раза, если вы, например, остановили контейнер по каким-то причинам), либо заказывает новую, запуская выше упомянутый клиентский workflow, также складывая на диск кешированный ID для БД. Также можно задать TTL на клиенте для БД, по умолчанию 300 секунд. Если остановить или удалить контейнер на клиенте то в aldaas вашу БД удалит TTL киллер.

Рекордное время поднятия БД - 30 секунд, ну тут вопрос к настройке argo. Конечно можно и без него, и время сократится до 5-10 секунд, но я лентяй и не хочу писать свою серверную часть.

Настройка инфраструктуры для aldaas

Для начала посоветую развернуть всё на чистом стенде, а то вдруг заденете условный ceph или настройки текущего argo.

Нам нужен kubernetes с VolumeSnapshot GA. Я использую rancher и через rke2 поднял 1.26.10. VolumeSnapshot понадобится как раз для того, чтобы выполнять магию поднятия слепков БД.

Сильно вдаваться в подробности с настройкой и установкой rook ceph не буду, просто скажу что нам нужен storage class и snapshot storage class в kubernetes, вместо ceph можно использовать любой другой storage provider, лишь бы снапшоты поднимались быстро, оба класса нужно будет передать aldaas.

Всю логику и серверную часть я оставил на Argo, сам argo server нужно вытащить наружу через ingress и поставить на него авторизацию для argo cli В helm лежит rbac, который нужно будет добавить на сервис аккаунт Argo workflow, потому что нам нужны будут права на взаимодействия с volumes и snapshots, jobs (из коробки в Argo workflow их не хватает), и также уточнить имя сервис аккаунта (serviceAccountName), которым будет орудовать aldaas для поднятия клиентского workflow. Также не помешает заменить секретный токен (tunnel.token), который и обеспечит безопасность по веб сокетам, и ваш домен (domain), по которому клиенты будут резолвить aldaas базы. Также я попытался заложить установку нескольких aldaas в один namespace, для этого нужно для всех остальных установок отключить EventBus (EventBus.enabled)

Давайте разберем values.yaml. Тут у нас стоит преднастроенный postgres:latest, бекап будем грузить из postgres в формате .sql.gz

# SA сервис аккаунт, с которым будут стартoвать argo wf
serviceAccountName: argo-workflow
# версии kubectl, jq (внезапно)
kubectl: v1.27.9 # я велосипедист, версия и для контейнера, и для бинаря
jq: 1.7.1 # только для бинаря
# в пустом NS создается default argo event bus 
EventBus:
  enabled: true
# домен по которому клиенты будут подключаться к aldaas базам (может быть доменом от argo server) 
domain: 
# единственный докер имадж с argo cli, tcp-over-websockets
# и aldaas скриптом, но в нем только bash в 30 строк 
tunnel:
  image: ghcr.io/negashev/aldaas
  tag: main
  port: 8080
  # секурный токен для вашего aldaas инстанса
  token: 9fKLQhjGWrHZ3pOSQLKmWlUxoJXkCJqbFs8WeT3EEG8AwKEL4B8YavzXYOApVaXfi2ZVLx77YhsbVyDJTY0l8maSiYKEm6WDyopM
  ingress:
    # тут берем наш tls секрет для домена или включаем аннотацию
    tlsSecretName: ""
    annotations: 
      # kubernetes.io/tls-acme: "true"
# два k8s сторадж класса, чтобы магия работала
rook:
  storageClassName: ceph-block
  volumeSnapshotClassName: ceph-block
# это s3 конфигурация для argo events
s3:
  existingSecret: ""
  accesskey: ""
  secretkey: ""
  host: minio.server
  port: 80
  bucket: backup
  insecure: true
  prefix: spilo/acid-database/shasum/logical_backups
  suffix: .sql.gz
# Само приложение, из которого будут подниматься aldaas БДшки 
# в нем же будет и инициализация БД для загрузки бекапов
application:
  image: postgres
  tag: latest
  port: 5432
  # ну тут и размер тех самых дисков (и бекап и для aldaas - логично же, aldaas тупо будет из диска для бекапа)
  storage: 10Gi
  # папка, в которую мантится диск 
  mount: /var/lib/postgresql/data
  # вдруг ты аргументы добавить хочешь
  args:
  command:
  # понятное дело понадобятся конфигурации через ENV
  env:
    - name: POSTGRES_PASSWORD
      value: passw0rd
    - name: POSTGRES_USER
      value: aldaas
    - name: POSTGRES_DB
      value: my_db_name
  # адекватные ресурсы... тут уже зависит от ваших потребностей, node-autoscaler в помощь
  resources:
    requests:
      cpu: 100m
      memory: 1Gi
    limits:
      cpu: 1000m
      memory: 1Gi
# а это уже пода, которая будет накатывать наш бекап
# ALDAAS_HOST_DAEMON адрес важный, он берется как раз от нашей базы для наката бекапа, генерируется в wf
restore:
  args:
   - gunzip -c /backup.sql.gz | psql -h $ALDAAS_HOST_DAEMON -p 5432 -U $POSTGRES_USER -d $POSTGRES_DB
  command:
   - sh
   - -c
  env:
    - name: PGPASSWORD
      value: passw0rd
    - name: POSTGRES_USER
      value: aldaas
    - name: POSTGRES_DB
      value: my_db_name
  resources:
    requests:
      cpu: 100m
      memory: 1Gi
    limits:
      cpu: 1000m
      memory: 1Gi
# а тут мы уже в самом конце проверяем все ли в порядке с базой, поднятой из снапшота
# уже после бекапа имадж берется
check:
  args:
   - psql -h $ALDAAS_HOST_DAEMON -p 5432 -U $POSTGRES_USER -d $POSTGRES_DB -c "\l"
  command:
   - sh
   - -c

Поставить нужные значения, думаю, не сложно :) устанавливаем чарт в неймспейс aldaas

Получили примерно такой вывод, тут можно использовать клиент с этими настройками, но сначала нужно сделать первый снапшот
Получили примерно такой вывод, тут можно использовать клиент с этими настройками, но сначала нужно сделать первый снапшот

После заходим в argo server и наблюдаем в ивентах, кроне и шаблонах такие картины:

Сенсор на создание снапшота и сенсор на удаление клиентских БД по TTL
Сенсор на создание снапшота и сенсор на удаление клиентских БД по TTL
Крон задача на удаление устаревших снапшотов
Крон задача на удаление устаревших снапшотов
шаблон workflow, который будет тригериться из клиентского контейнера
шаблон workflow, который будет тригериться из клиентского контейнера

В идеальном мире) если мы настроили хелм правильно, можно загрузить бекап с любым именем в формате .sql.gz, и сенсор запустит workflow, который начнет готовить снапшот из бекапа базы данных

Тригернулся workflow
Тригернулся workflow

Если все прошло удачно, должна поднятся БД с диском, в неё загружен дамп, БД остановлена, с диска сделан снапшот, потом из снапшота сделан новый диск, и на нем снова поднята БД, в которую выполняется тестовая команда (тут уже сами сможете придумать что-то своё, типа селекта на проверку целостности данных или их актуальности).

Зеленый пайплайн, результатом которого будет ceph диск с данными, загруженными из бекапа, снапшот этого диска c лейблом status=ready
Зеленый пайплайн, результатом которого будет ceph диск с данными, загруженными из бекапа, снапшот этого диска c лейблом status=ready

А теперь, когда у нас все взлетело, можно попробовать запуститься на клиентской стороне

docker run -it \
-p 5432:5432 \
-e ALDAAS_PORT=5432 \
-e ALDAAS_NAME=aldaas-chart \
-e ALDAAS_TOKEN=9fKLQhjGWrHZ3pOSQLKmWlUxoJXkCJqbFs8WeT3EEG8AwKEL4B8YavzXYOApVaXfi2ZVLx77YhsbVyDJTY0l8maSiYKEm6WDyopM \
-e ARGO_SERVER=argo.negash.ru \
-e ARGO_HTTP1=true \
-e ARGO_TOKEN='[REDACTED]' \
-e ARGO_NAMESPACE=aldaas \
ghcr.io/negashev/aldaas:main

немного о переменных

ALDAAS_PROTOCOL - протокол по умолчанию wss
ALDAAS_NAME - имя нужной нам aldaas установки
ALDAAS_TOKEN - соответственно секурити
ALDAAS_PORT - порт БД, который проксируем из aldaas
ALDAAS_SERVER - по умолчанию равен ARGO_SERVER
ALDAAS_TTL - ttl для базы по умолчанию 300 секунд, если остановить прокси, то БД удалится через это время

остальные настройки ARGO_* для argo cli соответственно, ARGO_NAMESPACE - тот NS, в который поставили aldaas helm chart.

На выходе клиента получаем такие логи: argo cli заказал нам БД, a tcp-over-websockets запустил до неё прокси, все это произошло за 38 секунд. В самой БД у нас данных условно 10+ гигов, мы их не качали к себе на машину, а подняли удаленно

Запустился контейнер, и теперь можно попробовать к нему подключиться по 5432 порту
Запустился контейнер, и теперь можно попробовать к нему подключиться по 5432 порту

После того как увидели первое соединение по ws[s] - все отлично, это сервис пинга, чтобы ttl киллер не убил вашу БД, пока контейнер с прокси работает. Теперь, соответственно, сколько раз вы запустите такой контейнер, столько уникальных БД с данными и получите.

Можно выдать разработчикам, пусть не боятся дропнуть таблицу - если что-то пойдет не так, разработчик просто пересоздаст контейнер с прокси, и новый слепок продовой БД снова доступен, даже чинить ничего не надо. Поставьте aldaas прокси в CI/CD для тестов миграций, и вы не пропустите в прод ничего медленного или несогласованного!

Вывод

- ругаем
Это, конечно, железо, ceph скушает у вас ресурсов. Можно использовать другие провайдеры снапшотов, но нужны бенчмарки. Также aldaas не почистит ваши бекапы от критических данных - это вы должны сделать сами

- хвалим
Вы экономите много времени на точность ваших CI/CD тестов, у ваших разработчиков не болит голова от поcтоянных пересечений или аварий миграций, можно устраивать взрывы в БД и не бояться что-то сломать! Это kubernetes и ceph, значит можно горизонтально масштабироваться, можно использовать другие базы, например mysql, mariadb, clickhouse, cassanda и т. д.

Версия сырая, пулреквесты приветствуются

- надо бы helm прилизать
- переехать с tcp-over-websockets на что-то другое, с basic auth например или grpc
- убрать с клиента argo cli и дописать сервер для быстрого создания слепков БД
- сделать очереди и держать всегда готовыми 3-4 слепка БД для мгновенной выдачи клиенту (не ждать kubernetes)

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


  1. serhio_ssh
    04.09.2024 17:01

    Спасибо за статью) Так еще боль с этими окружениями и данными.
    У нас как раз используется общая для всех stage, но данные там как правило не равны prod. Иногда могут просить залить dump в stage из prod, чтобы обновить.

    Но все-таки в идеале нужна чистая БД + миграция схем, а далее уже seed накатывать в зависимости от нужд разработки или тестирования.
    Но тут нужно иметь эти самые seed под каждую БД/сервис и еще их поддерживать актуальными. Можно конечно что-то с генерацией подумать этих самых данных. Для тестирования особенно удобно.
    В этом случае будет самое быстрое развертывание и не затратно по ресурсам.

    Вариант же с backup и развертыванием всех таких БД затратный во всех смыслах и даже избыточен.