How to deploy Python Telegram bot using Webhooks on Google Cloud Platform — Напиши телеграм-бота. Сейчас даже школьники пишут, — сказала она.
— А почему бы и нет, — подумал я тогда ( — Ну, ну, — сказал бы я сейчас). Мы сидели в Бине и за чашкой кофе обсуждали возможности тестирования идей с моделями искусственного интеллекта на близком и не очень круге друзей. Лена, моя бывшая коллега, и во всех отношениях не блондинка, только что закончившая магистратуру, рассуждала так. Создав бота, можно сэкономить силы и время на интерфейсе, сосредоточившись на ядре с машинным обучением. Согласитесь, что устоять против такой логики “спортсменки, комсомолки и просто красавицы” в то прекрасное воскресное утро было невозможно. Решено. Телеграм-бот, значит телеграм-бот. Первым делом я залез в гугл и нашел большое число ссылок “как сделать бот за 30 минут”. Это меня настолько воодушевило, что дальше названий я не пошел и занялся созданием ядра. В самом первом приближении мне предстояло написать систему обработки поисковых запросов с использованием NLP (natural language processing). Написание ядра заняло некоторое, вполне разумное, время (все же опыт кока-колой не пропить). И через несколько дней я был готов к тому, чтобы за пару часов обернуть первую тестовую версию ядра в пару другую команд send-receive, запустив все это в Телеграме на благо моим друзьям. Но не тут-то было.  Неожиданно возник целый клубок проблем. Потратив пару дней на поиски в интернете и общение с коллегами по цеху, я понял, что очевидное не очевидно, и еще одна “инструкция” точно не повредит. Так и появилась эта статья. Существует несколько библиотек на Python’е для телеграм-ботов. Сам Телеграм ссылается на три из них: python-telegram-bot, pyTelegramBotAPI, AIOGram. Ссылки на эти библиотеки и примеры на других языках можно посмотреть на сайте Телеграма здесь. Конечно, существует еще API самого Телеграма. В ходе своих экспериментов я попробовал два варианта API Телеграма и pyTelegramBotAPI. И пока остановился на втором. В целом впечатления от библиотек следующие. Все очень похожи, что неудивительно. Python-telegram-bot, мне показалось, имеет лучшую документацию из всех, хотя работа над ней прекратилась (надеюсь, ошибаюсь). Видно, что над pyTelegramBotAPI работа продолжается, появляется новый функционал. AIOGram кажется моложе и зеленее. В последней не понравилось, что вместо документации по webhook’ам шла отсылка на сайт Телеграма. Но все течет, все меняется. И окончательный выбор еще предстоит сделать. Больше к вопросу с библиотеками в этой статье мы возвращаться не будем.  Что касается кода самого бота, то, как я уже отмечал, в интернете существует множество мануалов, посвященных созданию простейших и более сложных программ. Для целей этой статьи это непринципиально. Возьмем для определенности два примера с официального гитхаба pyTelegramBotAPI:  Самые неоднозначные темы с телеграм-ботами это ssl-сертификаты, webhook’и, диплоинг. Вокруг этого и сосредоточим наше внимание. Ниже я приведу пошаговую инструкцию, которая позволит наиболее простым и надежным способом за минимальное время разместить ваш телеграм-бот на одном из лучших облачных сервисов в мире, да еще и совершенно бесплатно (по крайней мере, в первый год). Создание ssl-сертификатов, их регистрация в Телеграме и другие смежные вопросы также будут освещены. При необходимости я буду давать пояснения, почему мы используем тот или иной вариант, те или иные команды.
  Если бы не противостояние polling vs webhook и некоторые сложности (отчасти надуманные) с webhook’ом, в этой статье не было бы необходимости. Раз это имеет принципиальное значение, давайте разберемся подробнее. Что такое бот и как он общается с Телеграмом? Очевидно, бот это программа, которая работает на вашем компьютере или сервере. А общение с Телеграмом происходит методами отправки и получения сообщений. И если с отправкой сообщений примерно все ясно, вариант один — послать (адрес “куда” мы знаем). То вариантов получения ботом сообщений от Телеграма два.  Первый — это опрос (буквальный перевод слова polling) сервера Телеграма на наличие сообщений для бота. Второй — это “почтовый ящик” с ip-адресом (webhook — можно перевести как веб-ловушка), на который приходят сообщения от сервера Телеграма. Самая простая аналогия с реальной почтой. Пусть почта (почтовое отделение) — это сервер Телеграма, а вы — это ваш бот. Тогда, в первом случае (polling) вам приходится ходить на почту за корреспонденцией. И если хотите получать сообщения без задержек, то придется не ходить, а буквально бегать без передышек взад и вперед. Как понимаем, жить на почте в ожидании сообщений запрещено! Во втором случае вы сообщаете почтовому отделению свой домашний адрес и ждете корреспонденцию спокойно дома, попивая чай или покуривая бамбук.  Конечно, для человека первый вариант кажется самым суровым. Но, говоря между нами, если бегать за сообщениями мы посылаем железку с кодом, то нам должно быть все равно. И так бы и было, если бы не одна проблема. Время от времени, почта (сервер Телеграма) то закрывается на обед, то переезжает. И у вас в первом варианте (polling) происходит трагедия, которая в реальном мире ботов заканчивается их зависанием и сбоем.  Во втором же случае “почтового ящика” с адресом (webhook’ом) такого не происходит. Потому что ни вы, ни ваш бот, никуда не ходите, а просто ждете. И вам все равно, куда переехало почтовое отделение, потому что почту вам приносит почтальон.  Таким образом, по объему кода (см. ссылку выше) первый вариант кажется проще. А второй логичнее, но тяжелее. Для его реализации нужно завести адрес, подтвердить его достоверность и поднять веб-сервер, на который и будут приходить сообщения от Телеграма.  Конечно, для первого варианта есть возможность внести в код обработку исключений. Например, если почта закрыта, постоять и подождать. Но интернет утверждает, что все равно боты с polling’ом только для поиграться.  Я сначала не поверил, что проблема настолько серьезна. И сделал вариант с polling’ом, предполагая, что как-нибудь позже перепишу на webhook’и. Но это не сработало. На моем домашнем компе с macOS бот работал без проблем и час и два (конечно, с VPN) и не падал. Но стоило мне перенести его на облачный сервер на Linux’е он не мог проработать и 20 минут. Я пытался решить эту проблему разными путями и настройками, но получал лишь разный код ошибок. Селяви, это Телеграм. Потеряв день, пришлось взяться за webhook’и, не откладывая на потом. В конце концов, я же хотел запустить бот на сервере сейчас, а не через год.
  Не так страшен webhook как его малюют.  Если у вас уже есть код бота с polling’ом, то переписать его на webhook не составит почти никакого труда. Сравните коды из примеров библиотеки pyTelegramBotAPI. Вы обнаружите пересечение кода. Строки 13-25 из первого примера совпадают со строками 56-67 второго примера. Это логический блок, отвечающий за обработку команд, сообщений и ответов. В данном случае пример слишком простой, но главную суть он отражает. Таким образом, логический блок бота нужно оставить без изменений, а остальное поменять, используя банальный copy-past кода вне логического блока из второго примера (т.е. копируем строки 1-55 и 70-87). Если вы присмотритесь к коду, который копируете, то без труда выясните, что с помощью библиотеки aiohttp ваш бот устанавливает http-сервер и начинает прослушку выделенного для этого порта, а питоновский модуль ssl отвечает за шифрование и сертификаты. Помимо aiohttp существуют и другие аналогичные библиотеки, примеры с которыми можно посмотреть здесь. Итак. Бот с webhook’ом у нас есть. Осталось выложить его на сервер и запустить, попутно получив данные для заполнения пустых значений в коде API_TOKEN, WEBHOOK_HOST, WEBHOOK_PORT, WEBHOOK_SSL_CERT, WEBHOOK_SSL_PRIV. За каждым из этих значений скрывается определенная процедура с тайным смыслом.
  Начнем с токена бота. Чтобы его получить заходим в Телеграм-мессенджер и подключаемся к боту BotFather. Вводим команду “/newbot”. В ответ BotFather предлагает нам ввести name и username нашего бота и присылает вожделенный токен вида “712308912:DLGSteczdUnPdnNYLzNikaGOhome7l9q3vova” (конечно, у вас будет другой). Сохраняем токен, никому не показываем. В коде присваиваем его переменной API_TOKEN.
  Несколько слов о муках выбора облачного сервиса. В интернете вы встретите много примеров, где телеграм-боты размещаются на Heroku. Heroku — это PaaS платформа (платформа как сервис). Вы заливаете код на GitHub, а оттуда на Heroku. И ваш бот начинает сразу же работать. Не надо устанавливать сервер, Python и библиотеки, все уже сделано за вас. По крайней мере так в идеале. Это решение мне также рекомендовал один из моих знакомых. Но я от него отказался.  Как я понял, основным посылом для рекомендаций Heroku во многих руководствах “бот за 30 минут” является наличие на Heroku бесплатного тарифного плана. Но, если вы посмотрите внимательнее, то окажется, что этот план весьма ограничен и не позволит вам развернуть бесплатно бот, который бы работал сутки напролет. В отличие от Heroku Google Cloud Platform (GCP) предлагает целый год бесплатной работы на всех сервисах в пределах 300 долларов. За эту сумму за год много чего можно попробовать. На мой взгляд это шикарный подарок, особенно для тех, кто занимается датасайенсом. И, конечно, GCP тоже PaaS, полный список сервисов здесь. Есть и безсерверные решения, как и на Heroku. Я выбрал более консервативный путь для большего контроля в будущем, а именно серверное решение Compute Engine на GCP.
  Project, instance, static ip, port Заходим на GCP, выбираем “Get started for free” и следуем инструкциям. Если все сделаем правильно, то у нас появится аккаунт с суммой в 300 долларов и даже первый созданный проект. Выбираем этот проект или создаем другой в верхнем меню, что не сложно. И затем идем на вкладку “Compute Engine/VM Instance” в боковом открывающемся меню.  Нажимаем “CREATE INSTANCE” и выбираем конфигурацию сервера, например, как показано на скрине ниже. Вы можете выбрать конфигурацию micro и тогда это будет стоить всего 4 доллара в месяц. Можно и шикануть, средства будут списываться из подарка от Google в 300 долларов. В итоге получаем.   В колонке “External IP” мы видим адрес, который вбиваем в переменную WEBHOOK_HOST в нашем боте (в данном примере это 35.224.231.90). На этот адрес позже мы получим ssl-сертификат. Если вы собираетесь достаточно долго пользоваться ботом (даже, если будете менять сервер в пределах GCP), то рекомендую сделать этот ip-адрес статичным, чтобы сохранить его при переходе с одного instance на другой. Делается это в разделе “VPC/External IP addresses”. Меняем поле “Type” с “Ephemeral” на “Static”, задав имя статичному адресу, чтобы потом не запутаться, если позже у вас будет десяток ip-адресов.   Для корректной работы webhook’а нам потребуется открыть порт, на который будет стучаться Телеграм. В настоящее время (июль 2019 года) Телеграм поддерживает следующие порты: 443, 80, 88, 8443. Подробнее вы можете посмотреть полное руководство от Телеграма по webhook’ам здесь. Мы откроем порт 8443. В нашем боте это значение уже присвоено переменной WEBHOOK_PORT. Осталось настроить правило брэндмаура в GCP для нашего инстанса webhook-bot. Как это сделать? Идем на вкладку “VPC/Firewall rules” и нажимаем на “CREATE FIREWALL RULE”. И создаем правило, как это показано на скрине ниже.   На этом основная настройка сервера на Google Cloud Platform завершена. Мы завели аккаунт на GCP, создали проект, в рамках проекта создали сервер (инстанс) на базе Ubuntu 19.04, зарезервировали ip-адрес, открыли порт 8443. Осталось немного. Надеюсь, мы можем обойтись без подробного описания части с установкой Python и библиотек. Поэтому кратко.  Заходим в GCP на вкладку “Compute Engine/VM Instance” и в поле “Connect” нажимаем на “SSH”. На вашем локальном компьютере должен открыться терминал с доступом к инстансу на GCP. Это обычная среда Ubuntu. Устанавливаем conda или virtualenv, создаем виртуальную среду и устанавливаем Python 3 с основными библиотеками. Дополнительно устанавливаем библиотеки, необходимые для работы нашего бота:
pip install pyTelegramBotAPI
pip install aiohttp После установки не закрывайте терминал, он нам понадобится.
  Надо сказать, что в интернете вокруг темы ssl сертификатов для Телеграма сплошной хоррор. Как сказал Михаил Лермонтов на этот счет:  “Смешались в кучу кони, люди,
И залпы тысячи орудий
Слились в протяжный вой…”   Если почитать, что пишут в интернете, то получится, что если бот на webhook’ах не работает, то во всем виноваты неправильно полученные сертификаты. А еще, их нельзя регистрировать на ip. И вообще самоподписанные сертификаты Телеграм не принимает. И даже, если он примет сертификат, то это не значит, что ваш бот будет работать, и Телеграм даже ошибку не пришлет.  Вот такие страшилки. К чему я это пишу? К тому, что эти страшилки обошлись мне в еще одни потерянные сутки, даже пришлось пропустить просмотр “Хоббита”. Мой бот первоначально отказывался работать на webhook’ах. Это произошло из-за моих ошибок при его размещении на сервере (тогда я этого не знал). Но из-за страшилок выше я искал проблему не там, сосредоточившись на решении проблем с ssl-сертификатами, которых не было. Зато я приобрел бесценный опыт. Я нарегистрировался сертификатов на годы вперед как на доменные имена, так и на ip, как самоподписанные, так и платные, как в виде двух файлов, так и в виде четырех. Я узнал, какие бывают сертификаты и как делать из них цепочки. Надеюсь, мне это когда-нибудь пригодится.  Одно хорошо, вам на эти грабли наступать не надо, я это уже сделал за вас. Вполне допускаю, что как не бывает “дыма без огня”, так и для таких страшилок был повод. Но на текущий день ситуация похоже устаканилась.  Прямо сейчас мы с вами получим ssl-сертификат самым удобным для нас путем, а именно на ip (а не на доменное имя), самоподписанные (т.е. бесплатно) и обойдясь всего одной строчкой без всяких сайтов. В терминале (который мы еще не закрыли) подготовьте отдельный каталог, в который мы сохраним файл сертификата и приватный ключ. Зайдите в этот каталог и из него вызовите следующую команду. В ответ вы получите запросы на заполнение ряда простых полей. Заполните их по примеру ниже. Но, конечно, укажите верно код своей страны, область, город, название компаний (если они есть), подставьте свой ip и свой email.  В итоге вы получите два файла в каталоге, из которого запустили эту команду. Один файл url_cert.pem и есть ваш сертификат, а второй приватный ключ url_private.key. В коде бота для них выделены переменные WEBHOOK_SSL_CERT и WEBHOOK_SSL_PRIV соответственно. Не забудьте вписать пути доступа к этим файлам вместе с наименованиями. Скачайте копии и сохраните их у себя на будущее.  Не закрывайте терминал.
  Пожалуй, это самая волнительная часть “А что скажет Телеграм?”. Без всяких преамбул просто вбивайте в свой терминал следующую команду. IP-адрес замените своим, порт 8443 не трогайте. Вставьте полученный от BotFather токен между “/bot” и “/setWebhook”) вместо YOUR-TOKEN. Команду запускайте из каталога, где хранятся сертификаты. Если все верно, вы получите в терминале лаконичное сообщение от Телеграма, что webhook установлен.  Если у вас не самоподписанный сертификат, то установить webhook можно следующей командой прямо из броузера. Обратите внимание, что ваш токен указывается два раза. Кроме того, важно, чтобы YOUR.DOMAIN был указан в таком виде, как это в сертификате. Например. Я зарегистрировал домен mydreem.com, регистратор оформил мне ssl-сертификат на домен www.mydreem.com. Вместо YOUR.DOMAIN указывать нужно последний www.mydreem.com. Вы можете проверить установлен ли webhook из броузера следующей командой: Это работает для всех случаев. В ответ вы должны получить что-то типа этого в случае, если webhook установлен и бот запущен: Или типа этого, когда webhook установлен, но бот не работает (не запущен): Иногда вам может потребоваться сменить у бота сертификат, например, в случае переезда на другой сервер (домен). Тогда токен остается, а сертификат меняется (webhook устанавливается заново). Или возникает необходимость запустить бот не на webhook’е, а на polling’е (если webhook установлен, polling работать не будет). В обоих случаях полезна команда, которая “сбрасывает” webhook: Теперь все готово для загрузки кода на сервер и запуска бота. Если вы мастер игры в командной строке в десять пальцев, то для вас это не составит труда. И через минуту ваш бот заработает. Если нет, то следующий раздел поможет несколько облегчить задачу загрузки/выгрузки файлов на ваш сервер и управления ими.
  Мне как человеку далекому от системного администрирования тяжело понять, почему в век торжества искусственного интеллекта мы все еще общаемся с серверами командной строкой как в былые годы на ЕС/СМ — ЭВМ. Предположим, что существуют причины, для простых смертных неведомые. Тогда такое явление как Cloud Shell в GCP необходимо принять с большой радостью. Это средство позволяет, пусть и с костылями, но несколько облегчить труд непосвященных. Хотя может и навредить, если не знать некоторые нюансы. Cloud Shell предоставляет доступ к облачным ресурсам из командной строки прямо из броузера. Вы можете легко управлять своими проектами и ресурсами, не устанавливая Google Cloud SDK или другие инструменты в своей системе (последние два предложения взяты с их странички, “легко” зачеркнуто мной). Как управлять своими проектами из командной строки можно узнать здесь. Но основная для нас фишка не в этом. Запустим Cloud Shell (см. gif-ку ниже, взято с сайта google). Вы зашли в Cloud Shell. Теперь, если нажать на кнопку в виде карандаша, то откроется “редактор текстов версии beta”.  В меню “Help/About” можно узнать, что это “theia-editor-for-cloudshell-preview 0.0.1”. Я не буду под конец статьи углубляться в особенности работы с этим редактором. Выделю только ключевые моменты. Это не только редактор для нескольких языков (проверял только с Python), но и файловый менеджер. Вы можете легко организовать обмен файлами между Cloud Shell и вашим локальным компьютером. Мышкой можно переносить файлы и каталоги в пределах пространства Cloud Shell.  Обратите внимание, я нигде выше не написал, что с помощью редактора можно редактировать и управлять файлами на вашем сервере (инстансе). Но даже этого вполне достаточно, чтобы облегчить нам жизнь. И об этом немного ниже. А сейчас я расскажу нечто важное о Cloud Shell. Это необходимо понимать, чтобы не попасть в просак, как это случилось со мной. Cloud Shell — это сервер на базе Debian с 5Гб в виде диска и 1.7Гб оперативной памяти. На нем уже предустановлено некоторое ПО (включая Python). Cloud Shell легко можно принять за ваш инстанс, установить на него conda, создать env и запустить бота. И, если бот на polling, то он будет работать. Если на webhook, то работать не будет, потому что все порты у Cloud Shell закрыты! И сколько не открывай порты на инстансе, на Cloud Shell они не появятся.  Я по своей юношеской неопытности и невнимательности попался в эту ловушку и долго пытался понять, почему же Телеграм не видит моего бота с webhook’ом. Тогда я сильно расстроился. Но жена согрела, а сыновья успокоили, и появилась эта статья. Кстати, Cloud Shell существует вне пространства и времени (шутка близкая к истине). Если вы удалите у себя все инстансы и все проекты, ваш Cloud Shell продолжит существовать еще 120 дней со всем, что вы в него закачали. Не путайте это с резервным копирование ваших серверов. Итак. Предупрежден, значит вооружен. И теперь мы можем поговорить об удобствах и познакомиться с парой полезных команд. Например, если вы хотите воспользоваться Cloud Shell как терминалом для доступа к своему серверу (например, webhook-bot), то необходимо в командной строке активировать свой инстанс: После этого вы оказываетесь в командной строке своего сервера. Вернуться в Cloud Shell можно командой “exit”. Предположим вы хотите скопировать какие-то файлы из Cloud Shell на инстанс. Для этого в командной строке Cloud Shell (не инстанса) набираем следующую команду: Если поменять источник с назначением местами, то произойдет копирование с инстанса в Cloud Shell. В примере ниже мы копируем файл “ex1.py” из каталога “/examples” сервера “webhook-bot” в каталог “/telebot2” Cloud Shell: Эти и другие команды по обмену файлами можно найти здесь. Обмениваться можно не только файлами, но и целыми каталогами. В итоге мы получаем следующую схему, облегчающую нам жизнь. С локального компа мы копируем файлы нажатием пары кнопок на мышке в Cloud Shell через файловый менеджер редактора “theia-editor-for-cloudshell-preview 0.0.1”. А затем переправляем это на своей сервер (инстанс) командной строкой. Если нам требуется что-то оперативно отредактировать, мы это можем сделать в том же редакторе в Cloud Shell и забросить файлы той же командой на сервер. Получается достаточно оперативно. Верю, что существуют более элегантные и удобные во всех отношениях пути обмена и управления файлами для облачного сервера на GCP (без установки дополнительных программ на локальный компьютер). Возможно, через Cloud Storage. Скажу откровенно, я потратил на поиски и эксперименты по этому вопросу всего несколько часов. Поэтому буду признателен, если вы поделитесь своими идеями или работающими решениями в комментариях. Итак. Мы загрузили файлы на сервер (инстанс). И, если вы еще не запустили бот, то пора это сделать, зайдя в терминал сервера, активировав необходимое виртуальное окружение и набрав команду по типу “python my_webhook_bot.py”. Все должно заработать. Чтобы бот продолжил работать при закрытии терминала, его нужно запускать в фоновом режиме. Например, так “nohup python my_webhook_bot.py &”. Квест, как вывести процесс из фонового режима и завершить его, я оставляю вам, если, конечно, вы еще не знаете.
  Надеюсь, эта статья оказалась вам полезной, помогла сэкономить время и избежать ошибок, которые сделал я при написании телеграм-бота с webhook’ами и размещением его на сервере. Все, что описано в статье, я прошел сам, и на дату написания, июль-август 2019 года, работало именно так. Хочу выразить особую благодарность Михаилу Крутикову за совместный серфинг по просторам сервисов GCP и помощь в осознании ряда важных истин. Я открыт для вопросов, дискуссий и буду благодарен за советы в комментариях к этой статье. Или пишите смело мне в Телеграм @Eduard_Lanchev. До последнего момента для меня был открыт вопрос, стоит ли давать ссылку на свой телеграм-бот. Друзья убедили меня, что стоит. Пообщаться с моим ботом можно по адресу @AelitaSoccerBot. Бот в самом начале пути, и над ним предстоит еще много работать. Поэтому пишите, если что-то не так, пишите, если хотите поделиться своим опытом или дать совет. Моя благодарность не будет иметь границ в разумных пределах. Примечание. К концу 2019 года бот был отключен по причине недостатка времени на его поддержку и развитие. Мой канал в телеграме "Ланчев говорит" https://t.me/lanchev_says — заходите, буду рад видеть. И в завершении хочется пожелать успеха всем ботоводам и моим коллегам датасайентистам!
  Data Scientist