Serverless архитектура: AWS Lambda, Edge Functions
FaaS, event-driven архитектура и serverless best practices
Serverless не значит "без серверов" — это значит что вы не управляете ими. Платите только за использование, автоматическое масштабирование, нет DevOps overhead. AWS Lambda обрабатывает триллионы запросов в месяц. Что такое Serverless Характеристики: No server management — провайдер управляет инфраструктурой Auto-scaling — от 0 до тысяч instances Pay per execution — платите за время работы Event-driven — запуск по событиям Stateless — каждый запрос независим AWS Lambda: основы Простая Lambda функция: // index.js exports.handler = async (event) => { console.log('Event:', JSON.stringify(event)); return { statusCode: 200, headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }, body: JSON.stringify({ message: 'Hello from Lambda!', input: event }) }; }; Triggers (события): API Gateway — HTTP requests S3 — file upload/delete DynamoDB — database changes SNS/SQS — messages EventBridge — scheduled events CloudWatch — logs, alarms API Gateway + Lambda REST API: // Lambda function exports.handler = async (event) => { const { httpMethod, path, body, queryStringParameters } = event; switch (httpMethod) { case 'GET': if (path === '/users') { return getUsers(queryStringParameters); } if (path.startsWith('/users/')) { const userId = path.split('/')[2]; return getUser(userId); } break; case 'POST': if (path === '/users') { return createUser(JSON.parse(body)); } break; case 'PUT': if (path.startsWith('/users/')) { const userId = path.split('/')[2]; return updateUser(userId, JSON.parse(body)); } break; case 'DELETE': if (path.startsWith('/users/')) { const userId = path.split('/')[2]; return deleteUser(userId); } break; } return { statusCode: 404, body: JSON.stringify({ error: 'Not found' }) }; }; DynamoDB: serverless база данных import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; import { DynamoDBDocumentClient, PutCommand, GetCommand, QueryCommand } from '@aws-sdk/lib-dynamodb'; const client = new DynamoDBClient({}); const docClient = DynamoDBDocumentClient.from(client); // Create async function createUser(user) { await docClient.send(new PutCommand({ TableName: 'Users', Item: { userId: user.id, email: user.email, createdAt: Date.now() } })); } // Read async function getUser(userId) { const result = await docClient.send(new GetCommand({ TableName: 'Users', Key: { userId } })); return result.Item; } // Query async function getUsersByEmail(email) { const result = await docClient.send(new QueryCommand({ TableName: 'Users', IndexName: 'EmailIndex', KeyConditionExpression: 'email = :email', ExpressionAttributeValues: { ':email': email } })); return result.Items; } S3 Events exports.handler = async (event) => { // Обработка загрузки файлов for (const record of event.Records) { const bucket = record.s3.bucket.name; const key = decodeURIComponent(record.s3.object.key.replace(/\+/g, ' ')); console.log(`File uploaded: ${key} to ${bucket}`); // Например, генерация thumbnail для изображений if (key.match(/\.(jpg|jpeg|png)$/)) { await generateThumbnail(bucket, key); } } }; Environment Variables и Secrets // Использование environment variables const API_KEY = process.env.API_KEY; const DB_URL = process.env.DATABASE_URL; // Secrets Manager для чувствительных данных import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager'; const client = new SecretsManagerClient(); async function getSecret(secretName) { const response = await client.send( new GetSecretValueCommand({ SecretId: secretName }) ); return JSON.parse(response.SecretString); } const dbCredentials = await getSecret('prod/db/credentials'); Cold Starts: проблема и решение Что такое Cold Start: Первый запуск Lambda после периода неактивности занимает больше времени (100-1000ms). Решения: 1. Provisioned Concurrency: # serverless.yml functions: api: handler: handler.main provisionedConcurrency: 2 # Всегда 2 warm instances 2. Scheduled warming: // Warming Lambda каждые 5 минут exports.warmer = async (event) => { if (event.source === 'warmer') { return { statusCode: 200, body: 'Warmed' }; } // Regular logic }; 3. Optimization: Минимизируйте dependencies Используйте ARM processors (Graviton2) Увеличьте memory (улучшает CPU) Error Handling и Retry exports.handler = async (event) => { try { const result = await processData(event); return { statusCode: 200, body: JSON.stringify(result) }; } catch (error) { console.error('Error:', error); // Отправка в Dead Letter Queue для повторной обработки await sns.publish({ TopicArn: process.env.DLQ_TOPIC, Message: JSON.stringify({ event, error: error.message }) }); throw error; // Lambda автоматически retry (до 2 раз) } }; Step Functions: оркестрация Для сложных workflows с несколькими Lambda: { "StartAt": "ProcessOrder", "States": { "ProcessOrder": { "Type": "Task", "Resource": "arn:aws:lambda:region:account:function:processOrder", "Next": "CheckInventory" }, "CheckInventory": { "Type": "Task", "Resource": "arn:aws:lambda:region:account:function:checkInventory", "Next": "IsAvailable" }, "IsAvailable": { "Type": "Choice", "Choices": [ { "Variable": "$.available", "BooleanEquals": true, "Next": "ChargeCard" } ], "Default": "OutOfStock" }, "ChargeCard": { "Type": "Task", "Resource": "arn:aws:lambda:region:account:function:chargeCard", "End": true }, "OutOfStock": { "Type": "Fail", "Error": "OutOfStock", "Cause": "Product not available" } } } Serverless Framework # serverless.yml service: my-service provider: name: aws runtime: nodejs18.x region: us-east-1 environment: TABLE_NAME: ${self:service}-${opt:stage, self:provider.stage} iamRoleStatements: - Effect: Allow Action: - dynamodb:Query - dynamodb:Scan - dynamodb:GetItem - dynamodb:PutItem Resource: "arn:aws:dynamodb:${opt:region}:*:table/${self:provider.environment.TABLE_NAME}" functions: create: handler: api/create.handler events: - http: path: users method: post cors: true list: handler: api/list.handler events: - http: path: users method: get cors: true resources: Resources: UsersTable: Type: AWS::DynamoDB::Table Properties: TableName: ${self:provider.environment.TABLE_NAME} AttributeDefinitions: - AttributeName: userId AttributeType: S KeySchema: - AttributeName: userId KeyType: HASH BillingMode: PAY_PER_REQUEST CloudFront + Lambda@Edge Запуск кода ближе к пользователям: // Viewer Request: изменение запроса exports.handler = (event, context, callback) => { const request = event.Records[0].cf.request; // A/B testing if (Math.random() { const response = event.Records[0].cf.response; // Добавление security headers response.headers['strict-transport-security'] = [{ key: 'Strict-Transport-Security', value: 'max-age=31536000; includeSubDomains' }]; callback(null, response); }; Monitoring и Logging CloudWatch Logs: console.log('Info message'); console.error('Error message'); // Structured logging console.log(JSON.stringify({ level: 'info', message: 'User created', userId: user.id, timestamp: Date.now() })); X-Ray tracing: import AWSXRay from 'aws-xray-sdk-core'; import AWS from 'aws-sdk'; const capturedAWS = AWSXRay.captureAWS(AWS); const dynamodb = new capturedAWS.DynamoDB.DocumentClient(); Cost Optimization Стратегии: Right-size memory — больше памяти = больше CPU Reduce cold starts — меньше execution time Use ARM — 20% дешевле Batch processing — меньше invocations Reserve capacity — для predictable workloads Pricing пример: 1M requests: $0.20 400,000 GB-seconds: $6.67 Total: ~$7/месяц для среднего API Serverless Best Practices Один Lambda = одна задача — не создавайте монолиты Minimal dependencies — уменьшает cold start Connection pooling — переиспользуйте DB connections Idempotency — retry-safe операции Timeout правильно — не default 3 секунды Monitor everything — CloudWatch + X-Ray Когда НЕ использовать Serverless Long-running processes (> 15 минут) High memory requirements (> 10GB) Websockets (лучше containers) Consistent low latency critical Заключение: Serverless — мощный паттерн для event-driven архитектур. Начните с простых Lambda + API Gateway. Добавьте DynamoDB для данных. Используйте Step Functions для workflows. Мониторьте через CloudWatch. Оптимизируйте costs через right-sizing. Serverless снижает operational overhead и позволяет фокусироваться на бизнес-логике. Используйте для APIs, data processing, automation.