TL;DR

  • LocalStack эмулирует 80+ AWS сервисов локально — тестируйте S3, Lambda, DynamoDB без облачных затрат
  • Используйте LocalStack для быстрой итерации и CI; используйте реальный AWS для интеграционных тестов перед продакшном
  • Ошибка #1: относиться к LocalStack как к эквиваленту продакшна (это для тестирования, не 100% паритет)

Подходит для: Команд с AWS инфраструктурой которые хотят более быстрые циклы обратной связи и меньшие затраты на CI Пропустите, если: Вам нужны точные гарантии поведения AWS или вы используете сервисы которые LocalStack не поддерживает Время чтения: 10 минут

Ваши Terraform тесты против реального AWS занимают 8 минут за запуск. Счета за CI растут. Разработчики ждут пока облачные ресурсы провизионируются прежде чем валидировать свои изменения. Тем временем, ошибки конфигурации S3 bucket попадают в продакшн потому что тестирование казалось “слишком медленным.”

LocalStack меняет это уравнение. Он предоставляет локальный AWS cloud stack который работает в Docker, поддерживая 80+ сервисов. Ваши тесты запускаются за секунды, не минуты. Затраты на CI падают. Циклы обратной связи ускоряются. Но вам нужно понимать что LocalStack делает и чего не гарантирует.

Настоящая Проблема

Тестирование против реального AWS имеет затраты:

Время: Провизионирование RDS instance занимает минуты. Создание VPC с subnets, NAT gateways и routes — больше минут. Разработчики избегают тестирования потому что это медленно.

Деньги: CI запускающий Terraform applies против реального AWS накапливает затраты. Забытые тестовые ресурсы работают всю ночь. Атрибуция затрат размыта.

Flakiness: Сетевые проблемы, rate limits и eventual consistency вызывают периодические сбои. Тесты проходящие локально падают в CI.

Конфликты окружений: Несколько разработчиков или CI jobs конкурирующих за один AWS аккаунт создают конфликты именования ресурсов и повреждение состояния.

LocalStack решает это предоставляя локальное, изолированное AWS окружение которое провизионируется мгновенно и ничего не стоит.

Настройка LocalStack

LocalStack работает как Docker контейнер. Базовая настройка:

# Запуск LocalStack
docker run -d \
  --name localstack \
  -p 4566:4566 \
  -e SERVICES=s3,dynamodb,lambda,sqs,sns,iam \
  -e DEBUG=1 \
  -v /var/run/docker.sock:/var/run/docker.sock \
  localstack/localstack:latest

# Проверка что работает
curl http://localhost:4566/_localstack/health

Для docker-compose (рекомендуется для проектов):

# docker-compose.yml
version: '3.8'

services:
  localstack:
    image: localstack/localstack:latest
    ports:

      - "4566:4566"
    environment:

      - SERVICES=s3,dynamodb,lambda,sqs,sns,iam,secretsmanager
      - DEBUG=1
      - DOCKER_HOST=unix:///var/run/docker.sock
    volumes:

      - "/var/run/docker.sock:/var/run/docker.sock"
      - "./localstack-data:/var/lib/localstack"

Запуск с docker-compose up -d.

Terraform с LocalStack

Настройте Terraform для использования endpoints LocalStack:

# providers.tf
provider "aws" {
  access_key                  = "test"
  secret_key                  = "test"
  region                      = "us-east-1"

  s3_use_path_style           = true
  skip_credentials_validation = true
  skip_metadata_api_check     = true
  skip_requesting_account_id  = true

  endpoints {
    s3             = "http://localhost:4566"
    dynamodb       = "http://localhost:4566"
    lambda         = "http://localhost:4566"
    iam            = "http://localhost:4566"
    sqs            = "http://localhost:4566"
    sns            = "http://localhost:4566"
    secretsmanager = "http://localhost:4566"
  }
}

