Все что ты хотел знать о AWS Spot Instances, но боялся спросить

от автора

Всем привет, в данной статье мы затронем основные аспекты работы со спотовыми нодами AWS, как, что, и почему? А так же будут живые примеры инфраструктуры построенной на спотах, и в конце самое интересное это кол-во сэкономленных денег $. За подробностями прошу код кат.

Содержимое:

  1. Что такое споты, краткий принцип работы
  2. Что нужно для спотов, определится со стратегией
  3. Деплой CF стека со спотами + описание параметров
  4. Деплой Aws Node Termination Handler + дашборд + эвенты
  5. Как именно обрабатываются события и переезжают поды
  6. Cluster Autoscaler + Batch ETL живой пример
  7. Виндовые споты и IIS приложение
  8. Подсчет экономии на живом примере

1. Что такое споты, краткий принцип работы

Если простыми словами, то спотовые ноды это те же ec2 к которым вы привыкли, только они стоят на 50-70% дешевле чем обычный ec2, но есть нюанс — Амазон может «забрать» у тебя эти ноды в любой момент, при этом послав тебе уведомление за 2 минуты до его termination. А дальше уже ты должен обработать это событие перезапустить свои сервисы на соседней ноде и жить дальше, благо на все это есть автоматизация, которую мы детально рассмотрим ниже.

В данной статье мы будем говорить именно о AWS Spot Fleet это по сути коллекция нод в состав которой могут входить как спотовые так и on-demand ноды, а вот сколько и каких именно мы уже можем настраивать с помощью параметров этого Spot Fleet.

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

2. Что нужно для спотов, определится со стратегией

Spot Fleet параметры:

  • SpotInstancePools: 6
    Начнем с первого параметра что такое пулы для спот инстансов? Каждый конфиг инстанса, зона доступности и их свободное кол-во, это и есть пул. Рассмотрим на примере:
    Допустим мы используем все 3 зоны доступности для размещения наших нод, + мы выбрали как минимум 2 конфига для нашей коллекции Spot Fleet это t3.medium, t3a.medium Итого: 2 конфига * 3 зоны доступности = 6 пулов для спотов.

    Важный момент!

    Кол-во пулов рекомендуется выставлять по максимуму, так же как и выбирать как минимум 4 конфига схожих по параметрам и цене, будет все равно деплоится самый дешевый а если его нет то пойдет следующей по цене, но это все равно будет дешевле чем on-demand ноды. Если пулов или конфигов выберете мало то можете попасть на такой момент когда у амазона не будет нод, под ваши требования, старые уйдут в терминейт, новые не поднимутся.

  • SpotAllocationStrategy: lowest-price
    И так один из важных параметров это стратегия размещения, их бывает несколько, постараюсь коротко о каждом рассказать:

    priceCapacityOptimized — амазон выберет для вас пул спотовых нод где будет наименьшая вероятность в ближайшее время приход эвентов на terminate и следом из этих уже пулов выберет те где цена будет наименьшая, при этом вы балансируете якобы между capacity и низкой ценой, Такая консервативная стратегия, явно не для нас) надо выжать максимум чтобы быть как скрудж макдак на картинке)

    capacityOptimized- этот вариант еще более устойчивый к частоте прерываний работы спотовых нод, его так же рекомендуют для более высоких нагрузок типа обработки видео, или deep learning, в общем там где частое прерывание работы ноды сказалось бы хуже чем более стабильная работа длительное время но при более высокой цене за спотовую ноду.

    diversified — собственно из названия наверно уже догадались, диверсификация, это как в инвестициях, для более стабильного «портфеля», ваши ноды будут разбросаны по всем пулам равномерно, а в каждом пуле цена будет разнится, речь о самом дешевом варианта так же не идет.

    lowestPrice — ну и собственной самый интересный и мой выбор это самая низкая цена, раз уж мы решились внедрять споты, нужно выжать по максимуму с них, прерываний все равно не избежать, а значит наши приложения уже потенциально готовы к этому. В этом случае амазон будет подбирать вам самые дешевые пулы для нод, и соответственно скидка будет максимальной, на некоторые типы инстансов она доходит до 90%.

    InstancePoolsToUseCount — этот параметр используется только в комбинации с lowestPrice, мы можем как ограничивать кол-во пулов чтобы цена точно была низкой всегда, но тогда есть шанс что не будет свободных нод для заказа, либо выставить оптимальное кол-во пулов на основе наших зон доступности и конфигов инстансов, чтобы мы не остались совсем без нод и выбор был больше.

  • SpotMaxPrice: 0,5
    Можно так же для вашей коллекции задать максимальную цену на ноду в час, если мы этот параметр не задаем то максимальная цена считается = цена on-demand ноды, если задаем этот параметр то спотовые ноды выше этого лимита не будут задеплоены. Тут так же есть шанс попасть на то что в какой то момент ваша autoscale группа с Spot Fleet просто не сможем наполнится из за этих ограничений.

  • OnDemandAllocationStrategy: prioritized
    Теперь мы дошли до параметров которые отвечают за on-demand ноды в нашей Spot Fleet коллекции.
    Этот параметр отвечает за стратегию выбора нод для on-demand, рассмотрим пример, если выставить prioritized и мы допустим укажем 3 типа инстансов:
    m5.large – priority 1
    m4.large – priority 2
    m5a.large – priority 3
    То ноды будут деплоится в порядке приоритета, если не выставлять параметр prioritized, то амазон возьмет из списка выше самую дешевую, это, m4.large к примеру.

  • OnDemandBaseCapacity: 2
    Этот параметр отвечает за кол-во нод on-demand в вашей коллекции Spot Fleet. Опять же пример — если вы деплоете автоскейл группу с desired capacity 4, то в случае с этим параметром он вам задеплоит 2 ноды on-demand и 2 спотовых.

  • OnDemandPercentageAboveBaseCapacity: 0-100
    Параметр аналогичен тому что выше, за исключение что кол-во нод on-demand в вашей коллекции будет определять в % соотношении.

