Skip to content

Commit

Permalink
refactor: AWSリソース関連の処理を rake タスクの外へ移動
Browse files Browse the repository at this point in the history
  • Loading branch information
kzkn committed Oct 13, 2023
1 parent 390c751 commit f6e4169
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 82 deletions.
2 changes: 2 additions & 0 deletions lib/sg_fargate_rails.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

require_relative "sg_fargate_rails/version"
require_relative "sg_fargate_rails/config"
require_relative "sg_fargate_rails/current_ecs_task"
require_relative "sg_fargate_rails/event_bridge_schedule"
require 'lograge'

if defined?(::Rails::Railtie)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,6 @@ def cluster_arn
metadata[:Cluster]
end

def account_id
cluster_arn.split(':')[4]
end

def task_definition_arn
"#{cluster_arn.split(":cluster/")[0]}:task-definition/#{metadata[:Family]}:#{metadata[:Revision]}"
end
Expand All @@ -21,6 +17,35 @@ def cfn_stack_name
end

def security_group_ids
@security_group_ids ||= fetch_security_group_ids
end

def public_subnet_ids
@public_subnet_ids ||= fetch_public_subnet_ids
end

private

def metadata
@metadata ||= begin
response = Net::HTTP.get(URI.parse("#{ENV['ECS_CONTAINER_METADATA_URI']}/task"))
JSON.parse(response, symbolize_names: true)
end
end

def region
ENV['AWS_REGION'] || 'ap-northeast-1'
end

def ec2_client
@ec2_client ||= Aws::EC2::Client.new(region: region, credentials: credentials)
end

def credentials
@credentials ||= Aws::ECSCredentials.new(retries: 3)
end

def fetch_security_group_ids
security_group_params = {
filters: [
{
Expand All @@ -37,7 +62,7 @@ def security_group_ids
resp.to_h[:security_groups].map { |group| group[:group_id] }
end

def public_subnet_ids
def fetch_public_subnet_ids
subnet_params = {
filters: [
{
Expand All @@ -53,26 +78,5 @@ def public_subnet_ids
resp = ec2_client.describe_subnets(subnet_params)
resp.to_h[:subnets].map { |subnet| subnet[:subnet_id] }
end

private

def metadata
@metadata ||= begin
response = Net::HTTP.get(URI.parse("#{ENV['ECS_CONTAINER_METADATA_URI']}/task"))
JSON.parse(response, symbolize_names: true)
end
end

def region
ENV['AWS_REGION'] || 'ap-northeast-1'
end

def ec2_client
@ec2_client ||= Aws::EC2::Client.new(region: region, credentials: credentials)
end

def credentials
@credentials ||= Aws::ECSCredentials.new(retries: 3)
end
end
end
92 changes: 92 additions & 0 deletions lib/sg_fargate_rails/event_bridge_schedule.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
require "aws-sdk-scheduler"

module SgFargateRails
class EventBridgeSchedule
attr_reader :name

def initialize(name, cron, command)
@name = name
@cron = cron
@command = command
end

def create_run_task(group_name:, cluster_arn:, task_definition_arn:, network_configuration:)
params = {
name: @name,
state: 'ENABLED',
flexible_time_window: { mode: 'OFF' },
group_name: group_name,
schedule_expression: @cron,
schedule_expression_timezone: timezone,
target: {
arn: cluster_arn,
ecs_parameters: {
task_count: 1,
task_definition_arn: task_definition_arn,
launch_type: 'FARGATE',
network_configuration: network_configuration
},
input: {
"containerOverrides": [
{
"name": "rails",
"command": container_command,
}
]
}.to_json,
retry_policy: {
maximum_event_age_in_seconds: 120,
maximum_retry_attempts: 2,
},
role_arn: role_arn_for(group_name, cluster_arn),
},
}
client.create_schedule(params)
end

def container_command
%w[bundle exec] + @command.split(' ')
end

private

def timezone
ENV['TZ'] || 'Asia/Tokyo'
end

def role_arn_for(group_name, cluster_arn)
account_id = cluster_arn.split(':')[4]
"arn:aws:iam::#{account_id}:role/#{group_name}-eventbridge-scheduler-role"
end

def client
self.class.client
end

class << self
def parse(filename)
schedules = YAML.load(File.open(filename))
schedules.map { |name, info| EventBridgeSchedule.new(name, info['cron'], info['command']) }
end

def delete_all!(group_name)
client.list_schedules(group_name: group_name, max_results: 100).schedules.each do |schedule|
client.delete_schedule(name: schedule.name, group_name: group_name)
Rails.logger.info "[EventBridgeSchedule] Deleted #{group_name}/#{schedule.name}"
end
end

def client
@client ||= Aws::Scheduler::Client.new(region: region, credentials: credentials)
end

def region
ENV['AWS_REGION'] || 'ap-northeast-1'
end

def credentials
Aws::ECSCredentials.new(retries: 3)
end
end
end
end
76 changes: 20 additions & 56 deletions lib/tasks/sg_fargate_rails.rake
Original file line number Diff line number Diff line change
Expand Up @@ -3,67 +3,31 @@ namespace :sg_fargate_rails do

desc 'EventBridge Schedules'
task recreate_schedules: :environment do
require 'aws-sdk-scheduler'

ecs_task = SgFargateRails::CurrentEcsTask.new
security_group_ids = ecs_task.security_group_ids
Rails.logger.info "[INFO] security_group_ids: #{ecs_task.security_group_ids}"
Rails.logger.info "[INFO] subnet_ids: #{ecs_task.public_subnet_ids}"

subnet_ids = ecs_task.public_subnet_ids
Rails.logger.info "[INFO] subnet_ids: #{subnet_ids}"

region = ENV['AWS_REGION'] || 'ap-northeast-1'
timezone = ENV['TZ'] || 'Asia/Tokyo'
group_name = ecs_task.cfn_stack_name
Rails.logger.info "[EventBridgeSchedule] Clear all schedules in #{group_name}"
SgFargateRails::EventBridgeSchedule.delete_all!(group_name)

credentials = Aws::ECSCredentials.new(retries: 3)
client = Aws::Scheduler::Client.new(region: region, credentials: credentials)
Rails.logger.info "[EventBridgeSchedule] Clear all schedules in #{ecs_task.cfn_stack_name}"
client.list_schedules(group_name: ecs_task.cfn_stack_name, max_results: 100).schedules.each do |schedule|
client.delete_schedule(name: schedule.name, group_name: ecs_task.cfn_stack_name)
Rails.logger.info "[EventBridgeSchedule] Deleted #{ecs_task.cfn_stack_name}/#{schedule.name}"
end

Rails.logger.info "[EventBridgeSchedule] Register schedules in #{ecs_task.cfn_stack_name}"
role_arn = "arn:aws:iam::#{ecs_task.account_id}:role/#{ecs_task.cfn_stack_name}-eventbridge-scheduler-role"
schedules = YAML.load File.open(Rails.root.join('config', 'eventbridge_schedules.yml'))
schedules.each do |name, info|
params = {
name: name,
state: 'ENABLED',
flexible_time_window: { mode: 'OFF' },
group_name: ecs_task.cfn_stack_name,
schedule_expression: info["cron"],
schedule_expression_timezone: timezone,
target: {
arn: ecs_task.cluster_arn,
ecs_parameters: {
task_count: 1,
task_definition_arn: ecs_task.task_definition_arn,
launch_type: 'FARGATE',
network_configuration: {
awsvpc_configuration: {
assign_public_ip: 'ENABLED',
security_groups: security_group_ids,
subnets: subnet_ids,
},
},
},
input: {
"containerOverrides": [
{
"name": "rails",
"command": ["bundle", "exec"] + info["command"].split(" "),
}
]
}.to_json,
retry_policy: {
maximum_event_age_in_seconds: 120,
maximum_retry_attempts: 2,
Rails.logger.info "[EventBridgeSchedule] Register schedules in #{group_name}"
config_file = Rails.root.join('config/eventbridge_schedules.yml')
SgFargateRails::EventBridgeSchedule.parse(config_file).each do |schedule|
Rails.logger.info "[EventBridgeSchedule] Register schedule #{schedule.name} in #{group_name}"
# TODO: この辺で AWS の API Limit などのエラーが発生するとスケジュールが消えたままとなるので、エラーの内容に応じてリトライなどのエラー処理が必要
schedule.create_run_task(
group_name: group_name,
cluster_arn: ecs_task.cluster_arn,
task_definition_arn: ecs_task.task_definition_arn,
network_configuration: {
awsvpc_configuration: {
assign_public_ip: 'ENABLED',
security_groups: ecs_task.security_group_ids,
subnets: ecs_task.public_subnet_ids,
},
role_arn: role_arn,
},
}
client.create_schedule(params)
}
)
end
end
end

0 comments on commit f6e4169

Please sign in to comment.