Modern QA2026Эфемерные окружения
Join

Course08 Infrastructure as Code Testing

Cutting-edge · Chapter 08

Эфемерные окружения

Updated Jul 2026

Что такое эфемерные окружения

Эфемерные окружения — это одноразовые копии всего вашего инфраструктурного стека, создаваемые для каждого пулл-реквеста и уничтожаемые после мержа. Вместо того чтобы все разработчики разделяли один staging (с его неизбежными моментами «кто сломал staging?»), каждый PR получает собственный изолированный мир.

Разработчик отправляет PR. CI создаёт полное окружение — VPC, базу данных, очереди, контейнеры приложения, API Gateway. Тесты запускаются на этой реальной инфраструктуре по уникальному URL для предпросмотра. Комментарий к PR показывает результаты тестов и ссылку на предпросмотр. Когда PR мержится или закрывается, всё уничтожается.

Архитектура

PR #142 opened
    |
    +-- Terraform workspace: pr-142
    |     +-- VPC (изолированный)
    |     +-- RDS (маленький инстанс, заполненный тестовыми данными)
    |     +-- ECS service (контейнеры приложения)
    |     +-- S3 buckets (с префиксом pr-142-)
    |     +-- API Gateway (pr-142.preview.example.com)
    |
    +-- Запуск полного набора тестов на pr-142.preview.example.com
    |     +-- Модульные тесты (уже пройдены в CI)
    |     +-- Интеграционные тесты (реальная БД, реальные очереди)
    |     +-- E2E-тесты в браузере (Playwright на URL предпросмотра)
    |     +-- Сканирование безопасности (DAST на живых эндпоинтах)
    |
    +-- Комментарий к PR с результатами тестов + URL предпросмотра
    |
PR #142 merged
    |
    +-- terraform workspace select pr-142 && terraform destroy -auto-approve

Terraform Workspaces для эфемерных окружений

Terraform workspaces обеспечивают лёгкую изоляцию. Каждый workspace поддерживает собственный файл состояния, поэтому ресурсы, созданные в одном workspace, не влияют на другой.

Конфигурация с учётом workspace

# main.tf -- workspace-aware naming
locals {
  env_name = terraform.workspace == "default" ? "production" : terraform.workspace
  prefix   = "myapp-${local.env_name}"

  # Use smaller instances for PR environments
  is_ephemeral = terraform.workspace != "default"
}

resource "aws_s3_bucket" "data" {
  bucket = "${local.prefix}-data"
  # Each PR gets its own bucket: myapp-pr-142-data
}

resource "aws_db_instance" "main" {
  identifier     = "${local.prefix}-db"
  instance_class = local.is_ephemeral ? "db.t4g.micro" : "db.r6g.xlarge"
  allocated_storage = local.is_ephemeral ? 20 : 500
  # PR environments use minimal instance sizes to control cost

  # Skip final snapshot for ephemeral environments
  skip_final_snapshot = local.is_ephemeral

  # Use a smaller backup retention for ephemeral
  backup_retention_period = local.is_ephemeral ? 0 : 7
}

resource "aws_ecs_service" "app" {
  name            = "${local.prefix}-app"
  desired_count   = local.is_ephemeral ? 1 : 3
  # PR environments need only one replica
}

# DNS entry for the preview URL
resource "aws_route53_record" "preview" {
  count   = local.is_ephemeral ? 1 : 0
  zone_id = data.aws_route53_zone.preview.zone_id
  name    = "${local.env_name}.preview.example.com"
  type    = "CNAME"
  records = [aws_lb.app.dns_name]
  ttl     = 60
}

output "preview_url" {
  value = local.is_ephemeral ? "https://${local.env_name}.preview.example.com" : ""
}

CI-пайплайн для эфемерных окружений

#!/bin/bash
# scripts/ephemeral-env.sh
set -euo pipefail

ACTION="$1"  # "create" or "destroy"
PR_NUM=$(echo "$GITHUB_REF" | grep -oP '\d+')
WORKSPACE="pr-${PR_NUM}"

case "$ACTION" in
  create)
    cd terraform/
    terraform workspace new "$WORKSPACE" 2>/dev/null || \
      terraform workspace select "$WORKSPACE"

    terraform apply -auto-approve \
      -var="pr_number=${PR_NUM}" \
      -var="git_sha=${GITHUB_SHA:0:8}"

    PREVIEW_URL=$(terraform output -raw preview_url)
    echo "Preview environment ready: $PREVIEW_URL"

    # Wait for health check
    for i in $(seq 1 30); do
      if curl -sf "$PREVIEW_URL/healthz" > /dev/null; then
        echo "Environment is healthy"
        break
      fi
      echo "Waiting for environment to be ready... ($i/30)"
      sleep 10
    done

    # Run tests
    cd ..
    npx playwright test --base-url "$PREVIEW_URL"
    TEST_RESULT=$?

    # Post results to PR
    if [ $TEST_RESULT -eq 0 ]; then
      gh pr comment "$PR_NUM" --body \
        "Preview: $PREVIEW_URL | Tests: PASSED"
    else
      gh pr comment "$PR_NUM" --body \
        "Preview: $PREVIEW_URL | Tests: FAILED (see CI logs)"
    fi
    ;;

  destroy)
    cd terraform/
    terraform workspace select "$WORKSPACE"
    terraform destroy -auto-approve
    terraform workspace select default
    terraform workspace delete "$WORKSPACE"
    echo "Environment pr-${PR_NUM} destroyed"
    ;;
esac

Pulumi Stacks для эфемерных окружений

Pulumi stacks — это аналог Terraform workspaces, но с программным управлением:

// index.ts -- Pulumi stack per PR
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const stack = pulumi.getStack(); // e.g., "pr-142"
const isEphemeral = stack.startsWith("pr-");

const db = new aws.rds.Instance("main", {
    instanceClass: isEphemeral ? "db.t4g.micro" : "db.r6g.xlarge",
    allocatedStorage: isEphemeral ? 20 : 500,
    skipFinalSnapshot: isEphemeral, // No snapshot needed for PR envs
    identifier: `myapp-${stack}-db`,
    engine: "postgres",
    engineVersion: "16",
});

const service = new aws.ecs.Service("app", {
    desiredCount: isEphemeral ? 1 : 3,
    // ... other configuration
});

export const dbEndpoint = db.endpoint;
export const previewUrl = isEphemeral
    ? pulumi.interpolate`https://${stack}.preview.example.com`
    : undefined;

Стратегии контроля затрат

Эфемерные окружения могут быть дорогими без надлежащего управления. Одно PR-окружение с RDS-инстансом, ECS-сервисом и балансировщиком нагрузки обходится примерно в $5-15 в день. При 20 активных PR это $100-300 в день.

Стратегия Реализация Экономия
Автоматическое уничтожение через N часов Cron-задача GitHub Action + terraform destroy 60-80%
Минимальные размеры инстансов Условный размер на основе имени workspace 70-90%
Общие ресурсы только для чтения Ссылка на продакшен VPC, DNS-зону через data sources 20-30%
Spot-инстансы для вычислений capacity_type = "SPOT" для узлов EKS 60-70%
Масштабирование до нуля по расписанию Lambda, масштабирующая PR-окружения вне рабочих часов 50-60%
TTL-теги на всех ресурсах Автоматическая очистка ресурсов старше 48 часов Предотвращает зомби-ресурсы

Cron-задача автоматического уничтожения

# .github/workflows/cleanup-ephemeral.yml
name: Cleanup Stale Ephemeral Environments
on:
  schedule:
    - cron: '0 2 * * *'  # Daily at 2 AM UTC
  workflow_dispatch:

jobs:
  cleanup:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Find stale environments
        run: |
          cd terraform/
          terraform workspace list | grep "pr-" | while read ws; do
            WS_NAME=$(echo "$ws" | tr -d ' *')
            PR_NUM=$(echo "$WS_NAME" | grep -oP '\d+')

            # Check if the PR is still open
            PR_STATE=$(gh pr view "$PR_NUM" --json state -q '.state' 2>/dev/null || echo "UNKNOWN")

            if [ "$PR_STATE" != "OPEN" ]; then
              echo "Destroying stale environment: $WS_NAME (PR state: $PR_STATE)"
              terraform workspace select "$WS_NAME"
              terraform destroy -auto-approve
              terraform workspace select default
              terraform workspace delete "$WS_NAME"
            fi
          done

TTL-теги для ресурсов

# Add TTL tags to all ephemeral resources
locals {
  common_tags = merge(
    {
      Team        = "platform"
      Environment = local.env_name
      ManagedBy   = "terraform"
    },
    local.is_ephemeral ? {
      EphemeralTTL = timeadd(timestamp(), "48h")
      PRNumber     = var.pr_number
    } : {}
  )
}

Заполнение базы данных для эфемерных окружений

Эфемерные окружения нуждаются в данных, чтобы быть полезными. Варианты от самого быстрого до самого реалистичного:

Подход Скорость Реалистичность Лучше всего для
Только пустая схема Секунды Низкая API-тестирование с созданием тестовых данных
Фикстурные данные (SQL-скрипты) Секунды Средняя Предсказуемые тестовые сценарии
Анонимизированный снапшот продакшена Минуты Высокая Реалистичное тестирование, демо-окружения
Реплика продакшена для чтения Быстро (без копирования) Наивысшая Тестирование только для чтения на реальных данных
# Seed ephemeral database from fixture files
psql "$DATABASE_URL" < fixtures/schema.sql
psql "$DATABASE_URL" < fixtures/test-data.sql

Эфемерные окружения трансформируют уверенность в тестировании, поскольку устраняют проблему «работает на staging». Когда каждый PR получает собственную инфраструктуру, вы точно знаете, что изменилось и работает ли это.