Архитектура бэкенда CRC на AWS
Браузер → API Gateway (HTTP API) → Lambda → DynamoDB
↓
CloudWatch Logs
DynamoDB: создание и настройка
# Создаём таблицу
aws dynamodb create-table \
--table-name visitor-counter \
--attribute-definitions AttributeName=id,AttributeType=S \
--key-schema AttributeName=id,KeyType=HASH \
--billing-mode PAY_PER_REQUEST \
--tags Key=project,Value=crc
# Начальная запись
aws dynamodb put-item \
--table-name visitor-counter \
--item '{"id": {"S": "visitors"}, "count": {"N": "0"}}'
# Проверка
aws dynamodb get-item \
--table-name visitor-counter \
--key '{"id": {"S": "visitors"}}'
Lambda: полная реализация
Код функции
# lambda_function.py
import json
import logging
import boto3
from decimal import Decimal
logger = logging.getLogger()
logger.setLevel(logging.INFO)
# Initialize outside handler (reused between invocations)
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('visitor-counter')
CORS_HEADERS = {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': 'https://resume.ivanpetrov.com',
'Access-Control-Allow-Methods': 'GET, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type'
}
def lambda_handler(event, context):
"""Main handler for visitor counter."""
logger.info(f"Event: {json.dumps(event)}")
try:
# Atomic increment using UpdateExpression
response = table.update_item(
Key={'id': 'visitors'},
UpdateExpression='ADD #c :val',
ExpressionAttributeNames={'#c': 'count'},
ExpressionAttributeValues={':val': Decimal('1')},
ReturnValues='UPDATED_NEW'
)
count = int(response['Attributes']['count'])
logger.info(f"Visitor count: {count}")
return {
'statusCode': 200,
'headers': CORS_HEADERS,
'body': json.dumps({'count': count})
}
except Exception as e:
logger.error(f"Error: {e}", exc_info=True)
return {
'statusCode': 500,
'headers': CORS_HEADERS,
'body': json.dumps({'error': 'Service temporarily unavailable'})
}
Зависимости
# requirements.txt
boto3>=1.34.0
boto3 уже включён в Lambda runtime, но фиксация версии в requirements.txt обеспечивает воспроизводимость.
Деплой
# Создаём IAM Role
aws iam create-role \
--role-name lambda-crc-role \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "lambda.amazonaws.com"},
"Action": "sts:AssumeRole"
}]
}'
# Прикрепляем политику
aws iam put-role-policy \
--role-name lambda-crc-role \
--policy-name crc-dynamodb \
--policy-document '{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["dynamodb:UpdateItem", "dynamodb:GetItem"],
"Resource": "arn:aws:dynamodb:us-east-1:*:table/visitor-counter"
},
{
"Effect": "Allow",
"Action": ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"],
"Resource": "arn:aws:logs:us-east-1:*:*"
}
]
}'
# Упаковка и деплой
cd backend && zip -r ../function.zip . && cd ..
aws lambda create-function \
--function-name visitor-counter \
--runtime python3.12 \
--architectures arm64 \
--handler lambda_function.lambda_handler \
--role arn:aws:iam::123456789012:role/lambda-crc-role \
--zip-file fileb://function.zip \
--timeout 10 \
--memory-size 128 \
--environment 'Variables={TABLE_NAME=visitor-counter}'
API Gateway: HTTP API
# Создаём HTTP API
API_ID=$(aws apigatewayv2 create-api \
--name crc-api \
--protocol-type HTTP \
--cors-configuration '{
"AllowOrigins": ["https://resume.ivanpetrov.com"],
"AllowMethods": ["GET", "OPTIONS"],
"MaxAge": 86400
}' --query 'ApiId' --output text)
# Интеграция с Lambda
INT_ID=$(aws apigatewayv2 create-integration \
--api-id $API_ID \
--integration-type AWS_PROXY \
--integration-uri arn:aws:lambda:us-east-1:123456789012:function:visitor-counter \
--payload-format-version "2.0" \
--query 'IntegrationId' --output text)
# Маршрут
aws apigatewayv2 create-route \
--api-id $API_ID \
--route-key "GET /count" \
--target "integrations/$INT_ID"
# Стадия с auto-deploy
aws apigatewayv2 create-stage \
--api-id $API_ID \
--stage-name prod \
--auto-deploy
# Permission для API Gateway вызывать Lambda
aws lambda add-permission \
--function-name visitor-counter \
--statement-id apigateway \
--action lambda:InvokeFunction \
--principal apigateway.amazonaws.com \
--source-arn "arn:aws:execute-api:us-east-1:123456789012:$API_ID/*/GET/count"
echo "API URL: https://$API_ID.execute-api.us-east-1.amazonaws.com/prod/count"
Тестирование
# Тест API
curl https://API_ID.execute-api.us-east-1.amazonaws.com/prod/count
# {"count": 1}
# Повторный вызов
curl https://API_ID.execute-api.us-east-1.amazonaws.com/prod/count
# {"count": 2}
# Проверка CORS
curl -v -H "Origin: https://resume.ivanpetrov.com" \
https://API_ID.execute-api.us-east-1.amazonaws.com/prod/count
Мониторинг
# Просмотр логов Lambda
aws logs tail /aws/lambda/visitor-counter --follow
# Метрики
aws cloudwatch get-metric-statistics \
--namespace AWS/Lambda \
--metric-name Invocations \
--dimensions Name=FunctionName,Value=visitor-counter \
--start-time $(date -u -v-1H +%Y-%m-%dT%H:%M:%S) \
--end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
--period 3600 \
--statistics Sum