Как задеплоить своего телеграм-бота (почти) бесплатно — Quickguide в облачный Serverless
Всем привет! Меня зовут Антон Брехов. Я инженер в 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 ""
webhook_delete:
curl --request POST --url ""
webhook_create: webhook_delete
curl --request POST --url "" --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 и кидайте тудушки в бота — он будет добавлять их в конец страницы.