Лучший подход — используйте environment-специфичные providers:

# providers.tf
locals {
  is_localstack = var.environment == "localstack"
}

provider "aws" {
  region = var.aws_region

  access_key = local.is_localstack ? "test" : null
  secret_key = local.is_localstack ? "test" : null

  s3_use_path_style           = local.is_localstack
  skip_credentials_validation = local.is_localstack
  skip_metadata_api_check     = local.is_localstack
  skip_requesting_account_id  = local.is_localstack

  dynamic "endpoints" {
    for_each = local.is_localstack ? [1] : []
    content {
      s3             = "http://localhost:4566"
      dynamodb       = "http://localhost:4566"
      lambda         = "http://localhost:4566"
      iam            = "http://localhost:4566"
      sqs            = "http://localhost:4566"
      sns            = "http://localhost:4566"
      secretsmanager = "http://localhost:4566"
    }
  }
}

Terratest с LocalStack

Terratest может работать с LocalStack:

package test

import (
    "testing"
    "os"

    "github.com/gruntwork-io/terratest/modules/aws"
    "github.com/gruntwork-io/terratest/modules/terraform"
    "github.com/stretchr/testify/assert"
)

func TestS3BucketWithLocalStack(t *testing.T) {
    t.Parallel()

    // Override AWS SDK для использования LocalStack
    os.Setenv("AWS_ACCESS_KEY_ID", "test")
    os.Setenv("AWS_SECRET_ACCESS_KEY", "test")
    os.Setenv("AWS_DEFAULT_REGION", "us-east-1")

    terraformOptions := &terraform.Options{
        TerraformDir: "../modules/s3-bucket",
        Vars: map[string]interface{}{
            "environment":  "localstack",
            "bucket_name":  "test-bucket-" + random.UniqueId(),
        },
    }

    defer terraform.Destroy(t, terraformOptions)
    terraform.InitAndApply(t, terraformOptions)

    // Проверка существования bucket
    bucketName := terraform.Output(t, terraformOptions, "bucket_name")

    // Использование custom endpoint для LocalStack
    awsConfig := aws.NewConfig(
        aws.WithEndpointResolver(
            aws.EndpointResolverFunc(func(service, region string) (aws.Endpoint, error) {
                return aws.Endpoint{URL: "http://localhost:4566"}, nil
            }),
        ),
    )

    // Assert свойств bucket
    assert.True(t, aws.AssertS3BucketExists(t, "us-east-1", bucketName))
}

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

Для Python приложений, moto предоставляет AWS mocking:

import boto3
import pytest
from moto import mock_aws

@mock_aws
def test_s3_bucket_creation():
    # Создание mock S3
    s3 = boto3.client('s3', region_name='us-east-1')

    # Создание bucket
    s3.create_bucket(Bucket='test-bucket')

    # Проверка
    response = s3.list_buckets()
    bucket_names = [b['Name'] for b in response['Buckets']]
    assert 'test-bucket' in bucket_names

@mock_aws
def test_dynamodb_table():
    # Создание mock DynamoDB
    dynamodb = boto3.resource('dynamodb', region_name='us-east-1')

    # Создание таблицы
    table = dynamodb.create_table(
        TableName='test-table',
        KeySchema=[{'AttributeName': 'id', 'KeyType': 'HASH'}],
        AttributeDefinitions=[{'AttributeName': 'id', 'AttributeType': 'S'}],
        BillingMode='PAY_PER_REQUEST'
    )

    # Запись и чтение
    table.put_item(Item={'id': '123', 'data': 'test'})
    response = table.get_item(Key={'id': '123'})

    assert response['Item']['data'] == 'test'

