Русский
Русский
English
Статистика
Реклама

Serverless

Погружение в Serverless. По следам протокола S3

30.03.2021 10:05:43 | Автор: admin

Продолжаем беседовать с разработчиками экосистемы сервисов Serverless. В прошлый раз Глеб Борисов рассказал о возможностях и перспективах функций в Yandex.Cloud, сегодня Данил Ошеров погрузит нас в мир бессерверных систем и сервис Object Storage.

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

Со временем такие системы перестали быть актуальными, они ушли на второй план, а на смену пришли новые, построенные по протоколу S3. Почему так случилось? Почему S3 стал главным протоколом для синхронизации данных?

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

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

Как-то так получилось, что и решение оказалось удачным, и протокол стал популярным. Это заслуга сильной компании Amazon, или рынок созрел для переноса данных в облака?

Рынок созрел. Бизнес готов доверить свои данные облачным хранилищам. Если бы на месте Amazon оказалась Google, взорвав рынок своим протоколом, то мы бы сейчас пользовались им.

Еще один важный момент: большие компании, такие как Google и Microsoft, также предоставляют услугу облачного хранилища, но при этом вынужденно ушли от протокола S3. Свой протокол, свои клиенты, свои библиотеки для работы со своим хранилищем. Этот набор необходим для поддержания общей экосистемы облака, предоставления унифицированного интерфейса, а не потому что S3 чем-то плох.

Мы предоставляем S3-совместимый протокол, чтобы клиентам было максимально удобно как мигрировать к нам, так и использовать уже готовый инструментарий: всевозможные консольные утилиты, готовые приложения, написанные программы. Но в любой момент клиенты могут просто переключиться на использование Yandex Object Storage, и всё также будет работать. Это большой плюс.

В перспективе может появиться наш собственный SDK и наш собственный протокол. Потому что бежать за Amazon и смотреть только на него не совсем правильное решение.

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

Да, мы не должны себя ограничивать. Сейчас рынок максимально сфокусирован на S3-совместимых решениях. Это нормально, поэтому мы никоим образом не отказываемся от него, а продолжаем развивать нашу совместимость. Мы фокусируемся сейчас именно на поддержке S3-совместимого решения, но при этом всегда держим в голове и в планах развитие собственного протокола, собственных SDK. Но это вопрос будущего.

Решение на базе S3 стало настолько популярным, что появилось множество реализаций этого протокола.

Это так. Вот только самое важное во всех реализациях техническая сторона, как и где ты хранишь сами файлы. Готовый веб-сервер, который умеет отвечать S3-совместимым протоколом, отлично, но вопрос в том, где он эти файлы хранит. Если они лежат на одной машине, то такое решение не отказоустойчивое и не масштабируемое. Такое хранилище подойдет для тестов или простых сценариев, но что-то серьезное уже не потянет, и ты захочешь большего. Одной машины станет недостаточно, и возникнет вопрос: а как расширяться? Поэтому что-то готовое, что можно было бы построить поверх любого облака, сложно представить. Как итог, хранилище это базовый сервис любого облака, фундаментальный.

Object Storage существует поверх инфраструктуры дата-центров в разных местах. Сейчас есть три большие точки присутствия, три зоны доступности. А как между этими дата-центрами перемещаются копии файлов?

Никак, они сразу находятся везде. Когда ты отправляешь файл на загрузку в наш storage, мы его сохраняем сразу в нескольких точках присутствия и получаем сразу несколько копий.

Получается, на входе существует какой-то прокси, который разделяет эти данные в момент записи?

Всё так. Если бы мы сохраняли только одну копию, а затем этот файл синхронизировали на остальные копии, то в зазор времени между сохранением и репликацией могло бы произойти какое-то изменение или катастрофа. Машина, на которую мы сохранили файл, могла умереть, или мог выйти из строя диск. Это грозило бы потерей данных, что недопустимо для storage. Поэтому мы сразу сохраняем файлы в нескольких копиях.

Пока файл находится в нашем storage, возникают разные ситуации, вплоть до выхода из строя всего дата-центра. Поэтому наличие дополнительных копий критично необходимо, чтобы обеспечить непрерывный доступ к данным. Мы не можем ограничиваться только одним дата-центром. Аварии случаются не только с дата-центром, но и с машинами или дисками. Они могут умирать, унося с собой на тот свет и одну из копий данных. Ее нужно восстанавливать, и за этим сложным процессом у нас следит внутренний механизм, который определяет нехватку копий и автоматически сразу их восстанавливает.

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

Как реагирует Object Storage на отключение одного из дата-центров во время учений? Правильно я понимаю, что сервис, который следит за количеством живых копий файла, срочно начинает восстанавливать их количество?

Инфраструктуры Яндекса и Yandex.Cloud независимые, учения Яндекса никак не влияют на работоспособность сервисов Yandex.Cloud. Это важный момент.

Архитектура любых сервисов Яндекса построена так, чтобы обеспечивать их работу в случае отказа любого одного дата-центра. Таким же образом и Object Storage обязан хранить файлы в достаточном количестве копий, чтобы при отключении любого дата-центра файлы оставались доступны.

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

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

Правильно я понимаю, что на S3 как на базовом элементе инфраструктуры внутри Яндекса и Yandex.Cloud построено огромное количество других сервисов, отвечающих за работу других публичных услуг?

Да, объектное хранилище использует огромное количество сервисов. Причем сервис Object Storage, предоставляемый как услуга платформы Yandex.Cloud, построен на тех же технологиях, что и хранилище для внутренней инфраструктуры. Это не раздельные решения, специально написанные для облака или специально написанные для Яндекса, а единая система объектного хранения с независимыми инсталляциями, но одинаковым технологическим стеком.

В результате клиенты Yandex.Cloud получают сервис, способный справиться с любой нагрузкой. Его построили для безупречного оказания услуг Яндекса при любых нагрузках, и он показывает отличные тайминги и доступность. Качество Яндекса гарантировано для всех пользователей Yandex.Cloud, в производительности и отказоустойчивости можно не сомневаться.

Технологические решения в области S3 полностью переиспользуются в виде отдельной инсталляции?

Да. Тот опыт, который мы получаем, проектируя решения для Яндекса, очень важен и помогает предоставлять качественные услуги в Yandex.Cloud. Поэтому учения, проводимые Яндексом, не должны пугать клиентов облака, а скорее могут показать им нашу ответственность за предоставляемые сервисы.

От теории к практике. Какие возможности S3 чаще всего привлекают клиентов, и какие кейсы они реализуют? Для меня, например, это гарантированная доступность файла в нескольких зонах, в нескольких дата-центрах.

Действительно, это базовое требование к Object Storage, которое важно для всех. Но S3 подходит для любых данных давай поговорим о том, в каких случаях тебе это может потребоваться.

Наиболее популярный кейс это бэкапы. То есть ты можешь сохранять в облаке бэкапы своей базы данных, или логи своего приложения, или любые другие резервные копии. Они будут надежно хранится и будут доступны в любой момент, когда потребуются, хотя насчет бэкапов лучше, чтобы такой момент не наступал. Для задач резервного копирования объектное хранилище, построенное по протоколу S3, подходит очень хорошо.

Есть и другие кейсы. Например, хранение картинок. Это горячий контент, который ты показываешь на сайте, к нему нужен стабильный и быстрый доступ. Более того, ты можешь загрузить по S3 и данные самого сайта, всевозможные js-файлы и статику и показывать пользователям их напрямую из Object Storage.

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

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

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

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

Как это работает есть стандартные решения?

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

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

Меня еще волнует вопрос внезапной пиковой нагрузки. Допустим, видео или картинка завирусилась или на сайт совершена DDoS-атака.

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

На чем сейчас написана реализация S3?

Недавно мы целиком перевели разработку нашего S3 на Go. До этого мы разработали прототип S3 на Python, потому что нужно было быстро запуститься. После этого часть реализовали на плюсах, чтобы выдерживать большие нагрузки от сервисов Яндекса, когда S3 сильно начал расти. Но периодически возникали проблемы.

Поэтому мы решили, что нужно унифицировать процесс нашей разработки и перейти к единому стеку. Выбрали Go, и осенью прошлого года перевели всё на него. Теперь стек технологий, которые используются внутри Яндекса и в Облаке, единый. В результате мы не только привели весь стек к единообразию, но также получили неплохой performance boost.

А когда вообще в Яндексе появился S3?

Первый прототип был создан около четырех лет назад. Еще за год до начала реализации вопрос создания S3 внутри Яндекса регулярно поднимался, но начало работы всегда откладывалось. После успешного внутреннего использования мы запустили в Yandex.Cloud наше решение.

Насколько Object Storage в Yandex.Cloud отличается от реализации S3 в Amazon?

Все реализации S3, отличные от Amazon, отстают от оригинала. S3 в Amazon был реализован уже очень давно и всё время до текущего момента постоянно улучшался и обрастал новыми возможностями. За время, которое было у нас, мы реализовали неплохой набор фич, но при этом еще многое впереди.

Как я уже говорил, просто слепо бежать за Amazon, потому что он это делает так, в какой-то момент станет неактуально. Если мы возьмем готовые библиотеки приложения, то увидим, что они оперируют неким ограниченным набором функциональности, который им нужен от S3. Поэтому многие разработчики, зная, что есть не только S3 от Amazon, берут альтернативные совместимые решения для конкретных задач. Сейчас принято не оперировать огромной кучей фичей, а использовать подмножество S3-API функциональности.

Исходя из этого наш Object Storage обладает функциональностью, которая необходима большинству. И конечно, мы много сил уделили развитию UI. Сейчас наши клиенты получают полноценное продакшен-решение.

P.S.

Самый простой и понятный сценарий, который может использовать любой хаброжитель в своих целях, раздача статического контента с Object Storage, например статического сайта. Это может стать основой вашего Serverless-приложения. О подробностях и нюансах разработки в этой экосистеме можно узнать в сообществе Serverless в Telegram: Yandex Serverless Ecosystem.

На осенней конференции Yandex Scale был анонсирован free tier для сервисов экосистемы бессерверных вычислений. Это специальные тарифы для Serverless-сервисов с уровнем нетарифицируемого использования. Например, для Yandex Object Storage каждый месяц не тарифицируются первые 100 000 операций GET, HEAD и первые 10 000 операций PUT, POST. Более подробно об условиях читайте в разделе.

Подробнее..
Категории: Интервью , S3 , Aws , Yandex.cloud , Serverless

Погружение в Serverless. Рождение Yandex Database

13.04.2021 10:07:06 | Автор: admin

Продолжаем беседовать с разработчиками экосистемы сервисов Serverless. В начале нашего путешествия Глеб Борисов описал ситуацию с Yandex Cloud Function, затем Данил Ошеров погрузил нас в мир протокола S3 и сервиса Object Storage, а сегодня Андрей Фомичев поделится подробностями о NewSQL.

Благодаря современным облакам можно накапливать большие объёмы информации, что в перспективе позволит легко прогнозировать потребление ресурсов (то есть использовать предсказательную аналитику). В новых условиях мы ожидаем, что СУБД и сервисы, их сопровождающие, подскажут нам: сейчас необходимо повысить производительность, а для этого увеличить платёж. Или: игнорируйте запросы, которые приходят, чтобы спала мусорная нагрузка. Кажется, такого раньше не было...

В целом я с тобой согласен. Я давно работаю в Яндексе и занимаюсь пользовательскими сервисами. Я долго делал это для внутренних пользователей, а теперь делаю и для внешних.

Традиционная модель: ставишь PostgreSQL-кластер и обслуживаешь его самостоятельно или с помощью команды. Тратишь на это силы и время. Наши сервисы большие и сложные, для их обслуживания четверти человека не хватит, и половины не хватит нужно вкладываться по полной. Поэтому мы давным-давно живём в режиме, что если что-то не так работает (например, нагрузка выросла и ресурсов не хватает), то мы сами дёргаем пользователя и подсказываем, в чём проблема и как её исправить: ресурсы докинуть или запросы оптимизировать. В этом смысле публичное облако должно вести себя именно так.

Для меня облако это не просто возможность быстро создать виртуалочку, а потом удалить её. А его сервисная сущность, когда мы рассматриваем Platform as a Service, базы данных и так далее. Это те сервисы, которые человек и сам себе может организовать, но стоить это будет неимоверно дорого.

Если мы говорим об эволюции использования баз данных, мой путь начался с СУБД Berkeley DB. Очень старая, немного эзотерическая, но очень опенсорсная в своё время. Потом были и MySQL, и PostgreSQL. В основном применял их как записную книжку для данных с удобным поиском. А с каких баз данных ты начинал?

У меня история интересная. Со студенческих времён работаю системным программистом, а такой программист скорее не использует базы данных, а создаёт их. Подсел на это дело, создавал системы хранения и обработки данных. Первая база данных называлась Sedna. Мы её делали ещё в Академии наук, это был опенсорсный проект.

Тогда технология XML была на пике популярности. Мы использовали XML-файлы. Но однажды файлов стало так много, что потребовалась база данных, которая могла бы хранить, обрабатывать их и поддерживать декларативный язык запросов. Таким языком был XQuery, он был достаточно мощным, даже мощнее SQL. Многие, наверно, знают XPath, это такое подмножество XQuery. И среди тех, кто работал с XML, XPath очень популярен.

Дальше я работал со многими базами данных. Для удобства разделяю их на три вида. Встроенные базы данных с особой экосистемой интересные решения, там множество хаков на уровне железа. Клиент-серверные, где клиент устанавливает соединение с удалённой машиной (сервером баз данных) и работает с данными. И третий вид, наиболее мне интересный: распределённые базы данных. Это когда за соединением или множеством соединений скрывается кластер машин, который хранит и обрабатывает данные. Полноценные распределённые системы, распределённый консенсус, и об этом можно бесконечно разговаривать.

По факту все эти СУБД должны хорошо решать задачу масштабирования. А есть распределённые системы, построенные поверх клиент-серверных?

Если брать классические клиент-серверные базы данные, то многие из них (например, PostgreSQL) не распределённые. Они обеспечивают отказоустойчивость и масштабирование, но это лишь дополнения к основной функциональности. Они очень хорошо реализованы: стандартные способы, технологии репликации лога всё работает. Но эти механизмы появились потом, и поэтому такие базы данных на write-нагрузку масштабируются очень ограниченно или почти не масштабируются.

Если же мы касаемся именно распределённых систем, то для них есть свои интересные классификации.

К примеру, есть деление на shared nothing и shared что-нибудь. В случае с shared storage, например, можно подключить полку к EMC или NetApp, хранить там данные, и такое решение будет работать. И это хорошее enterprise-решение, оно масштабируется, но с нюансами. Shared nothing это узлы или машины, которые соединены только сетью. Идеальный кейс для отличного масштабирования это shared nothing: cluster, commodity, hardware классика жанра.

Этот класс систем появился почти одновременно у нескольких компаний. Откуда вообще возникла такая потребность?

Традиционно считается, что она возникла с появлением и развитием интернета. Неудивительно, что в ряду производителей есть Яндекс, который также приложил много усилий к развитию интернета. Если погружаться, то история появления Yandex Database (сокращённо YDB) длинная.

Внешние пользователи знают прежде всего о ClickHouse, но, когда я 12 лет назад пришёл в Яндекс, ситуация была иной. Мы, как и другие интернет-гиганты, только подступали к новому этапу развития сервисов.

Я работал в Поиске, в веб-роботе. Мы собирали данные поисковой базы, хранили их, индексировали, выкладывали на Поиск и по ним отрабатывали поисковые запросы. Сначала были десятки, потом сотни, а потом и сильно больше машин. Потребовалось распределённо хранить данные и делать репликацию, чтобы система была отказоустойчивой и процессы не падали, если откажут машины, вылетят диски или возникнут прочие стандартные проблемы.

Придумали массу способов бороться с отказами в распределённых системах. Системы писались разные, но и их все можно охарактеризовать, например, с точки зрения консистентности. Это значит, что вы, работая с кластером, видите или не видите спецэффекты распределённости.

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

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

Да, конечно. Вот классика: у нас есть key-value база данных. Я по ключу меняю значение, а мой сосед, который сидит рядом, за другим компьютером, его читает. Возникает вопрос: сосед увидит старое значение, которое я записал, или какие-то спецэффекты, особенно если в кластере что-то произойдёт? Усложняем задачу: я транзакционно пишу две записи, и у меня два ключа два значения, которые я хочу поменять транзакционно. Например, у меня есть два счёта, я перекладываю сто рублей с одного на другой. Что увижу я и что увидит наблюдатель, мой сосед?

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

Получается, изначально вы ставили задачу обрабатывать данные от веб-роботов? И придумали решение, которое позволило распределённо обрабатывать большие объёмы данных, причём отказоустойчиво?

К задаче надо добавить ещё одно важное обстоятельство, которое стало стартовым катализатором: требование real-time. Real-time, понятно, условный. И в контексте YDB мне больше нравится говорить не real-time, а интерактивность. Система должна обеспечивать все эти характеристики так, чтобы она могла интерактивно взаимодействовать с человеком.

Представь: ты разрабатываешь сложный сервис для миллионов пользователей. Хороший пример соцсеть, хотя есть много других веб-сервисов с похожей сложностью. Тебе нужно хранить данные в базе данных и обеспечивать отказоустойчивость с оглядкой на реакцию пользователя. Он подружился с кем-нибудь или сохранил запись и ты хочешь, чтобы это обрабатывалось за 10 или 100 миллисекунд, чтобы для пользователя всё происходило очень быстро.

Каждый крупный поисковик или соцсеть сталкивается с такой задачей. Но чем YDB полезна для простого разработчика на Java, Python или Go? Он говорит: Я приду завтра в свою контору, у нас там CRM-система всего на сто-двести-триста одновременно работающих пользователей. Что ему от такой СУБД?

Тут как раз много аспектов и нюансов. Архитектура в основе YDB очень интересная. Каждая инсталляция отвечает не за определённый объём данных (допустим, десять терабайт), которые хранятся локально. Система состоит из очень большого числа маленьких объектов tablet. Tablet реализует некий консенсус, и он хранит условно пару гигабайтов данных. Это означает, что Yandex Database может расти до очень больших значений, но быть маленькой и эффективной. Благодаря этому ты получаешь гибкость: если сервис вырастет в десятки, сотни, тысячи раз ты масштабируешься быстро и удобно. Но пока сервис маленький, он и стоить будет дёшево. Мне эта мысль очень нравится, она ко мне не сразу пришла.

Вот чем хороши виртуальные машины? Чем хороши облака? Чем хороша виртуализация в общем смысле?

Раньше было как? Хочешь запустить систему на сервере купи сервер. У него десятки (а сейчас встречаются и сотни) ядер, и есть вероятность, что тебе не нужно столько ресурсов, а они дорогие и сложные, требуют обслуживания... А виртуализация позволяет запустить виртуалку всего на два, четыре, шесть, восемь ядер и так далее: ровно по потребности. И если база данных начинает тебе предоставлять то же самое, что и виртуализация (CPU, RAM, диск), то это же по-настоящему удобно. Прямо по-настоящему.

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

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

Мы очень плавно перешли к serverless-составляющей YDB.

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

Serverless-решение по-другому работает и иначе масштабируется. Serverless YDB это managed-сервис. Появился он следующим образом: для YDB как для продукта нужна ниша, и очевидно, что она есть. YDB используют в Яндексе и Yandex.Cloud для масштабных сервисов. Понятно, что мало каким потребителям и клиентам нужны кластеры из десятков и сотен машин. Но serverless-парадигма позволяет большому количеству пользователей проектировать работу со всеми возможностями YDB.

Например, быстрый failover. В YDB шард очень маленький, и вся система устроена таким образом, что даже в dedicated-варианте, когда всё работает на выделенных виртуальных машинах, эти маленькие сущности удобно и легко балансировать. Они переезжают с машины на машину и поднимаются за доли секунды. Если вы пришли, создали таблицу и положили туда 100 МБ, все эти 100 МБ будут жить в одном шардике. YDB реализована с применением модели акторов этот шардик будут обслуживать несколько акторов. Она потребляет мало CPU и обходится дёшево с точки зрения аппаратных ресурсов.

Для небольших проектов это очень выгодно. К тому же у нас есть Free Tier: каждый месяц можно выполнить 1 миллион операций (в единицах RU) бесплатно. А крупные и высоконагруженные проекты могут быстро масштабироваться в зависимости от пиковых нагрузок.

Интересный подход использовать в тарификации условные единицы. Можешь рассказать, что такое RU и как они пересчитываются в рубли?

RU это request unit, некая условная единица. Фактически это стоимость чтения по ключу нескольких килобайт данных. Бесплатно во Free Tier дается миллион таких RU. Если запрос сложнее, чем просто чтение по ключу, то его стоимость пропорционально увеличивается. Так и происходит тарификация. Но миллион простых запросов в месяц это достаточно много. И простенький микросервис (или даже не совсем простенький) во Free Tier легко укладывается.

Давай под конец сформулируем: зачем современному разработчику сервисов в облаке использовать YDB?

Из-за многих причин. Возможность бесконечно масштабироваться. Меньше заботы о работоспособности часто самого критичного и сложного компонента сервиса базы данных (обычно, если база данных падает или перестаёт работать, всё становится плохо и сложно).

А ещё это безумно интересно, на пике serverless-подхода. Если попробуете я думаю, вы немного измените представление о мире современных технологий. Как в своё время понятие виртуализации изменило подход к построению вычислительных систем, так же его рано или поздно изменит и подход serverless.

Причём (я немножко похвастаюсь) YDB на голову выше других систем, в том числе serverless. Их на рынке немного. И далеко не все из них технически продвинуты. Взять хотя бы Amazon DynamoDB это NoSQL нереляционная база данных. Там нет SQL, и она этим проигрывает. У Google Cloud Spanner нет публичной serverless-версии. Поэтому Yandex.Cloud предлагает по-настоящему интересную, современную и в чём-то уникальную технологию.

Итак, с YDB разработчик получает: распределённую NewSQL СУБД с поддержкой бессерверных вычислений, высокую доступность, масштабируемость, поддержку строгой консистентности и ACID-транзакций. А в дополнение ещё и API-сервис, в режиме бессерверных вычислений совместимый с API Amazon DynamoDB. Ну а подробности о новых шагах Serverless-сервисов, подробностях и нюансах разработки можно узнать в сообществе Serverless в Telegram:Yandex Serverless Ecosystem.

Подробнее..

Запускаемприложение наExpress.js вYandexCloudFunctions

01.05.2021 10:11:55 | Автор: admin

Node.jsудобнаямасштабируемая сервернаяплатформа для работы сJavaScript. С помощьюнееи различных поддерживаемыхфреймворков,таких как Express, Connect или Koa, можно создавать полноценные приложения.

Еслиидтипо пути упрощенияадминистрирования, возникает желаниезагрузитьприложение вYandexCloudFunctionsи вызывать его из облака.Ксожалению,пока нельзяпросто так взять и запуститьв облакеприложение, написанное на любом популярномnode.js-фреймворке.Фреймворкипишут ответвсокетHTTP(S).Рантаймфункций ожидает получить от пользовательского кода функции объект определенного содержания.

{         "statusCode": <HTTP код ответа>,    "headers": <словарь со строковыми значениями HTTP-заголовков>,    "multiValueHeaders": <словарь со списками значений HTTP-заголовков>,    "body": "<содержимое ответа>",    "isBase64Encoded": <true или false> }

Из коробкиэто работать не будет, но можно научить приложение возвращать ответ в ожидаемом формате.Разберем,как это сделать,на примереприложенияExpress.jsс двумяэндпоинтами.

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

Создаемновую директорию и инициируем в ней новый проект:

mkdir sample-app && cd sample-appnpm init -ynpm install expresstouch index.js

Далее вindex.jsдобавляем следующий код:

const express = require('express');const app = express();app.use(express.urlencoded({ extended: true }));app.use(express.json());app.get('/api/info', (req, res) => {    res.send({ application: 'sample-app', version: '1.0' });});app.post('/api/v1/getback', (req, res) => {    res.send({ ...req.body });});app.listen(3000, () => console.log(`Listening on: 3000`)); 

Запускаем проект ипроверяем, чтоприходятожидаемые ответы:

$ curl 'http://localhost:3000/api/info'{"application":"sample-app","version":"1"}

АдаптируемпроектподServerless

Интегрируеммодуль serverless-http:

npm i --save serverless-http

Это универсальныйвраппер, онподдерживает не толькоExpress, но иConnect,Koa,restana, а также экспериментально другиефреймворки:Sails,Hapi,Fastify,Restify,PolkaиLoopBack.

Затеммодифицируем наш пример.Заменяемзапуск сервера напорте3000экспортом функции-обработчика, которая будет вызыватьсяserverless-рантаймомоблака:

const express = require('express');const app = express();const serverless = require('serverless-http');app.use(express.urlencoded({ extended: true }));app.use(express.json());app.get('/api/info', (req, res) => {    res.send({ application: 'sample-app', version: '1.0' });});app.post('/api/v1/getback', (req, res) => {    res.send({ ...req.body });});//app.listen(3000, () => console.log(`Listening on: 3000`)); module.exports.handler = serverless(app);

Теперь наше приложение готово к запуску воблаке.

Развертываем приложение в облаке

Для того чтобы развернуть код в облаке,проще всего воспользоваться утилитойserverless. УYandex.Cloudестьсвойплагин,который позволяетдеплоитьфункции.Из него пока нельзя развернуть еще один ключевой компонент системы YandexAPIGateway,мычуть позже сделаем это вручнуючерез консоль.

УстанавливаемServerlessFrameworkи плагин к нему:

npm i -g serverless serverless-yandex-cloud 

Далее создаем в проектефайлserverless.yamlс содержимым:

