linuxlab.io
Учебники▾
  • Линукс и сети
    Файловая система, процессы, TCP/IP, BGP и OSPF
    →
  • Terraform и IaC
    HCL, state, plan/apply на sandbox LocalStack
    →
  • Git и GitHub
    Объектная модель, plumbing, ветвление, GitHub Actions
    →
Все учебники →
ЦеныО платформеВойтиСоздать аккаунт
/
Intro
Lessons
Footer
linuxlab-УчебникиЦеныО платформеКонфиденциальность и куки
Copyright © 2026 LinuxLab. Все права защищены.
linuxlab.io
Учебники▾
  • Линукс и сети
    Файловая система, процессы, TCP/IP, BGP и OSPF
    →
  • Terraform и IaC
    HCL, state, plan/apply на sandbox LocalStack
    →
  • Git и GitHub
    Объектная модель, plumbing, ветвление, GitHub Actions
    →
Все учебники →
ЦеныО платформеВойтиСоздать аккаунт
/
  • Введение
  • Уроки
  • How it works
  • База знаний
  • Шпаргалка
  • Capstone
  • Собеседование
home/terraform/lessons/tf-advanced-08-capstone

lesson ── terraform-advanced ── ~35 мин ── 7 шагов

Capstone, VPC + ALB + ECS Fargate + Lambda

Финальный capstone. Используешь всё, что научился: модули (intermediate), testing (P2), policy-gate (P2), terraform_remote_state для разделения stack'ов (advanced). Архитектура, типичный mini-platform: VPC, ALB, ECS Fargate-сервис, Lambda-функция. Никакого EKS, LocalStack Community его не поддерживает; ECS на Fargate, близкий аналог.

35 минут, это много. Если режется по времени, делай первые 4 шага (network + compute) и оставь serverless+tests на повторное прохождение.

▶ интерактивный sandbox

Поднимется пара контейнеров: terraform 1.9 и localstack 3.8 в одной сети. В браузере откроется терминал, можно сразу terraform init. Каждый шаг проверяется автоматически. TTL 45 минут, без регистрации.

запустить sandbox →

stack ── terraform · localstack · 1 GB RAM · самоуничтожается через 45 мин простоя

Шаги

  1. 01

    Шаг 1: Network stack (VPC + subnets + IGW)

    bash
    cd /home/student/capstone/modules/network
    cat > main.tf <<'EOF'
    resource "aws_vpc" "main" {
      cidr_block           = "10.0.0.0/16"
      enable_dns_hostnames = true
      enable_dns_support   = true
      tags = { Name = "capstone-vpc", Stack = "network" }
    }
    resource "aws_subnet" "public" {
      count                   = 2
      vpc_id                  = aws_vpc.main.id
      cidr_block              = cidrsubnet(aws_vpc.main.cidr_block, 8, count.index)
      availability_zone       = "us-east-1${["a", "b"][count.index]}"
      map_public_ip_on_launch = true
      tags = { Name = "public-${count.index}", Tier = "public" }
    }
    resource "aws_subnet" "private" {
      count             = 2
      vpc_id            = aws_vpc.main.id
      cidr_block        = cidrsubnet(aws_vpc.main.cidr_block, 8, count.index + 10)
      availability_zone = "us-east-1${["a", "b"][count.index]}"
      tags = { Name = "private-${count.index}", Tier = "private" }
    }
    resource "aws_internet_gateway" "main" {
      vpc_id = aws_vpc.main.id
      tags   = { Name = "capstone-igw" }
    }
    output "vpc_id" {
      value = aws_vpc.main.id
    }
    output "public_subnet_ids" {
      value = aws_subnet.public[*].id
    }
    output "private_subnet_ids" {
      value = aws_subnet.private[*].id
    }
    EOF
    cp ../../provider.tf .
    terraform init -no-color > /dev/null
    terraform apply -auto-approve -no-color 2>&1 | tail -3
    terraform output -json > /tmp/network-outputs.json
    cat /tmp/network-outputs.json | jq -r '.vpc_id.value'

    Network развёрнут: 1 VPC, 4 subnets, 1 IGW. Outputs сохранены.

    ✓ Network готова. Stack'у не нужно меняться долго.

  2. 02

    Шаг 2: Compute (ALB + ECS Fargate)

    bash
    cd /home/student/capstone/modules/compute
    cat > main.tf <<'EOF'
    data "terraform_remote_state" "network" {
      backend = "local"
      config  = { path = "../network/terraform.tfstate" }
    }
    resource "aws_security_group" "alb" {
      name   = "capstone-alb-sg"
      vpc_id = data.terraform_remote_state.network.outputs.vpc_id
      ingress {
        from_port   = 80
        to_port     = 80
        protocol    = "tcp"
        cidr_blocks = ["0.0.0.0/0"]
      }
      egress {
        from_port   = 0
        to_port     = 0
        protocol    = "-1"
        cidr_blocks = ["0.0.0.0/0"]
      }
    }
    resource "aws_lb" "main" {
      name               = "capstone-alb"
      load_balancer_type = "application"
      security_groups    = [aws_security_group.alb.id]
      subnets            = data.terraform_remote_state.network.outputs.public_subnet_ids
    }
    resource "aws_lb_target_group" "app" {
      name        = "capstone-app-tg"
      port        = 8080
      protocol    = "HTTP"
      target_type = "ip"
      vpc_id      = data.terraform_remote_state.network.outputs.vpc_id
    }
    resource "aws_lb_listener" "http" {
      load_balancer_arn = aws_lb.main.arn
      port              = 80
      default_action {
        type             = "forward"
        target_group_arn = aws_lb_target_group.app.arn
      }
    }
    resource "aws_ecs_cluster" "main" {
      name = "capstone-cluster"
    }
    output "alb_dns" {
      value = aws_lb.main.dns_name
    }
    output "ecs_cluster_name" {
      value = aws_ecs_cluster.main.name
    }
    EOF
    cp ../../provider.tf .
    terraform init -no-color > /dev/null
    terraform apply -auto-approve -no-color 2>&1 | tail -3
    terraform state list

    Compute читает network через terraform_remote_state. Stack-coupling явный, через outputs.

    ✓ Compute поднят. ALB + ECS-cluster готовы.

  3. 03

    Шаг 3: Serverless (Lambda)

    bash
    cd /home/student/capstone/modules/serverless
    mkdir -p lambda-src
    cat > lambda-src/handler.py <<'EOF'
    def main(event, context):
        return {"statusCode": 200, "body": "ok"}
    EOF
    cat > main.tf <<'EOF'
    data "terraform_remote_state" "network" {
      backend = "local"
      config  = { path = "../network/terraform.tfstate" }
    }
    data "archive_file" "lambda" {
      type        = "zip"
      source_file = "${path.module}/lambda-src/handler.py"
      output_path = "${path.module}/lambda.zip"
    }
    resource "aws_iam_role" "lambda" {
      name = "capstone-lambda-role"
      assume_role_policy = jsonencode({
        Version = "2012-10-17"
        Statement = [{
          Action    = "sts:AssumeRole"
          Effect    = "Allow"
          Principal = { Service = "lambda.amazonaws.com" }
        }]
      })
    }
    resource "aws_cloudwatch_log_group" "lambda" {
      name              = "/aws/lambda/capstone-lambda"
      retention_in_days = 7
    }
    resource "aws_lambda_function" "demo" {
      function_name    = "capstone-lambda"
      filename         = data.archive_file.lambda.output_path
      source_code_hash = data.archive_file.lambda.output_base64sha256
      handler          = "handler.main"
      runtime          = "python3.12"
      role             = aws_iam_role.lambda.arn
      depends_on = [aws_cloudwatch_log_group.lambda]
    }
    output "lambda_arn" {
      value = aws_lambda_function.demo.arn
    }
    EOF
    cp ../../provider.tf .
    terraform init -no-color > /dev/null
    terraform apply -auto-approve -no-color 2>&1 | tail -3
    terraform state list

    Lambda с CloudWatch log group, IAM-role. Stack независим, читает только network для context'а (хотя в этом примере не использует VPC-config, но в реальности часто читает для VPC-attached Lambda).

    ✓ Serverless stack развёрнут. Три stack'а в работе.

  4. 04

    Шаг 4: OPA policy-gate

    Бизнес-правило: каждый ресурс с тегом Stack. Проверим compute-plan:

    bash
    cd /home/student/capstone/modules/compute
    terraform plan -no-color -out=plan.tfplan > /dev/null
    terraform show -json plan.tfplan > plan.json
    mkdir -p ../../policies
    cat > ../../policies/tags.rego <<'EOF'
    package main
    import future.keywords.contains
    import future.keywords.if
    import future.keywords.in
    deny contains msg if {
        some resource in input.resource_changes
        # фильтр по типу, только большие ресурсы (не ALB-listener'ы и т.д.)
        resource.type in {"aws_lb", "aws_ecs_cluster", "aws_lambda_function", "aws_vpc"}
        resource.change.actions[_] == "create"
        tags := object.get(resource.change.after, "tags", {})
        not tags.Stack
        msg := sprintf("%s missing Stack tag", [resource.address])
    }
    EOF
    conftest test plan.json --policy ../../policies/ 2>&1 | head -10

    Compute-ресурсы не имеют Stack-тегов, gate должен поймать.

    ✓ Policy-gate сработал. На главных ресурсах compute нет Stack-тега.

  5. 05

    Шаг 5: Тесты на network-module

    bash
    cd /home/student/capstone/modules/network
    mkdir -p tests
    cat > tests/network.tftest.hcl <<'EOF'
    mock_provider "aws" {}
    run "vpc_created_with_dns_enabled" {
      command = plan
      assert {
        condition     = aws_vpc.main.enable_dns_hostnames == true
        error_message = "VPC must have DNS hostnames enabled"
      }
      assert {
        condition     = aws_vpc.main.enable_dns_support == true
        error_message = "VPC must have DNS support"
      }
    }
    run "subnets_in_different_azs" {
      command = plan
      assert {
        condition     = aws_subnet.public[0].availability_zone != aws_subnet.public[1].availability_zone
        error_message = "public subnets must span at least 2 AZs"
      }
    }
    run "subnets_have_tier_tag" {
      command = plan
      assert {
        condition     = aws_subnet.public[0].tags["Tier"] == "public"
        error_message = "public subnet must have Tier=public tag"
      }
      assert {
        condition     = aws_subnet.private[0].tags["Tier"] == "private"
        error_message = "private subnet must have Tier=private tag"
      }
    }
    EOF
    terraform test 2>&1 | tail -10

    Три run'а pass'нулись с mock_provider, облако не нужно.

    ✓ Тесты на network прошли. Контракт модуля зафиксирован.

  6. 06

    Шаг 6: Integration smoke-test

    Проверяем что три stack'а связаны правильно:

    bash
    # network outputs
    cd /home/student/capstone/modules/network
    VPC_ID=$(terraform output -raw vpc_id)
    # compute видит VPC?
    cd ../compute
    COMPUTE_VPC=$(terraform state show aws_lb_target_group.app | grep vpc_id | awk -F'"' '{print $2}')
    echo "network VPC:   $VPC_ID"
    echo "compute VPC:   $COMPUTE_VPC"
    if [ "$VPC_ID" = "$COMPUTE_VPC" ]; then
      echo "PASS: compute correctly references network VPC"
    else
      echo "FAIL: VPC mismatch"
    fi
    # serverless видит ту же сеть?
    cd ../serverless
    SERVERLESS_LAMBDA=$(terraform state show aws_lambda_function.demo | grep function_name | awk -F'"' '{print $2}')
    echo "serverless Lambda: $SERVERLESS_LAMBDA"
    # finally
    aws --endpoint-url=http://localstack:4566 lambda list-functions \
      --query 'Functions[].FunctionName' --output text

    Видишь: compute-VPC == network-VPC; Lambda существует. Cross-stack ссылки работают.

    ✓ Three-stack integration работает. Network, compute, serverless читают друг друга.

    То же самое на OpenTofu

    OpenTofu держит CLI и state совместимыми с Terraform по командам этого шага: миграция обычно проходит через mv .terraform .terraform.bak; tofu init -upgrade. Но при первом переходе сделай backup state и прогон на feature-branch - расхождения концентрируются в новых фичах (variables в backend, state-encryption, OCI registry-backed модули). См. tf-opentofu-parity для полной матрицы.

    • → OpenTofu parity
  7. 07

    Шаг 7: Takeaways

    Что показал capstone:

    1. Stack-разделение по blast-radius. Network редкие changes, compute частые, serverless независим. Каждый, свой state, свой lock.

    2. terraform_remote_state как контракт. Только outputs кросс-stack. Внутреннее устройство network'а не видно compute'у.

    3. Policy-gate на plan.json. OPA-правило Stack-tag required ловит ошибку до apply. В CI этот gate блокирует merge.

    4. .tftest.hcl с mock_provider. Unit-тесты на модуль без облака, секунды на test-run.

    5. LocalStack как полноценная среда. Можно учиться, тестировать, CI'ить, всё без AWS-bill.

    Реальная команда расширяет это:

    • Terragrunt или Stacks для multi-env (dev/stage/prod).
    • OIDC для CI-runner'ов.
    • Drift-detection cron'ом.
    • Infracost-gate на cost-impact.
    • Custom-provider если есть внутренний API.

    Всё это покрыто в KB; этот capstone, отправная точка, не финал.

    bash
    echo "TerraformLab завершён."

    ✓ Курс пройден. Дальше, реальная работа.

    Что не покрыто (и где это лежит в реальном мире)

    Этот capstone, minimum-viable production-stack. Не покрыто:

    1. Stateful storage, RDS, DynamoDB. См. provider docs.
    2. CDN + WAF, CloudFront + WAF rules.
    3. Observability, CloudWatch dashboards, alarms, X-Ray.
    4. Multi-region, Route53 latency-routing, cross-region DR.
    5. Multi-account, AWS Organizations, AssumeRole patterns.
    6. EKS / Kubernetes, отдельный домен; см. tf-when-not-to-use про Terraform-границы.

    Hashicorp + Gruntwork опубликовали reference architectures полезный next-step после этого курса:

    • aws-reference-architecture (Gruntwork)
    • terraform-aws-modules (registry, поддерживаемые модули)

    Не пытайся написать всё с нуля, есть готовые модули, их используют тысячи команд.

    • → State at scale
    • → Multi-env через Terragrunt
    • → Границы Terraform

Что ты узнал

Multi-stack архитектура: network (VPC, subnets), compute (ALB, ECS, target groups), serverless (Lambda, log group). Между ними terraform_remote_state. policy-gate через OPA, tests через .tftest.hcl с mock_provider.

команды

  • terraform apply -auto-approveразворачивает один stack.
  • terraform testзапускает unit-тесты модулей.
  • conftest test plan.json --policy policies/policy-gate перед apply.

концепции

  • · Network stack, редко меняется, blast-radius огромный
  • · Compute stack, частые изменения (deployments), blast-radius средний
  • · Serverless, независимый, читает только outputs network'а

← предыдущий

Plan как артефакт, между PR и apply

следующий →

count vs for_each: массовое создание

Footer
linuxlab-
Copyright © 2026 LinuxLab. Все права защищены.
Учебники
Цены
О платформе
Конфиденциальность и куки