MidПрактика3 min

Serverless-функции

Lambda, Azure Functions и Cloud Functions: сравнение, триггеры, cold start и модели ценообразования

Что такое serverless

Serverless -- это модель выполнения кода, где облачный провайдер полностью управляет серверной инфраструктурой. Вы пишете функцию, загружаете её, и провайдер сам решает, на каком сервере её запустить, как масштабировать и когда выключить.

Ключевые характеристики

  • Нет управления серверами -- нет патчей, обновлений, мониторинга инфраструктуры
  • Автоматическое масштабирование -- от 0 до тысяч экземпляров
  • Оплата за использование -- платите только за время выполнения кода
  • Event-driven -- функции запускаются в ответ на события (HTTP-запрос, сообщение в очереди, изменение в БД)

AWS Lambda

Создание функции для CRC

# lambda_function.py
import json
import boto3
from decimal import Decimal

dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('visitor-counter')

def lambda_handler(event, context):
    """AWS Lambda handler for visitor counter."""

    # Atomic increment
    response = table.update_item(
        Key={'id': 'visitors'},
        UpdateExpression='ADD #count :inc',
        ExpressionAttributeNames={'#count': 'count'},
        ExpressionAttributeValues={':inc': Decimal('1')},
        ReturnValues='UPDATED_NEW'
    )

    count = int(response['Attributes']['count'])

    return {
        'statusCode': 200,
        'headers': {
            'Content-Type': 'application/json',
            'Access-Control-Allow-Origin': '*'
        },
        'body': json.dumps({'count': count})
    }

Деплой Lambda

# Упаковка кода
zip function.zip lambda_function.py

# Создание функции
aws lambda create-function \
  --function-name visitor-counter \
  --runtime python3.12 \
  --handler lambda_function.lambda_handler \
  --zip-file fileb://function.zip \
  --role arn:aws:iam::123456789012:role/lambda-crc-role \
  --timeout 10 \
  --memory-size 128

# Обновление кода
aws lambda update-function-code \
  --function-name visitor-counter \
  --zip-file fileb://function.zip

Конфигурация Lambda

Параметр Значение для CRC Описание
Runtime python3.12 Версия Python
Memory 128 MB Минимум для CRC
Timeout 10 секунд Максимальное время выполнения
Architecture arm64 Дешевле на 20%
Reserved concurrency 10 Защита от DDoS

Azure Functions

Создание функции

# function_app.py (Azure Functions v2 programming model)
import azure.functions as func
import json
import os
from azure.cosmos import CosmosClient

app = func.FunctionApp()

@app.route(route="count", methods=["GET"])
def visitor_counter(req: func.HttpRequest) -> func.HttpResponse:
    """Azure Function HTTP trigger for visitor counter."""

    connection = os.environ['COSMOS_CONNECTION']
    client = CosmosClient.from_connection_string(connection)
    database = client.get_database_client('crc-db')
    container = database.get_container_client('counters')

    try:
        item = container.read_item('visitor-count', partition_key='visitor-count')
        item['count'] += 1
        container.upsert_item(item)
    except Exception:
        item = {'id': 'visitor-count', 'count': 1}
        container.create_item(item)

    return func.HttpResponse(
        json.dumps({'count': item['count']}),
        mimetype='application/json',
        headers={'Access-Control-Allow-Origin': '*'}
    )

Деплой Azure Functions

# Создание Function App
az functionapp create \
  --resource-group crc-resources \
  --name crc-visitor-counter \
  --consumption-plan-location westeurope \
  --runtime python \
  --runtime-version 3.11 \
  --os-type Linux \
  --storage-account crcfuncstorage

# Деплой кода
func azure functionapp publish crc-visitor-counter

Google Cloud Functions

Создание функции

# main.py
import functions_framework
from google.cloud import firestore
import json

db = firestore.Client()

