HardПрактика2 min

Azure Functions и Cosmos DB

Azure Functions с HTTP trigger и Cosmos DB serverless для счётчика посещений CRC

Архитектура бэкенда CRC на Azure

Браузер → Azure Functions (HTTP trigger) → Cosmos DB (Serverless)
                    ↓
            Application Insights (мониторинг)

Cosmos DB Serverless

# Cosmos DB аккаунт (serverless)
az cosmosdb create \
  --name crc-cosmos-db \
  --resource-group crc-resources \
  --capabilities EnableServerless \
  --default-consistency-level Session

# База данных
az cosmosdb sql database create \
  --account-name crc-cosmos-db \
  --resource-group crc-resources \
  --name crc-database

# Контейнер
az cosmosdb sql container create \
  --account-name crc-cosmos-db \
  --resource-group crc-resources \
  --database-name crc-database \
  --name counters \
  --partition-key-path "/id"

# Начальные данные (через Azure Portal или SDK)

Azure Functions

Проект

# Инициализация проекта
func init crc-backend --python
cd crc-backend
func new --name count --template "HTTP trigger" --authlevel anonymous

Код функции (v2 programming model)

# function_app.py
import azure.functions as func
import json
import os
import logging
from azure.cosmos import CosmosClient, exceptions

app = func.FunctionApp()

# Initialize Cosmos client (outside function for reuse)
cosmos_client = CosmosClient.from_connection_string(
    os.environ['COSMOS_CONNECTION_STRING']
)
database = cosmos_client.get_database_client('crc-database')
container = database.get_container_client('counters')

@app.route(route="count", methods=["GET"], auth_level=func.AuthLevel.ANONYMOUS)
def visitor_counter(req: func.HttpRequest) -> func.HttpResponse:
    """HTTP trigger for visitor counter."""
    logging.info('Visitor counter triggered')

    try:
        # Read or create counter
        try:
            item = container.read_item(
                item='visitor-count',
                partition_key='visitor-count'
            )
            item['count'] += 1
        except exceptions.CosmosResourceNotFoundError:
            item = {'id': 'visitor-count', 'count': 1}

        # Upsert (create or update)
        container.upsert_item(item)

        logging.info(f"Count updated to {item['count']}")

        return func.HttpResponse(
            json.dumps({'count': item['count']}),
            mimetype='application/json',
            headers={
                'Access-Control-Allow-Origin': 'https://resume.ivanpetrov.com',
                'Access-Control-Allow-Methods': 'GET'
            }
        )

    except Exception as e:
        logging.error(f'Error: {e}')
        return func.HttpResponse(
            json.dumps({'error': 'Service unavailable'}),
            status_code=503,
            mimetype='application/json'
        )

host.json

{
  "version": "2.0",
  "extensions": {
    "http": {
      "routePrefix": "api"
    }
  },
  "logging": {
    "applicationInsights": {
      "samplingSettings": {
        "isEnabled": true,
        "excludedTypes": "Request"
      }
    }
  }
}

requirements.txt

azure-functions
azure-cosmos>=4.5.0

Деплой

# Создаём Storage Account для Functions
az storage account create \
  --name crcfuncstorage \
  --resource-group crc-resources \
  --location westeurope \
  --sku Standard_LRS

# Создаём 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 \
  --functions-version 4

# Устанавливаем connection string
COSMOS_CONN=$(az cosmosdb keys list \
  --name crc-cosmos-db \
  --resource-group crc-resources \
  --type connection-strings \
  --query 'connectionStrings[0].connectionString' --output tsv)

az functionapp config appsettings set \
  --name crc-visitor-counter \
  --resource-group crc-resources \
  --settings "COSMOS_CONNECTION_STRING=$COSMOS_CONN"

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

CORS

# Настройка CORS на Function App
az functionapp cors add \
  --name crc-visitor-counter \
  --resource-group crc-resources \
  --allowed-origins "https://resume.ivanpetrov.com"

Тестирование

# Тест
curl https://crc-visitor-counter.azurewebsites.net/api/count
# {"count": 1}

# С CORS
curl -H "Origin: https://resume.ivanpetrov.com" \
  https://crc-visitor-counter.azurewebsites.net/api/count

Проверь себя

Какой auth_level рекомендуется для публичного API CRC?

Что делает container.upsert_item() в Cosmos DB?

Что такое Consumption Plan в Azure Functions?

Зачем Cosmos DB connection string хранится в App Settings, а не в коде?

Почему для CRC рекомендуется Cosmos DB Serverless, а не Provisioned?