И еще разок про Serverless

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.
Логотип 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 init
Which template source would you like to use?
	1 - AWS Quick Start Templates
	2 - Custom Template Location
Choice: 1
What 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: 1

Which runtime would you like to use?
	1 - nodejs12.x
	2 - python3.8
	3 - ruby2.7
	4 - go1.x
	5 - java11
	6 - dotnetcore3.1
	7 - nodejs10.x
	8 - python3.7
	9 - python3.6
	10 - python2.7
	11 - ruby2.5
	12 - java8.al2
	13 - java8
	14 - dotnetcore2.1
Runtime: 2

Project name [sam-app]:

Cloning app templates from https://github.com/aws/aws-sam-cli-app-templates

AWS quick start application templates:
	1 - Hello World Example
	2 - EventBridge Hello World
	3 - EventBridge App from scratch (100+ Event Schemas)
	4 - Step Functions Sample App (Stock Trader)
	5 - Elastic File System Sample App
Template 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 HelloWorldFunction
Invoking 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 container
START RequestId: e7036160-e11c-440a-b089-8099b1e0d500 Version: $LATEST
END RequestId: e7036160-e11c-440a-b089-8099b1e0d500
REPORT RequestId: e7036160-e11c-440a-b089-8099b1e0d500	Init Duration: 0.29 ms	Duration: 114.08 ms	Billed Duration: 200 ms	Memory Size: 128 MB	Max 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 /markets
GET /markets/base
GET /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():
  pass

def get_ticker(pair):
  pass

def 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 в частности:

  • https://serverlessland.com/ - аггрегатор материалов по Serverless

  • https://cdkworkshop.com/ - обширная лаба по CDK

  • https://aws.amazon.com/serverless-workshops/ - лабы по sls с "реальными бизнес сценариями"

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

Источник: https://habr.com/ru/post/546530/


Интересные статьи

Интересные статьи

В этой статье мы расскажем, как оптимизировать крупный проект в «Битрикс24» и увеличить его производительность в 3 раза, изменяя настройки MySQL и режим питания CPU. Дано Корпоративн...
Недавно я посмотрела видео одного хорошего разработчика и ютубера, которое называется «Бессерверная бессмысленность». Мне понравились мысли автора, но я не согласна с некоторыми его выс...
Многие компании в определенный момент приходят к тому, что ряд процессов в бизнесе нужно автоматизировать, чтобы не потерять свое место под солнцем и своих заказчиков. Поэтому все...
Как широко известно, с 1 января 2017 года наступает три важных события в жизни интернет-магазинов.
Одной из «киллер-фич» 12й версии Битрикса была объявлена возможность отдавать статические файлы из CDN, тем самым увеличивая скорость работы сайта. Попробуем оценить практический выигрыш от использова...