@mock_aws
def test_lambda_invocation():
    # Создание mock Lambda
    lambda_client = boto3.client('lambda', region_name='us-east-1')
    iam = boto3.client('iam', region_name='us-east-1')

    # Создание role (требуется для Lambda)
    iam.create_role(
        RoleName='test-role',
        AssumeRolePolicyDocument='{}',
    )

    # Создание функции
    lambda_client.create_function(
        FunctionName='test-function',
        Runtime='python3.9',
        Role='arn:aws:iam::123456789:role/test-role',
        Handler='handler.main',
        Code={'ZipFile': b'fake code'},
    )

    # Проверка существования функции
    response = lambda_client.list_functions()
    function_names = [f['FunctionName'] for f in response['Functions']]
    assert 'test-function' in function_names

Интеграция CI/CD

GitHub Actions с LocalStack:

name: Infrastructure Tests

on:
  pull_request:
    paths:

      - 'terraform/**'
      - 'tests/**'

jobs:
  localstack-tests:
    runs-on: ubuntu-latest
    services:
      localstack:
        image: localstack/localstack:latest
        ports:

          - 4566:4566
        env:
          SERVICES: s3,dynamodb,lambda,sqs,sns,iam
          DEBUG: 1

    steps:

      - uses: actions/checkout@v4

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: 1.7.0

      - name: Wait for LocalStack
        run: |
          timeout 60 bash -c 'until curl -s http://localhost:4566/_localstack/health | grep -q "running"; do sleep 2; done'

      - name: Run Terraform Tests
        run: |
          cd tests
          go test -v -timeout 10m ./...
        env:
          AWS_ACCESS_KEY_ID: test
          AWS_SECRET_ACCESS_KEY: test
          AWS_DEFAULT_REGION: us-east-1
          LOCALSTACK_ENDPOINT: http://localhost:4566

  # Реальные AWS тесты только на main ветке
  aws-integration:
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:

      - uses: actions/checkout@v4
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789:role/ci-role
          aws-region: us-east-1
      - name: Run Integration Tests
        run: go test -v -tags=integration ./tests/integration/...

LocalStack Pro vs Community

LocalStack Pro добавляет сервисы не включённые в бесплатный tier:

FeatureCommunityPro
S3, DynamoDB, SQS, SNS, LambdaДаДа
IAM (полный)ЧастичноДа
RDS, AuroraНетДа
EKS, ECSНетДа
CloudFormation (полный)ЧастичноДа
PersistenceБазоваяПолная
Cloud Pods (snapshots)НетДа

Для большинства Terraform тестирования Community edition покрывает распространённые сервисы. Pro стоит того если вы активно используете RDS, EKS или нуждаетесь в persistence CI.

Что LocalStack Не Покрывает

LocalStack — это не AWS. Ключевые различия:

Оценка IAM: IAM LocalStack упрощён. Policies падающие в реальном AWS могут проходить в LocalStack.

Eventual consistency: S3 в LocalStack немедленно consistent. Реальный S3 имеет eventual consistency для некоторых операций.

Лимиты сервисов: LocalStack не enforce’ит квоты AWS сервисов. Ваш тест может пройти но упасть в продакшне из-за лимитов.

Networking: VPCs, subnets, security groups работают по-другому. Network ACLs и сложный routing не полностью эмулируются.

Характеристики производительности: LocalStack не симулирует AWS latency, throttling или cold starts точно.

Стратегия Тестирования: Многослойный Подход

Используйте LocalStack как один слой в полной стратегии тестирования:

┌─────────────────────────────────────────────┐
│  Продакшн (мониторинг, canary deployments)   │
├─────────────────────────────────────────────┤
│  Staging (реальный AWS, pre-production тесты)│
├─────────────────────────────────────────────┤
│  Интеграция (реальный AWS, CI на main ветке) │
├─────────────────────────────────────────────┤
│  LocalStack (быстрый feedback, все PRs)      │
├─────────────────────────────────────────────┤
│  Unit тесты (без инфраструктуры)             │
└─────────────────────────────────────────────┘

Запускайте LocalStack тесты на каждом PR. Запускайте реальные AWS тесты при merge в main. Деплойте в staging для финальной валидации.

AI-Ассистированные Подходы

Конфигурация и mocking LocalStack могут быть сложными. AI инструменты помогают.

Что AI делает хорошо:

  • Генерация конфигураций endpoint LocalStack для Terraform providers
  • Конвертация реального AWS test code в LocalStack-совместимые версии
  • Создание moto fixtures для Python тестов
  • Troubleshooting проблем совместимости сервисов LocalStack

Что всё ещё требует людей:

  • Решения какие тесты нуждаются в реальном AWS vs LocalStack
  • Понимание какие ограничения LocalStack влияют на ваш use case
  • Проектирование архитектуры тестов через пирамиду тестирования
  • Валидация что LocalStack тесты реально ловят проблемы

Полезный промпт:

У меня есть этот Terraform модуль который создаёт:

- S3 bucket с versioning и encryption
- DynamoDB таблицу с GSI
- Lambda функцию триггерящуюся событиями S3

Сгенерируй:

1. docker-compose.yml для LocalStack с нужными сервисами
2. Конфигурацию Terraform provider для LocalStack
3. Terratest Go код для валидации setup
4. Список ограничений о которых я должен знать

Когда Это Не Работает

LocalStack тестирование имеет ограничения:

Gaps сервисов: Если вы используете AppSync, Neptune или другие менее распространённые сервисы, LocalStack может их не поддерживать.

Различия поведения: Тесты проходят локально но падают в AWS. Это происходит когда эмуляция LocalStack отличается от поведения AWS.

Сложность состояния: Долго работающие LocalStack instances накапливают состояние. Тесты становятся зависимыми от порядка.

Docker overhead: На CI runners с ограниченными ресурсами LocalStack может медленно стартовать или потреблять слишком много памяти.

Рассмотрите дополнительные подходы:

Фреймворк Принятия Решений

Используйте LocalStack когда:

  • Быстрая итерация во время разработки
  • Затраты CI — проблема
  • Тестирование распространённых сервисов (S3, DynamoDB, Lambda, SQS)
  • Требуется сетевая изоляция (offline тестирование)

Используйте реальный AWS когда:

  • Тестирование IAM policies и permissions
  • Валидация сетевых конфигураций
  • Использование сервисов которые LocalStack не поддерживает
  • Финальное интеграционное тестирование перед продакшном

Используйте moto когда:

  • Unit тестирование Python кода с AWS SDK вызовами
  • Скорость критична (moto быстрее LocalStack)
  • Вам не нужно Terraform/инфраструктурное тестирование

Измерение Успеха

МетрикаДоПослеКак Отслеживать
Время выполнения теста8+ минут<2 минутМетрики CI
Месячные AWS CI затраты$500+<$100AWS Cost Explorer
Тесты пропущены как “слишком медленные”Много0Отчёты покрытия
Сбои LocalStack vs AWSN/A<5%Сравнение результатов тестов

Тревожные признаки что не работает:

  • Тесты проходят в LocalStack но падают в реальном AWS
  • LocalStack становится узким местом в CI
  • Разработчики обходят тесты потому что LocalStack “достаточно хорош”
  • Gaps сервисов вынуждают слишком много реальных AWS тестов

Что Дальше

Начните с ваших наиболее тестируемых сервисов:

  1. Определите какие AWS сервисы используют ваши Terraform модули
  2. Проверьте совместимость LocalStack для этих сервисов
  3. Настройте docker-compose для локальной разработки
  4. Конвертируйте один test suite для использования LocalStack
  5. Измерьте улучшение скорости и итерируйте
  6. Добавьте реальные AWS тесты как интеграционный слой

Цель — более быстрая обратная связь, не замена всего AWS тестирования. LocalStack — инструмент для скорости; реальный AWS — источник истины.


Связанные статьи:

Внешние ресурсы:

Официальные ресурсы

See Also