AWS Lambda та Serverless Compute
AWS Lambda та Serverless Compute
Еволюція моделей обчислень: від серверів до функцій
Щоб зрозуміти місце AWS Lambda в сучасній хмарній архітектурі, необхідно простежити еволюційний шлях, який пройшла індустрія від фізичних серверів до парадигми «функції як сервіс». Кожен крок цієї еволюції вирішував конкретну проблему попереднього покоління, але водночас породжував нові класи складності.
Перше покоління: фізичні сервери (On-Premises). Організації купували та обслуговували власне апаратне забезпечення. Час від ухвалення рішення до введення сервера в експлуатацію вимірювався тижнями або місяцями. Ресурси були фіксованими: якщо сервер придбаний з 32 ГБ RAM, рівно стільки і доступно — незалежно від фактичного навантаження. Команди витрачали значну частину зусиль на управління інфраструктурою замість розробки бізнес-логіки.
Друге покоління: IaaS та EC2. AWS у 2006 році запровадила модель Infrastructure as a Service, де фізичний сервер замінюється віртуальною машиною, що надається за хвилини. Це вирішило проблему тривалого розгортання (provisioning) і дозволило платити лише за час використання. Проте фундаментальна проблема залишилась: команда все одно відповідає за операційну систему, патчі безпеки, мережеву конфігурацію, моніторинг процесів та масштабування.
Третє покоління: PaaS та контейнери. Платформи як Elastic Beanstalk або Amazon ECS абстрагували рівень операційної системи. Docker-контейнери вирішили проблему залежностей та відтворюваності середовища. Але команда все ще мала думати про кількість контейнерів, їхнє масштабування та час, протягом якого вони запущені.
Четверте покоління: Serverless та FaaS. AWS Lambda, запущена у 2014 році, реалізує принципово іншу модель: розробник пише лише функцію — одиницю бізнес-логіки, — а вся інфраструктурна відповідальність делегується платформі. Lambda не просто приховує сервери — вона усуває саму концепцію сервера з розумової моделі розробника.
Фундаментальна модель AWS Lambda
AWS Lambda — це обчислювальний сервіс, що виконує код у відповідь на події та автоматично управляє обчислювальними ресурсами, необхідними для цього виконання. Розробник надає код (функцію), Lambda забезпечує все інше: розгортання (provisioning) серверів, масштабування, моніторинг, реєстрацію логів.
Центральна концепція Lambda — функція (Lambda Function). Функція є одиницею деплойменту та виконання. Вона містить код, конфігурацію (пам'ять, timeout, змінні середовища) та IAM роль, що визначає її права доступу до інших AWS-сервісів.
Модель виконання: Event → Handler → Response
Кожне виклик Lambda відбувається за єдиною схемою: зовнішня система (trigger) генерує подію (event), Lambda-сервіс ініціює виконання функції та передає подію у handler — точку входу коду, — а handler повертає відповідь або завершується без повернення значення (для асинхронних тригерів).
Ціноутворення: оплата за мілісекунди виконання
Модель ціноутворення Lambda кардинально відрізняється від EC2: оплата не за час, поки сервер запущений, а виключно за фактичний час виконання коду та кількість викликів.
Кількість викликів
$0.20 за 1 мільйон викликів
Безкоштовно: перший мільйон викликів щомісяця (постійно, не лише 12 місяців free tier)
Тривалість виконання
$0.0000166667 за GB-секунду
Тривалість округлюється до 1 мілісекунди
Безкоштовно: 400,000 GB-секунд щомісяця
Приклад розрахунку
Функція: 128 MB пам'яті, 200ms виконання, 10M викликів/місяць
Вартість: ~$1.67 (тривалість) + $1.80 (виклики) = ~$3.47/місяць
t3.small (2 vCPU, 2 GB) коштує ~$15/місяць і працює 24/7 незалежно від навантаження. Lambda з аналогічним навантаженням (10M коротких викликів) коштуватиме менше $5 і не генерує витрат у часи простою.Lambda Limits: операційні обмеження
Lambda накладає жорсткі технічні обмеження, які є наслідком її архітектурної природи. Знання цих меж є обов'язковим для проектування рішень на Lambda.
Execution Environment: анатомія ізоляції
Для розуміння поведінки Lambda — зокрема феноменів Cold Start та Warm Start — необхідно детально вивчити концепцію Execution Environment (середовища виконання).
Коли Lambda-сервіс отримує виклик, він або повторно використовує наявне Execution Environment (якщо воно вільне та відповідає функції), або ініціалізує нове. Execution Environment — це ізольоване мікро-VM середовище на базі технології AWS Firecracker (відкрита технологія легковісної віртуалізації, розроблена AWS). Кожне Execution Environment забезпечує:
- повну ізоляцію від інших функцій та інших викликів тієї ж функції;
- стабільне середовище виконання: операційна система Amazon Linux 2, встановлений Runtime (наприклад, .NET 10), і ваш код;
- персистентний стан між викликами у межах одного Execution Environment — файлову систему
/tmp, статичні змінні та об'єкти, ініціалізовані поза handler-функцією.
Cold Start vs Warm Start: детальний аналіз
Cold Start — це затримка, що виникає при ініціалізації нового Execution Environment. Вона складається з кількох фаз, кожна з яких додає свою частину до загального часу:
| Фаза | Опис | Типовий час |
|---|---|---|
| Download Code | Завантаження ZIP-артефакту з S3 | 10–200ms |
| Start Runtime | Запуск JVM, .NET CLR або інтерпретатора | 50–500ms |
| Run Init Code | Виконання static конструкторів, DI container | 10–5000ms |
| Run Handler | Перший виклик обробника | час функції |
Критично важливо розуміти: Cold Start — не просто «перший виклик повільніший». Cold Start відбувається кожного разу, коли Lambda масштабується горизонтально. Якщо одночасно надходить 100 запитів, а є лише 10 теплих Execution Environments, Lambda ініціалізує 90 нових — 90 одночасних Cold Starts.
Warm Start — виклик функції, коли Lambda-сервіс знаходить вже ініціалізоване (але вільне) Execution Environment. У цьому випадку фаза ініціалізації повністю пропускається, і виконання починається безпосередньо з Handler. Час Warm Start практично дорівнює часу виконання самої бізнес-логіки.
Lambda Runtimes: середовища виконання
Runtime — це компонент Execution Environment, що забезпечує взаємодію між Lambda-сервісом та кодом функції. Runtime відповідає за: отримання події від Lambda-сервісу, десеріалізацію JSON-payload у нативні об'єкти, виклик handler-функції та серіалізацію відповіді назад у JSON.
AWS надає керовані Runtime для більшості популярних мов програмування. У разі, коли жоден із керованих Runtime не підходить, розробник може створити Custom Runtime — довільний виконуваний файл, що реалізує Lambda Runtime API.
.NET 10
Identifier: dotnet10
Офіційний керований Runtime від AWS. LTS-версія .NET. Оптимальний вибір для нових .NET Lambda проектів. Підтримує Native AOT компіляцію для мінімізації Cold Start.
.NET 6
Identifier: dotnet6
Попередня LTS-версія. Підтримується до листопада 2024. Рекомендується міграція на .NET 10.
Node.js 20
Identifier: nodejs20.x
Найпопулярніший Runtime для serverless. Мінімальний Cold Start (~100ms). Ідеальний для простих обробників подій та glue-коду.
Python 3.12
Identifier: python3.12
Широко використовується у ML/Data pipelines та скриптах автоматизації. Дуже короткий Cold Start.
Java 21
Identifier: java21
Підтримує GraalVM Native Image для зменшення Cold Start. Аналогічна до .NET ситуація з часом ініціалізації JVM.
Custom Runtime
Identifier: provided.al2023
Будь-яка мова, що може скомпілюватися у Linux-бінарник (Rust, Go, C++, .NET Native AOT). Максимальна гнучкість та мінімальний Cold Start.
.NET Runtime для Lambda: специфіка та особливості
AWS надає Amazon.Lambda.Core NuGet-пакет, який є центральним компонентом для написання Lambda функцій на .NET. Він визначає інтерфейс ILambdaContext та базовий клас ILambdaSerializer.
Виклик Lambda-функції на .NET відбувається через механізм Function Handler — метод зі специфічною сигнатурою:
// Варіант 1: синхронний handler з поверненням значення
public TOutput FunctionHandler(TInput input, ILambdaContext context)
// Варіант 2: асинхронний handler (рекомендується для .NET)
public async Task<TOutput> FunctionHandlerAsync(TInput input, ILambdaContext context)
// Варіант 3: handler без вхідних даних
public async Task<TOutput> FunctionHandlerAsync(ILambdaContext context)
// Варіант 4: handler без вхідних даних та без повернення
public async Task FunctionHandlerAsync(ILambdaContext context)
ILambdaContext надає метаданих про поточний виклик:
RemainingTime та коректно завершувати роботу до примусового знищення.LogTrace, LogDebug, LogInformation, LogWarning, LogError, LogCritical. Для продакшн-систем рекомендується використовувати Microsoft.Extensions.Logging з відповідним Lambda-провайдером.Створення Lambda-проекту: від порожньої папки до Hello World
Для розробника .NET існує три основні шляхи створення проекту. Вибір залежить від того, чи хочете ви контролювати кожен файл, чи використати готові кращі практики від AWS.
Варіант А: Ручне створення (Manual) — для повного контролю
Це дозволяє зрозуміти, як саме працює Lambda без "магії" шаблонів.
- Створіть директорію та проект:
mkdir MyLambda && cd MyLambda dotnet new classlib - Додайте ядро SDK:
dotnet add package Amazon.Lambda.Core dotnet add package Amazon.Lambda.Serialization.SystemTextJson - Напишіть код: Створіть клас
Function.csз методом, який приймаєstringабо об'єкт. - Зареєструйте серіалізатор: Додайте атрибут
[assembly: LambdaSerializer(...)]над namespace.
Варіант Б: Використання AWS Templates (Рекомендовано)
AWS надає набір шаблонів, які автоматично створюють правильну структуру, включаючи конфігураційні файли.
- Встановіть шаблони:
dotnet new install Amazon.Lambda.Templates - Перегляньте список доступних типів:
dotnet new lambda --list - Створіть проект (наприклад, для S3 подій):
dotnet new lambda.S3 -n MyS3Processor
Структура проекту: Файл aws-lambda-tools-defaults.json
При створенні через шаблони ви знайдете цей файл. Він є критично важливим, бо зберігає налаштування для dotnet lambda cli.
{
"Information": "This file saves default values for the dotnet lambda CLI.",
"profile": "default",
"region": "eu-central-1",
"configuration": "Release",
"framework": "net10.0",
"function-runtime": "dotnet10",
"function-memory-size": 256,
"function-timeout": 30,
"function-handler": "MyProject::MyProject.Function::Handler"
}
Завдяки цьому файлу вам не потрібно щоразу вводити довгі параметри в терміналі.
Lambda Layers: механізм спільного використання коду
Lambda Layer — це ZIP-архів, що містить бібліотеки, кастомний Runtime, конфігурацію або будь-які інші файли, які можна розділити між кількома Lambda-функціями. Layer монтується в директорію /opt Execution Environment і стає доступним для функції під час виконання.
Технічна реалізація для .NET
Коли ви додаєте Layer до Lambda, AWS розпаковує його в папку /opt. Для того, щоб .NET Runtime міг знайти ваші бібліотеки (DLL), вони мають бути розміщені у специфічній підпапці.
Шлях пошуку за замовчуванням: /opt/dotnet/packages
Коли .NET Lambda ініціалізується, вона автоматично додає цей шлях до DOTNET_SHARED_STORE або використовує механізм Assembly Resolution для пошуку залежностей у цій локації.
Переваги використання Layers
Зменшення пакетів
Централізація
Розподіл праці
Mini-Workshop: Створення та деплой Layer для .NET
Розглянемо процес винесення бібліотеки Newtonsoft.Json у окремий шар.
Підготовка структури
Створіть папку зі специфічною структурою для .NET:
mkdir -p my-layer/dotnet/packages
cd my-layer/dotnet/packages
Публікація залежностей
Створіть тимчасовий проект і опублікуйте його DLL у цю папку:
dotnet new classlib -n TempLayer
dotnet add package Newtonsoft.Json
dotnet publish -c Release -o .
Після цього видаліть зайві файли (.csproj, .json), залишивши лише потрібні .dll.
Створення та завантаження шару
# Пакуємо папку 'dotnet' у ZIP
cd ../..
zip -r my-layer.zip dotnet/
# Завантажуємо в AWS
aws lambda publish-layer-version \
--layer-name SharedLibraries \
--description "Common NuGet packages" \
--zip-file fileb://my-layer.zip \
--compatible-runtimes dotnet10
Підключення до функції
Тепер у конфігурації вашої Lambda додайте ARN отриманого шару:
aws lambda update-function-configuration \
--function-name MyFunction \
--layers arn:aws:lambda:eu-central-1:123456789012:layer:SharedLibraries:1
AWS SDK for Lambda: Глибоке занурення в екосистему .NET пакетів
Для ефективної розробки Lambda-функцій на .NET недостатньо базового знання мови; необхідно розуміти структуру та призначення офіційних бібліотек від AWS. Екосистема SDK для Lambda складається з трьох рівнів: ядро (Core), серіалізація (Serialization) та бібліотеки подій (Event Libraries).
1. Фундаментальні пакети (Core & Serialization)
Ці пакети є обов'язковими для будь-якого проекту Lambda.
Amazon.Lambda.Core
ILambdaContext, ILambdaSerializer та базові атрибути логування. Без цього пакету неможливо створити коректну сигнатуру Handler.Amazon.Lambda.Serialization.SystemTextJson
System.Text.Json.Механізм реєстрації серіалізатора: AWS Lambda не знає за замовчуванням, як десеріалізувати вхідні дані. Ви ПОВИННІ вказати серіалізатор на рівні assembly (найкраща практика) або конкретного методу:
// Реєстрація на рівні всього проекту (assembly)
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
2. Бібліотеки подій (Event Libraries)
AWS не передає "просто JSON". Кожен сервіс-тригер має строго визначену схему даних. Пакети серії Amazon.Lambda.*Events містять готові класи (DTO), які відповідають цим схемам.
| Сервіс-тригер | Пакет NuGet | Ключовий клас події |
|---|---|---|
| API Gateway (HTTP) | Amazon.Lambda.APIGatewayEvents | APIGatewayHttpApiV2ProxyRequest |
| S3 Storage | Amazon.Lambda.S3Events | S3Event |
| Simple Queue (SQS) | Amazon.Lambda.SQSEvents | SQSEvent |
| DynamoDB Streams | Amazon.Lambda.DynamoDBEvents | DynamoDBEvent |
| EventBridge / Cron | Amazon.Lambda.CloudWatchEvents | ScheduledEvent |
3. Анатомія ILambdaContext
Об'єкт context, що передається у кожен виклик, є "вікном" у Runtime-середовище Lambda. Він надає метадані, необхідні для моніторингу та управління життєвим циклом виконання.
- Сценарій: якщо ви обробляєте батч з 100 елементів, перевіряйте
context.RemainingTimeпісля кожного елемента. Якщо залишилось < 2 секунд — зупиніться і збережіть стан. ::
LogLine та Log пишуть у стандартний потік, додаючи метадані Lambda....:function:my-func:PROD). Дозволяє коду знати, в якому середовищі він запущений.::
4. Просунута розробка: Lambda Annotations (Повна документація)
Фреймворк AWS Lambda Annotations (Amazon.Lambda.Annotations) — це сучасна модель програмування для .NET Lambda, яка кардинально спрощує створення REST та HTTP API. Замість того, щоб вручну парсити JSON-запити через низькорівневий об'єкт APIGatewayProxyRequest, ви використовуєте звичні атрибути в стилі ASP.NET Core Minimal APIs.
Як це працює під капотом?
Фреймворк використовує C# Source Generators. Під час компіляції вашого коду (Build), він автоматично:
- Аналізує ваші методи, помічені атрибутом
[LambdaFunction]. - Генерує "приховані" обгортки (wrapper classes), які виконують всю "брудну" роботу: десеріалізацію вхідних HTTP-запитів, обробку помилок та форматування HTTP-відповідей.
- Автоматично оновлює файл
serverless.template(шаблон AWS CloudFormation / SAM), записуючи туди всі маршрути (routes) та конфігурацію інфраструктури.
Ключові атрибути для маршрутизації
Фреймворк надає атрибути для прив'язки методів до API Gateway:
[LambdaFunction(ResourceName = "MyFunc", MemorySize = 512)]— перетворює метод на Lambda-функцію. Можна задати пам'ять, таймаут, ролі.[HttpApi(LambdaHttpMethod.Get, "/users/{id}")]— реєструє маршрут для сучасного та швидкого HTTP API.[RestApi(LambdaHttpMethod.Post, "/orders")]— реєструє маршрут для класичного REST API (з підтримкою WAF, Usage Plans).
Біндинг параметрів (Parameter Binding)
Подібно до ASP.NET, ви можете вказувати, звідки брати дані, за допомогою атрибутів:
[FromQuery] string status— з URL параметру (?status=active).[FromRoute] int id— зі змінної в шляху (/users/{id}).[FromBody] Order order— автоматично десеріалізує JSON-тіло запиту.[FromHeader("Authorization")] string token— з HTTP-заголовка.
Приклад коду:
[LambdaFunction(MemorySize = 256)]
[HttpApi(LambdaHttpMethod.Post, "/users/{userId}/roles")]
public async Task<IHttpResult> AssignRole(
[FromRoute] string userId,
[FromQuery] bool notify,
[FromBody] RoleRequest request,
ILambdaContext context) // ILambdaContext завжди доступний
{
// Ваша логіка...
return HttpResults.Ok(new { success = true, user = userId, role = request.Role });
}
Впровадження залежностей (Dependency Injection)
У звичайній Lambda налаштування DI-контейнера є неочевидним завданням. З Annotations ви створюєте клас із атрибутом [LambdaStartup], який виглядає точнісінько як Startup.cs в ASP.NET:
[LambdaStartup]
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Ці сервіси будуть автоматично ініціалізовані під час Cold Start
services.AddAWSService<IAmazonDynamoDB>();
services.AddScoped<IUserRepository, UserRepository>();
}
}
Після цього ви можете використовувати ін'єкцію через конструктор (Constructor Injection) у класі вашої функції.
serverless.template, ви не можете деплоїти такі функції звичайною командою dotnet lambda deploy-function. Ви ПОВИННІ використовувати команду деплою всього стеку:
dotnet lambda deploy-serverless --stack-name my-api-stack --s3-bucket my-bucket --region eu-central-15. SDK у дії: Практичні приклади реалізації
Розглянемо поширені сценарії використання SDK для вирішення типових задач.
А. Логування та контекст виконання
Використання ILambdaContext для відстеження стану та лімітів.
public async Task<string> Handler(string input, ILambdaContext context)
{
// 1. Логування з метаданими
context.Logger.LogInformation($"Request ID: {context.AwsRequestId}");
context.Logger.LogInformation($"Remaining Time: {context.RemainingTime.TotalMilliseconds}ms");
// 2. Логіка захисту від таймауту
if (context.RemainingTime < TimeSpan.FromSeconds(1))
{
context.Logger.LogWarning("Running out of time! Aborting...");
return "Timeout Imminent";
}
return $"Processed: {input}";
}
Б. Десеріалізація складних об'єктів (POCO)
Завдяки серіалізатору SystemTextJson ви можете приймати будь-які C# класи.
public record UserRequest(string Name, int Age, List<string> Roles);
public record UserResponse(string Message, bool IsAdult);
public UserResponse HandleUser(UserRequest request, ILambdaContext context)
{
context.Logger.LogInformation($"Processing user: {request.Name}");
return new UserResponse(
Message: $"Hello, {request.Name}!",
IsAdult: request.Age >= 18
);
}
Lambda Triggers: архітектура подієво-орієнтованої інтеграції
У хмарній екосистемі AWS Lambda ніколи не працює у вакуумі. Функція завжди ініціюється певною подією (Event) ззовні. Цей механізм ініціалізації називається Trigger (тригер).
Сучасні архітектури використовують подієво-орієнтований підхід (Event-Driven Architecture), де сервіси не викликають один одного напряму, а реагують на зміни стану. AWS пропонує понад 200 нативних інтеграцій з Lambda. Проте, незважаючи на таку кількість, архітектурно всі вони поділяються на дві фундаментальні моделі доставки подій: Push-модель (коли сервіс сам "штовхає" подію в Lambda) та Pull-модель (коли Lambda "витягує" події з джерела).
Важливо розуміти, що Push-модель додатково ділиться на синхронну та асинхронну. Ваш підхід до обробки помилок, ретраїв та масштабування буде кардинально відрізнятися залежно від обраної моделі.
1. Модель Push: Синхронний виклик (RequestResponse)
У цій моделі сервіс-тригер (або клієнт) робить прямий HTTP-запит до Lambda Service API і блокується, очікуючи на відповідь.
- Хто керує помилками: Клієнт. Якщо функція впала або сталася помилка таймауту, AWS Lambda не буде робити автоматичний retry. Клієнт отримує 5xx помилку і сам вирішує, чи повторювати запит.
- Типові сервіси: Amazon API Gateway, Application Load Balancer, Amazon Cognito, або прямий виклик через AWS SDK (
InvokeType = RequestResponse). - Use-case: HTTP API, де користувачу потрібна миттєва відповідь (наприклад, отримання профілю користувача).
2. Модель Push: Асинхронний виклик (Event)
У цій моделі сервіс-тригер відправляє подію до внутрішньої черги AWS Lambda Service і відразу отримує підтвердження "Подія прийнята" (HTTP 202). Він не чекає на завершення виконання коду вашої функції.
- Хто керує помилками: AWS Lambda Service. Внутрішня система автоматично робить 2 повторні спроби (Retries) з експоненційною затримкою (backoff), якщо ваш код кидає exception. Якщо всі спроби вичерпано, подія може бути відправлена у Dead Letter Queue (DLQ) або Lambda Destination.
- Типові сервіси: Amazon S3, Amazon SNS, Amazon EventBridge.
- Use-case: Фонова обробка. Наприклад, користувач завантажив відео в S3, і нам потрібно згенерувати прев'ю. Користувач не повинен чекати 3 хвилини; S3 асинхронно тригерить Lambda.
3. Модель Pull: Event Source Mapping (Poll-based)
Тут Lambda Service виступає в ролі активного "споживача". Спеціальний компонент (Event Source Mapping) безперервно опитує (polls) джерело даних на наявність нових записів, групує їх у "батчі" (Batches) і відправляє у вашу функцію одним синхронним викликом.
- Хто керує помилками: Event Source Mapping. Якщо ваша функція падає під час обробки батчу, весь батч повертається в чергу або потік, і AWS буде намагатися передати його знову (поки не вийде термін дії повідомлення, або поки ви не налаштуєте часткову обробку відмов —
ReportBatchItemFailures). - Типові сервіси: Amazon SQS, DynamoDB Streams, Amazon Kinesis, Kafka (MSK).
- Use-case: Обробка великих потоків даних, де важливий порядок повідомлень або потрібно контролювати швидкість (throttling), з якою Lambda споживає дані (через налаштування Batch Size).
Синхронний тригер: API Gateway + Lambda
Інтеграція Amazon API Gateway з Lambda є фундаментальним патерном побудови serverless HTTP API. API Gateway виступає проксі, що приймає HTTP-запити від клієнтів, трансформує їх у Lambda-події та повертає Lambda-відповідь клієнту.
AWS надає два типи API Gateway для інтеграції з Lambda:
HTTP API (v2)
Рекомендований вибір для більшості .NET Lambda сценаріїв.
- Нижча вартість (~70% дешевше за REST API)
- Нижча латентність
- Спрощена конфігурація
- Підтримка JWT авторизації «з коробки»
- Обмежена функціональність (немає трансформацій запитів/відповідей)
REST API (v1)
Для складних сценаріїв з потребою у:
- Трансформації request/response (Mapping Templates)
- API Keys та Usage Plans
- Детального контролю над схемою запитів (Request Validation)
- AWS WAF інтеграції на рівні API
Структура події від API Gateway (HTTP API):
Коли клієнт надсилає GET /users/42?format=json, API Gateway трансформує запит у таку JSON-структуру, яку Lambda отримує як event:
{
"version": "2.0",
"routeKey": "GET /users/{id}",
"rawPath": "/users/42",
"rawQueryString": "format=json",
"headers": {
"accept": "application/json",
"authorization": "Bearer eyJ...",
"content-type": "application/json",
"x-forwarded-for": "203.0.113.5"
},
"queryStringParameters": {
"format": "json"
},
"pathParameters": {
"id": "42"
},
"requestContext": {
"accountId": "123456789012",
"apiId": "abc123",
"http": {
"method": "GET",
"path": "/users/42",
"sourceIp": "203.0.113.5"
},
"requestId": "JKJaXmPLvHcESHA=",
"stage": "$default"
},
"body": null,
"isBase64Encoded": false
}
Реалізація HTTP API handler на .NET 10 з Lambda Annotations:
Lambda Annotations — це фреймворк від AWS, що генерує boilerplate-код через Source Generators і дозволяє писати Lambda-функції у стилі ASP.NET Core Minimal API:
using Amazon.Lambda.Annotations;
using Amazon.Lambda.Annotations.APIGateway;
using Amazon.Lambda.Core;
using Microsoft.Extensions.Logging;
// Атрибут реєструє assembly як Lambda-функцію
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
namespace MyApi.Lambda;
// У стандартній Lambda вам довелося б вручну створювати ресурси в AWS (наприклад, через веб-консоль)
// та налаштовувати, який URL викликає яку функцію.
// Завдяки фреймворку Lambda Annotations ви пишете код у стилі ASP.NET Core Controller.
// Під час компіляції генератор коду сам створить конфігурацію інфраструктури
// (CloudFormation шаблон) та налаштує всі URL-маршрути (routing) автоматично.
public class UserFunctions
{
private readonly IUserRepository _repository;
private readonly ILogger<UserFunctions> _logger;
// DI через конструктор — повністю підтримується!
public UserFunctions(IUserRepository repository, ILogger<UserFunctions> logger)
{
_repository = repository;
_logger = logger;
}
// GET /users/{id} → HTTP API Lambda Integration
[LambdaFunction(ResourceName = "GetUser")]
[HttpApi(LambdaHttpMethod.Get, "/users/{id}")]
public async Task<IHttpResult> GetUserAsync(
int id,
ILambdaContext context)
{
_logger.LogInformation(
"GetUser called. RequestId={RequestId}, UserId={UserId}",
context.AwsRequestId, id);
var user = await _repository.GetByIdAsync(id);
if (user is null)
return HttpResults.NotFound(new { message = $"User {id} not found" });
return HttpResults.Ok(user);
}
// POST /users → створення нового користувача
[LambdaFunction(ResourceName = "CreateUser")]
[HttpApi(LambdaHttpMethod.Post, "/users")]
public async Task<IHttpResult> CreateUserAsync(
[FromBody] CreateUserRequest request,
ILambdaContext context)
{
// Перевірка RemainingTime для graceful shutdown
if (context.RemainingTime < TimeSpan.FromSeconds(2))
{
_logger.LogWarning("Approaching timeout, aborting operation");
return HttpResults.InternalServerError();
}
var user = await _repository.CreateAsync(request);
return HttpResults.Created($"/users/{user.Id}", user);
}
}
Налаштування Startup (DI Container) для Lambda:
using Amazon.Lambda.Annotations;
using Microsoft.Extensions.DependencyInjection;
namespace MyApi.Lambda;
// LambdaStartup — аналог Program.cs для Lambda Annotations
[LambdaStartup]
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Реєстрація сервісів — виконується ОДНОРАЗОВО при Cold Start
// Всі наступні виклики (Warm Start) повторно використовують цей контейнер
services.AddAWSService<IAmazonDynamoDB>();
services.AddScoped<IUserRepository, DynamoDbUserRepository>();
// HttpClient з правильним connection pooling для Lambda
services.AddHttpClient("PaymentApi", client =>
{
client.BaseAddress = new Uri(Environment.GetEnvironmentVariable("PAYMENT_API_URL")!);
client.Timeout = TimeSpan.FromSeconds(10);
});
services.AddLogging(logging =>
{
logging.AddLambdaLogger();
logging.SetMinimumLevel(LogLevel.Information);
});
}
}
IServiceCollection реєструється лише один раз — під час Cold Start. Усі Scoped та Transient сервіси при кожному виклику створюються заново (що є коректною поведінкою), але Singleton'и живуть протягом усього часу існування Execution Environment.Асинхронний тригер: S3 Events
Інтеграція з Amazon S3 є одним із найпоширеніших сценаріїв використання Lambda. S3 автоматично надсилає повідомлення Lambda, коли в bucket відбуваються певні дії: завантаження об'єкта (s3:ObjectCreated:*), видалення (s3:ObjectRemoved:*), відновлення з Glacier та інші.
Структура S3 Event, що надходить у Lambda:
{
"Records": [
{
"eventVersion": "2.1",
"eventSource": "aws:s3",
"awsRegion": "eu-central-1",
"eventTime": "2024-03-15T10:30:00.000Z",
"eventName": "ObjectCreated:Put",
"s3": {
"s3SchemaVersion": "1.0",
"bucket": {
"name": "source-images-bucket",
"arn": "arn:aws:s3:::source-images-bucket"
},
"object": {
"key": "uploads/user-123/profile.jpg",
"size": 2048576,
"eTag": "d8e8fca2dc0f896fd7cb4cb0031ba249"
}
}
}
]
}
Зверніть увагу: S3 Event завжди містить масив Records, і теоретично може містити більше одного запису (хоча на практиці S3 зазвичай надсилає по одному запису). Обробник повинен ітерувати по всіх записах.
Handler для обробки S3 подій на .NET 10:
using Amazon.Lambda.Core;
using Amazon.Lambda.S3Events;
using Amazon.S3;
using Amazon.S3.Model;
[assembly: LambdaSerializer(
typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
namespace ImageProcessor.Lambda;
public class ImageProcessorFunction
{
// IAmazonS3 ініціалізується ОДНОРАЗОВО при Cold Start
// та повторно використовується між Warm Start викликами
private static readonly IAmazonS3 S3Client = new AmazonS3Client();
private const string DestinationBucket = "processed-images-bucket";
public async Task FunctionHandler(S3Event evnt, ILambdaContext context)
{
// S3Event може містити кілька Records — обробляємо всі
foreach (var record in evnt.Records)
{
var srcBucket = record.S3.Bucket.Name;
var srcKey = Uri.UnescapeDataString(record.S3.Object.Key.Replace("+", " "));
context.Logger.LogInformation(
"Processing: s3://{Bucket}/{Key} ({Size} bytes)",
srcBucket, srcKey, record.S3.Object.Size);
try
{
// Завантажуємо оригінальний файл з S3
var getRequest = new GetObjectRequest
{
BucketName = srcBucket,
Key = srcKey
};
using var response = await S3Client.GetObjectAsync(getRequest);
using var inputStream = response.ResponseStream;
// Обробка зображення (resize, compress тощо)
using var processedStream = await ProcessImageAsync(inputStream, context);
// Зберігаємо результат у destination bucket
var dstKey = $"processed/{srcKey}";
var putRequest = new PutObjectRequest
{
BucketName = DestinationBucket,
Key = dstKey,
InputStream = processedStream,
ContentType = "image/webp",
// Server-side encryption
ServerSideEncryptionMethod = ServerSideEncryptionMethod.AES256
};
await S3Client.PutObjectAsync(putRequest);
context.Logger.LogInformation(
"Successfully processed: s3://{Bucket}/{Key}",
DestinationBucket, dstKey);
}
catch (Exception ex)
{
context.Logger.LogError(ex,
"Failed to process s3://{Bucket}/{Key}",
srcBucket, srcKey);
// При асинхронному тригері — Lambda повторить виклик
// (до 2 разів за замовчуванням)
throw;
}
}
}
private static async Task<Stream> ProcessImageAsync(
Stream input, ILambdaContext context)
{
// ... логіка обробки зображення
// Можна використовувати SixLabors.ImageSharp або аналоги
return await Task.FromResult(input); // placeholder
}
}
Poll-based тригер: SQS та DynamoDB Streams
Poll-based тригери реалізуються через механізм Event Source Mapping — компонент Lambda-сервісу, що безперервно опитує джерело подій (SQS черга, DynamoDB Stream, Kinesis Stream) та автоматично викликає Lambda при появі нових записів.
Конфігурація Event Source Mapping для SQS:
ReportBatchItemFailures — дозволяє Lambda повертати часткові успіхи. Замість відкидання всього батчу при помилці одного запису, функція може повернути список batchItemFailures, і лише вказані записи повернуться у чергу для повторної обробки.true: якщо Lambda завершилась з помилкою, ESM автоматично ділить батч навпіл та повторює кожну половину окремо — рекурсивно, доки не знайде проблемне повідомлення. Корисно при відсутності ReportBatchItemFailures.Handler для SQS з підтримкою часткових збоїв (ReportBatchItemFailures):
using Amazon.Lambda.Core;
using Amazon.Lambda.SQSEvents;
namespace OrderProcessor.Lambda;
public class OrderProcessorFunction
{
private readonly IOrderService _orderService;
public OrderProcessorFunction(IOrderService orderService)
{
_orderService = orderService;
}
// Повертаємо SQSBatchResponse замість void/Task
// щоб Lambda знала, які записи потрібно повторити
public async Task<SQSBatchResponse> FunctionHandler(
SQSEvent sqsEvent,
ILambdaContext context)
{
var batchItemFailures = new List<SQSBatchResponse.BatchItemFailure>();
// Обробляємо кожне повідомлення НЕЗАЛЕЖНО
foreach (var message in sqsEvent.Records)
{
try
{
context.Logger.LogInformation(
"Processing SQS message: {MessageId}", message.MessageId);
var order = System.Text.Json.JsonSerializer
.Deserialize<OrderCreatedEvent>(message.Body)!;
await _orderService.ProcessOrderAsync(order);
context.Logger.LogInformation(
"Successfully processed order: {OrderId}", order.OrderId);
}
catch (Exception ex)
{
// НЕ перекидаємо виключення — додаємо до списку невдалих
context.Logger.LogError(ex,
"Failed to process message {MessageId}", message.MessageId);
// Це повідомлення повернеться у SQS для повторної обробки
batchItemFailures.Add(new SQSBatchResponse.BatchItemFailure
{
ItemIdentifier = message.MessageId
});
}
}
return new SQSBatchResponse { BatchItemFailures = batchItemFailures };
}
}
Тригер EventBridge: планові та подієво-орієнтовані задачі
Amazon EventBridge (раніше CloudWatch Events) — це serverless шина подій, що дозволяє маршрутизувати події між AWS-сервісами та власними застосунками. З точки зору Lambda, EventBridge надає два ключових патерни:
- Scheduled Events (Cron) — виклик Lambda за розкладом: щохвилини, щогодини, за cron-виразом
- Event Pattern Rules — виклик Lambda при настанні певної події в AWS-акаунті (наприклад, новий EC2 instance, зміна стану RDS, Custom Events від власних мікросервісів)
using Amazon.Lambda.Core;
using Amazon.Lambda.CloudWatchEvents.ScheduledEvents;
namespace Scheduler.Lambda;
public class ScheduledCleanupFunction
{
private readonly ICleanupService _cleanupService;
public ScheduledCleanupFunction(ICleanupService cleanupService)
{
_cleanupService = cleanupService;
}
// ScheduledEvent — типізована обгортка для EventBridge Scheduled Events
public async Task FunctionHandler(
ScheduledEvent scheduledEvent,
ILambdaContext context)
{
context.Logger.LogInformation(
"Scheduled cleanup started. Time={Time}, Source={Source}",
scheduledEvent.Time, scheduledEvent.Source);
// Перевіряємо достатність часу до Timeout
// Для завдань cleanup це критично
if (context.RemainingTime < TimeSpan.FromMinutes(2))
{
context.Logger.LogWarning(
"Insufficient time remaining ({Remaining}), skipping cleanup",
context.RemainingTime);
return;
}
var result = await _cleanupService.CleanupExpiredSessionsAsync(
olderThan: TimeSpan.FromDays(30),
cancellationToken: CreateCancellationToken(context));
context.Logger.LogInformation(
"Cleanup complete. Deleted={Count} sessions", result.DeletedCount);
}
private static CancellationToken CreateCancellationToken(ILambdaContext context)
{
// Автоматично скасовуємо операцію за 30 секунд до Timeout
var cts = new CancellationTokenSource(
context.RemainingTime - TimeSpan.FromSeconds(30));
return cts.Token;
}
}
Environment Variables та Secrets Management
Environment Variables: зовнішня конфігурація функції
Environment Variables у Lambda виконують ту саму роль, що і у традиційних серверних застосунках: вони забезпечують механізм зовнішньої конфігурації без необхідності змінювати код функції. Lambda дозволяє визначити довільний набір пар ключ-значення, що стають доступними у коді через стандартний Environment.GetEnvironmentVariable().
Ключові характеристики Environment Variables у Lambda:
- Зберігаються як частина конфігурації функції та автоматично присутні в кожному Execution Environment
- Значення шифруються AWS KMS (за замовчуванням — AWS Managed Key, можна вказати власний KMS ключ)
- Максимальний сумарний розмір усіх змінних — 4 KB
- Змінюються без необхідності нового деплойменту коду (але потребують нового Execution Environment — тобто спричиняють Cold Start)
Доступ до Environment Variables з .NET:
namespace MyApi.Lambda;
// Рекомендована практика: типізований клас конфігурації
// замість прямих викликів Environment.GetEnvironmentVariable скрізь у коді
public sealed class LambdaConfiguration
{
// Назва таблиці DynamoDB — змінюється між mid/prod оточеннями
public string UsersTableName { get; } =
Environment.GetEnvironmentVariable("USERS_TABLE_NAME")
?? throw new InvalidOperationException(
"USERS_TABLE_NAME environment variable is not set");
// URL зовнішнього API — різний для staging та production
public string PaymentApiBaseUrl { get; } =
Environment.GetEnvironmentVariable("PAYMENT_API_BASE_URL")
?? "https://api.payment.example.com";
// Рівень логування
public string LogLevel { get; } =
Environment.GetEnvironmentVariable("LOG_LEVEL") ?? "Information";
// Feature flag
public bool EnableDetailedLogging { get; } =
bool.TryParse(
Environment.GetEnvironmentVariable("ENABLE_DETAILED_LOGGING"),
out var value) && value;
}
lambda:GetFunctionConfiguration. Паролі, API ключі та токени не слід зберігати у Environment Variables у відкритому вигляді. Для секретів використовуйте AWS Secrets Manager або AWS Parameter Store (SSM).AWS Secrets Manager: безпечне зберігання секретів
AWS Secrets Manager — це спеціалізований сервіс для зберігання, ротації та надання доступу до секретних значень: паролів баз даних, API ключів, OAuth токенів. На відміну від Environment Variables, Secrets Manager:
- Підтримує автоматичну ротацію секрету (наприклад, щомісячне автоматичне оновлення пароля RDS)
- Зберігає версії секрету та дозволяє атомарне оновлення без простою
- Надає аудит доступу через CloudTrail
- Інтегрується з IAM для гранулярного контролю доступу до конкретних секретів
Реалізація безпечного отримання секрету з кешуванням у .NET:
using Amazon.SecretsManager;
using Amazon.SecretsManager.Model;
using System.Text.Json;
namespace MyApi.Lambda.Infrastructure;
public sealed class SecretsManagerService : ISecretsManagerService
{
private readonly IAmazonSecretsManager _client;
// Кеш секретів — статичний, живе протягом усього Execution Environment
// Ключ: ARN секрету, Значення: (розшифроване значення, час кешування)
private static readonly Dictionary<string, (string Value, DateTime CachedAt)>
_secretsCache = new();
// Час жизні кешу — 5 хвилин
// Довше = менше витрат, але затримка оновлення при ротації
private static readonly TimeSpan CacheTtl = TimeSpan.FromMinutes(5);
public SecretsManagerService(IAmazonSecretsManager client)
{
_client = client;
}
public async Task<T> GetSecretAsync<T>(string secretArn) where T : class
{
// Перевіряємо кеш
if (_secretsCache.TryGetValue(secretArn, out var cached))
{
if (DateTime.UtcNow - cached.CachedAt < CacheTtl)
return JsonSerializer.Deserialize<T>(cached.Value)!;
}
// Запит до Secrets Manager (лише при Cache Miss або протермінованому кеші)
var request = new GetSecretValueRequest { SecretId = secretArn };
var response = await _client.GetSecretValueAsync(request);
var secretValue = response.SecretString;
// Оновлюємо кеш
_secretsCache[secretArn] = (secretValue, DateTime.UtcNow);
return JsonSerializer.Deserialize<T>(secretValue)!;
}
}
// Типізований record для секрету бази даних
public record DatabaseSecret(
string Username,
string Password,
string Host,
int Port,
string DbName)
{
public string ToConnectionString() =>
$"Host={Host};Port={Port};Database={DbName};" +
$"Username={Username};Password={Password};SSL Mode=Require;";
}
Інтеграція у Startup з отриманням секрету при Cold Start:
[LambdaStartup]
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddAWSService<IAmazonSecretsManager>();
services.AddSingleton<ISecretsManagerService, SecretsManagerService>();
// Синхронне отримання рядка підключення при ініціалізації DI Container
// Виконується ОДНОРАЗОВО у Cold Start — не при кожному виклику
services.AddSingleton<IDbConnectionFactory>(provider =>
{
var secretsManager = provider.GetRequiredService<ISecretsManagerService>();
var secretArn = Environment.GetEnvironmentVariable("DB_SECRET_ARN")!;
// GetAwaiter().GetResult() допустимий у Cold Start
// (не в handler — там завжди async!)
var dbSecret = secretsManager
.GetSecretAsync<DatabaseSecret>(secretArn)
.GetAwaiter().GetResult();
return new NpgsqlConnectionFactory(dbSecret.ToConnectionString());
});
}
}
Lambda Destinations: маршрутизація результатів асинхронних викликів
Lambda Destinations — це конфігурація, що визначає, куди Lambda-сервіс автоматично надсилає результат асинхронного виклику залежно від того, завершилась функція успіхом чи помилкою. Destinations усувають необхідність вручну реалізовувати логіку маршрутизації результатів безпосередньо в коді функції.
Що Lambda автоматично надсилає у Destination:
Lambda формує збагачений JSON-payload, що містить повний контекст виклику — не лише результат, але й метадані про саму функцію та оригінальну подію. Це усуває необхідність ручного збору контексту у коді.
{
"version": "1.0",
"timestamp": "2024-03-15T10:30:00.123Z",
"requestContext": {
"requestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"functionArn": "arn:aws:lambda:eu-central-1:123456789012:function:FileProcessor",
"condition": "Success",
"approximateInvokeCount": 1
},
"requestPayload": {
"Records": [{ "eventSource": "aws:s3", "s3": { "object": { "key": "report.csv" } } }]
},
"responseContext": {
"statusCode": 200,
"executedVersion": "$LATEST"
},
"responsePayload": {
"processedRows": 15420,
"outputKey": "processed/report-2024-03-15.parquet"
}
}
Порівняння з Dead Letter Queue (DLQ):
| Характеристика | Lambda Destinations | Dead Letter Queue |
|---|---|---|
| Де спрацьовує | On Success та On Failure | Лише On Failure |
| Що передається | Повний контекст: input + output + metadata | Лише оригінальний input event |
| Підтримувані цілі | SQS, SNS, EventBridge, Lambda | SQS, SNS |
| Коли з'явилось | 2019 (новіший підхід) | 2016 (legacy підхід) |
| Рекомендація | ✅ Використовуйте для нових проектів | ⚠️ Тільки для зворотної сумісності |
Lambda Performance Optimization: стратегії мінімізації Cold Start
Оптимізація продуктивності Lambda для .NET є комплексним завданням, що охоплює декілька незалежних рівнів: архітектуру коду, конфігурацію Runtime, розмір deployment package та апаратні ресурси. Розглянемо кожен рівень детально.
Стратегія 1: оптимізація коду Init-фази
Найефективніший підхід — мінімізувати обсяг роботи, що виконується поза handler-функцією (тобто у Init-фазі Cold Start). Кожен об'єкт, ініціалізований у Init-фазі, живе протягом усього Execution Environment і використовується повторно при Warm Start. Це є перевагою, але Init-фаза повинна ініціалізувати лише те, що справді потрібно.
// ❌ ПОГАНО: Lazy ініціалізація у handler — Cold Start при КОЖНОМУ виклику,
// якщо об'єкт ще не ініціалізований
public class BadFunction
{
public async Task Handler(S3Event evt, ILambdaContext ctx)
{
// Новий клієнт створюється при першому виклику кожного Execution Environment
// Але також — при кожному Cold Start нового Execution Environment
var s3Client = new AmazonS3Client(); // ❌
var result = await s3Client.ListBucketsAsync();
}
}
// ✅ ДОБРЕ: Статична ініціалізація — виконується ОДНОРАЗОВО при Cold Start
// Повторно використовується при всіх Warm Start викликах
public class GoodFunction
{
// static readonly — ініціалізується при завантаженні класу (Init-фаза)
private static readonly IAmazonS3 S3Client = new AmazonS3Client();
private static readonly IAmazonDynamoDB DynamoClient = new AmazonDynamoDBClient();
// HttpClient НІКОЛИ не створюється у handler — connection pooling!
private static readonly HttpClient HttpClient = new HttpClient
{
BaseAddress = new Uri(Environment.GetEnvironmentVariable("API_BASE_URL")!),
Timeout = TimeSpan.FromSeconds(10)
};
public async Task<string> Handler(APIGatewayHttpApiV2ProxyRequest req, ILambdaContext ctx)
{
// Handler лише використовує вже ініціалізовані об'єкти ✅
var response = await HttpClient.GetStringAsync("/health");
return response;
}
}
Lazy initialization для важких залежностей:
Якщо певна залежність використовується не у кожному виклику (наприклад, лише при конкретному типі події), можна застосувати Lazy<T> для відкладеної ініціалізації — вона виконається при першому зверненні, але лише один раз протягом Execution Environment:
public class OptimizedFunction
{
// Lazy ініціалізація — підключення до ElasticSearch виконується
// лише при першому зверненні, але не затримує Init-фазу
private static readonly Lazy<IElasticClient> ElasticClient = new(() =>
{
var settings = new ConnectionSettings(
new Uri(Environment.GetEnvironmentVariable("ELASTICSEARCH_URL")!));
return new ElasticClient(settings);
});
public async Task Handler(SQSEvent evt, ILambdaContext ctx)
{
// Lazy.Value ініціалізує клієнт лише один раз
var client = ElasticClient.Value;
// ...
}
}
Стратегія 2: вибір оптимального обсягу пам'яті
Ця стратегія є одним із найпоширеніших нерозуміних аспектів оптимізації Lambda. Більше пам'яті = більше CPU. Збільшення пам'яті часто знижує загальну вартість, оскільки функція виконується швидше і загальна сума GB-секунд зменшується попри вищу ставку за GB.
Стратегія 3: оптимізація розміру deployment package
Розмір ZIP-архіву безпосередньо впливає на тривалість Cold Start: Lambda завантажує весь архів при ініціалізації нового Execution Environment. Для .NET проектів основний внесок у розмір вносять NuGet-залежності та нативні бінарники.
Техніки зменшення розміру для .NET:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<!-- Публікуємо self-contained з trimming для видалення невикористаних типів -->
<PublishReadyToRun>true</PublishReadyToRun>
<!-- Видаляє невикористані типи та члени при публікації -->
<!-- Зменшує розмір на 30-60% для типових .NET Lambda -->
<PublishTrimmed>true</PublishTrimmed>
<!-- Генерує нативний код заздалегідь → швидший запуск CLR -->
<TieredPGO>true</TieredPGO>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Amazon.Lambda.Core" Version="2.2.0" />
<PackageReference Include="Amazon.Lambda.Serialization.SystemTextJson" Version="2.4.1" />
<!-- Використовуємо System.Text.Json замість Newtonsoft.Json — менший розмір! -->
<!-- Для мінімізації: включаємо лише потрібні AWSSDK пакети -->
<!-- ❌ Не: AWSSDK.Core (тягне ВСЕ) -->
<!-- ✅ Так: точечні пакети для конкретних сервісів -->
<PackageReference Include="AWSSDK.S3" Version="3.7.*" />
<PackageReference Include="AWSSDK.DynamoDBv2" Version="3.7.*" />
</ItemGroup>
</Project>
Команда публікації з оптимізацією для Lambda:
# Публікація з trimming та ReadyToRun для зменшення Cold Start
dotnet publish src/MyFunction \
--configuration Release \
--runtime linux-x64 \
--self-contained true \
--output ./publish \
-p:PublishTrimmed=true \
-p:PublishReadyToRun=true
# Пакуємо у ZIP для завантаження у Lambda
cd publish && zip -r ../function.zip .
# Розмір до та після оптимізації (типові результати):
# До: 48 MB | Після trimming: 18 MB | Зменшення: 62%
Стратегія 4: .NET Native AOT — максимальна мінімізація Cold Start
Native AOT (Ahead-of-Time) компіляція — це технологія .NET, що компілює код безпосередньо у нативний машинний код Linux заздалегідь, повністю усуваючи необхідність запуску CLR та JIT-компіляції при ініціалізації. Результатом є самодостатній бінарний файл, що запускається на порядок швидше за стандартний .NET Runtime.
Налаштування проекту для Native AOT Lambda:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<!-- Увімкнення Native AOT -->
<PublishAot>true</PublishAot>
<!-- Для AOT необхідна явна вказівка типів серіалізації -->
<!-- (рефлексія недоступна в AOT для JSON серіалізації) -->
</PropertyGroup>
<ItemGroup>
<!-- Спеціальний пакет для Native AOT Lambda -->
<PackageReference Include="Amazon.Lambda.RuntimeSupport" Version="1.10.*" />
<PackageReference Include="Amazon.Lambda.Core" Version="2.2.0" />
<PackageReference Include="Amazon.Lambda.Serialization.SystemTextJson" Version="2.4.1" />
</ItemGroup>
</Project>
Native AOT потребує явної реєстрації типів для JSON серіалізації через Source Generators:
using Amazon.Lambda.Core;
using Amazon.Lambda.RuntimeSupport;
using Amazon.Lambda.Serialization.SystemTextJson;
using System.Text.Json.Serialization;
// Source Generator генерує AOT-сумісний серіалізатор для вказаних типів
[JsonSerializable(typeof(ApiRequest))]
[JsonSerializable(typeof(ApiResponse))]
[JsonSerializable(typeof(Amazon.Lambda.APIGatewayEvents.APIGatewayHttpApiV2ProxyRequest))]
[JsonSerializable(typeof(Amazon.Lambda.APIGatewayEvents.APIGatewayHttpApiV2ProxyResponse))]
public partial class LambdaJsonContext : JsonSerializerContext { }
// Точка входу для Native AOT Lambda
static class Program
{
static async Task Main(string[] args)
{
// Зовнішнє налаштування — без DI контейнера (AOT-сумісно)
var handler = new OrderHandler();
// Bootstrap Lambda Runtime з AOT-сумісним серіалізатором
var serializer = new SourceGeneratorLambdaJsonSerializer<LambdaJsonContext>();
await LambdaBootstrapBuilder
.Create<ApiRequest, ApiResponse>(
handler.HandleAsync,
serializer)
.Build()
.RunAsync();
}
}
public sealed class OrderHandler
{
// Статичні клієнти — ініціалізуються при запуску процесу
private static readonly IAmazonDynamoDB _dynamo = new AmazonDynamoDBClient();
public async Task<ApiResponse> HandleAsync(
ApiRequest request, ILambdaContext context)
{
context.Logger.LogInformation(
"AOT Handler invoked: {RequestId}", context.AwsRequestId);
// Бізнес-логіка...
return new ApiResponse { StatusCode = 200, Body = "OK" };
}
}
public record ApiRequest(string Path, string Method, string? Body);
public record ApiResponse(int StatusCode, string Body);
dynamic, Assembly.Load, Activator.CreateInstance без Source Generators. Деякі популярні бібліотеки (AutoMapper, Entity Framework Core) не сумісні з AOT або мають обмежену підтримку. Перевіряйте сумісність всіх NuGet-залежностей перед переходом на AOT.Provisioned Concurrency: усунення Cold Start для критичних функцій
Provisioned Concurrency — це механізм, що дозволяє AWS підтримувати вказану кількість заздалегідь ініціалізованих Execution Environments у постійній готовності. На відміну від звичайних (on-demand) Execution Environments, що ініціалізуються при надходженні запиту (Cold Start), Provisioned Environments вже прогріті та готові обробляти виклики негайно.
Коли Provisioned Concurrency є необхідністю
Не кожна Lambda-функція потребує Provisioned Concurrency. Цей механізм є виправданим лише в конкретних сценаріях:
User-facing API
API Gateway + Lambda, де Cold Start безпосередньо впливає на користувацький досвід. P99 latency > 2 секунди є неприйнятним для інтерактивних застосунків.
Рекомендація: PC = мінімальна очікувана одночасна кількість запитів у пік
SLA-критичні функції
Функції з дорогим Init
Конфігурація Provisioned Concurrency
Налаштування через AWS CLI:
# Крок 1: опублікувати нову версію функції
# (Provisioned Concurrency застосовується до версій, не до $LATEST)
aws lambda publish-version \
--function-name dotnet-api-function \
--description "v1.0.0 — production release" \
--region eu-central-1
# Крок 2: зберегти ARN версії
FUNCTION_VERSION_ARN=$(aws lambda list-versions-by-function \
--function-name dotnet-api-function \
--region eu-central-1 \
--query "Versions[-1].FunctionArn" \
--output text)
echo "Function version: $FUNCTION_VERSION_ARN"
# Крок 3: увімкнути Provisioned Concurrency для цієї версії
aws lambda put-provisioned-concurrency-config \
--function-name dotnet-api-function \
--qualifier 1 \
--provisioned-concurrent-executions 5 \
--region eu-central-1
Перевірка готовності Provisioned Concurrency:
# Очікуємо переходу статусу з IN_PROGRESS на READY
aws lambda get-provisioned-concurrency-config \
--function-name dotnet-api-function \
--qualifier 1 \
--region eu-central-1
Application Auto Scaling для Provisioned Concurrency
Оскільки Provisioned Concurrency має фіксовану щогодинну вартість, утримувати максимальний рівень цілодобово є нераціональним. Application Auto Scaling дозволяє автоматично змінювати кількість Provisioned Environments за розкладом або на основі метрик.
# Реєстрація Lambda як scalable target для Application Auto Scaling
aws application-autoscaling register-scalable-target \
--service-namespace lambda \
--resource-id "function:dotnet-api-function:1" \
--scalable-dimension "lambda:function:ProvisionedConcurrency" \
--min-capacity 2 \
--max-capacity 50 \
--region eu-central-1
# Створення Scheduled Action: збільшення PC о 07:00 UTC (ранкова активність)
aws application-autoscaling put-scheduled-action \
--service-namespace lambda \
--resource-id "function:dotnet-api-function:1" \
--scalable-dimension "lambda:function:ProvisionedConcurrency" \
--scheduled-action-name "scale-up-morning" \
--schedule "cron(0 7 * * ? *)" \
--scalable-target-action MinCapacity=10,MaxCapacity=50 \
--region eu-central-1
# Зменшення PC о 22:00 UTC (нічний мінімум)
aws application-autoscaling put-scheduled-action \
--service-namespace lambda \
--resource-id "function:dotnet-api-function:1" \
--scalable-dimension "lambda:function:ProvisionedConcurrency" \
--scheduled-action-name "scale-down-night" \
--schedule "cron(0 22 * * ? *)" \
--scalable-target-action MinCapacity=2,MaxCapacity=50 \
--region eu-central-1
Моніторинг Lambda через Amazon CloudWatch
Lambda автоматично публікує набір метрик у CloudWatch без будь-якого налаштування. Розуміння цих метрик є фундаментальним для операційного контролю над serverless системою.
Invocations
Errors
Error Rate = Errors / Invocations.Duration
Throttles
ConcurrentExecutions
InitDuration
Рекомендований CloudWatch Dashboard для Lambda:
# Створення CloudWatch Alarm для відстеження критичного рівня помилок
aws cloudwatch put-metric-alarm \
--alarm-name "lambda-dotnet-error-rate-critical" \
--alarm-description "Error rate > 5% for 2 consecutive periods" \
--metric-name Errors \
--namespace AWS/Lambda \
--dimensions Name=FunctionName,Value=dotnet-api-function \
--statistic Sum \
--period 60 \
--evaluation-periods 2 \
--threshold 5 \
--comparison-operator GreaterThanOrEqualToThreshold \
--treat-missing-data notBreaching \
--alarm-actions "arn:aws:sns:eu-central-1:123456789012:ops-alerts" \
--region eu-central-1
# Alarm для відстеження Cold Start тривалості > 3 секунд
aws cloudwatch put-metric-alarm \
--alarm-name "lambda-cold-start-too-long" \
--alarm-description "Init duration > 3000ms — investigate optimization" \
--metric-name InitDuration \
--namespace AWS/Lambda \
--dimensions Name=FunctionName,Value=dotnet-api-function \
--statistic p99 \
--period 300 \
--evaluation-periods 1 \
--threshold 3000 \
--comparison-operator GreaterThanThreshold \
--alarm-actions "arn:aws:sns:eu-central-1:123456789012:ops-alerts" \
--region eu-central-1
Mini-Workshop: Перший деплой Lambda-функції
У цьому міні-воркшопі ми пройдемо шлях від встановлення інструментів до запуску вашої першої функції в хмарі. Ми розглянемо два підходи: спрощений (автоматизований) та повний (через S3 та AWS CLI).
1. Підготовка інструментарію
Для роботи з .NET Lambda в AWS нам знадобиться спеціальний інструмент командного рядка, який розширює стандартний dotnet cli.
# 0. Встановлюємо шаблони
dotnet new install Amazon.Lambda.Templates
# 1. Встановлюємо глобальний інструмент Amazon.Lambda.Tools
dotnet tool install -g Amazon.Lambda.Tools
# 2. Оновлюємо (якщо вже був встановлений)
dotnet tool update -g Amazon.Lambda.Tools
# 3. Перевіряємо готовність
dotnet lambda --version
2. Створення тестового проекту
Створимо найпростішу функцію "Hello World", щоб перевірити ланцюжок деплойменту.
# Створюємо проект за допомогою AWS шаблону
dotnet new lambda.EmptyFunction -n MyFirstLambda -o src/MyFirstLambda
cd src/MyFirstLambda
# Додаємо залежності та перевіряємо проект
dotnet restore
using Amazon.Lambda.Core;
// Реєструємо серіалізатор (обов'язково для обробки JSON)
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
namespace MyFirstLambda;
public class Function
{
/// <summary>
/// Простий метод, який приймає рядок і повертає його у верхньому регістрі
/// </summary>
public string FunctionHandler(string input, ILambdaContext context)
{
context.Logger.LogInformation($"Received input: {input}");
return input.ToUpper();
}
}
3. Автоматизований деплой (dotnet lambda)
Це найшвидший спосіб для розробки та тестування. Команда deploy-function бере на себе компіляцію, пакування, завантаження та оновлення конфігурації.
# Деплой з автоматичним створенням/оновленням функції
dotnet lambda deploy-function MyFirstLambda \
--runtime dotnet10 \
--handler MyFirstLambda::MyFirstLambda.Function::FunctionHandler \
--memory-size 256 \
--timeout 30 \
--region eu-central-1
# Використовуємо той самий dotnet tool
dotnet lambda deploy-function MyFirstLambda `
--runtime dotnet10 `
--handler MyFirstLambda::MyFirstLambda.Function::FunctionHandler `
--memory-size 256 `
--timeout 30 `
--region eu-central-1
4. Повний контроль: Деплой через S3 та AWS CLI
У великих проектах та CI/CD пайплайнах часто використовують двостадійний деплой: спочатку завантажують пакет у S3, а потім оновлюють функцію. Це надійніше для великих артефактів (> 50MB).
Крок A: Пакування артефакту
dotnet lambda package \
--configuration Release \
--output-package ./publish/function.zip
Крок B: Завантаження в S3
aws s3 cp ./publish/function.zip \
s3://my-deployment-bucket/v1/my-first-lambda.zip \
--region eu-central-1
Крок C: Оновлення коду Lambda
aws lambda update-function-code \
--function-name MyFirstLambda \
--s3-bucket my-deployment-bucket \
--s3-key v1/my-first-lambda.zip \
--region eu-central-1
Крок D: Оновлення конфігурації (опційно)
aws lambda update-function-configuration \
--function-name MyFirstLambda \
--memory-size 512 \
--environment "Variables={ENV=production,DEBUG=false}" \
--region eu-central-1
5. Виклик функції та перевірка результату
Після успішного деплою ми можемо викликати нашу функцію і перевірити, чи вона працює коректно. Оскільки ми очікуємо рядок і повертаємо його у верхньому регістрі, передамо тестовий рядок.
# Викликаємо функцію з тестовим навантаженням (payload)
dotnet lambda invoke-function MyFirstLambda \
--payload '"hello serverless world"' \
--region eu-central-1
# Альтернативний спосіб виклику через стандартний AWS CLI
aws lambda invoke \
--function-name MyFirstLambda \
--payload '"hello serverless world"' \
--cli-binary-format raw-in-base64-out \
--region eu-central-1 \
response.json
# Перегляд результату
cat response.json
# Очікуваний вивід: "HELLO SERVERLESS WORLD"
6. Активація публічного лінка (Lambda Function URL)
Оскільки ми розглядаємо автоматизацію та повний контроль, найшвидше це зробити через AWS CLI.
Крок A: Створення конфігурації URL
Вмикаємо функцію URL для нашої Lambda. Прапор --auth-type NONE означає, що лінк буде публічним і не вимагатиме підписів AWS IAM для кожного запиту.
aws lambda create-function-url-config \
--function-name MyFirstLambda \
--auth-type NONE \
--region eu-central-1
Крок B: Дозвіл на публічний доступ (CORS / Permissions)
За замовчуванням AWS заблокує будь-які анонімні запити з інтернету з міркувань безпеки. Нам потрібно явно дозволити всьому світу (*) викликати цей URL:
aws lambda add-permission \
--function-name MyFirstLambda \
--statement-id FunctionURLAllowPublicAccess \
--action lambda:InvokeFunctionUrl \
--principal "*" \
--function-url-auth-type NONE \
--region eu-central-1
Крок C: Отримання вашого готового лінка
Тепер дізнаємося згенеровану адресу нашої функції:
aws lambda get-function-url-config \
--function-name MyFirstLambda \
--region eu-central-1 \
--query FunctionUrl \
--output text
Як це протестувати?
У результаті виконання останньої команди ви отримаєте унікальний лінк, який виглядає приблизно так: https://abcdefg1234567890.lambda-url.eu-central-1.on.aws/.
Оскільки ваш метод FunctionHandler у C# приймає рядок і повертає його в UPPERCASE, ви можете протестувати його через звичайний curl у терміналі (або через Postman), передавши текст у тілі POST-запиту:
curl -X POST https://your-lambda-url.on.aws/ \
-d '"hello from internet"' \
-H "Content-Type: application/json"
curl ми передаємо текст у подвійних лапках всередині одинарних ('"hello"'), щоб серіалізатор SystemTextJson правильно його розпізнав.
Результатом у консолі буде: "HELLO FROM INTERNET".7. Інтеграція з Amazon API Gateway (Альтернатива Function URL)
Якщо вам потрібен більший контроль над маршрутизацією, авторизацією (наприклад, інтеграція з Cognito), лімітами (Rate Limiting) або ви хочете побудувати повноцінний REST API, замість Function URL краще використовувати Amazon API Gateway.
Найпростіший спосіб створити HTTP API та підключити його до нашої Lambda-функції через AWS CLI:
Крок A: Створення HTTP API
aws apigatewayv2 create-api \
--name MyFirstLambdaAPI \
--protocol-type HTTP \
--target arn:aws:lambda:eu-central-1:ACCOUNT_ID:function:MyFirstLambda \
--region eu-central-1
Зверніть увагу: замініть ACCOUNT_ID на ваш реальний ідентифікатор аккаунта.
Крок B: Надання дозволу API Gateway на виклик функції
Як і у випадку з Function URL, Lambda повинна явно дозволити API Gateway викликати себе:
aws lambda add-permission \
--function-name MyFirstLambda \
--statement-id ApiGatewayInvokeAccess \
--action lambda:InvokeFunction \
--principal apigateway.amazonaws.com \
--region eu-central-1
Крок C: Отримання Endpoint URL
Після створення API (Крок A) у відповіді ви отримаєте поле ApiEndpoint. Ви також можете дізнатись його такою командою:
aws apigatewayv2 get-apis \
--query "Items[?Name=='MyFirstLambdaAPI'].ApiEndpoint" \
--output text \
--region eu-central-1
Як це протестувати?
Використовуйте отриманий ApiEndpoint (наприклад, https://xyz123.execute-api.eu-central-1.amazonaws.com) аналогічно до Function URL:
curl -X POST https://your-api-gateway-url.amazonaws.com/ \
-d '"hello via api gateway"' \
-H "Content-Type: application/json"
8. Використання AWS Toolkit (IDE)
Якщо ви віддаєте перевагу роботі в IDE, AWS надає потужні розширення (Toolkit) для Visual Studio, JetBrains Rider та VS Code.
- Visual Studio: Дозволяє створювати проекти з шаблонів, запускати локальне тестування (Mock Lambda Test Tool) та деплоїти правою кнопкою миші.
- VS Code: Надає інтерфейс для перегляду логів CloudWatch прямо в редакторі, керування Lambda-функціями та локального дебагу через SAM CLI.
- Rider: Найкраща підтримка для macOS/Linux розробників з глибокою інтеграцією AWS-ресурсів та шаблонів проектів.
Підсумок: коли використовувати Lambda, а коли — EC2/ECS
Вибір між Lambda та традиційними обчислювальними сервісами є архітектурним рішенням, що залежить від конкретних характеристик навантаження, вимог до латентності та операційної складності.
| Критерій | Lambda | EC2 / ECS |
|---|---|---|
| Час запуску | Cold Start 100ms – 3s | Постійно запущений (0ms latency) |
| Максимальний час виконання | 15 хвилин | Необмежено |
| Стан між запитами | Stateless (ephemeral) | Stateful (можливо) |
| Масштабування | Автоматичне, миттєве | ASG (хвилини) |
| Вартість при низькому навантаженні | Майже нульова | Фіксована |
| Вартість при високому навантаженні | Лінійна (може бути вищою) | Фіксована + резерви |
| Операційна складність | Мінімальна | Середня — висока |
| Контроль над середовищем | Обмежений | Повний |
Lambda є оптимальним вибором для:
- Event-driven обробки: S3 файлів, SQS повідомлень, DynamoDB Streams
- Нерегулярного трафіку з піками та тривалими паузами
- Простих HTTP API з низьким та середнім навантаженням
- Задач за розкладом (cron jobs)
- Glue-коду між AWS-сервісами
EC2 або ECS є кращим вибором для:
- Довготривалих задач (> 15 хвилин)
- Систем з постійно-стабільним навантаженням (Lambda буде дорожчою)
- Застосунків з жорсткими вимогами до Cold Start без бажання платити за Provisioned Concurrency
- Систем, що потребують специфічного апаратного забезпечення (GPU, великий обсяг RAM)
- Stateful додатків з WebSocket з'єднаннями тривалістю > 15 хвилин
Практичний воркшоп: Побудова системи обробки зображень (A-Z)
У цьому розділі ми побудуємо повноцінну serverless-систему «від А до Я». Наш сценарій: автоматичне створення мініатюр (thumbnails) для зображень, що завантажуються користувачами в S3.
Архітектура рішення
Система працює за подієво-орієнтованою моделлю:
- Користувач завантажує оригінальне зображення у
source-images-bucket. - S3 генерує подію
s3:ObjectCreated:*та викликає Lambda-функцію. - Lambda-функція (.NET 10) завантажує оригінал, змінює розмір та зберігає результат у
processed-images-bucket. - Всі дії логуються у CloudWatch для моніторингу.
Крок 1: Підготовка інфраструктури (S3 Buckets)
Перш за все, нам потрібно створити два S3-кошики: один для оригіналів, інший для мініатюр.
- Перейдіть у сервіс S3.
- Натисніть Create bucket.
- Bucket name:
kostyl-dev-source-images-[unique-id]. - Region:
eu-central-1. - Залиште інші налаштування за замовчуванням та натисніть Create bucket.
- Повторіть кроки для створення другого кошика з іменем
kostyl-dev-processed-images-[unique-id].
# Створюємо кошик для оригіналів
aws s3 mb s3://kostyl-dev-source-images \
--region eu-central-1
# Створюємо кошик для оброблених мініатюр
aws s3 mb s3://kostyl-dev-processed-images \
--region eu-central-1
# Створюємо кошик для оригіналів
New-S3Bucket -BucketName kostyl-dev-source-images -Region eu-central-1
# Створюємо кошик для оброблених мініатюр
New-S3Bucket -BucketName kostyl-dev-processed-images -Region eu-central-1
Крок 2: Налаштування безпеки (IAM Role)
Lambda-функція повинна мати права на читання з одного кошика та запис у інший.
- Перейдіть у сервіс IAM → Roles → Create role.
- Trusted entity: AWS service → Lambda. Натисніть Next.
- Натисніть Create policy (відкриється нова вкладка).
- Оберіть вкладку JSON та вставте наступний код (замініть імена bucket на ваші):
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": ["s3:GetObject"], "Resource": "arn:aws:s3:::kostyl-dev-source-images/*" }, { "Effect": "Allow", "Action": ["s3:PutObject"], "Resource": "arn:aws:s3:::kostyl-dev-processed-images/*" }, { "Effect": "Allow", "Action": ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"], "Resource": "arn:aws:logs:*:*:*" } ] } - Назвіть політику
ImageProcessorPolicyта збережіть. - Поверніться до вкладки створення ролі, оновіть список та оберіть вашу нову політику.
- Назвіть роль
ImageProcessorRoleта натисніть Create role.
# 1. Створюємо Trust Policy файл
echo '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": "lambda.amazonaws.com" },
"Action": "sts:AssumeRole"
}]
}' > trust-policy.json
# 2. Створюємо роль
aws iam create-role \
--role-name ImageProcessorRole \
--assume-role-policy-document file://trust-policy.json
# 3. Додаємо права (inline policy)
aws iam put-role-policy \
--role-name ImageProcessorRole \
--policy-name S3Access \
--policy-document '{
"Version": "2012-10-17",
"Statement": [
{ "Effect": "Allow", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::kostyl-dev-source-images/*" },
{ "Effect": "Allow", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::kostyl-dev-processed-images/*" },
{ "Effect": "Allow", "Action": ["logs:*"], "Resource": "arn:aws:logs:*:*:*" }
]
}'
# 1. Створюємо Trust Policy
$trustPolicy = '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": "lambda.amazonaws.com" },
"Action": "sts:AssumeRole"
}]
}'
# 2. Створюємо роль
New-IAMRole -RoleName ImageProcessorRole -AssumeRolePolicyDocument $trustPolicy
# 3. Додаємо права
$permissions = '{
"Version": "2012-10-17",
"Statement": [
{ "Effect": "Allow", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::kostyl-dev-source-images/*" },
{ "Effect": "Allow", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::kostyl-dev-processed-images/*" },
{ "Effect": "Allow", "Action": ["logs:*"], "Resource": "arn:aws:logs:*:*:*" }
]
}'
Write-IAMRolePolicy -RoleName ImageProcessorRole -PolicyName S3Access -PolicyDocument $permissions
Крок 3: Розробка .NET 10 коду
Ми використаємо бібліотеку SixLabors.ImageSharp для обробки зображень та офіційний AWS SDK.
# 1. Створюємо проект (класична бібліотека)
dotnet new classlib -n ImageProcessor.Lambda
cd ImageProcessor.Lambda
# 2. Додаємо необхідні NuGet-пакети
dotnet add package Amazon.Lambda.Core
dotnet add package Amazon.Lambda.Serialization.SystemTextJson
dotnet add package Amazon.Lambda.S3Events
dotnet add package AWSSDK.S3
dotnet add package SixLabors.ImageSharp
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<!-- Оптимізації для Lambda -->
<PublishReadyToRun>true</PublishReadyToRun>
<PublishTrimmed>true</PublishTrimmed>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Amazon.Lambda.Core" Version="2.5.*" />
<PackageReference Include="Amazon.Lambda.Serialization.SystemTextJson" Version="2.4.*" />
<PackageReference Include="Amazon.Lambda.S3Events" Version="3.1.*" />
<PackageReference Include="AWSSDK.S3" Version="3.7.*" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.*" />
</ItemGroup>
</Project>
using Amazon.Lambda.Core;
using Amazon.Lambda.S3Events;
using Amazon.S3;
using Amazon.S3.Model;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Processing;
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
namespace ImageProcessor.Lambda;
public class Function
{
private readonly IAmazonS3 _s3Client;
private const string DestinationBucket = "kostyl-dev-processed-images";
public Function() : this(new AmazonS3Client()) { }
public Function(IAmazonS3 s3Client) => _s3Client = s3Client;
public async Task FunctionHandler(S3Event evnt, ILambdaContext context)
{
foreach (var record in evnt.Records)
{
var srcBucket = record.S3.Bucket.Name;
var srcKey = Uri.UnescapeDataString(record.S3.Object.Key.Replace("+", " "));
context.Logger.LogInformation($"Processing {srcBucket}/{srcKey}");
using var response = await _s3Client.GetObjectAsync(srcBucket, srcKey);
using var inputStream = response.ResponseStream;
// Обробка зображення через ImageSharp
using var image = await Image.LoadAsync(inputStream);
// Змінюємо розмір до ширини 300px, зберігаючи пропорції
image.Mutate(x => x.Resize(new ResizeOptions {
Mode = ResizeMode.Max,
Size = new Size(300, 0)
}));
using var outputStream = new MemoryStream();
await image.SaveAsJpegAsync(outputStream);
outputStream.Position = 0;
// Зберігаємо мініатюру
var dstKey = $"thumbnails/{srcKey}";
await _s3Client.PutObjectAsync(new PutObjectRequest
{
BucketName = DestinationBucket,
Key = dstKey,
InputStream = outputStream,
ContentType = "image/jpeg"
});
context.Logger.LogInformation($"Thumbnail saved to {DestinationBucket}/{dstKey}");
}
}
}
Крок 4: Деплой та конфігурація тригера
Тепер ми скомпілюємо код та налаштуємо автоматичний виклик Lambda при завантаженні файлу в S3.
- Деплой коду:
- Виконайте
dotnet lambda packageлокально, щоб отриматиfunction.zip. - В консолі Lambda натисніть Create function.
- Назва:
ImageProcessor, Runtime:.NET 10. - Permissions: оберіть Use an existing role та вкажіть
ImageProcessorRole. - Після створення у вкладці Code натисніть Upload from → .zip file та оберіть ваш архів.
- У Runtime settings змініть Handler на:
ImageProcessor::ImageProcessor.Lambda.Function::FunctionHandler.
- Виконайте
- Налаштування тригера:
- На сторінці функції натисніть + Add trigger.
- Оберіть S3. Bucket:
kostyl-dev-source-images. - Event type: All object create events.
- Підтвердіть згоду (Recursive invocation warning) та натисніть Add.
# 1. Компіляція
dotnet lambda package \
--output-package function.zip
# 2. Деплой (використовуйте ARN вашої ролі)
aws lambda create-function \
--function-name ImageProcessor \
--runtime dotnet10 \
--handler ImageProcessor::ImageProcessor.Lambda.Function::FunctionHandler \
--role arn:aws:iam::ACCOUNT_ID:role/ImageProcessorRole \
--zip-file fileb://function.zip \
--region eu-central-1
# 3. Дозвіл для S3
aws lambda add-permission \
--function-name ImageProcessor \
--statement-id s3-trigger \
--action "lambda:InvokeFunction" \
--principal s3.amazonaws.com \
--source-arn "arn:aws:s3:::kostyl-dev-source-images"
# 4. Реєстрація тригера
aws s3api put-bucket-notification-configuration \
--bucket kostyl-dev-source-images \
--notification-configuration '{
"LambdaFunctionConfigurations": [{
"LambdaFunctionArn": "arn:aws:lambda:eu-central-1:ACCOUNT_ID:function:ImageProcessor",
"Events": ["s3:ObjectCreated:*"]
}]
}'
# 1. Деплой
Publish-LMFunction -FunctionName ImageProcessor `
-Runtime dotnet10 `
-Handler "ImageProcessor::ImageProcessor.Lambda.Function::FunctionHandler" `
-Role "arn:aws:iam::ACCOUNT_ID:role/ImageProcessorRole" `
-ZipFile "function.zip" `
-Region eu-central-1
# 2. Дозвіл
Add-LMPermission -FunctionName ImageProcessor `
-StatementId s3-trigger `
-Action "lambda:InvokeFunction" `
-Principal s3.amazonaws.com `
-SourceArn "arn:aws:s3:::kostyl-dev-source-images"
# 3. Тригер
$config = @{
LambdaFunctionConfigurations = @(@{
LambdaFunctionArn = "arn:aws:lambda:eu-central-1:ACCOUNT_ID:function:ImageProcessor"
Events = @("s3:ObjectCreated:*")
})
}
Write-S3BucketNotification -BucketName kostyl-dev-source-images -Configuration $config
Крок 5: Тестування та моніторинг
Завантаження тестового файлу
# Завантажуємо будь-яке зображення у source bucket
aws s3 cp my-photo.jpg s3://kostyl-dev-source-images/photo-1.jpg
Перевірка результату
# Перевіряємо, чи з'явилася мініатюра у другому кошику
aws s3 ls s3://kostyl-dev-processed-images/thumbnails/
Моніторинг логів
# Дивимось логи виконання у реальному часі
aws logs tail /aws/lambda/ImageProcessor \
--follow
Практичний воркшоп: Проектування Serverless URL Shortener (API + DynamoDB)
Цей розділ присвячений побудові високонавантаженої системи скорочення посилань. Проект демонструє синергію синхронного HTTP API та персистентного сховища NoSQL, реалізуючи патерн «Compute-as-a-Service» з мінімальною латентністю.
Архітектурна парадигма та потік даних
Система базується на трирівневій serverless-архітектурі:
- Інтерфейсний рівень (API Gateway): забезпечує HTTP-ендпоінти та автоматичну десеріалізацію запитів.
- Логічний рівень (Lambda .NET 10): виконує бізнес-логіку (генерація кодів, валідація URL) та взаємодію з базою даних.
- Рівень зберігання (DynamoDB): забезпечує атомарне збереження пар «Код — Оригінальний URL» з гарантованим часом доступу < 10ms.
Крок 1: Проектування схеми даних (DynamoDB)
Нам потрібна таблиця з високою пропускною здатністю для ключ-значення запитів.
- Перейдіть до DynamoDB → Tables → Create table.
- Table name:
UrlMappingTable. - Partition key:
ShortCode(String). - Table settings: Default settings (On-Demand capacity).
- Натисніть Create table.
aws dynamodb create-table \
--table-name UrlMappingTable \
--attribute-definitions AttributeName=ShortCode,AttributeType=S \
--key-schema AttributeName=ShortCode,KeyType=HASH \
--billing-mode PAY_PER_REQUEST \
--region eu-central-1
New-DDBTable -TableName "UrlMappingTable" `
-AttributeDefinition @{AttributeName="ShortCode";AttributeType="S"} `
-KeySchema @{AttributeName="ShortCode";KeyType="HASH"} `
-BillingMode PAY_PER_REQUEST `
-Region eu-central-1
Крок 2: Дефініція політик доступу (IAM)
Lambda потребує гранулярних прав на виконання операцій GetItem та PutItem над створеною таблицею.
- Створіть роль UrlShortenerRole для сервісу Lambda.
- Додайте Inline Policy з наступним JSON:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": ["dynamodb:GetItem", "dynamodb:PutItem"], "Resource": "arn:aws:dynamodb:eu-central-1:*:table/UrlMappingTable" }, { "Effect": "Allow", "Action": ["logs:*"], "Resource": "arn:aws:logs:*:*:*" } ] }
# 1. Створення ролі
aws iam create-role --role-name UrlShortenerRole \
--assume-role-policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"lambda.amazonaws.com"},"Action":"sts:AssumeRole"}]}'
# 2. Прикріплення прав
aws iam put-role-policy --role-name UrlShortenerRole \
--policy-name DynamoDBAccess \
--policy-document '{
"Version": "2012-10-17",
"Statement": [
{ "Effect": "Allow", "Action": ["dynamodb:PutItem", "dynamodb:GetItem"], "Resource": "arn:aws:dynamodb:eu-central-1:*:table/UrlMappingTable" },
{ "Effect": "Allow", "Action": ["logs:*"], "Resource": "arn:aws:logs:*:*:*" }
]
}'
Крок 3: Реалізація бізнес-логіки (.NET 10)
Для чистоти коду ми використаємо Lambda Annotations, що дозволяє будувати API у декларативному стилі.
# Створюємо проект
dotnet new lambda.Annotations -n UrlShortener.Lambda
cd UrlShortener.Lambda
# Додаємо SDK для DynamoDB
dotnet add package AWSSDK.DynamoDBv2
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishReadyToRun>true</PublishReadyToRun>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Amazon.Lambda.Annotations" Version="1.6.0" />
<PackageReference Include="Amazon.Lambda.Core" Version="2.5.*" />
<PackageReference Include="Amazon.Lambda.Serialization.SystemTextJson" Version="2.4.*" />
<PackageReference Include="AWSSDK.DynamoDBv2" Version="3.7.*" />
</ItemGroup>
</Project>
using Amazon.Lambda.Annotations;
using Amazon.Lambda.Annotations.APIGateway;
using Amazon.Lambda.Core;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.Model;
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
namespace UrlShortener.Lambda;
public class Functions
{
private readonly IAmazonDynamoDB _dynamoDb;
private const string TableName = "UrlMappingTable";
public Functions(IAmazonDynamoDB dynamoDb) => _dynamoDb = dynamoDb;
[LambdaFunction]
[HttpApi(LambdaHttpMethod.Post, "/shorten")]
public async Task<IHttpResult> ShortenUrl([FromBody] ShortenRequest request)
{
var shortCode = Guid.NewGuid().ToString()[..8]; // Спрощена генерація
await _dynamoDb.PutItemAsync(TableName, new Dictionary<string, AttributeValue>
{
["ShortCode"] = new AttributeValue { S = shortCode },
["LongUrl"] = new AttributeValue { S = request.Url }
});
return HttpResults.Created($"/{{shortCode}}", new { shortCode });
}
[LambdaFunction]
[HttpApi(LambdaHttpMethod.Get, "/{shortCode}")]
public async Task<IHttpResult> Redirect(string shortCode)
{
var response = await _dynamoDb.GetItemAsync(TableName, new Dictionary<string, AttributeValue>
{
["ShortCode"] = new AttributeValue { S = shortCode }
});
if (!response.IsItemSet) return HttpResults.NotFound();
var longUrl = response.Item["LongUrl"].S;
return HttpResults.Redirect(longUrl);
}
}
public record ShortenRequest(string Url);
using Amazon.Lambda.Annotations;
using Microsoft.Extensions.DependencyInjection;
using Amazon.DynamoDBv2;
[LambdaStartup]
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddAWSService<IAmazonDynamoDB>();
}
}
Крок 4: Деплоймент та інтеграція з API Gateway
# 1. Деплой функції (Amazon.Lambda.Tools автоматично налаштує API Gateway на основі анотацій)
dotnet lambda deploy-serverless --stack-name url-shortener-stack \
--s3-bucket my-deployment-bucket \
--region eu-central-1
Використання deploy-serverless замість deploy-function є критичним при використанні Lambda Annotations. Ця команда генерує CloudFormation шаблон, який автоматично створює:
- Lambda функції.
- API Gateway (HTTP API).
- Схеми маршрутизації (Routes).
- IAM ролі та дозволи. ::
Після успішного деплою ви отримаєте Base URL вашого API.
Скорочення посилання
curl -X POST https://api-id.execute-api.eu-central-1.amazonaws.com/shorten \
-H "Content-Type: application/json" \
-d '{"url": "https://kostyl.dev/aws/lambda"}'
Відповідь системи
{
"shortCode": "a1b2c3d4"
}
Перевірка редиректу
# Відкрийте у браузері або через curl -I
curl -I https://api-id.execute-api.eu-central-1.amazonaws.com/a1b2c3d4
::
Академічний висновок
Крок 6: Очистка ресурсів
# 1. Видаляємо CloudFormation стек (видаляє Lambda + API Gateway + IAM ролі автоматично)
aws cloudformation delete-stack \
--stack-name url-shortener-stack \
--region eu-central-1
# 2. Видаляємо DynamoDB таблицю
aws dynamodb delete-table \
--table-name UrlMappingTable \
--region eu-central-1
# 3. Перевіряємо, що стек видалено
aws cloudformation describe-stacks \
--stack-name url-shortener-stack \
--region eu-central-1
# Очікуємо: "An error occurred (ValidationError): Stack with id url-shortener-stack does not exist"
- Перейдіть до CloudFormation → Stacks.
- Оберіть
url-shortener-stackта натисніть Delete. - Підтвердіть видалення — AWS автоматично видалить Lambda, API Gateway та IAM ролі, що були створені стеком.
- Окремо перейдіть до DynamoDB → Tables, оберіть
UrlMappingTableта натисніть Delete table.
Фінальний висновок
AWS Lambda — це не просто «функція в хмарі». Це архітектурний підхід, що кардинально змінює спосіб мислення про серверну інфраструктуру. Від простого Hello World до розподілених систем обробки зображень та URL-шортенерів — ми пройшли повний шлях.
Ключові принципи, які варто запам'ятати
Stateless за замовчуванням
Cold Start — це компроміс
IAM — найменші привілеї
Observability з CloudWatch
Вартість = час × пам'ять
IaC для всього
Що далі?
Після освоєння основ Lambda, наступні кроки у вивченні serverless на AWS:
| Технологія | Що вивчити |
|---|---|
| AWS SAM | Infrastructure as Code для serverless: шаблони, локальне тестування |
| AWS Step Functions | Оркестрація складних multi-step бізнес-процесів |
| Amazon EventBridge | Event-driven архітектура між сервісами та SaaS-інтеграції |
| AWS App Runner | Serverless контейнери для довготривалих HTTP-сервісів |
| Amazon Bedrock + Lambda | Інтеграція AI/ML моделей у serverless пайплайни |
Amazon DynamoDB — NoSQL Database
Фундаментальний посібник з Amazon DynamoDB для .NET розробників. Від моделі даних та первинних ключів до вторинних індексів, транзакцій, Streams, TTL, Global Tables та інтеграції через AWS SDK for .NET — з акцентом на проектування схем для реальних production-сценаріїв.
Amazon Bedrock - Foundation Models, RAG та Agents
Глибоке занурення в Amazon Bedrock. Практична інтеграція великих мовних моделей Claude, Llama та Titan з .NET 8 та React. Реалізація Streaming, побудова Knowledge Bases для RAG, налаштування Guardrails безпеки та розробка автономних AI Agents.