CI/CD, инфраструктура и облака AWS: базовые сервисы, модель ответственности, сеть, стоимость, типичные ошибки
0%

AWS: базовые сервисы, модель ответственности, сеть, стоимость, типичные ошибки

AWS: базовые сервисы, модель ответственности, сеть, стоимость, типичные ошибки

В предыдущей статье — https://courses.digitable.life/post/devops/10-configuration-management/ — мы разбирали, как приводить машины в нужное состояние. Но сами машины откуда-то берутся. В https://courses.digitable.life/post/devops/09-terraform-and-iac/ мы описывали инфраструктуру кодом, не углубляясь в то, что именно этот код создаёт. Настало время открыть коробку.

AWS — не «сервер в аренду». Это примерно двести сервисов, каждый со своей моделью тарификации, своими границами отказа и своим представлением о том, кто за что отвечает. Человек, который умеет запустить EC2, но не понимает, почему счёт вырос на 900 $ без единого нового инстанса, не умеет AWS. Эта статья — про модель, а не про кнопки: про то, что происходит с пакетом, запросом и долларом.

Оговорка про цены: все цифры — регион us-east-1/eu-central-1, порядок величины на момент написания. AWS меняет прайс, вводит новые классы и иногда — как с платными IPv4-адресами в 2024-м — добавляет строки, которых раньше не было. Проверяйте калькулятор и текущие прайс-листы. Важны не конкретные центы, а соотношения: почему одно в двадцать раз дороже другого.

Каркас: аккаунт, регион, зона доступности

Три уровня, которые определяют почти всё остальное.

Аккаунт — граница биллинга, квот и (главное) взрыва. Всё в AWS по умолчанию доступно внутри одного аккаунта и недоступно между аккаунтами. Это не «проект» в терминах GCP и не «подписка» Azure — это самая жёсткая изоляция, которую предлагает платформа. Отсюда практика: прод и стейдж живут в разных аккаунтах, а не в разных VPC. Ошибка в IAM-политике стейджа не должна иметь возможности дотянуться до прод-базы физически, а не по договорённости.

Регион — географически разделённый набор дата-центров со своей плоскостью управления. Регионы почти полностью независимы: S3-бакет в eu-central-1 не виден API в us-east-1 без явной репликации, отказ региона не распространяется. Исключения важны: IAM, Route 53, CloudFront и биллинг — глобальные, их control plane физически живёт в us-east-1. Поэтому крупный сбой us-east-1 исторически ломает вещи по всему миру.

Зона доступности (AZ) — один или несколько ДЦ внутри региона, связанных сетью с задержкой ~1 мс. AZ — это единица отказа, вокруг которой построена вся модель надёжности AWS. У каждого аккаунта имена AZ перемешаны: ваша eu-central-1a и eu-central-1a соседа — разные физические зоны (устойчивый идентификатор — AvailabilityZoneId, например euc1-az2).

Модель разделяемой ответственности: где заканчивается AWS

Формулировка AWS — «безопасность облака — на нас, безопасность в облаке — на вас» — верна, но бесполезна, пока не приземлена на конкретный сервис. Реальная граница ползёт в зависимости от уровня управляемости.

Что EC2 RDS Fargate S3 Lambda
Физика, гипервизор, сеть ДЦ AWS AWS AWS AWS AWS
ОС хоста и её патчи AWS AWS AWS AWS AWS
Гостевая ОС, ядро, патчи вы AWS AWS AWS
Рантайм и его CVE вы AWS вы (образ) AWS (managed runtime)
Конфигурация сервиса вы вы (параметр-группы) вы вы (политики бакета) вы
Сетевой доступ (SG, политики) вы вы вы вы вы
Шифрование и ключи вы вы (включить) вы вы (включить) вы
Данные и их классификация вы вы вы вы вы
Резервные копии и их проверка вы вы (настроить + проверить восстановление) вы (версионирование)
Доступность приложения вы вы (Multi-AZ — выбор) вы AWS вы

Три следствия, которые стоят денег и нервов:

  1. Управляемость не равна отказоустойчивости. RDS Single-AZ — управляемая база, которая недоступна 10–20 минут при отказе хоста и теряет данные при отказе тома, если нет свежего снапшота. Multi-AZ — это ваш выбор и удвоение цены, не подарок.
  2. AWS никогда не отвечает за ваши данные. «Удалили бакет» — ваша проблема. Отсюда MFA Delete, версионирование, Object Lock и отдельный аккаунт-хранилище бэкапов, куда прод имеет право только писать.
  3. SLA — это скидка, а не гарантия. Compute SLA 99.99 % по региону означает возврат части платы при нарушении, а не то, что сервис не упадёт. Проектируйте под отказ AZ; отказ региона — отдельное решение с отдельным ценником.

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

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

Ключевые сущности:

  • Principal — кто делает запрос: IAM-пользователь, роль (точнее, её сессия), сервис, анонимный вызов.
  • Identity-based policy — политика на принципале («роль может читать этот бакет»).
  • Resource-based policy — политика на ресурсе («бакет разрешает читать этой роли»): S3, SQS, KMS, Lambda, Secrets Manager, ECR.
  • SCP (Service Control Policy) — потолок прав для всего аккаунта из AWS Organizations. Ничего не разрешает, только ограничивает.
  • Permissions boundary — потолок прав для конкретной роли. Инструмент делегирования: «команда может создавать роли, но не мощнее этой границы».
  • Session policy — сужение прав при AssumeRole.

Два правила, из которых выводится всё остальное: по умолчанию запрещено и явный Deny неотменяем. Второе — главный инструмент безопасности: SCP с Deny на ec2:* в регионах, которыми вы не пользуетесь, невозможно обойти даже администратору аккаунта.

Практический SCP, который стоит включить на день первый:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ЗапретитьВсёВнеРазрешённыхРегионов",
      "Effect": "Deny",
      "NotAction": ["iam:*", "organizations:*", "route53:*", "cloudfront:*",
                    "support:*", "sts:*", "budgets:*", "waf:*"],
      "Resource": "*",
      "Condition": {
        "StringNotEquals": { "aws:RequestedRegion": ["eu-central-1", "us-east-1"] }
      }
    },
    {
      "Sid": "ЗапретитьВыключениеАудита",
      "Effect": "Deny",
      "Action": ["cloudtrail:StopLogging", "cloudtrail:DeleteTrail",
                 "guardduty:DeleteDetector", "config:DeleteConfigurationRecorder"],
      "Resource": "*"
    },
    {
      "Sid": "ЗапретитьДолгоживущиеКлючи",
      "Effect": "Deny",
      "Action": "iam:CreateAccessKey",
      "Resource": "*"
    }
  ]
}

Последний блок — самый спорный и самый полезный. Долгоживущие ключи доступа (AKIA...) — источник большинства утечек в AWS. Они попадают в git, в Docker-образы, в Slack. Правильная альтернатива — временные учётные данные через STS, которые AWS выдаёт на 15 минут — 12 часов:

  • EC2 → instance profile (IMDSv2 обязателен, IMDSv1 отключить — он уязвим к SSRF);
  • ECS/Fargate → task role;
  • EKS → IRSA или EKS Pod Identity;
  • Lambda → execution role;
  • CI вне AWS (GitHub Actions, GitLab) → OIDC federation, без единого секрета в настройках репозитория;
  • люди → IAM Identity Center (бывший SSO) с федерацией к вашему IdP.

Как это работает на примере IRSA — самого частого способа дать поду в EKS доступ к S3 без секретов:

Trust policy роли — самое важное место, и именно здесь чаще всего делают дыру:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "Federated": "arn:aws:iam::123456789012:oidc-provider/oidc.eks.eu-central-1.amazonaws.com/id/ABC123" },
    "Action": "sts:AssumeRoleWithWebIdentity",
    "Condition": {
      "StringEquals": {
        "oidc.eks.eu-central-1.amazonaws.com/id/ABC123:aud": "sts.amazonaws.com",
        "oidc.eks.eu-central-1.amazonaws.com/id/ABC123:sub": "system:serviceaccount:production:api"
      }
    }
  }]
}

Если вместо StringEquals для sub написать StringLike с system:serviceaccount:*:*, роль сможет принять любой под любого неймспейса — включая тот, куда разработчик задеплоил тестовый контейнер. Это реальная и очень распространённая ошибка.

Проверять политики нужно не глазами, а симулятором — он учитывает SCP, boundary и условия:

$ aws iam simulate-principal-policy \
    --policy-source-arn arn:aws:iam::123456789012:role/api-prod \
    --action-names s3:GetObject s3:DeleteObject \
    --resource-arns arn:aws:s3:::app-uploads/customer/1.jpg \
    --query 'EvaluationResults[].[EvalActionName,EvalDecision]' --output table
------------------------------------
|    SimulatePrincipalPolicy       |
+------------------+---------------+
|  s3:GetObject    |  allowed      |
|  s3:DeleteObject |  implicitDeny |
+------------------+---------------+

