Class: Hako::Schedulers::EcsAutoscaling

Inherits:
Object
  • Object
show all
Defined in:
lib/hako/schedulers/ecs_autoscaling.rb

Defined Under Namespace

Classes: Policy

Constant Summary collapse

PUT_METRIC_ALARM_OPTIONS =
%i[
  alarm_name alarm_description actions_enabled ok_actions alarm_actions
  insufficient_data_actions metric_name namespace statistic dimensions
  period unit evaluation_periods threshold comparison_operator
].freeze

Instance Method Summary collapse

Constructor Details

#initialize(options, dry_run:) ⇒ EcsAutoscaling

Returns a new instance of EcsAutoscaling.



9
10
11
12
13
14
15
# File 'lib/hako/schedulers/ecs_autoscaling.rb', line 9

def initialize(options, dry_run:)
  @dry_run = dry_run
  @role_arn = required_option(options, 'role_arn')
  @min_capacity = required_option(options, 'min_capacity')
  @max_capacity = required_option(options, 'max_capacity')
  @policies = required_option(options, 'policies').map { |r| Policy.new(r) }
end

Instance Method Details

#apply(service) ⇒ nil

Parameters:

  • service (Aws::ECS::Types::Service)

Returns:

  • (nil)


25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/hako/schedulers/ecs_autoscaling.rb', line 25

def apply(service)
  resource_id = service_resource_id(service)
  service_namespace = 'ecs'
  scalable_dimension = 'ecs:service:DesiredCount'

  Hako.logger.info("Registering scalable target to #{resource_id}")
  unless @dry_run
    autoscaling_client.register_scalable_target(
      service_namespace: service_namespace,
      resource_id: resource_id,
      scalable_dimension: scalable_dimension,
      min_capacity: @min_capacity,
      max_capacity: @max_capacity,
      role_arn: @role_arn,
    )
  end
  @policies.each do |policy|
    Hako.logger.info("Configuring scaling policy #{policy.name}")
    if @dry_run
      policy.alarms.each do |alarm_name|
        Hako.logger.info("Configuring #{alarm_name}'s alarm_action")
      end
    else
      policy_arn = autoscaling_client.put_scaling_policy(
        policy_name: policy.name,
        service_namespace: service_namespace,
        resource_id: resource_id,
        scalable_dimension: scalable_dimension,
        policy_type: 'StepScaling',
        step_scaling_policy_configuration: {
          adjustment_type: policy.adjustment_type,
          step_adjustments: [
            {
              scaling_adjustment: policy.scaling_adjustment,
              metric_interval_lower_bound: policy.metric_interval_lower_bound,
              metric_interval_upper_bound: policy.metric_interval_upper_bound,
            },
          ],
          cooldown: policy.cooldown,
          metric_aggregation_type: policy.metric_aggregation_type,
        },
      ).policy_arn

      alarms = cw_client.describe_alarms(alarm_names: policy.alarms).flat_map(&:metric_alarms).map { |a| [a.alarm_name, a] }.to_h
      policy.alarms.each do |alarm_name|
        alarm = alarms.fetch(alarm_name) { raise Error.new("Alarm #{alarm_name} does not exist") }
        Hako.logger.info("Updating #{alarm_name}'s alarm_actions from #{alarm.alarm_actions} to #{[policy_arn]}")
        params = PUT_METRIC_ALARM_OPTIONS.map { |key| [key, alarm.public_send(key)] }.to_h
        params[:alarm_actions] = [policy_arn]
        cw_client.put_metric_alarm(params)
      end
    end
  end
  nil
end

#autoscaling_clientAws::ApplicationAutoScaling (private)

Returns:

  • (Aws::ApplicationAutoScaling)


124
125
126
# File 'lib/hako/schedulers/ecs_autoscaling.rb', line 124

def autoscaling_client
  @autoscaling_client ||= Aws::ApplicationAutoScaling::Client.new
end

#cw_clientAws::CloudWatch::Client (private)

Returns:

  • (Aws::CloudWatch::Client)


129
130
131
# File 'lib/hako/schedulers/ecs_autoscaling.rb', line 129

def cw_client
  @cw_client ||= Aws::CloudWatch::Client.new
end

#remove(service) ⇒ nil

Parameters:

  • service (Aws::ECS::Types::Service)

Returns:

  • (nil)


83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/hako/schedulers/ecs_autoscaling.rb', line 83

def remove(service)
  resource_id = service_resource_id(service)
  service_namespace = 'ecs'
  scalable_dimension = 'ecs:service:DesiredCount'

  Hako.logger.info("Deregister scalable target #{resource_id} and its policies")
  unless @dry_run
    begin
      autoscaling_client.deregister_scalable_target(service_namespace: service_namespace, resource_id: resource_id, scalable_dimension: scalable_dimension)
    rescue Aws::ApplicationAutoScaling::Errors::ObjectNotFoundException => e
      Hako.logger.warn(e)
    end
  end
  nil
end

#required_option(options, key) ⇒ Object (private)

Parameters:

  • options (Hash)
  • key (String)

Returns:

  • (Object)


119
120
121
# File 'lib/hako/schedulers/ecs_autoscaling.rb', line 119

def required_option(options, key)
  options.fetch(key) { raise Error.new("scheduler.autoscaling.#{key} must be set") }
end

#service_resource_id(service) ⇒ String (private)

Parameters:

  • service (Aws::ECS::Types::Service)

Returns:

  • (String)


135
136
137
# File 'lib/hako/schedulers/ecs_autoscaling.rb', line 135

def service_resource_id(service)
  "service/#{service.cluster_arn.slice(%r{[^/]+\z}, 0)}/#{service.service_name}"
end

#status(service) ⇒ nil

Parameters:

  • service (Aws::ECS::Types::Service)

Returns:

  • (nil)


101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/hako/schedulers/ecs_autoscaling.rb', line 101

def status(service)
  resource_id = service_resource_id(service)
  service_namespace = 'ecs'
  scalable_dimension = 'ecs:service:DesiredCount'

  autoscaling_client.describe_scaling_activities(service_namespace: service_namespace, resource_id: resource_id, scalable_dimension: scalable_dimension, max_results: 50).scaling_activities.each do |activity|
    puts "  [#{activity.start_time} - #{activity.end_time}] #{activity.status_message}"
    puts "    description: #{activity.description}"
    puts "    cause: #{activity.cause}"
    puts "    details: #{activity.details}"
  end
end