Всем привет! Меня зовут Артём Моносов, я DevOps-инженер в компании «Флант».
В одном из проектов, хостящихся в Amazon Web Services (AWS), был развёрнут полноценный кластер на базе Deckhouse Kubernetes Platform (DKP). Позже возникла задача оптимизировать затраты и перевести часть рабочей нагрузки на ARM64-инстансы с процессорами Graviton. Однако на текущий момент DKP не поддерживает работу с ARM64-инстансами.
Для управления ARM64-узлами решили использовать Karpenter — инструмент, который хорошо интегрируется с Amazon Elastic Kubernetes Service (EKS), управляемым Kubernetes-сервисом AWS и обеспечивающим поддержку ARM64-инстансов. При этом в кластере оставили возможность использовать привычные модули DKP для работы с x64-инстансами, чтобы, например, развернуть Prometheus для мониторинга и реализовать другие привычные функции.
Также в исходном x64-кластере мы активно использовали spot-инстансы, но иногда EC2 массово отзывал их, и поды не успевали переехать на новые узлы. Использование Karpenter вместе с EKS помогло эффективнее управлять spot-инстансами ARM64 и упростить масштабирование кластера.

О Karpenter
Karpenter — это проект с открытым исходным кодом (лицензия Apache 2.0), разрабатывающий альтернативу «автопилота» узлов в Kubernetes — Cluster Autoscaler. Он отслеживает запросы на вычислительные ресурсы и старается разместить их на узлах максимально эффективно.
Когда появляется под в статусе Pending, например из-за нехватки в кластере свободных ресурсов, Karpenter пытается найти подходящий NodePool, который сможет удовлетворить этот запрос на ресурсы. Если такой NodePool находится, Karpenter через EC2 API заказывает соответствующий инстанс, чтобы обеспечить размещение пода.
Особенно полезной оказывается работа со spot-инстансами, с помощью которых Karpenter отслеживает события от EC2 и и реагирует на них заранее. Например, получив от AWS уведомление за 2 минуты до отзыва spot-инстанса, Karpenter сразу заказывает новый инстанс и переносит на него нагрузку. Это позволяет полностью и плавно (с учётом заданных PodDisruptionBudget) разгрузить старую машину до её выключения.
Ещё Karpenter может постоянно отслеживать возможность оптимизации затрат. Например, после пика нагрузки VPA/HPA приложений поскейлился вниз. В результате запрошенные ресурсы умещаются на меньшем количестве узлов. Karpenter при определённых настройках отследит такие ситуации и автоматически изменит состав инстансов, перенеся нагрузку.
Установка EKS и Karpenter
Запустить кластер EKS можно разными способами, и мы будем следовать гайду из документации Karpenter. В нём с помощью CloudFormation создаются все необходимые дополнительные ресурсы AWS, после чего утилитой eksctl развёртывается кластер EKS.
Сам по себе Karpenter тоже должен где-то запускаться, при этом не рекомендуется запускать его на узлах, которые им же и обслуживаются. Есть три варианта установки:
Fargate: serverless-движок от AWS, в котором тоже можно запускать поды;
EKS managed node group, выделенная для Karpenter. Мы выбрали этот способ. В этом случае EKS создаёт и добавляет в кластер узлы в соответствии с настроенными параметрами;
Self managed node: ручной способ создать EC2-инстанс и добавить его в EKS-кластер. Как и в случае с EKS managed node group, мы получаем полноценный узел, на который при необходимости можно залогиниться и провести полноценный дебаг.
Во всех случаях установку можно выполнить с помощью официального Helm Chart. После запуска необходимо создать кастомные ресурсы, описывающие параметры инстансов, которые может использовать Karpenter для выделения ресурсов.
Настройка CR Karpenter
После запуска Karpenter необходимо создать кастомные ресурсы (CR, custom resource) EC2NodeClass
и NodePool
, описывающие параметры инстансов, которые он может использовать для выделения ресурсов.
Так в EC2NodeClass
описываются образ для инстанса, его блочные устройства, параметры kubelet и так далее. NodePool
в свою очередь ссылается на определённый EC2NodeClass
и дополнительно описывает, как Karpenter может работать с пулом: как и когда можно убирать узлы из кластера, в рамках какого лимита ресурсов можно создавать узлы, какие типы инстансов, архитектуру процессора, labels и taints использовать и прочее.
Настройка EC2NodeClass
:
---
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
name: al2023-worker
spec:
amiFamily: AL2023
amiSelectorTerms:
- alias: al2023@v20250715
associatePublicIPAddress: false
blockDeviceMappings:
- deviceName: /dev/sda1
ebs:
encrypted: true
volumeSize: 50Gi
volumeType: gp3
rootVolume: true
kubelet:
maxPods: 110
role: KarpenterNodeRole-eks-main-cluster
securityGroupSelectorTerms:
- tags:
aws:eks:cluster-name: eks-main-cluster
subnetSelectorTerms:
- tags:
Name: eks-main-private-subnet-1
- tags:
Name: eks-main-private-subnet-2
- tags:
Name: eks-main-private-subnet-3
tags:
clusterName: eks-main-cluster
Настройка NodePool
:
---
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: worker-spot
spec:
disruption:
budgets:
- nodes: 10%
consolidateAfter: 10m
consolidationPolicy: WhenEmptyOrUnderutilized
limits:
cpu: 32
memory: 128Gi
template:
metadata:
labels:
node-role/worker-spot: ""
spec:
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: al2023-worker
requirements:
- key: kubernetes.io/arch
operator: In
values:
- arm64
- amd64
- key: kubernetes.io/os
operator: In
values:
- linux
- key: karpenter.sh/capacity-type
operator: In
values:
- spot
- key: karpenter.k8s.aws/instance-family
operator: In
values:
- m6g
- m6a
- key: karpenter.k8s.aws/instance-size
operator: In
values:
- xlarge
- 2xlarge
taints:
- effect: NoExecute
key: dedicated
value: worker-spot
В примере выше Karpenter будет создавать spot-инстансы семейств m6a
/m6g
и размерностью xlarge
/2xlarge
, при этом общий пул ресурсов не выйдет за пределы 32 vCPU / 128Gb RAM. Так как consolidationPolicy
установлен в WhenEmptyOrUnderutilized
, Karpenter будет оптимизировать затраты на узлы путём их удаления и/или замены. Для того, чтобы Karpenter создал узлы, осталось только создать под с соответствующими nodeSelector
/affinity
и tolerations
.
Deckhouse Kubernetes Platform
Теперь, когда мы позаботились об узлах, приступим к установке DKP. С помощью его модулей мы с относительно небольшими затратами получим необходимый функционал для полноценной работы приложений в кластере, например:
ingress-nginx: для доступа к приложениям извне кластера;
cert-manager: для выпуска и обновления сертификатов;
prometheus: для мониторинга приложений.
Для этих задач будет достаточно DKP CE. Процесс установки описан в документации. Необходимо установить платформу в минимальной конфигурации и потом отдельно включить все нужные модули.
Однако в процессе установки именно в EKS мы столкнулись с рядом особенностей, которые стоит подсветить. На текущий момент Deckhouse не поддерживает ARM64-узлы, поэтому для компонентов платформы мы создали отдельные NodePool
, в которых описали инстансы только с архитектурой процессора AMD64. Клиентские приложения запускаются на Graviton-инстансах, создаваемых из отдельного NodePool, где ARM64 разрешён.
Kubeconfig для EKS-кластера по умолчанию настроен на получение токена через AWS CLI, поэтому каждый запрос занимает довольно много времени. Кроме этого, «из коробки» с EKS не заработал функционал генерации локального kubeconfig из нашего модуля user-authn. Так как в EKS прямого доступа к master-узлам у нас нет, нужно было искать обходное решение. Оказалось, что достаточно настроить для EKS-провайдера идентификации OIDC identity provider, передав ему адрес Dex, который как раз и развёртывает user-authn. При этом Issuer URL обязательно должен заканчиваться на слеш, например https://dex.example.com/.
После проделанных манипуляций мы смогли использовать kubeconfig из kubeconfig-generator, и вызовы к API Server стали работать значительно быстрее.
Спустя некоторое время после переключения трафика в новый кластер мы столкнулись с недоступностью сети в новых подах. Проблема воспроизводилась только на части узлов, но не сразу удалось найти то, что их объединяет. Оказалось, что на затронутых машинах обновлялся пакет udev, что приводило к перезаписи параметра MacAddressPolicy
на veth-интерфейсах. После правки MacAddressPolicy
проблема не повторялась.
Результат
По итогам подготовки и переключения нагрузки в новый кластер мы смогли улучшить надёжность работы приложений на spot-инстансах. Средняя скорость ввода в кластер нового узла в наших сценариях составила 40 секунд, что вместе с отслеживанием событий от EC2 позволяет нам ещё до выключения узла перенести всю нагрузку.
Набор инструментов для работы с кластером при этом практически полностью остался прежним: за исключением master-узлов, остальное обеспечивает платформа Deckhouse.
P. S.
Читайте также в нашем блоге: