Как поставить вебхук telegram

REDMOND

Telegram-бот, webhook и 50 строк кода

Как, опять? Ещё один туториал, пережёвывающий официальную документацию от Telegram, подумали вы? Да, но нет! Это скорее рассуждения на тему того, как построить функциональный бот-сервис используя Python3.5+, asyncio и aiohttp. Тем интереснее, что заголовок на самом деле лукавит…

Так в чём же лукавство заголовка? Во-первых, кода не 50 строк, а всего 39, а во-вторых, и бот не такой сложный, просто эхо-бот. Но, как мне кажется, этого достаточно, чтобы поверить в то, что сделать свой собственный бот-сервис не столь сложно, как может показаться.

Содержание:

1. Что используем

  • во-первых, Python 3.5+. Почему именно 3.5+, потому что asyncio [2] и потому что сахарные async, await etc;
  • во-вторых, aiohttp. Так как сервис на вебхуках, то он одновременно и HTTP-сервер и HTTP-клиент, а что для этого использовать, как не aiohttp [3];
  • в-третьих, почему webhook, а не long polling? Если не планируется изначально бот-рассыльщик, то интерактивность является его основной функцией. Выскажу своё мнение, что для этой задачи, бот в роли HTTP-сервера подходит лучше, чем в роли клиента. Да, и отдадим часть работы (доставку сообщений) сервисам Telegram.

2. Как используем

Сервер

Состояние библиотеки aiohttp на текущий момент таково, что с её использованием можно построить полноценный web-сервер в Джанго-стиле [4].

Для standalone-сервиса вся мощь не пригодится, поэтому создание сервера ограничивается несколькими строками.

N.B. Обратите внимание, что здесь мы определяем роутинг и задаём обработчик входящих сообщений handler.

И стартуем веб-сервер:

Клиент

Для отправки сообщения используем метод sendMessage из Telegram API, для этого необходимо отправить на оформленный должным образом URL POST-запрос с параметрами в виде JSON-объекта. И это мы делаем с помощью aiohttp:

N.B. Обратите внимание, что в случае успешной обработки входящего сообщения и удачной отправки «эха», обработчик возвращает пустой ответ со статусом HTTP 200. Если этого не сделать, сервисы Telegram продолжат в течение какого-то времени «дёргать» запросами хук, либо пока не получат в ответ 200, либо пока не истечёт определённое для сообщения время.

3. Что можно улучшить

Совершенству нет предела, пара идей, как сделать сервис функциональней.

Используем middleware

Допустим, возникла необходимость фильтровать входящие сообщения. Препроцессинг сообщений можно сделать на специальных веб-обработчиках, в терминах aiohtttp — это middlewares [5].

Пример, определяем мидлварь для игнора сообщений от пользователей из черного списка:

И добавляем обработчик при инициализации web-приложения:

Мысли по поводу обработки входящих сообщений

Если бот будет сложнее, чем репитер-попугай, то можно предложить следующую иерархию объектов ApiConversationCustomConversation.

Наследуя от Conversation и переопределяя _handler получаем кастомные обработчики, в зависимости от функциональности бота — погодный, финансовый etc.

И наш сервис превращается в ферму:

4. Реальный мир

Регистрация webhook

Создаём data.json:

И вызываем соответствующий метод API любым доступным способом, например:

N.B. Ваш домен, хук на который вы устанавливаете, должен резолвится, иначе метод setWebhook не отработает.

Используем прокси-сервер

Как говорит документация: ports currently supported for Webhooks: 443, 80, 88, 8443.

Как же быть в случае self-hosted, когда необходимые порты уже скорее всего заняты веб-сервером, да и соединение по HTTPS мы в нашем сервисе не настроили?

Ответ простой, запуск сервиса на любом доступном локальном интерфейсе и использование реверс-прокси, и лучше nginx здесь сложно найти что-то другое, пусть он возьмёт на себя задачу организации HTTPS-соединения и переадресацию запросов нашему сервису.

Заключение

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

Пусть ваши идеи находят достойный инструмент для реализации.

Telegram bot Webhook как настроить и запустить

Все кто начинает писать различных telegram ботов рано или поздно доходят до бота на вебхуках (webhook).

Для чего нужны webhook?

Когда мы пишем telegram бота и запускаем без вебхуков, то наш бот (который крутится на нашем сервере / компьютере) постоянно обращается к серверам телеграмма и спрашивает есть ли для него информация. Иногда сервера телеграмма не отвечают или посылают куда подальше и наш бот перестает работать. Это в принципе не большая проблема, можно же положить наш скрипт под тот же самый supervisor, который будет перезапускать демонок со скриптом и в целом то вся проблема решена. Конечно при таком раскладе будет периодически простой бота в 2-15 сек (а может и больше, зависит еще от тачки на котором крутится бот) и еще наш бот будет постоянно обращаться к серверам телеграмма, создавая на них нагрузку. В принципе это не наша проблема, но почему не сделать по человечески? Почему не увеличить скорость работы бота, отказоустойчивость и убрать создаваемую нагрузку? Для этого и приходят к нам на помощь Webhook. При работе бота на вебхуках сервер телеграмма сам обращается к нашему боту когда есть новые данные. Собственно и на наш сервер нагрузки будет меньше, ведь бот чаще будет простаивать, что рассмотрим как несомненный плюс.

Что необходимо для Webhook?

Нам понадобится сервер с белым IP адресом и SSL сертификат, можно самоподписной. Так же если у вас есть дома статический (белый) IP адрес, то можно сделать проброс портов до компьютера, на котором будет работать телеграмм бот. Но имейте ввиду, что для вебхуков нужен один из портов 80, 443, 8443.

Собственно небольшое ТЗ: нужно поднять свой веб-сервер, который висит на порту 443 (например) с открытым SSL сертификатом. Я покажу как это делается на примере python и Flask.

Настраиваем сервер

Вообще в интернете много статей как настроить webhook для телеграмм бота на python, но блииииина, нигде не указана пара нюансов, из-за которых ничего не работает и приходится сидеть и копаться, причем на офф страничке по API Telegram так же ничего интересного не указано для корректной работы сервера.

Сразу же ставим сам Flask:

Нужно сделать самоподписной сертификат:

Путь до файла ключа и сертификата, как и название, подставьте свои.

Теперь нам нужно сделать своего бота в @BotFather (в телеграмме, уже не раз обсуждалось как его сделать, да и в инете гляньте, там полно статей с картинками) и привязать нашего бота к серверу через API телеграмма:

заменяем BOT_TOKEN на токен, который получили при создании бота и IP:PORT на IP адрес нашего сервера и порт, на котором будет поднят наш сервис.

Все готово для того, чтобы закодить наш сервис и принять первое сообщение:

Такой небольшой telegram bot на webhook, который при запросе /start присылает сообщение «Привет WebHook» .

Не забудьте подставить свой IP адрес в host=»IP» и url=»https://IP:PORT», и конечно же указать правильный путь до сертификатов.

Теперь можно запустить бота и отправить ему /start. В логе фласка можно будет увидеть POST запрос в корень «сайта», в котором уже происходит наша обработка.

Вроде ничего сложного, но первый раз я потратил достаточно времени чтобы понять что нужно затирать вебхуки перед новым запуском и заново их привязывать, при этом открыв файл с сертификатом на чтение и передав в API телеграмма.

Если кому то вообще ничего не понятно, то просто копируйте команды терминала и код python, и все будет работать.

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.

Django Telegram Bot

Alexander Podrabinovich

В данной статье я расскажу о том как написать Telegram бота с нуля. При этом, делая это не на коленке, а достаточно “по-взрослому”: имея “на борту” Django (для управления контентом, настройками бота, расписанием выполнения задач и пр.), Celery, python-telegram-bot в качестве обертки над Telegram API, Redis, PostgreSQL для хранения всего, что нужно боту. При этом для разработки используя Docker, а деплой в продакшн реализуя через Dokku с python buildback-ом от Heroku.

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

Данный проект основан на потрясающем репозитории https://github.com/ohld/django-telegram-bot/ , код которого и был взят за основу для разработки бота на вышеописанном стеке.

Репозитории

Первым делом оставлю ссылки на репозитории, где все можно посмотреть, потыкать, клонировать и применить в своих проектах:

https://github.com/UNREALre/DjangoTelegramBot_Skeleton — чистый скелет приложения, подойдет для старта любого бота, которого вы планируете написать.

https://github.com/UNREALre/PoetryBot — финальный код нашего тестового поэтического бота.

Режимы работы бота

В работе Telegram ботов нет особых сложностей. Есть отличное API — https://core.telegram.org/bots/api , есть множество еще более отличных библиотек-оберток над этим API (я использую в проектах python-telegram-bot https://github.com/python-telegram-bot/python-telegram-bot) — можно брать и пользоваться. Но, прежде всего, необходимо понять несколько основных моментов. Одним из самых важных для понимания является режим работы боты.

Telegram поддерживает два режима работы:

  1. polling — концепция данного режима очень проста — мы со своей стороны раз в определенное количество времени (например, раз в секунду) шлем запросы на сервера Telegram, чтобы узнать, а не было ли со стороны пользователей каких-нибудь событий, если они есть — получаем их и как-то реагируем.
  2. webhook — полностью противоположная концепция. Со своей стороны мы не делаем вообще ничего, кроме того, что говорим Telegram о том, что вот ссылка (обязательно с HTTPS), по этой ссылке мы будем ждать обновлений. И далее, Telegram уже сам шлет нам по указанному адресу обновления, когда пользователь начинает взаимодействовать с ботом, что, безусловно, очень удобно.

