Блог Amazon Web Services

Разработка архитектуры для оптимизации затрат и устойчивости на базе EKS с использованием спотовых инстансов

Оригинал статьи: ссылка (Ben Peven и Chris Foote, Sr. EC2 Spot Specialist Solutions Architect)

Запуск Kubernetes и контейнеризированных рабочих нагрузок на спотовых инстансах Amazon EC2 — это отличный способ сократить расходы. Kubernetes — это популярная система управления контейнерами с открытым исходным кодом, которая позволяет развёртывать контейнеризированные приложения и управлять ими в больших масштабах. AWS облегчает запуск Kubernetes с помощью Amazon Elastic Kubernetes Service (EKS) — управляемого сервиса Kubernetes для запуска на AWS рабочих нагрузок, предназначенных для промышленной эксплуатации. Чтобы оптимизировать затраты на запуск таких рабочих нагрузок, запускайте их на спотовых инстансах. Спотовые инстансы доступны со скидками до 90% в сравнении с ценами на инстансы по требованию. Эти инстансы лучше всего подходят для различных отказоустойчивых приложений, не зависящих от типов инстансов. Спотовые инстансы и контейнеры являются отличной комбинацией, так как контейнеризированные приложения часто не имеют состояния и не зависят от типов инстансов.

В этой статье я покажу лучшие практики использования спотовых инстансов, такие, как диверсификация, автоматическая обработка спотовых прерываний и использование Amazon EC2 Auto Scaling для получения вычислительных ресурсов. Затем вы адаптируете эти лучшие практики использования спотовых инстансов к EKS для оптимизации затрат и повышения устойчивости контейнеризированных рабочих нагрузок.

Обзор спотовых инстансов

Спотовые инстансы — это неиспользуемые вычислительные ресурсы Amazon EC2, которые позволяют клиентам экономить до 90% в сравнении с ценами на инстансы по требованию. Спотовые инстансы разделены на спотовые группы, определяемые типом инстанса, зоной доступности (Availability Zone, AZ) и регионом AWS. Цены на спотовые инстансы постепенно корректируются в зависимости от долгосрочных тенденций спроса и предложения на спотовые инстансы определенной спотовой группы, как показано ниже:

Spot Instance pricing

Указанные цены являются примером и могут не соответствовать текущим ценам. Цены спотовых инстансов указаны в оранжевых блоках, инстансов по требованию — в темно-синих.

Когда сервис EC2 нуждается в ресурсах, служба спотовых инстансов посылает уведомления о прерывании работы произвольным инстансам, входящих в соответствующую спотовую группу. Эти уведомления о прерывании становятся доступными в EC2-метаданных инстанса, а также отправляются в Amazon Eventbridge. Через две минуты после уведомления о прерывании спотовый инстанс завершает работу. Вы можете настроить свою инфраструктуру на автоматизацию ответа на это двухминутное уведомление. Примерами могут служить безопасный перенос контейнеров на другие рабочие узлы, безопасный разрыв соединений балансировщика нагрузки или постобработка.

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

Количество спотовых групп = (количество зон доступности) * (количество типов инстансов)

Если ваше приложение развернуто в двух зонах доступности и использует только один тип инстансов c5.4xlarge, то вам доступны (2 * 1 = 2) две спотовые группы. Следуя лучшим практикам использования спотовых инстансов, лучше использовать шесть зон доступности и разрешить вашему приложению использовать типы инстансов c5.4xlarge, c5d.4xlarge, c5n.4xlarge, и c4.4xlarge. Это даёт нам (6 * 4 = 24) 24 спотовых группы, что значительно повышает стабильность и устойчивость вашего приложения.

EC2 Auto Scaling поддерживает развёртывание приложений с использованием нескольких типов инстансов и автоматически заменяет инстансы в случае сбоя в работе или завершения работы из-за спотового прерывания. Чтобы снизить вероятность прерывания, используйте стратегию распределения спотовых инстансов, оптимизированную для использования наиболее свободных групп. Эта стратегия автоматически запускает спотовые инстансы в наиболее свободных спотовых группах, отслеживая данные об их загрузке в режиме реального времени и определяя, какие из них являются наиболее свободными.