@functions_framework.http
def visitor_counter(request):
    """Cloud Function for visitor counter."""
    if request.method == 'OPTIONS':
        return ('', 204, {
            'Access-Control-Allow-Origin': '*',
            'Access-Control-Allow-Methods': 'GET',
            'Access-Control-Max-Age': '3600'
        })

    counter_ref = db.collection('counters').document('visitors')

    @firestore.transactional
    def increment(transaction):
        doc = counter_ref.get(transaction=transaction)
        new_count = (doc.get('count') + 1) if doc.exists else 1
        transaction.set(counter_ref, {'count': new_count})
        return new_count

    count = increment(db.transaction())

    return (
        json.dumps({'count': count}),
        200,
        {
            'Content-Type': 'application/json',
            'Access-Control-Allow-Origin': '*'
        }
    )

Деплой Cloud Functions

# Деплой (2nd gen -- рекомендуется)
gcloud functions deploy visitor-counter \
  --gen2 \
  --runtime=python312 \
  --trigger-http \
  --allow-unauthenticated \
  --entry-point=visitor_counter \
  --source=. \
  --region=us-central1 \
  --memory=128Mi \
  --max-instances=10

Cold Start

Что такое Cold Start

Cold Start -- задержка при первом вызове функции после периода простоя. Провайдеру нужно время на:

  1. Выделение контейнера
  2. Загрузку runtime
  3. Загрузку вашего кода
  4. Инициализацию зависимостей

Сравнение Cold Start

Провайдер Cold Start (Python) Warm Start
AWS Lambda 200-500 мс 1-5 мс
Azure Functions 500-2000 мс 1-10 мс
GCP Cloud Functions 300-800 мс 1-5 мс

Как уменьшить Cold Start

  1. Минимизируйте пакет -- только нужные зависимости
  2. Используйте arm64 (Lambda) -- быстрее и дешевле
  3. Инициализация вне handler -- подключение к БД в глобальной области
  4. Provisioned Concurrency (Lambda) -- держит функции "тёплыми" (платно)
  5. Минимальный memory -- 128 MB достаточно для CRC
# ПРАВИЛЬНО: инициализация вне handler (переиспользуется между вызовами)
import boto3

dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('visitor-counter')

def lambda_handler(event, context):
    # table уже инициализирована
    response = table.update_item(...)
    return response

# НЕПРАВИЛЬНО: инициализация внутри handler (каждый вызов заново)
def lambda_handler(event, context):
    dynamodb = boto3.resource('dynamodb')  # Медленно!
    table = dynamodb.Table('visitor-counter')  # Каждый раз!
    ...

Триггеры

Serverless-функции запускаются событиями (triggers):

Триггер AWS Lambda Azure Functions Cloud Functions
HTTP API Gateway / Function URL Built-in HTTP Built-in HTTP
Очередь SQS Queue Storage Pub/Sub
Хранилище S3 Events Blob Trigger GCS Events
БД DynamoDB Streams Cosmos DB Trigger Firestore Trigger
Расписание EventBridge Timer Trigger Cloud Scheduler

Для CRC используется HTTP trigger -- функция вызывается при HTTP GET-запросе.

Ценообразование

AWS Lambda

Бесплатно: 1M запросов + 400,000 GB-секунд / месяц

Платно:
- $0.20 за 1M запросов
- $0.0000166667 за GB-секунду

CRC: ~10,000 запросов/мес * 200 мс * 128 MB
= 0.025 GB-секунд = $0.00 (Free Tier)

Azure Functions (Consumption Plan)

Бесплатно: 1M запросов + 400,000 GB-секунд / месяц

Платно:
- $0.20 за 1M запросов
- $0.000016 за GB-секунду

CRC: те же расчёты = $0.00 (Free Tier)

GCP Cloud Functions

Бесплатно: 2M вызовов + 400,000 GB-секунд + 200,000 GHz-секунд

Платно:
- $0.40 за 1M вызовов
- $0.0000025 за GB-секунду

CRC: те же расчёты = $0.00 (Free Tier)

Для CRC стоимость serverless-функций на всех трёх провайдерах -- $0.00 в пределах Free Tier.

Проверь себя

Сколько стоит serverless-функция CRC в месяц при ~10,000 запросов?

Почему инициализацию boto3 (подключение к DynamoDB) нужно делать ВНЕ handler-функции?

Какой runtime рекомендуется для Lambda в CRC с точки зрения стоимости?

Какой тип триггера используется для функции-счётчика посещений в CRC?

Что такое Cold Start в контексте serverless-функций?