Введение
Я работал в организации, где трудились более 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
Экономия более $30 000 в год
Централизованная видимость сразу же открыла возможности для оптимизации затрат, которые прежде были скрыты в разрозненных консолях отдельных аккаунтов:
-
Брошенные ресурсы (Zombie Resources): мы сразу обнаружили EC2-инстансы, находившиеся в состоянии «Stopped» месяцами, но всё ещё имевшие подключённые дорогостоящие EBS-тома.
-
Неиспользуемые IP-адреса: отчёт о неподключённых Elastic IP по всем аккаунтам выявил излишние расходы.
-
Избыточное выделение ресурсов (Over-provisioning): получив данные о конфигурации CPU/памяти вычислительных ресурсов (через CloudWatch API) вместе с тегами окружений, мы уменьшили размер непродакшн-инстансов RDS, EC2 и узлов EKS.
-
Осиротевшие снимки (Orphaned Snapshots): мы обнаружили терабайты старых снимков удалённых томов, которые больше не были нужны.
Постепенно зачищая эти ресурсы во всех 25+ аккаунтах, мы добились экономии более $30 000 в год уже в первые несколько месяцев после запуска.
Автоматизация DevOps
Поиск ресурсов в обширной инфраструктуре AWS стал значительно проще. Мы автоматизировали отслеживание инвентаря для Route53, VPC, подсетей и групп безопасности, представив всё это в едином интерфейсе. Это сократило среднее время устранения инцидентов (MTTR) при отладке инфраструктурных проблем примерно на 40% и автоматизировало 90% ручных задач по поиску ресурсов.
Интеграция с Kubernetes
Более 30 Kubernetes-кластеров обслуживали критически важные рабочие нагрузки, поэтому единое представление об их состоянии было необходимо. Однако здесь мы столкнулись с серьёзным ограничением с точки зрения безопасности и эксплуатации: продакшн-бастионы были строго ограничены. Устанавливать Python, сторонние библиотеки или запускать сложные агенты мониторинга на этих jump-хостах, через которые пролегал единственный путь к API-серверам кластеров, было нельзя.
Стратегия агрегации данных
Для решения этой задачи я спроектировал развязанную архитектуру (decoupled architecture) с использованием S3 в качестве промежуточного хранилища. Вместо того чтобы центральный инструмент мониторинга сам обращался к кластерам (что требует сложного VPC-пиринга и создаёт риски безопасности), кластеры сами отправляли отчёты о своём состоянии.
-
Выполнение на сервере доступа: у каждого кластера был собственный выделенный сервер доступа (bastion) с предустановленными
kubeconfigиkubectl. Простой shell-скрипт, запускавшийся по cron на этих серверах, выполнял стандартную командуkubectl get all -o jsonи сохранял полное состояние кластера в файл. -
Защищённая загрузка: скрипт использовал AWS CLI (уже установленный на сервере доступа) для загрузки этого JSON-дампа в центральный S3-бакет с IAM-политикой строго ограниченного доступа.
-
Централизованная обработка: центральный Python ETL-конвейер отслеживал появление новых файлов в S3, скачивал их, разбирал JSON и обновлял состояние в MongoDB.
Поток данных и архитектура 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 и архитектура развёртывания приложения
Заключение
То, что начиналось как небольшая система email-оповещений о конфигурации EC2-инстансов в одном аккаунте AWS, переросло в полноценную платформу управления активами для всей облачной инфраструктуры. Она позволила отслеживать и управлять всеми облачными ресурсами в едином месте, упростила их поиск и администрирование, а также дала возможность понимать и сокращать облачные расходы при минимальном ручном вмешательстве.
Изначально опубликовано на varunarora14.github.io 23 декабря 2025 года.