Catch the Latest

Catch the Latest


Alexandr Kruchkov

Ах сколько же славных историй, ах сколько же копий было разбито об использовании великолепного image:latest.

В этой старой истории сразу несколько факапов, но основной это latest.


Просыпаюсь утром, иду на работу, а там прод лежит.

Ну как прод - вся часть, что отвечает за realtime данные.

Мне необходимо сделать небольшое отступление и рассказать, что я имею в виду под realtime data.

---------------------------------------------

Итак, Realtime.

На проекте есть база данных MySQL.

Есть backend, есть frontend.

Поступила пока ещё абстрактная задача "выдавать куда-то в фронт все те действия, что происходят в базе данных, чтобы на фронте были актуальные данные".

Почитал в интернете что это такое, сходу принял решение через MySQL binary file.

https://dev.mysql.com/doc/refman/8.4/en/binary-log.html

Binary-log в MySQL — это файл, содержащий записи об изменениях в базе данных, таких как создание таблиц или модификация данных. Он используется для трёх основных целей:

  1. Репликация: Бинарный лог на сервере-источнике фиксирует изменения данных, которые передаются на реплики для воспроизведения тех же изменений.
  2. Восстановление данных: после восстановления из резервной копии бинарный лог позволяет применить изменения, сделанные после создания копии, для актуализации базы.
  3. Наш случай

Основные характеристики бинлога:

  • Логирует изменения данных и потенциально изменяющие запросы (например, DELETE, даже если он не затронул строки).
  • Не записывает запросы вроде SELECT или SHOW, не изменяющие данные.
  • Поддерживает шифрование для защиты данных (через binlog_encryption=ON). Для параноиков.
  • Формат лога (строковый, на основе запросов или смешанный) зависит от версии MySQL.
  • Файлы бинарного лога нумеруются и сопровождаются индексным файлом (.index), который отслеживает их порядок.
  • Лог устойчив к сбоям: записываются только завершённые события или транзакции.

Ок, включаем бинари лог через опцию binlog_format=ROW
Хорошо, лог включили, а дальше что? Дальше нам надо научится забирать эти данные, да и не все.

На помощь нам приходит мега мощный проект Debezium.

Debezium — это открытая платформа для захвата изменений данных (Change Data Capture, CDC), построенная на Apache Kafka. Она отслеживает изменения в базах данных (MySQL, PostgreSQL, MongoDB и др.) на уровне строк и публикует их как поток событий в Kafka. Основные особенности:

  • Назначение: Позволяет приложениям реагировать на вставки, обновления и удаления в реальном времени, обеспечивая синхронизацию данных, обновление кэшей, индексов или аналитических систем.
  • Надёжность: События сохраняются в упорядоченном виде, приложения не пропустят изменения даже при сбоях.
  • Развертывание: Используется через Kafka Connect, Debezium Server или как библиотека в приложениях.
  • Поддержка: Работает с различными СУБД, включая MySQL (через бинарный лог), PostgreSQL (через логическую репликацию) и другие.

Debezium упрощает интеграцию микросервисов и обработку данных в реальном времени, минимизируя задержки и нагрузку на источник

Пилю Debezium как новый микросервис с отдельным гит репозиторием.

Dockerfile был примерно такого варианта

Дебезиум настраивается через вызовы в его API.

То есть вся настройка/конфигурация/обновление только через POST отправку с JSON файлом конфигурации.

На момент принятий решений я был весьма неопытен, самонадеян и решил сделать так.

Можно ругать, можно хихикать или показывать пальцем. Я сделал это так.

Сперва в билд стейдже я скачиваю шаблон конфигурации configuration.json

"table.include.list" содержал в себе список ровно тех таблиц MySQL, изменения которых попросил бизнес. 1,2,3,4 - название таблиц в БД.

Все остальные параметры в целом понятны любому - логин, пароль, таймауты, адреса и так далее.

Затем, при помощи envsubst генерирую готовый файл конфигурации (пароли, логины, URL etc.). Затем runtime image где мы подкладываем этот файл в нужную директорию.

Докер билд собирается, конфиг на месте, как автоматизировать дальше?

Как заставить кого-то или чего-то отправить POST запрос при старте POD-а?

Я выбрал самую ненадежную конструкцию, но она на момент реализации работала и выдерживала все мои тесты.

Привет, меня зовут Алекс и я на прод выбрал кубернетис постстарт хук.

https://kubernetes.io/docs/tasks/configure-pod-container/attach-handler-lifecycle-event/

В общем в values файле для постстарта дебезиума у меня было следующее:

lifecycle:

  postStart:

    exec:

      command:

        - /bin/sh

        - -c

        - 'sleep 30;until [ $(eval curl -o -I -L -s -w "%{http_code}" http://localhost:8083/connectors) -eq 200 ]; do echo "Waiting for ready probe ";sleep 10;  done && curl -X PUT -H "Content-Type: application/json" -H "Accept:application/json" -d @connector.json http://localhost:8083/connectors/mysql-connector/config'

Сперва poststart hook проверяет поднялся ли сам дебезиум(иначе POST запрос никто не обработает), и когда он оживает, то в него посылает POST запрос с готовым файлом конфигурации.

Вы не поверите, но это работало идеально!

Это всё работало и выдерживала любые (мои) тесты!

Дебезиум стартует, создает необходимые 3 топика для собственной работы и создаёт топики с префиксом БД для каждой таблицы. Начинает читать бинарилог и все события кидает мессаджами в Kafka.
Затем бизнес приложение при помощи https://watermill.io/ и кастомного кода читает топики кафка и отправляет что надо во frontend.


Давайте ещё раз опишу как всё это работает:

  • MySQL все UPDATE/DELETE и прочие запросы на изменение базы данных скидывает в binarylog
  • В репозитории Debezium при докербилде билде формируется файл конфигурации для хука
  • При старте POD-а poststart hook ждёт старта Debezium API, затем кидает в него файл конфигурации, который создает MySQL connector для того, чтобы подключится к MySQL серверу и скачать с него binary data
  • Сам Debezium после создания/пересоздания MySQL connector создаёт в kafka 3 топика для собственных нужд (оффсеты по таблицам, конфиг и прочее). Так же он создаёт N топиков в Kafka для каждой таблицы в конфигурации коннектора. Начинает читать binary log и отправлять сообщения в Kafka
  • Бизнес приложение читает топики kafka, использует какой-то там фреймоворк https://watermill.io/ и отправляет в frontend.

Фича заработала!!!

Бизнес ликует, разработчики в восторге, клиенты пищат от восторга!

Все благодарят Алекса, ах какой он инженер (нет, я это сам выдумал).

---------------------------------------------
Ах да, вернёмся к упавшему проду, ведь про realtime я вам всё рассказал.

Вся эта связка с дебезиум и кафкой работала несколько месяцев без сбоя.

Но несколько дней назад вышел новый релиз Debezium. Сейчас уже не помню, но вроде была версия 1.9.0, вышла 2.0.0 с брейкинг чейнджем. Как сладок запах брейкинг чейнджей на проде.
В файле конфигурации часть параметров стали депрекейтед, а часть параметров стали обязательными. У меня само собой не было новых обязательных параметров, Debezium не стартовал. Самом собой и realtime данных не было для клиентов.

Почему же так случилось, спросите вы?

  • тег latest в Dockerfile. Он скачал новый имадж(повторюсь, я не помню версии, ну допустим 2.0.0 вместо 1.9.0)
  • PostStart hook. Даже по словам авторов кубернетиса он не гарантирует 100% выполнения и это значит, что критически важный для меня и сервиса API вызов не вызывался бы и helm/argocd посчитали, что релиз задеплоен успешно
  • отсутствие health check на Debezium (кстати не знаю почему его не сделал, я к тому времени говна поел с probes, не знаю почему не было их)
  • добавление новой таблицы разработчиком в файл-шаблон конфигурации.

Как это было, пока я сладко спал:

  • разработчик добавил новую таблицу в файл шаблона, получил аппрув, смержил
  • докер имадж пересобрался с новой версией дебезиума, выкатился на прод
  • реалтайм дата упала на всём проде
  • разработчик сделал git revert своего pull request, все снова пересобралось, но это не решило проблему - дебезиум был уже новый при любой сборке
  • пайплайн в тот момент не подразумевал шага helm rollback да и разработчик ну ничегошеньки не знал про какие-то там хелмы. Он привык в случае факапа делать реверт пулл реквеста и всё.

Для восстановления сервиса я просто скачал докеримадж из реджистри, посмотрел какая там была версия, в dockerfile заменил latest тег на номер ранее работающей версии и после пересборки всё заработало.

---------------------------------------------

Вот такая вот лонгстори со счастливым концом.

Report Page