Единый дашборд для 25+ аккаунтов AWS: экономия $30k

Введение

Я работал в организации, где трудились более 500 разработчиков, а инфраструктура насчитывала свыше 25 аккаунтов AWS — по одному на каждого клиента. Всем этим хозяйством управляла команда DevOps, которая по численности уступала количеству аккаунтов. При этом почти все аккаунты были активны: ресурсы постоянно выделялись и освобождались. В работе использовался широкий спектр сервисов AWS: EC2, S3, EKS, SageMaker, RDS, Redshift, CloudFront, Route53, SSM и другие.

Когда в каком-либо сервисе или вычислительном узле возникала ошибка, первым делом нужно было выяснить, в каком именно аккаунте AWS находится проблемный ресурс. Это требовало ручного ведения инвентаря: DNS-записи для разных аккаунтов, правила файрволов и внутренние DNS-записи приходилось отслеживать самостоятельно. Версионирование давалось с трудом, и весь процесс занимал слишком много времени.

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

  • EC2-инстансы с приложениями переставали работать, и команде требовалось разобраться, какие изменения конфигурации могли к этому привести. Данные о загрузке CPU и RAM также помогали в диагностике.

  • Нужно было видеть степень заполненности подсетей и использование CIDR-блоков, чтобы грамотно распределять кластеры и инстансы по подсетям с достаточным запасом IP-адресов.

  • Узлы кластеров EKS и RDS имели собственные конфигурации, получение которых упростило бы отладку связанных с этими сервисами проблем.

  • Route53 играл ключевую роль в управлении DNS-записями, и время на поиск конкретного аккаунта AWS, в котором работает нужный сервис, необходимо было сократить.

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

Менеджер предложил мне начать с малого — использовать AWS SDK для получения информации о нескольких сервисах в рамках одного аккаунта, чтобы хотя бы научиться находить запущенные и остановленные EC2-инстансы и рассылать их конфигурации по почте команде DevOps.

Прежде всего нужно было продумать структуру хранения данных в базе. Поскольку вызовы Boto3 возвращают данные в формате JSON, я выбрал NoSQL-базу MongoDB. Я понимал: сбор данных по каждому сервису будет выполняться для одного аккаунта за раз, и это время нужно минимизировать. Поддержка API для получения данных по всем аккаунтам и регионам для конкретного сервиса тоже планировалась, но главным приоритетом была скорость чтения.

Я написал пакетные конвейеры (batch pipelines) с механизмом повторных попыток и подробным логированием для каждого сервиса AWS. Они использовали Boto3 SDK для периодического получения конфигурационных данных и наполнения базы MongoDB — по принципу cron-задач. На поля, по которым предполагался поиск, я добавил индексы. Для хранения данных об аккаунтах AWS и их IAM-ключах создавались конфигурационные файлы: IAM-роли нельзя было применить с одной машины, работающей в одном аккаунте (IAM-роли привязаны к конкретному аккаунту, а IAM-ключи оказались проще в использовании и управлении для этой кросс-аккаунтной схемы на этапе MVP). Впоследствии схему перевели на кросс-аккаунтные IAM-роли.

Архитектура системы

Чтобы сделать данные доступными и полезными для всей команды, я построил полноценное full-stack-решение:

  • Слой приёма данных (Data Ingestion Layer): Python-скрипты на базе boto3 для получения данных из 25+ аккаунтов AWS. Каждый скрипт реализовывал пагинацию (для получения полного списка ресурсов) и ограничение частоты запросов (чтобы не получать ошибки троттлинга от AWS API).

  • База данных: выбрана MongoDB благодаря гибкой схеме — она позволяла хранить разнородные конфигурационные JSON-объекты от EC2, RDS, S3 и других сервисов без жёстких ограничений на структуру данных.

  • Backend API: приложение на Flask предоставляло API для запросов к этим данным. В него были включены библиотеки аутентификации для защиты доступа.

  • Фронтенд-дашборд (Frontend Dashboard): приложение на Next.js (React) служило пользовательским интерфейсом. Данные отображались в табличном виде с динамическими заголовками в зависимости от типа ресурса, а также поддерживался поиск.

  • Оповещения (Alerting): скрипты приёма данных включали обработку ошибок с отправкой уведомлений по электронной почте при сбоях — это позволяло сразу узнавать об устаревших данных.

