Всем привет! Меня зовут Антон Брехов. Я инженер в Yandex Cloud. Сегодня хочу рассказать о том, как дешевле всего задеплоить своего телеграм-бота. Возможно, этот опыт пригодится и для других решений.
Готовых фреймворков для телеграм-ботов уже достаточно много на любых языках. Однако после написания кода встает вопрос: а как теперь заставить бота работать постоянно, сделать доступным 24/7?
Новички оставляют персональный компьютер работающим и опрашивают сервер телеграма с некоторой частотой. У опытных, скорее всего, есть свой VPS-сервер с reverse proxy для деплоя приложений. Первое решение не является высокодоступным – всё-таки персональный компьютер не предназначен для круглосуточной работы (чаще всего он не подключен к источнику бесперебойного питания, может иметь проблемы с доступом в интернет и т.д.). А отдельный сервер, даже в облаке, — это слишком дорого для деплоя одного бота. Стоимость одного VPS в среднем — от 5$ в месяц.
В статье расскажу, как работает альтернативное решение на базе наших Serverless-сервисов, которое использую сам.
Вот так выглядит схема в случае бота с настроенным вебхуком. Пользователь отправляет сообщение боту в телеграм. Тот стучится на настроенный вебхук. Запускается написанная логика — отправляем ответ.
Здесь мы используем несколько продуктов:
Основное — Serverless Containers. Это сервис, позволяющий запускать свои контейнеры по вызову HTTP или других триггеров. Подробнее тут. А также репозиторий образов контейнеров Container Registry.
Следующий сервис — API Gateway. Он позволяет бессерверно предоставлять в открытый доступ HTTP (а с недавнего времени ещё и WS) эндпоинт для доступа к вашим облачным функциям, бессерверным контейнерам, другим сервисами Yandex Cloud и не только.
Container Registry — собственный registry для контейнеров с поддержкой сканера на уязвимости.
СУБД YDB в режиме Serverless — мультитенантная реляционная СУБД внутренней разработки Яндекса. Здесь важно, что не придётся платить за ресурсы, в отличие от сервисов Managed Service for MySQL или Managed Service for PostgreSQL. Решение отлично подходит в том числе для прототипов, потому что оплата идёт только за полезную нагрузку (количество выполненных запросов).
После создания Serverless-контейнера можно использовать в качестве вебхука прикрепленный к нему URL (если вы сделали его публично доступным). Однако рекомендую использовать перед контейнером API Gateway.
Если вам нужно после прихода запроса от серверов телеграма сделать обратный запрос (к примеру, скачать присланный в бота файл), этот запрос будет воспринят как конечный ответ приложения Serverless. После этого контейнер будет помечен как выполненный и остановится через несколько секунд. Если же между контейнером и телеграмом будет прослойка в виде API Gateway, такого не произойдет.
У YDB свой синтаксис YQL, схожий с привычным всем SQL. Но различий предостаточно. С удовольствием бы использовал его as is, если бы к нему был готовый ORM driver (как, например, PostgreSQL driver для gorm.io ).
YDB в Serverless-режиме имеет dynamodb compatible API, то есть можно брать API совместимую библиотеку и работать с YDB (правда, это уже не реляционная модель, но для прототипа сойдёт!).
Так и сделаем: https://github.com/guregu/dynamo
Вот пример модели пользователя телеграм.
package user
import "time"
type User struct {
UserID int64 `dynamo:",hash"`
Created time.Time `dynamo:",range"`
Username string `dynamo:""`
}
В качестве сервера использую echo от labstack (очень нравится синтаксис).
package main
import (
"log"
"os"
"yc-qr-bot/pkg/agent"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
"github.com/joho/godotenv"
"github.com/labstack/echo/v4"
)
var Version string
func main() {
log.Printf("Version: %v\\n", Version)
godotenv.Load()
bot, err := tgbotapi.NewBotAPI(os.Getenv("TELEGRAM_APITOKEN"))
if err != nil {
panic(err)
}
whInfo, _ := bot.GetWebhookInfo()
log.Printf("whInfo: %#v\\n", whInfo)
a := agent.New(bot)
e := echo.New()
e.POST("/", a.HandleUpdate)
e.Start(":" + os.Getenv("PORT"))
}
Детали самой реализации обработки можно найти в репозитории .
Самое трудное во всей схеме для новичка — задеплоить решение. Для этого написал Makefile для автоматизации.
include .env
create:
yc serverless container create --name $(SERVERLESS_CONTAINER_NAME)
yc serverless container allow-unauthenticated-invoke --name $(SERVERLESS_CONTAINER_NAME)
create_gw_spec:
$(shell sed "s/SERVERLESS_CONTAINER_ID/${SERVERLESS_CONTAINER_ID}/;s/SERVICE_ACCOUNT_ID/${SERVICE_ACCOUNT_ID}/" api-gw.yaml.example > api-gw.yaml)
create_gw: create_gw_spec
yc serverless api-gateway create --name $(SERVERLESS_CONTAINER_NAME) --spec api-gw.yaml
webhook_info:
curl --request POST --url "<https://api.telegram.org/bot$(TELEGRAM_APITOKEN)/getWebhookInfo>"
webhook_delete:
curl --request POST --url "<https://api.telegram.org/bot$(TELEGRAM_APITOKEN)/deleteWebhook>"
webhook_create: webhook_delete
curl --request POST --url "<https://api.telegram.org/bot$(TELEGRAM_APITOKEN)/setWebhook>" --header 'content-type: application/json' --data "{\\"url\\": \\"$(SERVERLESS_APIGW_URL)\\"}"
build: webhook_create
docker build -t cr.yandex/$(YC_IMAGE_REGISTRY_ID)/$(SERVERLESS_CONTAINER_NAME) .
push: build
docker push cr.yandex/$(YC_IMAGE_REGISTRY_ID)/$(SERVERLESS_CONTAINER_NAME)
deploy: push
$(shell sed 's/=.*/=/' .env > .env.example)
yc serverless container revision deploy --container-name $(SERVERLESS_CONTAINER_NAME) --image 'cr.yandex/$(YC_IMAGE_REGISTRY_ID)/$(SERVERLESS_CONTAINER_NAME):latest' --service-account-id $(SERVICE_ACCOUNT_ID) --environment='$(shell tr '\\n' ',' < .env)' --core-fraction 5 --execution-timeout $(SERVERLESS_CONTAINER_EXEC_TIMEOUT)
all: deploy
Будем использовать утилиту yc CLI. С помощью неё можно управлять ресурсами Yandex Cloud. Установите и инициализируете yc.
Скопируйте .env.example в .env
TELEGRAM_APITOKEN=
YC_IMAGE_REGISTRY_ID=
SERVICE_ACCOUNT_ID=
SERVERLESS_CONTAINER_EXEC_TIMEOUT=
SERVERLESS_CONTAINER_NAME=
SERVERLESS_CONTAINER_ID=
SERVERLESS_CONTAINER_URL=
SERVERLESS_APIGW_URL=
AWS_DEFAULT_REGION=
YDB_ENDPOINT=
Заполните четыре первые переменные.
⚡ Создайте сервисный аккаунт и дайте ему права serverless.containers.invoker container-registry.images.puller ydb.admin а также registry контейнеров .
Потом выполняем: make create
Копируем id созданного Serverless-контейнера, и дописываем в .env
Создаем теперь API Gateway: make create_gw
Копируем URL для доступа к созданному API Gateway и копируем обратно в .env (SERVERLESS_APIGW_URL).
Устанавливаем вебхук для бота: make webhook_create
И, наконец, собираем образ и отправляем в Docker Registry: make deploy
Отправляем в бота сообщение и смотрим в логи Serverless-контейнера.
Если нагрузка на бота незначительная, то стоимость решения будет копеечной. Объясняю почему. Согласно документации первые 1 млн вызовов в месяц для сервиса Serverless Containers — бесплатные. API Gateway — бесплатно первые 100к вызовов ежемесячно. Такая же ситуация с Serverless YDB — 1 миллион Request Units (RU) в месяц бесплатно.
Стоимость размещения моих ботов в месяц не превышает 10 рублей. По сути я плачу только за образы, которые храню в Container Registry.
Также воспользуюсь случаем и покажу своих ботов:
QR Bot: кодирование и декодирование QR-кодов.
Stenographer bot: конвертер аудиосообщений в текст. Сделано на базе Yandex SpeechKit
Levitan bot: конвертер текста в аудио. Присылайте ему текст — озвучит. Делал, чтобы читать длинные новости в телеграме, пока еду за рулем.
Notion To-Do Adder bot: привяжите страницу в Notion и кидайте тудушки в бота — он будет добавлять их в конец страницы.
Комментарии (11)
itmind
24.05.2023 09:55На каждый запрос из телеграма всегда запускается заново контейнер?
nik_the_spirit
24.05.2023 09:55Не на каждый. Есть возможность задать параметр concurrency — количество одновременно обрабатываемых контейнером запросов. От 1 до 16. Также serverless контейнеры будут горизонтально масштабироваться, если одновременно поступает много вызовов.
Anton_Andreevich
24.05.2023 09:55У меня только в яндекс облаке получилось сделать бота который стабильно работает.
писал на питоне. библиотеки не использовал для телеграм бота чисто API
без API gateway
YDB тоже использовал в этом
было моловато информации но техническая поддежка в самые сложные моменты помогала.
damewigit
24.05.2023 09:55а как физлицу получить доступ к этому шайтанству?
FlashHaos
24.05.2023 09:55А разве есть какие-то проблемы? Зарегистрировался - и вперед.
damewigit
24.05.2023 09:55У меня во время создания платежного акаунта спрашивает название организации и поле обязательное
abrekhov Автор
24.05.2023 09:55+2Все так. В Yandex Cloud иерархия ресурсов выглядит следующим образом: Организация -> Облако -> Folder (Проект) -> Ресурсы (VM, VPC, etc)
Поэтому при регистрации вам надо будет создать организацию. Даже если вы физ лицо.
К организации привязаны такие сервисы как DataLens, Billing.
damewigit
24.05.2023 09:55получается я ничего не нарушу если введу там название несуществующей организации?
Iktash
А пока бот в разработке/отладке вы переключаете вебхук на локальную машину? Или каждый раз деплоитесь, чтобы протестировать?
abrekhov Автор
Помню, что в начале разработки запускал локально. Как только сделал Makefile стало проще сделать make deploy и подождать секунд 10-20 до деплоя в облако :)
YHx07
Спасибо за статью!
Я настроил actions на гитхабе, чтобы деплоить код в облако. Взял этот action: https://github.com/Goodsmileduck/yandex-serverless-action