Теперь, когда я рассказал о лучших практиках использования спотовых инстансов, вы можете применить их для разработки архитектуры для EKS на спотовых инстансах.

Архитектура решения

Цели этой архитектуры таковы:

  • Автоматическое масштабирование рабочих узлов кластеров Kubernetes для соответствия потребностям приложения.
  • Использование спотовых инстансов для оптимизации затрат на запуск рабочих нагрузок на Kubernetes.
  • Адаптация лучших практик использования спотовых инстансов (например, диверсификации) к EKS и Cluster Autoscaler.

Вы достигнете этих целей с помощью следующих компонентов:

Компонент Роль Детали Метод развёртывания
Cluster Autoscaler Автоматически масштабирует EC2 инстансы в соответствии с требованиями приложений, запущенных в кластере Открытый код Deployment на инстансах по требованию
Группа EC2 Auto Scaling Запускает и поддерживает необходимое количество EC2 инстансов AWS CloudFormation через eksctl
AWS Node Termination Handler Обнаруживает спотовые прерывания и автоматически безопасно завершает работу узлов Открытый код DaemonSet через Helm на спотовых инстансах

Эта архитектура разворачивает рабочие узлы EKS в трех зонах доступности и использует три группы EC2 Auto Scaling — две для спотовых инстансов и одну для инстансов по требованию. Kubernetes Cluster Autoscaler развёртывается на рабочих узлах на инстансах по требованию, а AWS Node Termination Handler — на всех рабочих узлах.

EKS worker node architecture

Дополнительная информация о взаимодействии Kubernetes с группами EC2 Auto Scaling:

  • Cluster Autoscaler управляет масштабированием рабочих узлов путём изменения параметра DesiredCapacity у групп EC2 Auto Scaling, а так же принудительно завершая работу инстансов. Группы EC2 Auto Scaling используются для получения ресурсов, и автоматической замены инстансов в случае сбоя в работе или завершения работы из-за спотового прерывания.
  • Cluster Autoscaler развёртывается как deployment одного pod на одном из инстансов в группе EC2 Auto Scaling на инстансах по требованию. Предыдущая диаграмма для примера показывает Cluster Autoscaler в зоне доступности AZ1.
  • Каждая группа узлов соответствует одной группе EC2 Auto Scaling. Однако, Cluster Autoscaler требует, чтобы все инстансы в группе рабочих узлов имели одинаковое количество vCPU и одинаковый объем оперативной памяти. Чтобы придерживаться лучших практик использования спотовых инстансов и максимизировать диверсификацию, используйте несколько групп рабочих узлов. Каждая из этих групп рабочих узлов представляет собой группу EC2 Auto Scaling для инстансов различных типов с настроенной стратегией распределения спотовых инстансов, оптимизированной для использования наиболее свободных групп.

Kuberentes Node Groups

Автоматическое масштабирование кластера Kubernetes

Существует два стандартных способа масштабирования кластеров Kubernetes:

  1. Horizontal Pod Autoscaler (HPA) масштабирует pods в Deployment или ReplicaSet в соответствии с требованиями приложения. Правила масштабирования зависят от загрузки процессора или других пользовательских метрик.
  2. Cluster Autoscaler (CA) — отдельная программа, которая изменяет размер кластера Kubernetes в зависимости от текущих требований ресурсов. Она увеличивает размер кластера при наличии pods, которые не смогли запуститься ни на одном из текущих рабочих узлов из-за нехватки ресурсов. Она также пытается удалить низко загруженные узлы, когда pods, запущенные на этих узлах, могут быть перенесены на другие узлы.

Когда pod не может быть запущен из-за нехватки доступных ресурсов, Cluster Autoscaler определяет, что необходимо увеличить размер кластера и после этого увеличивает размер группы рабочих узлов. Когда используется несколько групп рабочих узлов, Cluster Autoscaler выбирает одну из них, основываясь на конфигурации Expander. В настоящее время поддерживаются следующие стратегии: random, most-pods, least-waste и priority.

