Catch the Latest

Ах сколько же славных историй, ах сколько же копий было разбито об использовании великолепного 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 — это файл, содержащий записи об изменениях в базе данных, таких как создание таблиц или модификация данных. Он используется для трёх основных целей:
- Репликация: Бинарный лог на сервере-источнике фиксирует изменения данных, которые передаются на реплики для воспроизведения тех же изменений.
- Восстановление данных: после восстановления из резервной копии бинарный лог позволяет применить изменения, сделанные после создания копии, для актуализации базы.
- Наш случай
Основные характеристики бинлога:
- Логирует изменения данных и потенциально изменяющие запросы (например, 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 тег на номер ранее работающей версии и после пересборки всё заработало.
---------------------------------------------
Вот такая вот лонгстори со счастливым концом.