Пример кода для AWS SDK

# Mock code
import boto3
from botocore.exceptions import ClientError

def fetch_ec2_inventory(access_key, secret_key, region):
    """
    Safely fetches EC2 instance data with pagination to handle
    accounts with thousands of resources.
    """
    try:
        # In a production env, use AssumeRole instead of direct keys
        session = boto3.Session(
            aws_access_key_id=access_key,
            aws_secret_access_key=secret_key,
            region_name=region
        )
        ec2 = session.client('ec2')

        # Paginator automatically handles the 'NextToken' for large lists
        paginator = ec2.get_paginator('describe_instances')
        instances = []

        for page in paginator.paginate():
            for reservation in page['Reservations']:
                for instance in reservation['Instances']:
                    # Extract only necessary fields to minimize DB impact
                    instances.append({
                        'InstanceId': instance['InstanceId'],
                        'Type': instance['InstanceType'],
                        'State': instance['State']['Name'],
                        'Tags': instance.get('Tags', [])
                    })
        return instances

    except ClientError as e:
        print(f"Failed to fetch inventory for region {region}: {e}")
        return []

Поток данных и архитектура инвентаризации AWS

Схема потока данных и архитектуры инвентаризации AWS

Экономия более $30 000 в год

Централизованная видимость сразу же открыла возможности для оптимизации затрат, которые прежде были скрыты в разрозненных консолях отдельных аккаунтов:

  1. Брошенные ресурсы (Zombie Resources): мы сразу обнаружили EC2-инстансы, находившиеся в состоянии «Stopped» месяцами, но всё ещё имевшие подключённые дорогостоящие EBS-тома.

  2. Неиспользуемые IP-адреса: отчёт о неподключённых Elastic IP по всем аккаунтам выявил излишние расходы.

  3. Избыточное выделение ресурсов (Over-provisioning): получив данные о конфигурации CPU/памяти вычислительных ресурсов (через CloudWatch API) вместе с тегами окружений, мы уменьшили размер непродакшн-инстансов RDS, EC2 и узлов EKS.

  4. Осиротевшие снимки (Orphaned Snapshots): мы обнаружили терабайты старых снимков удалённых томов, которые больше не были нужны.

Постепенно зачищая эти ресурсы во всех 25+ аккаунтах, мы добились экономии более $30 000 в год уже в первые несколько месяцев после запуска.

Автоматизация DevOps

Поиск ресурсов в обширной инфраструктуре AWS стал значительно проще. Мы автоматизировали отслеживание инвентаря для Route53, VPC, подсетей и групп безопасности, представив всё это в едином интерфейсе. Это сократило среднее время устранения инцидентов (MTTR) при отладке инфраструктурных проблем примерно на 40% и автоматизировало 90% ручных задач по поиску ресурсов.

Интеграция с Kubernetes

Более 30 Kubernetes-кластеров обслуживали критически важные рабочие нагрузки, поэтому единое представление об их состоянии было необходимо. Однако здесь мы столкнулись с серьёзным ограничением с точки зрения безопасности и эксплуатации: продакшн-бастионы были строго ограничены. Устанавливать Python, сторонние библиотеки или запускать сложные агенты мониторинга на этих jump-хостах, через которые пролегал единственный путь к API-серверам кластеров, было нельзя.

Стратегия агрегации данных

Для решения этой задачи я спроектировал развязанную архитектуру (decoupled architecture) с использованием S3 в качестве промежуточного хранилища. Вместо того чтобы центральный инструмент мониторинга сам обращался к кластерам (что требует сложного VPC-пиринга и создаёт риски безопасности), кластеры сами отправляли отчёты о своём состоянии.

  1. Выполнение на сервере доступа: у каждого кластера был собственный выделенный сервер доступа (bastion) с предустановленными kubeconfig и kubectl. Простой shell-скрипт, запускавшийся по cron на этих серверах, выполнял стандартную команду kubectl get all -o json и сохранял полное состояние кластера в файл.

  2. Защищённая загрузка: скрипт использовал AWS CLI (уже установленный на сервере доступа) для загрузки этого JSON-дампа в центральный S3-бакет с IAM-политикой строго ограниченного доступа.

  3. Централизованная обработка: центральный Python ETL-конвейер отслеживал появление новых файлов в S3, скачивал их, разбирал JSON и обновлял состояние в MongoDB.