В этом примере вы используете random стратегию для Cluster Autoscaler. Это — стратегия по умолчанию, и она произвольно выбирает группу рабочих узлов, когда кластер должен масштабироваться. Random стратегия максимизирует возможность использования несколько спотовых групп. Однако, вы можете попробовать и другие стратегии, одна из них может оказаться более подходящей для вашей рабочей нагрузки.

Atlassian Escalator:

Escalator — это альтернатива Cluster Autoscaler для пакетных рабочих нагрузок. Escalator предназначен для больших блоков вычислений или рабочих нагрузок, которые не могут быть остановлены или прерваны для перемещения на другие рабочие узлы, когда необходимо уменьшить размер кластера.

Группы EC2 Auto Scaling

Следование лучшим практикам использования спотовых инстансов означает развертывание инстансов различных типов и размеров в нескольких зонах доступности. EC2 Auto Scaling является одним из лучших механизмов для достижения этой цели. Группы EC2 Auto Scaling автоматически заменяют спотовые инстансы, которые были остановлены из-за прерывания, на инстанс из другой спотовой группы.

Группы EC2 Auto Scaling поддерживают запуск инстансов разных типов и размеров в одной группе. Для примера, в этой статье было выбрано соотношение 1:4 vCPU к памяти для всех типов инстансов, но у вашего приложения могут быть другие требования. Ниже приведены типы инстансов, выбранные для двух групп EC2 Auto Scaling:

  • Группа 4vCPU / 16GB: m5.xlarge, m5d.xlarge, m5n.xlarge, m5dn.xlarge, m5a.xlarge, m4.xlarge
  • Группа 8vCPU / 32GB: m5.2xlarge, m5d.2xlarge, m5n.2xlarge, m5dn.2xlarge, m5a.2xlarge, m4.2xlarge

В этом примере я использую в общей сложности 12 различных типов инстансов и три разных зоны доступности, что даёт мне (12 * 3 = 36) 36 различных спотовых группы. Группа EC2 Auto Scaling выбирает, какие типы инстансов будут запущены на основе стратегии распределения спотовых инстансов. Чтобы снизить вероятность прерывания, я использую стратегию распределения спотовых инстансов, оптимизированную для использования наиболее свободных групп. Эта стратегия автоматически запускает спотовые инстансы в наиболее свободных спотовых группах, отслеживая данные об их загрузке в режиме реального времени и определяя, какие из них являются наиболее свободными.

Capacity-optimized Spot allocation strategy

Пример стратегии распределения спотовых инстансов, оптимизированной для использования наиболее свободных групп.

Для Cluster Autoscaler и других административных приложений, а также рабочих нагрузок с сохранением состояния, запускаемых на рабочих узлах EKS, создается третья группа EC2 Auto Scaling с использованием инстансов по требованию. Это гарантирует, что Cluster Autoscaler не будет зависеть от спотовых прерываний. В Kubernetes, labels и nodeSelectors могут использоваться для управления размещением pods. Используйте nodeSelector для размещения Cluster Autoscaler на рабочем узле из группы EC2 Auto Scaling с использованием инстансов по требованию.

Примечание: Группы EC2 Auto Scaling постоянно пытаются сбалансировать количество инстансов во всех зонах доступности, в которых они развернуты. Это может привести к остановке инстансов, если во время масштабирования группы EC2 Auto Scaling она оказалась несбалансированной между зонами доступности. Эту функцию можно отключить, приостановив процесс AZRebalance, но это может привести к тому, что группа EC2 Auto Scaling станет несбалансированной. Другой возможностью является использование инструмента для безопасного завершения работы инстансов при масштабировании группы EC2 Auto Scaling, например, EKS Node Drainer. EKS Node Drainer — это функция AWS Lambda, которая интегрируется как EC2 Auto Scaling Lifecycle Hook. При вызове эта функция Lambda вызывает Kubernetes API для блокировки остановки инстанса и переноса всех pods с останавливаемого узла на другие рабочие узлы кластера. Затем она ждет окончания переноса всех pods с останавливаемого узла на другие рабочие узлы кластера, и снимает блокировку, чтобы группа EC2 Auto Scaling могла завершить остановку инстанса.