3. Деплой CF стека со спотами + описание параметров

Итак мы прошлись по теории кратко, теперь время задеплоить автоскейл группу с теми самыми Spot Fleet, ниже будет код CloudFormation стека, который деплоит группу спотовых нод для подключения к AWS EKS кластеру.

CloudFormation stack EKS node group linux

--- AWSTemplateFormatVersion: 2010-09-09 Description: Amazon EKS - Linux Node Group  Parameters:    KeyName:     Description: The EC2 Key Pair to allow SSH access to the instances     Type: AWS::EC2::KeyPair::KeyName    NodeImageId:     Type: "AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>"     Default: /aws/service/eks/optimized-ami/1.24/amazon-linux-2/recommended/image_id     Description: AWS Systems Manager Parameter Store parameter of the AMI ID for the worker node instances.    NodeInstanceType:     Description: EC2 instance type for the node instances     Type: String     ConstraintDescription: Must be a valid EC2 instance type    NodeAutoScalingGroupMinSize:     Description: Minimum size of Node Group ASG.     Type: Number    NodeAutoScalingGroupMaxSize:     Description: Maximum size of Node Group ASG. Set to at least 1 greater than NodeAutoScalingGroupDesiredCapacity.     Type: Number    NodeAutoScalingGroupDesiredCapacity:     Description: Desired capacity of Node Group ASG.     Type: Number    NodeVolumeSize:     Description: Node volume size     Type: Number     Default: 30    ClusterName:     Description: The cluster name provided when the cluster was created. If it is incorrect, nodes will not be able to join the cluster.     Type: String    OnDemandBaseCapacity:     Type: Number     Description: "on-demand base capacity"     Default: 0    OnDemandPercentageAboveBaseCapacity:     Type: Number     Description: "on-demand percentage above base capacity(0-100)"     Default: 0    SpotInstancePools:     Type: Number     Description: "spot instance pools(1-20)"     Default: 12    InstanceTypesOverride:     Type: String     Description: "multiple spot instances to override(seperated by comma, 4 counts) t3.medium,t2.medium,t3a.medium,t3.large"    BootstrapArgumentsForOnDemand:     Description: Arguments to pass to the bootstrap script. See files/bootstrap.sh in https://github.com/awslabs/amazon-eks-ami     Default: "--kubelet-extra-args --node-labels=lifecycle=OnDemand"      Type: String    BootstrapArgumentsForSpotFleet:     Description: Arguments to pass to the bootstrap script. See files/bootstrap.sh in https://github.com/awslabs/amazon-eks-ami     Default: "--kubelet-extra-args --node-labels=lifecycle=Ec2Spot"     Type: String    NodeGroupName:     Description: Unique identifier for the Node Group.     Type: String    ClusterControlPlaneSecurityGroup:     Description: The security group of the cluster control plane.     Type: AWS::EC2::SecurityGroup::Id    SharedNodeSecurityGroup:     Description: The security group for communication between all nodes in the cluster.     Type: AWS::EC2::SecurityGroup::Id    VpcId:     Description: The VPC of the worker instances     Type: AWS::EC2::VPC::Id    Subnets:     Description: The subnets where workers can be created.     Type: List<AWS::EC2::Subnet::Id>    UnlimitedCredits:     Type: String     Description: "Select yes if instance type t3,t2 family"     AllowedValues: [yes, no]     Default: yes    PauseTime:     Type: String     Description: "Select yes if you want pause node update"     AllowedValues: [yes, no]     Default: yes    SetPauseTime:     Type: String     Description: "Set delay for node update"     Default: PT15M  Metadata:    AWS::CloudFormation::Interface:     ParameterGroups:       - Label:           default: EKS Cluster         Parameters:           - ClusterName           - ClusterControlPlaneSecurityGroup       - Label:           default: Worker Node Configuration         Parameters:           - NodeGroupName           - NodeAutoScalingGroupMinSize           - NodeAutoScalingGroupDesiredCapacity           - NodeAutoScalingGroupMaxSize           - NodeInstanceType           - NodeImageId           - NodeVolumeSize           - KeyName       - Label:           default: Worker Network Configuration         Parameters:           - VpcId           - Subnets  Conditions:   UsePauseTime: !Equals [!Ref PauseTime, yes]   UseUnlimitedCredits: !Equals [!Ref UnlimitedCredits, yes]  Resources:    NodeInstanceProfile:     Type: AWS::IAM::InstanceProfile     Properties:       Path: "/"       Roles:         - !Ref NodeInstanceRole    NodeInstanceRole:     Type: AWS::IAM::Role     Properties:       AssumeRolePolicyDocument:         Version: 2012-10-17         Statement:           - Effect: Allow             Principal:               Service: ec2.amazonaws.com             Action: sts:AssumeRole       Path: "/"       ManagedPolicyArns:         - arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy         - arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy         - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly         - !Sub 'arn:aws:iam::${AWS::AccountId}:policy/ALB_Ingress_Controller'    NodeSecurityGroup:     Type: AWS::EC2::SecurityGroup     Properties:       GroupDescription: Security group for all nodes in the cluster       VpcId: !Ref VpcId       Tags:         - Key: !Sub kubernetes.io/cluster/${ClusterName}           Value: owned    NodeSecurityGroupIngress:     Type: AWS::EC2::SecurityGroupIngress     DependsOn: NodeSecurityGroup     Properties:       Description: Allow node to communicate with each other       GroupId: !Ref NodeSecurityGroup       SourceSecurityGroupId: !Ref NodeSecurityGroup       IpProtocol: -1       FromPort: 0       ToPort: 65535    NodeSecurityGroupFromControlPlaneIngress:     Type: AWS::EC2::SecurityGroupIngress     DependsOn: NodeSecurityGroup     Properties:       Description: Allow worker Kubelets and pods to receive communication from the cluster control plane       GroupId: !Ref NodeSecurityGroup       SourceSecurityGroupId: !Ref ClusterControlPlaneSecurityGroup       IpProtocol: tcp       FromPort: 1025       ToPort: 65535    ControlPlaneEgressToNodeSecurityGroup:     Type: AWS::EC2::SecurityGroupEgress     DependsOn: NodeSecurityGroup     Properties:       Description: Allow the cluster control plane to communicate with worker Kubelet and pods       GroupId: !Ref ClusterControlPlaneSecurityGroup       DestinationSecurityGroupId: !Ref NodeSecurityGroup       IpProtocol: tcp       FromPort: 1025       ToPort: 65535    NodeSecurityGroupFromControlPlaneOn443Ingress:     Type: AWS::EC2::SecurityGroupIngress     DependsOn: NodeSecurityGroup     Properties:       Description: Allow pods running extension API servers on port 443 to receive communication from cluster control plane       GroupId: !Ref NodeSecurityGroup       SourceSecurityGroupId: !Ref ClusterControlPlaneSecurityGroup       IpProtocol: tcp       FromPort: 443       ToPort: 443    ControlPlaneEgressToNodeSecurityGroupOn443:     Type: AWS::EC2::SecurityGroupEgress     DependsOn: NodeSecurityGroup     Properties:       Description: Allow the cluster control plane to communicate with pods running extension API servers on port 443       GroupId: !Ref ClusterControlPlaneSecurityGroup       DestinationSecurityGroupId: !Ref NodeSecurityGroup       IpProtocol: tcp       FromPort: 443       ToPort: 443    ClusterControlPlaneSecurityGroupIngress:     Type: AWS::EC2::SecurityGroupIngress     DependsOn: NodeSecurityGroup     Properties:       Description: Allow pods to communicate with the cluster API Server       GroupId: !Ref ClusterControlPlaneSecurityGroup       SourceSecurityGroupId: !Ref NodeSecurityGroup       IpProtocol: tcp       ToPort: 443       FromPort: 443    NodeGroup:     Type: AWS::AutoScaling::AutoScalingGroup     Properties:       DesiredCapacity: !Ref NodeAutoScalingGroupDesiredCapacity       MixedInstancesPolicy:         InstancesDistribution:           OnDemandAllocationStrategy: prioritized           OnDemandBaseCapacity: !Ref OnDemandBaseCapacity           OnDemandPercentageAboveBaseCapacity: !Ref OnDemandPercentageAboveBaseCapacity           SpotAllocationStrategy: lowest-price           SpotInstancePools: !Ref SpotInstancePools           # SpotMaxPrice: String         LaunchTemplate:           LaunchTemplateSpecification:             LaunchTemplateId: !Ref MyLaunchTemplate             # LaunchTemplateName: String             Version: !GetAtt MyLaunchTemplate.LatestVersionNumber           Overrides:             - InstanceType: !Select [0, !Split [ ",", !Ref InstanceTypesOverride ] ]             - InstanceType: !Select [1, !Split [ ",", !Ref InstanceTypesOverride ] ]             - InstanceType: !Select [2, !Split [ ",", !Ref InstanceTypesOverride ] ]             - InstanceType: !Select [3, !Split [ ",", !Ref InstanceTypesOverride ] ]       MinSize: !Ref NodeAutoScalingGroupMinSize       MaxSize: !Ref NodeAutoScalingGroupMaxSize       VPCZoneIdentifier:         !Ref Subnets       Tags:       - Key: Name         Value: !Sub "${ClusterName}-${NodeGroupName}-Node"         PropagateAtLaunch: 'true'       - Key: !Sub 'kubernetes.io/cluster/${ClusterName}'         Value: 'owned'         PropagateAtLaunch: 'true'     UpdatePolicy:       AutoScalingRollingUpdate:         MinInstancesInService: '1'         MaxBatchSize: '1'         PauseTime: !If [UsePauseTime, !Ref SetPauseTime, !Ref "AWS::NoValue"]    MyLaunchTemplate:     Type: AWS::EC2::LaunchTemplate     Properties:       LaunchTemplateName: !Sub "eksLaunchTemplate-${AWS::StackName}"       LaunchTemplateData:         CreditSpecification:            CpuCredits: !If [UseUnlimitedCredits, unlimited, !Ref "AWS::NoValue"]         TagSpecifications:           -             ResourceType: instance             Tags:               - Key: Name                 Value: !Sub "${ClusterName}-${NodeGroupName}-ASG-Node"               - Key: KubernetesCluster                 Value: !Ref ClusterName               - Key: !Sub 'kubernetes.io/cluster/${ClusterName}'                 Value: 'owned'         BlockDeviceMappings:         - DeviceName: /dev/xvda           Ebs:             VolumeSize: !Ref NodeVolumeSize             VolumeType: gp3             DeleteOnTermination: true         UserData: !Base64           "Fn::Sub": |             #!/bin/bash             set -o xtrace             iid=$(curl -s http://169.254.169.254/latest/meta-data/instance-id)             export AWS_DEFAULT_REGION=${AWS::Region}             ilc=`aws ec2 describe-instances --instance-ids  $iid  --query 'Reservations[0].Instances[0].InstanceLifecycle' --output text`             if [ "$ilc" == "spot" ]; then               /etc/eks/bootstrap.sh ${ClusterName} ${BootstrapArgumentsForSpotFleet}             else               /etc/eks/bootstrap.sh ${ClusterName} ${BootstrapArgumentsForOnDemand}             fi             # /etc/eks/bootstrap.sh ${ClusterName} $BootstrapArgumentsForOnDemand             /opt/aws/bin/cfn-signal --exit-code $? \                      --stack  ${AWS::StackName} \                      --resource NodeGroup  \                      --region ${AWS::Region}         IamInstanceProfile:           Arn: !GetAtt NodeInstanceProfile.Arn         KeyName: !Ref KeyName         NetworkInterfaces:           -             DeviceIndex: 0             AssociatePublicIpAddress: 'false'             SubnetId: !Select [0, !Ref Subnets]             Groups:               - !Ref NodeSecurityGroup               - !Ref SharedNodeSecurityGroup         ImageId: !Ref NodeImageId         InstanceType: !Ref NodeInstanceType    LCH:     Type: AWS::AutoScaling::LifecycleHook     Properties:       AutoScalingGroupName: !Ref NodeGroup       HeartbeatTimeout: 60       DefaultResult: CONTINUE       LifecycleHookName: !Sub "${NodeGroupName}-LCH"       LifecycleTransition: autoscaling:EC2_INSTANCE_TERMINATING  Outputs:    NodeInstanceRole:     Description: The node instance role     Value: !GetAtt NodeInstanceRole.Arn    NodeSecurityGroup:     Description: The security group for the node group     Value: !Ref NodeSecurityGroup 

Пройдемся по основным параметрам которые задаем при деплое что касается Spot Fleet:

Кейс1 — деплой группы нод только спотовых для любых приложений.

NodeAutoScalingGroupDesiredCapacity: 5 #выставляем минимальное кол-во нод которое хотим видеть всегда к примеру 5  NodeAutoScalingGroupMinSize:3 #Можно выставить условно, если не планируете использовать какие то правила для автоскейлинга, тогда опираемся только на desired capacity  NodeAutoScalingGroupMaxSize: 10 #Можно выставить условно, если не планируете использовать какие то правила для автоскейлинга, тогда опираемся только на desired capacity  OnDemandBaseCapacity: 0 #Группа нод только спотовых  OnDemandPercentageAboveBaseCapacity: 0  #Группа нод только спотовых  InstanceTypesOverride: t3.medium,t2.medium,t3a.medium,t3.large #Типы инстансов которые хотите видеть у себя в коллекции  SpotInstancePools: 12 #3 зоны доступности * 4 конфига инстансов = 12

Кейс2 — деплой группы нод споты + on-demand для критически важных приложений где отсутствие нод грозит большими потерями.

Все те же параметры что и выше кроме:

 OnDemandBaseCapacity: 2 #Группа нод только спотовых

Итого у нас будет 2 ноды on-demand всегда доступные и 3 спотовых по сниженной цене из того же набора конфигов что мы указали выше.

В Cloudformation стеке выше мы используем в итоге стратегию lowest-price для спотов и prioritized для on-demand нод.

          OnDemandAllocationStrategy: prioritized           OnDemandBaseCapacity: !Ref OnDemandBaseCapacity           OnDemandPercentageAboveBaseCapacity: !Ref OnDemandPercentageAboveBaseCapacity           SpotAllocationStrategy: lowest-price           SpotInstancePools: !Ref SpotInstancePools

4. Деплой Aws Node Termination Handler + дашборд + эвенты

И так ноды мы задеплоили, что дальше?! как обрабатывать события когда амазон шлет тебе весточку что через 2 минуты твоя нода выключится. Мы будем рассматривать на примере нод для AWS EKS, поэтому и события будем ловить и обрабатывать из кубера.

Aws Node Termination Handler GitHub

За вас уже все сделали, нужно только немного настроить и задеплоить все это в ваш кластер, сразу скажу что лучше всего настраивать этот микросервис в режиме Queue Processor, плюсы там же в гитхабе перечислены. Во первых он больше эвентов может обрабатывать, во вторых в этом режиме он будет запущен всего в 1 экземпляре и смотреть в вашу настроенную SQS очередь, вместо daemonset-а который на каждой спотовой ноде запустится и будет смотреть терминейшн эвенты по урлу http://169.254.169.254/latest/meta-data/spot/instance-action.

