Проектирование отказоустойчивой двухуровневой архитектуры в AWS с использованием Terraform

Курс посвящен переходу от ручного управления облаком к профессиональной разработке инфраструктуры как кода (IaC). Вы научитесь создавать масштабируемую архитектуру с разделением на публичный веб-слой и приватный слой баз данных, используя модульный подход в Terraform.

1. Инициализация проекта и проектирование структуры модульного Terraform кода

Инициализация проекта и проектирование структуры модульного Terraform кода

Представьте, что вы строите современный жилой комплекс. Вы не начнете заливать бетон, пока у вас нет детального чертежа, понимания того, где пройдут коммуникации, и системы маркировки стройматериалов. В мире облачной инфраструктуры роль такого чертежа выполняет структура вашего кода. Ошибка на этапе инициализации проекта в Terraform — например, хранение всех ресурсов в одном файле main.tf — неизбежно приведет к «инфраструктурному спагетти», которое невозможно обновлять без риска обрушить всю систему. Для создания профессиональной двухуровневой архитектуры в AWS нам предстоит не просто написать скрипты, а спроектировать масштабируемую систему, где каждый компонент автономен, тестируем и предсказуем.

Философия Infrastructure as Code и выбор модульного подхода

Когда мы говорим о переходе от ручного управления AWS Console к Terraform, мы меняем парадигму с «администрирования» на «разработку». Infrastructure as Code (IaC) требует применения тех же принципов, что и написание качественного софта: DRY (Don't Repeat Yourself), инкапсуляция и разделение ответственности.

В контексте двухуровневой архитектуры (веб-слой и слой баз данных) модульность — это не роскошь, а механизм выживания. Модуль в Terraform — это логическая группа ресурсов, которая может быть вызвана многократно с разными параметрами.

Зачем нам модули на этапе инициализации?

  • Изоляция рисков: Изменение в модуле базы данных не должно случайно затронуть конфигурацию сети, если они логически разделены.
  • Повторное использование: Написав качественный модуль для VPC (Virtual Private Cloud), вы сможете использовать его в десяти других проектах, просто меняя CIDR-блоки.
  • Читаемость: Вместо файла на 2000 строк кода, вы получаете чистую структуру, где каждый файл выполняет одну задачу.
  • При проектировании нашей архитектуры мы будем придерживаться иерархии: корневой модуль (root module), который координирует работу, и дочерние модули (child modules), отвечающие за конкретные уровни инфраструктуры.

    Анатомия профессиональной структуры каталогов

    Стандартная инициализация terraform init в пустой папке — это лишь технический шаг. Проектирование начинается с файловой структуры. Для проекта двухуровневой архитектуры оптимальной считается следующая организация:

    Каждый модуль внутри папки modules/ является самодостаточной единицей. Это означает, что если вы скопируете папку vpc/ в другой проект, она должна заработать (при условии передачи нужных переменных). Это и есть истинная модульность.

    Настройка провайдера и версионный контроль

    Первый файл, который мы создаем — providers.tf. В нем мы жестко фиксируем версии Terraform и провайдера AWS. Это критически важно для командной разработки. Представьте ситуацию: один разработчик использует Terraform , а другой — . Из-за различий в синтаксисе или логике обработки состояния (state file), один запуск может повредить инфраструктуру другого.

    Здесь мы используем оператор ~>, который позволяет обновлять минорные версии (исправления багов), но блокирует мажорные обновления, которые могут содержать ломающие изменения (breaking changes). Блок default_tags — это «золотой стандарт» AWS. Все ресурсы, созданные через этот провайдер, автоматически получат указанные теги. Это упрощает аудит затрат и поиск ресурсов в консоли.

    Проектирование интерфейсов модулей: Входы и Выходы

    Самая сложная часть проектирования — определить границы ответственности модулей. В двухуровневой архитектуре данные должны течь сверху вниз:

  • Модуль VPC создает фундамент и отдает (через outputs.tf) ID подсетей и ID самой сети.
  • Модуль EC2 принимает эти ID и создает серверы, отдавая ID групп безопасности.
  • Модуль RDS принимает ID приватных подсетей и ID групп безопасности от веб-слоя, чтобы разрешить входящий трафик на порт базы данных.
  • Рассмотрим логику файла variables.tf в корневом модуле. Мы не должны «зашивать» (hardcode) значения. Вместо этого мы описываем типы данных и описания:

    Использование типизации (string, list, map) позволяет Terraform проводить валидацию еще до начала создания ресурсов. Если вы случайно передадите число там, где ожидается строка, Terraform выдаст ошибку на этапе plan.

    Инициализация и управление состоянием (State)

    Когда вы выполняете команду terraform init, происходит три ключевых процесса:

  • Загрузка провайдеров: Terraform скачивает бинарные файлы для взаимодействия с AWS API.
  • Поиск модулей: Анализируется дерево каталогов и подготавливаются ссылки на локальные компоненты.
  • Инициализация Backend: Настройка места хранения файла состояния.
  • Файл terraform.tfstate — это самый важный и самый опасный артефакт. В нем хранится соответствие между вашим кодом и реальными объектами в облаке. На этапе инициализации проекта в портфолио допустимо хранить состояние локально, но в реальной практике это недопустимо.

    > Важное замечание по безопасности: > Файл состояния может содержать конфиденциальные данные в открытом виде (пароли от баз данных, приватные ключи). Никогда не фиксируйте .tfstate в Git. Всегда добавляйте его в .gitignore.

    Для профессионального проекта рекомендуется использовать удаленный бэкенд (Remote Backend), например, корзину S3 с включенным версионированием и таблицу DynamoDB для блокировок (State Locking). Блокировка предотвращает ситуацию, когда два инженера одновременно пытаются изменить одни и те же ресурсы, что могло бы привести к повреждению состояния.

    Логическое разделение уровней: почему это важно?

    В нашей двухуровневой схеме мы разделяем веб-серверы и базу данных. С точки зрения проектирования кода, это означает создание четких контрактов между модулями.

    Веб-уровень (Public Tier)

    Этот слой доступен из интернета. В модуле ec2 мы будем описывать не только сами инстансы, но и логику их запуска. На этапе проектирования структуры мы закладываем возможность масштабирования. Даже если сейчас у нас один сервер, структура должна позволять легко внедрить Application Load Balancer (ALB) и Auto Scaling Group (ASG) в будущем.

    Уровень данных (Private Tier)

    База данных должна находиться в изолированной подсети, не имеющей прямого доступа к интернету. Доступ к ней разрешен только с IP-адресов или групп безопасности веб-уровня. При проектировании модуля rds мы закладываем переменные для:
  • Типа инстанса базы (например, db.t3.micro для экономии).
  • Имени базы и параметров аутентификации.
  • Флага multi_az для обеспечения отказоустойчивости (развертывание реплики в другой зоне доступности).
  • Работа с файлом terraform.tfvars

    Чтобы сделать наш проект универсальным, мы выносим конкретные значения в файл terraform.tfvars. Это позволяет использовать один и тот же код для разных окружений (Dev, Staging, Prod).

    Пример наполнения для нашего проекта:

    Жизненный цикл разработки и проверки

    После того как структура папок создана и файлы инициализированы, в арсенале инженера появляются три главные команды, которые сопровождают весь процесс проектирования:

  • terraform fmt: Автоматическое форматирование кода согласно стандартам HashiCorp. Чистый код — это признак профессионализма.
  • terraform validate: Проверка синтаксической корректности и логической связности модулей. Она находит ошибки в именах переменных или неверные ссылки на атрибуты ресурсов без обращения к облаку.
  • terraform plan: Генерация плана изменений. В модульной архитектуре это критически важно: вы видите, как данные передаются из модуля VPC в модуль EC2 и далее.
  • Граничные случаи и подводные камни

    При проектировании модульной структуры новички часто совершают две ошибки:

  • Циклические зависимости: Модуль А требует данные от модуля Б, а модуль Б — от модуля А. Например, вы пытаетесь создать Security Group в модуле EC2, которая ссылается на базу данных, а база данных в своем модуле ссылается на эту Security Group. Решение — выносить общие ресурсы (например, Security Groups) в отдельный слой или передавать только необходимые ID.
  • Избыточная вложенность: Не стоит создавать модули ради модулей. Если ресурс используется один раз и он прост (например, Internet Gateway), его можно оставить в модуле VPC, а не выделять в отдельную папку modules/igw/.
  • Проектирование — это баланс между гибкостью и сложностью. Наша задача в этом курсе — создать структуру, которая будет достаточно простой для понимания, но достаточно мощной, чтобы выдержать нагрузку реального приложения.

    Начав с правильной инициализации, мы гарантируем, что каждый последующий шаг — создание сетей, настройка безопасности и развертывание серверов — ляжет на подготовленную почву. Мы не просто пишем скрипты, мы создаем программный продукт, управляющий облачным гигантом AWS.

    2. Создание сетевого фундамента: проектирование VPC и сегментация подсетей

    Создание сетевого фундамента: проектирование VPC и сегментация подсетей

    Представьте, что вы строите современный бизнес-центр. Прежде чем возводить стены офисов или устанавливать серверные стойки, вам необходимо проложить коммуникации: водопровод, электрические кабели и, самое главное, систему контроля доступа, которая определит, кто может зайти в холл, а кто имеет право спуститься в защищенное хранилище. В облаке AWS роль такого фундамента играет Virtual Private Cloud (VPC). Ошибка в расчете адресного пространства или неверная сегментация подсетей на этапе проектирования — это «архитектурный долг», который позже потребует полной остановки системы и пересоздания всей сетевой инфраструктуры.

    Логика изоляции: почему два уровня недостаточно

    В двухуровневой архитектуре (2-Tier) мы разделяем приложение на веб-слой (презентационный) и слой данных (база данных). Однако на уровне сети в AWS мы должны мыслить категориями «доверенных» и «недоверенных» зон.

    Основная задача проектирования VPC — гарантировать, что база данных физически не имеет маршрута в интернет, а веб-серверы доступны извне только по строго определенным портам. Для этого используется механизм подсетей (Subnets). В Terraform мы не просто создаем ресурсы, мы описываем топологию, которая должна быть устойчива к сбоям целых дата-центров (Availability Zones).

    Если мы разместим все ресурсы в одной подсети, компрометация одного веб-сервера даст злоумышленнику прямой доступ к сетевому интерфейсу базы данных. Сегментация на публичные и приватные подсети создает первый эшелон обороны на уровне маршрутизации, который работает еще до того, как пакет данных достигнет брандмауэра (Security Group).

    Проектирование адресного пространства VPC

    Первое решение, которое принимает архитектор — выбор CIDR-блока (Classless Inter-Domain Routing). Это диапазон IP-адресов, которые будут доступны внутри вашей сети.

    Стандарт организации IETF (RFC 1918) определяет диапазоны для частных сетей. В AWS чаще всего используются:

  • 10.0.0.0/8 (крупные корпоративные сети).
  • 172.16.0.0/12 (средние сети).
  • 192.168.0.0/16 (малые сети).
  • Для нашей архитектуры мы выберем блок 10.0.0.0/16. Это дает нам адресов. Кажется, что это избыточно для одного приложения, но Terraform позволяет легко управлять этим пространством. Важно помнить: CIDR-блок VPC не может быть изменен после создания. Если вы планируете в будущем соединять эту VPC с другой сетью через VPC Peering или VPN, диапазоны не должны пересекаться.

    Математика подсетей

    При разделении 10.0.0.0/16 на подсети мы должны учитывать требования высокой доступности (High Availability). AWS рекомендует распределять ресурсы как минимум по двум зонам доступности (AZ). Следовательно, нам нужно:

  • Две публичные подсети (по одной в каждой AZ).
  • Две приватные подсети для баз данных (по одной в каждой AZ).
  • Если мы выделим подсети с маской /24, каждая из них получит адресов. Математически это выглядит так:

    Где — количество доступных адресов, а — зарезервированные адреса AWS (сетевой адрес, VPC router, DNS, зарезервированный адрес для будущего использования и широковещательный адрес). Таким образом, в подсети /24 нам доступно адрес.

    Реализация VPC на Terraform

    Перейдем к написанию кода в модуле modules/vpc/main.tf. Мы используем ресурс aws_vpc, который является контейнером для всех остальных сетевых объектов.

    Почему мы используем две подсети в разных AZ для базы данных? Amazon RDS в режиме Multi-AZ создает основной экземпляр в одной подсети и синхронную реплику в другой. Если дата-центр одной зоны выйдет из строя, AWS автоматически переключит трафик на реплику. Без наличия подсетей в разных AZ такая отказоустойчивость невозможна.

    Таблицы маршрутизации: логика движения трафика

    Сама по себе подсеть — это просто диапазон адресов. Чтобы пакеты знали, куда лететь, нужны Route Tables.

  • Public Route Table: Будет содержать маршрут 0.0.0.0/0 через Internet Gateway. Все подсети, ассоциированные с этой таблицей, становятся публичными.
  • Private Route Table: В базовом варианте содержит только локальный маршрут (10.0.0.0/16), что позволяет ресурсам внутри VPC общаться друг с другом, но закрывает выход во внешний мир.
  • В Terraform ассоциация таблицы с подсетью выполняется через ресурс aws_route_table_association. Это важный момент: если вы создали подсеть, но не привязали её к конкретной таблице маршрутизации, она будет автоматически привязана к «Main Route Table» вашей VPC, которая по умолчанию является приватной. Это защитный механизм AWS: «закрыто по умолчанию».

    Динамическое получение зон доступности

    Никогда не хардкодьте названия зон доступности (например, us-east-1a), так как их доступность может меняться в зависимости от аккаунта. Используйте Data Source:

    Это позволяет вашему коду быть переносимым. Если вы решите развернуть ту же инфраструктуру в регионе eu-central-1 (Франкфурт), Terraform сам запросит список доступных зон и распределит по ним подсети.

    Нюансы и граничные случаи

    Лимиты VPC

    В одном регионе AWS по умолчанию можно создать до 5 VPC. Если ваш проект подразумевает создание множества сред (dev, staging, prod) в одном аккаунте, стоит заранее запросить увеличение лимитов (Service Quotas). Terraform plan не всегда может предсказать ошибку превышения лимита до момента реального применения.

    Выбор размеров подсетей

    Хотя мы использовали /24, для очень больших систем этого может быть мало. С другой стороны, использование слишком больших подсетей (например, /18) может привести к быстрому исчерпанию адресного пространства всей VPC. Правило большого пальца: всегда оставляйте как минимум 50% адресного пространства VPC нераспределенным. Это позволит вам в будущем добавить слои (например, для кэширования Elasticache или Lambda-функций), не перестраивая всю сеть.

    IPv6

    В данной статье мы фокусируемся на IPv4, так как это стандарт для большинства корпоративных приложений. Однако AWS поддерживает Dual-stack VPC. Если ваше приложение требует IPv6, вам потребуется добавить assign_generated_ipv6_cidr_block = true в ресурс aws_vpc и настроить соответствующие блоки в подсетях.

    Визуализация структуры в коде

    Для удобства поддержки проекта, переменные для CIDR-блоков стоит выносить в variables.tf модуля:

    А результаты работы модуля (ID подсетей и VPC) — в outputs.tf. Это позволит другим модулям (например, модулю EC2 или RDS) «узнать», где именно им нужно создавать свои ресурсы.

    Использование оператора splat ([*]) позволяет передать список всех созданных ID подсетей одним массивом, что крайне удобно при настройке Load Balancer или RDS Subnet Groups.

    Проектирование сети — это упражнение в предвидении. Мы создали структуру, которая изолирует данные и обеспечивает избыточность. Но на текущий момент наши подсети — это «острова» без связи с внешним миром. В следующей главе мы построим «мосты», которые позволят пользователям заходить на наши веб-серверы, а серверам — безопасно скачивать обновления, не подставляя под удар базу данных.

    3. Обеспечение внешней связности: конфигурация Internet Gateway и NAT Gateway

    Обеспечение внешней связности: конфигурация Internet Gateway и NAT Gateway

    Представьте, что вы построили современный технологичный бункер: стены выдерживают любые нагрузки, внутри идеальный порядок и автономные системы жизнеобеспечения. Но без вентиляционных шахт и защищенного шлюза для связи с внешним миром обитатели бункера окажутся в полной изоляции, не имея возможности получать обновления или передавать сигналы наружу. В облачной архитектуре AWS ситуация идентична. Ваша VPC — это тот самый бункер. Чтобы веб-серверы могли принимать запросы от пользователей, а базы данных — скачивать патчи безопасности, не подставляя себя под прямые атаки из интернета, необходимо спроектировать механизмы внешней связности.

    Архитектурная логика интернет-доступа в облаке

    В двухуровневой архитектуре мы сталкиваемся с дилеммой. С одной стороны, публичный слой (Web Tier) обязан «смотреть» в интернет, чтобы обслуживать трафик. С другой стороны, приватный слой (Database Tier) должен быть максимально изолирован. Однако изоляция не означает отсутствие исходящего трафика. Базе данных часто требуется доступ к внешним репозиториям для обновления ПО или интеграция со сторонними API.

    Для решения этих задач в AWS используются два фундаментальных компонента:

  • Internet Gateway (IGW) — логический узел, обеспечивающий двустороннюю связь между VPC и интернетом.
  • NAT Gateway (Network Address Translation) — управляемый сервис, позволяющий ресурсам в приватных подсетях инициировать исходящий трафик в интернет, при этом запрещая входящие соединения извне.
  • Разница между ними принципиальна: IGW работает как прозрачная дверь, а NAT Gateway — как односторонний клапан.

    Проектирование и реализация Internet Gateway

    Internet Gateway является горизонтально масштабируемым, отказоустойчивым компонентом. У него нет ограничений по пропускной способности, которые могли бы стать узким местом для вашего приложения. С точки зрения Terraform, создание IGW — одна из самых простых операций, но она требует четкой привязки к ID вашей VPC.

    При описании ресурса aws_internet_gateway важно понимать, что сам по себе он не «включает» интернет. Он лишь создает точку выхода. Чтобы трафик пошел, нам потребуется изменить таблицы маршрутизации, созданные на предыдущем этапе.

    Параметр domain = "vpc" указывает, что данный адрес предназначен для использования внутри виртуального частного облака. Стоит помнить, что в AWS Elastic IP бесплатен, пока он привязан к запущенному ресурсу, но за неиспользуемые адреса взимается небольшая плата, чтобы предотвратить дефицит IPv4-адресов.

    Обеспечение высокой доступности (High Availability)

    В нашей двухуровневой архитектуре мы используем несколько зон доступности (Availability Zones, AZ). Возникает вопрос: сколько NAT-шлюзов нам нужно?

    Существует три стратегии:

  • Один NAT Gateway на всю VPC. Самый дешевый вариант. Однако, если зона доступности, в которой находится шлюз, выйдет из строя, все приватные подсети во всех AZ потеряют доступ к интернету.
  • Один NAT Gateway на каждую AZ. Рекомендуемый вариант для Production. Это гарантирует, что сбой в одной зоне не затронет остальные.
  • NAT Instance. Использование обычного EC2-инстанса с настроенным NAT. Это дешевле, но требует ручного управления, патчинга и не обеспечивает автоматического масштабирования. Профессионалы выбирают управляемый NAT Gateway.
  • Для нашего проекта мы выберем стратегию "один шлюз на каждую AZ", чтобы обеспечить максимальную отказоустойчивость. В Terraform это элегантно реализуется с помощью аргумента count или for_each.

    Использование depends_on здесь критично. NAT Gateway не сможет инициализироваться корректно, если Internet Gateway еще не готов принимать трафик. Terraform обычно хорошо справляется с графом зависимостей, но явное указание в данном случае избавляет от редких ошибок при развертывании.

    Настройка таблиц маршрутизации для приватного слоя

    Теперь, когда у нас есть точки выхода, нужно обновить маршруты. У нас будет две группы таблиц маршрутизации:

  • Public Route Table: Направляет трафик 0.0.0.0/0 на aws_internet_gateway.main.id.
  • Private Route Tables: Направляют трафик 0.0.0.0/0 на соответствующий aws_nat_gateway.main[count.index].id.
  • Обратите внимание, что для публичных подсетей достаточно одной таблицы маршрутизации на все зоны, так как IGW — ресурс уровня всей VPC. Но для приватных подсетей нам нужно по одной таблице на каждую AZ, потому что в каждой зоне свой NAT Gateway.

    Пример конфигурации маршрута для приватной сети

    Затем мы связываем каждую приватную подсеть с её таблицей маршрутизации через aws_route_table_association. Это замыкает цикл настройки связности.

    Экономические аспекты и лимиты

    Проектируя инфраструктуру, профессор педагогики всегда должен напоминать о цене ошибок. NAT Gateway в AWS — это платный сервис. Вы платите:

  • Фиксированную почасовую ставку за каждый развернутый шлюз (около 32 USD в месяц за один шлюз в регионе us-east-1).
  • За каждый гигабайт обработанных данных (Data Processing).
  • Если в вашем проекте 3 зоны доступности и вы разворачиваете 3 NAT Gateway, только за их наличие вы будете платить около 100 USD в месяц, даже если трафика нет совсем. Для учебных или небольших проектов часто допустимо использовать один NAT Gateway на все зоны, жертвуя отказоустойчивостью ради экономии. Однако в рамках данного курса мы придерживаемся стандартов Enterprise-архитектуры.

    > «Отказоустойчивость — это не отсутствие сбоев, а способность системы продолжать работу, когда они происходят». > > AWS Well-Architected Framework

    Нюансы работы с DNS и именами хостов

    Для полноценной работы связности недостаточно просто пробросить маршруты. В предыдущей главе мы активировали параметры enable_dns_hostnames и enable_dns_support в VPC. Теперь это приносит плоды. Когда инстанс запрашивает внешний ресурс, он использует системный DNS-резолвер AWS (обычно это IP-адрес основания сети + 2, например, 10.0.0.2).

    Если Internet Gateway настроен верно, DNS-запросы будут успешно уходить во внешний мир и возвращаться. Без работающего DNS ваш NAT Gateway будет бесполезен для большинства приложений, так как они не смогут разрешить доменные имена в IP-адреса.

    Граничные случаи: когда NAT Gateway не нужен?

    Существуют сценарии, когда трафик из приватной подсети в интернет можно минимизировать или исключить:

  • VPC Endpoints (PrivateLink): Если вам нужно обращаться только к сервисам AWS (S3, DynamoDB, KMS), лучше использовать VPC Endpoints. Трафик при этом не покидает сеть AWS, не проходит через NAT Gateway и стоит дешевле (или вовсе бесплатен для S3 Gateway Endpoint).
  • IPv6: Если вы используете IPv6, вместо NAT Gateway применяется Egress-Only Internet Gateway. Он выполняет ту же функцию, но гораздо проще в настройке и бесплатен, так как IPv6 не требует трансляции адресов (NAT) из-за огромного адресного пространства.
  • В нашей 2-tier архитектуре мы используем IPv4, так как это стандарт для большинства корпоративных приложений на текущий момент, поэтому NAT Gateway остается нашим основным инструментом.

    Инкапсуляция логики в модули

    Чтобы наш Terraform-код оставался чистым, всю логику создания IGW, NAT и маршрутов стоит вынести в модуль vpc. На выходе из модуля мы должны получить ID публичных и приватных подсетей, которые потом передадим в модули ec2 и rds.

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

    Внутри модуля мы можем использовать тернарный оператор для расчета количества ресурсов: count = var.single_nat_gateway ? 1 : length(var.az_names).

    Проверка конфигурации

    Как убедиться, что всё настроено верно? После выполнения terraform apply можно провести ряд тестов:

  • Попробовать подключиться по SSH к инстансу в публичной подсети (ему нужен публичный IP и маршрут через IGW).
  • С этого инстанса попробовать пропинговать инстанс в приватной подсети.
  • На инстансе в приватной подсети выполнить команду curl -I https://google.com. Если вы получили HTTP-ответ, значит, цепочка "Приватная подсеть -> NAT Gateway -> Internet Gateway -> Интернет" работает исправно.
  • Важно помнить, что ICMP-трафик (ping) часто блокируется группами безопасности по умолчанию, поэтому для тестов лучше использовать HTTP/HTTPS запросы.

    Замыкание сетевого контура

    Мы завершили создание «кровеносной системы» нашей инфраструктуры. У нас есть VPC, разделенная на сегменты с разным уровнем доверия, и настроены шлюзы для связи с миром. Публичные подсети готовы принимать внешних пользователей, а приватные — безопасно обновляться через NAT.

    Однако наличие маршрутов — это лишь возможность передачи данных. Чтобы эта передача была безопасной, нам нужно настроить «фильтры» на входе и выходе каждого ресурса. В следующей главе мы займемся проектированием Security Groups и Network ACL, которые определят, кому именно разрешено проходить через созданные нами шлюзы.

    4. Безопасность и контроль доступа: настройка Security Groups и сетевых ACL

    Безопасность и контроль доступа: настройка Security Groups и сетевых ACL

    Представьте, что ваша база данных RDS, содержащая конфиденциальные данные пользователей, внезапно становится доступной для прямого подключения из любой точки мира. Это не сценарий фильма ужасов для системного администратора, а реальное последствие одной ошибки в конфигурации брандмауэра. В облачной среде AWS безопасность не является «надстройкой» над инфраструктурой; она вплетена в саму ткань сети. Когда мы строим двухуровневую архитектуру, вопрос стоит не в том, как разрешить трафик, а в том, как запретить всё лишнее, оставив лишь узкие, строго контролируемые коридоры для взаимодействия между веб-серверами и базой данных.

    Эшелонированная защита: Stateful vs Stateless

    В AWS VPC существует концепция «эшелонированной защиты» (Defense in Depth), где трафик проходит через несколько фильтров, прежде чем достигнет целевого ресурса. Основными инструментами здесь выступают Security Groups (SG) и Network Access Control Lists (NACL). Несмотря на кажущуюся схожесть, они работают на разных уровнях модели OSI и обладают принципиально разной логикой обработки пакетов.

    Ключевое различие кроется в понятии «состояния» (state). Security Groups являются stateful (с сохранением состояния). Если вы разрешаете входящий трафик на порт 80 (HTTP), SG автоматически «запоминает» это соединение и позволяет ответному трафику выйти обратно к клиенту, даже если в правилах исходящего трафика (egress) всё запрещено. Это значительно упрощает настройку, так как нам не нужно беспокоиться о диапазонах портов для ответов.

    Напротив, Network ACL — это stateless (без сохранения состояния) фильтр. Он работает на уровне подсети. Если вы разрешили входящий трафик на порт 80, вам необходимо явно прописать правило для исходящего трафика, чтобы пакеты-ответы могли покинуть подсеть. Обычно для этого используются эфемерные порты (диапазон 1024–65535).

    | Характеристика | Security Group (SG) | Network ACL (NACL) | | :--- | :--- | :--- | | Уровень действия | Инстанс (ENI) | Подсеть (Subnet) | | Состояние | Stateful (автоответы разрешены) | Stateless (нужны правила в обе стороны) | | Тип правил | Только разрешающие (Allow) | Разрешающие и запрещающие (Allow/Deny) | | Порядок обработки | Все правила оцениваются вместе | Правила оцениваются по номерам (от меньшего к большему) | | Применение | Применяется только если указано | Применяется ко всем ресурсам в подсети автоматически |

    В нашей 2-tier архитектуре мы будем использовать Security Groups как основной инструмент микросегментации, а NACL — как «грубый» фильтр для блокировки нежелательных подсетей или реализации специфических требований комплаенса.

    Проектирование Security Groups для веб-уровня

    Веб-уровень (Web Tier) — это «лицо» нашего приложения. Он должен принимать трафик из интернета, но только по строго определенным протоколам. В профессиональной среде мы никогда не оставляем порты вроде 22 (SSH) открытыми для всего мира ().

    При создании модуля security_groups в Terraform, мы должны придерживаться принципа наименьших привилегий. Для веб-серверов нам потребуются следующие правила:

  • Входящий HTTP (80) и HTTPS (443): Разрешены для всех, если инстансы смотрят в интернет напрямую, или только для Load Balancer, если он используется.
  • Входящий SSH (22): Разрешен только с вашего доверенного IP-адреса или из специальной подсети управления (Bastion Host).
  • Исходящий трафик: Обычно разрешается весь исходящий трафик (), чтобы сервер мог скачивать обновления ОС и библиотек.
  • Реализация на Terraform

    Рассмотрим, как описать Security Group для веб-сервера, используя динамические блоки для чистоты кода:

    Здесь важно отметить использование переменной var.admin_ip. Никогда не хардкодьте свои личные IP-адреса в модулях. Модуль должен быть универсальным, а специфические данные должны приходить из terraform.tfvars.

    Безопасность уровня данных: Ссылка на Security Group

    Самая частая ошибка новичков — открывать порт базы данных (например, 3306 для MySQL/Aurora) для CIDR-блока веб-подсети. Это работает, но это небезопасно. Если злоумышленник создаст новый инстанс в вашей публичной подсети (даже не имеющий отношения к вашему приложению), он получит сетевой доступ к базе.

    Правильный подход — ссылка на ID Security Group (Source Security Group). Мы говорим базе данных: «Принимай трафик на порту 3306 только от тех ресурсов, которым назначена группа web_sg». Это создает логическую связку, не зависящую от IP-адресов. Если количество веб-серверов вырастет с 2 до 200, правила безопасности не изменятся ни на йоту.

    Настройка группы безопасности для RDS

    Использование security_groups = [aws_security_group.web_sg.id] вместо cidr_blocks — это стандарт индустрии. Это гарантирует, что даже если IP-адрес веб-сервера изменится (что происходит постоянно в Auto Scaling группах), доступ к базе данных сохранится.

    Сетевые ACL: Вторая линия обороны

    Хотя Security Groups обеспечивают детальный контроль, NACL (Network Access Control Lists) играют роль «вышибалы» на входе в подсеть. Они полезны для массовой блокировки трафика. Например, если вы фиксируете атаку из определенного диапазона IP-адресов, вы можете заблокировать его на уровне NACL одной строкой.

    По умолчанию каждая VPC поставляется с «дефолтным» NACL, который разрешает всё. В профессиональном проектировании часто создают кастомные NACL для каждой подсети.

    Особенности нумерации правил

    В NACL правила обрабатываются по порядку. Если правило №100 разрешает трафик, а правило №200 запрещает его — трафик пройдет. Поэтому рекомендуется оставлять зазоры в нумерации (например, 100, 110, 120), чтобы в будущем можно было вставить правило между существующими.

    Пример настройки NACL для приватной подсети БД

    Для базы данных в приватной подсети нам нужно:

  • Разрешить входящий трафик на порт 3306 из публичной подсети.
  • Разрешить исходящий трафик на эфемерные порты, чтобы база могла ответить веб-серверу.
  • Заметьте сложность: в NACL мы вынуждены оперировать CIDR-блоками, так как NACL не умеет «заглядывать» в ID Security Group. Именно поэтому NACL считается более грубым инструментом.

    Тонкие нюансы: ICMP и Path MTU Discovery

    Часто при жесткой настройке Security Groups администраторы блокируют весь ICMP трафик. Это кажется безопасным («сервер не пингуется — значит защищен»), но это может привести к трудноуловимым проблемам с производительностью сети.

    Протокол ICMP используется не только для ping, но и для механизма Path MTU Discovery. Если пакет слишком велик для какого-то узла в интернете, этот узел отправляет обратно ICMP-сообщение "Destination Unreachable (Fragmentation Needed)". Если вы полностью заблокируете ICMP, веб-сервер никогда не получит это сообщение, и соединение просто «зависнет» при попытке передать большие объемы данных (например, изображения или тяжелые скрипты).

    Лучшая практика — разрешать хотя бы ICMP Type 3 Code 4 (Fragmentation Needed). В Terraform это выглядит так:

    Организация кода: Модуль Security

    В рамках нашего проекта мы не будем разбрасывать Security Groups по разным модулям. Создадим отдельную директорию modules/security. Это позволит централизованно управлять всеми правилами доступа и избежать циклических зависимостей.

    Циклическая зависимость возникает, когда Модуль А нуждается в ID из Модуля Б, а Модуль Б — в ID из Модуля А. Например, если Web SG должна разрешать трафик от DB SG (для каких-то редких админских задач), а DB SG — от Web SG. Terraform не сможет решить, что создавать первым.

    Вынос всех SG в один модуль и использование ресурса aws_security_group_rule (вместо встроенных блоков ingress/egress) решает эту проблему. Ресурс aws_security_group_rule позволяет добавлять правила к уже созданной группе:

    Безопасность и масштабируемость

    Когда мы проектируем 2-tier архитектуру, мы должны помнить, что безопасность не должна мешать масштабируемости. Использование именных Security Groups (через ID) позволяет нам использовать Auto Scaling Group (ASG). Когда ASG запускает новый инстанс, ему просто назначается существующая web_sg. База данных мгновенно «узнает» новый инстанс как доверенный источник трафика, так как проверка идет по ID группы, а не по списку IP.

    Также стоит упомянуть о лимитах. В AWS по умолчанию существует лимит на количество правил в одной Security Group (обычно 60). Если ваше приложение требует открытия сотен портов для разных источников, это сигнал о том, что архитектура переусложнена или вам нужно пересмотреть стратегию сегментации.

    Финальное замыкание

    Настройка Security Groups и NACL — это процесс нахождения баланса между абсолютной изоляцией и функциональностью приложения. В нашей двухуровневой архитектуре мы создали надежный заслон: веб-серверы защищены от произвольного доступа, а база данных полностью изолирована от внешнего мира, доверяя только нашему веб-слою. Помните, что в облаке «закрыто по умолчанию» — это не просто рекомендация, а фундамент, на котором строится доверие пользователей к вашему сервису. В следующей главе мы перейдем к практическому развертыванию вычислительных мощностей — инстансов EC2, которые займут свои места в подготовленных нами «безопасных гаванях».

    5. Развертывание веб-уровня: конфигурация инстансов EC2 и пользовательских данных

    Развертывание веб-уровня: конфигурация инстансов EC2 и пользовательских данных

    Представьте ситуацию: вы успешно спроектировали защищенную сеть, расставили «цифровых часовых» в виде Security Groups и проложили маршруты. Но сейчас ваша инфраструктура — это лишь пустые коридоры дата-центра. Чтобы архитектура ожила, нам необходимо разместить в публичных подсетях вычислительные мощности, которые примут на себя первый удар пользовательского трафика. В двухуровневой модели этот слой называется Web Tier. Его задача — не просто «быть включенным», а обладать способностью к автоматической настройке, корректно идентифицировать себя в сети и обеспечивать базовую логику приложения еще до того, как к нему прикоснется рука системного администратора.

    Выбор вычислительной стратегии: AMI, типы инстансов и жизненный цикл

    Прежде чем написать первую строку кода в modules/ec2/main.tf, необходимо определить «генетический код» наших серверов. В AWS Elastic Compute Cloud (EC2) этот код начинается с выбора Amazon Machine Image (AMI). Для профессионального проекта выбор образа — это баланс между безопасностью и скоростью развертывания.

    Использование стандартного образа Amazon Linux 2023 является «золотым стандартом» для Terraform-проектов. Он оптимизирован для работы в облаке, содержит актуальные патчи безопасности и минимальный набор предустановленного ПО, что снижает поверхность атаки. Однако в Terraform мы не можем просто скопировать ID образа из консоли, так как AMI ID различаются в зависимости от региона (например, ami-0c55b159cbfafe1f0 в us-east-1 будет недействителен в eu-central-1).

    Для обеспечения переносимости кода мы используем блок данных data "aws_ami". Это позволяет Terraform динамически запрашивать самый свежий ID образа по заданным фильтрам.

    Этот подход гарантирует, что ваша инфраструктура всегда будет стартовать на актуальной базе. Что касается типов инстансов, то для веб-слоя в учебных и тестовых целях идеально подходит семейство t3. В отличие от старого t2, инстансы t3 работают на системе Nitro, которая обеспечивает более предсказуемую производительность и эффективную работу с сетью. Использование t3.micro позволяет оставаться в рамках Free Tier, предоставляя при этом 2 vCPU и 1 ГБ оперативной памяти — достаточно для запуска Nginx или легковесного Python/Node.js приложения.

    Анатомия ресурса aws_instance в модульном исполнении

    При проектировании модуля EC2 важно соблюдать принцип инкапсуляции. Модуль не должен знать о деталях реализации VPC, он должен лишь получать необходимые ID (подсетей и групп безопасности) через входные переменные.

    Основной блок конфигурации веб-сервера выглядит следующим образом:

    Разберем критически важные аргументы, которые часто становятся причиной ошибок в продакшене:

  • Логика распределения (subnet_id): Использование оператора деления по модулю % позволяет равномерно распределять инстансы по списку публичных подсетей. Если у нас 2 подсети в разных AZ и мы создаем 4 инстанса, Terraform назначит их по схеме: AZ1, AZ2, AZ1, AZ2. Это фундамент отказоустойчивости.
  • Root Block Device: Мы явно указываем тип gp3. В отличие от gp2, этот тип дисков позволяет настраивать IOPS и пропускную способность независимо от объема диска, а также стоит на 20% дешевле. Шифрование (encrypted = true) — обязательное требование для соответствия стандартам безопасности.
  • Жизненный цикл (delete_on_termination): Для веб-слоя, который является эфемерным (взаимозаменяемым), мы устанавливаем true. Данные приложения должны храниться в базе или S3, а не на локальном диске сервера.
  • User Data: автоматизация «последней мили»

    Одной из самых мощных функций EC2 является User Data. Это скрипт, который передается сервису EC2 и исполняется облачной утилитой cloud-init один раз при первом запуске инстанса с правами root.

    В контексте нашей 2-tier архитектуры, User Data выполняет роль моста между «голой» ОС и работающим веб-сервером. Однако хранение длинных Bash-скриптов внутри main.tf превращает код в нечитаемое полотно. Правильный подход — использование функции templatefile.

    Создание шаблона скрипта

    Создадим файл scripts/install_web.tftpl. Расширение .tftpl подчеркивает, что это шаблон Terraform, в который можно динамически подставлять переменные (например, адрес будущей базы данных).

    Передача данных в Terraform

    В корневом модуле мы считываем этот файл и передаем его в модуль EC2:

    hcl metadata_options { http_endpoint = "enabled" http_tokens = "required" # Принудительный IMDSv2 http_put_response_hop_limit = 1 } hcl resource "aws_key_pair" "deployer" { key_name = "web-admin-key" public_key = file("~/.ssh/id_rsa.pub") } hcl resource "aws_iam_instance_profile" "web_profile" { name = "web_instance_profile" role = aws_iam_role.web_role.name }

    В ресурсе aws_instance добавляем:

    iam_instance_profile = aws_iam_instance_profile.web_profile.name hcl resource "aws_placement_group" "web_pg" { name = "web-spread-pg" strategy = "spread" } hcl resource "aws_network_interface" "web_eni" { subnet_id = var.public_subnet_ids[0] security_groups = [var.web_sg_id] }

    resource "aws_instance" "web" { # ... другие параметры ... network_interface { network_interface_id = aws_network_interface.web_eni.id device_index = 0 } } hcl resource "aws_instance" "web" { # ... depends_on = [var.nat_gateway_dependency] } ``

    Этот нюанс часто упускают: инстанс в публичной подсети имеет публичный IP, но если Internet Gateway еще не готов или маршруты не прописаны, dnf` не сможет достучаться до репозиториев.

    Замыкание архитектурного контура

    Развертывание веб-уровня через Terraform — это не просто запуск виртуальных машин. Это создание самодокументированной, воспроизводимой среды. Мы объединили динамический поиск AMI, безопасную работу с метаданными через IMDSv2, автоматизацию настройки через User Data и стратегии физического размещения.

    Теперь, когда наши веб-серверы готовы принимать HTTP-запросы, они остаются «одинокими» — у них нет постоянного хранилища данных. Любая информация, записанная пользователем на диск веб-сервера, исчезнет при его замене. Поэтому следующим логическим шагом станет создание уровня данных (Data Tier) на базе Amazon RDS, который мы разместим в глубоком тылу нашей сети — в приватных подсетях, к которым мы уже подготовили доступ в предыдущих главах.

    6. Настройка уровня данных: развертывание Amazon RDS и управление параметрами БД

    Настройка уровня данных: развертывание Amazon RDS и управление параметрами БД

    Почему опытные архитекторы никогда не размещают базу данных на том же сервере, что и веб-приложение, даже если проект кажется небольшим? Ответ кроется в «радиусе поражения»: если ваш веб-сервер упадет под нагрузкой или будет взломан, база данных, находящаяся на том же диске, окажется под угрозой или станет недоступной. В двухуровневой архитектуре (2-Tier) уровень данных — это «святая святых», которая должна быть изолирована в приватных подсетях, защищена строгими правилами Security Groups и лишена прямого доступа из интернета.

    Стратегия выбора: Managed Service против Self-Managed

    При проектировании уровня данных в AWS перед инженером стоит выбор: установить MySQL или PostgreSQL на обычный инстанс EC2 или использовать Amazon RDS (Relational Database Service). Для профессионального портфолио и реальных продакшен-сред выбор почти всегда падает на RDS.

    Основная причина — перекладывание операционной ответственности на провайдера. При использовании EC2 вы обязаны самостоятельно настраивать репликацию, следить за патчами ОС, настраивать бэкапы на уровне файловой системы и обеспечивать High Availability (HA). В случае с RDS эти задачи решаются одной строчкой кода в Terraform.

    Amazon RDS предоставляет:

  • Автоматическое резервное копирование: снимки состояния (snapshots) и транзакционные логи позволяют восстановить базу на любой момент времени (Point-in-Time Recovery).
  • Multi-AZ развертывание: синхронная репликация данных в другую зону доступности. Если основной дата-центр выйдет из строя, RDS автоматически переключит DNS-запись на резервную копию.
  • Масштабируемость: изменение типа инстанса (например, с t3.micro на m5.large) или объема диска происходит без сложной миграции данных.
  • Подготовка сетевой инфраструктуры для RDS

    Прежде чем объявить ресурс базы данных, необходимо подготовить для него «посадочную площадку». База данных не может существовать в вакууме; ей требуется группа подсетей, которая определяет, в каких зонах доступности (AZ) она может размещать свои узлы.

    Создание DB Subnet Group

    Ресурс aws_db_subnet_group — это логическое объединение приватных подсетей, которые мы создали во второй главе. Важно, чтобы в группу входили подсети как минимум из двух разных AZ. Это обязательное требование для включения режима Multi-AZ.

    Параметр family должен строго соответствовать версии движка. Если вы планируете использовать PostgreSQL 15, семейство будет postgres15.

    Описание основного ресурса базы данных

    Теперь перейдем к самому ресурсу aws_db_instance. Здесь мы объединяем все созданные ранее компоненты: подсети, параметры безопасности и вычислительные мощности.

    Теперь в ресурсе aws_db_instance мы можем указать password = random_password.db_password.result. Это исключает «человеческий фактор» при выборе слабых паролей.

    Тонкая настройка производительности и обслуживания

    Управляемые базы данных требуют понимания того, когда и как AWS будет проводить технические работы.

    Окно обслуживания (Maintenance Window)

    AWS периодически обновляет нижележащую ОС или патчит движок БД. Параметр maintenance_window позволяет задать время (например, ночь понедельника), когда кратковременная недоступность базы (обычно при Multi-AZ это секунды) нанесет минимальный ущерб бизнесу.

    Резервное копирование (Backup Retention)

    Параметр backup_retention_period = 7 означает, что вы можете восстановить состояние базы на любой момент в течение последней недели. AWS хранит инкрементальные бэкапы в S3 (вы за них платите отдельно, но управление автоматизировано). Если установить 0, автоматические бэкапы будут отключены, что крайне не рекомендуется.

    Особенности работы с хранилищем gp3

    В отличие от старого типа gp2, где производительность (IOPS) зависела от объема диска (чем больше диск, тем быстрее он работает), gp3 позволяет настраивать IOPS и пропускную способность (Throughput) независимо от объема.

    Для небольших баз данных это означает экономию: вы можете взять диск 20 ГБ и принудительно выставить ему 3000 IOPS, если ваше приложение выполняет много мелких операций записи. В Terraform это выглядит так:

    Если эти параметры не указаны, AWS предоставит базовые значения (3000 IOPS и 125 MiB/s), которые включены в стоимость хранения.

    Жизненный цикл и защита от случайного удаления

    База данных — самый ценный ресурс. Ошибка в коде или случайная команда terraform destroy в неправильном терминале может привести к катастрофе. Чтобы минимизировать риски, в Terraform предусмотрен блок lifecycle.

  • deletion_protection: Этот аргумент на уровне API AWS запрещает удаление инстанса. Чтобы его удалить, нужно сначала изменить этот параметр на false и применить изменения.
  • prevent_destroy: Это «предохранитель» самого Terraform. Он выдаст ошибку, если какая-либо операция приведет к попытке удаления ресурса.
  • ignore_changes: Если вы планируете менять пароль вручную или через автоматическую ротацию в Secrets Manager, добавьте его в этот список. Иначе при следующем запуске terraform apply Terraform увидит расхождение и попытается вернуть пароль к тому значению, которое записано в коде.
  • Взаимосвязь с Security Groups (Source Security Group)

    В четвертой главе мы создали группу безопасности для базы данных (db_sg). Теперь важно убедиться, что база данных действительно использует её.

    Логика доступа строится не на IP-адресах, а на идентификаторах групп.

  • Веб-серверы имеют web_sg.
  • База данных имеет db_sg.
  • Правило в db_sg разрешает входящий трафик на порт 3306 только если источником является web_sg.
  • Это создает «динамический периметр»: сколько бы веб-серверов вы ни запустили (даже если их IP-адреса постоянно меняются при масштабировании), они всегда будут иметь доступ к базе, а любой другой ресурс в VPC — нет.

    Мониторинг и логирование

    Для полноценной работы уровня данных в продакшене необходимо видеть, что происходит «под капотом». RDS позволяет экспортировать логи напрямую в CloudWatch.

  • error: ошибки запуска и работы движка.
  • slowquery: запросы, которые выполняются дольше заданного порога (по умолчанию 10 секунд, настраивается в Parameter Group). Это главный инструмент оптимизации производительности.
  • Также рекомендуется включить Enhanced Monitoring. В отличие от стандартных метрик CloudWatch, которые собираются раз в 1-5 минут, Enhanced Monitoring собирает данные об использовании CPU, памяти и I/O каждую секунду, запуская специальный агент на инстансе БД.

    Примечание: Для этого потребуется создать отдельную IAM-роль с политикой AmazonRDSEnhancedMonitoringRole.

    Интеграция с веб-слоем

    После того как ресурс aws_db_instance будет создан, Terraform получит от AWS адрес подключения (Endpoint). Этот адрес выглядит примерно так: myapp-db.c1234567890.us-east-1.rds.amazonaws.com.

    Этот адрес — динамический. Вы не знаете его заранее, пока база не создана. Чтобы ваше веб-приложение (развернутое в главе 5) могло подключиться к базе, нам нужно передать этот Endpoint в User Data веб-сервера.

    Это делается через механизм Outputs и интерполяцию строк, что станет центральной темой нашей следующей главы. Однако на уровне проектирования RDS важно понимать: мы никогда не используем IP-адрес базы данных, только её DNS-имя (Endpoint), так как при переключении Multi-AZ IP-адрес изменится, а DNS-имя останется прежним.

    Граничные случаи: когда RDS может подвести

    Несмотря на мощь управляемого сервиса, есть нюансы, которые нужно учитывать при проектировании:

  • Время модификации: Некоторые изменения (например, смена Parameter Group или изменение типа инстанса) требуют перезагрузки или могут занять значительное время (до 30-60 минут для больших объемов данных). Всегда используйте флаг apply_immediately = false в продакшене, чтобы изменения применились только в окно обслуживания.
  • Лимиты соединений: Поскольку RDS работает на инстансах с ограниченной памятью, количество одновременных подключений к MySQL/PostgreSQL ограничено. Если ваше приложение создает тысячи соединений, вам может потребоваться RDS Proxy — промежуточный слой для пулинга соединений.
  • Стоимость трафика: Трафик между AZ в AWS платный. Если ваш веб-сервер находится в us-east-1a, а база данных (Primary) — в us-east-1b, вы будете платить за каждый гигабайт переданных данных. В идеале приложение должно стремиться работать с базой в той же зоне, где находится оно само.
  • Развертывание уровня данных завершает формирование «скелета» нашей архитектуры. Теперь у нас есть изолированная сеть, защищенные веб-серверы и надежное хранилище. Следующим шагом станет «склеивание» этих компонентов в единую живую систему.

    7. Управление состоянием инфраструктуры, использование переменных и Output-значений

    Управление состоянием инфраструктуры, использование переменных и Output-значений

    Представьте, что вы строите сложный механизм, где детали производятся в разных цехах. Чтобы собрать его воедино, вам недостаточно просто иметь чертежи каждой детали — вам нужно точно знать размеры выходных валов одного узла, чтобы они совпали с входными отверстиями другого. В мире Terraform роль таких «стыковочных узлов» играют Output-значения, а переменные определяют гибкость производства. Но есть и третья, самая важная составляющая: «бортовой журнал» или State-файл, который помнит всё, что уже было создано, и не позволяет системе войти в противоречие с самой собой.

    Анатомия Terraform State: от локального файла к облачному бэкенду

    Файл состояния (terraform.tfstate) — это единственный источник истины (Single Source of Truth) для Terraform. Без него инструмент превращается в обычный генератор API-запросов, который не знает, существует ли ресурс на самом деле или его нужно создать с нуля. Когда вы выполняете terraform plan, происходит сопоставление вашего кода, текущего состояния в файле и реальной инфраструктуры в AWS.

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

  • Отсутствие совместной работы: если два инженера запустят apply одновременно, они перезапишут изменения друг друга.
  • Безопасность: State-файл содержит конфиденциальные данные в открытом виде (пароли RDS, приватные ключи).
  • Риск потери: удаление локального файла означает потерю контроля над облачными ресурсами (их придется импортировать вручную).
  • Для решения этих проблем используется Remote Backend на базе Amazon S3 и DynamoDB. S3 обеспечивает надежное хранение и версионирование файла, а DynamoDB реализует механизм блокировок (State Locking).

    Настройка Remote State с блокировкой

    Чтобы перенести состояние в облако, необходимо настроить блок terraform в корневом модуле. Важно понимать, что ресурсы для самого бэкенда (S3 бакет и таблица DynamoDB) должны быть созданы заранее — либо вручную, либо отдельным «инициализирующим» скриптом Terraform.

    В этой конфигурации:

  • bucket: Имя S3 бакета. Рекомендуется включить в нем версионирование, чтобы иметь возможность откатиться к предыдущему состоянию инфраструктуры.
  • key: Путь к файлу внутри бакета. Использование четкой иерархии (проект/окружение/файл) помогает избежать путаницы.
  • encrypt: Обязательный параметр для защиты данных в покое.
  • dynamodb_table: Имя таблицы, где Terraform будет создавать временную запись при запуске любой операции изменения. Если другой пользователь попытается запустить apply, он получит ошибку State Locked.
  • Иерархия переменных: от жесткого кода к гибким абстракциям

    Переменные в Terraform — это не просто способ подстановки строк. Это инструмент управления сложностью, позволяющий использовать один и тот же код для разных окружений (Dev, Staging, Prod).

    Типизация и валидация

    Профессиональный код требует строгой типизации. Это предотвращает ошибки на этапе validate, не дожидаясь попытки создания ресурсов.

    Здесь блок validation выступает в роли архитектурного предохранителя. Мы не просто просим число, мы диктуем бизнес-логику: "не меньше двух для HA и не больше пяти для контроля бюджета".

    Стратегии передачи значений

    Terraform ищет значения переменных в определенном порядке приоритетности:

  • Аргументы командной строки: -var="web_instance_count=3".
  • Файлы .tfvars или .tfvars.json.
  • Переменные окружения: TF_VAR_web_instance_count.
  • Значения по умолчанию (default).
  • В нашем проекте мы используем файл terraform.tfvars для хранения специфичных для окружения данных (например, CIDR блоков или типов инстансов), в то время как variables.tf описывает только структуру и типы.

    Output-значения как клей между модулями

    В двухуровневой архитектуре слои (Web и DB) часто разделены на разные модули. Модуль базы данных создает RDS инстанс и генерирует Endpoint (адрес подключения). Веб-серверу этот адрес необходим для настройки приложения через User Data.

    Output-значения выполняют две функции:

  • Экспорт данных из модуля: делают внутренние атрибуты ресурсов доступными для других частей кода.
  • Информационный вывод: показывают важные данные (например, публичный IP балансировщика) в терминале после завершения apply.
  • Пример передачи данных между слоями

    Предположим, у нас есть модуль modules/db/outputs.tf:

    Теперь в корневом модуле (main.tf) мы можем захватить этот выход и передать его в модуль веб-серверов:

    Этот механизм создает явную зависимость. Terraform понимает, что module.web_tier нельзя создать или обновить до того, как будет получен db_endpoint от module.database.

    Глубокая интеграция: динамическое формирование User Data

    Одной из самых сложных задач в автоматизации является «проброс» динамических данных внутрь скриптов инициализации ОС. Мы уже изучили функцию templatefile, но теперь рассмотрим её в контексте связки переменных и выходов.

    Когда база данных развернута, её адрес заранее неизвестен. Он генерируется AWS в процессе создания. Чтобы веб-приложение смогло подключиться к БД, мы должны внедрить этот адрес в конфигурационный файл приложения на лету.

    В модуле web мы определяем переменную для шаблона:

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

    Сравнение механизмов передачи данных

    | Механизм | Область видимости | Назначение | | :--- | :--- | :--- | | Variables | Внешняя (ввод) | Параметризация модулей пользователем | | Locals | Внутренняя | Упрощение кода, исключение повторов (DRY) | | Outputs | Внешняя (вывод) | Передача данных между модулями или пользователю | | Remote State | Межпроектная | Чтение данных из независимых состояний |

    Завершая проектирование связей в нашей 2-tier архитектуре, мы должны убедиться, что каждый элемент знает ровно столько, сколько ему нужно для работы. Веб-слой не должен знать пароль суперпользователя базы данных — он должен получить лишь строку подключения и свои ограниченные учетные данные. База данных не должна знать IP-адреса серверов — она должна доверять ID группы безопасности веб-слоя. Именно такое грамотное управление состоянием и потоками данных превращает набор скриптов в надежную, масштабируемую систему.

    8. Тестирование развернутой инфраструктуры и проверка межслойной связности

    Тестирование развернутой инфраструктуры и проверка межслойной связности

    Представьте, что вы построили современный жилой комплекс: стены возведены, коммуникации проложены, а ключи переданы жильцам. Но при первой попытке включить свет выясняется, что проводка в квартире не соединена с общедомовым щитком, а вода не доходит до верхних этажей из-за неверного давления. В мире облачной инфраструктуры ситуация идентична. Вы можете написать безупречный Terraform-код, развернуть десятки ресурсов без единой ошибки в консоли, но если веб-сервер не может «достучаться» до базы данных из-за опечатки в правилах Security Group или неверно переданного DNS-имени, ваша архитектура остается набором изолированных островов, а не работающей системой.

    Проверка связности — это не просто финальный штрих, а критический этап верификации проектных решений. На этом этапе мы проверяем, насколько корректно отработали наши абстракции: переменные, output-значения и шаблоны User Data.

    Методология сквозного тестирования 2-Tier архитектуры

    В классической двухуровневой архитектуре тестирование должно следовать логике прохождения трафика. Мы не можем просто проверить «всё сразу»; эффективнее разделить процесс на три уровня верификации:

  • Внешняя доступность (L3/L4/L7): Доступен ли веб-интерфейс из интернета? Работает ли проброс портов 80/443?
  • Внутренняя связность (Inter-tier connectivity): Видит ли инстанс EC2 в публичной подсети эндпоинт базы данных RDS в приватной подсети?
  • Прикладная интеграция: Корректно ли отработал скрипт инициализации (User Data)? Смогло ли PHP/Python приложение авторизоваться в MySQL, используя секреты из Environment Variables или конфигурационных файлов?
  • Одной из главных ловушек при тестировании является использование ICMP (ping). Многие инженеры по привычке пытаются «пропинговать» RDS-инстанс. Однако в AWS по умолчанию ICMP закрыт в Security Groups, а сам сервис RDS может игнорировать эхо-запросы. Поэтому нашим основным инструментом станет проверка конкретных TCP-портов и HTTP-ответов.

    Верификация веб-слоя и отладка User Data

    Первым делом необходимо убедиться, что наши EC2-инстансы не просто находятся в состоянии running, но и успешно прошли стадию инициализации. Часто бывает так: Terraform рапортует об успешном создании ресурса, но скрипт установки Apache или Nginx внутри User Data завершился с ошибкой из-за отсутствия связи с репозиториями или конфликта пакетов.

    Проверка статуса инициализации

    В AWS консоли или через CLI мы видим два типа проверок: System Status Check и Instance Status Check. Если вторая проверка затянулась, вероятно, возникла проблема на уровне ОС.

    Для детальной диагностики мы используем системный лог инстанса. В Terraform мы не видим вывод Bash-скриптов, которые передали в user_data. Чтобы понять, почему веб-сервер не отвечает, необходимо подключиться к инстансу по SSH (если это разрешено в Security Group) и изучить файл /var/log/cloud-init-output.log. Именно там фиксируется весь вывод команд, исполняемых при старте.

    > Важный нюанс: если вы используете Amazon Linux 2023, логика инициализации может отличаться от старых версий. Всегда проверяйте, что ваш скрипт начинается с шебанга #!/bin/bash, иначе система воспримет его как обычный текст.

    Тестирование HTTP-отклика

    Для проверки внешней доступности используйте curl с флагами заголовков:

    Если вы получаете HTTP/1.1 200 OK, значит: * Internet Gateway настроен и маршрут 0.0.0.0/0 в Public Route Table активен. * Security Group разрешает входящий трафик на порт 80. * Веб-сервер внутри EC2 запущен и слушает интерфейс.

    Если же запрос «висит» до тайм-аута, проблема на 99% в сетевых фильтрах (Security Groups или Network ACL). Если возвращается Connection Refused — проблема внутри инстанса (сервис не запущен).

    Тестирование межслойного взаимодействия: EC2 → RDS

    Это самый ответственный этап. База данных находится в приватной подсети, у неё нет публичного IP, и она защищена группой безопасности, которая разрешает вход только с ID группы безопасности веб-сервера.

    Инструментарий для проверки портов

    Поскольку мы не можем подключиться к RDS напрямую из своего офиса или дома (и это правильно с точки зрения безопасности), проверку нужно проводить «глазами» веб-сервера. Подключитесь к EC2-инстансу и используйте утилиту nc (netcat) или telnet.

    Пример проверки доступности MySQL на стандартном порту 3306:

    Здесь <rds_endpoint_address> — это DNS-имя, которое мы получили через output модуля RDS в предыдущих главах. Если результат Connection to ... port 3306 [tcp/mysql] succeeded!, значит, «магия» Security Groups сработала.

    Разбор типичных ошибок связности

    Если соединение не устанавливается, проверьте следующие точки отказа:

  • DNS Resolution: Попробуйте выполнить nslookup или dig для эндпоинта RDS внутри EC2. Если имя не разрешается в IP-адрес 10.0.x.x, проверьте параметры VPC: enable_dns_hostnames и enable_dns_support должны быть true.
  • DB Subnet Group: Убедитесь, что RDS развернут именно в тех приватных подсетях, для которых прописаны маршруты в таблицах маршрутизации.
  • Security Group Ingress: Проверьте, что в группе безопасности базы данных есть правило, разрешающее порт 3306, где в качестве source указан не IP-адрес, а именно security_group_id веб-сервера. Это критически важно для динамических сред, где IP инстансов могут меняться.
  • Проверка динамической конфигурации приложения

    В предыдущей главе мы использовали функцию templatefile для передачи адреса базы данных и пароля в скрипт User Data. Теперь нужно убедиться, что эти данные корректно «проросли» в конфигурацию приложения.

    Валидация шаблонов

    Если ваше приложение написано на PHP, типичный файл db_config.php, сгенерированный через Terraform, может выглядеть так:

    После развертывания зайдите на инстанс и проверьте итоговый файл. Все переменные conn = new mysqli(db_user, conn->connect_error) { die("Ошибка подключения: " . TOKEN" http://169.254.169.254/latest/meta-data/placement/availability-zone `

    Если Terraform-цикл count или for_each настроен верно, вы увидите разные зоны (например, us-east-1a и us-east-1b).

    Безопасность: Тестирование «негативных» сценариев

    Качественное тестирование включает проверку того, что система не делает. Мы должны убедиться, что наши границы безопасности (Security Boundaries) непроницаемы.

  • Доступ к БД извне: Попробуйте подключиться к эндпоинту RDS со своего локального компьютера. Вы должны получить тайм-аут. Если подключение проходит (даже с ошибкой Access Denied), значит, ваша база данных случайно оказалась в публичной подсети или имеет атрибут publicly_accessible = true. Это критическая дыра в безопасности.
  • Доступ между слоями по запрещенным портам: Попробуйте с веб-сервера выполнить telnet <db_endpoint> 22 (SSH). Соединение должно быть сброшено. Мы разрешали только 3306, и любые другие попытки взаимодействия должны пресекаться правилом по умолчанию (Implicit Deny).
  • Выход в интернет из приватной подсети: Если бы у нас были другие инстансы в приватной подсети (например, воркеры), важно проверить работу NAT Gateway. Попробуйте выполнить ping 8.8.8.8 или curl google.com. Трафик должен идти, но при этом у самого инстанса не должно быть публичного IP.
  • Анализ производительности сетевого стыка

    Хотя на этапе функционального тестирования производительность не является приоритетом, полезно замерить задержки (latency) между веб-слоем и базой данных. В пределах одного региона AWS задержка обычно составляет менее 1–2 мс.

    Используйте утилиту iperf или простые замеры времени выполнения SQL-запросов. Если задержки аномально высокие (например, 10+ мс), проверьте, не находятся ли ваши ресурсы в разных регионах (что маловероятно при использовании Terraform модулей, но возможно при ошибках в конфигурации провайдеров) или не перегружен ли NAT Gateway.

    Инструменты автоматизированного тестирования (IaC Test)

    Для профессионального портфолио ручных проверок может быть недостаточно. Существуют инструменты, которые позволяют автоматизировать проверку инфраструктуры сразу после terraform apply.

    * Terratest: Библиотека на языке Go, которая позволяет писать полноценные unit-тесты для инфраструктуры. Она может развернуть ваш код, проверить HTTP-ответ, попробовать подключиться к БД и, если всё успешно, выполнить terraform destroy`. * Checkov / TFLint: Статические анализаторы, которые проверяют безопасность и корректность кода еще до его деплоя. Они помогут отловить открытые порты 0.0.0.0/0 там, где их быть не должно. * InSpec / Kitchen-Terraform: Инструменты для проверки состояния ресурсов («Убедись, что на сервере X установлен пакет Apache и порт 80 открыт»).

    Интеграция таких проверок в CI/CD пайплайн превращает ваш проект из «просто скриптов» в надежное инженерное решение.

    Финализация этапа тестирования

    После того как вы убедились, что:

  • Веб-страница открывается и отображает данные из БД.
  • Секреты передаются безопасно и не светятся в логах.
  • Отказоустойчивость работает при перезагрузке RDS.
  • Приватные ресурсы защищены от внешнего мира.
  • Можно считать, что сетевой фундамент и прикладной слой вашей 2-Tier архитектуры успешно интегрированы. Мы перешли от набора изолированных ресурсов к целостной системе. Однако работа с кодом на этом не заканчивается. Впереди — критически важный этап оптимизации: избавление от «хардкода», наведение порядка в именовании и подготовка проекта к передаче в эксплуатацию или включению в портфолио. Ведь хороший инженер отличается от посредственного не тем, что его код работает, а тем, насколько легко этот код поддерживать и масштабировать другим людям.

    9. Оптимизация, очистка кода и финализация проекта для профессионального портфолио

    Оптимизация, очистка кода и финализация проекта для профессионального портфолио

    Представьте, что вы — технический директор, рассматривающий код кандидата на позицию DevOps-инженера. Что скажет вам больше: факт того, что инфраструктура просто «поднялась» после десятой попытки, или то, как элегантно организованы зависимости, насколько читаемы манифесты и предусмотрена ли процедура безопасного удаления ресурсов? В мире Infrastructure as Code (IaC) работающая кнопка «Deploy» — это лишь 40% успеха. Остальные 60% — это поддерживаемость, безопасность и готовность кода к передаче в эксплуатацию другой команде.

    На финальном этапе нашего проекта мы превращаем «черновик», который помог нам изучить компоненты AWS, в профессиональный продукт. Мы сфокусируемся на рефакторинге, устранении «магических чисел», внедрении стандартов именования и подготовке документации, которая сделает ваш репозиторий образцовым для портфолио.

    Ревизия жестких зависимостей и внедрение динамики

    Одной из самых частых ошибок при переходе от обучения к практике является использование Hardcoded-значений (жестко заданных строк или чисел) внутри модулей. Если в вашем коде модуля VPC до сих пор встречается строка вроде cidr_block = "10.0.0.0/16", это ограничивает переиспользование кода. Профессиональный подход подразумевает, что модуль — это «черный ящик», который получает конфигурацию извне.

    Переход на локальные переменные для вычисляемых имен

    Использование locals позволяет избежать дублирования логики формирования имен ресурсов. Вместо того чтобы в каждом ресурсе писать Name = "{var.environment}-web", мы создаем единый вычислительный центр внутри модуля.

    Применение такого подхода гарантирует, что при изменении названия проекта в одном месте, оно корректно обновится во всех ресурсах: от тегов инстансов до имен групп безопасности. Это снижает риск возникновения «дрейфа имен», когда часть ресурсов в консоли AWS подписана по старому стандарту, а часть — по новому.

    Оптимизация выбора типов инстансов

    В процессе разработки мы могли использовать t3.micro из-за Free Tier. Однако для портфолио важно показать понимание гибкости. Использование карт (maps) для выбора типа инстанса в зависимости от окружения — это стандарт индустрии.

    Такая структура позволяет легко масштабировать проект. Если завтра вам понадобится развернуть Stage-окружение, достаточно будет добавить одну строку в карту, не переписывая логику создания ресурсов в main.tf.

    Глубокий рефакторинг безопасности и жизненного цикла

    На этапе финализации необходимо убедиться, что мы не оставили «дыр», которые допустимы при тестах, но фатальны в продакшене. Это касается не только правил Security Groups, но и того, как Terraform управляет жизненным циклом критически важных данных.

    Защита от случайного удаления (Deletion Protection)

    Для базы данных RDS и критических S3-корзин с бэкапами использование мета-аргумента lifecycle является обязательным. В профессиональном коде вы должны явно указать Terraform, что ресурс нельзя удалять без предварительного изменения кода.

    Блок ignore_changes здесь критически важен. Если пароль базы данных был изменен вручную или через AWS Secrets Manager (вне Terraform), последующий запуск terraform apply не попытается откатить его к значению из кода, что могло бы привести к простою приложения.

    Минимизация прав доступа через IAM

    Проверьте свои Instance Profiles. Если ваш веб-сервер имеет роль AdministratorAccess, это «красный флаг» для любого ревьюера. На финальном этапе мы должны ограничить права до абсолютного минимума (Principle of Least Privilege). Если веб-серверу нужно только читать конфиги из S3, роль должна разрешать только s3:GetObject для конкретного бакета.

    Использование data блоков для формирования политик IAM делает код чище и безопаснее:

  • Опишите политику в JSON-шаблоне.
  • Подгрузите её через data "aws_iam_policy_document".
  • Прикрепите к роли.
  • Это избавляет от громоздких Heredoc-строк внутри Terraform-файлов и позволяет переиспользовать JSON-определения политик.

    Стандартизация структуры и форматирование

    Чистота кода — это не только отсутствие ошибок, но и визуальная консистентность. Terraform предоставляет встроенные инструменты, которыми часто пренебрегают новички.

    Автоматизация через terraform fmt и validate

    Перед коммитом в портфолио выполните команду terraform fmt -recursive. Она выровняет отступы, расставит знаки равенства в одну линию и приведет структуру блоков к единому стандарту HashiCorp. Это кажется мелочью, но для опытного глаза это маркер дисциплины разработчика.

    Команда terraform validate проверит не только синтаксис, но и внутреннюю логику связей (например, соответствие типов переменных). Профессиональный проект в портфолио должен проходить эти проверки без единого предупреждения.

    Организация документации (README.md)

    Ваш проект в GitHub — это не просто набор .tf файлов, это документация. Хороший README должен содержать:

  • Архитектурную диаграмму. Используйте инструменты вроде Diagrams.net или CloudCraft. Визуализация 2-tier архитектуры (VPC, Subnets, IGW, NAT, EC2, RDS) помогает сразу понять масштаб проделанной работы.
  • Инструкцию по развертыванию. Какие переменные обязательны (terraform.tfvars), какие права нужны в AWS CLI.
  • Таблицу Inputs и Outputs. Не обязательно писать её вручную. Существует инструмент terraform-docs, который автоматически генерирует таблицы на основе ваших variables.tf и outputs.tf.
  • Управление стоимостью и ресурсоемкостью

    Профессионал всегда думает о бюджете. В AWS легко забыть включенный NAT Gateway или RDS инстанс, который за месяц «съест» несколько сотен долларов.

    Стратегия тегирования для Cost Allocation

    Тегирование — это не только для красоты. В крупных компаниях теги используются для разделения счетов между отделами. Внедрите переменную common_tags и примените её ко всем ресурсам, поддерживающим теги.

    Функция merge позволяет объединять общие теги проекта (Environment, Owner) со специфичными для ресурса (Name).

    Процедура очистки (Cleanup)

    Для портфолио важно описать, как безопасно удалить инфраструктуру.

  • Сначала удаляются ресурсы с prevent_destroy = true (если они были временно изменены для удаления).
  • Затем выполняется terraform destroy.
  • Очистка S3-бакета с State-файлом (если он не пуст, Terraform не сможет его удалить автоматически).
  • Упоминание этих нюансов в документации показывает, что вы понимаете жизненный цикл ресурсов и не оставите после себя «мусор» в облаке.

    Использование модулей из Terraform Registry vs Собственные

    В реальной работе часто используются проверенные модули от сообщества (например, terraform-aws-modules/vpc/aws). Однако для портфолио новичка критически важно показать, что вы умеете писать собственные модули с нуля.

    Если вы используете сторонние модули, обязательно делайте «обертку». Не вызывайте модуль напрямую из корневого каталога с сотней параметров. Создайте свой модуль, который инкапсулирует логику вашей архитектуры, и вызывайте его. Это демонстрирует навык композиции систем.

    Подготовка к демонстрации: "Golden Path"

    Чтобы ваш проект выглядел убедительно, создайте сценарий «быстрого старта».

  • Подготовьте файл examples/basic/main.tf, который вызывает ваш основной модуль с минимальным набором параметров.
  • Убедитесь, что outputs возвращают полезную информацию: URL веб-сайта, DNS-имя базы данных (даже если она в приватной сети) и команды для проверки связности.
  • Пример качественного вывода:

    Это показывает, что вы заботитесь о конечном пользователе вашей инфраструктуры.

    Финализация: Проверка на "Idempotency"

    Идемпотентность — это свойство системы при повторном применении одного и того же воздействия давать тот же результат, что и при первом. Запустите terraform apply дважды. Второй запуск должен выдать сообщение: No changes. Your infrastructure matches the configuration.

    Если Terraform каждый раз пытается что-то изменить (например, из-за использования timestamp() в тегах или некорректно настроенных ignore_changes), это признак нестабильного кода. Исправьте это, используя статические значения или функции, которые не меняются при каждом запуске.

    Замыкание архитектурного цикла

    Создание 2-tier архитектуры на AWS через Terraform — это путь от понимания отдельных «кубиков» (сетей, серверов, баз данных) к системному мышлению. Мы начали с инициализации пустого проекта, прошли через дебри маршрутизации, настроили барьеры безопасности и связали слои приложения через динамические переменные.

    Ваш проект теперь — это не просто набор инструкций для облака, а отчуждаемый продукт. Он отказоустойчив благодаря распределению по зонам доступности, безопасен благодаря изоляции базы данных и удобен в поддержке благодаря модульности. Такой подход отличает инженера, который «знает кнопки в консоли», от архитектора, который проектирует надежные системы.