
Как я сократил вычислительные затраты на AWS на 70% с помощью кластера MultiarChing EKS и Karpenter
16 августа 2025 г.После просмотра нашего ежемесячного законопроекта AWS неуклонно поднимается по мере роста наших рабочих нагрузок Kubernetes, я знал, что нам нужен более разумный подход к обеспечению узлов. Традиционное кластерное автоматическое масштабы прожигали через наш бюджет с чрезмерными узлами, сидящими, сидящими, при этом все еще занимал вечность, чтобы масштабироваться, когда мне нужны мощность.
Я слышал о Карпентере, но я не пробовал это. Этот инцидент заставил меня, наконец, попробовать. В этом руководстве я поделюсь тем, как я создал готовый к производству многоинктурный кластер EKS, который снижает наши вычислительные затраты на 70%, делая масштабирование почти мгновенным.
Полный исходный код доступен наGitHubПолем
- Переключен от кластера Autoscaler на Карпентер
- Сократить расчеты на 70%, используя Spot + Graviton
- Снижение задержки планирования POD с 3 минут до 20 секунд
- Построенный кластер EKS с AMD64 + ARM64
- Полный трубопровод Terraform + CI/CD на GitHub
Отказ от традиционного автоматического масштаба
Вот что мне не понравилось в стандартном кластере Kubernetes Cluster Autoscaler: у меня были бы стручки, сидящие в «ожидании» состояния в течение 2-3 минут в ожидании новых узлов, в то же время платя за кучу чрезмерных случаев M5.
Я помню один особенно разочаровывающий инцидент, когда всплеск трафика попал в наше приложение в 2 часа ночи. Cluster Autoscaler потребовался более 4 минут, чтобы предоставить новые узлы, и к тому времени наши пользователи получали тайм -ауты. Именно тогда я начал серьезно смотреть на Карпентер.
Что отличает Карпентер, так это то, что он не думает с точки зрения фиксированных групп узлов. Вместо этого он смотрит на ваши ожидаемые стручки и говорит: «Хорошо, вам нужно 2 VCPU и 4 ГБ оперативной памяти? Позвольте мне найти самый дешевый точечный экземпляр, который соответствует этим требованиям». Это как действительно умный помощник по обеспечению, который действительно понимает ваши шаблоны рабочей нагрузки.
После его реализации я увидел, как наше время подготовки узлов упало с 3+ минут до менее 20 секунд. И экономия затрат - примерно на 70% в нашем вычислительном законопроекте, который освободил бюджет на другие улучшения инфраструктуры.
Наша мультиархитектурная настройка
Одна вещь, которая впечатлила меня с Карпентером, было то, как легко он справился с нашими требованиями к смешанной нагрузке. У меня были некоторые устаревшие приложения PHP, которые нуждались в экземплярах X86, но я также хотел экспериментировать с экземплярами Graviton ARM64 для наших новых микросервисов.
Архитектура, с которой я оказался, выглядит так:
┌─────────────────────────────────────────────────────────────┐
│ EKS Cluster │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ AMD64 │ │ ARM64 │ │ Karpenter │ │
│ │ NodePool │ │ NodePool │ │ Controller │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Spot Instance Handling │ │
│ │ SQS Queue + EventBridge Rules │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Архитектура состоит из нескольких ключевых компонентов:
- VPC инфраструктура: Получение правильного общения имело решающее значение. Я потратил некоторое время на идеальное теги подсети - эти
karpenter.sh/discovery
Теги не просто хороши, они необходимы для Карпентера, чтобы найти ваши подсети. Я забылkarpenter.sh/discovery
Тэг один раз - потребовалось час, чтобы отлаживать, почему он не запускает узлы.
- EKS Cluster: Интеграция поставщика OIDC была, вероятно, самой сложной частью, чтобы получить право изначально. Это то, что позволяет Карпентеру безопасно поговорить с API API без твердых кодировки.
- Карпентер -контроллер: Здесь происходит магия. После развертывания он постоянно следит за незапланированными стручками и разумно обеспечивает правильные случаи.
- Многоархитектурная поддержка: Наличие отдельных пулов узлов для AMD64 и ARM64 позволяет нам запускать различные рабочие нагрузки на наиболее экономически эффективном оборудовании.
- Управление экземплярами SPOT: Обработка прерываний была чем -то, о чем я изначально беспокоился, но интеграция AWS SQS делает ее удивительно надежной.
Структура терраформ, которая на самом деле работает
Моя первая попытка структурировать это была беспорядком. У меня было все в одном гигантеmain.tf
Файл, и внесение изменений было ужасающим. После некоторого рефакторинга (и нескольких поздних ночей) я приземлился на этот модульный подход:
├── eks-module/ # EKS cluster creation
├── karpenter/ # Karpenter autoscaler setup
├── vpc/ # VPC infrastructure
├── root/ # Main Terraform execution
└── .github/workflows/ # CI/CD pipeline
VPC: правильная сетевая фонд
Настройка VPC заняла у меня несколько итераций, чтобы получить право. Ключевым пониманием было то, что стратегия тегов не просто документация - это функциональная.
Вот то, что я узнал, работает:
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr_block
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "${var.project_name}-vpc"
"kubernetes.io/cluster/${var.project_name}" = "shared"
}
}
resource "aws_subnet" "private_subnets" {
# ... configuration
tags = {
Name = "private-subnet-${count.index}"
"karpenter.sh/discovery" = var.project_name
"kubernetes.io/role/internal-elb" = "1"
}
}
Экс и Карпентер: сердце системы
Установка EKS была простой, но право правильного поставщика OIDC было критическим. Это то, что позволяет Карпентеру надежно взять на себя роли:
resource "aws_eks_cluster" "project" {
name = var.cluster_name
role_arn = aws_iam_role.cluster_role.arn
vpc_config {
subnet_ids = var.private_subnet_ids
endpoint_private_access = true
endpoint_public_access = true
}
access_config {
authentication_mode = "API"
}
}
resource "aws_iam_openid_connect_provider" "eks" {
client_id_list = ["sts.amazonaws.com"]
thumbprint_list = [data.tls_certificate.eks.certificates[0].sha1_fingerprint]
url = aws_eks_cluster.project.identity[0].oidc[0].issuer
}
Для развертывания Карпентера я подходил с подходом Helm Chart. Одна вещь, которую я узнал, это то, что вам нужно сначала установить CRDS, а затем основной график. Установка очереди прерывания имела решающее значение - это то, что предотвращает резкое прекращение увольнения ваших рабочих нагрузок, когда экземпляры точечных экземпляров восстанавливаются:
# Install CRDs first
resource "helm_release" "karpenter_crd" {
name = "karpenter-crd"
chart = "oci://public.ecr.aws/karpenter/karpenter-crd"
version = var.karpenter_version
namespace = var.namespace
}
# Then install the main Karpenter chart
resource "helm_release" "karpenter" {
depends_on = [helm_release.karpenter_crd]
name = "karpenter"
chart = "oci://public.ecr.aws/karpenter/karpenter"
version = var.karpenter_version
namespace = var.namespace
set {
name = "settings.clusterName"
value = var.cluster_name
}
set {
name = "settings.aws.interruptionQueueName"
value = aws_sqs_queue.karpenter_interruption_queue.name
}
set {
name = "serviceAccount.annotations.eks\\.amazonaws\\.com/role-arn"
value = var.controller_role_arn
}
}
Многоархитектура NodePools
Первоначально я скептически относился к ARM64. "Будут ли наши приложения вообще работать?" Но после некоторого тестирования я обнаружил, что наши службы Node.js и Python прекрасно работали в экземплярах Graviton - часто с лучшей производительностью за доллар.
Вот как я настроил бассейны узлов:
AMD64 NODEPOOL
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: amd64-nodepool
spec:
template:
spec:
requirements:
- key: kubernetes.io/arch
operator: In
values: ["amd64"]
- key: karpenter.sh/capacity-type
operator: In
values: ["spot", "on-demand"]
- key: node.kubernetes.io/instance-type
operator: In
values: ["m5.large", "m5.xlarge", "c5.large", "c5.xlarge"]
limits:
cpu: 1000
disruption:
consolidationPolicy: WhenEmpty
consolidateAfter: 1m
ARM64 NODEPOOL: эксперимент по гравитону
Установка ARM64 требует немного большей продуманности. Я добавил Taints, чтобы предотвратить несовместимые рабочие нагрузки случайной посадки на узлы ARM64:
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: arm64-nodepool
spec:
template:
spec:
requirements:
- key: kubernetes.io/arch
operator: In
values: ["arm64"]
- key: node.kubernetes.io/instance-type
operator: In
values: ["t4g.medium", "c6g.large", "m6g.large"]
taints:
- key: arm64
value: "true"
effect: NoSchedule
Я узнал, что указание точных идентификаторов AMI дает вам больше контроля над тем, что развертывается, особенно при тестировании различных конфигураций ARM64. Вы также можете использовать требования Nodeselector для нацеливания на конкретные архитектуры без нуждающихся в странах, если ваши приложения имеют архитектуру.
Экономия стоимости с этим подходом
Позвольте мне поделиться реальным влиянием нашей производственной среды. Перед Карпентером мы проводили смесь экземпляров по требованию в нескольких группах узлов-около 15-20 м.
После перехода на Карпентер с примерно на 70% отдельного использования экземпляра наши ежемесячные расчеты снизились на 70%. Это значительное сокращение, которое освободило значительный бюджет на новые функции и улучшения инфраструктуры.
ARM64 Преимущества
Экземпляры ARM64 предоставили дополнительный бонус. Помимо экономии затрат на ~ 20% по сравнению с эквивалентными эквивалентами X86, я увидел лучшую производительность на некоторых наших рабочих нагрузках, интенсивной процессоре. Наша служба обработки изображений, например, проходила примерно на 15% быстрее в экземплярах Graviton.
Вот что я обычно вижу в наших кластерах:
- Снижение на 70%в расчетных затратах
- Суб-20 секунды масштабированиявместо минут
- Лучшее использование ресурсов- Больше не нужно платить за простоя циклов ЦП
Мониторинг производительности показал, что мы перешли от среднего 25% использования ЦП на наших фиксированных узлах до 70% использования с правым размером Карпентера.
Уроки производства извлечены
Безопасность: IRSA - ваш друг
Правильное получение модели безопасности имело решающее значение. Я использую свои роли для учетных записей услуг (IRSA) везде, что означает, что в нашем кластере больше нет никаких жестких учетных данных AWS:
resource "aws_iam_role" "karpenter_controller" {
name = "KarpenterController-${var.cluster_name}"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRoleWithWebIdentity"
Effect = "Allow"
Principal = {
Federated = var.oidc_provider_arn
}
Condition = {
StringEquals = {
"${replace(var.oidc_provider_url, "https://", "")}:sub" = "system:serviceaccount:karpenter:karpenter"
}
}
}
]
})
}
Мониторинг
После некоторой проб и ошибок я решил контролировать эти ключевые метрики. Слишком много мониторинга становятся шумом, но эти четыре говорят мне все, что мне нужно знать о нашем кластере:
- karpenter_nodes_created_total: Узлыны масштабируются, когда они должны быть?
- karpenter_nodes_mermination_total: Узлыны с масштабируются?
- karpenter_pods_state: Строительство запланированы быстро?
- karpenter_node_utilization: Примечания имеют правильный размер?
Метрика использования узлов была особенно открывающей глаза. Мы достигли ~ 25% среднего использования до 70% после того, как Карпентер начал право на размер в наши экземпляры.
CI/CD
Мой трубопровод развертывания довольно прост. Я использую действия GitHub с аутентификацией OIDC для AWS (больше нет ключей доступа в секретах!). Рабочий процесс поддерживает филиалы функций для разработки, постановки тестирования и основного для производства:
name: Terraform Deploy
on:
push:
branches:
- 'feature/**'
- 'staging'
- 'main'
workflow_dispatch:
permissions:
id-token: write # Required for OIDC JWT
contents: read # Required for checkout
jobs:
deploy:
environment: ${{ (github.ref == 'refs/heads/main' && 'prod') || (github.ref == 'refs/heads/staging' && 'staging') || 'dev' }}
runs-on: ubuntu-22.04
defaults:
run:
working-directory: root/
steps:
- name: Clone repo
uses: actions/checkout@v3
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-region: us-east-1
role-to-assume: ${{ vars.IAM_Role }}
role-session-name: gha-assignment-session
- name: Initialize Terraform
run: terraform init -backend-config="bucket=${{ vars.TF_STATE_BUCKET }}"
- name: Terraform Plan
run: terraform plan -input=false -var-file=${{ vars.STAGE }}.tfvars
- name: Terraform Apply
run: terraform apply -auto-approve -input=false -var-file=${{ vars.STAGE }}.tfvars
Что мне нравится в этой настройке, так это выбор динамической среды и использование переменных GitHub для разных этапов. Аterraform fmt -check
Шаг обеспечивает согласованность кода в нашей команде.
Краткое содержание
Слушай, я не собираюсь притворяться, что это было плавное плавание. У меня были некоторые начальные сбои с перерывами экземпляров Spot (Protip: убедитесь, что ваши приложения изящно обрабатывают Sigterm), и правильно обработать рабочие нагрузки ARM64 приняла некоторую итерацию.
Но результаты говорят сами за себя. Мы используем более устойчивую, экономически эффективную инфраструктуру, которая разумно масштабируется. Самая существенная экономия затрат заплатила за инженерное время, которое я потратил на эту миграцию в течение первого месяца.
Если вы имеете дело с непредсказуемыми рабочими нагрузками, растущими счетами AWS или просто хотите попробовать что -то, что похоже на «будущее Kubernetes», я определенно рекомендую сделать снимок Карпентера. Начните с кластера разработки, освоитесь с концепциями, а затем постепенно мигрируйте свои рабочие нагрузки на производство.
Полная реализация доступна наGitHubПолем Я включил все модули Terraform, тестирование сценариев и руководство по устранению неполадок, основанное на проблемах, с которыми я столкнулся. Не стесняйтесь использовать его в качестве отправной точки для вашей собственной реализации - и дайте мне знать, как это происходит!
Оригинал