Перед тем как деплоить Aws Node Termination Handler нужно немного настроиться со стороны сервисов AWS, если коротко то нам нужно:

  1. IAM Role
  2. Openid Connect Provider for EKS
  3. SQS Queue & SQS Queue Policy
  4. EventBridge rules

Есть готовый terraform код для этого, можно посмотреть тут: Terraform code

Есть так же готовый хелм чарт, пройдемся немного по настройкам основным:

Прописываем аннотацию для serviceaccount.

serviceaccount

serviceAccount:   # Specifies whether a service account should be created   create: true   # The name of the service account to use. If namenot set and create is true, a name is generated using fullname template   name:   annotations:     eks.amazonaws.com/role-arn: arn:aws:iam::<YOUR ACCOUNT ID>:role/node-termination-handler

Включаем режим SQS Queue mode.

enableSqsTerminationDraining

# enableSqsTerminationDraining If true, this turns on queue-processor mode which drains nodes when an SQS termination event is received enableSqsTerminationDraining: true

Настраиваем нотификацию себе, ссылка + темплейт.

webhooks

# webhookURL if specified, posts event data to URL upon instance interruption action. webhookURL: "https://xxx"  # webhookTemplate if specified, replaces the default webhook message template. webhookTemplate: '{"text":"[Instance Interruption]   \nAWS Account: {{ .AccountId }}   \nKind: {{ .Kind }}   \nInstance: {{ .InstanceID }}   \nNode: {{ .NodeName }}   \nDescription: {{ .Description }}   \nStart Time: {{ .StartTime }}   \nEventID: {{ .EventID }}"}'