Поток данных и архитектура Kubernetes

Схема потока данных и архитектуры мониторинга Kubernetes

Пример кода: разбор дампа кластера

# Mock code
import json
import boto3

def process_cluster_dump(bucket, key, cluster_name, account_id):
    """
    Downloads a raw kubectl JSON dump from S3 and extracts
    all Pods, Services, and Ingress resources for centralized monitoring.
    """
    s3 = boto3.client('s3')
    obj = s3.get_object(Bucket=bucket, Key=key)
    cluster_data = json.loads(obj['Body'].read())

    resources = {
        'pods': [],
        'services': [],
        'ingresses': []
    }

    # The dump allows us to process offline without stressing the API server
    for item in cluster_data.get('items', []):
        kind = item['kind']
        metadata = item['metadata']

        # Parse Pod data
        if kind == 'Pod':
            status = item.get('status', {})
            resources['pods'].append({
                'cluster': cluster_name,
                'account': account_id,
                'name': metadata['name'],
                'namespace': metadata['namespace'],
                'status': status.get('phase'),
                'ip': status.get('podIP'),
                'node': item.get('spec', {}).get('nodeName'),
                'labels': metadata.get('labels', {})
            })

        # Parse Service data
        elif kind == 'Service':
            spec = item.get('spec', {})
            resources['services'].append({
                'cluster': cluster_name,
                'account': account_id,
                'name': metadata['name'],
                'namespace': metadata['namespace'],
                'type': spec.get('type'),
                'clusterIP': spec.get('clusterIP'),
                'ports': spec.get('ports', [])
            })

        # Parse Ingress data
        elif kind == 'Ingress':
            spec = item.get('spec', {})
            resources['ingresses'].append({
                'cluster': cluster_name,
                'account': account_id,
                'name': metadata['name'],
                'namespace': metadata['namespace'],
                'rules': spec.get('rules', []),
                'hosts': [rule.get('host') for rule in spec.get('rules', [])]
            })

    return resources

Поскольку исходные данные представляли собой стандартные JSON-списки Kubernetes, их обработка на стороне центрального аккаунта управления была простой и надёжной.

Такой подход означал полное отсутствие внешних зависимостей для продакшн-кластеров, что удовлетворяло строгим требованиям безопасности. При этом обеспечивалась наблюдаемость в реальном времени: проблемные поды, PVC в состоянии ожидания или падающие сервисы были видны немедленно, и команда могла реагировать в течение нескольких минут.

Для данных AWS и Kubernetes сохранялась история за предыдущие дни, что позволяло отслеживать изменения ресурсов и их состояние через интерфейс.

Развёртывание приложения

Чтобы сам инструмент мониторинга был надёжным и удобным в обновлении, я выстроил современный CI/CD-конвейер на основе GitLab CI и Docker. Backend (Flask) и frontend (Next.js) были контейнеризированы и разворачивались автоматически после слияния изменений.

Приложение развёрнуто на одном сервере в центральном аккаунте управления и доступно по внутреннему DNS-имени. Поскольку оно использовалось только внутри команды DevOps с умеренной нагрузкой, одной машины с регулярным созданием снимков оказалось вполне достаточно.

Поток CI/CD и архитектура развёртывания приложения

Схема CI/CD-конвейера и архитектуры развёртывания приложения

Заключение

То, что начиналось как небольшая система email-оповещений о конфигурации EC2-инстансов в одном аккаунте AWS, переросло в полноценную платформу управления активами для всей облачной инфраструктуры. Она позволила отслеживать и управлять всеми облачными ресурсами в едином месте, упростила их поиск и администрирование, а также дала возможность понимать и сокращать облачные расходы при минимальном ручном вмешательстве.

Изначально опубликовано на varunarora14.github.io 23 декабря 2025 года.

© 2026 meganuke