Обработка прерываний спотовых инстансов

Чтобы минимизировать последствия возможных прерываний спотовых инстансов, используйте обработчик остановки узлов (AWS Node Termination Handler). При его развёртывании, DaemonSet запускает pod с обработчиком для обнаружения уведомления о спотовом прерывании на каждом спотовом инстансе, что позволяет как безопасно остановить работу pods, работавших на этом узле, так и исключить его из балансировщиков нагрузки и позволить Kubernetes перенести pods на другие рабочие узлы кластера.

Процесс можно обобщить следующим образом:

  • Обнаружение уведомления об остановке спотового инстанса через два минуты.
  • Использование двухминутного интервала для подготовки рабочего узла к остановке.
  • Маркировка узла для предотвращения запуска на нём новых pods.
  • Исключение рабочего узла из кластера.

В результате:

  • Контроллеры, которые управляют объектами K8s, такими как Deployments и ReplicaSet, обнаружат, что один или несколько pods недоступны, и запустят их на других узлах.
  • Cluster Autoscaler и группа EC2 Auto Scaling при необходимости запустят новые инстансы.

Пошаговое руководство 

Запуск EKS

В первую очередь, используйте eksctl для создания кластера EKS с именем spotcluster-eksctl в сочетании с группой управляемых узлов. Группа управляемых узлов будет состоять из двух инстансов по требованию типа t3.medium и создастся с метками lifeecycle=OnDemand и intent=control-apps. Обязательно замените <YOUR REGION> на регион, в который вы запускаете свой кластер.

eksctl create cluster --version=1.15 --name=spotcluster-eksctl --node-private-networking --managed --nodes=3 --alb-ingress-access --region=&amp;lt;YOUR REGION&amp;gt; --node-type t3.medium --node-labels="lifecycle=OnDemand" --asg-access

Это займёт примерно 15 минут. Как только создание кластера будет завершено, проверьте подключенные рабочие узлы:

kubectl get nodes

Создание рабочих узлов

Используйте eksctl create nodegroup и конфигурационные файлы eksctl для добавления новых рабочих узлов в кластер. Сначала создайте файл конфигурации spot_nodegroups.yml. Затем вставьте этот код и замените <YOUR REGION> на регион, в котором вы запустили кластер EKS.

apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
    name: spotcluster-eksctl
    region: <YOUR REGION>
nodeGroups:
    - name: ng-4vcpu-16gb-spot
      minSize: 0
      maxSize: 5
      desiredCapacity: 1
      instancesDistribution:
        instanceTypes: ["m5.xlarge", "m5n.xlarge", "m5d.xlarge", "m5dn.xlarge", "m5a.xlarge", "m4.xlarge"]
        onDemandBaseCapacity: 0
        onDemandPercentageAboveBaseCapacity: 0
        spotAllocationStrategy: capacity-optimized
      labels:
        lifecycle: Ec2Spot
        intent: apps
        aws.amazon.com/spot: "true"
      tags:
        k8s.io/cluster-autoscaler/node-template/label/lifecycle: Ec2Spot
        k8s.io/cluster-autoscaler/node-template/label/intent: apps
      iam:
        withAddonPolicies:
          autoScaler: true
          albIngress: true
    - name: ng-8vcpu-32gb-spot
      minSize: 0
      maxSize: 5
      desiredCapacity: 1
      instancesDistribution:
        instanceTypes: ["m5.2xlarge", "m5n.2xlarge", "m5d.2xlarge", "m5dn.2xlarge", "m5a.2xlarge", "m4.2xlarge"]
        onDemandBaseCapacity: 0
        onDemandPercentageAboveBaseCapacity: 0
        spotAllocationStrategy: capacity-optimized
      labels:
        lifecycle: Ec2Spot
        intent: apps
        aws.amazon.com/spot: "true"
      tags:
        k8s.io/cluster-autoscaler/node-template/label/lifecycle: Ec2Spot
        k8s.io/cluster-autoscaler/node-template/label/intent: apps
      iam:
        withAddonPolicies:
          autoScaler: true
          albIngress: true

Данный конфигурационный файл добавляет две диверсифицированные группы рабочих узлов на спотовых инстансах типов 4vCPU/16GB и 8vCPU/32GB. Эти группы узлов используют стратегию распределения спотовых инстансов, оптимизированную для использования наиболее свободных групп, как описано выше. Наконец, все узлы помечаются жизненным циклом инстансов (lifecycle) «Ec2Spot», что в дальнейшем позволит развёртывать приложения на этих узлах, используя фильтр nodeSelectors. Чтобы создать обе группы узлов, запустите:

eksctl create nodegroup -f spot_nodegroups.yml

Это займёт примерно три минуты. После этого проверьте, что узлы были добавлены в кластер:

kubectl get nodes --show-labels --selector=lifecycle=Ec2Spot

Развёртывание обработчика остановки узлов

Вы можете установить .yaml файл из официального репозитория GitHub командой:

kubectl apply -f https://github.com/aws/aws-node-termination-handler/releases/download/v1.3.1/all-resources.yaml

Это устанавливает обработчик установки узлов как на рабочие узлы на спотовых инстансах, так и на рабочие узлы на инстансах по требованию, что полезно, так как обработчик реагирует как на события обслуживания EC2, так и на спотовые прерывания. Однако, если вы заинтересованы в развертывания обработчика только на узлах на спотовых инстансах, в репозитории есть инструкции для выполнения этой задачи.

Убедитесь, что обработчик остановки узлов запущен:

kubectl get daemonsets --all-namespaces

Развёртывание Cluster Autoscaler

Подробную информацию можно найти на странице EKS здесь. Выполните экспорт конфигурации Cluster Autoscaler в файл следующей командой:

curl -LO https://raw.githubusercontent.com/kubernetes/autoscaler/master/cluster-autoscaler/cloudprovider/aws/examples/cluster-autoscaler-autodiscover.yaml

Откройте созданный файл и отредактируйте команду развёртывания контейнера cluster-autoscaler, заменив <YOUR CLUSTER NAME> на имя вашего кластера, а также добавьте следующие опции:

--balance-similar-node-groups
--skip-nodes-with-system-pods=false

Также необходимо изменить конфигурацию Expander. Найдите - --expander= и замените least-waste на random.

Пример:

    spec:
        containers:
        - command:
            - ./cluster-autoscaler
            - --v=4
            - --stderrthreshold=info
            - --cloud-provider=aws
            - --skip-nodes-with-local-storage=false
            - --expander=random
            - --node-group-auto-discovery=asg:tag=k8s.io/cluster-autoscaler/enabled,k8s.io/cluster-autoscaler/<YOUR CLUSTER NAME>
            - --balance-similar-node-groups
            - --skip-nodes-with-system-pods=false

Сохраните файл, а затем установите Cluster Autoscaler командой:

kubectl apply -f cluster-autoscaler-autodiscover.yaml

Далее добавьте аннотацию cluster-autoscaler.kubernetes.io/safe-to-evict к установленному Cluster Autoscaler следующей командой:

kubectl -n kube-system annotate deployment.apps/cluster-autoscaler cluster-autoscaler.kubernetes.io/safe-to-evict="false"

Откройте страницу с релизами Cluster Autoscaler в веб-браузере и найдите последнюю версию Cluster Autoscaler, которая соответствует основной и второстепенной версии Kubernetes вашего кластера. Например, если версия Kubernetes вашего кластера 1.16, найдите последнюю версию Cluster Autoscaler, которая начинается с 1.16.

Установите тег образа Cluster Autoscaler на эту версию, используя следующую команду, заменив 1.15.n на значение найденной версии. Вы также можете заменить us на asia или eu:

kubectl -n kube-system set image deployment.apps/cluster-autoscaler cluster-autoscaler=us.gcr.io/k8s-artifacts-prod/autoscaling/cluster-autoscaler:v1.15.n

Чтобы просмотреть логи Cluster Autoscaler, используйте следующую команду:

kubectl -n kube-system logs -f deployment.apps/cluster-autoscaler

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

Создайте файл web-app.yaml, вставьте в него следующую спецификацию и сохраните файл:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-stateless
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        service: nginx
        app: nginx
    spec:
      containers:
      - image: nginx
        name: web-stateless
        resources:
          limits:
            cpu: 1000m
            memory: 1024Mi
          requests:
            cpu: 1000m
            memory: 1024Mi
      nodeSelector:    
        lifecycle: Ec2Spot
--- 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-stateful
spec:
  replicas: 2
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        service: redis
        app: redis
    spec:
      containers:
      - image: redis:3.2-alpine
        name: web-stateful
        resources:
          limits:
            cpu: 1000m
            memory: 1024Mi
          requests:
            cpu: 1000m
            memory: 1024Mi
      nodeSelector:
        lifecycle: OnDemand

Эта спецификация развёртывает три копии приложения «web-stateless», которые размещаются в одной из групп рабочих узлов на спотовых инстансах так, как nodeSelector выбирает узлы, помеченные lifecycle: Ec2Spot. Приложение «web-stateful» не является отказоустойчивыми и поэтому не подходит для развертывания на узлах на спотовых инстансах, поэтому для него снова используется nodeSelector и выбираются узлы, помеченные lifecycle: OnDemand. Архитектура, в которой вы размещаете на спотовых инстансах приложения, способные переживать прерывания, а на инстансах по требованию — остальные приложения, также может быть использована и для создания многопользовательских (multi-tenant) кластеров.

Разверните приложения следующей командой:

kubectl apply -f web-app.yaml

Убедитесь, что оба приложения запущены:

kubectl get deployment/web-stateless
kubectl get deployment/web-stateful

Теперь, отмасштабируйте «web-stateless» приложение до 30 копий:

kubectl scale --replicas=30 deployment/web-stateless

Убедитесь, что не все копии приложения смогли запуститься. Подождите примерно 5 минут и проверьте, что все копии запустились:

kubectl get pods

Удаление использованных ресурсов

Удалите обработчик остановки узлов:

kubectl delete daemonset aws-node-termination-handler -n kube-system

Удалите две группы EC2 Auto Scaling на спотовых инстансах, которые были развернуты ранее:

eksctl delete nodegroup ng-4vcpu-16gb-spot --cluster spotcluster-eksctl
eksctl delete nodegroup ng-8vcpu-32gb-spot --cluster spotcluster-eksctl

Если вы создали новый кластер, а не использовали существующий, удалите кластер EKS:

eksctl delete cluster --name spotcluster-eksctl

eksctl немедленно подтверждает удаление стека CloudFormation, но само удаление может занять до 15 минут. Вы можете отследить удаление в консоли CloudFormation.

Выводы

Следуя лучшим практикам, рабочие нагрузки Kubernetes могут быть развернуты на спотовых инстансах, обеспечивая как отказоустойчивость, так и оптимизацию затрат. Независимость приложений от типов инстансов и зон доступности является краеугольным камнем для использования нескольких спотовых групп и обеспечения необходимого масштаба развёртывания приложения. Кроме того, существуют заранее созданные инструменты для обработки прерываний спотовых инстансов, если таковые происходят. EKS облегчает поддержку подобных сценариев, снижая эксплуатационные затраты, предлагая масштабируемую и высокодоступную плоскость управления и управляемые группы рабочих узлов. Теперь вы готовы приступить к использованию спотовых инстансов для кластеров Kubernetes, чтобы снизить затраты на свои рабочие нагрузки и обеспечить необходимый масштаб развёртывания приложений.