service: sample-appframeworkVersion: ">=1.1.0"configValidationMode: offprovider:  name: yandex-cloud  runtime: nodejs12-previewplugins:  - serverless-yandex-cloudpackage:  exclude:    - ./**  include:    - ./package.json    - ./**/*.jsfunctions:  express:    # this is formatted as <FILENAME>.<HANDLER>    handler: index.handler    memory: 128    timeout: 5

Деплоимфункцию командой:

serverlessdeploy

Еслисделатьфункцию публичной и вызвать ее по предложенному URL, передав путь/api/info , то в ответ мы получим следующую ошибку:

$ curl 'https://functions.yandexcloud.net/%function-id%/api/info'{"errorCode":400,"errorMessage":"Invalid functionID: /%function-id%/api/info","errorType":"ProxyIntegrationError"}

необходима настройка APIGateway.

Создание APIGateway

Спецификация должна соответствоватьстандартуOpenAPI3.0, для нашего простого APIееможнонаписатьруками:

openapi: 3.0.0info:  title: Sample API  version: 1.0.0paths:  /api/info:    get:      responses:        '200':          description: Ok      x-yc-apigateway-integration:        type: cloud_functions        function_id: %function_id%        tag: $latest        service_account_id: %service_account_id%  /api/v1/getback:    post:      responses:        '200':          description: Ok          content:            application/json:              schema:                $ref: '#/components/schemas/Test'      requestBody:        required: false        content:          application/json:            schema:              $ref: '#/components/schemas/Test'      x-yc-apigateway-integration:        type: cloud_functions        function_id: %function_id%        tag: $latest        service_account_id: %service_account_id%components:  schemas:    Test:      type: object

Не забудьтепоменять%function_id%и%service_account_id%на ваши значения. У сервисного аккаунта должна быть рольserverless.functions.invokerиливыше, если вы оставили функцию без публичного доступа.

Вболее сложныхслучаяхможно попробовать сгенерироватьспецификациюOpenAPIна основе уже имеющегося кода API. Для этогоподойдетexpress-oas-generator.

Теперь наше приложение работает идоступно по URL.

$ curl 'https://%api-gw-id%.apigw.yandexcloud.net/api/info'{"application":"sample-app","version":"1"}

Кстати,кAPIGatewayможно привязать свой домен.Какприязатьдоменчитайтев этомпосте.

Новый параметрAPIGateway

Совсем недавно вAPIGatewayпоявилась возможность указать параметр вида{param+}.Вэтом случае будутматчитьсяи вложенные пути.

paths:  /api/{proxy+}:    get:      x-yc-apigateway-integration:        type: cloud_functions        function_id: d4e***        tag: $latest        service_account_id: aje***      responses:        200:          description: Ok      parameters:        - explode: true          in: path          name: proxy          required: true          schema:            type: string          style: simple

Впервом параметре функцииeventвпропертиpathбудет лежатьзначениевида/api/%7Bproxy+%7Dи роутерExpress.jsбудет ломаться.

Решения как минимум два:

  • написать честныйproviderдляYandex.Cloudпо образу того,что сейчас есть дляAWS;

  • пропатчитьобъектevent, положив вpathзначение изurl(строки1319в примере ниже).

Пример готового скриптаможноскачать.

const express = require('express');const serverless = require('serverless-http');const app = express();app.use(express.urlencoded({ extended: true }));app.use(express.json());app.get('/api/info', (req, res) => {    res.send({ application: 'sample-app', version: '1.0' });});app.get('/api/pet/:name?', (req, res) => {    res.send({ ...req.params });});module.exports.handler = (event, context) => {    const patchedEvent = {        ...event,        path: event.url,        originalPath: event.path,    }    return serverless(app)(patchedEvent, context);}

Вы можете бесплатнопопробовать запустить приложений Express.js на YandexCloudFunctionsпо программеfreetier:сервис не тарифицируетпервыймиллионвызовов функций и первые 10ГБчасвыполнения функций.А любые вопросыоработесервисов можно обсудить как с их пользователями, так ис ихсоздателямивчате Yandex Serverless Ecosystem.

Подробнее..
Категории: Javascript , Node.js , Serverless

Перевод Terraform в AWS несколько аккаунтов и другие хитрости

19.03.2021 08:14:45 | Автор: admin


В этой статье поговорим о нюансах использования Terraform от HashiCorp, в частности о том, как использовать Terraform при управлении несколькими аккаунтами Amazon Web Services (так делают все чаще из-за размера организации или предпочтений команды DevOps). По сути, AWS сами намекают клиентам, что неплохо было бы использовать несколько аккаунтов, и недавно даже выпустили для этого несколько сервисов.


Зачем нужно несколько аккаунтов AWS?


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


Управлять безопасностью в этом случае определенно проще, чем когда у вас есть один гигантский аккаунт AWS с запутанной структурой разрешений IAM. По умолчанию отдельные аккаунты AWS не имеют доступа к ресурсам друг друга.


На практике крупные организации из-за своих размеров уже используют несколько аккаунтов AWS, но эти аккаунты никак не связаны. Это серьезно усложняет жизнь отделам закупок на каждый аккаунт AWS нужен отдельный счет, причем организация может группировать счета в соответствии со своими внутренними правилами или нормативными требованиями. Чтобы упростить работу, можно использовать AWS Organizations для управления иерархией аккаунтов и объединения счетов по группам. Недавно AWS представили AWS Control Tower для подготовки новых аккаунтов с предустановленным набором ресурсов и централизованным управлением всеми аккаунтами.


Новый тренд


В целом, организации все чаще используют несколько аккаунтов AWS, наверное, потому, что владельцы дают всем пользователям в своем аккаунте права админа, так что они могут просматривать любые ресурсы и делать что угодно.


Нужно много времени, чтобы настроить разрешения IAM вместо дефолтного админского доступа для этих пользователей. Причем нет гарантий, что принцип наименьших привилегий будет соблюдаться. С другой стороны, по умолчанию у администратора одного аккаунта AWS нет доступа к другому аккаунту, даже в той же организации. Это безопасно, но нужно поработать над разрешениями IAM, чтобы пользователь из одного аккаунта при необходимости имел доступ к ресурсам в другого. Команда DevOps в таких случаях, скорее всего, будет применять принцип наименьших привилегий.


Этот тренд влияет на Инфраструктуру как код (IaC) например, при использовании Terraform. Инструменты IaC обычно по умолчанию привязаны к одному аккаунту AWS. С самого начала в Terraform могло быть несколько так называемых провайдеров в одном скрипте, чтобы получать доступ к нескольким аккаунтам AWS или даже других облачных вендоров.


Инфраструктура как код с несколькими аккаунтами


Управление ресурсами в разных аккаунтах


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


По умолчанию блок провайдера AWS работает в аккаунте, данные которого использовались для подключения к AWS API. Например, если вы используете идентификатор ключа доступа/секретный ключ доступа определенного пользователя AWS, Terraform будет ссылаться на аккаунт этого пользователя. Код Terraform:


provider aws {region = us-east-1}

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


provider aws {  region = us-east-1  assume_role {    role_arn = arn:aws:iam::123456789012:role/iac  }}

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


Промежуточная роль для доступа к третьему аккаунту


Второй вариант использования продолжает первый: назначенная роль может иметь разрешения на создание, обновление и удаление ресурсов в третьем аккаунте AWS. Для этого нужно настроить разрешения IAM в финальном аккаунте AWS, чтобы промежуточный аккаунт мог создавать, обновлять и удалять ресурсы в нём. Схематично это выглядит так:



Рис. 1. Схема промежуточной роли для доступа к финальному аккаунту AWS


Это довольно сложный сценарий, в котором придется приложить немало усилий для контроля и отладки разрешений IAM. В определенной ситуации это допустимо, но на особые преимущества с точки зрения безопасности не рассчитывайте. Вместо атаки на аккаунт 333333333333 хакеры попытаются получить доступ к 222222222222, чтобы уже оттуда добраться до 333333333333. Если аккаунт 222222222222 используется для контроля ресурсов не только в 333333333333, но и в других аккаунтах, безопасность пострадает еще больше.


Другие продвинутые стратегии Terraform


Несколько стейтов


Если ресурсов относительно много, можно разделить скрипты Terraform на несколько стейтов особенно при использовании continuous deployment (CD). Управление всеми ресурсами в одном стейте имеет недостатки:


  • Каждый раз, когда мы вносим даже незначительные изменения, мы боимся, что Terraform затронет базовые ресурсы, которые трогать не надо.
  • Ошибочные изменения в базовых ресурсах, примененные вслепую в рамках continuous deployment, могут иметь плачевные последствия.
  • Разрешения IAM, которые нужны для выполнения скрипта Terraform, будут затрагивать самые разные задачи. Их будет больше, чем требуется для CD.
  • На применение изменений нужно очень много времени, потому что Terraform будет собирать стейт всех ресурсов, которыми управляет стейт, даже если в большинстве из них ничего не поменялось.
  • Если при деплойменте произойдет сбой, последствия могут быть обширными.

Обычно базовые ресурсы редко меняются. Речь идет о VPC, подсетях, Transit Gateways, VPN, базах данных RDS и балансировщиках нагрузки. Эти ресурсы принадлежат базовому стейту, который почти не меняется, а если и меняется, то руками человека.


Некоторые ресурсы, наоборот, меняются постоянно. Это инстансы EC2, автоскейлинг группы, сервисы в ECS и EKS. Обычно такие ресурсы деплоятся (или обновляются) через пайплайны CD и помещаются в другой стейт, отдельно от базовых ресурсов. Этот отдельный набор скриптов Terraform будет управлять лишь небольшой, быстро меняющейся группой ресурсов, и вам потребуется гораздо меньше разрешений. Такое разделение стейтов Terraform идеально подойдет для автоматизированных деплоев.


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


Модули


Terraform упрощает разбивку кода IaC на модули. Просто поместите код в другой каталог и используйте директиву module:


provider aws {region = us-east-1}module mymodule {source = ./modules/my_modulevariable1 = value1variable2 = value2}

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


Модули Terraform позволяют соблюдать принцип DRY (Dont Repeat Yourself не повторяйся). Но все равно остается шаблонный код (определения в бэкенде и вызовы к самим модулям), который нельзя поместить в модуль.


Окружения


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


Terraform предлагает воркспейсы, но прежде чем применить изменения, мы должны переключиться с одного на другой (например, с продакшена на стейджинг). Если делать это вручную, высок риск, что однажды кто-нибудь забудет сменить воркспейс и задеплоит код не в то окружение. Очевидно, что последствия могут быть катастрофическими.


Есть и другое решение составить разные наборы скриптов для разных окружений, но это громоздко, и будет много повторяющих друг друга фрагментов даже при использовании модулей. Это одна из причин создания Terragrunt (подробнее об этом один из основателей рассказывает здесь).


Заключение


Большие (и не очень) организации часто используют несколько аккаунтов AWS, и, если постараться, вполне можно наладить Terraform в такой системе. По сути, Terraform специально разработан как мультиоблачный инструмент и поддерживает множество провайдеров, которые могут использоваться в определенном наборе скриптов.


С помощью советов в этой статье можно создать аккуратный код Terraform в соответствии с принципами DRY.

Подробнее..

Перевод Реализуем бессерверный API с AWS Gateway и Lambda

01.04.2021 16:06:37 | Автор: admin


Без API не обходится ни одно веб-приложение. Для их разработки используются разные методы. Сейчас, например, набирает популярность бессерверный подход он экономичный, масштабируемый и относительно простой. Как ведущий провайдер бессерверных вычислений Amazon Web Services (AWS) вносит огромный вклад в бессерверную разработку. Здесь мы обсудим общие концепции реализации API с помощью AWS Lambda и других сервисов AWS.


Почему именно AWS Lambda?


AWS Lambda это сервис AWS, который отвечает за выполнение определенных функций в ответ на триггеры, то есть события в приложении. Это могут быть HTTP-вызовы, события в других сервисах AWS, например S3, Kinesis или SNS, или повторяющиеся запланированные события. Функции выполняются в эфемерных контейнерах, подготовкой и масштабированием которых занимается AWS, так что разработчики избавлены от хлопот, связанных с инфраструктурой.


Еще одно привлекательное преимущество оплата по мере использования. Вы платите только за общее время выполнения функций и не тратитесь в периоды простоя. Конечно, у Lambda, как и у любого другого сервиса, есть свои ограничения. Он не подходит для некоторых задач вроде очень длительных заданий, интенсивных вычислений или процессов, где мы должны контролировать среду выполнения. Но для реализации API сервис AWS Lambda обычно подходит идеально.


Роль API Gateway


AWS API Gateway это сервис, с помощью которого разработчики создают конечные точки (HTTP endpoints), управляют ими и сопоставляют их с определенными ресурсами AWS, а также настраивают кастомные домены, механизмы авторизации, кэширование и другие фичи. API Gateway это основная часть бессерверного API, потому что отвечает за связь между определенным API и функцией, обрабатывающей запросы к этому API.


HTTP API


API Gateway включает множество функций и интеграций. В какой-то момент в Amazon поняли, что разработчикам, которые используют бессерверные вычисления, обычно не нужно столько всего. Скорее, они предпочли бы в целом упростить процесс реализации. Наверное, поэтому в конце 2019 года AWS объявили о новых HTTP API, облегченной версии API Gateway, которая существенно упрощает разработку, повышает производительность и снижает расходы для бессерверных API. Несмотря на свою простоту, HTTP API поддерживают такие важные фичи, как настройка CORS для всех конечных точек, интеграция JWT, кастомные домены и соединения с VPC.


Принципы бессерверных API


Чтобы разобраться с главными принципами реализации бессерверных API, рассмотрим лаконичный пример простого приложения виртуальной доски, состоящий из двух конечных точек: POST для записи сообщений и GET для извлечения трех последних сообщений. Рассмотрим и другие возможные функции (параметры пути, CORS и авторизаторы), но постараемся не усложнять финальную реализацию.


AWS DynamoDB


Наш проект будет полностью бессерверным, поэтому используем AWS DynamoDB для хранения сообщений. Эта база данных отвечает принципам бессерверной архитектуры простота использования и оплата только за запросы. DynamoDB это база данных NoSQL ключ-значение от AWS, где данные хранятся на серверах AWS и полностью управляются Amazon.


AWS Serverless Application Model


Для реализации вам понадобится аккаунт AWS, а также установленный и настроенный фреймворк AWS Serverless Application Model (SAM). SAM это инструмент для создания, обновления и администрирования бессерверных приложений и всех ресурсов, которые нужны ему для работы. С AWS SAM мы не создаем каждый сервис вручную на веб-консоли мы просто описываем все, что нам нужно, в специальном файле шаблона.
После установки CLI переходим в нужный каталог и выполняем команду:


$ sam init -r nodejs12.x -n whiteboard
Инициализация нового проекта


Выбираем первую опцию и нажимаем Quick Start from Scratch (Быстрый запуск с нуля). Создается каталог нашей доски с минимумом установочных файлов.


Определяем необходимые ресурсы


Сначала открываем файл template.yml и удаляем все, что находится под разделом Resources. Прежде чем перейти к самому API, создаем дополнительные ресурсы. Определяем таблицу DynamoDB, где будут храниться сообщения:


Resources:  BoardMessagesTable:      Type: AWS::DynamoDB::Table      Properties:        TableName: board-messages-table        AttributeDefinitions:          - AttributeName: partKey            AttributeType: S          - AttributeName: createdAt            AttributeType: N        KeySchema:          - AttributeName: partKey            KeyType: HASH          - AttributeName: createdAt            KeyType: RANGE        ProvisionedThroughput:          ReadCapacityUnits: 5          WriteCapacityUnits: 5

Объявление таблицы DynamoDB


AWS создает таблицу DynamoDB, где атрибутом partKey будет ключ партиции, одинаковый для всех записей, а атрибутом createdAt будет ключ диапазона для сортировки по метке времени. Можно добавить в записи другие ключи и значения, но определять их необязательно.


Теперь в том же файле, сразу под предыдущим определением, объявим HTTP API, с которым будут связаны все будущие конечные точки и функции.


BoardHttpApi:    Type: AWS::Serverless::HttpApi    Properties:      StageName: Test      CorsConfiguration: True

Объявление HTTP API


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


Определяем функции обработчиков API


Наконец, определив все API, давайте объявим две функции, связанные с конкретными конечными точками.


PostMessageFunction:      Type: AWS::Serverless::Function      Properties:        Handler: src/handlers/postMessage.handler        Runtime: nodejs12.x        MemorySize: 128        Timeout: 5        Events:          PostMessage:            Type: HttpApi            Properties:              ApiId: !Ref BoardHttpApi              Method: POST              Path: /messages        Policies:          - AmazonDynamoDBFullAccess  GetMessagesFunction:      Type: AWS::Serverless::Function      Properties:        Handler: src/handlers/getMessages.handler        Runtime: nodejs12.x        MemorySize: 128        Timeout: 5        Events:          GetMessages:            Type: HttpApi            Properties:              ApiId: !Ref BoardHttpApi              Method: GET              Path: /messages        Policies:          - AmazonDynamoDBFullAccess

Объявление обработчиков для запросов POST и GET


Тут все понятно: две функции, одна из которых будет вызываться при запросе POST к пути /messages, а другая при запросе GET к тому же пути. У обеих функций есть ограничения на 128 МБ оперативки и пятисекундный таймаут. Код функций находится в файлах postMessage.js и getMessage.js в каталоге /src/handlers/. Создадим их прямо сейчас. (Мы предоставили полный доступ к DynamoDB в разделе Policies каждой функции, чтобы упросить код. В реальном проекте доступ нужно будет настроить более тонко).


Написание функций


Идем в каталог /src/handlers и создаем файлы со следующим содержимым:


postMessage.js


