Всем привет.
Итак, небольшая предыстория.
Была идея сделать автоответчик в Instagram с поддержкой, кхм… ИИ. Да-да, знаю, как надоел вам этот ИИ. Задача была такая: когда пишет клиент, программа берёт сообщение, отправляет его в OpenAI с заранее заданным промптом и отвечает уже в Instagram.
Конкретно в подробности вникать не будем, как устроена программа. В ходе работы мы поняли, что нужна некая среда, где будут крутиться аккаунты Instagram. А ещё лучше, если эта среда будет разворачиваться сама. Пользователь настроил аккаунт, нажал кнопочку «Старт» — и аккаунт начал читать и отвечать на директ-сообщения.
Итак, для интерфейса взяли Telegram (а почему бы и нет).
В качестве передачи данных от пользователя — Redis.
Далее включается RTF (RedTailFox)
Первым принимает данные у нас manager. Ну как принимает — вытягивает из Redis. Примерно такие данные прилетают в него:
task = {
"command": "start",
"slot_id": 1,
"config": {
"job": {
"name": "demo_worker",
"interval": 300
}
}
}
Это уже демонстрационный JSON-файл. Который мы использовали в автоответчике — не покажу 🙂
Суть какая:
«command» — параметр, который задаёт логику программы. В данном случае — «start».
«slot_id» — это условный ID (воркер), который будет лежать в Docker. Какой передашь — такой и запишется.
«config» — это конфигурация, которую будет брать воркер для работы. Мы передавали там данные для аккаунта, промпт и так далее.
Далее менеджер смотрит, есть ли контейнеры. Контейнеры хранятся и локально в словаре, и в Redis.
Ситуация 1: контейнеров нет
Менеджер создаёт контейнер (образ задаём в переменной), передаёт в него переменные, Redis и канал, который будет слушать Docker (брать команды от менеджера).
Менеджер создал контейнер, регистрирует его у себя, сохраняет канал, который слушает контейнер, и какие воркеры там сидят (мы поставили ограничение — 10 воркеров на 1 контейнер). После этого пушит туда те же данные, что и получил.
Ситуация 2: контейнер уже есть
Всё просто: менеджер смотрит, в каком контейнере есть место, проверяет, какой канал он слушает, и пушит туда те же данные, что и получил.
Контейнер получает данные от менеджера. Команда «start» создаёт новый поток с логикой воркера и отчитывается менеджеру, что воркер запущен. Менеджер регистрирует воркера у себя и пушит в канал БД, что воркер работает.
Далее воркер, выполняя работу, пишет статусы и метрики в свой экземпляр (у нас он просто менял переменные в своём классе).
Heartbeat (сердцебиение) снимает данные с воркера и метрики, и если изменилась версия, пушит их в свой канал (метрики). Далее отправляет в канал сообщение:
{
"container": "rtf_worker_1",
"ts": 1678901234,
"slots": [
{
"slot_id": 1,
"running": true,
"status": "IDLE",
"last_active": 1678901200
}
]
}
Далее включается монитор
Что это за зверь? Этот лев берёт данные из канала Redis, которые прислал heartbeat, и проверяет ts (последнее время активности воркера).
Если оно больше положенного (например, 5 минут — время задаётся в переменной), он его перезапускает? Нет. Кладёт в словарь и помечает как подозрительный. Как только 3 раза он будет замечен в «криминале», монитор пушит в канал, который слушает менеджер, команду на перезапуск слота:
json.dumps({
"command": "restart_slot",
"slot_id": slot_id,
"container_name": container,
"initiated_by": "monitor"
})
Далее монитор проверяет, какие контейнеры вообще должны работать. Если хотя бы один не прислал отчёт:
json.dumps({
"command": "restart_container",
"slot_id": slot_id,
"container_name": container,
"initiated_by": "monitor"
})
Вот и вся логика работы. Возможно, кому-то пригодится — штука вроде полезная.
Хотел вставить картинку логики работы, но не нашёл как — ну и ладно.
Вот ссылочка на репозиторий:
https://github.com/Dark-F0X/RedTailFox
(Возможно, поставите звёздочку 🙂)
А если зайдёт — расскажем, как сделали на основе этого же «кибернетиса» другой проект.
Перемещено hobbit из development