Указываем ссылку на созданную очередь SQS.

queueURL

# Listens for messages on the specified SQS queue URL queueURL: "https://sqs.<region>.amazonaws.com/YOUR ACCOUNT ID/AWSTerminationHandlerQueue"

Я так же включал метрики и сервис монитор, чтобы потом можно было на графике смотреть как часто у нас ноды уходят и в каком количестве.

Собственно дашборд очень простой, кому надо можете что по интереснее добавить, но мне главное чтобы было видно общее количество + статистика за день.

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

image

Пример эвентов приходящих от AWS Node Termination Handler выглядит вот так:

image

5. Как именно обрабатываются события и переезжают поды

Попробую вкратце описать всю цепочку событий чтобы было понятней:

  1. AWS Node Termination Handler мониторит SQS Queue на наличие нужных нам эвентов
  2. Амазон посылает эвент о том что заберет ноду с id xxx
  3. AWS Node Termination Handler принял этот эвент из SQS Queue и тут же отправил cordon и drain этой ноде
  4. Ваши поды обязаны корректно завершится и запустится на соседней свободной ноде
  5. Нода ушла в Terminate, и так же удалится из списка нод в кубере
  6. Autoscale группа увидит что количество desired нод не совпадает, и закажет еще одну спотовую
  7. Нода задеплоится и подключится к кластеру взамен старой.

И все это происходит без вашего участия, вы можете наблюдать за этими событиями со стороны на графике или смотреть эвенты иногда которые AWS Node Termination Handler шлёт через вебхук.

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

Spot Instance advisor

Здесь очень важно все оттестировать заранее на какой ни будь тестовой коллекции нод, убедившись в том что ваше приложение готово к drain, и при этом вы не ловите 5хх ошибки в процессе рестарта пода.

Для этого есть много разных вариантов как правильно делать Graceful Shutdown для микросервисов написанных на разных языках.

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

6. Cluster Autoscaler + Batch ETL на спотах живой пример

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

Описание задачи:

  • Основная часть отчетов стартует после 1 ночи, и завершается где то в 10-11 утра
  • После 11 утра до 21 00 ни чего не выполняется, ноды не нужны
  • После 21 00 нужна только 1 нода максимум, до 1 ночи
  • Перезапуск подов выполняющих отчетность не грозит сбоем всего отчета, рестарт в следующую попытку все продолжает.

Для выполнения задачи описанной выше как ни как, кстати подходит Cluster AutoScaler + спотовые ноды, благодаря спотовым нодам мы можем выбрать конфиг повыше при той же стоимости on-demand и отчеты будут строится быстрее.