И регулярно смотреть на фактически использованные права — IAM Access Analyzer умеет генерировать политику по CloudTrail за последние 90 дней. Это единственный практичный способ ужать роль, которую год назад создали с *.

Сеть: VPC и её скрытая стоимость

VPC — программно-определяемая сеть с вашим адресным пространством. Модель проста, но детали тарификации превращают невинную топологию в дорогую.

Раскладка VPC на две зоны доступности с указанием тарифицируемых участков

Ключевые понятия:

  • Subnet привязана ровно к одной AZ. «Публичная» — та, у которой в таблице маршрутов есть 0.0.0.0/0 → igw. Никакого флага «public» не существует.
  • Internet Gateway — горизонтально масштабируемый, отказоустойчивый, бесплатный как объект. Платите только за трафик наружу.
  • NAT Gateway — управляемый NAT для приватных подсетей. Стоит ~0,045 $/час (~32 $/мес за факт существования) плюс 0,045 $ за каждый обработанный гигабайт — и входящий, и исходящий. Живёт в одной AZ.
  • Security Group — stateful-фильтр на уровне ENI: разрешил исходящее — ответ придёт. Только Allow-правила.
  • Network ACL — stateless-фильтр на уровне подсети, с Deny. Требует явно открывать эфемерные порты для ответов. Используйте только как грубый аварийный барьер.
Security Group Network ACL
Уровень сетевой интерфейс подсеть
Состояние stateful stateless
Правила только Allow Allow и Deny
Порядок правил все сразу по номеру, первое совпадение
Ссылки на другие SG да (главная суперсила) нет, только CIDR
Типичное применение основной инструмент блокировка IP, изоляция при инциденте

Возможность ссылаться в правиле SG на другую SG («в базу можно с чего угодно, где висит sg-app») убирает необходимость знать IP-адреса и переживает автоскейлинг. Это правильный способ описывать доступ внутри VPC.

Рабочий Terraform для базовой сети с оптимизацией стоимости:

# Сеть на две AZ. Один NAT вместо двух — сознательный компромисс: -32 $/мес,
# но при отказе AZ-a приватные подсети обеих зон теряют выход в интернет.
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 5.13"

  name = "prod"
  cidr = "10.0.0.0/16"

  azs             = ["eu-central-1a", "eu-central-1b"]
  public_subnets  = ["10.0.0.0/20", "10.0.16.0/20"]
  private_subnets = ["10.0.32.0/20", "10.0.48.0/20"]
  database_subnets = ["10.0.64.0/24", "10.0.80.0/24"]

  enable_nat_gateway = true
  single_nat_gateway = true   # для прода с жёстким SLA поставьте false

  enable_dns_hostnames = true

  # Flow logs — единственный способ потом понять, кто нагенерировал трафик.
  # В S3, а не в CloudWatch Logs: дешевле в разы.
  enable_flow_log                = true
  flow_log_destination_type      = "s3"
  flow_log_max_aggregation_interval = 600

  tags = { Environment = "prod", ManagedBy = "terraform" }
}

# Gateway-endpoint для S3 и DynamoDB — БЕСПЛАТЕН и снимает трафик с NAT.
# Обязателен всегда: pull образов из ECR тянет слои именно из S3.
resource "aws_vpc_endpoint" "s3" {
  vpc_id            = module.vpc.vpc_id
  service_name      = "com.amazonaws.eu-central-1.s3"
  vpc_endpoint_type = "Gateway"
  route_table_ids   = module.vpc.private_route_table_ids
}

# Interface-endpoints: 0,01 $/час за каждый в каждой AZ + 0,01 $/ГБ.
# Выгодны, когда через NAT идёт заметный трафик к этим сервисам.
resource "aws_vpc_endpoint" "ecr_dkr" {
  vpc_id              = module.vpc.vpc_id
  service_name        = "com.amazonaws.eu-central-1.ecr.dkr"
  vpc_endpoint_type   = "Interface"
  subnet_ids          = module.vpc.private_subnets
  security_group_ids  = [aws_security_group.endpoints.id]
  private_dns_enabled = true
}

resource "aws_security_group" "endpoints" {
  name   = "vpc-endpoints"
  vpc_id = module.vpc.vpc_id

  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = [module.vpc.vpc_cidr_block]  # только изнутри VPC
  }
}

Арифметика endpoint против NAT, на которой экономят тысячи долларов. Кластер тянет 3 ТБ/мес образов из ECR:

Через NAT:      3000 ГБ × 0,045 $  = 135,00 $/мес
Через endpoint: 2 AZ × 0,01 $/ч × 730 ч = 14,60 $
                + 3000 ГБ × 0,01 $       = 30,00 $   → 44,60 $/мес
Экономия: ~90 $/мес на одном сервисе. Gateway-endpoint для S3 сделал бы это бесплатно.

Тарифы на трафик, которые надо помнить наизусть:

Направление Цена Комментарий
Входящий из интернета 0 $ всегда бесплатно
Исходящий в интернет ~0,09 $/ГБ первые 100 ГБ/мес бесплатно
Между AZ внутри региона 0,01 $/ГБ в каждую сторону то есть 0,02 $ за обмен
Внутри одной AZ по приватным IP 0 $ но по публичным IP — платно
Через NAT Gateway +0,045 $/ГБ сверх всего остального
Между регионами 0,02 $/ГБ
Из S3 через CloudFront 0 $ до CDN egress платится на стороне CloudFront, дешевле

Отсюда конкретика: сервис, который болтает с базой в другой AZ на 4 ТБ/мес, платит 80 $ ни за что. Kubernetes с topologyAwareRouting и правильными topologySpreadConstraints (см. https://courses.digitable.life/post/devops/06-kubernetes/) убирает бо́льшую часть этого трафика. А отдача статики напрямую из ALB вместо CloudFront — это переплата в разы плюс лишняя нагрузка на приложение.

Вычисления: что выбрать под свой масштаб

Четыре модели, между которыми реально выбирают.

EC2 + ASG ECS Fargate EKS Lambda
Единица виртуалка задача-контейнер под вызов функции
Плата за простой control plane 0 0 73 $/мес 0
Цена 2 vCPU / 4 ГБ 24×7 ~30 $ (on-demand) ~72 $ ~30 $ + доля CP зависит от вызовов
Порог входа низкий низкий высокий средний
Эксплуатационная нагрузка патчи ОС, AMI, автоскейлинг почти нулевая высокая (апгрейды, CNI, аддоны) низкая
Холодный старт минуты ~40 с ~40 с (или мгновенно) 50 мс – 5 с
Портируемость средняя низкая (vendor lock) высокая низкая
Когда брать легаси, GPU, особые требования 3–20 сервисов, малая команда много команд, свои платформенные абстракции событийные и редкие нагрузки

Разбор без маркетинга:

Fargate дороже EC2 примерно вдвое за тот же размер — это плата за то, что вам не нужно управлять нодами, думать о bin packing и патчить AMI. Для команды из пяти человек с десятком сервисов это отличная сделка: одна инженерная неделя в месяц стоит дороже, чем 40 $ разницы на сервис. Для кластера из ста подов та же арифметика разворачивается: разница становится тысячами долларов, и появляется смысл в EC2 с Karpenter и Spot.

EKS оправдан не размером нагрузки, а количеством команд. 73 $ за control plane — ерунда; настоящая цена — это апгрейды раз в квартал (AWS поддерживает версию ~14 месяцев, потом extended support по 0,60 $/час — в шесть раз дороже), совместимость CNI, CSI-драйверов, cert-manager и всего зоопарка. Если у вас нет человека, для которого это работа, EKS будет источником инцидентов. ECS в этом смысле честнее: скучнее, беднее, но не ломается сам по себе.

Lambda выигрывает на неровной нагрузке. 1 млн вызовов по 200 мс при 512 МБ: 0,20 $ за запросы + 1 000 000 × 0,2 с × 0,5 ГБ × 0,0000166667 = 1,67 $ за вычисления. Меньше двух долларов. Тот же трафик, размазанный ровно 24×7, дешевле держать на EC2. Точка безубыточности примерно там, где утилизация постоянного инстанса переваливает за 40–50 %.

Spot — самая недооценённая скидка в AWS: 70–90 % от on-demand за инстансы, которые могут отобрать с уведомлением за 2 минуты. Это идеально для CI-раннеров (см. https://courses.digitable.life/post/devops/03-modern-ci-platforms/), пакетной обработки и stateless-подов с запасом реплик.

Практический рецепт устойчивого Spot: диверсификация типов инстансов. Пул из одного m5.large отзовут целиком; пул из пятнадцати похожих типов в трёх AZ со стратегией capacity-optimized переживает отзывы почти незаметно. В Kubernetes это делает Karpenter:

apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: spot-general
spec:
  template:
    spec:
      requirements:
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["spot"]                 # on-demand — в отдельном NodePool ниже приоритетом
        - key: kubernetes.io/arch
          operator: In
          values: ["amd64", "arm64"]       # Graviton дешевле на ~20% при той же производительности
        - key: karpenter.k8s.aws/instance-family
          operator: In
          values: ["m6i", "m6a", "m7i", "m7g", "c6i", "c7g", "r6i"]
        - key: karpenter.k8s.aws/instance-size
          operator: NotIn
          values: ["nano", "micro", "small"]
      nodeClassRef:
        group: karpenter.k8s.aws
        kind: EC2NodeClass
        name: default
      expireAfter: 168h                    # принудительная ротация нод раз в неделю
  disruption:
    consolidationPolicy: WhenEmptyOrUnderutilized
    consolidateAfter: 1m                   # уплотнение: Karpenter сам убирает недозагруженные ноды
  limits:
    cpu: "400"

Отдельно про Graviton (ARM-процессоры AWS): при переходе на m7g вместо m7i цена ниже примерно на 20 % при сопоставимой или лучшей производительности на большинстве серверных нагрузок. Если ваш стек собирается под arm64 — Go, Java, Python, Node — это самая дешёвая оптимизация из существующих. Мультиархитектурные образы разбирались в https://courses.digitable.life/post/devops/05-containers-and-registries/.

Хранение и данные

S3 — не файловая система, а key-value-хранилище объектов с плоским пространством имён. «Папки» — иллюзия консоли, разделитель / в ключе. С 2020 года чтение после записи строго согласованно (read-after-write consistency), что убрало целый класс костылей.

Класс Хранение, $/ГБ·мес Извлечение Минимальный срок Когда
Standard 0,023 0 активные данные
Intelligent-Tiering 0,023 → 0,004 0 доступ непредсказуем
Standard-IA 0,0125 0,01 $/ГБ 30 дней ежемесячный доступ
One Zone-IA 0,010 0,01 $/ГБ 30 дней воспроизводимые данные
Glacier Instant 0,004 0,03 $/ГБ 90 дней архив с редким мгновенным доступом
Glacier Flexible 0,0036 0,01 $/ГБ, минуты–часы 90 дней бэкапы
Deep Archive 0,00099 0,02 $/ГБ, 12 ч 180 дней комплаенс, «никогда не понадобится»

Ловушка минимальных сроков: объект, удалённый из Standard-IA через 5 дней, оплачивается как за 30. Для мелких и короткоживущих файлов IA дороже Standard. Ещё одна: биллинг Standard-IA округляет объекты меньше 128 КБ до 128 КБ — миллион файлов по 4 КБ будет считаться как 128 ГБ.

Рабочая lifecycle-политика с защитой от обеих ловушек:

{
  "Rules": [
    {
      "ID": "логи-в-архив-и-в-мусор",
      "Filter": { "And": { "Prefix": "logs/", "ObjectSizeGreaterThan": 131072 } },
      "Status": "Enabled",
      "Transitions": [
        { "Days": 30,  "StorageClass": "STANDARD_IA" },
        { "Days": 120, "StorageClass": "GLACIER_IR" }
      ],
      "Expiration": { "Days": 730 }
    },
    {
      "ID": "чистить-незавершённые-мультипарт-загрузки",
      "Filter": {},
      "Status": "Enabled",
      "AbortIncompleteMultipartUpload": { "DaysAfterInitiation": 7 }
    }
  ]
}

Второе правило обязательно всегда. Незавершённые multipart-загрузки не видны в листинге бакета, но занимают место и тарифицируются годами. Это одна из самых частых «загадочных» строк в счёте.

EBS — сетевой блочный том, привязанный к одной AZ. Практический вывод один: gp2 устарел.

Тип $/ГБ·мес IOPS Замечание
gp3 0,080 3000 базово, до 16 000 за доплату дефолт по умолчанию
gp2 0,100 3 IOPS/ГБ (нужно 1 ТБ ради 3000) легаси, дороже на 20 %
io2 Block Express 0,125 + 0,065 за IOPS до 256 000 только для тяжёлых СУБД
st1 0,045 throughput-оптимизированный HDD последовательное чтение, логи

Массовая миграция gp2 → gp3 делается онлайн, без остановки, и даёт мгновенные 20 %:

# Найти все gp2-тома и оценить экономию
$ aws ec2 describe-volumes --filters Name=volume-type,Values=gp2 \
    --query 'Volumes[].[VolumeId,Size,State]' --output text | \
    awk '{s+=$2} END {printf "Томов: %d, всего %d ГБ, экономия ~%.2f $/мес\n", NR, s, s*0.02}'
Томов: 47, всего 8400 ГБ, экономия ~168.00 $/мес

# Конвертация без даунтайма
$ aws ec2 modify-volume --volume-id vol-0a1b2c3d --volume-type gp3 --iops 3000 --throughput 125

Базы данных. RDS — это ваша СУБД на управляемом хосте с автоматикой бэкапов и failover. Aurora — переписанный AWS слой хранения под протоколы MySQL/PostgreSQL: шесть копий данных по трём AZ, репликация на уровне журнала, реплики за миллисекунды и failover за десятки секунд вместо минут. Цена — премия ~20 % к инстансу плюс 0,20 $ за миллион операций ввода-вывода, которая на write-heavy нагрузке легко превышает стоимость самих инстансов (в Aurora I/O-Optimized её нет, но базовая цена выше на 30 %).

DynamoDB — другая история: нет соединений, нет схемы, нет джойнов, зато предсказуемая задержка на любом объёме. Требует проектировать под запросы заранее (single-table design), и переделать потом дорого. Берите, когда паттерн доступа известен и стабилен; не берите как «быструю замену PostgreSQL».

Стоимость: как читать счёт и чем на него влиять

Структура счёта AWS: доля сетевой обвязки и логов

Первое правило FinOps в AWS: сначала выключить лишнее, потом оптимизировать размер, и только потом покупать обязательства. Скидка на неиспользуемый ресурс — самая дорогая скидка в мире.

Модели оплаты вычислений:

Модель Скидка Гибкость Риск Кому
On-Demand 0 % полная нет всплески, эксперименты
Spot 70–90 % отзыв за 2 мин потеря узла CI, батчи, stateless
Compute Savings Plans до 66 % любой регион, семейство, EC2/Fargate/Lambda обязательство $/час на 1–3 года дефолтный выбор
EC2 Instance SP до 72 % привязка к семейству и региону то же + жёсткая привязка стабильный крупный кластер
Reserved Instances до 72 % привязка к типу; Convertible — обмен обязательство легаси-подход, RDS/ElastiCache

Практическое правило: покупайте Savings Plans на базовую нагрузку, которая точно останется — обычно 60–70 % от текущего минимума за последние три месяца. Разница между 1 год no-upfront (~28 %) и 3 года all-upfront (~50–66 %) — это ставка на то, что через три года вы будете здесь и на этой архитектуре. Для стартапа это плохая ставка; для банка — хорошая.

Инструменты, которые надо включить в первый день, а не когда счёт удивил:

# 1. Бюджет с алертом на прогноз, а не на факт — узнать заранее
$ aws budgets create-budget --account-id 123456789012 --budget '{
    "BudgetName": "prod-monthly",
    "BudgetLimit": {"Amount": "4000", "Unit": "USD"},
    "TimeUnit": "MONTHLY",
    "BudgetType": "COST"
  }' --notifications-with-subscribers '[{
    "Notification": {"NotificationType":"FORECASTED","ComparisonOperator":"GREATER_THAN",
                     "Threshold":90,"ThresholdType":"PERCENTAGE"},
    "Subscribers": [{"SubscriptionType":"EMAIL","Address":"platform@example.com"}]
  }]'

# 2. Разбивка за месяц по сервисам — что вообще растёт
$ aws ce get-cost-and-usage --time-period Start=2026-06-01,End=2026-07-01 \
    --granularity MONTHLY --metrics UnblendedCost \
    --group-by Type=DIMENSION,Key=SERVICE \
    --query 'ResultsByTime[0].Groups[?Metrics.UnblendedCost.Amount>`100`].[Keys[0],Metrics.UnblendedCost.Amount]' \
    --output text | sort -k2 -rn
EC2 - Other                     1121.40     <- сюда прячутся NAT, EBS и трафик!
Amazon Elastic Compute Cloud    1849.77
Amazon Relational Database      760.12
AmazonCloudWatch                214.88

# 3. Самая полезная детализация: тип использования внутри "EC2 - Other"
$ aws ce get-cost-and-usage --time-period Start=2026-06-01,End=2026-07-01 \
    --granularity MONTHLY --metrics UnblendedCost \
    --filter '{"Dimensions":{"Key":"SERVICE","Values":["EC2 - Other"]}}' \
    --group-by Type=DIMENSION,Key=USAGE_TYPE --output text | sort -k3 -rn | head -5
EUC1-NatGateway-Bytes           270.00
EUC1-EBS:VolumeUsage.gp3        210.44
EUC1-DataTransfer-Out-Bytes     180.20
EUC1-NatGateway-Hours            65.70
EUC1-DataTransfer-Regional-Bytes 80.06

EC2 - Other — категория, в которой прячется всё, что вы не заказывали явно: NAT, тома, трафик, снапшоты, платные IPv4. Если счёт вырос непонятно почему, начинайте разбор отсюда.

Более серьёзный инструмент — Cost and Usage Report (CUR) в S3 + Athena: это построчная детализация каждого часа каждого ресурса. Только он позволяет ответить «сколько стоит вот эта команда» — при условии, что у вас есть теги.

-- Athena по CUR: во что обошёлся каждый сервис за месяц, с распределением
-- нераспределённого по доле (untagged показываем отдельно — это долг команды)
SELECT
  COALESCE(resource_tags_user_service, '<без тега>') AS service,
  ROUND(SUM(line_item_unblended_cost), 2)            AS cost_usd,
  ROUND(100.0 * SUM(line_item_unblended_cost)
        / SUM(SUM(line_item_unblended_cost)) OVER (), 1) AS pct
FROM cur.prod
WHERE line_item_usage_start_date >= DATE '2026-06-01'
  AND line_item_usage_start_date <  DATE '2026-07-01'
  AND line_item_line_item_type IN ('Usage', 'SavingsPlanCoveredUsage', 'DiscountedUsage')
GROUP BY 1
ORDER BY cost_usd DESC;

Теги в AWS не наследуются автоматически и не проставляются задним числом — их нужно активировать в биллинге и навязывать через Terraform (default_tags в провайдере) и SCP, запрещающий создание ресурсов без обязательных тегов. Без этого CUR показывает большой блок «неизвестно чьё», и разговор о стоимости упирается в спор.

Типичные ошибки

Собранное по инцидентам и разборам счетов — в порядке того, как часто встречается.

  1. Долгоживущие ключи AKIA.... Утекают в git и образы. Лечение: OIDC/instance profile/IRSA, SCP Deny iam:CreateAccessKey, обязательный секрет-сканер в CI (https://courses.digitable.life/post/devops/17-security-in-pipeline/).
  2. Публичный S3-бакет. Сейчас Block Public Access включён по умолчанию — не выключайте его «на минутку для отладки». Для отдачи файлов наружу — CloudFront + OAC, а не публичный бакет.
  3. Один аккаунт на всё. Прод, стейдж и песочница в одном аккаунте — это вопрос времени. Разделяйте через Organizations, связывайте ролями, накрывайте SCP.
  4. NAT Gateway без VPC-endpoints. Сотни долларов за трафик, который мог идти бесплатно. Gateway-endpoint для S3 — обязателен всегда.
  5. Забытые EBS-тома и снапшоты. Удаление инстанса не удаляет том, если DeleteOnTermination = false. Снапшоты копятся годами. aws ec2 describe-volumes --filters Name=status,Values=available — список того, что просто лежит.
  6. Логи в CloudWatch без retention. По умолчанию — «никогда не удалять», а приём стоит 0,50 $/ГБ. Логи debug-уровня из подов в CloudWatch — способ платить больше за наблюдаемость, чем за вычисления. Ставьте retention сразу, а объёмные логи отправляйте в S3.
  7. Cross-AZ-трафик по невнимательности. Kafka, Cassandra и любой чатливый межсервисный обмен, размазанный по зонам. Помните про 0,01 $/ГБ в каждую сторону.
  8. Отсутствие тестов восстановления. Бэкап, который никогда не разворачивали, — не бэкап. Раз в квартал восстанавливайте RDS-снапшот в отдельный инстанс и прогоняйте проверку целостности.
  9. 0.0.0.0/0 в security group. Особенно на 22 и 3389. Доступ к машинам — через SSM Session Manager: без открытых портов, с аудитом в CloudTrail.
  10. IMDSv1. Позволяет вытащить учётные данные роли через SSRF в приложении. Требуйте IMDSv2 (http_tokens = "required"), а лучше — запрещайте IMDS для подов вовсе.
  11. t-инстансы под постоянной нагрузкой. Burstable-инстансы копят CPU-кредиты; при их исчерпании производительность падает в разы, а в режиме unlimited появляются неожиданные списания. Для ровной нагрузки берите m/c.
  12. Игнорирование квот. Лимит на eIP, ENI, инстансы конкретного семейства обнаруживается в момент масштабирования — то есть в худший момент. Запрашивайте повышение заранее.
  13. Терраформ-стейт в аккаунте, которым он управляет. Классический способ потерять и инфраструктуру, и возможность её восстановить. Стейт — в отдельном аккаунте.
  14. Аварийное восстановление «мы же в облаке». Мультирегион не появляется сам: нужны репликация S3, глобальные таблицы или read-replica в другом регионе, копии AMI, план переключения DNS и — главное — регулярные учения.

Практика продакшена: как это обычно устроено

Зрелая установка выглядит примерно так, и почти все приходят к ней одинаково:

Разворачивают это либо Control Tower (быстро, с готовыми guardrails, но со своей магией и ограничениями), либо Terraform + Organizations вручную (дольше, зато всё понятно и версионируется). Для команды, которая уже живёт в IaC, второй путь обычно честнее.

Подключение GitHub Actions без единого секрета — минимальный рабочий кусок, который заменяет AWS_ACCESS_KEY_ID в настройках репозитория:

name: deploy
on:
  push:
    branches: [main]

permissions:
  id-token: write      # без этого OIDC-токен не выдадут
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production     # ручное подтверждение и защита секретов среды
    steps:
      - uses: actions/checkout@v4

      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/gha-deploy-prod
          aws-region: eu-central-1
          role-duration-seconds: 900   # ровно столько, сколько нужно деплою

      - name: Проверить, кем мы стали
        run: aws sts get-caller-identity

      - name: Обновить сервис ECS
        run: |
          aws ecs update-service --cluster prod --service api \
            --task-definition api:${{ github.run_number }} --force-new-deployment
          aws ecs wait services-stable --cluster prod --services api

Trust policy для роли gha-deploy-prod обязана ограничивать sub конкретным репозиторием и веткой — иначе развернуть прод сможет любой чужой репозиторий на GitHub:

"Condition": {
  "StringEquals": { "token.actions.githubusercontent.com:aud": "sts.amazonaws.com" },
  "StringLike": { "token.actions.githubusercontent.com:sub": "repo:acme/api:ref:refs/heads/main" }
}

Минимальный набор «включено с первого дня», который стоит воспринимать как чек-лист:

  • CloudTrail с организационным трейлом в отдельный аккаунт, с валидацией целостности файлов
  • Block Public Access на уровне аккаунта, шифрование S3 и EBS по умолчанию
  • GuardDuty во всех регионах, включая неиспользуемые (там как раз майнят)
  • SCP: запрет регионов, запрет отключения аудита, запрет корневого пользователя
  • MFA на root, root-ключи удалены, root используется только для нескольких специальных операций
  • Бюджеты с алертом по прогнозу и аномалиям (AWS Cost Anomaly Detection)
  • Обязательные теги через default_tags в Terraform-провайдере и SCP
  • Backup-план в AWS Backup с копией в отдельный аккаунт и Vault Lock
  • Квоты проверены и повышены заранее для критичных ресурсов

Мини-итог

  • Аккаунт — единица изоляции и взрыва, регион — независимая плоскость управления, AZ — единица отказа. Проектируйте от этих трёх границ.
  • Разделяемая ответственность двигается вместе с уровнем управляемости, но данные, доступы и восстановление всегда ваши.
  • IAM: по умолчанию запрещено, явный Deny неотменяем, долгоживущих ключей быть не должно. Проверяйте политики симулятором, а не глазами.
  • Сеть — главный источник неожиданных счетов. NAT, egress и cross-AZ не видны в архитектурной диаграмме, но дают четверть счёта. VPC-endpoints и AZ-осведомлённая маршрутизация окупаются мгновенно.
  • Fargate дороже EC2 вдвое и почти всегда стоит своих денег на малом масштабе; EKS оправдан числом команд, а не числом подов; Lambda выигрывает при утилизации ниже ~40 %.
  • Порядок оптимизации денег: выключить лишнее → уменьшить размеры → Spot и Graviton → и только потом Savings Plans.
  • Всё, что не покрыто тегами и CUR, — не управляется. Начинайте с наблюдаемости счёта, а не с догадок.

Источники

Что дальше

AWS — не единственный вариант, и для части команд не лучший: если стек построен на .NET, Active Directory и корпоративных лицензиях Microsoft, экономика и интеграция выглядят иначе. Об этом — следующая статья: Microsoft Azure: сервисы, интеграция с .NET-миром, AKS, сравнение с AWS.

Нашли неточность? Выделите фрагмент текста — рядом появится жучок.

Нужен разбор именно вашей ситуации?

Статья описывает общий случай. Если у вас частный — можно разобрать его отдельно, платно. А если не хватает целого материала, предложите тему: её оплачивают вскладчину, и она выходит открытой для всех.

Доска запросов