Перейти к содержимому

AWS в глубину: от региона до Lambda

В краткой версии ты видел AWS как набор сервисов: EC2 — виртуалка, S3 — файлы, RDS — база. Здесь мы смотрим под капот: как физически устроено облако, почему один регион — это минимум три дата-центра, как IAM решает, кому что можно, и где именно в VPC «живёт» твоё приложение. Это знание отличает человека, который кликает в консоли, от инженера, который проектирует инфраструктуру.

Истории из практики начинаются одинаково: «приложение лежало, потому что мы запустили его в одной AZ, а AZ ушла на профилактику» или «счёт вырос в пять раз, потому что NAT Gateway обрабатывал трафик между двумя инстансами в одной подсети». Понимание устройства AWS — это страховка от обоих сценариев. Бесплатная инженерная классика от самих Amazon — AWS Builders’ Library: статьи о том, как устроены их распределённые системы внутри.

Глобальная инфраструктура: регионы и зоны доступности

Заголовок раздела «Глобальная инфраструктура: регионы и зоны доступности»

AWS делит мир на регионы (Regions) — изолированные географические локации (eu-west-1 — Ирландия, eu-central-1 — Франкфурт). Внутри региона — зоны доступности (Availability Zones, AZ): это отдельные дата-центры с собственным питанием, охлаждением и сетью, соединённые между собой быстрыми каналами. Обычно в регионе 3 AZ, но их количество не раскрывается точно — ты видишь только eu-west-1a, eu-west-1b и т.д.

Ключевой принцип: AZ изолируют отказы. Дата-центр горит, страдает питание, режутся кабели — сбой одной AZ не должен уронить сервис. Поэтому production-архитектура всегда растянута минимум по двум AZ:

Регион eu-west-1
├─ AZ-a ├─ AZ-b
│ ┌──────────┐ │ ┌──────────┐
│ │ EC2 app-1│ │ │ EC2 app-2│
│ └────┬─────┘ │ └────┬─────┘
│ │ │ │
│ ┌────▼─────┐ │ ┌────▼─────┐
│ │ RDS (мастер, Multi-AZ)│ ◄┤ standby │
│ └──────────┘ синхр. │ └──────────┘
│ │ реплика в другой AZ,
│ │ failover за минуты

Сверху над регионами — Edge Locations сеть CloudFront: сотни точек присутствия по миру, которые кэшируют статику ближе к пользователю. Регион выбирают по трём критериям: близость к пользователям (задержка), юрисдикция данные (GDPR: данные европейцев — в Европе) и наличие нужных сервисов (не всё появляется во всех регионах одновременно).

IAM — фундамент безопасности AWS, и его ломают чаще всего. Модель сущностей и язык политик разобраны в IAM User Guide. Три сущности:

  • Пользователи — люди. У каждого свой логин, пароль и (обязательно) MFA. Root-аккаунт запирается: MFA, длинный пароль, никакой повседневной работы из-под него.
  • Роли (Roles) — не люди, а «должности». EC2-инстанс, Lambda-функция или GitHub Actions могут взять на себя роль и получить её временные ключи (автоматически ротируются). Никаких access key в коде.
  • Политики (Policies) — JSON-документы, описывающие разрешения. Принцип минимальных прав: разрешаем только то, что нужно, только на те ресурсы, к которым есть доступ.
// Политика: приложение может читать и писать только один бакет
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "AppAccessToUploadsOnly",
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
"Resource": "arn:aws:s3:::myapp-uploads-prod/*"
}]
}

ARN — единый идентификатор ресурса: arn:aws:s3:::myapp-uploads-prod/* читается как «все объекты внутри бакета». Можно сузить до конкретного префикса (.../tmp/*) или конкретного ключа.

Окно терминала
# Привязать роль к инстансу при запуске — вместо ключей в коде
aws ec2 run-instances \
--image-id ami-0c02fb55956c7d316 \
--instance-type t3.micro \
--iam-instance-profile Name=app-role-profile
# Проверить, какие политики прикреплены к роли
aws iam list-attached-role-policies --role-name app-role

EC2 — это KVM-виртуалки на серверах AWS. Три вещи, которые надо понимать глубже, чем «выбрал образ — запустил».

Типы инстансов. Буква в названии — семейство: t — burstable (дёшево, копит CPU-кредиты, всплески до 100% на короткое время), m — general purpose (сбалансировано), c — compute-optimized (много CPU на RAM), r — memory-optimized (большие кэши, БД), i/d — локальные NVMe-диски. Число — поколение: t3 старше и дешевле, t4g — на ARM Graviton (на ~20% дешевле x86 при той же производительности — проверяй совместимость бинарников). Полная таблица типов и семейств — в EC2 User Guide.

User-data — скрипт, который выполняется при первом старте. Это и есть «bootstrap» инстанса: обновить пакеты, поставить Docker, склонировать и запустить приложение.

# user-data.sh — передаётся через --user-data file://user-data.sh
#!/bin/bash
set -euo pipefail
apt-get update && apt-get install -y docker.io
usermod -aG docker ubuntu
docker run -d --name app -p 80:3000 \
-e DATABASE_URL="$DATABASE_URL" \
ghcr.io/darkpix/petapp:latest

Длинный user-data превращается в мусорку. Как только скрипт больше ~30 строк — собирай AMI (свой образ с предустановленным всем) через Packer или System Manager. Старт из готового AMI быстрее и воспроизводимее.

Security Groups — stateful-файрвол на уровне инстанса: разрешаешь входящее — ответы разрешены автоматически, обратные правила не нужны. Правило вида «вход с 0.0.0.0/0 на 22-й порт» — топ-1 причина взломанных инстансов. Доступ по SSH — только с твоего IP или через VPN/bastion в приватной подсети.

Окно терминала
aws ec2 authorize-security-group-ingress \
--group-id sg-0abc123 \
--protocol tcp --port 22 \
--cidr 203.0.113.45/32 # только мой IP, не весь интернет
# Посмотреть, что сейчас разрешено
aws ec2 describe-security-groups --group-ids sg-0abc123

S3: хранилище объектов и его жизненный цикл

Заголовок раздела «S3: хранилище объектов и его жизненный цикл»

S3 хранит объекты (файл + метаданные) в бакетах с уникальным глобальным именем. Под капотом — это не диски, а распределённая система с 11 девятками доступности: твои объекты реплицированы минимум по трём AZ автоматически. Ты за это не платишь отдельно — в цену входит. Версионирование, lifecycle и классы хранения подробно описаны в S3 User Guide.

Три механизма, которые нужно знать до продакшена:

Версионирование — S3 сохраняет каждую версию объекта при перезаписи. Удалил файл — на самом деле поставил delete-маркер, старая версия на месте. Защита от «перезаписал бэкап сломанным архивом» и от ransomware, который шифрует и удаляет оригиналы.

Lifecycle-политики — автоматические правила перехода между классами хранения: через 30 дней объекты уходят в Standard-IA (редкий доступ, дешевле хранение, дороже чтение), через 90 — в Glacier, через 365 — в Deep Archive. Плюс правила удаления старых версий, иначе версионирование превратит бакет в свалку.

// lifecycle.json — горячие данные остывают сами
{
"Rules": [{
"Id": "archive-backups",
"Status": "Enabled",
"Filter": { "Prefix": "backups/" },
"Transitions": [
{ "Days": 30, "StorageClass": "STANDARD_IA" },
{ "Days": 90, "StorageClass": "GLACIER" },
{ "Days": 365, "StorageClass": "DEEP_ARCHIVE" }
],
"NoncurrentVersionExpiration": { "NoncurrentDays": 30 }
}]
}

Presigned URLs — временная подпись, дающая право скачать (или загрузить) объект без AWS-ключей. Классика: клиент просит у API ссылку «загрузить аватар», API вызывает generate_presigned_url с TTL 15 минут, клиент грузит файл напрямую в S3, минуя твой сервер.

# Python + boto3: выдать ссылку на загрузку на 15 минут
import boto3
s3 = boto3.client("s3")
url = s3.generate_presigned_url(
"put_object",
Params={"Bucket": "myapp-uploads-prod", "Key": f"avatars/{user_id}.jpg",
"ContentType": "image/jpeg"},
ExpiresIn=900,
)
# url — обычный HTTPS URL, работает без авторизации, живёт 15 минут

RDS — управляемый PostgreSQL/MySQL: AWS патчит, бэкапит (снапшоты + WAL-архивы, point-in-time recovery до 35 дней назад) и делает failover. Механики Multi-AZ и read replicas описаны в RDS User Guide. Но два механизма резервирования решают разные задачи, и путать их — классическая ошибка:

Multi-AZ Read Replica
Цель Отказоустойчивость Масштабирование чтения
Репликация Синхронная Асинхронная (лаг секунды)
При failover DNS-имя переключается на standby автоматически, 1–2 минуты Не участвует, пока ты сам не промотишь
Читать с неё Нельзя Можно, с лага
Цена ~2x инстанса ~1x инстанса за реплику
Окно терминала
# Postgres 16, мультизона, шифрование, автообновление минорных версий
aws rds create-db-instance \
--db-instance-identifier petdb \
--engine postgres --engine-version 16 \
--db-instance-class db.t4g.micro \
--allocated-storage 20 --storage-encrypted \
--multi-az \
--master-username appuser \
--manage-master-user-password \ # пароль в Secrets Manager, не в истории bash
--backup-retention-period 7 \
--db-subnet-group-name private-subnets

Lambda запускает код по событию без серверов: HTTP (через API Gateway), загрузка объекта в S3, сообщение в SQS/Stream, расписание (EventBridge). Модель оплаты — за миллионы запросов и гигабайт-секунды памяти. Тысячи вызовов в день обойдутся в копейки; постоянно работающий API на Lambda обойдётся дороже, чем контейнеры. Лимиты и модель исполнения — в документации Lambda.

Лимиты, которые реально встречаются: память 128 МБ – 10 ГБ (CPU выдаётся пропорционально), максимум 15 минут выполнения (дольше — нельзя, задачу надо дробить), пакет кода 250 МБ, развёртывание 1000 одновременных инстансов по умолчанию (мягкий лимит, поднимается запросом).

Холодный старт — первый вызов после простоя (или при масштабировании) тратит время на подготовку окружения: сотни миллисекунд для Node.js, до нескольких секунд для Java или тяжёлых зависимостей. Митигация: держать функцию тёплой (пинг раз в 5 минут — костыль, но работает), Provisioned Concurrency (AWS сам держит N готовых окружений — стоит денег), миниатюрные рантаймы и ленивая загрузка зависимостей.

// index.mjs — Node.js 20, ленивая инициализация: тяжёлый импорт внутри обработчика
let db; // коннект создаётся один раз и живёт между вызовами тёплого инстанса
export const handler = async (event) => {
db ??= await createConnection(); // только при первом вызове
const { key } = event.Records[0].s3.object;
// обработка...
return { statusCode: 200 };
};

VPC — твой изолированный участок облака с выбранным адресным пространством (например, 10.0.0.0/16). Ты делишь его на подсети, каждая подсеть привязана к одной AZ и получает route table — таблицу маршрутизации. Подсеть с маршрутом 0.0.0.0/0 → Internet Gatewayпубличная: из неё виден интернет (и интернет видит инстанс, если Security Group разрешает). Без этого маршрута — приватная: входящих извне нет, исходящих тоже, пока не добавить NAT.

Internet
┌───────▼────────┐
│ Internet Gw │
└───┬────────┬───┘
│ │ (только из публичной)
┌─────────▼───┐ │
│ Route: │ │
│ 0.0.0.0/0 │ │
│ → IGW │ │
└──────┬──────┘ │
┌───────────────┼───────────┼───────────────────┐
│ VPC 10.0.0.0/16 │ │
│ ┌───▼────┐ │
│ ┌─────────────────┐ │ NAT │ исходящий │
│ │ Public subnet │ │ Gateway│ трафик из │
│ │ 10.0.1.0/24 │ └───┬────┘ приватной │
│ │ │ │ подсети │
│ │ ALB / bastion │ │ маскируется│
│ └────────┬────────┘ │ публичным │
│ │ │ IP NAT │
│ ┌────────▼────────┐ ◄────┘ │
│ │ Private subnet │ Route: 0.0.0.0/0 │
│ │ 10.0.2.0/24 │ → NAT Gateway │
│ │ │ │
│ │ EC2 (app) │ │
│ │ RDS (база) │ │
│ └─────────────────┘ │
└───────────────────────────────────────────────┘

Почему это важно: база данных в приватной подсети не имеет публичного IP и физически недостижима из интернета — независимо от того, кто взломал твоё приложение. Приложение из приватной подсети ходит наружу (обновления, вызовы API) через NAT Gateway, который заменяет их частные IP на свой публичный.

CloudWatch собирает метрики (CPU, сетевой трафик, латентность ALB), логи (агент на инстансе или встроенная интеграция Lambda) и алерты. Метрики базовые приходят бесплатно каждые 5 минут; детализация до 1 минуты — платная опция. Логи — отдельная строка расходов: один балбечущий сервис может писать гигабайты в день.

Окно терминала
# Алерт: CPU выше 80% два периода подряд (10 минут) → SNS → email/Telegram
aws cloudwatch put-metric-alarm \
--alarm-name high-cpu \
--metric-name CPUUtilization --namespace AWS/EC2 \
--dimensions Name=InstanceId,Value=i-0abc123 \
--statistic Average --period 300 \
--evaluation-periods 2 --threshold 80 \
--comparison-operator GreaterThanThreshold \
--alarm-actions arn:aws:sns:eu-west-1:123:alerts-topic
# Подписка топика на email
aws sns subscribe --topic-arn arn:aws:sns:eu-west-1:123:alerts-topic \
--protocol email --notification-endpoint oncall@example.com
# Греп по логам Lambda за последний час
aws logs filter-log-events \
--log-group-name /aws/lambda/my-func \
--start-time $(($(date +%s%3N) - 3600000)) \
--filter-pattern "ERROR"

AWS — индустриальный инструмент: сотни сервисов, сложность, цена за гибкость. Для pet-проекта, MVP и большинства маленьких SaaS — избыточен.

Критерий AWS Hetzner Cloud DigitalOcean
VPS 2 CPU / 4 GB ~$30–40/мес (t3.medium + трафик) ~€5/мес, трафик 20 ТБ включён ~$24/мес, трафик включён
Управляемая БД RDS от ~$15/мес + storage нет (поднимай сам) Managed PG от $15/мес
Объектное хранилище S3, $0.023/ГБ нет Spaces, $5/мес за 250 ГБ
Сложность высокая: IAM, VPC, 200+ сервисов низкая: проект → сервер → firewall низкая: аналогично
Масштаб/фичи бесконечный до сотен серверов до сотен серверов
Биллинг-сюрпризы да (NAT, egress, логи) почти нет: фикс цена почти нет

Прагматичная стратегия: учись на Hetzner/DO (концепции те же: виртуалка, снапшот, приватная сеть, firewall, managed БД), а AWS используй, когда проект перерос простое — нужны очереди, распределённые системы, соответствие требованиям Enterprise-клиентов или команда, которая уже живёт в AWS. Многие компании строят гибрид: вычисления на Hetzner, бэкапы в S3, CDN на CloudFront.

  1. SSH открыт на весь интернет. 0.0.0.0/0:22 в Security Group — боты подбирают пароли в течение часов после создания инстанса. Разреши только свой IP, лучше — вообще убери SSH наружу и ходи через AWS Systems Manager Session Manager (бастион без открытых портов).
  2. Одна AZ для «экономии». Экономия в 50% стоимости инстанса оборачивается полным даунтаймом на время падения AZ (обычно 30–60 минут, бывали часы). Multi-AZ для базы — не опция, а норма.
  3. Версионирование S3 без lifecycle. Через год в бакете — сотни тысяч старых версий, и счёт за хранение вырос в десять раз. Всегда добавляй NoncurrentVersionExpiration.
  4. Ключи в коде вместо ролей. Access key в репозитории — это ключ от всего облака (если политика широкая). GitHub сканирует утечки, но сканирование — реакция, а не профилактика. На EC2 и Lambda — только IAM-роли.
  5. Холодные старты игнорируют. API на Lambda с p99 в 4 секундах из-за стартов — типичный результат. Меряй реальную латентность, а не среднюю; средняя спрячет проблему.
  6. CloudWatch-логи без retention. По умолчанию логи хранятся бесконечно. Один цикл с ошибками = десятки гигабайт = счёт за хранение. Ставь retention 7–30 дней: aws logs put-retention-policy --log-group-name ... --retention-in-days 30.
  1. Чем Multi-AZ отличается от Read Replica в RDS? Multi-AZ — синхронная standby-копия только для failover, читать с неё нельзя; Read Replica — асинхронная копия для разгрузки чтения с возможным лагом. Первое про доступность, второе — про масштабирование.
  2. Почему базу кладут в приватную подсеть? Чтобы у неё не было публичного маршрута вообще: защита не на уровне пароля, а на уровне сетевой топологии. Даже при компрометации приложения злоумышленник не может подключиться к БД извне.
  3. Как дать приложению на EC2 доступ к S3 без ключей? Создать IAM-роль с политикой на конкретный бакет, прикрепить к инстансу через instance profile. Ключи временные, ротируются, в коде ничего нет.
  4. Что такое холодный старт Lambda и как с ним бороться? Задержка первого вызова из-за инициализации окружения. Лечится миниатюрными рантаймами, ленивой загрузкой, Provisioned Concurrency и архитектурно — не кладя латентно-чувствительные пути на Lambda.
  5. Зачем NAT Gateway, если есть Internet Gateway? IGW даёт инстансам публичные IP и двусторонний доступ из интернета. Приватные подсети не должны быть видны снаружи, но им нужен исходящий доступ — его даёт NAT, подменяя частные адреса своим публичным.
  6. S3 реплицирует данные между AZ. Почему тогда нужно версионирование? AZ-репликация защищает от потери дата-центра, но не от человека: перезаписал или удалил файл — перезаписался везде. Версионирование защищает от ошибок и ransomware.
  1. Заведи AWS-аккаунт (или используй учебный), первым делом настрой MFA для root, создай IAM-пользователя с правами администратора для работы, включи Budgets с алертами на $10. Только после этого создавай ресурсы.
  2. Собери VPC с нуля через CLI или Terraform: 10.0.0.0/16, по одной публичной и приватной подсети в двух AZ, NAT Gateway, route tables. Подними t3.micro в приватной подсети и докажи, что из интернета до него не достучаться, а она сама выходит наружу через NAT (curl ifconfig.me с неё покажет IP NAT).
  3. Подними RDS Postgres с Multi-AZ в тех же приватных подсетях. Приложение — в публичной подсети за ALB (или хотя бы с Security Group, ограничивающим доступ к БД по группе приложения). Проверь failover: aws rds reboot-db-instance --db-instance-identifier petdb --force-failover и понаблюдай за DNS.
  4. Создай S3-бакет с версионированием и lifecycle-политикой из примера выше. Напиши скрипт бэкапа: pg_dump | gzip | aws s3 cp - s3://bucket/backups/$(date +%F).sql.gz. Затем «случайно» перезапиши бэкап пустым файлом и восстанови старую версию через aws s3api list-object-versions.
  5. Напиши Lambda на Node.js, триггер — загрузка файла в S3 (префикс incoming/), функция копирует его в processed/ и пишет лог. Замерь холодный и тёплый старт через CloudWatch Logs (добавь console.log(Date.now() - start)).
  • AWS Well-Architected Framework — шесть столпов: операционное совершенство, безопасность, надёжность, эффективность, оптимизация стоимости, устойчивость.
  • AWS VPC Documentation — официальное описание сетевой модели.
  • IAM Best Practices — рабочий чек-лист по правам.
  • Hetzner Cloud Docs и DigitalOcean Docs — та же модель, проще изложенная.
  • AWS CLI Command Reference — все команды из этой главы с полным списком параметров.