Как легко догадаться, webhook-и намного более эффективны. Аналитики подсчитали, что, в среднем, 98.5% запросов в режиме polling-а происходит “вхолостую”, тогда как эффективность webhook-ов, само собой, равняется 100%, потому что обращение происходит тогда и только тогда, когда появляется событие для этого обращения.

Polling — быстрое идеальное решение для разработки и отладки, поэтому его мы и используем для этих целей.

Webhook — идеален для продакшена.

Регистрация бота

Первое с чего следует начать разработку бота — с его регистрации.

Для регистрации бота добавляем в Телеграм @BotFather — головного бота, где происходит регистрация новых ботов.

Пишем команду /newbot и задаем наименование нашего бота. В моем случае: “Поэтическая душа”.

После этого бот попросит ввести имя на латинском для никнейма бота, которое должно заканчиваться на _bot. В моем случае будет: poetry_soul_bot.

После этого бот выдаст информацию о том, по какой ссылке можно начать диалог с ботом и токен, который необходимо сохранить и добавить в .env файл проекта (об этом позже).

На этом — регистрация бота завершена. Можно переходить к разработке.

Структура проекта

Давайте рассмотрим из чего состоит наш проект, предварительно упомянув, что локальную разработку мы будем вести в Docker-е в режиме pooling-а, а деплоить на продакшн будем используя Dokku и webhook. Рассматриваем структуру скелетона (https://github.com/UNREALre/DjangoTelegramBot_Skeleton).

.env файлик содержит в себе переменные окружения, которые нужны нам для локальной разработки. При деплое на прод мы единожды при развертывании приложения пропишем несколько переменных окружения на сервере и более про это забудем. Основные переменные, которые нам понадобятся:

WEB_DOMAIN=http://localhost:8001 # Непосредственный домен бота

MEDIA_DOMAIN=http://localhost:8001 # MEDIA_DOMAIN — для доступа к медиа контенту, который может быть загружен в бота. Может совпадать с WEB_DOMAIN, а может быть иным, если используются сторонние хранилища контента.

DJANGO_DEBUG=True # Для разработки ставим True

DJANGO_SUPERUSER_USERNAME=admin # задаем креды админа, который будет создан в entrypoint.sh

TELEGRAM_TOKEN=token_from_bot_father # Токен, который мы получили от BotFather при регистрации бота

DB_USER=poetry_bot # креды БД

REDMOND

Dockerfile — докерфайл проекта, базовые директива для докера, применяться будет только для локальной разработки.

docker-compose.yml — тут описаны сервисы, которые будут подняты в контейнерах докером на локальной машине. По умолчанию это: db (PostgreSQL), redis, web, bot, celery, celery-beat (последние два нужны для периодических задач, которые, возможно, понадобится выполять нашему боту).

entrypoint.sh — последний файлик для локальной работы проекта, говорит докеру, что делать после того как поднимутся все контейнеры. Тут говорим, что надо сделать миграции, создать админа по кредам из .env файла, прогрузить различного рода фикстуры, если таковые имеются в проекте, собрать статику и запустить сервер.

Procfile — данный файл необходим для Dokku при деплое на прод. Он содержит одну или более строк описывающих типы процессов и ассоциированных им команд. При деплое приложения в Dokku будет создан Docker образ, затем Dokku извлечет файл Procfile и команды, которые в нем описаны, будут переданы в docker run. В нашем случае мы говорим, что необходимо сделать миграции, поднять gunicorn сервер, запустить celery воркера и celery beat.

requirements.txt — тут все понятно, список зависимостей нашего бота, которые будут ставится как при развертывании на локальной машине, так и при деплое на продашкн.

run_pooling.py — для кейса, когда приложение работает в pooling режиме (на локальном ПК).

runtime.txt — говорит Dokku, какую версию Python мы хотим использовать.

staticfiles — в данной папке вся статика проекта (админка).

media — по умолчанию используем данную папку для хранения медиа файлов. Предположим, мы из админки закачиваем какие-то картинки, которые надо будет показывать пользователям, вот они падают в данную папку потому, что в settings.py проекта прописано: MEDIA_ROOT = os.path.join(BASE_DIR, ‘media’)

logs — очень важная папка, потому что по логам мы будем отлавливать все, что происходит с ботом. В settings.py у нас задан логгер по умолчанию, который будет писать в файлик main.log все, что надо, ротируя его каждые 5 Мб.

dtb — головная папка Django проекта. Тут, скорее всего, ничего менять не придется, кроме settings.py.

tgbot — вся бизнес-логика бота находится в данной папке.

tgbot/admin.py — регистрирует модели приложения для использования в админке.

tgbot/forms.py — кастомная форма для массовой рассылки сообщений из админки.

tgbot/models.py — содержит все модели, используемые ботом. Как минимум для базовой работы необходимы модели:

  • Config — для задания различных параметров бота в формате “параметр” — “значение”;
  • User — для пользователей бота;
  • Location, Arcgis — для работы с геокодированием, если будет надо боту;
  • UserActionLog — фиксация действий пользователя.

tgbot/tasks.py — celery задачи, которые можно устанавливать на исполнение по расписанию в очень удобной форме в админке проекта благодаря django-celery-beat.

tgbot/utils.p — различные функции-хелперы.

tgbot/fixtures — здесь могут быть различные фикстуры с данными для моделей проекта.

tgbot/migrations — тут все понятно, миграции проекта.

tgbot/templates — кастомные шаблоны для админки.

tgbot/handlers/admin.py — модуль-обработчик событий для пользователей с правами администратора. Наша модель пользователей, описанная в модуле tgbot/models.py содержит возможность задавать различные права пользователям. Так вот, если пользователь — администратор, то можно описать для него тот или иной функционал, который будет реализовываться при вызове им команд бота. Логика как раз описывается в данном модуле.

tgbot/handlers/files.py — модуль-обработчик событий, связанных с отправкой боту файлов. Опять же, по-умолчанию, заточен на администраторов. Когда мы посылаем какой-нибудь файл боту (фото, видео, документ и пр) — файл сохраняется и ему присваивается уникальные ID. В последующем, если мы хотим отослать файл пользователю, можно в соответствующий API метод передавать полный URL до этого файла (например, на внешнем хранилище), а можно передать просто Telegram ID этого файла. Вот как раз тут описана функция, которая возвращает ID файла отосланного боту, если его отослал пользователь с правами администратора.

tgbot/handlers/handlers.py — головной модуль-обработчик всех пользовательских событий, которые могут возникнуть у пользователей в процессе взаимодействия с ботом. Логика большинства функций довольно простая: прилетает в метод два параметра — update и context, из которых можно извлечь все: id чата, информацию о пользователе, что отослано и т.п. Далее происходит та или иная бизнес-логика, после чего, либо вызывается api метод edit_message, либо send_message, либо какая-то другая отправка, либо ничего, в зависимости от нужд логики.

tgbot/handlers/utils.py — функции-хелперы для работы обработчиков. Базовый набор: функция отсылки пользователя действия “печатаю”, декоратор для логирования и обертка над api методом send_message с обработками исключений.

tgbot/handlers/commands.py — модуль-обработчик для команд бота. Чтобы не смешивать обычные обработчики действий (нажатия на кнопки, отправки сообщений, документов и пр) и обработчики команд — выносим их в отдельным модуль.

tgbot/handlers/keyboard_utils.py — модуль содержит функции генерации клавиатур. В огромном количестве случаев боту необходимо посылать пользователю сообщения вместе с клавиатурами, чтобы пользователь выбирал те или иные действия. Безусловно, можно все генерировать на месте, внутри модуля handlers.py (к примеру), но логичнее все вынести в единый модуль, отвечающий только за клавиатуры.

tgbot/handlers/manage_data.py — в данном модуле определяются переменные для параметра callback_data, задаваемого при использовании клавиатуры. Т.е. в keyboard_utils модуле мы создаем клавиатуры, которые состоят из кнопок, а у кнопок есть два главных параметра: текст и обратный вызов, т.е. какая строка будет отправлена боту, когда пользователь нажмет на кнопку.

tgbot/handlers/static_text.py — в данный модуль выносим все текстовые данные, которые используем в боте.

tgbot/handlers/dispatcher.py — наконец, пожалуй, ключевой модуль бота, в нем, в соответствии с названием, происходит диспетчеризация всех процессов бота. Во-первых, он содержит функцию запуска pooling-а для тестовых сред. Во-вторых, что самое главное, он содержит функцию setup_dispatcher, главная задача которой связать события, которые вызывает пользователь (например, вызов команды — CommandHandler, отправка сообщения — MessageHandler, нажатие на кнопку — CallbackQueryHandler и т.п.) с функционалом, который необходимо выполнить боту по данном событию.

Изучение кода

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

Прежде всего, небольшая вводная. Разворачивая проект в Docker-е на локальной машине, не забывайте, что после того как внесли правки в код, необходимо перезагрузить контейнер с ботом. Т.е. вы делаете:
docker-compose up -d — build — в результате поднимутся все контейнеры.

Далее просматриваете их:

И, найдя в списке контейнер с ботом пишите (когда это необходимо):

docker restart XXX , где XXX — ID контейнера.

Соответственно, если вы изменяете Celery-задачи, то не забывает перезагружать контейнер соответствующий для тестирования.

Модели проекта

Для нашего проекта, не считая стандартных моделей из скелетона, необходимо добавить две новых: для хранения полученных стихов и для хранения списка избранных стихов пользователем.

Головной файл с логикой

Модуль /tgbot/poetry.py содержит в себе класс, который описывает головную логику проекта. Давайте рассмотрим его.

У класса есть два свойства — адрес источника и домен источника для последующей компоновки ссылок на стихи.

Инициализируя объект класса мы привязываем его к конкретному пользователю, который обратился за тем или иным функционалом.

Метод load_poem делает следующее: если в него передан id, то он просто выбирает из базы стих по id и возвращает его, если ничего не передано, то используя библиотеки requests и bs4, метод парсит источник со стихами выбирая случайный стих.

Метод add_to_fav получает id стиха, который пользователь хочет добавить в избранное и делает это.

Метод get_authors — нужен в нескольких местах нашего бота. Когда пользователь выбирает посмотреть содержимое избранное, то сначала бот показывает ему алфавитный список всех авторов, которые есть в избранном. Кликнув по букве, бот показывает уже полный список всех авторов на эту букву, наконец, кликнув на автора — пользователь получает список стихов. Так вот, данный метод либо просто возвращает всех авторов из избранного пользователя, либо список из первых букв фамилий авторов. В обоих случаях список отсортирован по алфавиту.

Метод get_poems — тут все просто, получает фамилию автора и возвращает все его стихи из избранного пользователя.

Метод get_poem_by_id — имеет красноречивое название.

Метод format_poem — статический, получает объект модели Poem и возвращает строку в разметке Markdown, которая будет красиво выводится ботом (название — жирным, автор — курсивом).

Как все взаимодействует?

Центральный модуль нашей логики — /tgbot/handlers/dispatcher.py, а именно его метод setup_dispatcher.

Здесь происходит связка событий с нашими реакциями на события. Например, следующая строчка говорит о том, что мы хотим добавить в диспетчер обработчик команд (это, когда пользователь печатает слеш и что-то там), который при вводе команды /start вызовет соответствующую функцию command_start из модуля commands:

А следующая строчка говорит о том, что мы хотим добавить в диспетчер обработчик нажатия на кнопку пользователем CallbackQueryHandler, который должен вызывать функцию send_more из модуля hnd, когда бот получит callback-команду SEND_MORE, описанную в модуле md:

И вот таким образом мы добавляем обработчики в диспетчер для всех возможных событий: команды, отправка сообщений, отправка картинок, видео, стикеров, нажатия на кнопки и т.п.

Теперь рассмотрим как бот реагирует на все это дело на примере функции send_more модуля hnd:

Как видим, функция получает два параметра — update и context.

Context — объект типа telegram.ext.callbackcontext.CallbackContext и он нам не особо интересен, т.к. в нашей функции все, что мы делаем, просто в конце с помощью этого объекта вызываем нужный нам мето: edit_message_text, send_message и т.п.

А вот update — интересный объект, ниже привожу все данные, которые прилетают в нем:

Как видно, в update-е содержится избыточная информация по всему, что нас может интересовать: кто написал, кому написал, что написал, какая была клавиатура, все даты и времена, какая кнопка была нажата и т.п.

Как уже очевидно с помощью этих параметров мы можем узнать всю интересующую нас информацию. Например, id чата для последующего ответа, можем получить всю информацию по пользователю, а в финале, вызвать метод, скажем, edit_message_text, как в примере выше, в который передается сообщение ответа, id чата, id сообщения, клавиатура (если нужна) и указывается режим парсинга сообщения — в данном случае мы используем режим Markdown, где, скажем, для выделения жирным используются звездочки: *жирный текст*, для курсив подчеркивания: _курсив_ и т.п. Можно использовать HTML режим, но имейте ввиду, что Telegram поддерживает крайне малое количество html тегов для разметки — https://core.telegram.org/bots/api#formatting-options

В ряде случаев нам необходимо передать не просто некий callback от пользователя, но еще и тот или иной его выбор. Предположим, если речь идет о клавиатуре с алфавитом. В функции обработчике нам необходимо знать, какую буквы выбрал пользователь. Мы, конечно, можем для каждой буквы написать свой обработчик, но это будет ужасно. Поэтому, можно поступить проще. Посмотрим пример формирования клавиатуры для такого случая:

Как видно, мы указываем в callback_data не просто фиксированное значение, а указываем еще после символ решетки и текущую букву кнопки. После чего, в диспетчере мы определяем обработчик события как всегда, обычном способом:

А вот уже в функции обработчике получаем доступ к тому, что было передано после решетки вот так:

Как видно, мы получаем запрос из update.callback_query, а затем обращаемся к свойству data, которое и хранит нужную нам информацию, вычленяя из нее необходимую букву и производя необходимые манипуляции.

Реагировать на пользовательские сообщения можно, само собой, не только методом edit_message_text, можно отсылать новые сообщения через send_message, можно отсылать, скажем, видео файлы через send_video и т.п.

Dokku и его настройка

Что такое Dokku? Если вкратце и совсем просто Dokku — это open source Heroku, сделанный на базе Docker. Когда я прочел это определение, то следующим вопросом лично у меня был: “А что такое Heroku?”. Да, я слышал о Heroku неоднократно, но никогда не вдавался в подробности. Так вот, Heroku — это облачная PaaS (платформа как услуга)-платформа, поддерживающая множество языков программирования. Heroku, так же как и Dokku, обеспечивает быстрый и легкий деплой приложений “под ключ”: базы данных, логирования, мониторинг, контейниризация и т.д. и т.п. Heroku — модный и дорогой, Dokku — маленький, бесплатный и, возможно, не столь user friendly, но тем не менее, очень простой в изучении.

Можно сказать, что Dokku — это обертка над Docker, Heroku Buildpacks, Nginx и Git. Docker — обеспечивает Dokku контейнерами; Dokku использует свой собственный базовый образ Docker с ubuntu, всеми необходимыми пакетами, Heroku buildpack-ами и т.п. Heroku Buildpack — набор скриптов, задача которых определить, соответствует ли приложение заданному типу, скомпилировать и выпустить его. Билдпак, который Dokku запускает внутри контейнера, создает всю необходимую среду выполнения, устанавливает все зависимости и на выходе приложение готово к работе на 100%. Что касается Nginx, то Dokku передает внутрь приложения номер порта 5000, а для внешних использует порты 49200+, а трафик из/в контейнер проксирует Nginx, который нам не надо конфигурировать вообще никак, потому что Dokku все сделает сам. Наконец, в аспекте GIT-а тоже все очень удобно: Dokku использует git-hooks, отслеживает, когда код пушится в GIT и запускает скрипт, который и делает всю магию: создает docker образ из базового, запускает скрипт инициализации среды, запускает само приложение и рестартует Nginx.

Как я раньше разворачивал проекты, если не использовал Docker (хотя, с ним тоже были свои сложности)? Заход на чистый сервер, обновление пакетов, установка всех необходимых пакетов, создание пользователей, морока с правами, установка git, инициализация и пул проекта, установка virtual env, установка всех зависимостей проекта, установка python сервера и настройка, установка nginx и настройка для связи с Python сервером, установка БД и конфигурация приложения, установка redis, celery и их конфигурация, установка supervisor-а, который бы менеджил все это дело… В общем, очень много всяких муторных действий. Ниже я опишу как все это сделать буквально за пяток команд по 10–15 букв в каждой с использованием Dokku.

  1. Генерируем SSH ключ. Для этого пользуемся командой ssh-keygen (если у вас Windows, то делаем это в терминале git bash). Даем какое-нибудь осознанное имя файлам и запоминаем путь, куда мы их сохраним (это пригодится чуть дальше, когда будем настраивать GIT).
  2. Теперь нам надо установить Dokku. Как установить Dokku на хостинге со всей необходимой информацией в ручном режиме — можно прочитать тут: https://dokku.com/docs/getting-started/installation/
    В моем случае — я решил взять droplet на DigitalOcean-е с уже предустановленным Dokku. Как итог — за 5 кликов мышки на сайте DigitalOcean-а, получаем машину с ubuntu, dokku и всем необходимым для начала конфигурации.
  3. Используя созданный в п.1 ключ — заходим на сервер и начинаем настройку Dokku.
  4. Работать в консоли с Dokku приятно и легко. Запоминать особо не нужно команды, мы просто пишем dokku и получаем список доступных команд. Пишем, скажем, dokku apps и получаем список доступных вызовов уже для приложений и так далее. Первым делом — создаем наше приложение. Для этого пишем:
    dokku apps:create poetry — где poetry имя приложения.
  5. Теперь, когда приложение создано, можно добавить в него все необходимые нам переменные окружения. Для этого пишем:
    dokku config:set poetry TELEGRAM_TOKEN=ВАШ_ТГ_ТОКЕН WEB_DOMAIN=https://site.ru MEDIA_DOMAIN=https://site.ru DOKKU_LETSENCRYPT_EMAIL=ivanov@gmail.com DJANGO_DEBUG=False BUILDPACK_URL=https://github.com/heroku/heroku-buildpack-python.git#v191
    Формат команды установки переменной окружения: dokku config:set APP_NAME var_name=var_value , но никто не мешает сделать так как сделал я выше — написать все переменные окружения в строку через пробел. Что мы тут установили?
    TELEGRAM_TOKEN — это, конечно, самое важно. Токен нашего бота, который мы получили у BotFather, при регистрации.
    WEB_DOMAIN и MEDIA_DOMAIN — в данном приложении нам не пригодятся, но для чего они нужны я описывал выше, рассказывая про настройки Django приложения.
    DOKKU_LETSENCRYPT_EMAIL — тут мы идем на опережение и сразу задаем емейл в данной переменной, она пригодится нам, когда мы будем настраивать SSL сертификат на домене (Telegram работает только по HTTPS, при использовании webhook-ов).
    DJANGO_DEBUG — само собой в False, это же продакшн.
    BUILDPACK_URL — а это интересная переменная, которая указывает Dokku, что для билда не следует обращать внимание на Dockerfile, который присутствует в корне нашего репозитория, а следует использовать heroku buildpack.
    Что вообще такое buildpack? Как видим со ссылки — все тянется к Heroku. Buildpack — это набор скриптов, которые в зависимости от языка программирования подтягивают и ставят все нужные зависимости, компилируют код и делают прочую черновую работу автоматически за вас. Подробнее можно почитать тут: https://devcenter.heroku.com/articles/buildpacks
  6. Теперь подключим PostgreSQL к нашему приложению. Для этого, сначала установим соответствующий плагин следующей командой:
    dokku plugin:install https://github.com/dokku/dokku-postgres.git postgres
    Создаем postgres сервис:
    dokku postgres:create pg_poetry
    Связываем только что созданный сервис с нашим приложением:
    dokku postgres:link pg_poetry poetry
    После этой связки в окружении нашего приложения автоматически появится переменная DATABASE_URL, содержащая в себе настройки подключения к базе, в чем мы можем убедиться, написав команду:
    dokku config:show poetry
    Чтобы подключить к PostgreSQL извне, можно написать команду:
    dokku config:show poetry
    Cмотрим там на строку подключения к сервису (DATABASE_URL) и разбираем ее на составляющие:
    [database type]://[username]:[password]@[host]:[port]/[database name]
    Вспоминаем как мы назвали только что сервис postgres (в нашем случае — pg_poetry) или пишем команду, чтобы вспомнить:
    dokku postgres:list
    После это пишем команду:
    dokku postgres:expose pg_poetry
    Выведется порт на который будет переброшено подключение и можно с pgadmin-а подключаться к нашей базе, используя логин и пароль с config строки, распаршенной нами выше, а порт берем отсюда. В качестве IP адреса хоста базы — IP dokku сервера.
  7. Пришла пора поставить Redis. Все делаем по аналогии с PostgreSQL. Сначала ставим плагин:
    dokku plugin:install https://github.com/dokku/dokku-redis.git redis
    Создаем сервис:
    dokku redis:create rd_poetry
    Привязываем сервис к нашему приложению:
    dokku redis:link rd_poetry poetry
    В результате манипуляций в нашем приложении появилась новая переменная окружения — REDIS_URL, которую мы можем использовать в настройках нашего Django приложение (уже использовали).
  8. Пора и честь знать, точнее, пора залить код! Для этого — инициализируем GIT репозиторий:
    dokku git:initialize poetry
    Теперь возвращаемся на локальную машину и вспоминаем путь, куда мы сохранили наш SSH ключ из п.1.
    Открываем нашу папку с проектом, идем в .git папку и открываем файл config, там прописываем строчку, которая скажет гиту, где искать ключ (выделена жирным):
    [core]
    repositoryformatversion = 0
    filemode = false
    bare = false
    logallrefupdates = true
    symlinks = false
    ignorecase = true
    sshCommand = ssh -i C:/Users/myuser/.ssh/id_rsa_poetry
    Если до этого в докку был добавлен другой ключ, то добавляем текущий через dokku ssh-keys:add
    После этого идем в PyCharm или в консоль, или куда вам удобно и добавляет remote:
    git remote add dokku dokku@droplet_ip_goes_here:project_name_goes_here
    Разберем строку: dokku@droplet_ip_goes_here:project_name_goes_here. dokku — имя пользователя, обязательно именно такое. После собачки — указываем IP адрес сервера с Dokku. После двоеточия — имя приложения, которое мы создали в Dokku, в моем случае: poetry.
    Наконец, делаем git push.
  9. Мы почти добрались до финала. Осталось добавить домен к приложению и поставить сертификат. Для начала, добавим домен:
    dokku domains:add poetry mydomain.ru
    Теперь, сделаем сертификат через lets encrypt. Сначала поставим плагин:
    dokku plugin:install https://github.com/dokku/dokku-letsencrypt.git
    Пишем команду, которая сделает сертификаты и все сама пропишет в настройки nginx-а:
    dokku letsencrypt:enable poetry
  10. Сообщаем Telegram, чтобы будем работать через webhook и ожидать оповещений на нашем только что сконфигурированном домене просто открыв следующую
    https://api.telegram.org/bot<TELEGRAM_TOKEN>/setWebhook?url=https://<YOURDOMAIN.COM>/super_secter_webhook/
  11. Финальный пункт. После того как все будет развернуто, мы можем зайти “внутрь” нашего приложения, как мы это делаем с докером просто написав:
    dokku enter poetry web , где poetry — название приложения, а web — название сервиса в который мы входим (список сервисов можно посмотреть в файле Procfile в корне проекта).
    Там мы можем создать админа для доступа к админке используя стандартные django команды:
    python manage.py createsuperuser
    Если потребуется почитать логи, то напишем:
    dokku logs poetry -t

В рамках данного раздела с установкой и настройкой Dokku хотелось бы акцентировать еще на одном нюансе — работа со статикой. Если в нашем проекте все отлично и нам не о чем беспокоиться, потому что мы используем whitenoise , то в случае деплоя проекта, который не использует whotenoise, а хочет управлять статикой через nginx (хотя, даже официальная дока Heroku советует использовать whitenoise)— у нас возникнут небольшие проблемы. Дело в том, что хоть у нас и будет выполняться команда collectstatic, но nginx не будет знать, где находится наша статика — это проблема №1. Проблема №2 — статика будет внутри нашего контейнера с приложением, поэтому нам надо как-то смонтировать volume-ы, чтобы nginx имел доступ к статике проекта. Делается это все довольно просто. Ниже пара пунктов реализации:

  1. Для начала надо запомнить, что dokku располагает конфигурационный файл nginx-а по пути /home/dokku/your_app_name. Править файл мы не можем. Но почитав его содержимое мы видим, что он включает в себя все конфигурационные файлы, которые находятся по пути /home/dokku/your_app_name/nginx.conf.d — и это наш вариант. Но руками на сервере прописывать файлы не очень хочется, хочется прописать их в проекте в IDE и добавить в репозиторий, не так ли? Так. Для этого нам понадобится плагин dokku-supply-config. Что делает этот плагин? При деплое он ищет файлы, которые будут находиться в папке /.dokku вашего репозитория, брать их всех и копировать по пути /home/dokku/your_app_name. Установим плагин:
    dokku plugin:install https://github.com/josegonzalez/dokku-supply-config.git supply-config
  2. Создадим в нашем проекте папку /.dokku, а внутри папку nginx.conf.d, внутри файлик конфигурационный static.conf со следующим содержимым:
    location /static <
    autoindex on;
    alias /home/dokku/your_app_name/staticfiles;
    >
    Запоминаем путь, который мы указали тут. Он понадобится нам, когда мы будем связывать volume-ы. Делаем коммит и пуш.
  3. Теперь нам надо находясь на сервере прописать для dokku параметр, который прокинет volume со статикой внутри нашего контейнера
    dokku docker-options:add your-django-app deploy “-v /home/dokku/appname/staticfiles:/app/appname/staticfiles”
    То, что идет после двоеточия — папка со статикой внутри вашего контейнера с приложением. Тут может быть staticfiles как у меня, может быть static, может быть все, что угодно, смотря что вы сконфигурировали по статике в settings.py вашего django приложения. Чтобы узнать точно путь можно написать:
    dokku enter your_app_name your_container_with_django_name
    Выбрать там папку со статикой и написать:
    pwd
    А то, что слева от двоеточия — это тот самый путь из п.2., который мы запоминали, прописывая в static.conf для nginx.
  4. Теперь запустим “редеплой”, т.е. перезагрузим наше приложение написав команду:
    dokku ps:restart your_app_name

Бонусом к очень удобной и беспроблемной настройке теперь идет прекрасный и очень удобный деплой: вносите правки в ваш код, делаете комит — пуш, и во время пуша в dokku происходит ребилд проекта, установка всего необходимого, прогоны тестов (если они у вас есть) и сборка проекта. Невероятно круто и очень удобно.

Итоги

Ознакомившись и разобравшись с написанным, склонировав проект-скелетон, вы готовы начать писать своего бота. Мы изучили основы работы ботов, детально изучили структуру проекта, аспекты реализации функционала и удобные техники разворачивания решения на продакшне.

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

REDMOND

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *