open atlas
↑ К треку
AWS на практике AWS · 08 · 01

Капстоун: безопасная и устойчивая three-tier архитектура от края до края

Капстоун: связать хранилище, сеть, вычисления, данные, безопасность, наблюдаемость, IaC и стоимость в одно безопасное устойчивое three-tier веб-приложение — Route 53, CloudFront, ALB, Fargate, RDS Multi-AZ, S3 — и пройти путь запроса и каждую границу доверия на нём.

AWS Senior ◷ 22 min
Уровень
ОсновыJuniorMiddleSenior

Один стартап назвал свой дизайн «three-tier» и выкатил его. ALB впереди, серверы приложения за ним, база Postgres в глубине — по учебнику. Потом аудитор задал один вопрос: в какой подсети база? Никто не знал. Посмотрели. Инстанс RDS был запущен в публичной подсети с security group, разрешавшей 5432 с 0.0.0.0/0, потому что полтора года назад кому-то понадобилось подключиться с ноутбука «только на миграцию» — и он так и не откатил это. У базы был публичный IP и открытый порт для всего интернета полтора года. Её ещё не взломали — это была удача, а не архитектура. Тот же разбор нашёл вторую, более тихую проблему: весь стек жил в одной Availability Zone, поэтому «высокодоступное» приложение умерло бы в тот момент, когда у этой одной AZ настанет плохой день. «Three-tier» — это не диаграмма из трёх коробок. Это набор границ доверия и границ отказа, и если ты не можешь назвать, где каждая из них проходит, у тебя их нет.

К концу этого урока ты сможешь пройти полный путь запроса, назвать каждую границу доверия на нём и найти два структурных изъяна, из-за которых «three-tier» стартапа превратился в ждущий своего часа инцидент.

Форма: один VPC, две AZ, три тира

Всё живёт в одном VPC (Virtual Private Cloud, виртуальная частная облачная сеть), охватывающем минимум две Availability Zones — это «минимум две» и есть вся история надёжности, потому что AZ — это радиус поражения, который AWS позволяет отказать независимо. Внутри VPC подсети делятся на две зоны доверия. Публичные подсети (по одной на AZ) держат только обращённый в интернет край: Application Load Balancer и NAT Gateways. Приватные подсети держат всё, что никогда не должно быть напрямую доступно: Fargate-таски приложения и базу RDS. Правило безжалостно просто — если у ресурса есть маршрут к internet gateway, он открыт; у базы его быть не должно.

Путь запроса рассказывает всю историю. Пользователь обращается к твоему apex-домену; Route 53 разрешает его (ALIAS-запись на apex зоны — так указывают голый домен на ресурс AWS, ведь на apex нельзя сделать CNAME). Запрос попадает на CloudFront, который терминирует TLS на краю по сертификату ACM, отдаёт закэшированные статические ассеты и пробрасывает динамические запросы на Application Load Balancer. ALB живёт в публичных подсетях, слушает HTTPS и маршрутизирует в target group из Fargate-тасков в приватных подсетях. Таск выполняет твоё приложение и, чтобы делать свою работу, говорит с двумя бэкендами: RDS (Postgres, Multi-AZ) для реляционных данных и S3 для объектов. Ответ уходит обратно той же цепочкой. Каждая стрелка на этом пути — ещё и граница, которую ты можешь запереть.

Каждый тир маппится на управляемый сервис не просто так — мышцы крутишь не ты, а AWS, и своё внимание ты тратишь на границы.

ТирСервисПочему именно он
Край / DNSRoute 53 + CloudFront + ACMALIAS apex на цель AWS; TLS терминируется на краю; статические ассеты кэшируются вне origin
БалансировкаApplication Load BalancerL7 HTTPS, health-чеки, единственный публичный вход в приложение; разносит трафик по AZ
App (вычисления)ECS на FargateServerless-контейнеры в приватных подсетях, нет нод под патчинг, автомасштаб по нагрузке, IAM task-role
Данные (реляционные)RDS Postgres, Multi-AZСинхронный standby во второй AZ, автоматический failover; доступен только из app SG
Данные (объекты)S3 + VPC gateway endpointДолговечное объектное хранилище; приватный путь через gateway endpoint; presigned URL или CloudFront OAC для отдачи
EgressNAT Gateway (на каждую AZ)Даёт приватным таскам выйти в интернет за образами/патчами, оставаясь недостижимыми входящим

Безопасность: границы, ссылающиеся друг на друга

Модель безопасности — это defense in depth, хребет Well-Architected Security Pillar: много слоёв, каждый бесполезно обходить в одиночку. Первый слой — разделение подсетей: у RDS в приватной подсети просто нет маршрута в интернет, поэтому «открытый порт для всего мира» перестаёт быть возможным. Второй — security groups, ссылающиеся друг на друга по ID, а не по CIDR. Ты пишешь три: ALB SG разрешает 443 из интернета; app SG разрешает трафик только из ALB SG; DB SG разрешает 5432 только из app SG. Никто не вбивает диапазон IP. Цепочка — интернет, ALB SG, app SG, DB SG, и каждое звено наименее привилегированно по построению — ровно тот провал, в который угодил стартап, только теперь структурно невозможный.

Остальные слои укладываются сверху. Секреты (пароль базы, API-ключи) живут в Secrets Manager и инжектятся в таск во время выполнения, никогда не запекаются в образ или env-файл в git. Шифрование в покое даёт KMS на RDS и S3; шифрование в транзите — TLS от края до края: ACM на CloudFront, а хоп app-to-RDS использует RDS CA. Task role у Fargate — наименее привилегированная IAM-роль, дающая ровно те бакеты S3 и пути Secrets Manager, что нужны этому сервису, и ничего больше, так что скомпрометированный контейнер не сможет распространиться по аккаунту. Ничто во всей системе не доступно публично, кроме CloudFront и ALB.

# Урезанный CloudFormation: границы безопасности — это и есть весь смысл.
Resources:
  AlbSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !Ref Vpc
      SecurityGroupIngress:
        - { IpProtocol: tcp, FromPort: 443, ToPort: 443, CidrIp: 0.0.0.0/0 }  # ALB = единственный публичный вход

  AppSG:                       # Fargate-таски — только приватные подсети
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !Ref Vpc
      SecurityGroupIngress:
        - { IpProtocol: tcp, FromPort: 8080, ToPort: 8080, SourceSecurityGroupId: !Ref AlbSG }

  DbSG:                        # RDS — доступен app-tier и больше никому
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !Ref Vpc
      SecurityGroupIngress:
        - { IpProtocol: tcp, FromPort: 5432, ToPort: 5432, SourceSecurityGroupId: !Ref AppSG }

  Db:
    Type: AWS::RDS::DBInstance
    Properties:
      Engine: postgres
      MultiAZ: true                          # синхронный standby во 2-й AZ, авто failover
      StorageEncrypted: true                 # KMS в покое
      PubliclyAccessible: false              # никогда не публичный IP
      DBSubnetGroupName: !Ref PrivateDbSubnets
      VPCSecurityGroups: [ !Ref DbSG ]
      ManageMasterUserPassword: true         # пароль живёт в Secrets Manager, не в шаблоне

Это infrastructure as code намеренно. Весь VPC, каждая подсеть, каждое правило SG, ALB, сервис Fargate и инстанс RDS объявлены в одном проверяемом шаблоне (CloudFormation, CDK или Terraform). Инцидент с открытой базой случился потому, что человек прогнал разовое изменение через консоль, которое никто не ревьюил и никто не мог увидеть позже. Когда архитектура — это код, такой дрейф всплывает в diff и ловится на ревью — правило SG выше не сможет тихо стать 0.0.0.0/0 без того, чтобы кто-то одобрил pull request.

Устойчивость и наблюдаемость: пережить AZ и увидеть это

Провал с одной AZ из хука исправлен структурно. ALB охватывает обе публичные подсети; Fargate-таски бегут по обеим приватным подсетям; а RDS Multi-AZ держит синхронный standby во второй AZ. Согласно докам RDS, репликация в этот standby синхронна, так что закоммиченная запись лежит в обеих AZ до того, как клиент получит ack, и при отказе primary AWS выполняет автоматический failover, переключая DNS-эндпоинт базы на standby — твоё приложение переподключается к тому же хостнейму. Цена этой безопасности реальна: синхронная репликация добавляет латентность записи/коммита против single-AZ, и ты платишь за standby. Это и есть центральный трейдофф устойчивости, и для production data-tier его почти всегда стоит платить. (Заметь: standby не обслуживает чтения — он существует для failover; если нужно масштабирование чтений, добавляют read replicas или Multi-AZ cluster.)

Нельзя эксплуатировать то, чего не видишь, поэтому наблюдаемость встроена с самого начала, как предписывает detection-руководство Well-Architected. CloudWatch собирает метрики и логи со всех тиров; алармы следят за сигналами, которые реально значат «пользователям больно»: HTTPCode_ELB_5XX_Count у ALB, утилизация CPU/памяти Fargate (она же гонит автомасштаб) и DatabaseConnections и свободное место у RDS. Алармы срабатывают в топик SNS, который пейджит человека. X-Ray трассирует запрос через CloudFront, ALB, Fargate, RDS, чтобы при скачке латентности ты видел, чей это тир, а не гадал. Смысл того, что мы раньше прошли путь запроса, ровно в этом: каждый хоп — место, где должны существовать метрика, лог и сегмент трассы.

Почему это работает

Почему NAT Gateway на каждую AZ, а не один общий? NAT Gateway живёт в одной AZ. Если ты маршрутизируешь обе приватные подсети через один NAT в AZ-A и AZ-A падает, твои таски в AZ-B теряют выход в интернет, хотя их собственная AZ здорова — ты заново внёс единую точку отказа ради экономии денег. Один NAT на AZ убирает эту связанность. Трейдофф — стоимость: NAT Gateway тарифицируются за час и за обработанный ГБ, так что два штуки плюс egress-данные — это статья расходов, о которой искренне забывают, пока не придёт счёт. Для трафика, который говорит только с сервисами AWS (S3, DynamoDB), VPC gateway endpoint обходит NAT целиком и бесплатен — поэтому путь к S3 в этом дизайне его и использует.

Стоимость: тот же дизайн, в цене

Корректная архитектура, которую ты не можешь себе позволить, под давлением переписывается в некорректную, поэтому стоимость — часть дизайна, а не запоздалая мысль. App-tier на Fargate крутит постоянный baseline плюс запас под автомасштаб; baseline ты покрываешь Compute Savings Plan (обязательство на 1 или 3 года, дисконтирующее и Fargate, и Lambda) и даёшь on-demand гасить всплески. Standby Multi-AZ у data-tier примерно удваивает стоимость compute RDS — это премия за доступность, и это единственное место, где ты не режешь. Часы NAT Gateway и egress за ГБ — коварная статья; per-AZ NAT нужны ради устойчивости, но их счёт ты режешь, отправляя AWS-направленный трафик (S3) через бесплатный gateway endpoint и держа болтливый межзональный трафик в узде. S3 получает lifecycle-правила, чтобы перекладывать холодные объекты в более дешёвые storage-классы. Подгоняй vCPU/память Fargate-тасков и класс инстанса RDS под реальную утилизацию из CloudWatch, а не гадая — та наблюдаемость, что ты построил ради надёжности, заодно говорит, где у тебя перевыделение.

Выбери лучший вариант

Production B2B SaaS гоняет свой основной транзакционный Postgres на single-AZ инстансе RDS ради экономии. Руководство хочет знать, переводить ли data-tier на Multi-AZ. Выбери самую обоснованную позицию.

Викторина

В запертом дизайне как должна быть настроена security group базы RDS, чтобы app-tier мог подключаться на 5432?

Викторина

Fargate-таски приложения живут в приватных подсетях и должны тянуть обновления и достучаться до внешних API через интернет, но не должны быть достижимы из интернета входящим. Что это обеспечивает?

Вспомните перед уходом
  1. 01
    Пройди полный путь запроса этого three-tier дизайна от пользователя к data-tier, называя сервис и границу на каждом хопе.
  2. 02
    Назови границы устойчивости и безопасности, которых не хватало упавшему 'three-tier' приложению из хука, и как этот дизайн исправляет каждую.
Итог

Настоящая three-tier архитектура определяется границами, а не рисованием трёх коробок. Всё живёт в одном VPC через минимум две Availability Zones, разделённом на публичные подсети, держащие только обращённый в интернет ALB и NAT Gateways, и приватные подсети, держащие Fargate-таски приложения и базу RDS. Путь запроса идёт Route 53 ALIAS apex, затем CloudFront с ACM TLS и кэшем, затем публичный ALB на HTTPS, затем target group из Fargate-тасков в приватных подсетях, затем RDS Postgres Multi-AZ и S3 через VPC gateway endpoint, и каждая из этих стрелок — граница доверия. Безопасность — defense in depth: security groups, ссылающиеся друг на друга по ID, образуют цепочку интернет, ALB SG, app SG, DB SG, так что база доступна ничему, кроме app-tier; секреты живут в Secrets Manager, данные шифруются в покое через KMS и в транзите через TLS, task role у Fargate наименее привилегированна, и ничто не публично, кроме CloudFront и ALB. Устойчивость — Multi-AZ насквозь, с RDS, держащим синхронный standby во второй AZ и делающим failover автоматически переключением эндпоинта базы — ценой добавленной латентности записи и платного standby, той премии, что стоит платить. Наблюдаемость связывает метрики, логи и алармы CloudWatch (5xx ALB, CPU Fargate, соединения RDS) с пейджингом через SNS плюс трассировку X-Ray через тиры, и весь стек — infrastructure as code, чтобы дрейф вроде открытой security group базы ловился в diff pull request’а, а не на аудите через полтора года. Стоимость тоже заложена в дизайн: Compute Savings Plan на baseline Fargate, осознанно принятая премия Multi-AZ, per-AZ NAT, компенсированные бесплатным gateway endpoint к S3, lifecycle-тиринг S3 и right-sizing по тем же метрикам, что ты собираешь ради надёжности. У стека стартапа было три коробки и ноль границ; навык — мочь ткнуть в каждую подсеть, каждую security group и каждую AZ и сказать ровно, от чего она защищает. Теперь, когда встретишь архитектуру под названием «three-tier», задай этот вопрос первым — и если никто не может ответить, значит, ты только что нашёл объём работы.

Практика

Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 5 завершено

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

Примени это

Примени этот урок в реальном проекте.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources4
expand
  1. 01
  2. 02
  3. 03
  4. 04

Trademarks belong to their respective owners. Editorial reference only.