Масштабируемый Продакшн-реди Телеграм бот на Django

  • Tutorial

На Хабре очень много статей о том, как создать простейшего Телеграм бота с кнопками меню и логикой, есть инструкции, как это все задеплоить. В этой статье я расскажу, как делать ботов для продакшена, которыми смогут пользоваться сотни тысяч пользователей.

Всем привет! Меня зовут Даниил Охлопков и у меня есть рой телеграм ботов, которыми пользуются суммарно более 300 000 людей. Разрабатывая их на протяжении прошлого года, я пришел к стеку технологий и подходов, которые обеспечивают мне не только комфортную разработку, но и масштабируемость. В этой статье я поделюсь всеми накопленными знаниями.

Про мой опыт

В этом моменте мне хочется сказать, что все "знания" из этой статьи были добыты мной в боях, никто меня этому не учил - я просто гуглил все проблемы и искал решения на stackoverflow. Я считаю, что у меня есть чуйка на костыли и гов*нокод, именно она помогла мне открыть набор инструментов и технологий, упомянутый в статье. Я предполагаю, что все можно сделать проще и лучше, но сейчас я расскажу свой способ. Поделитесь в комментариях, как бы деплоили тг бота в прод!

Я выложил на GitHub пример проекта, который использует все упомянутые в статье подходы и технологии. Это шаблон телеграм бота, основанный на python-telegram-bot, Django, Celery, Postgres, Redis, Dokku, GitHub Actions и др. Надеюсь, будет полезно и интересно. Поехали

Почему Django?

Многие скажут: ведь Django - очень большой фреймворк! Зачем забивать гвозди микроскопом? У меня на это есть несколько аргументов:

  1. В Django есть поддержка баз данных, встроенная админка и огромная библиотека плагинов на все случаи жизни. Теория от YCombinator учит использовать готовые компоненты вместо написания своих с нуля, так как это позволит сильно сэкономить время на старте и не придется наступать на грабли, на которые уже кто-то наступал.

  2. Если какие-то модули не нужны сейчас, то они могут пригодится в будущем. Например, ваш бот стал популярным и появились фродеры: админка сильно упростит анализ данных системы и позволит расширить вашу команду "не программистами".

  3. Django проверена миллионами разработчиков. Написаны тысячи сайтов с документацией и примерами. Понятно как ее правильно деплоить в продакшн и масштабировать.

Каждый раз, разрабатывая Телеграм бота сложнее, чем hello-world, нужна база данных как минимум, чтобы хранить информацию о всех пользователях, которые пользуются ботом (да, на данный момент Telegram Bot API не выдает даже такую базовую статистику). Как только ботом начинают пользоваться десятки тысячи людей, понадобится как-то масштабировать систему - возможно, одного сервера не хватит.

Если логика вашего Телеграм бота сложна, вам понадобятся background tasks. Например, чтобы разослать тысячам пользователей сообщение с рекламой анонсом новых возможностей бота. В экосистеме Django такие задачи легко реализуются с помощью модуля Celery. В качестве его брокера я люблю использовать Redis, а для периодических задач модуль Django-celery-beat (появляется возможность через админку просто накликать, какую функцию по какому крону вызывать).

Телеграм бот в продакшене: особенности

Предвижу летящие тухлые помидоры, ведь я не знаток девопса и не эксперт в определениях. Поправьте меня в комментариях. Для меня в контексте телеграм ботов "продакшн" означает, что

  1. База данных должна быть нормальная (не текстовый фай, не sqlite, не in memory). Например, Postgres.

    1. Это обеспечит стабильность и уменьшит шансы, что все данные внезапно потеряются.

  2. Если что-то упало, чтобы поднялось само.

    1. Желательно при этом собирать ошибки, например, через Sentry.

  3. Настроен CI, который автоматизирует превращение нового кода в рабочий сервис.

    1. Я использую GitHub Actions, который заставляет Dokku сделать git pull и начать собирать проект, когда кто-то закоммитил в мастер. Подробнее об этом в конце статьи.

  4. Есть возможность легко масштабировать проект по необходимости. В частности - обработчики сообщений пользователей.

    1. Для этого polling не подойдет, только вебхуки - об этом прямо сейчас ⬇️.

Вебхуки

Существуют два способа работать с Telegram Bot API (да и вообще со всеми микросервисами): пулинг и вебхуки.

  1. Polling - это когда ваш скрипт периодически заходит на серверы Телеграмма и запрашивает новые события о том, как ваши пользовали провзаимодействовали с ботом.

  2. Webhook - это когда вы говорите серверам Телеги: присылай мне на мой URL события сам.

Первый способ сильно упрощает разработку ботов, в то время как второй требует поднятого веб-сервера, который будет слушать входящие сообщения и засовывать из в handlers, которые вы описали. Более того, серверы Телеграмма требуют настроенного HTTPS шифрования, поэтому, например, настроить вебхуки на ваш домашний IP без настроенного DNS вряд ли получится. Решение с ngrok поможет лишь протестировать работу бота через вебхуки, в продакшене его, конечно же, не надо использовать.

Если polling - отлично помогает при разработке телеграм бота, то Webhook отлично подходит для продакшена: в случаях наплыва пользователей, можно запустить два инстанса с вашими хендлерами, а заранее настроенный load balancer сам будет параллелить между ними входящие события по вебхуку (такая функция есть как в docker-compose, так и в большинстве современных облачных PaaS, таких как Heroku, Google App Engine или Digital Ocean App Platform).

Как скрестить Django и Telegram bot

Как работать с вебхуками в случае с Django? Просто создать view (пример кода), который получит входящий ивент от Телеграмма и отправит его в ваш обработчик событий (куда вы вешали handlers - логику реакций бота на разные действия пользователей) (пример кода).

Стоит создать Django Model для Телеграм пользователя. И заполнять ее как минимум каждый раз, когда кто-то нажимает команду /start. В этом случае, все пользователи вашего бота будут аккуратно складываться в базу данных, и вы сможете как минимум их посчитать. Для этого рекомендую создать @classmethod, который будет создавать либо возвращать существующий объект класса User для произвольного Телеграм-события.

Как задеплоить в продакшн

Выше я перечислил большой набор технологий, с которыми многие напрямую не работали. Всякие Redis и load balancers .... звучит сложно.

Год назад до меня дошло, что все, что я перечислил выше уже кто-то делал. И да, это стандартные задачи, которые приходится решать, когда ваш продукт начинает сильно расти. Значит ли это, что кто-то взял и автоматизировал весь процесс, позволив разработчикам не тратить время на уже стандартные действия? КОНЕЧНО.

Создание базы данных Postgres и Redis. Повесить домен и прикрутить HTTPs. Запуск еще одного инстанса приложения и запуск load balancer между ними. Автоматическая сборка новой версии приложения и, в случае успеха, zero-downtime замена старого приложения на новое?

Все эти и другие задачи уже давно автоматизированы. Есть платные платформы, которые это умеют (Google App Engine, Digital Ocean App Platform, Heroku). А есть и self-hosted решения (Dokku, CapRover). Self-hosted означает, что вы снимаете где-нибудь сервер (например, Digital Ocean), а потом простым apt-get install запускаете бесплатный аналог платных платформ.

Почему я пользуюсь Dokku?

Я люблю Heroku за его простоту. Мне очень нравится их интеграция с GitHub репозиторием, которая максимально автоматизирует процесс деплоя. В итоге код, оказавшись в репозитории, автоматически превращается в рабочий сервис. Больше не надо заходить на серверы, скачивать свежую версию кода, собирать из этого контейнер, запускать его и ждать, что он не упадет сразу же по глупой ошибке, перенаправлять траффик со старого контейнера на новый и гасить старый контейнер. Это все происходит автоматически.

Разработка превращается в творческий процесс, а не в мучительную боль гугления на stackoverflow "установить докер", "как же открыть порт", "настроить https" и тп. Один раз вкусив этот плод, уже не сможешь вернуться обратно.

Сравнение с пакетом в продуктовом магазине.

Это как с кассирами во Вкусвилле: они сами складывают продукты в пакет. Однажды побывав этом магазине, ты уже ожидаешь, что в Пятерочке тебе тоже будут сами складывать продукты в пакет. Но нет! К хорошему быстро привыкаешь и начинаешь требовать это ото всех.

Основная проблема Heroku в том, что он становится очень дорогим, когда бесплатного инстанса перестает хватать: отдельно плати за сервер, отдельно за БД. Что же делать?

Как это обычно бывает, умельцы сделали Open Source self-hosted версию. Называется Dokku. Да, у нее нет красивого UI с кнопочками и one-click github integration, но зато есть очень понятная документация.

Аналогично Heroku, Dokku собирает ваш проект, используя open-source технологию Buildpacks, и позволяет в 2 строчки подключить базы данных и HTTPS шифрование траффика к проекту.

Dokku + Django

Чтобы buildpacks поняли, как запускать ваш проект, нужно указать а) какие зависимости поставить и б) какие команды запускать. Я предпочитаю это все указывать в файлах requirements.txt и Procfile.

В случае с Dokku существует еще третий вспомогательный файл DOKKU_SCALE, в котором указывается сколько копий сервисов нужно запустить. Например, если указать worker=4, это будет значит: возьми из Procfile команду под названием worker (в нашем случае, это Celery worker), запусти ее 4 раза в разных контейнерах, а потом между ними настрой load balancer. Удобно? Удобно.

Прелесть Dokku в том, что когда вы создаете "плагины" с Postgres или Redis, а потом прикрепляете их к вашему приложению, к нему автоматически добавляются переменные окружения DATABASE_URLи REDIS_URL , поэтому возможно понадобится немного пошаманить в settings.py, чтобы код был готов читать эти переменные.

Автодеплои по коммиту делаются очень легко. Достаточно настроить CI так, чтобы во время нового изменения ветки Main он заходил по ssh на сервере и делал условный git pull (во внутренний репозиторий Dokku). Этот процесс легко автоматизируется через GitHub Actions, вот код.

Если вам нужна пошаговая инструкция, как создать Dokku приложение, как подключить к нему базы данных, как прикрутить HTTPs, автоматическую сборку через GitHub Actions по коммиту в мастер, читайте в Wiki страничке.


Очень надеюсь, что такая сумбурная статья кому-то будет полезной. Напомню, что вот сюда я выложил рабочий код - шаблон телеграм бота, где реализовано все, что упоминалось в статье. Также у меня есть свой Телеграм канал, где я делюсь своим опытом разработки ТГ ботов и не только.

А как вы деплоете своих телеграм ботов в продакшн? Как бы вы улучшили мой подход? Напишите об этом в комментариях.

Комментарии 16

  • НЛО прилетело и опубликовало эту надпись здесь
      0
      В случае высоких нагрузок на тг бот, команда телеграма заопенсорсила часть Telegram Bot API, которую можно развернуть на своем сервере: github.com/tdlib/telegram-bot-api Для очень высоконагруженных ботов это оправдано.
      • НЛО прилетело и опубликовало эту надпись здесь
          0
          Я считаю, чтобы понести огромные убытки, нужно сначала создать бизнес / продукт, который генерит огромные прибыли. Иначе можно очень долго готовиться, но так ничего и не сделав прекрасного. Если я не буду делать ничего сомнительного, меня не отключат. От человеческого фактора спасают простейшие бекапы.
          • НЛО прилетело и опубликовало эту надпись здесь
              0
              Так можно про все сказать) за всем всегда стоит человек / группа людей. А также законы и чьи-то интересы.
                0
                С таким же успехом можно провести аналогию с Майкрософт, Google, AWS etc. Кто платит того и тапки как известно. Начнет гайки крутить телеграмм, перейдут в Дискорд, Сигнал или еше куда. Селф-хостед Джаббер не панацея + этот протокол по сей день имеет куча недоделок (решаемых, тут не спорю).
                  0
                  Видимо, это был условный «кремлебот», так как его комментарий никак не связан с темой статьи :)
        +1

        Мне кажется, очень уместно заиспользовали все перечисленные технологии. Отличная работа!

          0
          Спасибо большое!
          0
          Касательно «высоконагруженности» ботов.

          Допустим, есть некий бот, он делает polling, тебе удобно его использовать в разработке, он периодически запрашивает новые активности с телеграм-сервера. На какие метрики стоит смотреть не со стороны пользователя, а со стороны запущенного бота? Можно смотреть на количество открытых соединений, утилизацию ресурсов, ошибки, профилировать узкие места на основе метрик. А есть ли какие-то готовые решения, которые могут помочь с оценкой производительности работы бота (как с точки зрения утилизации ресурсов сервера, так и с точки зрения общей нагрузки от всех пользователей)? Стоит ли вообще переходить от polling модели, если текущий вариант потенциально может выдержать прогнозируемый рост?

          И еще интересно, на основе каких показателей можно принимать решение о масштабировании бота.
            0

            Как решаете вопрос если надо все таки обратиться к апи? Вебхук тут не поможет.
            Например отправить уведомления надо.
            Через те самые celery tasks идете в telegram bot api ?

              0

              Для отправки сообщений не нужны ни пуллинг, ни вебхуки. Достаточно requests и знаний об api

              0

              Что-нибудь делали чтобы не терять сообщения, если их количество превышает лимиты телеграма? В пуллинг модели можно обойтись готовой очередью из telegram-bot-api, а с вебхуками и несколькими инстансами бота та очередь уже не поможет

                0
                Сообщения не будут теряться и в случае вебхука. Если по какой-либо причине вы не ответили 200 на вебхук, телега сделает retry и отправит его еще несколько раз.
                  0

                  Не совсем то. Я о исходящих сообщениях. В случае превышения лимитов на сервер телеграма они не попадут, нужно делать свою очередь

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

              Самое читаемое