const AWS = require('aws-sdk');const dynamodb = new AWS.DynamoDB();exports.handler = async (event) => {  const { body } = event;  try {    const { author, text } = JSON.parse(body);    if (!author || !text) {      return {        statusCode: 403,        body: 'author and text are required!'      }    }    await dynamodb.putItem({      TableName: 'board-messages-table',      Item: {        msgId: { S: 'board' },        author: { S: author },        text: { S: text },        createdAt: { N: String(Date.now()) } // still expects string!      }    }).promise();    return {       statusCode: 200,       body: 'Message posted on board!',    }  } catch (err) {    return {       statusCode: 500,       body: 'Something went wrong :(',    }  }};

Код обработчика запросов POST


Эта функция будет выполняться в ответ на запросы POST. Она вычленяет из тела запроса автора и текст и сохраняет эти данные в базе данных. Еще она заполняет атрибут partKey одинаковым значением для всех записей. Обычно это не лучший метод, но для нашего примера подходит, потому что позволяет выполнять сортировку по ключам диапазона среди всех элементов с одинаковым ключом партиции. Кстати, DynamoDB всегда ожидает строку, даже если тип атрибута число.


getMessages.js


const AWS = require('aws-sdk');const dynamodb = new AWS.DynamoDB();exports.handler = async () => {  try {    const result = await dynamodb.query({      TableName: 'board-messages-table',      KeyConditionExpression: 'partKey = :partKey',      ScanIndexForward: false,      Limit: 3,      ExpressionAttributeValues: {':partKey': { S: 'board'}}    }).promise();    return {      statusCode: 200,      headers: {        'Content-Type': 'application/json',      },      body: JSON.stringify(result.Items),    }  } catch (err) {    console.log(err);    return {      statusCode: 500,      body: 'Something went wrong :(',    }  }};

Код обработчика запросов GET


В этой функции мы сначала получаем записи, у которых partKey равен board, потом задаем для ScanIndexForward значение false, чтобы последние сообщения отображались сверху, и наконец с помощью свойства Limit указываем, что хотим увидеть только три сообщения.


Развёртывание


Деплоить с AWS SAM легко достаточно одной команды с несколькими введенными значениями. Переходим в root-каталог проекта и выполняем команду:


$ sam deploy --guided

Команда развертывания


Нас попросят ввести имя приложения и выбрать регион AWS, а еще подтвердить несколько действий:



Указание и подтверждение параметров


После подтверждения параметров запускается развертывание и создаются ресурсы. На это уйдет не больше минуты.



Список создаваемых ресурсов их статусы


Когда процесс завершится, открываем веб-консоль AWS в браузере, переходим к сервису API, находим только что созданный API и копируем URL к корневому эндпоинту нашего API.



URL для конечной точки root API


Тестируем API


Давайте напишем на доске несколько сообщений используя, как и большинство из нас, утилиту curl. В следующей команде замените плейсхолдеры своими данными.


curl -d '{"author":"name", "text":"Message text"}' -H "Content-Type: application/json" -X POST https://your-api-id.execute-api.your-region.amazonaws.com/test/messages

Выполнение запроса POST в curl


Отправляем несколько запросов с разными сообщениями. Если все нормально, в консоли отобразится Message posted on board! (Сообщение опубликовано на доске) без ошибок.


Чтобы извлечь последнее сообщение, выполним простую команду, даже короче предыдущей:


curl https://your-api-id.execute-api.your-region.amazonaws.com/test/messages

Выполнение запроса GET в curl


Вот и все. Мы создали простой HTTP API с AWS Lambda и AWS SAM. Конечно, в реальном проекте нужно будет больше функций и конфигураций, но принцип будет тот же: определяем ресурсы, определяем конфигурации, пишем код и запускаем деплой.


Подключение мониторинга Thundra


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


Можно подключить мониторинг Thundra к только что созданным функциям Lambda (см. краткое руководство). После подключения Thundra вам нужно будет инструментировать функции Lambda postMessage и getMessages, чтобы просмотреть подробную информацию о каждом вызове и получить общую картину приложения.


Выбираем функции в списке, нажимаем сначала кнопку Instrument, а затем OK.



Подтверждение инструментирования функции Lambda


Пробуем сделать несколько запросов к API, возвращаемся на дашборд Thundra, нажимаем на имя функции и выбираем вызовы из списка. Здесь мы видим время, производительность, входные и выходные данные функции и т. д. Это очень полезно при отладке API в реальных проектах.



Сведения об одном вызове


Если вы используете бессерверные вычисления в сложных проектах с разными сервисами AWS или других вендоров, вам определенно пригодится функция Unique Tracing, которая существенно упрощает устранение неполадок и отладку приложения.


Стоит ли использовать HTTP API?


С помощью HTTP API с AWS Lambda можно создавать высокопроизводительные и экономичные API. Хоть это и облегченная версия API Gateway REST API, она обладает всем необходимым функционалом и покрывает 90% нужд разработчиков. HTTP API не поддерживают некоторые полезные функции, вроде кэширования, валидации схем и трансформации ответов.


Кэширование вам, скорее всего, не пригодится, потому что HTTP API работают гораздо быстрее старых REST API, а валидацию и трансформацию можно выполнять на уровне кода функции.
Если у ваших разработчиков нет других причин отказаться от HTTP API, можете с уверенностью их использовать. Обычно бессерверная транзакция начинается с вызова API, а отслеживать асинхронный поток событий не так-то просто. В таких ситуациях используйте комплексную функцию распределенного трейсинга в Thundra. Thundra предоставляет 250 тысяч запросов в месяц бесплатно вполне достаточно для маленьких проектов или стартапов.

Подробнее..

Перевод Как создать инфраструктуру в разных окружениях с помощью Terraform

09.04.2021 06:22:09 | Автор: admin

Terraform это опенсорс-инструмент IaC (инфраструктура как код), который предоставляет согласованный рабочий процесс в CLI для управления сотнями облачных сервисов. Terraform преобразует облачные API в декларативные файлы конфигурации.


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


image


В статье рассмотрим несколько способов подготовки инфраструктуры в разных окружениях. У каждого из них есть свои преимущества и недостатки.


Введение


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


Раз Terraform это инструмент IaC, мы прописываем инфраструктуру в коде, поэтому ее можно приспособить для разных окружений с помощью модульного подхода.
Рассмотрим способы создания инфраструктуры в нескольких окружениях.


Предварительные требования


Если у вас еще нет опыта работы с Terraform, сначала лучше почитать эту статью.


Используем папки метод 1


Здесь мы дублируем одну и ту же инфраструктуру в каждой папке с разными значениями в файле terraform.tfvars. Это не идеальный вариант, если у вас одинаковая инфраструктура во всех окружениях.


Папка представляет отдельное окружение. У вас может быть бэкэнд в каждой папке, а может не быть ничего общего между папками. В каждой папке могут находиться файлы outputs.tf, providers.tf, variables.tf и т. д. При выполнении команд terraform приходится переходить в соответствующую папку и выполнять три команды: init, plan, apply.


image
Использование папок (метод 1)


Преимущества:


  • для каждого окружения можно легко добавлять или удалять ресурсы,
  • изменения в одном окружении не влияют на другие окружения.

Недостатки:


  • дублирование кода;
  • если нужно изменить ресурс, приходится делать это во всех окружениях.

Используем папки метод 2


Здесь у нас одна и та же инфраструктура в общих файлах, но для каждого окружения есть отдельный файл terraform.tfvars. Это не идеальный вариант, если у вас разные инфраструктуры во всех окружениях.


Раз файлы main.tf и variables.tf у нас одинаковые, при выполнении команд terraform мы передаем разные переменные в зависимости от окружения. Например, если у нас три окружения, для создания инфраструктуры мы должны выполнить следующие три команды:


// Dev Environmentterraform plan --var-file="tfvars/environment/dev.tfvars"// QA Environmentterraform plan --var-file="tfvars/environment/qa.tfvars"// Prod Environmentterraform plan --var-file="tfvars/environment/prod.tfvars"

image
Использование папок (метод 2)


Преимущества:


  • код не дублируется;
  • если нужно изменить ресурс, не приходится делать это во всех окружениях.

Недостатки:


  • для каждого окружения нельзя легко добавлять или удалять ресурсы;
  • изменения в одном окружении влияют на другие окружения, ведь мы используем одни и те же файлы с разными файлами переменных.

Воркспейсы


Terraform начинается с одного воркспейса с именем default. Это дефолтный воркспейс, который, в отличие от остальных, невозможно удалить. Если вы никогда явно не использовали воркспейсы, значит, вы работали только в воркспейсе default.
Воркспейсы управляются командами terraform workspace. Существует целый набор таких команд. Например, terraform workspace new создает воркспейс.


Модули


Модуль это контейнер для нескольких ресурсов, которые используются вместе. В каждой конфигурации Terraform есть хотя бы один модуль root-модуль. Root-модуль обычно состоит из ресурсов, определенных в файлах с расширением .tf в главном рабочем каталоге.


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


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


image
Несколько окружений


Terragrunt


Terragrunt это тонкая обертка, которая предоставляет дополнительные инструменты для соблюдения принципа DRY в конфигурациях, работы с несколькими модулями Terraform и управления удаленным стейтом.


Подробнее об этом на их официальном сайте.


Итоги


  • Terraform это опенсорс-инструмент IaC, который предоставляет согласованный рабочий процесс в CLI для управления сотнями облачных сервисов.
  • Обычно мы деплоим инфраструктуру в нескольких окружениях, которые мы используем для разработки, стейджинга, тестирования и продакшена.
  • Есть пять способов написать многоразовый код для разных окружений в Terraform.
  • Самый популярный из них папки. Работать с папками можно двумя методами.
  • Terraform начинается с одного воркспейса с именем default. Это дефолтный воркспейс, который, в отличие от остальных, невозможно удалить.
  • Модуль это контейнер для нескольких ресурсов, которые используются вместе. Использование модулей можно настроить таким образом, чтобы один и тот же модуль подходил для разных окружений без изменения кода.
  • Terragrunt это тонкая обертка, которая предоставляет дополнительные инструменты для соблюдения принципа DRY в конфигурациях, работы с несколькими модулями Terraform и управления удаленным стейтом.

Итак, мы рассмотрели несколько способов создать инфраструктуру для разных окружений с помощью Terraform. У каждого из них свои преимущества и недостатки. Эти способы можно сочетать друг с другом.

Подробнее..

Telegram бот на Firebase

26.04.2021 14:21:23 | Автор: admin

В основном, про Firebase рассказывают в контексте создания приложений под IOS или Android. Однако, данный инструмент можно использовать и в других областях разработки, например при создании Telegram ботов. В этой статье хочу рассказать и показать насколько Firebase простой и удобный инструмент (а ещё и бесплатный, при разумных размерах проекта).


Motivation

Ровно посередине апреля я очнулся от работы и вдруг вспомнил, что у меня ещё не написан диплом, а сдавать его через месяц - полтора. У меня была заброшенная научная статья на достаточно скучную тему, делать на её базе диплом мне уж совсем не хотелось. Нужен был глоток свежего воздуха - какой-нибудь новый проект.

В середине февраля я с ребятами из веб студии обсуждал идею создания приложения по подбору квартир с рекомендательной системой, которая анализировала бы изображения интерьеров и подстраивалась под предпочтения пользователя. Так как мой диплом должен быть на тему Computer Vision, то я решил развить эту тему. Да, и было придумало прикольное название - Flinder (Flats Tinder).

Если погружаться в детали, то для создания рекомендательной системы, работающей с изображениями, необходимо построить пайплайн конструирования эмбедингов на базе изображений. Эмбединг - это вектор, содержащий в себе информацию о изображении и являющийся результатом работы нейронной сети.

В частности, меня вдохновила одна научная статья про DeViSE: A Deep Visual-Semantic Embedding Model. Мне было интересно попробовать такие эмбединги.

В чём суть?

Если кратко, то авторы статьи обучили нейронную сеть предсказывать не конкретные классы изображений, по типу "кошка", "собака", а векторные представления названий классов. Это те самые векторные представления, для которых "King - Man + Woman = Queen".

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

Какого бота я делал?

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

Итак, телеграм бот:

  • Присылает пользователю изображение и просит его оценить

  • Получает оценку от пользователя

  • Сохраняет оценку пользователя в базу данных

  • *киллер фича* - удаляет изображение из диалога, если оно не понравилось пользователю

На первом этапе работы бот присылает пользователю случайные изображения. Но при последующем развитии проекта и интеграции рекомендательной системы бот будет присылает всё более и более релевантные фотографии.

Да, также важно раздобыть контент, который пользователи будут оценивать. Немного заморочившись я скачал сразу 20.000 изображений с интерьерами с Pinterest. Это были и запросы как скандинавский интерьер квартиры так и готический интерьер дома. Старался собрать как можно более разнообразный (репрезентативный) набор изображений.

Изображения добывал с помощью библиотечки pinterest-image-scraper (Там есть баги и она не супер удобная, но мне её хватило).

Firebase

Меня немного смущал момент отправки изображений телеграм ботом. Получившаяся база изображений в 20.000 штук весила примерно 1.5 гигабайта и мучаться с переносом её на сервер мне уж совсем не хотелось.

Тут я подумал о том, что было бы классно выложить все фотографии на какой-нибудь облачный сервис, и дальше в телеграмм боте использовать только ссылки на изображения, а не сами изображения. А ссылки на изображения (или идентификаторы изображений) и оценки пользователей можно хранить в Firebase Realtime Database.

Перед этим я быстро проверил, поддерживает ли библиотека pyTelegramBotAPI отправку изображений ссылками. Метод send_photo спокойно работал с ссылками на изображения, так что я решил дальше использовать этот подход.

Прежде я работал только с Firebase Realtime Database, но про удобство Firebase Storage был наслышан.

Инициализация проекта в Firebase

Итак, чтобы начать работать с Firebase вам необходимо зарегистрироваться на этом сервисе и создать там проект. После чего у вас откроется вкладка Project Overview.

Project OverviewProject Overview

Для того чтобы получить доступ к функциям Firebase из кода необходимо скачать ключи доступа к проекту. Сделать это можно нажав на значок шестерёнки в верхнем левом углу, справа от надписи Project Overview, и выбрать пункт Project Settings. Затем, на открывшемся экране нужно выбрать Service Accounts и нажать Generate new private key.

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

import firebase_adminfrom firebase_admin import credentialsfrom firebase_admin import dbfrom firebase_admin import storagecred = credentials.Certificate("/path/to/secret/key.json")default_app = firebase_admin.initialize_app(cred, {  'databaseURL': 'https://realtime-db-name',    'storageBucket' : 'storage-bucket-local-name'})bucket = storage.bucket()

Где правые части внутри выражения initialize_app есть условные ссылки на названия ваших баз данных внутри проекта в Firebase. После инициализации у вас будут доступны две базы данных

  • db - объект Realtime Database. Данные хранятся в виде одного JSON дерева. В случае работы с питоном - это по сути объект dict.

  • bucket - объект Storage, по сути, обёртка над Google Storage, позволяющая по API загружать и скачивать объекты.

Далее покажу несколько примеров использования этих двух баз данных.

Firebase Realtime Database

Обожаю эту базу данных и готов петь ей дифирамбы. Она очень удобная, быстрая, надёжная, а главное - никакого SQL! Это JSON based Database. Но хватит похвалы, давайте посмотрим, как с ней работать.

Например, у нас есть несколько пользователей, которые хранятся в users_database.

users_databse = {"1274981264" : {"username" : "user_1","last_activity" : 1619212557},"4254785764" : {"username" : "user_2","last_activity" : 1603212638}}

Добавить их в в Realtime Database мы можем так:

db.reference("/users_databse/").set(users_databse)

Также мы можем добавить и следующего пользователя таким вот образом:

user_3_id = "2148172489"user_3 = {"username" : "user_3","last_activity" : 1603212638}db.reference("/users_database/" + user_3_id).set(user_3)

Этот код добавит user_3 в users_database

Получить данные можно так.

user_3 = db.reference("/users_database/" + user_3_id).get()users_databse = db.reference("/users_databse/").get()

Это вернет объекты формата Python dict

Стоит отметить, что массивы в Realtime database хранятся в следующем виде.

a = ["one", "two", "three"]firebase_a = {"0" : "one","1" : "two","2" : "three"}

То есть также в формате json

И ещё один нюанс, Realtime Database не хранит объекты None и [] То есть код

db.reference("/users_database/" + user_3_id).set(None)

Приведёт к ошибке

А код

db.reference("/users_database/" + user_3_id).set([])

Удалит данные user_3

Также стоит добавить, что если внутри вашего объекта в питоне есть какое-либо поле, значение которого есть None или [], то в объекте, загруженном в Realtime Database этих полей не будет. То есть:

user_4 = {"username" : "user_4","last_activity" : 4570211234,  "interactions" : []}# Но user_4_in_fb = {"username" : "user_4","last_activity" : 4570211234}

На самом деле, методами get() и set() всё не ограничивается. По ссылке вы можете посмотреть документацию по firebase_admin.db

Firebase Storage

Вернёмся к Firebase Storage. Допустим, у нас на локальном диске хранится изображение по пути image_path Следующий код добавит это изображение в Storage.

def add_image_to_storage(image_path):    with open(image_path, "rb") as f:        image_data = f.read()    image_id = str(uuid.uuid4())        blob = bucket.blob(image_id + ".jpg")        blob.upload_from_string(        image_data,        content_type='image/jpg'    )

Где image_id - уникальный идентификатор изображения.

С получением доступа к изображению всё чуточку сложнее. blob имеет формат

blob.__dict__
blob.__dict__ = {'name': 'one.jpg', '_properties': {'kind': 'storage#object',  'id': 'flinder-interiors/one.jpg/1619134548019743',  'selfLink': 'https://www.googleapis.com/storage/v1/b/flinder-interiors/o/one.jpg',  'mediaLink': 'https://storage.googleapis.com/download/storage/v1/b/flinder-interiors/o/one.jpg?generation=1619134548019743&alt=media',  'name': 'one.jpg',  'bucket': 'flinder-interiors',  'generation': '1619134548019743',  'metageneration': '1',  'contentType': 'image/jpg',  'storageClass': 'REGIONAL',  'size': '78626',  'md5Hash': 'OyY/IkYwU3R1PlYxeay5Jg==',  'crc32c': 'VfM6iA==',  'etag': 'CJ+U0JyCk/ACEAE=',  'timeCreated': '2021-04-22T23:35:48.020Z',  'updated': '2021-04-22T23:35:48.020Z',  'timeStorageClassUpdated': '2021-04-22T23:35:48.020Z'}, '_changes': set(), '_chunk_size': None, '_bucket': <Bucket: flinder-interiors>, '_acl': <google.cloud.storage.acl.ObjectACL at 0x7feb294ff410>, '_encryption_key': None}

Где есть selfLink и mediaLink, однако доступ к изображению по этим ссылкам - ограничен и доступен только при наличии определенных прав доступа, которые настраиваются в консоли Firebase.

В своём проекте я постарался сделать всё максимально просто и поэтому воспользовался методом blob.generate_signed_url(...). Этот метод генерирует ссылку, которая имеет определённое время жизни. Время жизни ссылки является параметром метода.

Следующий метод генерирует ссылку, живущую 10 минут.

def get_image_link_from_id(image_id):    blob = bucket.blob(image_id + ".jpg")    time_now = int(time.time() // 1)    ttl = 600    return blob.generate_signed_url(time_now + ttl)

Telegram Bot

Не буду вдаваться в подробности написания телеграм ботов, так как на эту тему статей много (простой туториал, супер подробная статья). Пройдусь только по основным моментам.

Как выглядит бот?

В своём проекте на pyTelegramBotAPI я использовал InlineKeyboardButton, состоящую из эмоджи и callback_query_handler, обрабатывающий нажатия на кнопки.

keyboard = types.InlineKeyboardMarkup(row_width = 3)nott = types.InlineKeyboardButton(text="no_emoji", callback_data='no')bad = types.InlineKeyboardButton(text="bad_emoji", callback_data='bad')yes = types.InlineKeyboardButton(text="yes_emoji", callback_data='yes')keyboard.add(nott, bad, yes)
Небольшой баг хабра

Пока писал статью столкнулся с тем, что редактор статей Хабр в браузере Safari не переваривает эмоджи внутри вставок с кодом. Если что, в моём боте кнопки имеют такой вот вид, ниже скрин кода.

Реакции пользователей я храню в Realtime Database. Добавляю их туда следующим образом.

def push_user_reaction(chat_id, image_id, reaction):  db_path = "/users/" + str(chat_id)+ "/interactions/"+ str(image_id)db.reference(db_path).set(reaction)

База данных Firebase Realtime Database имеет следующий вид

users - база данных пользователей.

Для каждого пользователя в разделе interactions мы храним взаимодействия пользователя с изображениями. last_image_id и last_message_id - элементы логики работы телеграмм бота. Что-то типо конечного автомата.

Да, и идентификаторы пользователей в базе данных - это telegram id пользователей (chat_id для библиотеки telebot).

usersusersinteractionsinteractions

interiors_images - база идентификаторов изображений. В ней хранятся метаданные изображений, а также дублируются реакции пользователей.

interiors_imagesinteriors_images

Ну и images_uuids в Realtime Database - это просто массив с уникальными идентификаторами изображений. Своего рода костыль, чтобы было откуда выбирать идентификаторы изображений.

Костыль

Собственно говоря сам костыль.

IMAGES_UUIDS = Nonedef obtain_images_uuids():    global IMAGES_UUIDS    IMAGES_UUIDS = db.reference("/images_uuids/data").get()obtain_images_uuids()def get_random_image_id():    image_id = np.random.choice(IMAGES_UUIDS)    return image_id

Да, на первом этапе, изображения, отправляемые пользователю, выбираются случайным образом.

Firebase Storage выглядит таким вот образом. Можно заметить, что названия изображений в Storage есть просто идентификаторы изображений + их расширение.

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

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

Деплой на сервер

Последнее время я стал адептом докера, поэтому и этот свой проект я размещал на сервер с помощью докера. Пройдусь по основным моментам, которые я использовал в проекте.

Dockerfile

FROM python:busterCOPY requirements.txt /tmp/RUN pip install -r /tmp/requirements.txtRUN mkdir /srcWORKDIR /srcCOPY ./src .CMD python3 /src/code/bot.py
requirements.txt
pyTelegramBotAPIfirebase-admingoogle-cloud-storagenumpy

Где src - это место монтирования docker volume, который я создал до этого командой

docker volume create \            --opt type=none \            --opt o=bind \            --opt device=/home/ubuntu/Flinder/src \            --name flinder_volume

После чего собрал образ и запустил контейнер следующим образом, где флаг-v монтирует созданные ранее flinder_volume в директорию src внутри докер контейнера.

docker run -d \--network=host \--name flinder_bot \--restart always \-v "flinder_volume:/src" devoak/flinder:1.0

Ну и полезное замечание, что у команды docker run можно указать прекрасный параметр --restart always, который обеспечит постоянную работу бота на сервере.

До этого я делал через systemctl, что было сложнее и менее удобно.

Заключение и капелька пиара

Flinder - именно так называется мой проект (Flats Tinder)Flinder - именно так называется мой проект (Flats Tinder)

Надеюсь, моя статья была полезна. Хочу привнести в сообщество программистов такую идею, что работать с Firebase - это очень просто, приятно и удобно, а главное - бесплатно, при разумных размерах проекта.

Более того, использование Firebase не ограничивается Телеграм ботами, недавно я сделал целый промышленный парсер инстаграмма на основе Firebase Realtime Database, о чём я тоже планирую написать статью.

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

По поводу других проектов, оставлю свой телеграм канал. Периодически пишу туда про проекты, которыми занимаюсь (а их много, есть даже нейросети для театра). Более того, если вам вдруг нужен сайт или приложение с продвинутым бэкендом, то милости прошу, моя веб студия. Занимаемся проектами любой сложности.

Подробнее..

Перевод Развертывание приложения Symfony в AWS Lambda

11.06.2021 18:10:18 | Автор: admin

Сначала давайте разберемся, что такое бессерверная архитектура и когда она нужна.

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

AWS Lambda обеспечивает высокую доступность, причем плата взимается только за фактически затрачиваемое время вычислений. Этот сервис может быть весьма полезен для таких задач, как запуск cron-заданий, отправка уведомлений в режиме реального времени, предоставление доступа к API, обработка каких-нибудь событий при выполнении различных операций и т.д. В сети можно найти массу примеров использования сервиса.

Наш сценарий использования

Предоставить доступ к API, созданному с помощью Symfony, который публикует сообщения в LinkedIn. Процесс разработки будет включать этапы от написания до развертывания кода.

Пишем код

В Symfony 5-й версии появился новый компонент под названием Notifier, который дает возможность отправлять уведомления через разные сервисы (Slack, Twitter, Twilio и др.).

В Symfony нет встроенной поддержки LinkedIn, поэтому несколько месяцев назад я создал поверх Notifier шлюз для публикации контента в этой социальной сети. Исходный код шлюза можно посмотреть по ссылке, его же мы будем использовать и в этой демонстрации.

Приступаем

$ symfony new --full aws-lambda-linkedin-notifier$ cd aws-lambda-linkedin-notifier$ composer require eniams/linkedin-notifier

Включаем шлюз (см. документацию)

<?php// config/bundles.phpreturn [// others bundles,Eniams\Notifier\LinkedIn\LinkedInNotifierBundle::class => ['all' => true]];// .envLINKEDIN_DSN=

Логика публикации контента

<?phpclass PostContentController{    /**     * @Route("/contents", name="post_content", methods="POST")     */    public function __invoke(NotifierInterface $notifier, Request $request)    {        if(null !== $message = (\json_decode($request->getContent(), true)['message'] ?? null)) {            $notifier->send(new Notification($message, ['chat/linkedin']));            return new JsonResponse('message posted with success', 201);        }        throw new BadRequestException('Missing "message" in body');    }}

Логика проста: мы предоставляем доступ к API через маршрут /contents, который принимает запрос POST с сообщением message в его теле.

В 11-й строке мы отправляем публикуемое сообщение в LinkedIn благодаря Symfony и шлюзу это делается очень просто!

Будучи профессиональными разработчиками, покроем этот код автотестами:

<?phpclass PostContentControllerTest extends WebTestCase    /**     * @dataProvider methodProvider     */    public function testANoPostRequestShouldReturnA405(string $method)    {        $client = static::createClient();        $client->request($method, '/contents');        self::assertEquals(405, $client->getResponse()->getStatusCode());    }    public function testAPostRequestWithoutAMessageInBodyShouldReturnA400()    {        $client = static::createClient();        $client->request('POST', '/contents');        self::assertEquals(400, $client->getResponse()->getStatusCode());    }    public function testAPostRequestWithAMessageInBodyShouldReturnA201()    {        $request = new Request([],[],[],[],[],[], json_encode(['message' => 'Hello World']));        $notifier = new class implements NotifierInterface {            public function send(Notification $notification, Recipient ...$recipients): void            {            }        };        $controller = new PostContentController();        $response = $controller->__invoke($notifier, $request);        self::assertEquals(201, $response->getStatusCode());    }    public function methodProvider()    {        return [            ['GET'],            ['PUT'],            ['DELETE'],        ];    }view rawTestPostContentController.php hosted with  by GitHub

Код готов! Пришло время его развернуть

Стоп! Что?! AWS Lambda не поддерживает PHP!

Именно так: AWS Lambda поддерживает не все языки программирования, а только некоторые, в том числе Go, Java, Python, Ruby, NodeJS и .NET.

Теперь надо учить новый язык и переписывать код? Надеюсь, нет!

Ничего переписывать не придется благодаря Матье Напполи (Matthieu Nappoli), создателю Bref.sh, и замечательной команде, помогающей ему поддерживать этот проект с открытым исходным кодом.

Bref позволяет развертывать PHP-приложения в AWS и запускать их на AWS Lambda.

Конфигурация развертывания

Добавим в kernel.php обработку логов:

<?php    // Kernel.php    public function getLogDir(): string    {        if (getenv('LAMBDA_TASK_ROOT') !== false) {            return '/tmp/log/';        }        return parent::getLogDir();    }    public function getCacheDir()    {        if (getenv('LAMBDA_TASK_ROOT') !== false) {            return '/tmp/cache/'.$this->environment;        }        return parent::getCacheDir();    }

Подготовим фреймворк Serverless (см. документацию):

$ npm install -g serverless$ serverless config credentials --provider aws --key  --secret$ composer require bref/bref

Зададим конфигурацию в файле serverless.yaml:

service: notifier-linkedin-apiprovider:    name: aws    region: eu-west-3    runtime: provided    environment: # env vars        APP_ENV: prod        LINKEDIN_DSN: YOUR_DSNplugins:    - ./vendor/bref/breffunctions:    website:        handler: public/index.php # bootstrap         layers:            - ${bref:layer.php-73-fpm} # https://bref.sh/docs/runtimes/index.html#usage         timeout: 28 # Timeout to stop the compute time        events:            - http: 'POST /contents' # Only POST to /contents are allowedpackage:    exclude:        - 'tests/**'view rawserverless.yaml hosted with  by GitHub

Развертываем!

$ serverless deployServerless: Packaging service...Serverless: Excluding development dependencies...Serverless: Uploading CloudFormation file to S3...Serverless: Uploading artifacts...Serverless: Uploading service notifier-linkedin-api.zip file to S3 (10.05 MB)...Serverless: Validating template...Serverless: Updating Stack...Serverless: Checking Stack update progress.......................Serverless: Stack update finished...Service Informationservice: notifier-linkedin-apistage: devregion: eu-west-3stack: notifier-linkedin-api-devresources: 15api keys:  Noneendpoints:  POST - https://xxx.execute-api.eu-west-3.amazonaws.com/dev/contentsfunctions:  website: notifier-linkedin-api-dev-websitelayers:  NoneServerless: Removing old service artifacts from S3...Serverless: Run the "serverless" command to setup monitoring, troubleshooting and testing.

Теперь наш код развернут в AWS Lambda, а API доступен по адресу https://xxx.execute-api.eu-west-3.amazonaws.com/dev/contents.

Попробуем:


Перевод материала подготовлен в рамках курса "Symfony Framework". Если интересно узнать о курсе больше, приглашаем на день открытых дверей онлайн, где преподаватель расскажет о формате и программе обучения.

Подробнее..

CloudWatch и Lambda, или Как я перестал бояться и полюбил AWS

23.04.2021 14:18:12 | Автор: admin

Облачные провайдеры это реактор, где вместо обогащённого урана используется твой кошелёк. В позапрошлом году наша компания начала активно применять облака и мы в полной мере ощутили это на себе: несколько команд разрабатывали отдельные продукты, и для большинства тестов запускались виртуальные мощности в AWS. Мы с коллегами получили сертификаты от Amazon, и это, вместе с наглядностью происходящего, Free Tier и Soft Limitами, создавало ложное чувство спокойствия за свой бюджет. А когда этому чувству поддаёшься, получаешь локальный Чернобыль.

После нескольких превышений бюджета и встреч с начальством из-за забытых виртуалок мне надоело оправдываться, и я решил предотвратить лишние расходы, тем более что было известно, сколько можно потратить. Я использовал связку CloudWatch и Lambda и этот способ себя оправдал.

Почему настройка оповещений это плацебо

Первый способ, на который указывает сам AWS это настройка бюджетов AWS Budgets и оповещений из них. Да, здесь есть очевидные плюсы: это решение очень легко настроить, а сервис предупреждает в случае превышения текущего или прогнозируемого бюджета. С сентября 2020 г. он даже научился прогнозировать потребление и отправку событий в Amazon SNS.

Но объективно это просто ещё одна загорающаяся лампочка: оповещение может прийти на почту в середине ночи, а узнаёшь об этом только в понедельник утром. К тому же, по умолчанию AWS Budgets ничего не делает после того как заметил превышение. Он отлично подходит для контроля конкретных задач (например, отмеряет скорость расходования средств на проде), но среагирует слишком поздно, если у нас рядом будут работать запущенные по ошибке или безымянные виртуальные машины. Не подойдёт.

Как взять под контроль облачный ядерный реактор

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

Я решил, что нужно где-то разместить логику реагирования и дёргать за различные API в облаке. Проще всего это сделать в AWS Lambda, так как Serverless может работать бесплатно, логика реагирования оформляется как на NodeJS, так и на Python, а простота API-вызовов из облака и ролевая модель доступа сокращают время на тестирование такого решения.

Но как тогда нам собирать информацию о запущенных инстансах и их потреблении? Зная время работы и цену инстанса в час, можно соотнести потребление с нашим бюджетом. Цены инстансов статическая и общедоступная информация. Тут главный вопрос как давно стартовал инстанс?

Узнав на него ответ, можно принять решение: выключать инстанс за избыточностью или оставлять, потому что он нам по каким-то причинам нужен или пока не мешает.

Какие есть инструменты для контроля трат на AWS

В AWS есть несколько сервисов, которые помогают контролировать бюджет. В процессе поиска я рассматривал все возможные варианты, так как у каждого из них есть свои сильные стороны и ограничения:

  • Cost Explorer даёт подробную разбивку по сервисам, но единственное, что он показывает, это сколько аккаунт уже успел потратить. Ничего не знает про запущенные в данный момент виртуальные мощности, но с его помощью можно оптимизировать траты.

  • Budgets внутри Cost Explorer. С сентября 2020 г. он научился определять Usage в часах, но всё ещё не даёт ответа, как идентифицировать забытые сущности.

  • CloudTrail мощный инструмент для контроля всего, что происходит в облаке. С его помощью можно раскрывать все сущности, совершившие любое действие, и узнавать точное время. Главный его недостаток для мой задачи он собирает вообще всю информацию в массивные лог-файлы, без конкретного объяснения в какой момент он заканчивает один файл и начинает другой. Писать целый парсер среди множества файлов под одну задачу это как из пушки по воробьям.

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

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

Накопление данных Lambda + S3

Так как сложные варианты мне не подошли, я остановился на простом: решил накапливать знание о времени работы каждого инстанса самостоятельно, благо хранение файлов это тривиальная задача для Lambda + S3.

Для упрощения тестирования и читаемости кода разобьём задачу между несколькими Lambda:

  • отдельно собираем информацию по запущенным инстансам;

  • решаем, какие инстансы заслуживают выключения, а какие нет;

  • выключаем ненужные инстансы.

Оркестратором простой цепочки Lambda я сделал Step Functions, который может запускаться по расписанию из CloudWatch.

Пример решения

Файлы для Proof of Concept вы можете найти по ссылке на GitHub, а ниже я распишу, что делает этот код. В жизни он дорабатывался под нужды команд и использовался до тех пор, пока мы не обучили достаточное количество сотрудников соблюдать правила работы с облаком.

Что тут происходит?

Каждые 5 минут CloudWatch отправляет заготовленный вызов в Step Functions, который управляет последовательным запуском четырёх Lambda-функций. Каждая Lambda-функция исполняет код на JavaScript (версии Node.js 10.x), использует сервисы EC2, Config или S3, и завершает свою работу передачей JSON в следующую Lambda. Исполнение скрипта завершается записью логов в CloudWatch Logs.

Как это работает?

CloudWatch Event > Step Function > 4 Lambda functions > CloudWatch Logs

  1. Get List of Working Instances получает список работающих инстансов и передает его через JSON в следующую Lambda-функцию

  2. Update Budget Usage делает много вещей, но главное обновляет данные файла в S3-хранилище.

  3. Terminate Instances выключает инстансы, которые превышают бюджет или нас не устраивают.

Как устанавливать?

После размещения кода и настройки ролей в Lambda-функциях необходимо составить схему работы через Step Functions, а также привязать событие CloudWatch Event Rule, которое будет запускать систему каждые 5 минут.

Как бы я подошёл к этой проблеме сейчас

Облака постоянно развиваются, и завтра может появиться очередная машина Судного дня, которая изменит ландшафт до неузнаваемости. С момента написания кода до публикации этой статьи прошло почти 2 года, в течение которых AWS доработал свой сервис AWS Budgets: теперь он позволяет отслеживать не только затраты, но и потребление с расширенным набором параметров например, по тегам.

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

Если бы я сейчас начал заново решать проблему избыточного времени работы, то в S3 вместо файла бюджета оказался бы файл с договорённостями по использованию тегов. Lambda вместо оценки времени работы немедленно выключала бы все мощности, не подходящие по тегам. А контроль за расходами перешёл бы в связку AWS Budget и SNS с той же Lambda, управляющей их отключением.

Подробнее..

Облачные Gateway API зачем нужны подобные сервисы и чем они отличаются у разных платформ

19.05.2021 16:06:46 | Автор: admin

Добро пожаловать в современный интернет, где большая часть взаимодействия приходится на интерфейсы прикладного программирования API. На API держится цифровой бизнес: с ними стало возможным предоставлять и получать услуги через приложения и подключённые к Сети устройства. Платёжные системы? Работают через API. Интерактивная карта, показывающая, как добраться от метро до офиса? Снова API. Даже бэкенд строится на API.

Похоже, мы окружены значит, придётся разбираться. Что такое API, на Хабре уже рассказывали, а я предлагаю рассмотреть поподробнее реализацию API Gateway на облачных платформах.

Зачем вообще нужны Gateway API

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

Представьте себе: у вас есть интернет-магазин по продаже реплик молота Тора. Для удобства пользователя имеется как сайт под десктоп и мобильные устройства, так и приложения для Android и iPhone, которые взаимодействуют с сервером через REST API.

Чтобы на странице товара отображались верные данные, нам нужно обратиться к нескольким службам: в одной учитывается наличие молота, в другой записаны материал, вес и длина ручки, в третьей сохраняются отзывы клиентов, а цена вообще указана в четвёртой. API Gateway позволяет обойтись одним запросом.

API Gateway выполняет множество задач: принимает, обрабатывает и распределяет запросы, контролирует трафик, осуществляет мониторинг и контроль доступа.

В микросервисной архитектуре паттерн API Gateway появился в качестве службы, обеспечивающей единую точку входа для веб-приложений и API, эдакой серверной части для клиентской части. В чём польза именно для микросервисов?

Например возможность повторного использования компонентов, упрощение бэкенда приложения, обеспечение доступа к статическим веб-страницам и документам, удобная проверка авторизации и подбор оптимального для каждого типа клиента API как это делает Netflix API Gateway.

Что такое облачные API Gateway

Облачные структуры заимствуют многие паттерны микросервисов в том числе API Gateway и необходимость в их применении. API Gateway упрощает интеграцию приложения с сервисами облачной платформы и позволяет в полной мере использовать её возможности.

Классический API Gateway представляет собой шлюз между пользователями и любым количеством сервисов (API), выполняющий функцию обратного прокси, как Nginx и HAProxy. В то же время облачная версия API Gateway уже полноценный сервис для разработчиков, который простым в исполнении не назовёшь.

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

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

Как облачные API Gateway облегчают жизнь

Итак, в разработке всё чаще применяются облачные технологии и закономерно возникает вопрос об облачных шлюзах API, их особенностях и преимуществах. Стоит ли их применять или лучше как-нибудь по старинке?

Для чего разработчики вообще выбирают облачные API Gateway?

  • Чтобы сократить время разработки API Gateway создаётся в несколько кликов, а интеграция с облачными сервисами выбранной платформы занимает пару минут.

  • Чтобы обеспечить минимальную задержку ответа на запрос об этом позаботится система автоматического масштабирования.

  • Чтобы лучше контролировать трафик к примеру, с помощью ограничения нагрузки на количество запросов в секунду для каждого HTTPметода. А при необходимости можно сформировать кеш с настраиваемыми ключами и указанием жизненного срока в секундах.

  • Чтобы отлаживать API встроенными средствами меньше головной боли.

  • Чтобы генерировать клиентские SDK.

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

  • Чтобы контролировать доступ к API и управлять его жизненным циклом от создания до публикации.

  • Чтобы уведомление приходило от сервиса, а не от разозлённого клиента, если что-то идёт не так.

  • Чтобы настраивать авторизацию удобным методом с помощью средств Lambda или токенов OAuth.

  • Чтобы отслеживать показатели к примеру, количество запросов, задержку вызова и количество ошибок на удобной панели мониторинга с визуальным интерфейсом.

  • Чтобы платить только за количество запросов в месяц или пользоваться сервисами бесплатно, если не выходить за рамки определённой цифры.

Как используют облачные API Gateway

Виртуальная доска

Простое приложение, состоящее из двух конечных точек POST для записи сообщений и GET для извлечения трёх последних сообщений. Реализовано с помощью AWS Gateway, AWS DynamoDB, AWS Serverless Application Model и Lambda.

Голосовой сервиc

Рецепт сервиса записи к врачу и регистрации в поликлинике, разработанный коммуникационной платформой Voximplant и Yandex.Cloud.

Бот для телеграма

Запуск бота на Python внутри одного из облачных сервисов, а именно Yandex.Cloud.

Трекер пульсометрии

Один из вариантов решения для сбора данных пульсовой оксиметрии для нескольких пользователей, отслеживания этих данных и обмена ими. Фронт написан на VueJS, бэкенд реализован с применением Amazon API Gateway.

Статический сайт в облаке

Пошаговая инструкция по деплою статического сайта в облако, прикрутке к нему сертификата Lets Encrypt, домена второго уровня и настройке API-шлюза в системе Yandex.Cloud.

Блог

И снова приложение на микросервисах реализация клиентской части на VueJS, взаимодействие настроено через REST API и gRPC, а в качестве базы данных используется MongoDB.

Реализация на разных облачных платформах

Сервис API Gateway предлагают несколько облачных платформ и все они предоставляют более-менее схожий пакет услуг. Так в чём же разница?

Azure API Management

Платформа гибридного кросс-облачного управления через API Позволяет в том числе самостоятельное размещение шлюза в своей среде и управление им через API Azure. Мультиклауд для отважных.

Amazon API Gateway

Amazon API Gateway пожалуй, самый известный сервис, предназначенный для создания, публикации, обслуживания, мониторинга и обеспечения безопасности API в любых масштабах.

Документация включает подробные инструкции от развёртывания RESTful API при создании бессерверного веб-приложения до работы с HTTP API, поэтому не придётся искать примеры по всей Сети, чтобы разобраться.

Особенности:

  • Создание API RESTful при помощи API HTTP или API REST.

  • Интерфейсы API WebSocket для разработки приложений, которым требуется двусторонняя связь в режиме реального времени.

  • Частная интеграция с AWS ELB и AWS Cloud Map.

  • Ключи API для сторонних разработчиков.

  • Генерирование клиентских SDK на многих языках, включая JavaScript, iOS и Android.

  • Внедрение подписи четвёртой версии для API REST и API WebSocket при авторизации и проверке запросов API к другим сервисам AWS API Gateway.

  • Авторизация с помощью AWS Lambda.

  • Amazon API Gateway можно пользоваться бесплатно целый год пока ваши потребности не превышают один миллион вызовов API, полученных для API REST, один миллион вызовов API, полученных для API HTTP, и один миллион сообщений и 750 000 минут подключения для API WebSocket в месяц.

  • Обучение с помощью пошаговых учебных пособий, а также доступ к более чем 500 бесплатным онлайн-курсам.

Oracle API Gateway

Сервис Oracle API Gateway стал доступен любому пользователю в конце 2019 года и уже пытается активно конкурировать с Amazon API Gateway. Получится ли у него отвоевать хотя бы часть аудитории у AWS, нам только предстоит увидеть а сравнивать всегда интереснее на собственном опыте. Почитать про создание своего Gateway API можно вот в этой статье.

Особенности платформы:

  • RESTful API в комбинации с Oracle Functions, а также возможностями Kubernetes и Compute.

  • Каждая служба в облачной инфраструктуре Oracle интегрируется с IAM для аутентификации и авторизации (консоль, SDK или CLI и REST API).

  • Интеграция с системой управления доступом Oracle Cloud Infrastructure.

  • Бесплатный период длительностью в тридцать дней, чтобы опробовать возможности широкого спектра сервисов Oracle Cloud, в том числе к Databases, Analytics, Compute, Container Engine for Kubernetes и т. д.

  • Платформа Oracle Cloud позиционирует себя как более экономичное решение, чем AWS, и в качестве примера упоминает, что соотношение цены и производительности в 2 раза выше, а стоимость исходящей пропускной способности составляет только 1/4 от стоимости у AWS.

Google API Gateway

Сервис перешёл на стадию публичного бета-тестирования 18 сентября 2020 года, так что пока о нём известно довольно мало и тем интереснее пронаблюдать за его развитием.Сейчас Google API Gateway позволяет управлять API других сервисов облачной платформы Cloud Functions, Cloud Run, App Enginе, Compute Engine и Google Kubernetes Engine. Настроить работу с Cloud Run, к примеру, можно всего за несколько минут.

Особенности:

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

  • До 2 миллионов запросов в месяц бесплатно.

  • Наличие пробной версии. Google Cloud предоставляет виртуальный кредит в размере 300 долларов, который необходимо потратить в течение последующих трёх месяцев. После окончания бесплатного периода оплата не начинает взиматься автоматически на платный тариф необходимо перейти вручную.

SberCloud API Gateway

SberCloud API Gateway использует наработки Huawei, а информации об особенностях применении в Сети можно найти немного, но здесь вам поможет Хабр: после недавнего хакатона один из участников рассказал о впечатлениях от SberCloud и сравнил функциональность с более известным AWS.

Особенности:

  • Доступ к облачным продуктам для физических лиц возможен только с помощью входа/регистрации через Сбер ID.

  • Управление квотами и регулирование запросов пользователей.

  • Встроенный инструмент отладки.

  • Визуализированная панель мониторинга API.

  • Создание каналов VPC для доступа к бэкенд-сервисам в сети VPC и управления нагрузкой путём отправки API-запросов на различные серверы.

  • Цифровая подпись, которая вступает в силу только после привязки к API.

  • Никакой минимальной или предварительной платы оплачивается только фактическое использование.

  • Возможность монетизации API.

Yandex API Gateway

23 сентября 2020 года к четырём сервисам платформы Yandex.Cloud прибавились ещё два Yandex API Gateway и база данных Yandex Database в режиме Serverless.

Yandex API Gateway интегрирован с другими сервисами платформы, благодаря чему возможна отправка HTTP-запросов с помощью функций Yandex Cloud Functions, доступ к статическим данным осуществляется Yandex Object Storage напрямую из хранилища, а запуск произвольных HTTP-сервисов в облаке возможен с помощью Yandex Managed Service for Kubernetes. Так что спектр применения широк к примеру, внутри облака можно запустить приложение на Express.js.

К слову, до октября 2021 года на бессерверные решения действуют специальные тарифы, которые позволяют создавать и размещать небольшие сайты и сервисы бесплатно.

Особенности:

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

  • Поддержка OpenAPI 3.0.

  • Обработка запросов только по протоколу HTTPS. Сервис автоматически перенаправляет все запросы к API-шлюзам по протоколу HTTP на их HTTPS-версии.

  • Интеграция с системой управления доменами сервиса Certificate Manager. Для обеспечения TLS-соединения используется привязанный к домену сертификат.

  • Система квот и лимитов. Максимальный размер спецификации 3,5 МБ. Количество API-шлюзов в одном облаке 10, но, в отличие от максимального размера спецификации, меняется по запросу в техническую поддержку.

Тарификация по количеству запросов к созданным API-шлюзам и исходящему трафику. При этом запросы к API-шлюзам до 100 000 запросов в месяц не тарифицируются. Как, кстати, и входящий трафик, а также передача данных между сервисами Yandex.Cloud. Больше подробностей можно узнать в сообществе Serverless в Telegram:Yandex Serverless Ecosystem. Мы регулярно встречаемся в виртуальном пространстве и похоже созревает потребность в очной встрече.

Подробнее..

Перевод Бессерверные функции длямикросервисов хорошее решение, но не забывайте про гибкость

25.03.2021 22:22:00 | Автор: admin

В преддверии старта курса "Microservice Architecture" подготовили для вас традиционный перевод материала.


При проектировании и планировании новой архитектуры на основе (микро) сервисов бывают моменты, когда архитекторам приходится думать о стратегии развертывания и, следовательно, задаваться вопросом: Должны ли мы развернуть этот (микро) сервис как бессерверную функцию или лучше поместить его в контейнер? Или, быть может, лучше использовать выделенную виртуальную машину (VM)?

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

Кроме того, их модель оплаты по мере использования (pay per use) очень привлекательна, когда мы не уверены, какая нагрузка придется на наше приложение. Позже, когда сервис станет зрелым и нагрузка станет предсказуемой, может оказаться целесообразным перейти к более традиционной топологии развертывания, основанной на контейнерах или выделенных серверах.

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

Бессерверные функции

Бессерверные функции (Serverless Functions, также известные как FaaS, функция как сервис) - это блоки определенной логики, которые создаются и выполняются в ответ на заданные события, такие как HTTP-запросы или сообщения, полученные в топике Kafka. По завершении своего выполнения такие функции исчезают, по крайней мере, логически, и их стоимость возвращается к нулю.

FAAS реагирует на HTTP-запросы: множественное параллельное выполнение и модель pay per useFAAS реагирует на HTTP-запросы: множественное параллельное выполнение и модель pay per use

Все крупные общедоступные облака имеют FAAS предложения (AWS Lambda, Azure Functions, Google Functions, Oracle Cloud Functions). Кроме того FAAS также могут быть доступны в локальных вариациях с помощью таких фреймворков, как Apache OpenWhisk. У них есть некоторые ограничения с точки зрения ресурсов (например, для AWS максимум 10 ГБ памяти и 15 минут времени выполнения), но они могут покрыть многие варианты использования современных приложений.

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

Фронтэнд приложение, обслуживаемое микросервисами, реализованными на различных моделях развертывания.Фронтэнд приложение, обслуживаемое микросервисами, реализованными на различных моделях развертывания.

Преимущества FaaS

Когда бессерверные функции простаивают, они ничего вам не стоят (модель Pay per use). Если бессерверная функция вызывается 10 клиентами одновременно, 10 ее инстансов запускаются почти одновременно (по крайней мере, в большинстве случаев). Полное предоставление инфраструктуры, управление, высокая доступность (по крайней мере, до определенного уровня) и масштабирование (от 0 до лимитов, определенных клиентом) полностью предоставляются командами специалистов, работающих за кулисами.

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

Таким образом, FaaS подразумевает два больших преимущества.

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

  • Фокус на том, что отличает ваш бизнес: сконцентрируйте силы на разработке наиболее важных приложений, не тратя драгоценную энергию на сложные области, такие как современные инфраструктуры, которые FaaS предлагает вам в качестве товара.

Эластичность и затраты

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

Новый сервис должен быстро выходить на рынок с минимально возможными стартовыми вложениями и с самого начала должен быть хорошим сервисом.

Когда мы хотим запустить новый сервис, модель FaaS, вероятно, будет лучшим выбором. Бессерверные функции можно быстро настроить и минимизировать затраты на инфраструктуру. Их модель pay per use подразумевает отсутствие стартовых вложений. Их возможности масштабирования обеспечивают стабильное время отклика при различных уровнях нагрузки.

Если по прошествии некоторого времени нагрузка станет более стабильной и предсказуемой, тогда ситуация может измениться, и более традиционная модель, основанная на выделенных ресурсах, будь то кластеры Kubernetes или виртуальные машины, может стать более удобной, чем FaaS.

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

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

Бессерверные функции могут СЭКОНОМИТЬ ВАМ МНОГО ДЕНЕГ, ровно также как и могут СТОИТЬ ВАМ БОЛЬШИХ ДЕНЕГ

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

Пример приложения. Рассмотрим приложение, которое получает 3.000.000 запросов в месяц. Обработка каждого запроса с помощью Lambda с 4 ГБ памяти занимает 500 мс (ЦП назначается автоматически в зависимости от памяти).

FaaS имеет модель pay per use, поэтому, независимо от кривой нагрузки (будь то пиковая или фиксированная), стоимость в месяц является фиксированной: 100,60 долларов США.

С другой стороны, если мы рассмотрим модель, основанную на выделенных виртуальных машинах, все будет по-другому, и стоимость сильно зависит от формы кривой нагрузки.

Сценарий с пиками нагрузки. Если для нагрузки характерны пиковые моменты, и мы хотим гарантировать постоянное хорошее время отклика для наших клиентов, нам необходимо определить размер инфраструктуры, чтобы выдерживать пиковую нагрузку. Если на пике у нас есть 10 одновременных запросов в секунду (что вполне возможно, если 3.000.000 запросов сосредоточены в определенные часы дня или в определенные дни, например, в конце месяца), вполне возможно, что нам понадобится виртуальная машина (AWS EC2) с 8 процессорами и 32 ГБ памяти, чтобы обеспечить ту же производительность, что и Lambda. В этом случае ежемесячная стоимость подскочит до 197,22 долларов США (можно сэкономить при заключении многолетнего контракта, но это снижает финансовую гибкость). Затраты увеличились почти вдвое. Эту разницу можно уменьшить путем динамического включения и выключения экземпляров EC2 в зависимости от нагрузки, но для этого требуется, чтобы нагрузка была предсказуемой, что увеличивает сложность и стоимость решения.

Сценарий с равномерной нагрузкой. Если нагрузка в основном равномерная, то дела обстоят совсем по другому. Если нет пиков, то мы можем легко выдержать нагрузку с помощью гораздо более дешевой машины. Вероятно, будет достаточно виртуальной машины с 2 процессорами и 8 МБ памяти, а ежемесячные затраты в этом случае составят 31,73 доллара США, что меньше трети стоимости Lambda.

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

Тут возникает вопрос: как мы можем достичь такой гибкости? Насколько это будет сложно?

Анатомия кодовой базы современного приложения

При использовании современных методов разработки кодовая база приложения обычно разбивается на логические области

  • Логика приложения. Код (обычно написанный на таких языках, как Java, TypeScript или Go), который реализует то, что должно делать наше приложение

  • DevSecOps (CI/CD). Обычно это скрипты и файлы конфигурации, которые автоматизируют сборку, тестирование, проверки безопасности и развертывание приложения.

Логика приложения

Мы можем разделить логику приложения бэкенд сервиса на логические части

  • Бизнес-логика. Код, который реализует поведение службы, выраженное в форме логических API (методов или функций), которые обычно ожидают некоторые данные, например, в формате JSON, в качестве входных и возвращают некоторые данные в качестве выходных. Этот код не зависит от технических механизмов, связанных с реальной средой, в которой он выполняется, будь то контейнер, бессерверная функция или сервер приложения. Находясь в контейнере, эти логические API могут быть вызваны чем-нибудь наподобие Spring Boot (если языком является Java), Express (с Node) или Gorilla (с Go). При вызове в бессерверной функции он будет использовать конкретный механизм FaaS, реализованный конкретным облачным провайдером.

  • Код, связанный с развертыванием. Код, который касается механики среды выполнения. Если необходимо поддерживать более одной модели развертывания, должны быть разные реализации этой части (но только этой части). В случае развертывания в контейнерах это та часть, где сосредоточены зависимости от аналогов Spring Boot, Express или Gorilla. В случае FaaS эта часть будет содержать код, реализующий механизмы, определенные конкретным облачным провайдером (функции AWS Lambda, Azure или Google Cloud, которые имеют собственные проприетарные библиотеки для вызова бизнес-логики).

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

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

  • Кода, связанный с развертыванием импортирует модули бизнес-логики

  • Бизнес-логика никогда не импортирует какие-либо модули/пакеты, которые зависят от конкретной среды выполнения.

Следуя этим двум простым правилам, мы максимизируем объем кода (бизнес-логики), пригодного для использования всеми моделями развертывания, и, следовательно, минимизируем стоимость перехода от одной модели к другой.

Невозможно абстрактно оценить относительные размеры частей бизнес-логики и кода, связанного с развертыванием. Но на примере, анализируя одну простую интерактивную игру, развертываемую как на AWS Lambda, так и на Google Application Engine, выясняется, что код, связанный с развертыванием, составляет 6% от кодовой базы (всего около 7 200 строк кода). Таким образом, 94% кодовой базы одинаковы, независимо от того, работает ли служба в Lambda или в контейнере.

DevSecOps (CI/CD)

Это часть кодовой базы, отвечающая за автоматизацию сборки, тестирования (включая шлюзы безопасности) и развертывания приложения.

Этапы сборки и развертывания по своей природе сильно зависят от фактической среды выполнения, выбранной для выполнения сервиса. Если мы выберем контейнеры, то на этапе сборки, скорее всего, будут использоваться инструменты Docker. Если мы выберем поставщика FaaS, этапа сборки как такового не будет, но будут команды для загрузки кода в механизм FaaS и инструктирования на точке входа при вызове бессерверной функции. Точно так же могут быть проверки безопасности, специфичные для модели FaaS.

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

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

Стремитесь изолировать то, что не зависит от модели развертывания, и максимизировать это

Если мы хотим быть гибкими в выборе модели развертывания, мы должны сохранять четкие границы между тем, что относится к коду, связанному с развертыванием, и тем, что является независимым. Это означает, что наши решения нужно продумывать заранее, чтобы минимизировать первое и максимизировать второе, а также обеспечить четкое разделение этих частей на протяжении всего жизненного цикла проекта/продукта, возможно, используя регулярные ревью дизайна и кода.

Разделение обязанностей между частями кодовой базы современного приложенияРазделение обязанностей между частями кодовой базы современного приложения

Заключение

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

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

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

Следуя этим нескольким простым рекомендациям по архитектуре с самого начала и включив регулярные процессы экспертной оценки, мы можем сохранить эту гибкость.


Узнать подробнее о курсе "Microservice Architecture".

Подробнее..

Перевод Оптимизируем затраты с помощью AWS Cost Explorer

15.04.2021 14:13:15 | Автор: admin

У Amazon Web Services отличный бесплатный пакет:хороший набор сервисов и щедрая раздача кредитов для разработчиков. Я был уверен: проблем с оплатой моего окружения не будет, поэтому о расходах не беспокоился. Мое приложение на 100% serverless, и я всегда укладывался в уровень бесплатного использования, так что просто игнорировал вопрос оплаты. В какой-то момент я расслабился и потерял бдительность.

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

AWS Cost Explorer

Сервис AWS Billing dashboard хорошо подходит для оплаты счетов и показывает график прогноза счетов за текущий месяц. Но этот сервис едва ли претендует на звание лучшего в AWS. Месячный прогноз часто врет, поэтому лучше игнорировать его вовсе.

Помимо Billing Dashboard, соседний Cost Explorer. Он предоставляет очень хорошую детализацию и возможность прогнозирования. Кроме просмотра стандартной разбивки потребления в AWS, можно писать код под Cost Explorer, извлекая много ценной информации. И мне это дело зашло.

Используя Cost Explorer, я смог заранее определить уязвимые места и исправить их задолго до того, как с меня начнут списывать за них деньги. Еще раз спасибо AWS.

Пользовательский интерфейс

Прежде чем начать работать, надо познакомиться со стандартным видом консоли Billing Dashboard. Нужно сначала включить её, что будет стоить денег. Лучше сделать это заранее, чтобы потом не было мучительно больно. У кого много остатку, тот не боится недостатку!

Пользовательский интерфейс интуитивно понятный: здесь мы можем получать подробные отчеты по дням / услуге; или сгруппированные по времени / региону / услуге и множеством других способов.

Это мой график потраченного за последние несколько месяцев.

Отчеты

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

Бюджеты

Cost Explorer не ограничиваетсяотчетами и графиками. Чащеначинают осваивать AWS с небольших бюджетов и настроенных под них оповещений, но есть возможность сделать гораздо больше. Можно задать независимые бюджеты (по стоимости и использованию) для отдельных сервисов, и даже для отдельных инстансов или операций внутри сервиса.

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

Обнаружение аномалий

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

Cost Explorer API

Стандартный вид консоли управления меня устраивает но только для эпизодического ознакомления. Для того, чтобы получить нечто большее, AWS предоставляет отличный API. Репозиторий AWS Samples Github дает нам наглядный пример доступа к API Cost Explorer.

Мой код основан на этом примере, и позволяет разработать собственный отчет для Cost Explorera.

Код Lambda функции

import osimport sys# Required to load modules from vendored subfolder (for clean development env)sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "./vendored"))import boto3import datetimeimport loggingimport pandas as pd#For datefrom dateutil.relativedelta import relativedelta#For emailfrom email.mime.application import MIMEApplicationfrom email.mime.multipart import MIMEMultipartfrom email.mime.text import MIMETextfrom email.utils import COMMASPACE, formatdateSES_REGION="ap-south-1"CURRENT_MONTH = True#Default exclude support, as for Enterprise Support#as support billing is finalised later in month so skews trends    INC_SUPPORT = os.environ.get('INC_SUPPORT')if INC_SUPPORT == "true":    INC_SUPPORT = Trueelse:    INC_SUPPORT = FalseTAG_VALUE_FILTER = os.environ.get('TAG_VALUE_FILTER') or '*'TAG_KEY = os.environ.get('TAG_KEY')class CostExplorer:    """Retrieves BillingInfo checks from CostExplorer API    >>> costexplorer = CostExplorer()    >>> costexplorer.addReport(GroupBy=[{"Type": "DIMENSION","Key": "SERVICE"}])    >>> costexplorer.generateExcel()    """        def __init__(self, CurrentMonth=False):        #Array of reports ready to be output to Excel.        self.reports = []        self.client = boto3.client('ce', region_name='us-east-1')        # self.end = datetime.date.today().replace(day=1)        self.riend = datetime.date.today()        self.end = self.riend        # Default is last 12 months        self.start = (datetime.date.today() - relativedelta(months=+12)).replace(day=1) #1st day of month 12 months ago        self.ristart = (datetime.date.today() - relativedelta(months=+11)).replace(day=1) #1st day of month 11 months ago        self.sixmonth = (datetime.date.today() - relativedelta(months=+6)).replace(day=1) #1st day of month 6 months ago, so RI util has savings values        self.accounts = {}    def addRiReport(self, Name='RICoverage', Savings=False, PaymentOption='PARTIAL_UPFRONT', Service='Amazon Elastic Compute Cloud - Compute'): #Call with Savings True to get Utilization report in dollar savings        type = 'chart' #other option table        if Name == "RICoverage":            results = []            response = self.client.get_reservation_coverage(                TimePeriod={                    'Start': self.ristart.isoformat(),                    'End': self.riend.isoformat()                },                Granularity='MONTHLY'            )            results.extend(response['CoveragesByTime'])            while 'nextToken' in response:                nextToken = response['nextToken']                response = self.client.get_reservation_coverage(                    TimePeriod={                        'Start': self.ristart.isoformat(),                        'End': self.riend.isoformat()                    },                    Granularity='MONTHLY',                    NextPageToken=nextToken                )                results.extend(response['CoveragesByTime'])                if 'nextToken' in response:                    nextToken = response['nextToken']                else:                    nextToken = False            rows = []            for v in results:                row = {'date':v['TimePeriod']['Start']}                row.update({'Coverage%':float(v['Total']['CoverageHours']['CoverageHoursPercentage'])})                rows.append(row)              df = pd.DataFrame(rows)            df.set_index("date", inplace= True)            df = df.fillna(0.0)            df = df.T        elif Name in ['RIUtilization','RIUtilizationSavings']:            #Only Six month to support savings            results = []            response = self.client.get_reservation_utilization(                TimePeriod={                    'Start': self.sixmonth.isoformat(),                    'End': self.riend.isoformat()                },                Granularity='MONTHLY'            )            results.extend(response['UtilizationsByTime'])            while 'nextToken' in response:                nextToken = response['nextToken']                response = self.client.get_reservation_utilization(                    TimePeriod={                        'Start': self.sixmonth.isoformat(),                        'End': self.riend.isoformat()                    },                    Granularity='MONTHLY',                    NextPageToken=nextToken                )                results.extend(response['UtilizationsByTime'])                if 'nextToken' in response:                    nextToken = response['nextToken']                else:                    nextToken = False            rows = []            if results:                for v in results:                    row = {'date':v['TimePeriod']['Start']}                    if Savings:                        row.update({'Savings$':float(v['Total']['NetRISavings'])})                    else:                        row.update({'Utilization%':float(v['Total']['UtilizationPercentage'])})                    rows.append(row)                  df = pd.DataFrame(rows)                df.set_index("date", inplace= True)                df = df.fillna(0.0)                df = df.T                type = 'chart'            else:                df = pd.DataFrame(rows)                type = 'table' #Dont try chart empty result        elif Name == 'RIRecommendation':            results = []            response = self.client.get_reservation_purchase_recommendation(                #AccountId='string', May use for Linked view                LookbackPeriodInDays='SIXTY_DAYS',                TermInYears='ONE_YEAR',                PaymentOption=PaymentOption,                Service=Service            )            results.extend(response['Recommendations'])            while 'nextToken' in response:                nextToken = response['nextToken']                response = self.client.get_reservation_purchase_recommendation(                    #AccountId='string', May use for Linked view                    LookbackPeriodInDays='SIXTY_DAYS',                    TermInYears='ONE_YEAR',                    PaymentOption=PaymentOption,                    Service=Service,                    NextPageToken=nextToken                )                results.extend(response['Recommendations'])                if 'nextToken' in response:                    nextToken = response['nextToken']                else:                    nextToken = False            rows = []            for i in results:                for v in i['RecommendationDetails']:                    row = v['InstanceDetails'][list(v['InstanceDetails'].keys())[0]]                    row['Recommended']=v['RecommendedNumberOfInstancesToPurchase']                    row['Minimum']=v['MinimumNumberOfInstancesUsedPerHour']                    row['Maximum']=v['MaximumNumberOfInstancesUsedPerHour']                    row['Savings']=v['EstimatedMonthlySavingsAmount']                    row['OnDemand']=v['EstimatedMonthlyOnDemandCost']                    row['BreakEvenIn']=v['EstimatedBreakEvenInMonths']                    row['UpfrontCost']=v['UpfrontCost']                    row['MonthlyCost']=v['RecurringStandardMonthlyCost']                    rows.append(row)              df = pd.DataFrame(rows)            df = df.fillna(0.0)            type = 'table' #Dont try chart this        self.reports.append({'Name':Name,'Data':df, 'Type':type})    def addReport(self, Name="Default",GroupBy=[{"Type": "DIMENSION","Key": "SERVICE"},],     Style='Total', NoCredits=True, CreditsOnly=False, RefundOnly=False, UpfrontOnly=False, IncSupport=False):        type = 'chart' #other option table        results = []        if not NoCredits:            response = self.client.get_cost_and_usage(                TimePeriod={                    'Start': self.start.isoformat(),                    'End': self.end.isoformat()                },                Granularity='MONTHLY',                Metrics=[                    'UnblendedCost',                ],                GroupBy=GroupBy            )        else:            Filter = {"And": []}            Dimensions={"Not": {"Dimensions": {"Key": "RECORD_TYPE","Values": ["Credit", "Refund", "Upfront", "Support"]}}}            if INC_SUPPORT or IncSupport: #If global set for including support, we dont exclude it                Dimensions={"Not": {"Dimensions": {"Key": "RECORD_TYPE","Values": ["Credit", "Refund", "Upfront"]}}}            if CreditsOnly:                Dimensions={"Dimensions": {"Key": "RECORD_TYPE","Values": ["Credit",]}}            if RefundOnly:                Dimensions={"Dimensions": {"Key": "RECORD_TYPE","Values": ["Refund",]}}            if UpfrontOnly:                Dimensions={"Dimensions": {"Key": "RECORD_TYPE","Values": ["Upfront",]}}            tagValues = None            if TAG_KEY:                tagValues = self.client.get_tags(                    SearchString=TAG_VALUE_FILTER,                    TimePeriod = {                        'Start': self.start.isoformat(),                        'End': datetime.date.today().isoformat()                    },                    TagKey=TAG_KEY                )            if tagValues:                Filter["And"].append(Dimensions)                if len(tagValues["Tags"]) > 0:                    Tags = {"Tags": {"Key": TAG_KEY, "Values": tagValues["Tags"]}}                    Filter["And"].append(Tags)            else:                Filter = Dimensions.copy()            response = self.client.get_cost_and_usage(                TimePeriod={                    'Start': self.start.isoformat(),                    'End': self.end.isoformat()                },                Granularity='MONTHLY',                Metrics=[                    'UnblendedCost',                ],                GroupBy=GroupBy,                Filter=Filter            )        if response:            results.extend(response['ResultsByTime'])            while 'nextToken' in response:                nextToken = response['nextToken']                response = self.client.get_cost_and_usage(                    TimePeriod={                        'Start': self.start.isoformat(),                        'End': self.end.isoformat()                    },                    Granularity='MONTHLY',                    Metrics=[                        'UnblendedCost',                    ],                    GroupBy=GroupBy,                    NextPageToken=nextToken                )                results.extend(response['ResultsByTime'])                if 'nextToken' in response:                    nextToken = response['nextToken']                else:                    nextToken = False        rows = []        sort = ''        for v in results:            row = {'date':v['TimePeriod']['Start']}            sort = v['TimePeriod']['Start']            for i in v['Groups']:                key = i['Keys'][0]                if key in self.accounts:                    key = self.accounts[key][ACCOUNT_LABEL]                row.update({key:float(i['Metrics']['UnblendedCost']['Amount'])})             if not v['Groups']:                row.update({'Total':float(v['Total']['UnblendedCost']['Amount'])})            rows.append(row)          df = pd.DataFrame(rows)        df.set_index("date", inplace= True)        df = df.fillna(0.0)        if Style == 'Change':            dfc = df.copy()            lastindex = None            for index, row in df.iterrows():                if lastindex:                    for i in row.index:                        try:                            df.at[index,i] = dfc.at[index,i] - dfc.at[lastindex,i]                        except:                            logging.exception("Error")                            df.at[index,i] = 0                lastindex = index        df = df.T        df = df.sort_values(sort, ascending=False)        self.reports.append({'Name':Name,'Data':df, 'Type':type})    def generateExcel(self):        # Create a Pandas Excel writer using XlsxWriter as the engine.\        os.chdir('/tmp')        writer = pd.ExcelWriter('cost_explorer_report.xlsx', engine='xlsxwriter')        workbook = writer.book        for report in self.reports:            print(report['Name'],report['Type'])            report['Data'].to_excel(writer, sheet_name=report['Name'])            worksheet = writer.sheets[report['Name']]            if report['Type'] == 'chart':                # Create a chart object.                chart = workbook.add_chart({'type': 'column', 'subtype': 'stacked'})                chartend=13                for row_num in range(1, len(report['Data']) + 1):                    chart.add_series({                        'name':       [report['Name'], row_num, 0],                        'categories': [report['Name'], 0, 1, 0, chartend],                        'values':     [report['Name'], row_num, 1, row_num, chartend],                    })                chart.set_y_axis({'label_position': 'low'})                chart.set_x_axis({'label_position': 'low'})                worksheet.insert_chart('O2', chart, {'x_scale': 2.0, 'y_scale': 2.0})        writer.save()        #Time to deliver the file to S3        if os.environ.get('S3_BUCKET'):            s3 = boto3.client('s3')            s3.upload_file("cost_explorer_report.xlsx", os.environ.get('S3_BUCKET'), "cost_explorer_report.xlsx")        if os.environ.get('SES_SEND'):            #Email logic            msg = MIMEMultipart()            msg['From'] = os.environ.get('SES_FROM')            msg['To'] = COMMASPACE.join(os.environ.get('SES_SEND').split(","))            msg['Date'] = formatdate(localtime=True)            msg['Subject'] = "Cost Explorer Report"            text = "Find your Cost Explorer report attached\n\n"            msg.attach(MIMEText(text))            with open("cost_explorer_report.xlsx", "rb") as fil:                part = MIMEApplication(                    fil.read(),                    Name="cost_explorer_report.xlsx"                )            part['Content-Disposition'] = 'attachment; filename="%s"' % "cost_explorer_report.xlsx"            msg.attach(part)            #SES Sending            ses = boto3.client('ses', region_name=SES_REGION)            result = ses.send_raw_email(                Source=msg['From'],                Destinations=os.environ.get('SES_SEND').split(","),                RawMessage={'Data': msg.as_string()}            )     def lambda_handler(event, context):    costexplorer = CostExplorer(CurrentMonth=False)    #Default addReport has filter to remove Support / Credits / Refunds / UpfrontRI    #Overall Billing Reports    costexplorer.addReport(Name="Total", GroupBy=[],Style='Total',IncSupport=True)    costexplorer.addReport(Name="TotalChange", GroupBy=[],Style='Change')    costexplorer.addReport(Name="TotalInclCredits", GroupBy=[],Style='Total',NoCredits=False,IncSupport=True)    costexplorer.addReport(Name="TotalInclCreditsChange", GroupBy=[],Style='Change',NoCredits=False)    costexplorer.addReport(Name="Credits", GroupBy=[],Style='Total',CreditsOnly=True)    costexplorer.addReport(Name="Refunds", GroupBy=[],Style='Total',RefundOnly=True)    costexplorer.addReport(Name="RIUpfront", GroupBy=[],Style='Total',UpfrontOnly=True)    #GroupBy Reports    costexplorer.addReport(Name="Services", GroupBy=[{"Type": "DIMENSION","Key": "SERVICE"}],Style='Total',IncSupport=True)    costexplorer.addReport(Name="ServicesChange", GroupBy=[{"Type": "DIMENSION","Key": "SERVICE"}],Style='Change')    costexplorer.addReport(Name="Accounts", GroupBy=[{"Type": "DIMENSION","Key": "LINKED_ACCOUNT"}],Style='Total')    costexplorer.addReport(Name="AccountsChange", GroupBy=[{"Type": "DIMENSION","Key": "LINKED_ACCOUNT"}],Style='Change')    costexplorer.addReport(Name="Regions", GroupBy=[{"Type": "DIMENSION","Key": "REGION"}],Style='Total')    costexplorer.addReport(Name="RegionsChange", GroupBy=[{"Type": "DIMENSION","Key": "REGION"}],Style='Change')    if os.environ.get('COST_TAGS'): #Support for multiple/different Cost Allocation tags        for tagkey in os.environ.get('COST_TAGS').split(','):            tabname = tagkey.replace(":",".") #Remove special chars from Excel tabname            costexplorer.addReport(Name="{}".format(tabname)[:31], GroupBy=[{"Type": "TAG","Key": tagkey}],Style='Total')            costexplorer.addReport(Name="Change-{}".format(tabname)[:31], GroupBy=[{"Type": "TAG","Key": tagkey}],Style='Change')    #RI Reports    costexplorer.addRiReport(Name="RICoverage")    costexplorer.addRiReport(Name="RIUtilization")    costexplorer.addRiReport(Name="RIUtilizationSavings", Savings=True)    costexplorer.addRiReport(Name="RIRecommendation") #Service supported value(s): Amazon Elastic Compute Cloud - Compute, Amazon Relational Database Service    costexplorer.generateExcel()    return "Report Generated"

IAM Role

Чтобы запускаться, Lambda функция должна обладать ролью с приведенными ниже правами:

Базовая политика Lambda

{    "Version": "2012-10-17",    "Statement": [        {            "Effect": "Allow",            "Action": [                "logs:CreateLogGroup",                "logs:CreateLogStream",                "logs:PutLogEvents"            ],            "Resource": "*"        }    ]}

Разрешение для записи отчетов в S3 бакет

{    "Version": "2012-10-17",    "Statement": [        {            "Sid": "VisualEditor0",            "Effect": "Allow",            "Action": [                "s3:PutObject",                "s3:GetObject"            ],            "Resource": "arn:aws:s3:::account.admin/*"        }    ]}

Simple Email Service

{    "Version": "2012-10-17",    "Statement": [        {            "Sid": "VisualEditor0",            "Effect": "Allow",            "Action": [                "ses:SendEmail",                "ses:SendRawEmail"            ],            "Resource": "*"        }    ]}

Cost Explorer

{    "Version": "2012-10-17",    "Statement": [        {            "Sid": "VisualEditor0",            "Effect": "Allow",            "Action": "ce:*",            "Resource": "*"        }    ]}

Запуск на Event Bridge

Наконец, мы настраиваем регулярный запуск нашей Lambda функции на Event Bridge, например, 5 числа каждого месяца. В результате работы всех настроек я буду получать email с прикрепленным XLS-отчетом. Также можно настраивать срабатывание еженедельно и даже на определенные дни недели, при необходимости.

Подробнее..

Реализация подписки на обновления с помощью Google Sheets, Netlify Functions и React. Часть 1

04.06.2021 10:04:57 | Автор: admin

В этом туториале мы реализуем ~~Real World App~~ подписку на обновления с помощью гугл таблиц, бессерверных функций и реакта.


Основной функционал нашего приложения будет следующим:


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

Дополнительный функционал:


  • с помощью скрипта осуществляется автоматическая рассылка уведомлений
  • в рассылаемых письмах содержится ссылка на отписку от обновлений
  • при переходе по этой ссылке адрес электронной почты передается бессерверной функции, с помощью которой из таблиц удаляется соответствующая строка пользователь больше не будет получать уведомлений

В первой части туториала мы реализуем основной функционал, во второй дополнительный.


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


Демо приложения, которое мы создадим, можно посмотреть здесь (оно вполне работоспособное, если хотите, можете подписаться на обновления).


Код приложения находится здесь.


Для реализации приложения мы будем использовать следующие технологии:


  • netlify-cli интерфейс командной строки для запуска сервера для разработки (инициализации бессерверных функций) и "деплоя" приложения на Netlify; требуется глобальная установка: yarn global add netlify-cli или npm i -g netlify-cli; обязательно
  • google-spreadsheet JavaScript-библиотека для работы с гугл таблицами; обязательно
  • react на мой взгляд, это лучший JavaScript-фреймворк для фронтенда, но вы можете использовать любую другую библиотеку; наши бессерверные функции не будут зависеть от конкретного фреймворка
  • react-router-dom React-библиотека для маршрутизации
  • semantic-ui-react React-CSS-фреймворк (компоненты с готовыми стилями, ну, почти готовыми, мы их немного поправим)
  • react-google-recaptcha React-компонент, позволяющий напрямую взаимодействовать с соответствующим сервисом
  • nodemailer наиболее популярная Node.js-библиотека для работы с электронной почтой (рассылки писем)
  • dotenv утилита для доступа к переменным среды окружения

Разумеется, на вашей машине должен быть установлен Node.js и, желательно, yarn (после того, как вы поработаете с этим пакетным менеджером, вы едва ли вернетесь к npm).


Также будьте готовы к тому, что придется создать несколько аккаунтов и, возможно, кое-что погуглить в случае, если я забуду об этом упомянуть в туториале (постараюсь этого не делать).


Надеюсь, мне удалось вас заинтриговать, и вы с нетерпением ждете, когда мы начнем писать код. Скоро, но сначала давайте создадим и настроим таблицу, а также свяжем ее с Google Cloud Platform.


Подготовка таблицы


Заходим в Google Cloud Platform (по ссылке, приведенной выше) и выполняем следующие действия:


  • создаем новый проект под названием, например, mail-list
  • ожидаем завершения создания проекта и выбираем его
  • переходим к обзору API (Go to APIs overview)
  • включаем Google Sheets API (Enable APIs and services)
  • создаем сервис-аккаунт для доступа к API (Create credentials)
  • переходим в созданный сервис-аккаунт
  • открываем вкладку Keys и добавляем ключ в формате JSON (Add key -> Create new key)
  • в скачанном файле (например, mail-list-315211-ca347b50f56a.json) нас интересуют свойства private_key и client_email; сохраните их где-нибудь, позже мы запишем их в переменные среды окружения

.


.


.


.


.


.


.


.


.


.


.


.


.


Заходим в Google Speadsheets и создаем новую таблицу (Пустой файл) с двумя графами: username и email.


.


Открываем настройки доступа, находим созданный нами сервис-аккаунт и добавляем его в качестве Редактора.


.


В поисковой строке между d/ и /edit находится идентификатор таблицы, также где-нибудь его сохраните.


На этом настройка нашей таблицы завершена.


Бессерверные функции


Приложение, реализацией которого мы занимаемся, представляет собой отличный пример использования бессерверных вычислений: нам не нужен полноценный сервер, мы всего лишь хотим отправлять данные из заполненной пользователем формы в гугл таблицу, читать эти данные и рассылать уведомления подписчикам. Netlify Functions это лишь один из вариантов использования AWS Lambda Functions для выполнения такого рода задач.


О том, что такое Netlify Functions, можно почитать здесь.


Функции, как правило, размещаются в директории functions в корне проекта. Создаем новый React-проект (mail-list название нашего проекта):


yarn create react-app mail-list# илиnpx create ...

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


cd mail-listyarn add google-spreadsheet dotenv# илиnpm i ...

В корне проекта создаем файл .env (touch .env) и записываем в него сохраненные данные в следующем формате:


GOOGLE_SERVICE_ACCOUNT_EMAIL="YOUR_CLIENT_EMAIL"GOOGLE_PRIVATE_KEY="-----BEGIN PRIVATE KEY----- YOUR_PRIVATE_KEY -----END PRIVATE KEY-----\n"GOOGLE_SPREADSHEET_ID="YOUR_SPREADSHEET_ID"

Создаем директорию functions, переходим в нее, создаем файл subscribe.js и открываем его в редакторе кода:


mkdir functionscd !$touch subscribe.jscode subscribe.js

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


// Загружаем переменные среды окружения из файла ".env"require('dotenv').config()const { GoogleSpreadsheet } = require('google-spreadsheet')// Бессерверная функция (о ее сигнатуре мы поговорим позже)// В данном случае, нас интересует только первый аргумент, принимаемый функцией - `event`// `event` - это тоже самое, что `req` в `express`, т.е. объект запросаexports.handler = async (event) => {  // Создаем экземпляр класса, представляющего внутренний документ гугл таблиц  // Конструктор класса принимает идентификатор таблицы  const doc = new GoogleSpreadsheet(process.env.GOOGLE_SPREADSHEET_ID)  try {    // Выполняем авторизацию с помощью сервис-аккаунта    await doc.useServiceAccountAuth({      client_email: process.env.GOOGLE_SERVICE_ACCOUNT_EMAIL,      private_key: process.env.GOOGLE_PRIVATE_KEY.replace(/\\n/g, '\n')    })    // Загружаем данные документа    await doc.loadInfo()    // Получаем ссылку на созданную нами таблицу    const sheet = doc.sheetsByIndex[0]    // Получаем данные от клиента в формате JSON и преобразуем их в объект    const data = JSON.parse(event.body)    // Получаем строки таблицы    const rows = await sheet.getRows()    // Обратите внимание, что заголовки столбцов таблицы становятся одноименными свойствами строк    // Если какая-либо из строк содержит email, указанный пользователем,    // значит, пользователь уже оформил подписку на обновления    if (rows.some((row) => row.email === data.email)) {      // Формируем ответ      const response = {        statusCode: 400,        body: JSON.stringify({          error: 'Пользователь с таким email уже оформил подписку'        }),        // Про это поговорим позже        headers: {          'Access-Control-Allow-Origin': '*',          'Access-Control-Allow-Credentials': 'true'        }      }      // и возвращаем его      return response    }    // Добавляем данные пользователя в таблицу в виде новой строки    await sheet.addRow(data)    // Формируем ответ    const response = {      statusCode: 200,      body: JSON.stringify({ message: 'Спасибо за подписку!' }),      headers: {        'Access-Control-Allow-Origin': '*',        'Access-Control-Allow-Credentials': 'true'      }    }    // и возвращаем его    return response  } catch (err) {    // Обрабатываем ошибку, возникшую на стороне сервера    console.error(err)    const response = {      statusCode: 500,      body: JSON.stringify({ error: 'Что-то пошло не так. Попробуйте позже' }),      headers: {        'Access-Control-Allow-Origin': '*',        'Access-Control-Allow-Credentials': 'true'      }    }    return response  }}

Бессерверные функции имеют такую сигнатуру:


exports.handler = (event, context, callback) => {...}

Если очень коротко, то event, как было отмечено ранее, это объект запроса (данные, поступающие от клиента), context любая дополнительная информация, связанная с запросом, например, статус пользователя, callback нужен в случае синхронной (блокирующей) функции для возврата ответа (мы используем асинхронную функцию, поэтому нам данный аргумент не нужен, впрочем, как и аргумент context).


Что касается этих заголовков ответа:


headers: {  'Access-Control-Allow-Origin': '*',  'Access-Control-Allow-Credentials': 'true'}

То они связаны с внутренними настройками Netlify (с выполняемыми перенаправлениями при обращении к функции из клиента). Перенаправления блокируются CORS (Cross-Origin Resource Sharing доступ к ресурсу из другого источника), потому что бессерверные функции не совсем бессерверные, под капотом они работают на основе централизованного сервера. Эти заголовки не требуются для локальной разработки, но развернуть приложение на хостинге без них не получится. В официальной документации про это ни слова. Возможно, к тому моменту, когда вы будете читать данную статью, этот недостаток будет устранен.


Следует отметить, что эти заголовки можно указать для всех ответов в файле netlify.toml.


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


Клиент


Устанавливаем зависимости для клиента:


yarn add react-router-dom semantic-ui-css semantic-ui-react react-google-recaptcha# илиnpm i ...

Код клиента находится в директории src. Удаляем из нее лишние файлы (оставляем только index.js и index.css), создаем директорию pages для страниц и hooks для пользовательских хуков. В директории pages создаем следующие файлы:


  • Home.js домашняя/главная страница
  • Subscribe.js страница с формой
  • Success.js страница с сообщением об успехе операции
  • NotFound.js резервная страница (ошибка 404)

В директории hooks создаем три файла:


  • useDeferredRoute.js хук для отложенной маршрутизации (опционально)
  • useTimeout.js хук-обертка для setTimeout
  • index.js экспорт индикатора загрузки и ре-экспорт хуков

Структура директории src:


src  hooks    index.js    useDeferredRoute.js    useTimeout.js  pages    Home.js    NotFound.js    Subscribe.js    Success.js  index.css  index.js

В index.css мы подключаем кастомный шрифт и вносим небольшие правки в стили semantic-ui:


@import url('https://fonts.googleapis.com/css2?family=Montserrat&display=swap');* {  font-family: 'Montserrat', sans-serif !important;}body {  min-height: 100vh;  display: grid;  align-content: center;  background: #8360c3;  background: linear-gradient(135deg, #2ebf91, #8360c3);}h2 {  margin-bottom: 3rem;}.ui.container {  max-width: 480px !important;  margin: 0 auto !important;  text-align: center;}.ui.form {  max-width: 300px;  margin: 0 auto;}.ui.form .field > label {  text-align: left;  font-size: 1.2rem;  margin-bottom: 0.8rem;}.ui.button {  margin-top: 1.5rem;  font-size: 1rem;  letter-spacing: 1px;  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3) !important;}.email-error {  color: #f93154;  text-align: left;}

В index.js мы импортируем компоненты приложения и реализуем разделение кода на уровне маршрутов с помощью lazy и Suspense:


import React, { lazy, Suspense } from 'react'import ReactDOM from 'react-dom'// Средства для маршрутизацииimport { BrowserRouter as Router, Switch, Route } from 'react-router-dom'// Индикатор загрузкиimport { Spinner } from './hooks'// Стили `semantic-ui`import 'semantic-ui-css/semantic.min.css'// Кастомные стилиimport './index.css'// "Ленивые" компоненты - динамический импортconst Home = lazy(() => import('./pages/Home'))const Subscribe = lazy(() => import('./pages/Subscribe'))const Success = lazy(() => import('./pages/Success'))const NotFound = lazy(() => import('./pages/NotFound'))ReactDOM.render(  <Suspense fallback={<Spinner />}>    <Router>      <Switch>        <Route path='/' exact component={Home} />        <Route path='/subscribe' component={Subscribe} />        <Route path='/success' component={Success} />        <Route component={NotFound} />      </Switch>    </Router>  </Suspense>,  document.getElementById('root'))

Рассмотрим, что из себя представляют пользовательские хуки.


Хук useDeferredRoute добавляет искусственную задержку при начальной загрузке приложения и переходе пользователя к другой странице. Это совершенно не обязательно, возможно, кто-то даже скажет, что это плохая практика, но мне показалось, что так приложение будет более "живым". Кроме того, не так давно при работе над одним из проектов я столкнулся с необходимостью скрытия от пользователя позиционирования элементов фона отрендеренного компонента через искусственную задержку отображения компонента (вот где бы пригодился хук useTransition, но он пока еще экспериментальный).


import { useState, useEffect } from 'react'const sleep = (ms) => new Promise((r) => setTimeout(r, ms))export const useDeferredRoute = (ms) => {  const [loading, setLoading] = useState(true)  useEffect(() => {    const wait = async () => {      await sleep(ms)      setLoading(false)    }    wait()  }, [ms])  return { loading }}

Хук useTimeout, как было отмечено, это всего лишь обертка над нативным setTimeout:


import { useEffect, useRef } from 'react'export const useTimeout = (cb, ms) => {  const cbRef = useRef()  useEffect(() => {    cbRef.current = cb  }, [cb])  useEffect(() => {    function tick() {      cbRef.current()    }    if (ms > 1) {      const id = setTimeout(tick, ms)      return () => {        clearTimeout(id)      }    }  }, [ms])}

А вот как выглядит hooks/index.js:


// Мне не хотелось создавать директорию `components` для одного компонентаimport { Loader } from 'semantic-ui-react'export const Spinner = () => <Loader active inverted size='large' />export { useDeferredRoute } from './useDeferredRoute'export { useTimeout } from './useTimeout'

Теперь займемся страницами.


В Home.js нет ничего особенного. После скрытия индикатора загрузки, мы приветствуем пользователя и предлагаем ему подписаться на (кнопка "Подписаться" это на самом деле ссылка на страницу Subscribe):


import { Link } from 'react-router-dom'import { Container, Button } from 'semantic-ui-react'import { Spinner, useDeferredRoute } from '../hooks'function Home() {  const { loading } = useDeferredRoute(1500)  if (loading) return <Spinner />  return (    <Container>      <h2>Доброго времени суток!</h2>      <h3>        Подпишитесь на обновления, <br /> чтобы оставаться в курсе событий!      </h3>      <Button color='teal' as={Link} to='/subscribe'>        Подписаться      </Button>    </Container>  )}export default Home

В Success.js также нет ничего особенного. После скрытия индикатора загрузки, мы благодарим пользователя за подписку и выполняем автоматическое перенаправление на главную страницу через 3 секунды с помощью нашего хука useTimeout. На случай, если автоматического перенаправления не произошло, имеется кнопка-ссылка на страницу Home:


import { Link, useHistory } from 'react-router-dom'import { Container, Button } from 'semantic-ui-react'import { Spinner, useDeferredRoute, useTimeout } from '../hooks'function Success() {  const { loading } = useDeferredRoute(500)  const history = useHistory()  const redirectToHomePage = () => {    history.push('/')  }  useTimeout(redirectToHomePage, 3000)  if (loading) return <Spinner />  return (    <Container>      <h2>Спасибо за подписку!</h2>      <h3>Сейчас вы будете перенаправлены на главную страницу</h3>      <Button color='teal' as={Link} to='/'>        На главную      </Button>    </Container>  )}export default Success

Еще одна простая страница NotFound пользователь попадает на эту страницу при отсутствии совпадения с маршрутами приложения:


import { Link, useHistory } from 'react-router-dom'import { Container, Button } from 'semantic-ui-react'import { Spinner, useDeferredRoute, useTimeout } from '../hooks'function NotFound() {  const { loading } = useDeferredRoute(500)  const history = useHistory()  const redirectToHomePage = () => {    history.push('/')  }  useTimeout(redirectToHomePage, 2000)  if (loading) return <Spinner />  return (    <Container>      <h2>Страница отсутствует</h2>      <h3>Сейчас вы будете перенаправлены на главную страницу</h3>      <Button color='teal' as={Link} to='/'>        На главную      </Button>    </Container>  )}export default NotFound

На странице Subscribe используется компонент react-google-recaptcha, которому в качестве пропа передается ключ сайта (sitekey). Данный ключ можно получить в административной консоли Google ReCAPTCHA, но для этого приложение надо сначала развернуть на Netlify. К счастью, для локальной разработки можно использовать этот тестовый ключ (это официальный ключ для тестирования): 6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI. Позже мы вернемся к этому вопросу.


Еще один важный момент это конечная точка отправки пользовательских данных. Она должна начинаться с /.netlify/, затем указывается путь к соответствующей функции: functions/subscribe /.netlify/functions/subscribe (название функции часть пути). Следует отметить, что часть пути /.netlify/functions можно изменить в netlify.toml.


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


import { useState } from 'react'import { useHistory } from 'react-router-dom'import { Container, Form, Button } from 'semantic-ui-react'import ReCAPTCHA from 'react-google-recaptcha'import { Spinner, useDeferredRoute } from '../hooks'// Утилита для проверки того, что все поля заполненыconst isEmpty = (fields) => fields.some((f) => f.trim() === '')// Простой вариант утилиты для проверки адреса электронной почтыconst isEmail = (v) => /\w+@\w+\.\w+/i.test(v)function Subscribe() {  const [formData, setFormData] = useState({    username: '',    email: ''  })  const [error, setError] = useState(null)  const [recaptcha, setRecaptcha] = useState(false)  const { loading } = useDeferredRoute(1000)  const history = useHistory()  const onChange = ({ target: { name, value } }) => {    setError(null)    setFormData({      ...formData,      [name]: value    })  }  const onSubmit = async (e) => {    e.preventDefault()    const email = isEmail(formData.email)    if (!email) {      return setError('Введен неправильный email')    }    try {      const response = await fetch('/.netlify/functions/subscribe', {        method: 'POST',        body: JSON.stringify(formData),        headers: {          'Content-Type': 'application/json'        }      })      if (!response.ok) {        const json = await response.json()        return setError(json.error)      }      history.push('/success')    } catch (err) {      console.error(err)    }  }  // Учитывая, что мы используем тестовый ключ, капча всегда будет иметь истинное значение  const disabled = isEmpty(Object.values(formData)) || !recaptcha  const { username, email } = formData  if (loading) return <Spinner />  return (    <Container>      <h2>Подписаться на уведомления</h2>      <Form onSubmit={onSubmit}>        <Form.Field>          <label>Ваше имя</label>          <input            placeholder='Имя'            type='text'            name='username'            value={username}            onChange={onChange}            required          />        </Form.Field>        <Form.Field>          <label>Ваш email</label>          <input            placeholder='Email'            type='email'            name='email'            value={email}            onChange={onChange}            required          />        </Form.Field>        <p className='email-error'>{error}</p>        <ReCAPTCHA          sitekey='6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI'          onChange={() => setRecaptcha(true)}        />        <Button color='teal' type='submit' disabled={disabled}>          Подписаться        </Button>      </Form>    </Container>  )}export default Subscribe

Итак, у нас имеется клиентская часть приложения и функция для записи данных пользователя в таблицу. Пришло время проверить работоспособность приложения.


Если вы еще не установили netlify-cli, самое время это сделать:


yarn global add netlify-cli# илиnpm i -g ...

Все, что нужно сделать для запуска приложения и инициализации функции, это выполнить следующую команду:


netlify dev

После выполнения указанной команды клиент будет запущен по адресу localhost:3000, а сервер также на локальном хосте, но с портом 8888.


Прелесть в том, что netlify-cli умеет автоматически определять, какой фреймворк используется в проекте, и выполнять нужные команды для его запуска.


Нажимаем на кнопку "Подписаться", заполняем поля формы, проходим проверку и снова нажимаем на "Подписаться". Выполняется перенаправление на страницу с сообщением об успехе операции, затем еще одно перенаправление на главную страницу. Открываем таблицу, видим введенные нами данные.


.


.


.


Отлично, приложение работает, как ожидается.


В следующей части туториала мы развернем приложение на Netlify, закончим настройку капчи, реализуем автоматическую рассылку уведомлений и отписку от обновлений.


Благодарю за внимание и хорошего дня!




Облачные серверы от Маклауд быстрые и безопасные.


Зарегистрируйтесь по ссылке выше или кликнув на баннер и получите 10% скидку на первый месяц аренды сервера любой конфигурации!


Подробнее..

Создание превью картинок в объектном хранилище с помощьюYandex Cloud Functions

17.03.2021 10:08:48 | Автор: admin

Довольно распространенная задача создание превью картинок для сайта из полноразмерных изображений. Автоматизируем этот процесс с помощью триггера для Yandex Object Storage с функцией в Yandex Cloud Functions, которую он будет запускать с наступлением определенного события в бакете в нашем случае, появлением в нем картинки. Функция сделает из нее превью и сохранит в соседний бакет. Возможна вариация сохранения превью в тот же бакет, но с другим префиксом.

Также с помощью триггера в Yandex.Cloud можно задавать действия не только для объектов в хранилище, но и для событий в очереди.

Триггер для Object Storage и Cloud Functions

Для решения этой задачи необходим настроенный бакет в Object Storage, в него будут загружаться файлы и в нем же будет лежать архив с кодом функции. Репозиторий с примером кода можно скачатьтут. После выполнения инструкций из репозитория у вас будет готовая функция, к которой необходимо подключить триггер, срабатывающий на загрузку новых файлов.

В настройках триггера задаем его параметры: название, описание и тип в нашем случае Object Storage. Также указываем бакет, в который будут загружаться картинки. Если дополнительно указать префикс и суффикс объекта, то можно обойтись одним бакетом и для исходных файлов, и для превью.

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

Превью на лету

Но у такого решения есть пара ограничений.

  1. Превью создаются только для новых изображений, если картинка уже лежала в бакете, то для нее превью не будет создано.

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

Воспользуемся новой возможностью Yandex.Cloud - runtime для функций C#, чтобы доработать нашу функцию на csharp.

Теперь схема будет работать так.

При первичном обращении за превью:

  1. Пользователь запрашивает картинку, указав в URL ее желаемые размеры.

  2. Объектное хранилище не находит нужного файла и согласно настроенным правилам, переадресует запрос в функцию.

  3. Функция идет за оригинальным изображением в объектное хранилище.

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

  5. Сразу отдает готовый файл пользователю.

При последующих обращениях за файлом пользователь будет сразу получать файл из объектного хранилища без вызова функции.

Особенности технической реализации

Прописываем правила переадресации для бакета, чтобы Object Storage и Cloud Functions смогли работать в связке.

s3.putBucketWebsite({ Bucket: "%bucket_name%", WebsiteConfiguration: { IndexDocument: { Suffix: "index.html" }, RoutingRules: [ { Condition: { HttpErrorCodeReturnedEquals: "404", KeyPrefixEquals: "images/" }, Redirect: { HttpRedirectCode: "302", HostName: "functions.yandexcloud.net", Protocol: "https", ReplaceKeyPrefixWith: "%function_id%?path=", } } ] }})

Если объектное хранилище не найдет (HttpErrorCodeReturnedEquals: "404") запрошенный файл с указанным KeyPrefixEquals, то применит указанный ниже редирект. Подробнее можно посмотреть в документации к AWS S3, а скачать готовый код функции можно в репозитории тут.

В Yandex.Cloud удобно реализовано создание функций с помощью CLI. Вам не надо архивировать код и загружать его в объектное хранилище, достаточно лишь сложить все файлы в директорию и указать на нее при создании версии функции в ключе --source-path. Так же вы можете не передавать все node_modules, а загрузить только package.json и выбрать --runtime nodejs12 или --runtime nodejs14. Все зависимости будут подтянуты в момент создания версии функции.

Обращаться к фалам надо не по обычному хосту %bucket_name%.storage.yandexcloud.net, так как редиректы обрабатываться не будут, а через %bucket_name%.website.yandexcloud.net/PREFIX/%width%x%height%/%path%.

Например, при обращении к %bucket_name%.website.yandexcloud.net/images/500x500/cats/meow.png вы получите картинку которую положили в бакет %bucket_name% по ключу images/cats/meow.png но отмасштабированную до размеров 500x500px.

Чтобы не выстрелить себе в ногу

Если не задать значение переменной окружения ALLOWED_DIMENSIONS, то в функции не будут накладываться ограничения на размеры. Это значит, что если будут запрошены много разных вариантов размеров, то все они будут созданы и загружены в объектное хранилище. Поэтому если вы точно знаете, что вам понадобятся определенные размеры картинок лучше их указать. В противном случае, вы быстро израсходуете все свободное пространство хранилища.

Так же, если у вас очень много изображений, можно задать некоторое значение TTL, создать LRU схему кэширования.

P.S.

Любые вопросы по Serverless и Yandex Cloud Functions обсуждаем у нас в Telegram: Yandex Serverless Ecosystem

Полезные ссылки:

Подробнее..
Категории: C , S3 , Csharp , Yandex.cloud , Serverless , Faas

Реализация подписки на обновления с помощью Google Sheets, Netlify Functions и React. Часть 2

08.06.2021 12:19:01 | Автор: admin

Это вторая часть туториала, посвященного реализации Real World App подписки на обновления с помощью гугл таблиц, бессерверных функций и реакта.


Вот ссылка на первую часть.


Напомню, что основной функционал нашего приложения, который мы реализовали в первой части туториала, является следующим:


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

Дополнительный функционал, реализацией которого мы займемся в этой части:


  • с помощью скрипта осуществляется автоматическая рассылка уведомлений
  • в рассылаемых письмах содержится ссылка на отписку от обновлений
  • при переходе по этой ссылке адрес электронной почты передается бессерверной функции, с помощью которой из таблиц удаляется соответствующая строка

Демо приложения, разработкой которого мы занимаемся, можно посмотреть здесь (оно вполне работоспособное, если хотите, можете подписаться на обновления).


Код приложения находится здесь.


Для реализации приложения используются следующие технологии:


  • netlify-cli интерфейс командной строки для запуска сервера для разработки (инициализации бессерверных функций) и "деплоя" приложения на Netlify; требуется глобальная установка: yarn global add netlify-cli или npm i -g netlify-cli; обязательно
  • google-spreadsheet JavaScript-библиотека для работы с гугл таблицами; обязательно
  • react на мой взгляд, это лучший JavaScript-фреймворк для фронтенда, но вы можете использовать любую другую библиотеку; наши бессерверные функции не зависят от конкретного фреймворка
  • react-router-dom React-библиотека для маршрутизации
  • semantic-ui-react React-CSS-фреймворк
  • react-google-recaptcha React-компонент, позволяющий напрямую взаимодействовать с соответствующим сервисом
  • nodemailer наиболее популярная Node.js-библиотека для работы с электронной почтой (рассылки писем)
  • dotenv утилита для доступа к переменным среды окружения

Начнем с деплоя приложения на Netlify.


Деплой приложения


Заходим на Netlify, создаем аккаунт, затем вводим в терминале следующую команду:


netlify login

Вводим логин и пароль, получаем сообщение об успешной авторизации.


Выполняем сборку проекта:


yarn build# илиnpm run build

И разворачиваем приложение в тестовом режиме:


netlify deploy

Отвечаем на вопросы (новое приложение, название приложения (например, mail-list), директория для деплоя (build) и т.д.), получаем ссылку на развернутое приложение.


Переходим по ссылке, видим, что приложение не работает. Почему? Потому что мы не добавили переменные среды окружения.


Переходим в раздел sites, открываем наше приложение, выбираем вкладку Site settings, затем вкладку Build & deploy, находим раздел Environment, добавляем переменные (Environment variables).




Не будем ходить вокруг да около, а сразу развернем приложение в продакшн-режиме:


netlify deploy --prod

Готово. Легко, правда? Вот за что я люблю Netlify.


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


Заходим в консоль администратора и создаем новое приложение (+). Вводим название сайта (ярлык), выбираем reCAPTCHA v2, указываем домен (URL нашего приложения без протокола), принимаем условия использования (флажок "Отправлять владельцам оповещения" можно снять), нажимаем "Отправить". Получаем ключ сайта и секретный ключ, нам нужен только первый.





Добавляем в .env такую переменную:


REACT_APP_GOOGLE_RECAPTCHA_SITE_KEY=YOUR_SITE_KEY

Вносим изменение в Subscribe.js:


<ReCAPTCHAsitekey={process.env.REACT_APP_GOOGLE_RECAPTCHA_SITE_KEY}onChange={() => setRecaptcha(true)}/>

Повторно собираем и разворачиваем приложение:


yarn build# илиnpm run build# иnetlify deploy --prod

Если все сделано правильно, то на странице с формой появится настоящая капча.



Отлично.


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


Автоматическая рассылка уведомлений


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


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


Для реализации автоматической рассылки уведомлений мы будем использовать nodemailer, а для тестирования Mailtrap.


Создаем аккаунт на Mailtrap, открываем автоматически созданный проект MyInbox, на вкладке SMTP Settings в разделе Integrations выбираем Node.js -> Nodemailer, получаем данные для авторизации.




Сохраняем эти данные в .env:


SMTP_USER='USER'SMTP_PASS='PASS'

В корне проекта создаем директорию send-mail, а в ней index.js следующего содержания:


require('dotenv').config()const nodemailer = require('nodemailer')const { GoogleSpreadsheet } = require('google-spreadsheet')const doc = new GoogleSpreadsheet(process.env.GOOGLE_SPREADSHEET_ID)// Тестовый транспортер для отправки сообщенийconst testTransporter = nodemailer.createTransport({host: 'smtp.mailtrap.io',port: 2525,auth: {user: process.env.SMTP_USER,pass: process.env.SMTP_PASS}})// Функция для создания сообщения в формате HTML// Она принимает имя пользователя и его email// Обратите внимание на значение атрибута `href` тега `a` -// URL соответствующей страницы нашего приложения (скоро мы ее создадим) + email пользователяconst createMessage = (username, email) => `<p><strong>Уважаемый ${username} </strong>, <em>спасибо за подписку</em>!</p><p>Для того, чтобы отписаться от обновлений, перейдите по <a href="http://personeltest.ru/aways/mail-list.netlify.app/unsubscribe/${email}" target="_blank">этой ссылке</a></p>`const sendMail = async () => {try {await doc.useServiceAccountAuth({client_email: process.env.GOOGLE_SERVICE_ACCOUNT_EMAIL,private_key: process.env.GOOGLE_PRIVATE_KEY.replace(/\\n/g, '\n')})await doc.loadInfo()const sheet = doc.sheetsByIndex[0]const rows = await sheet.getRows()// Перебираем строки таблицы  данные пользователей,// создаем сообщение и отправляем его// text  резервный контент на случай, если почтовый клиент пользователя не поддерживаем сообщения в формате HTMLrows.forEach(async (row) => {await testTransporter.sendMail({from: 'Mail list <mail-list.netlify.app>',to: row.email,subject: 'Благодарность за подписку',text: 'Спасибо за подписку',html: createMessage(row.username, row.email)})})console.log('Сообщения отправлены')} catch (err) {console.error(err)}}sendMail()

Добавим в package.json (раздел scripts) команду для рассылки уведомлений:


send: node send-mail/index.js

Запускаем скрипт (разумеется, в таблице должны быть какие-то данные):


yarn send# илиnpm run send

Получаем Сообщения отправлены в терминале и письмо в Mailtrap.



Для взаимодействия с реальными почтовыми службами (yahoo в моем случае) нужен реальный SMTP-провайдер.


Среди наиболее популярных решений можно назвать SendGrid и SendingBlue, но в случае выбора одного из этих сервисов, нам придется долго и упорно убеждать их владельцев в том, что мы не собираемся заниматься рассылкой спама. Еще есть Mailgun, но он платный с трехмесячным free trial.


Поэтому мы будем использовать Gmail.


Безусловно, если очень хочется, можно поднять собственный SMPT-сервер. Также существуют инструменты для рассылки писем, которые, как заявляют их разработчики, работают без SMTP, например, sendmail.


Добавляем в .env переменные с данными вашего Gmail-аккаунта:


GMAIL_USER='USER'GMAIL_PASS='PASS'

И вносим изменения в send-mail/index.js:


/*const testTransporter = nodemailer.createTransport({host: 'smtp.mailtrap.io',port: 2525,auth: {user: process.env.SMTP_USER,pass: process.env.SMTP_PASS}})*/const gmailTransporter = nodemailer.createTransport({service: 'gmail',auth: {user: process.env.GMAIL_USER,pass: process.env.GMAIL_PASS}})rows.forEach(async (row) => {await gmailTransporter.sendMail({// ...})})

Запускаем скрипт (в таблице должен быть указан ваш email):


yarn send


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


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



С реализацией автоматической рассылки мы закончили, но при переходе по ссылке, указанной в письме, мы попадаем на резервную страницу, поскольку в нашем приложении нет страницы для отписки от обновлений, как и соответствующей бессерверной функции. Давайте это исправим.


Отписка от обновлений


Добавляем в приложение (src/pages) новую страницу Unsubscribe.js. На этой странице после скрытия индикатора загрузки, мы пытаемся получить email пользователя из параметров строки запроса с помощью хука useParams. Если email отсутствует, выполняется перенаправление на главную страницу. Иначе мы отправляем email в функцию, которая удаляет из таблицы соответствующую строку. Если пользователь с указанным email не оформлял подписку на обновления, выбрасывается исключение. При успешном завершении операции отображается сообщение о том, что пользователь больше не будет получать уведомлений.


import { useState, useEffect } from 'react'import { Link, useParams, useHistory } from 'react-router-dom'import { Container, Button } from 'semantic-ui-react'import { Spinner, useDeferredRoute } from '../hooks'function Unsubscribe() {const { loading } = useDeferredRoute(1000)const [error, setError] = useState(null)// Извлекаем email из параметров строки запросаconst { email } = useParams()const history = useHistory()useEffect(() => {// Если email отсутствует, выполняем перенаправление на главную страницуif (!email) {return history.push('/')}async function unsubscribe() {try {// Отправляем email в функциюconst response = await fetch('/.netlify/functions/unsubscribe', {method: 'POST',body: JSON.stringify(email),headers: {'Content-Type': 'application/json'}})// Если возникла ошибка, значит, пользователь не оформлял подпискуif (!response.ok) {const json = await response.json()setError(json.error)}} catch (err) {console.error(err)}}unsubscribe()// eslint-disable-next-line}, [])if (loading) return <Spinner />return (<Container>{error ? (<h3>{error}</h3>) : (<h3>Вы больше не будете получать уведомлений</h3>)}<Button color='teal' as={Link} to='/'>На главную</Button></Container>)}export default Unsubscribe

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


require('dotenv').config()const { GoogleSpreadsheet } = require('google-spreadsheet')exports.handler = async (event) => {const doc = new GoogleSpreadsheet(process.env.GOOGLE_SPREADSHEET_ID)try {await doc.useServiceAccountAuth({client_email: process.env.GOOGLE_SERVICE_ACCOUNT_EMAIL,private_key: process.env.GOOGLE_PRIVATE_KEY.replace(/\\n/g, '\n')})await doc.loadInfo()const sheet = doc.sheetsByIndex[0]// Получаем email пользователяconst data = JSON.parse(event.body)const rows = await sheet.getRows()// Выполняем поиск соответствующей строкиconst index = rows.findIndex((row) => row.email === data)// Если строка не найдена, значит, пользователь не оформлял подпискуif (index === -1) {const response = {statusCode: 400,body: JSON.stringify({error: 'Пользователь с указанным email не найден'}),headers: {'Access-Control-Allow-Origin': '*','Access-Control-Allow-Credentials': 'true'}}return response}// Удаляем строкуawait rows[index].delete()const response = {statusCode: 200,body: JSON.stringify({ message: 'Пользователь удален' }),headers: {'Access-Control-Allow-Origin': '*','Access-Control-Allow-Credentials': 'true'}}return response} catch (err) {console.error(err)const response = {statusCode: 500,body: JSON.stringify({ error: 'Что-то пошло не так. Попробуйте позже' }),headers: {'Access-Control-Allow-Origin': '*','Access-Control-Allow-Credentials': 'true'}}return response}}

Еще раз (обещаю, что в последний) собираем и разворачиваем проект:


yarn build# илиnpm run build# иnetlify deploy --prod

Не забудьте обновить переменные среды окружения на Netlify.


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




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


Как бы то ни было, если вы в точности следовали инструкциям, а еще лучше реализовали какие-то дополнительные возможности, то в вашем портфолио появилось настоящее Real World App, разработанное с использованием самых современных технологий (да, мы не использовали TypeScript, но для нашего небольшого проекта это было бы слишком круто).




Облачные серверы от Маклауд быстрые и безопасные.


Зарегистрируйтесь по ссылке выше или кликнув на баннер и получите 10% скидку на первый месяц аренды сервера любой конфигурации!


Подробнее..

Как работают и где применяются бессерверные вычисления (Function-as-a-Service)

06.05.2021 14:05:14 | Автор: admin

Serverless-вычисления и работающие на их основе решения Function-as-a-Service помогают разработчикам развивать продукты, ориентируясь на бизнес-фичи. Мы поэкспериментировали с этими технологиями и пришли к выводу, что для боевого применения существующие решения сыроваты. Пойдём по порядку.

Термин бессерверные вычисления отчасти вводит в заблуждение конечно, в основе продукта сервера остаются, но разработчикам не приходится о них заботиться. По сути своей Serverless продолжает те же идеи виртуализации, что и более ранние aaS-технологии: позволить команде сосредоточиться на коде и развитии функций. Если IaaS это абстракция оборудования, контейнеры абстракция приложений, то FaaS это абстракция бизнес-логики сервиса.

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

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

Команда не беспокоится о бэкенде и процессах деплоя, В идеальных условиях реализация новой фичи сводится к загрузке одной функции на сервер. В результате разработка двигается быстрее, Time-to-Market ползёт вниз. А в компании в целом внедрение FaaS помогает развить платформенный подход для Serverless-вычислений нужен либо пул облачных ресурсов от провайдера, либо Kubernetes-кластер.

Как это работает на практике

На рынке есть уже целый набор Serverless-платформ. Мы внимательно изучили два решения: Lambda от Amazon и KNative. Первое представляет собой проприетарный сервис для работы с облаком Amazon, второе работает поверх Kubernetes.

Amazon Lambda вполне рабочий вариант со всеми возможностями, о которых мы говорили выше. Платформа выполняет все рутинные операции с продуктом, разворачивает приложения, мониторит работоспособность и производительность групп инстансов, обеспечивает отказоустойчивость и масштабирование.

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

KNative для нас решение более интересное, поскольку оно работает поверх Kubernetes.

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

  • Event source сущность FaaS-платформы, которая взаимодействует с внешними источниками событий. Триггером может быть HTTP-запрос, сообщение от брокера сообщений, событие самой платформы

  • Broker корзина, которая принимает и хранит информацию о событиях от Event Source. Брокер может представлять собой модуль Kafka, работать в оперативной памяти и т.п.

  • Trigger подписанный на Broker компонент, который достаёт сообщения из корзины и передаёт их на исполнение в Service.

  • Service рабочая функция, изолированная бизнес-логика.

С точки зрения разработчика процесс выглядит практически так же, как с уже привычными контейнеризированными приложениями, меняется только объект: (1) написать функцию, (2) упаковать её в Docker-образ, (3) загрузить.

Главный недостаток KNative нет средств логирования и мониторинга, а для FaaS-решений это критически важно. Если ваш продукт разбит на функции, без эффективного мониторинга и логирования быстро установить источник сбоя невозможно, поскольку придётся смотреть на каждую функцию отдельно.

Преимущества FaaS

Лучше всего подход показывает себя, когда не требуется мгновенный ответ пользователю и когда нагрузка может колебаться от 0 до 100%:

  • Задачи, которые выполняются по расписанию. Операции экспорта/импорта в системах финансовой отчётности, учётных системах, решениях для создания резервных копий.

  • Асинхронная отправка уведомлений пользователю (push, email, СМС).

  • Машинное обучение, Интернет вещей, системы ИИ все эти отрасли точно оценят такие возможности. Serverless позволяет выполнять вычисления ближе к эндпойнту, т.е. к пользователю. А значит, у продукта уменьшается задержка, сокращается нагрузка на передачу данных.

Какие можно выделить недостатки Serverless:

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

  2. Лучшие платформы на данный момент привязывают компанию к тому или иному облачному провайдеру будь то AWS, Microsoft Azure или Google Cloud. Решениям для Kubernetes ещё предстоит подрасти до этого уровня.

  3. FaaS это не волшебная таблетка, с которой разработчики могут забыть об инфраструктуре и просто отправлять на прод фичи. Всё равно нужно продумывать архитектуру, проектировать функции и их взаимодействие с помощью DDD. Иначе продукт превращается в массу сильно связанных между собой функций, в которых будет сложно разобраться. Разработчики не смогут деплоить такие функции и менять по отдельности. В худшем случае при обработке пользовательских запросов пользователя придётся поднимать все функции.

Наш вывод до эпохи Serverless ещё несколько лет

При условии, что разработчики будут развивать это направление, в частности развивать open source платформы до уровня того же Amazon Lambda.

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

Подробнее..

Пишем телеграм бота на Go и AWS Lambda

03.05.2021 02:05:54 | Автор: admin

Что будем делать?

Будем писать простой телеграм бот, отвечающий тем же сообщением, что и отправили.

Почему AWS Lambda ?

  1. Удобство деплоя, просто пишешь sls deploy, и lambda уже выгружена

  2. Платишь только за время, когда lambda работает

  3. Не надо настраивать никаких серверов, и беспокоиться о масштабировании

Что понадобится?

  • Установленный go

  • Nodejs и npm для установки serverless

  • AWS аккаунт для деплоя

TLDR

  • Клонируем репозиторий https://github.com/R11baka/echotgbot

  • Устанавливаем в .env файле BOT_TOKEN переменную

  • Компилируем бинарник env GOOS=linux go build -o bin/webhook main.go

  • Выгружаем лямбду с помощью sls deploy

  • Устанавливаем webhook с помощью BOT_TOKEN

Регистрация в AWS

  • Регистирируем пользователя в AWS aws console и получаем aws_access_key_id, и aws_secret_access_key и прописываем их в .aws/credentials файле

Вот как выглядит мой .aws/credentials

cat ~/.aws/credentials[default]aws_access_key_id = ADEFEFEFFEBDXK3aws_secret_access_key = Zy6ewfir/zGaT1B2/o9JDWDSssdrlaregion = us-west-1

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

Для начала, нам надо зарегистировать бота в BotFather. Идем по ссылке, отправляем команду BotFather /newbot, придумываем имя боту, описание. В конце, BotFather вернет нам токен бота.Этот токен понадобится,нам для дальнейшей разработки.

Установка Serverless

Serverless-это framework, облегчающий настройку, деплой AWS Lambda функций. Написан на node, поэтому для его установки понадобится nodejs и npm. Устанавливаем serverless через npm

npm install -g serverless

После установки serverless проверяем, все ли установилось

sls -vFramework Core: 2.35.0 (standalone)Plugin: 4.5.3SDK: 4.2.2Components: 3.8.2

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

service: echoBotuseDotenv: trueconfigValidationMode: error # если в конфиге,чтото неправильно ,то ошибкаframeworkVersion: '>=1.28.0 <2.50.0'provider:  region: "us-west-1"  lambdaHashingVersion: "20201221"  name: aws  runtime: go1.x  logRetentionInDays: 30 # сколько дней хранить логи  endpointType: regional  tracing: # включаем трейсинг для лямбды    apiGateway: true    lambda: true  iam:    role:      statements:        - Effect: "Allow"          Resource: "*"          Action:            - "xray:*"package:  patterns:    - "bin/webhook" # деплоить только бинарникfunctions:  webhook:     handler: bin/webhook    timeout: 15    description: simple echo bot    memorySize: 128 # размер памяти в мегабайтах для функции    environment:      BOT_TOKEN: ${env:BOT_TOKEN}    events:      - http:          path: /webhook          method: ANY          cors: false

Имлементация логики бота на Go

  1. Устанавливаем библиотеки telebot.v2 и aws-lambda-go

      go mod init testBotgo: creating new go.mod: module testBot  go get -u gopkg.in/tucnak/telebot.v2go: gopkg.in/tucnak/telebot.v2 upgrade => v2.3.5go: github.com/pkg/errors upgrade => v0.9.1 go get github.com/aws/aws-lambda-gogo: github.com/aws/aws-lambda-go upgrade => v1.23.0
    

2. Создаем файл main.go с контентом

package mainimport ("encoding/json""fmt""github.com/aws/aws-lambda-go/events""github.com/aws/aws-lambda-go/lambda"tb "gopkg.in/tucnak/telebot.v2""os")func main() {settings := tb.Settings{Token:       os.Getenv("BOT_TOKEN"),Synchronous: true,Verbose:     true,}tgBot, err := tb.NewBot(settings)if err != nil {fmt.Println(err)panic("can't create bot")}tgBot.Handle(tb.OnText, func(m *tb.Message) {message := m.TexttgBot.Send(m.Sender, message)})lambda.Start(func(req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {var u tb.Updateif err = json.Unmarshal([]byte(req.Body), &u); err == nil {tgBot.ProcessUpdate(u)}return events.APIGatewayProxyResponse{Body: "ok", StatusCode: 200}, nil})}

Текущая документация в tucnak/telebot.v2 немного устарела, и если просто написать return вместо return events.APIGatewayProxyResponse{Body: "ok", StatusCode: 200}, nil, то телеграм будет повторно отправлять сообщения к боту.

Deploy бота

  • Создаем файл .env и вставляем API_TOKEN полученный от BotFather

    echo API_TOKEN={API_TOKEN_FROM_BOTFATHER} > .env
    
  • Проверяем serverlss конфиг с помощью команды. Не должно быть никаких ошибок.

    sls print
    
  • Потом собираем бинарник

    env GOOS=linux go build  -o bin/webhook main.go 
    
  • И выгружаем его с помощью serverless

    serverless deploy  -v
    

    При успешной выгрузке, мы получим в конце

    Service Informationservice: echoBotstage: devregion: us-west-1stack: echoBot-devresources: 11api keys:Noneendpoints:ANY - https://y7p31bwnu1.execute-api.us-west-1.amazonaws.com/dev/webhookfunctions:webhook: echoBot-dev-webhooklayers:None
    

    https://y7p31bwnu1.execute-api.us-west-1.amazonaws.com/dev/webhook => этот эндпоинт и token бота нужен нам, чтобы установить webhook

Интеграция с telegram

Осталось сообщить телеграму, какой эндпоинт дергать при получении сообщения. Делается это командой setWebhook

curl https://api.telegram.org/bot{YOUR_TOKEN}/setWebhook?url={YOUR_DEPLOYED_AWS_URL}

Проверка что webhook установлен, происходит с помощью getWebhookInfo

  ~ curl https://api.telegram.org/bot1324913549:AAE1zYMH6K3hF2TOgUQoIP-E1g4rMIamck/setWebhook\?url\= https://y7p31bwnu1.execute-api.us-west-1.amazonaws.com/dev/webhook{"ok":true,"result":true,"description":"Webhook was set"}  ~ curl https://api.telegram.org/bot1324913549:AAE1zYMH6K3hF2TOgUQoIP-E1g4rMIamck/getWebhookInfo{"ok":true,"result":{"url":"https://y7p31bwnu1.execute-api.us-west-1.amazonaws.com/dev/webhook","has_custom_certificate":false,"pending_update_count":0,"max_connections":40,"ip_address":"184.169.148.254"}}

Ошибки

Если что-то пошло не так,идем в CloudWatch и смотрим логи,или же из консоли также можно посмотреть логи

sls logs -f webhook
Подробнее..
Категории: Go , Golang , Serverless

И еще разок про Serverless

15.03.2021 14:07:31 | Автор: admin
Логотип AWS Lambda (ну или Half Life, я так и не понял)Логотип AWS Lambda (ну или Half Life, я так и не понял)

С публичного релиза AWS Lambda прошло ни много ни мало 6 с лишним лет. Реактивные функции, реагирующие на события, не только позволили по-другому смотреть на архитектуру систем и приложений, но и породили новый buzzword Serverless.

Мотивом к написанию этой статьи служит пост в блоге Netflix. Прочитав его, а так же комментарии русскоязычной аудитории, я прихожу к выводу, что направление "безсерверных вычислений" еще не обкатано вдоль и поперек (как и в англоязычной тусовке, чего греха таить), и неплохо бы проитерироваться по этой теме еще раз.

Мой пост будет опираться на технологии, разработанные в компании AWS, но тезисы из него применимы как к другим облачным провайдерам (GCP, Azure), так и к "домашним" имплементациям (OpenFaaS).


Я собираюсь пройтись по трем, на мой взгляд самым важным, пунктам, касающимся Serverless

  • Сначала я "демистифицирую" понятие Serverless, отделив его от реактивных функций Lambda,

  • Затем я пройдусь по архитектуре Lambda

  • И закончу этот пост рядом рекомендаций по разработке и сопровождению Serverless приложений.

В дальнейшем я буду использовать sls для обозначения Serverless. Я очень ленивый.

Lambda != Serverless, Lambda in Serverless == True

Горе павшим жертвами сладких речей адвокатов и евангелистов! "Вам не нужно будет управлять парком машин!", "Вы сможете развернуть свое приложение за считанные секунды!", "С помощью sls эффективность вашей разработки возрастет в разы!", - это и многое другое пытаются втолкнуть мне толпы героев и евангелистов AWS, но я познал суть.

Видите ли, риторика "мне не нужно думать об инфраструктуре" обречена на провал. Разве код работает в воздухе? Разве он не нуждается в сети? Разве мне нужно заботиться об инфраструктуре, если у меня есть Kubernetes, и я просто объявляю в нем сущности?

На мой взгляд, лучше всего преимущество sls описал Рик Хулихан на своем докладе посвященному архитектурным паттернам DynamoDB, кратко затронув новую парадигму (весь доклад сам по себе интересный, но ссылка ведет на последние несколько минут).

Fail cheap - вот, что конкурентно "продает" sls. Чтобы проверить тезис, мне не нужно так много ресурсов, как понадобилось бы чтобы развернуть небольшой кластер из виртуалок или контейнеров. Скорость здесь - не решающий фактор. В 2021-ом году, что Lambda функция, что таблица DynamoDB, что контейнер в ECS/EKS, что экземпляр EC2 - все это запускается в считанные минуты, если не секунды.

Скрытое послание 1

Если на этом моменте у вас пропал интерес, смело закрывайте статью. Serverless не нужен просто, чтобы был. Это иной вид мышления и разработки, и не сказать, что он лучше уже устоявшихся.

Что такое Serverless

AWS предоставляет огромный набор сервисов для построение систем различной степени нагрузки и тяжести, часть из которых имеет бирку sls - очереди и потоковая обработка (SQS, Kinesis), нотификации (SES, SNS), интеграции (EventBridge, Step Functions), хранилище (S3), базы данных (DynamoDB), работа с данными (Glue, Athena). Если вы смотрели мое выступление на HighLoad++, вы помните, как именно я отличаю sls от "обычных" сервисов. Sls создает для вас отдельный уровень абстракции, снимая с вас операционную нагрузку по работе с сервисом (на самом деле нет - теперь вам надо учить новую технологию/подход).

Взять к примеру базы данных DynamoDB - сама СУБД уже есть! В ваше пользование предоставляется таблица с ее "пропускной способностью" (WCU/RCU), индексы и прочие фичи. В случае с сервисом ETL Glue вас не допускают к работе с самим движком ETL - вместо этого вы объявлете схему трансформации данных, указываете источник и пункт назначения данных, а так же описываете задачу. Все остальное находится вне вашего ведения и управления, чтобы вы себе лишний раз ногу не отстрелили (если очень хотите отстрелить - поднимайте свое на виртуалках ЕС2).

Отличие будет в биллинге. Если в случае с базами RDS и задачами ECS Fargate где оплата идет поминутно (или посекундно?), с sls вы платите за объем (сколько гигабайт "весят" данные в S3) и утилизацию (запросы и трафик).

За счет гранулярного биллинга вы можете точнее прогнозировать и считать расходы и проецировать их на вашу архитектуру и бизнес-логике. В частности, если у вас есть некий сервис, и конкретное действие пользователя вызывает 3 функции, которые пишут по сообщению в свою очередь, откуда потом данные складываются в таблицу, то несложно посчитать сколько вам обойдется 1000 таких действий. Или миллион.

Отсюда же и управление масштабируемостью. Все ограничения либо у вас в голове, либо отражены на странице квот AWS.

Ничего сложного, верно? Основное непонимание связано как раз с отсутствием простой истины в умах: sls - это абстракция над PaaS, которая тарифицируется по-другому.

Отсюда и сложности с sls приложениями. Sls приложение - не только набор Lambda функций за API GW. Sls приложение состоит только из sls сервисов.

Надеюсь, по этой теме вопросов у вас не осталось. Если остались - пишите в комментариях, отвечу всем по возможности.

Архитектура AWS Lambda

Родоначальница sls подхода вызвала в своей время немало шума, ведь ее релиз пришелся на то же время, что и буря вокруг Docker и рождение Kubernetes. За простотой Lambda функций (далее - Функция) стоит определенная хитрость, ведь мысль о том, что "просто нужно написать код и запустить" вызывает недоверие.

На самом же деле Функции несколько сложнее, чем звучат из сладких уст многочисленных sls евангелистов и героев, (и порождают не меньше сложностей, но об этом позже). Архитектура Функции состоит из 3 частей: Источник События (Triggering Event); внутренности AWS Lambda - runtime, deployment package (логика с зависимостями), слои (layers) и "дополнение" (extensions); точка назначения Функции (если имеется) - корзина S3, таблица DynamoDB, очередь SQS и т.д.

Картинка посвящается Ване Моисееву - моему другу и любителю иконок AWSКартинка посвящается Ване Моисееву - моему другу и любителю иконок AWS

В довесок к этому идут еще мониторинг, трассировка запроса и многие прелести, например Lambda Permission - а именно "разрешение" какому-то внешнему ресурсу (генератору событий) запускать Функции, передавая им полезную нагрузку (Событие).

Что происходит под капотом Lambda - история куда сложнее, и лучше послушать о ней из первоисточника.

Незамысловатая на первый взгляд архитектура породила очень много вопросов по эксплуатации и разработке, безопасности и мониторингу, compliance, governance и много много других. Покрыть их все - тема не одного десятка статей, поэтому дальше я пройдусь по основным практикам.

Как жить с Lambda

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

  1. Локальная разработка и отладка

  2. Архитектура sls приложений

  3. Организация кодовой базы

  4. Развертывание

  5. Эксплуатация (логирование, мониторинг, трассировка)

  6. Безопасность

Звучит сложно? Ну передавайте от меня привет тем, кто говорил, что sls это легко.

1. Локальная разработка и отладка

Стоит первым пунктом потому, что с этого все и начинается. AWS завез прекрасный инструмент под названием Serverless Application Model или SAM.

SAM представляет собой два компонента: интерфейс командной строки aws-sam-cli (ставится как через brew так и через pip) и шаблон (сильно упрощенный CloudFormation).

Для отладки Функции нужно объявить ее в шаблоне. Затем можно будет вызывать ее двумя способами: sam local invokeили sam local start-api(для Функций, отвечающих на вызовы API GW).

Если вы впервые видите SAM, то проще всего запустить один из quick start шаблонов, заботливо приготовленных для вас инженерами AWS.

$ sam initWhich template source would you like to use?1 - AWS Quick Start Templates2 - Custom Template LocationChoice: 1What package type would you like to use?1 - Zip (artifact is a zip uploaded to S3)2 - Image (artifact is an image uploaded to an ECR image repository)Package type: 1Which runtime would you like to use?1 - nodejs12.x2 - python3.83 - ruby2.74 - go1.x5 - java116 - dotnetcore3.17 - nodejs10.x8 - python3.79 - python3.610 - python2.711 - ruby2.512 - java8.al213 - java814 - dotnetcore2.1Runtime: 2Project name [sam-app]:Cloning app templates from https://github.com/aws/aws-sam-cli-app-templatesAWS quick start application templates:1 - Hello World Example2 - EventBridge Hello World3 - EventBridge App from scratch (100+ Event Schemas)4 - Step Functions Sample App (Stock Trader)5 - Elastic File System Sample AppTemplate selection: 1    -----------------------    Generating application:    -----------------------    Name: sam-app    Runtime: python3.8    Dependency Manager: pip    Application Template: hello-world    Output Directory: .    Next steps can be found in the README file at ./sam-app/README.md

Пройдя в sam-appможно вызвать функцию локально (первый запуск займет некоторое время, образ Lambda runtime нужно скачать):

$ sam local invoke HelloWorldFunctionInvoking app.lambda_handler (python3.8)Image was not found.Building image.......................................Skip pulling image and use local one: amazon/aws-sam-cli-emulation-image-python3.8:rapid-1.15.0.Mounting /Users/karentovmasyan/Development/Personal/dummy_project/sam-app/hello_world as /var/task:ro,delegated inside runtime containerSTART RequestId: e7036160-e11c-440a-b089-8099b1e0d500 Version: $LATESTEND RequestId: e7036160-e11c-440a-b089-8099b1e0d500REPORT RequestId: e7036160-e11c-440a-b089-8099b1e0d500Init Duration: 0.29 msDuration: 114.08 msBilled Duration: 200 msMemory Size: 128 MBMax Memory Used: 128 MB{"statusCode": 200, "body": "{\"message\": \"hello world\"}"}%

А установив плагин для JetBrain IDE или VS Code можно использовать и полноценный отладчик, чтобы узнать, где конкретно входящий JSON неправильно обрабатывается!

2. Архитектура sls приложений

Микросервисная архитектура подразумевает какой-то компонент (микросервис) достаточно изолированный от остальных, чтобы не рушить весь бизнес своим падением, и достаточно интегрированный, чтобы обслуживать конкретную бизнес-задачу.

Давайте представим некий market-service для биржи, который имплементирует API GET /markets. Вызовы на этот API вернут нам список текущих рынков, базовых валют, по прямому запросу - тикер.

С точки зрения микросервисов это может имплементировать один сервис, в котором будут реализованы следующие API:

GET /marketsGET /markets/baseGET /markets/ticker?pair=USD-RUB

В контейнерной оркестрации это делается с помощью обычного сервиса, а вся логика имплементирована в нем.

Реализовать такой функционал в Lambda тоже можно в одной функции. Полезная нагрузка в запросе будет выглядеть следующим образом (при условии, что запрос пришел из API GW):

# GET /markets{  # some params...  "resource": "/markets",  "path": "/markets",  "headers": #...  "queryStringParameters": null,  # etc...}# GET /markets/ticker?pair=USD-RUB{  # some params...  "resource": "/markets/ticker",  "path": "/markets/ticker",  "queryStringParameters": {  "pair": "USD-RUB"}} 

Мы можем осуществить проверку пути/ресурса и в зависимости от этого реализовать логику одной функции. Что-то навроде:

def get_markets():  passdef get_ticker(pair):  passdef handler(event, context):  path = event.get('path')  if path == '/markets':    return get_markets()  elif path == '/markets/ticker':    return get_ticker(event.get('queryStringParameters').get('pair'))

У этого подхода есть одно заметное достоинство - код централизован, и мы точно знаем где его править. Однако этот крайне неэффективен и является антипаттерном для sls архитектур. И вот ряд почему:

  1. Тарификация Функции идет по времени выполнения и мы тратим драгоценное время на избыточный control flow.

  2. Если Функция должна "ходить" в несколько мест (S3, DynamoDB и прочие API AWS), то мы даем ей опасно большое количество разрешений.

  3. Проблемы масштабируемости: нам нужно масштабировать GET /markets/ticker, но вот GET /marketsдорогой, и нам хотелось бы применить к нему throttling, что в условиях одной функции сделать невозможно.

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

И каждая функция будет иметь свою ограниченную область применения. Это порождает другую проблему - как нам организовать управление повторяемым кодом и кодовую базу в принципе?

3. Организация кодовой базы

Подход выше открывает 2 проблемы: управление общим кодом (что если 90% кода в Функциях одинаковые? Где моя бритва?!) и организация репозитория.

Раньше чтобы запаковать Функцию с зависимостями, нужно было создавать специальный deployment package и загружать его на S3. Что вручную, что с SAM это представляет собой следующее:

$ pip install -t . -r requirements.txt$ zip -r lambda.zip .$ aws s3 cp lambda.zip s3://bucket_name/

И если с внешними зависимостями еще можно как-то жить, то с внутренними возникает вопрос. А в "многофункциональном" sls приложении наверняка найдется одна или две библиотеки, которые делят между собой все функции.

Для компилируемых языков проблема не такая неприятная, управление зависимостями пройдет на стадии сборки. Для интерепретируемых языков, таких как Python, JavaScript и Ruby, проблема решается с помощью слоев (Lambda Layers). Слои Функций работают по схожему принципу со слоями контейнерных образов и предоставляют собой отдельное хранилище, монтируемое к runtime'у Функции.

Таким образом, проект можно организовать следующим образом:

$ tree -L 2. README.md  requirements.txt src # исходный код  lambda # handler'ы приложения  lib # общие библиотеки template.yaml tests venv

В шаблоне SAM можно указать директорию, чтобы создать нужный слой:

Resources:  Layer:    Type: AWS::Serverless::LayerVersion    Properties:      CompatibleRuntimes:          - python3.6          - python3.7          - python3.8          - python3.9      ContentUri: 'src/lib'

Ну и дальше дело техники - в ресурсе функции указать версию слоя, ссылаясь на нее в самом же шаблоне с помощью Fn::Ref.

Resources:  MarketsGet:    Type: AWS::Serverless::Function    Properties:      CodeUri: src/lambda/markets/get      Handler: markets_get.handler      Layers:        - !Ref Layer      Events:        ApiEvent:          Type: Api          Properties:            Path: /markets            RestApiId: !Ref Api            Method: get

Возникает вопрос: "А что делать, если в одном репозитории мне нужны разные версии зависимостей?" С помощью Слоев эта проблема не решается. Самое правильное - собрать, упаковать и положить зависимость в репозиторий. В случае с Python - Pip server.

4. Развертывание

С архитектурой и кодовой базой разобрались, теперь надо понять как "катить". Я уверен, ни для кого не станет сюрпризом, что катиться нужно с помощью IaC инструментов (молодое поколение называет это GitOps).

Что CloudFormation, что Terraform обладают возможностью объявлять ресурсы в облаке от Amazon, но объявление sls ресурсов может стать очень муторным процессом в виду гибкости и тонкой настройки.

SAM хорош тем, что создает абстракцию над CloudFormation для упрощенного объявления ресурсов sls приложений. В свою очередь Антон Бабенко сделал похожий инструмент для TF. Выбирать между одним или вторым я не буду (в тусовке амазонщиков у меня уже есть репутация "хейтера" Terraform), а разбор отличий заслуживает отдельной статьи.

Все еще живой Serverless framework тоже управляет жизненным циклом sls приложений (сам я с ним не работал, так что на ваш страх и риск).

Ну и вишенка на торте и вершина айсберга Хайпа - Cloud Development Kit или CDK. В отличие от выше описанных инструментов CDK объявляет инфраструктуру с использованием высокоуровневых языков программирования (TypeScript, JavaScript, Python, C#, Java), код которых компилируется ("синтезируется") в шаблон CloudFormation. Похожую историю имплементировали и ребята из HashiCorp, назвав ее cdktf.

Архитектура CDK/cdktf сама по себе сложная, но это не выступает барьером, который даже неопытные инженеры-облачники не в состоянии предолодеть. В сети полно материалов, как блогов, так и self-paced workshop, лекций, CDK day - их море.

Скрытое послание 2

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

В остальном развертывание sls приложений ничем не отличается от развертывания других приложений на AWS - у вас имеется цепочка поставки (pipeline), которая прогоняет тесты, пакует ресурсы и раскатывает их по различным регионам и аккаунтам. Делаете вы это с помощью sam deployили terraform apply- не так уж и важно.

5. Эксплуатация

С внутренними метриками и логами Функций проблем нет - они сами собой складываются в CloudWatch (разумеется, если вы не забыли прописать нужные политики в роли).

Отладить простейшую CRUD-функцию тоже много ума не надо. Прилетел JSON в Функцию, с ним что-то произошло, выхлоп лег куда-то. Если что-то в этом потоке пошло не так, достаточно локально отладить Функцию с помощью того же SAM CLI, приложив к ней багообразующий event.

Другое дело, если Функций много и работают они в каком-то долгом транзакционном процессе, наппример Step Functions. Отлавливать логи по времени, как прилетел злосчастный запрос, который не смог корректно обработать - то еще приключение, и разработчик может потратить неприемлемо много времени, чтобы найти баг.

На помощь придет инструмент observability от AWS под названием X-Ray. X-Ray является сервисом трассировки, он тесно интегрирован в sls экосистему. Достаточно включить поддержку X-Ray в Функции, и каждому запросу начнет присваиваться нужный идентификатор.

После этого можно спокойно посмотреть, куда прилетел запрос, через какие Функции прошел, и что было внутри логов тех Функций на момент принятия запроса. X-Ray стоит отдельных денег, но его можно как включить, так и выключить - что вполне себе удешевляет процесс отладки, предоставляя возможность на лету переводить ваше приложение в production в debug mode.

6. Безопасность

С очевидными вещами разбираться не будем. Всем известны такие базовые паттерны безопасности, как шифрование (at-rest, in-transit) и контроль сетевого периметра c помощью NACL и Security Groups.

У каждой Функции должна быть своя IAM роль. И по лучшим практикам безопасности каждая роль должна следовать least-privilege principle - то есть не даем больше прав, чем минимально нужно для работы приложения.

Иными словами, если наша Функция выполняет операцию dynamodb:PutItem, как минимум странно давать роли разрешение dynamodb:*(разрешаем все действия по отношению к DynamoDB). Так же странно видеть Resource: "*", в то время как работа идет в отношении одной таблицы.

Поэтому первое правило безопасности в sls приложениях - минимум прав. Потратье время и изучите как работает AWS IAM, и какие трюки там есть для тонкого управления доступом.

Скрытое послание 3

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

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

Бояться тут надо не того, что кто-то взломает Функцию и изнутри нее будет делать всякие глупости (такая вероятность есть, но это тема другой статьи), но того, что роль Функции кто-то может assume, т.е. "принять на себя". Именно таким образом взломали Capital One: не взломали сервер, а просто получили ARN роли, через которую получили STS ключ, секрет и токен. Потому что кто-то ошибся в поле Principal. Стыд!


Вроде ничего не забыл? Я не ожидаю, что после прочтения вы сразу побежите писать Lambda функции и хвастаться стремительно сокращаюимся парком EC2 машин. Ниже я прилагаю ссылки, которые позволят вам двигаться дальше в изучении Serverless и Lambda в частности:

P.S. Я редко пишу на русском языке и еще реже чувствую страдания моей русскоязычной амазонской братии. Если у вас есть что-то, что хочется понять, но нет сил или навыка - пишите, я попробую разжевать это для вам в удобоваримом формате.

Подробнее..
Категории: Lambda , Amazon web services , Aws , Cloudnative , Serverless

Перевод Как выглядит обычная 100-но бессерверная архитектура в AWS Lambda

11.05.2021 10:09:42 | Автор: admin

Когда мы говорим о бессерверной архитектуре, мы обычно выходим далеко за рамки модели функция как услуга (FaaS), одной из реализаций которой являются функции AWS Lambda.

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

Как же будет выглядеть такого рода архитектура в интернет-проекте?

Мы вTheodo очень любим бессерверные технологии и внедряем их во всё новые и новые проекты. Какие-то сервисы и модели мы применяем наиболее широко. Поэтому мы решили поделиться опытом в разработке архитектуры веб-приложений. Если бессерверные технологии вам в новинку и хотелось бы получить общие рекомендации и ответы на вопросы, то вы обратились по адресу!

Откровенный и вопиющий спойлер (не бойтесь, мы успеем погрузиться в каждый аспект темы):

На схеме вверху каждый блок это стандартная четко выделенная область применения или техническая возможность, характерная для большинства бессерверных архитектур. Это необязательно будут микросервисы или стеки, если говорить на языке CloudFormation (это мы обсудим чуть позже).

Мы рекомендуем полномасштабный переход на AWS Lambda c использованием событийных микросервисов, написанных на TypeScript

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

Amazon Web Services

Конкурентный рынок облачных технологий весьма насыщен: AWS, GCP, Azure, IBM Cloud, Alibaba Cloud каждая из этих компаний предлагает свое замечательное решение и развивается беспрецедентно высокими темпами.

Бен Эллерби (Ben Ellerby) сравнил трех крупнейших облачных провайдеров в этой статье: мы отдали предпочтение решениям AWS. С точки зрения бессерверных технологий они оказались самыми продвинутыми. Именно решения AWS позволяют нам максимально приблизиться к полностью бессерверной архитектуре. Чтобы это проиллюстрировать, в следующей части статьи мы подробно рассмотрим каждый из AWS-сервисов, входящих в состав наших архитектурных блоков.

Node.js с использованием TypeScript

JavaScript это один из самых популярных языков программирования в мире, с огромным сообществом разработчиков (посмотрите статистику на GitHub). Мир бессерверных вычислений не сильно выпадает из этой статистики. Например,по данным Datadog, 39% всех развернутых сегодня систем AWS Lambda работает на JavaScript, хоть Python и лидирует с 47%. TypeScript делает еще один шаг вперед и добавляет к JavaScript еще один замечательный уровень защиты. И наконец, JavaScript отлично работает в AWS Lambda с подавляющим большинством сценариев использования!

Serverless Framework

Именно в этом фреймворке по большей части реализуется практика IaC (инфраструктура как код) поверхCloudFormation. Задав функцию AWS Lambda, которая будет реагировать на HTTP-событие, Serverless Framework автоматически развернет связанный ресурс API-шлюза, а также соответствующий маршрут вместе с новой функцией AWS Lambda. Затем, когда будет достигнута предельная емкость фреймворка и нам понадобится более сложная конфигурация сервисов, мы сможем легко добавить CloudFormation.

C гордостью сообщаем: команда Theodo настолько уверовала в Serverless Framework, что решиластать официальным партнером фреймворка!

Узкоспециализированные функции AWS Lambda

Lambda это именно функция. У нее есть одна задача, и она выполняет ее хорошо. Нужно подтянуть в интерфейс список элементов? Создайте для этого функцию AWS Lambda. Нужно отправить пользователю подтверждение регистрации по электронной почте? Создайте еще одну функцию AWS Lambda. Разумеется, узкоспецифичный код (например, для описания сущностей данных) может быть факторизован и предоставлен в общее пользование в виде отдельной папки утилит. Однако работать с таким кодом нужно очень внимательно, потому что любое его изменение повлияет на все связанные с ним функции AWS Lambda, а поскольку эти функции тестируются и развертываются независимо друг от друга, легко можно что-то упустить (TypeScript нам тут в помощь).

разделение на микросервисы

Чтобы не заставлять команды разработчиков толкаться и мешать друг другу, не раздувать файлы package.json и serverless.yml до немыслимых размеров (кстати, в CloudFormation довольно щедрое ограничение на число ресурсов 500), не тратить много времени на развертывание CloudFormation, а также лучше ориентироваться в базе кода и четко разграничивать ответственность между функциями AWS Lambda, мы устанавливаем границы и разделяем проект на отдельные микросервисы.Бен Эллерби написал здесь о полезной методологии мозгового штурма под названием EventBridge Storming, которая как раз и помогает определить эти границы.

В нашем монорепозитории: 1 микросервис = 1 стек CloudFormation = 1 файл serverless.yml + 1 файл package.json. Кроме того, микросервисы управляют своими собственными сущностями данных и не предоставляют их в общее пользование другим микросервисам.

Раньше мы рекомендовали переходить на полноценный JavaScript, однако есть множество причин, по которым вам, возможно, захочется использовать другой язык. А может быть, вы предпочтете поэтапно перейти на бессерверные вычисления на чистом JavaScript. Неоспоримое преимущество микросервисов при бессерверных вычислениях заключается в том, что можно легко комбинировать технологии в рамках архитектуры, сохраняя ее простоту и согласованность, а также не зависящие от используемых технологий интерфейсы между микросервисами.

взаимодействие на языке событий

Эти микросервисы должны быть полностью независимыми друг от друга если один из них перестанет работать или претерпит кардинальные изменения, остальные компоненты системы не должны из-за этого сильно страдать. Чтобы это стало возможно, функции Lambda общаются друг с другом исключительно через бессерверную шину событий EventBridge. Вэтой статье Бен Эллерби (это снова он) подробно рассказывает о том, что делает сервис EventBridge таким полезным.

Для каждого типа функций требуется особая бессерверная архитектура с конкретным набором AWS-сервисов

Установив контекст, давайте пройдемся по архитектурным блокам приведенной выше устрашающей диаграммы и посмотрим, какие самые бессерверные сервисы можно найти в AWS.

Фронтенд (разработка)

Наш сияющий бессерверный бэкенд должен каким-то образом скармливать данные фронтенду. Чтобы облегчить фронтенд-разработку при использовании AWS, мы используемAmplify.

Amplify это сразу несколько полезных вещей: инструмент CLI, инструмент IaC, SDK, а также набор UI-компонентов. Мы используем при разработке фронтенда JS SDK, чтобы ускорить интеграцию с ресурсами (например, Cognito для аутентификации), развернутыми при помощи других инструментов IaC, таких как Serverless Framework.

Хостинг сайтов

Большинство современных веб-сайтов это одностраничные приложения (Single Page Applications, SPA). Это полнофункциональные динамические приложения, упакованные в единый набор статических файлов, которые загружает пользовательский браузер, когда он впервые обращается к URL-адресу. В среде AWS мы размещаем эти файлы на файловом хранилищеS3и предоставляем к нему доступ через CDN-сетьCloudFront.

Тем не менее тенденция явно склоняется в пользу SSR-сайтов, использующих отрисовку на стороне сервера, как в случае технологии Next.js. Для настройки SSR-сайта в бессерверной инфраструктуре мы используемLambda@Edgeвнутри CloudFront. Это позволяет реализовать отрисовку на стороне сервера, располагая функции AWS Lambda как можно ближе к конечным пользователям. Чтобы погрузиться в тему, прочитайтеэту статью.

Домен и сертификат

Разумеется, для нашего сайта мы не можем довольствоваться необработанным автоматически сгенерированным S3-адресом, поэтому мы формируем сертификаты и привязываем их к CloudFront с помощьюCertificate Manager, а затем для управления доменами используемRoute 53.

Бизнес-API

Наконец, мы должны организовать взаимодействие сайта с бэкендом, чтобы начать получать и отправлять данные. Для этого мы используемAPI-шлюз, который позволяет обрабатывать HTTP-соединения и маршруты, синхронно вызываяLambda-функциюдля каждого маршрута. Наши Lambda-функции содержат бизнес-логику взаимодействия сDynamoDB, позволяющую хранить и потреблять данные.

Как мы уже говорили, мы используем событийную логику, то есть быстро отвечаем пользователям, а параллельно, так сказать за кадром, продолжаем обрабатывать пользовательский запрос в асинхронном режиме. DynamoDB, например, предоставляет доступ к потокам, которые могут асинхронно вызвать Lambda-функции в качестве реакции на любое изменение данных. Большинство бессерверных сервисов поддерживают аналогичную возможность.

DynamoDB это огромная отдельная тема, и вэтой статьеРоб Кронин (Rob Cronin) дает обнадеживающий комментарий о классических проблемах, связанных с широко известной базой данных NoSQL.

Асинхронные задачи

Управление нашей архитектурой устроено при помощи событий, поэтому многие Lambda-функции асинхронны и запускаются событиями EventBridge и S3, потоками DynamoDB и так далее. Например, у нас может быть асинхронная Lambda-функция,которая будет отвечать за отправку пользователю приветственного письма после успешной регистрации.

Обработка отказов играет решающую роль в распределенной асинхронной системе. Поэтому в случае асинхронных Lambda-функций мы используем DLQ очередь недоставленных сообщений и передаем окончательное сообщение об отказе сначала в службу уведомленийSNS (Simple Notification Service), а затем в службу очередейSQS (Simple Queue Service). Сейчас нам приходится поступать так, потому что на данный момент невозможно привязать SQS напрямую к DLQ-очереди Lambda-функций.

Передача пуш-сообщений от бэкенда к фронтенду

В асинхронном режиме фронтенд уже не может ограничиваться показом иконки загрузчика в ожидании XHR-ответа. Нужны состояния ожидания и передача пуш-данных от бэкенда. Для этого мы используем API WebSocket от нашего API-шлюза, который поддерживает постоянное соединение WebSocket и запускает Lambda-функции, только когда приходят сообщения. Я написалстатьюдля глубокого погружения в тему. Там я рассказал, почему мы выбрали WebSocket, а не другие решения и как лучше еговнедрять.

Загрузка файлов

Поскольку обработка потока загружаемых файлов из Lambda-функции может оказаться весьма дорогостоящим делом, в S3 Lambda-функции могут сгенерировать подписанный (защищенный) URL-адрес загрузки, который будет использоваться нашим фронтендом для прямой загрузки файлов в S3. Приятный момент заключается в том, что в данном случае (как и в большинстве сервисов AWS) можно сделать другую асинхронную Lambda-функцию, которая будет слушать изменения файла S3 и обрабатывать любые последующие операции.

Пользователи и аутентификация

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

В нашем примере мы проиллюстрируем возможность привязать собственныеавторизаторы Cognito к нашим маршрутам API-шлюза. Мы также назначили одну Lambda-функцию для обновления аутентификационных токенов, а другую для получения списка пользователей.

Здесь я бы хотел предостеречь: Cognito еще не готов к тому, чтобы ему можно было полностью доверить управление сущностью пользователя. У него есть ряд ограничений, например по числу поддерживаемых атрибутов. Если для вас важна гибкость, пожалуй, лучше хранить пользовательские атрибуты в DynamoDB.

Машина состояний

В некоторых ситуациях наша логика и поток данных могут стать довольно сложными. Чтобы нам не пришлось вручную управлять этим потоком изнутри Lambda-функций (а следить за происходящим и структурировать весь процесс дело непростое), у AWS есть то, что нам нужно: сервисStep Functions.

Мы объявляем машину состояний через CloudFormation включая любые последующие шаги и состояния, все ожидаемые либо неожиданные результаты и назначаем этим шагам некие стандартные действия (например, wait или choice) либо Lambda-функцию (выбираем изподдерживаемых интеграций). Затем мы можем в реальном времени, используя интерфейс AWS, следить за работой машины (на основе логов). На каждом из этих шагов мы можем задать обработку повторных и неудачных попыток. Бен Эллерби более подробно описал сервис в этой статье.

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

  • Шаг 1 Lambda: дает SaaS указание выполнить email-рассылку и получает в ответ идентификатор рассылки.

  • Шаг 2 Task Token Lambda: получает callback-токен от Step Function, связывает его с идентификатором рассылки, а затем ждет обратного вызова от SaaS.

  • Шаг 3 (внепотока) Lambda: вызывается перехватчиком из SaaS при изменении статуса рассылки (ожидание, архивация, неудача, успех) и возобновляет поток по статусу кампании, используя соответствующий callback-токен.

  • Шаг 4 выбор: в зависимости от статуса, если рассылка еще не была успешно выполнена, вернуться к шагу 2.

  • Шаг 5 (заключительный) Lambda: обновляет данные о пользователях после рассылки.

В этой статье хорошо объяснено, как работают токены Task Token.

Безопасность

Управление идентификацией и доступом (Identity & Access Management, IAM) эта система нужна для того, чтобы филигранно управлять всеми AWS-доступами со стороны разработчиков, конвейеров CI/CD, а также сервисов AWS, которые обращаются друг к другу. Поначалу это может выглядеть пугающе, однако преимущество тут в широких возможностях тонкой настройки. Они позволяют нам хорошо продумать и детализировать любое действие, которое мы хотим разрешить конкретному потребителю. Это означает, что каждый уровень нашей инфраструктуры защищен по умолчанию.Бен Эллерби выступал на ServerlessDays Nashville на эту тему.

Что касается данных повышенной секретности, таких как API-ключ SaaS, то мы безопасно храним их вSystems Manager (в Parameter Store). Запрашиваем мы эти данные из наших файлов в Serverless Framework или в CloudFormation или даже напрямую из исходного кода (при помощи соответствующего SDK). Полезно упомянуть, что системаSecrets Managerвыполняет аналогичные действия. Такжев данном случае полезен сервисKey Management Service (KMS) он помогает нам управлять ключами шифрования.

Если вас заинтересовала эта тема,Sat G написалотличную обзорную статью о безопасности в бессерверной среде там можно найти более полную информацию.

Мониторинг

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

Мы не прекращаем поиск альтернативных вариантов.X-Ray, например, комплексно отслеживает весь путь запросов в рамках нашей распределенной системы, а затем красочно отображает его в динамике. Иногда, правда, он сбивается со следа, поскольку пока еще не поддерживает некоторые сервисы, такие как EventBridge (а это как раз центральный элемент нашей архитектуры). Еще один сервис,ServiceLens, развертывается поверх X-Ray и CloudWatch и выглядит потрясающе. Есть также ряд перспективных внешних (с точки зрения AWS) решений: Thundra, Epsagon, Lumigo. Однако мы еще не успели в полной мере их опробовать.

Команда Theodo гордится тем, что добавила собственный компонент в экосистему: если вы хотите повысить у себя качество разработки и уровень наблюдаемости, вам обязательно стоит попробовать инструментServerless-Dev-Tools.

Бессерверные вычисления это потенциал, способный перерасти в успех, и мы хотим вам помочь сделать решительный шаг в его сторону

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

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

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

Я, безусловно, продолжу работать над всеми этими темами, поэтому, если вам интересно,напишите мне в твиттер.

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

П.С. От переводчика

Присоединяйтесь к сообществу Serverless в Telegram:Yandex Serverless Ecosystem. Мы регулярно встречаемся в виртуальном пространстве и похоже созревает потребность в очной встрече.

Подробнее..
Категории: Aws , Serverless , Faas

Прыжок до небес запускаем телеграм бота на Python в serverless облаке

16.04.2021 00:22:45 | Автор: admin

Одним из современных архитектурных подходов в области облачных вычислений является так называемый Serverless. Этот способ запуска приложений в облаке освобождает разработчиков от нужды администрировать сервер и заботиться о чем-то, кроме кода.

Очевидно, что для такой задачи, как телеграм бот, этот подход очень удобен.

В этой статье описаны все шаги для запуска бота в Yandex.Cloud Functions. Опоры на код я не делаю. Наша основная задача сейчас - настроить запуск в облаке.

Создадим бота

Чтобы создать телеграмм бота, нужно воспользоваться @BotFather. Для этого используйте команду /new_bot. Скопируйте токен (он будет там, где оранжевая полоса)

Настройка Yandex.Cloud

Для работы с Яндекс.Облаком перейдите на сайт https://cloud.yandex.ru/ и войдите в свой аккаунт. Если вы все сделали правильно, вы увидите рабочий дашборд.

Cloud Functions

  • Перейдите в раздел Cloud Functions

  • Создайте новую функцию c названием, например, python-tg-bot.

  • Укажите язык python и выберите самую последнюю версию (python3.8 на момент написания этой статьи).

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

  • Запомним идентификатор функции (первая строка)

API-Gateway

Чтобы мы смогли получить доступ к нашей функции, нужно настроить API-Gateway.

  • Перейдите в раздел API-Gateway

  • Создайте новый шлюз и настройте его.Скопируйте конфигурацию и замените YOUR_FUNCTION_ID на идентификатор функции, полученный ранее.

openapi: 3.0.0info:title: for-python-tg-botversion: 1.0.0paths:/:post:x-yc-apigateway-integration:type: cloud-functionsfunction_id: YOUR_FUNCTION_IDoperationId: tg-webhook-function
  • Запомним ссылку, по которой можно вызвать нашу функцию

Устанавливаем webhook

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

Для этого:

  • Установите библиотеку pip install pyTelegramBotAPI

  • Запустите питоновский скрипт:

import telebotbot = telebot.TeleBot("YOUR_TOKEN")bot.remove_webhook()bot.set_webhook("YOUR_URL")

Тестируем

Сделали "эхо-бота". Что дальше?

Как говорилось в начале, такой способ запустить бота очень легок для разработчика, но что же делать, если нам нужна база данных или сложные api-запросы к другим ресурсам. Все это можно реализовать в Yandex.Cloud. Например, с помощью сервисов Yandex Database (тоже serverless) или Object Storage. Отдельные сервисы можно запустить, как отдельные функции. В следующей статье, я расскажу о том, как подключить базу данных Yandex Database к боту.

Именно возможность создавать все по кусочкам и уверенность в том, что однажды настроенный модуль будет работать всегда, отличает serverless подход от простого "давайте свалим все в одну виртуальную машину".

Тарифы Yandex.Cloud

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

  • Yandex API Gateway

    Каждый месяц не тарифицируются первые 100 000 запросов к API-шлюзам.

  • Yandex Cloud Functions

    Каждый месяц не тарифицируются:

    • первые 1 000 000 вызовов функций;

      \nu = \frac{10^6}{30 * 24 * 60} \approx 23 \text{ } \frac{\text{запроса}}{\text{час}}

    • первые 10 ГБчас выполнения функций.

  • Бессерверный режим Yandex Database

    Каждый месяц не тарифицируются:

    • первые 1 000 000 операций (в единицах RU);

    • первый 1 ГБ/месяц хранения данных.

Полезные ссылки

  1. Репозиторий с кодом бота

  2. Yandex.Cloud

  3. Описание тарифа free tier

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru