Все, кто интересуется serverless computing (бессерверными вычислениями), наверняка видели публикации с примерами запуска небольших приложений или тестирования работы упрощенных eCommerce MVP (minimum working product) на Azure Functions или AWS Lambda. А можно ли запустить как бессерверное бэкенд-приложение полноценную коммерческую headless платформу B2B электронной торговли Virto Commerce, написанную на .NET Core? Такой эксперимент мы поставили в лаборатории разработки Virto DevLabs (компания Virto Commerce), и да — это работает.

Что вы узнаете из этой статьи?

  • Про актуальность бессерверных вычислений в eCommerce;

  • Как это работает — наше видео про запуск платформы Virto Commerce как serverless;

  • О пользовательских обработчиках со ссылкой на документацию;

  • Что нужно добавить к проекту для запуска на Azure Functions;

  • Как локально запустить приложение, предназначенное для Azure Functions;

  • Как задеплоить приложение в serverless среде на Azure Functions;

  • С какими проблемами мы столкнулись и как их решили;

  • Эксклюзив: Результаты тестирования serverless против dedicated.

Зачем вообще запускать eCommerce как бессерверное приложение

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

Модель оплаты «за ценность», т.е. в соответствии с нагрузкой и без какого-либо минимального уровня, тоже очень подходит для eCommerce. И здесь, поскольку речь о торговле, то экономия на издержках — одно из условий жизнеспособности этого вида бизнеса.

Представим себе ситуацию: «Ночь, улица, фонарь, аптека» — и на сайте аптечной сети никого нет. Если это бессерверная платформа, то платить хостеру за ресурсы не нужно. Утром в СМИ супер-новость — ВОЗ сообщает, что найдено новое свойство известного лекарства (для примера неважно чего, хотя в реале был ажиотаж на противомалярийный препарат гидроксихлорохин) в качестве средства для борьбы с коронавирусной инфекцией COVID-19. Тысячи посетителей под эмоциями ринулись на сайт делать заказ этого препарата «на всякий случай». Даже если препарата нет в наличии, такая пиковая нагрузка будет для сайта аптечной сети сродни небольшой DDoS-атаке, и сайт перестанет продавать даже имеющиеся в каталоге товары.

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

Термин «бессерверные вычисления» (serverless computing) относится к маркетинговой терминологии на рынке облачного хостинга.  Это не означает, что приложения выполняются без использования серверов. Речь идет о том, что клиент хостинга не покупает какую-то определенную конфигурацию аппаратных ресурсов сервера под свой проект (число ядер ЦП, память, дисковое пространство), а разрешает хостингу подобрать подходящую конфигурацию в зависимости от нагрузки, создаваемой приложением. Это удобно разработчикам, т.к. все вопросы, связанные с серверным пространством и ИТ-инфраструктурой, решаются поставщиком услуг облачного хостинга, а счет выставляется по факту потребленных ресурсов.

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

При этом, хотя headless-архитектура не является обязательным требованием для запуска платформ eCommerce как бессерверный бэкенд на Azure Functions. доступ к которому по API, headless позволил нам переиспользовать наши автоматические тесты и сделать быстрое тестирование тестовой среды. И забегая вперед — подтверждение о том, что наша платформа запустилась как бессерверное приложение, мы узнали по ответу API. 

Microsoft дает добро

Идея разработчиков Virto DevLabs попробовать запуск бэкенда eCommerce-платформы на Azure Functions зрела давно, но на практике такой инструментарий стал доступен только в марте 2021. Тогда Microsoft объявила о поддержке запуска производственных приложений .NET 5 в Azure Functions, и это было принципиально важно, т.к. Virto Commerce написана на этом стеке.

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

Далее в этой статье мы расскажем про опыт запуска платформы B2B электронной торговли (приложения ASP.NET Core Web API) как serverless, покажем основные шаги и потенциальные проблемы, с которыми столкнулись.

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

Лучше один раз увидеть — поэтому мы записали видео как выполнили запуск платформы Virto Commerce как serverless приложения под управлением Windows. Мы также запускали наше приложение и под Linux, и об этом ниже.

 Продолжим … В сентябре 2020  в Azure Functions появилась замечательная возможность использования пользовательских обработчиков  Azure Functions custom handlers, которая  существенно расширила поддержку запуска приложений как функции. Стало возможным запускать приложения, написанные на любых, даже официальное не поддерживаемых средой Azure Functions языках и средах — таких как Go, Rust, R, PHP и др.

Если кратко, то эта технология обработчиков очень напоминает старую бородатую CGI, когда Web-server запускал внешнее приложение (скрипт) и передавал туда http-запросы через специальный интерфейс. По такому же принципу построены пользовательские обработчики Azure Functions.

Диаграмма работы пользовательских обработчиков. (источник: Microsoft)
Диаграмма работы пользовательских обработчиков. (источник: Microsoft)

Когда Functions host переадресует все входящие события от всех поддерживаемых триггеров на «легковесный» Web Server, тот запускает основной код пользовательского обработчика “Functions code”, и результат работы передается обратно Functions host, и далее через Output Binding на выход функции к получателям. 

Единственное условие для работы пользовательского обработчика — это поддержка HTTP вашем языком/средой выполнения. Так как в ASP.NET Core Web API все это присутствует в избытке, мы и будем запускать наше приложение-функцию как пользовательский обработчик Azure Functions Custom Handlers.

Что установили заранее для запуска приложения на Azure Functions

1.     Для начала установили Azure Functions Core Tools – этот инструментарий понадобился для тестирования и локального запуска нашего приложения как serverless Azure Functions.
2.     Взяли исходник Deploy from source code - Virto Commerce Documentation и на новом шаге добавили к проекту обязательные файлы, необходимые для запуска приложения как Azure Functions Custom handler. Тут надо повториться, что благодаря Custom handler мы смогли запустить платформу без каких-либо изменений кода.
3.     В продолжение работы добавили два файла, host.json и function.json:

host.json  - сообщает хосту функций, куда отправлять запросы, указывая на веб-сервер, способный обрабатывать события HTTP

Файл function.json расположен в папке, имя которой используется в качестве имени функции. Мы выбрали название папки azure-func, туда будем прозрачно проксировать весь входящий HTTP трафик с сохранением оригинальных путей для нашего Web API приложения.

4.     В итоге, файловая структура запуска eCommerce платформы стала выглядеть так

Скрин файловой структуры поатформы c файлами json
Скрин файловой структуры поатформы c файлами json

Для этого делаем publish в папку bin:

dotnet publish

переходим в publish папку

cd bin/debug/ net5.0/publish

6.     Запускаем нашу функцию-приложение:

func start

Тут мы проверили, что наше приложение как функция работает, по умолчанию функция слушает 7071 порт (http://localhost:7071/{*paths}), так что будем использовать его для обращения к нашему API.

curl http://localhost:7071/api/...

Деплой приложения на serverless env на Azure Functions

Для того чтобы выполнить деплой Virto Commerce на Azure Functions, установили интерфейс Azure CLI, так как все операции выполняли с помощью командной строки.

Для разворачивания приложения-функции на Azure Functions, создали три типа ресурсов:

       I.          Группу ресурсов (Resource group), являющуюся логическим контейнером для соответствующих ресурсов.
см. [Create resource groups] (https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/manage-resource-groups-portal#create-resource-groups)

     II.          Аккаунт хранилища (Storage account), который используется для отслеживания состояния и другой информации о ваших функциях.
см. [Create a storage account] (https://docs.microsoft.com/en-us/azure/storage/common/storage-account-create)

    III.          Приложение Function app, которое предоставляет среду для выполнения приложения платформы как функции.

Дальнейшие действия разберем по пунктам:

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

az login

Эта команда выполнила авторизацию и сопоставила учётную запись с нашей подпиской Azure.

2.     Идем дальше, создали группу ресурсов (resource group):

az group create -l westeurope -n serverless-weather-forecast-rg

3.     Далее создали storage account для хранения кода нашего приложения как функции:

az storage account create --name weatherfuncstorage --location westeurope  --resource-group serverless-weather-forecast-rg --sku Standard_LRS

4.     Последним подготовительным шагом будет создание function app в нашей resource group:

az functionapp create --resource-group vc-platform-func-rg --consumption-plan-location westeurope --runtime custom --functions-version 3 --name vc-platform-func-app --storage-account vcplatformfuncstorage --os-type {Linux, Windows} --subscription <your azure subscription id>

После успешного выполнения всех этих действий мы оказались готовы к последнему шагу — публикации (деплоя) в Azure.

func azure functionapp publish vc-platform-func-app
--subscription <your azure subscription id>

Значение false для конфигурации
Значение false для конфигурации

Важно: чтобы сократить время запуска приложения платформы, на следующем шаге после успешной публикации в Azure мы установили для параметра
VirtoCommerce: RefreshProbingFolderOnStart для Windows или VirtoCommerce__RefreshProbingFolderOnStart для Linux значение false для конфигурации приложения-функции Azure на портале Azure.

С чем мы столкнулись при запуске нашего приложения eCommerce как serverless на Azure Functions

  1. Холодный старт. Общеизвестны опасения про возможность проблем с медленным стартом приложения, но по факту наше большое модульное приложения B2B электронной коммерции Virto Commerce стартовало менее чем за 30 сек. Это не нарушило отведенный лимит и каких-то заметных сбоев даже при нагрузочном тестировании с перерывами в активности замечено не было

  2. Соблюдение нейминга. Поскольку сервис Azure Functions по умолчанию навязывает некоторый naming convention для route, было сложно сохранить прежние пути для WEB API в таком виде, чтобы для существующих клиентов это было незаметно. Вот такой код мы добавили в файл host.json:

    "http": {
    //Эта настройка убирает обязательный префикc API добавляемый по усолчанию ко всем маршрутам, что позволят сохранить оригинальные маршруты с приложения для внешних клиентов
    "routePrefix": ""
    }

  3. Запуск на Linux. Линукс потребовал некоторого изменения файла host.json, там пришлось удалить строчку с extensionBundle, без этого лезла ошибка "provider null reference".

  4. Различие при запуске на Windows и Linux. Подтвердилось, что конфигурация приложения на Windows и Linux отличается, для перекрытия настроек appsettings.json на Azure portal пришлось использовать различные разделители для пути настроек:

    •  для Windows ':' "Section:SubSection"

    • для Linux '__' "Section__SubSection".


    Собственно, это все замечания по неочевидным настройкам.

Тест Serverless против Dedicated Azure environment на приложении Virto Commerce

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

  • В классическом хостинге с ресурсами Azure S1 с параметрами: 1 Core, 1.75 GB RAM c включённым scale-out до 3х экземпляров.

  • Как serverless на Azure Functions с Consumption plan на Windows и Linux.

Сам тест представлял из себя ряд API-вызовов c линейно увеличивающейся нагрузкой в виде параллельных потоков (что имитировало условных пользователей). Нагрузка росла до 100 параллельных потоков в течение 3 минут. 

По результатам тестирования видно, что полоса пропускания приложения serverless почти в 3 раза выше, чем Dedicated Azure environment, но при этом для Windows на Serverless наблюдался довольно большой процент ошибок. Это были ошибки "System.OutOfMemoryException", что говорит о недостаточной пригодности данного способа хостинга для критических сервисов (возможно, это мы что-то не учли в настройках). Кстати, ошибки ушли, когда запустили приложение как функцию в операционной среде Linux.

Вопрос как отлавливать утечки памяти в Azure Functions остался пока открытый, будем общаться с Microsoft.

Графики производительности serverless Azure Functions vs Azure S1
Графики производительности serverless Azure Functions vs Azure S1

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

Для сравнения два плана 

·        Dedicated - S1: 1 Core, 1.75 GB RAM = $219 в месяц;

·        Serverless - Consumption plan, 1.6 GB RAM, 30M вызовов в месяц = $71.40. 

Тарифы* взяты из калькулятора Azure сервисов https://azure.microsoft.com/en-us/pricing/calculator/.
*Цены на дату публикации или вашего просмотра статьи на Хабре могут отличаться от указанных здесь, — в зависимости от региона и от политики цен Azure.

Скрин по расчету цены serverless
Скрин по расчету цены serverless
Скрин по расчету цены облачного хостинга
Скрин по расчету цены облачного хостинга

Если комментировать известное опасение о Serverless как неконтролируемого процесса списания баксов с вашего счета, то да, следует быть аккуратными при использовании Consumption plan в Azure и не только там. Хотелось бы услышать в комментариях ваше мнение, как не проснуться утром с долгом в пару сотен килобаксов на тестовом деплое.

Заключение

Мы подтвердили, что полноценные приложения, даже такие сложные как платформа электронной торговли, можно запускать в бессерверном режиме. Минута славы — с нашей точки зрения, это был первый практический опыт запуска большой коммерчески доступной B2B eCommerce платформы как serverless, по крайней мере, никаких похожих кейсов в интернете мы не нашли (разные варианты упрощенных eCommerce-приложений уровня MVP не в счет).

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

Хочу поблагодарить коллег Евгения Татаринцева и Олега Жука за помощь в работе над этим постом.

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