Деплоим коллекцию нод под отчеты, и скейлим её в 0 по умолчанию, как только у нас появляются поды на запуск на этой группе нод, Cluster Autoscaler поднимает нам нужное количество нод, для размещения всех подов, как только поды отработали и больше нет задач для запуска, Cluster Autoscaler скейлит нашу коллекцию нод опять в 0.

Тут конечно важно подобрать requests & limits для ваших подов, а так же тайминги сколько будет ждать Cluster Autoscaler перед тем как скейлить коллекцию в 0. Приведу ниже ссылку на гитхаб Cluster Autoscaler и некоторые параметры из его хелм чарта при которых сценарий выше работает

Cluster Autoscaler GitHub

Helm Chart Values

autoscalingGroups:   - name: NODE GROUP NAME     maxSize: 3     minSize: 0  extraArgs:   logtostderr: true   stderrthreshold: info   v: 4   scale-down-utilization-threshold: 0.5   daemonset-eviction-for-empty-nodes: true   scale-down-unneeded-time: 30m

image

7. Виндовые споты и IIS приложение

Еще один кейс, спотовые ноды можно использовать не только в классических кластерах где линуксовые ноды только. Мы используем гибридный кластер AWS EKS где одновременно работают как линуксовые ноды так и ноды на windows server core.

Как всем известно виндовые ноды в амазоне стоят раза в 2 дороже, из за стоимости лицензии, и соответственно использовать их со скидкой в 50-70% это очень помогает снизить общие косты за инфраструктуру.

На виндовых нодах у нас в основном работают IIS приложения, которые надо так же готовить к корректному завершению соединений когда прилетает drain на ноду от Aws Node Termination Handler.

Можно использовать lifecycle preStop + terminationGracePeriodSeconds

  lifecycle:       preStop:       exec:         command: [           "iisreset", "/stop", "/noforce"         ]      terminationGracePeriodSeconds: 60

Опять же зависит от вашего приложения, если у него есть привязка сессий, то тут надо уже другие варианты продумывать, вариант выше это как рекомендация лиж для начального этапа проверок.

Так же в случае использования спотовых нод для виндовых контейнеров, все мы знаем размеры виндовых контейнеров, да в 2022 винде они снизились, но все равно далеки от идеала. Поэтому нужно позаботится об AMI образе с залитым базовым докер образом для вашего микросервиса, чтобы процесс бутстрапа проходил в районе 10-12 минут максимум.

8. Подсчет экономии на живом примере

И так если вы дочитали или домотали до этого места, вам все таки интересно ради чего столько приседаний и обвязок, и стоит ли оно того.

Что имеем:

Общее кол-во спотовых нод: 46

Количество по конфигам:

Linux:
t3a.large — 3
m5.xlarge — 2
t3.xlarge — 4
m6i.xlarge — 4
t3.medium — 16
r6i.xlarge — 2
t3.large — 4

Windows:
c6i.xlarge — 6
t3.medium — 5

Тотал:
On-demand цена Linux: 2956.82$
On-demand цена Windows: 1964.75$
On-demand цена за все: 4921,57$

против

Spot цена Linux: 829,224$
Spot цена Windows: 1148,688$
Spot цена за все: 1977,912$

*Стоимость посчитана по 1 региону и по 1 зоне доступности, без разделения.

Можно так же посмотреть Saving Summary в разделе Spot Requests но он покажет только за последние 3 дня.

Заключение

И так я надеюсь после прочитанного вам захотелось попробовать как минимум свои приложения в работе со спотами. Что хотел бы сказать, даже если у вас с первого раза будет видно что ваше приложение плохо обрабатывает рестарт и корректное закрытие соединения, не отчаивайтесь, попробуйте оптимизировать его, допишите небольшие конструкции которое помогут сделать ваше приложение более stateless, или возьмите готовые примеры, на хабре так же была масса статей про Gracefull Shutdown.

Как только вы преодолеете эти трудности и внедрите споты, ваша инфраструктура встанет на ряду с такими же гигантами как Lyft, Dropbox, Skyscanner, которые уже давно используют споты повсеместно для теста и прода, для аналитики и батчевых операций, экономя при этом десятки тысяч долларов. Чем ты хуже?)


ссылка на оригинал статьи https://habr.com/ru/post/719052/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *