ECSD 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c069cbb5aaee9472442db19e06ac7985d348888fe4792e64f507b1e99d385505
4
+ data.tar.gz: 20ee8759eb119d57eda9f092b601218b0e4653bc5baf38db83fd4fd97f65926a
5
+ SHA512:
6
+ metadata.gz: c6983cfa3baaeb1218206de55ee79aa347ffb8066ad93c2c39e7ae343183413780fcef89c4e394eff1d2a20af940a896988965081cae4628293defe818c30791
7
+ data.tar.gz: c4a1d62a25eb836623cd27c824b5f17e094e90dcc1c4b279e55a7b35a8292242d1f417ca9d40785f43ef719b1269b1306a160c44c4d6b89d6fae40458a073ecd
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ # 0.9.0
2
+
3
+ See commit log.
data/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # ECSD
2
+
3
+ Simple service for generate ec2_sd config files (one file per cluster)
4
+ to allow ECS cluster to be discovered by prometheus monitoring service.
5
+
6
+ Warning! Tested only with EC2 cluster model.
7
+ Working with fargate cluster model is not guaranteed
8
+
9
+ ## Getting started
10
+
11
+ First create AWS IAM user with roles for describe and list.
12
+
13
+ Then add gem to your service:
14
+ ```ruby
15
+ gem 'ecsd'
16
+ ```
17
+ Or install directly:
18
+ ```
19
+ $ gem install ecsd
20
+ ```
21
+
22
+ To start discover ECS cluster define config:
23
+ ```ruby
24
+ require 'ecsd'
25
+
26
+ ECSD.config do |c|
27
+ c.clusters = %w[cluster_name]
28
+ c.region = 'region'
29
+ c.credentials = { AWS_ACCESS_KEY_ID: 'ID', AWS_SECRET_ACCESS_KEY: 'SECRET' }
30
+ c.options = {
31
+ export_path: '/dir/path/to/export/folder',
32
+ timeout: 101
33
+ }
34
+ c.logger = Logger.new($stdout)
35
+ end
36
+ ```
37
+
38
+ - `clusters` - array of cluster names to discover. Same name as ECS cluster
39
+ - `region` - AWS region name where ECS cluster deployed
40
+ - `credentials` - `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` from your IAM user
41
+ - `options`
42
+ - `export_path` - path to export config folder. Default is `#{service root}/export`
43
+ - `timeout` - time in seconds between cluster discovery cycles.
44
+ - `logger` - define your logger or default logger will be set
45
+
46
+ Then start discover:
47
+ ```ruby
48
+ ECSD.start
49
+ ```
50
+ By default ECSD continuously discovering defined clusters and save templates into
51
+ corresponded files.
52
+
53
+ Also it's possible to start only ony cycle discover:
54
+ ```ruby
55
+ ECSD.start(cycle: false)
56
+ ```
57
+
58
+ ## Contributing
59
+
60
+ [Fork the project](https://github.com/Ad1n/ECSD) and send pull
61
+ requests.
@@ -0,0 +1,59 @@
1
+ # @example
2
+ # ECSD.config do |c|
3
+ # c.clusters = %w[cluster0 cluster1 cluster2]
4
+ # c.region = "aws_region_name"
5
+ # c.credentials = { AWS_ACCESS_KEY_ID: "ID", AWS_SECRET_ACCESS_KEY: "SECRET" }
6
+ # c.options = {
7
+ # export_path: 'dir/path/to/export_config/folder',
8
+ # timeout: 101
9
+ # }
10
+ # c.logger = Logger.new($stdout)
11
+ # end
12
+ module ECSD
13
+ unless defined?(CoreConfig)
14
+ class CoreConfig < Struct.new(:credentials, :region, :clusters, :options, :logger)
15
+ # @param payload [Hash] { AWS_ACCESS_KEY_ID: 'XXX', AWS_SECRET_ACCESS_KEY: 'XXX' }
16
+ def credentials=(payload)
17
+ validate_credentials!(payload)
18
+ super(::Aws::Credentials.new(
19
+ payload[:AWS_ACCESS_KEY_ID],
20
+ payload[:AWS_SECRET_ACCESS_KEY]
21
+ ))
22
+ end
23
+
24
+ # @param payload [Hash] { export_path: 'path/to/folder', timeout: 0 }
25
+ def options=(payload)
26
+ payload[:export_path] ||= ECSD::DEFAULT_EXPORT_PATH
27
+ payload[:timeout] ||= 0
28
+ super(payload)
29
+ end
30
+
31
+ private
32
+
33
+ # Removing the secret credentials from the default inspect string.
34
+ # @api private
35
+ def inspect
36
+ "#<#{self.class.name} clusters=#{clusters.inspect} region=#{region.inspect} options=#{options.inspect} logger=#{logger.inspect}>"
37
+ end
38
+
39
+ def validate_credentials!(p)
40
+ raise Config::ValidationError, 'Please, provide AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY' if p.values.size != 2
41
+ raise Config::ValidationError, 'Invalid AWS credentials' unless p.values.all? String
42
+ end
43
+ end
44
+ end
45
+
46
+ def config
47
+ yield @config if block_given?
48
+
49
+ @config
50
+ end
51
+ module_function :config
52
+
53
+ def logger
54
+ @config.logger || Logger.new($stdout)
55
+ end
56
+ module_function :logger
57
+
58
+ @config ||= CoreConfig.new
59
+ end
@@ -0,0 +1,10 @@
1
+ module ECSD
2
+ module Constants
3
+ DEFAULT_METRICS_PATH = '/metrics'.freeze
4
+ DEFAULT_EXPORT_PATH = (File.expand_path('../..', __dir__) << '/export').freeze
5
+ LAUNCH_TYPE = 'EC2'.freeze
6
+ CONNECTIVITY = {
7
+ connected: 'CONNECTED'
8
+ }.freeze
9
+ end
10
+ end
data/lib/ecsd/core.rb ADDED
@@ -0,0 +1,60 @@
1
+ module ECSD
2
+ class Core
3
+ include Helpers::EC2
4
+ include Helpers::ECS
5
+ include Helpers::Common
6
+
7
+ attr_reader :ecs, :ec2, :cluster
8
+
9
+ def initialize(cluster_name)
10
+ @ecs = ::Aws::ECS::Client.new(
11
+ credentials: ECSD.config.credentials,
12
+ region: ECSD.config.region
13
+ )
14
+ @ec2 = ::Aws::EC2::Client.new(
15
+ credentials: ECSD.config.credentials,
16
+ region: ECSD.config.region
17
+ )
18
+ @cluster = cluster_name
19
+ end
20
+
21
+ def handle!
22
+ task_arns = task_arns(cluster)
23
+ tasks = connected_tasks(cluster, task_arns)
24
+ handle_tasks(tasks)
25
+ end
26
+
27
+ def handle_tasks(tasks)
28
+ ECSD.logger.info <<~TEXT
29
+
30
+ Handle cluster: [#{cluster}]
31
+ Started at: #{Time.now}
32
+ TEXT
33
+ tasks.map do |t|
34
+ instance_id = instance_id(cluster, t)
35
+ task_definition_name, task_revision = task_definition_data(t)
36
+ task_container = task_container(t)
37
+
38
+ TEMPLATE.call(
39
+ ip_addr: instance_ip_addr(instance_id),
40
+ dyn_port: dyn_port(task_container),
41
+ task_id: task_id(t),
42
+ task_definition_name: task_definition_name,
43
+ task_revision: task_revision,
44
+ cluster_name: cluster_name(t),
45
+ container_name: task_container.name,
46
+ instance_id: instance_id
47
+ )
48
+ end.then do |r|
49
+ write_to_file(cluster, r)
50
+
51
+ ECSD.logger.info <<~TEXT
52
+
53
+ Handled cluster: [#{cluster}]
54
+ Tasks in cluster: #{r.count}
55
+ Finished at: #{Time.now}
56
+ TEXT
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,7 @@
1
+ module ECSD
2
+ class BaseError < RuntimeError; end
3
+
4
+ class Config
5
+ class ValidationError < BaseError; end
6
+ end
7
+ end
@@ -0,0 +1,40 @@
1
+ module ECSD
2
+ module Helpers
3
+ module Common
4
+ def write_to_file(cluster, data, path = ECSD.config.options[:export_path] || DEFAULT_EXPORT_PATH)
5
+ File.open("#{path}/#{cluster.downcase}.yml", 'w') do |f|
6
+ f.write(data.join)
7
+ end
8
+ end
9
+
10
+ def task_id(task)
11
+ task.task_arn
12
+ .split('/')
13
+ .last
14
+ end
15
+
16
+ def task_definition_data(task)
17
+ task.task_definition_arn
18
+ .split('/')
19
+ .last
20
+ .split(':')
21
+ end
22
+
23
+ def cluster_name(task)
24
+ task.cluster_arn
25
+ .split('/')
26
+ .last
27
+ end
28
+
29
+ def task_container(task)
30
+ task.containers.find { |c| c.task_arn == task.task_arn }
31
+ end
32
+
33
+ def dyn_port(task_container)
34
+ task_container.network_bindings
35
+ .first
36
+ .host_port
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,17 @@
1
+ module ECSD
2
+ module Helpers
3
+ module EC2
4
+ # @param instance_id [String]
5
+ # @return [String] private ip
6
+ def instance_ip_addr(instance_id)
7
+ ec2.describe_instances(instance_ids: Array(instance_id))
8
+ .data
9
+ .reservations
10
+ .first
11
+ .instances
12
+ .first
13
+ .private_ip_address
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,38 @@
1
+ module ECSD
2
+ module Helpers
3
+ module ECS
4
+ # @param cluster [String] cluster name
5
+ # @return [Array<Array<String>>] batched task_arns by 100
6
+ def task_arns(cluster)
7
+ ecs.list_tasks(cluster: cluster).each.with_object([]) do |response, task_arns|
8
+ task_arns << response[:task_arns]
9
+ end
10
+ end
11
+
12
+ # @param cluster [String] cluster name
13
+ # @param task_arns [Array<Array<String>>] batched task_arns by 100
14
+ # @return [Array<Array<Aws::ECS::Types:Task>>] connected tasks
15
+ def connected_tasks(cluster, task_arns)
16
+ task_arns.each.with_object([]) do |pt_batch, connected_tasks|
17
+ connected_tasks << ecs.describe_tasks(cluster: cluster, tasks: pt_batch)
18
+ .data
19
+ .tasks
20
+ .select { |t| t.connectivity == ECSD::Constants::CONNECTIVITY[:connected] }
21
+ end.flatten
22
+ end
23
+
24
+ # @param cluster [String] cluster name
25
+ # @param task [Aws::ECS::Types::Task]
26
+ # @return [String]
27
+ def instance_id(cluster, task)
28
+ ecs.describe_container_instances(
29
+ cluster: cluster,
30
+ container_instances: Array(task.container_instance_arn)
31
+ ).data
32
+ .container_instances
33
+ .first
34
+ .ec2_instance_id
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,35 @@
1
+ module ECSD
2
+ # @info Start ECS clusters discovery
3
+ def start(cycle: :infinite)
4
+ case cycle
5
+ when :infinite
6
+ loop { invoke!; wait }
7
+ else
8
+ invoke!
9
+ end
10
+ end
11
+ module_function :start
12
+
13
+ private
14
+
15
+ # @info one discovery cycle invocation
16
+ def invoke!
17
+ config.clusters.each do |cluster|
18
+ core(cluster).handle!
19
+ end
20
+ end
21
+ module_function :invoke!
22
+
23
+ # @info Initialize core component
24
+ # @param cluster [String] cluster name
25
+ def core(cluster)
26
+ ECSD::Core.new(cluster)
27
+ end
28
+ module_function :core
29
+
30
+ # @info Time in seconds between discovery iterations
31
+ def wait
32
+ sleep ECSD.config.options[:timeout]
33
+ end
34
+ module_function :wait
35
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ECSD
4
+ TEMPLATE = lambda do |ip_addr:,
5
+ dyn_port:,
6
+ task_id:,
7
+ task_definition_name:,
8
+ task_revision:,
9
+ cluster_name:,
10
+ container_name:,
11
+ instance_id:,
12
+ metrics_path: DEFAULT_METRICS_PATH|
13
+ <<~TEXT
14
+ - targets:
15
+ - #{ip_addr}:#{dyn_port}
16
+ labels:
17
+ task_id: #{task_id}
18
+ task_definition_name: #{task_definition_name}
19
+ task_revision: #{task_revision}
20
+ cluster_name: #{cluster_name}
21
+ container_name: #{container_name}
22
+ instance_id: #{instance_id}
23
+ __metrics_path__: #{metrics_path}
24
+ TEXT
25
+ end
26
+ end
@@ -0,0 +1,3 @@
1
+ module ECSD
2
+ VERSION = '0.9.0'.freeze
3
+ end
data/lib/ecsd.rb ADDED
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aws-sdk-ecs'
4
+ require 'aws-sdk-ec2'
5
+
6
+ require_relative 'ecsd/version'
7
+ require_relative 'ecsd/errors'
8
+ require_relative 'ecsd/constants'
9
+ require_relative 'ecsd/template'
10
+ require_relative 'ecsd/config'
11
+ require_relative 'ecsd/helpers/ec2'
12
+ require_relative 'ecsd/helpers/ecs'
13
+ require_relative 'ecsd/helpers/common'
14
+ require_relative 'ecsd/runner'
15
+ require_relative 'ecsd/core'
16
+
17
+ #:nodoc
18
+ module ECSD
19
+ include Constants
20
+ end
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ECSD
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.0
5
+ platform: ruby
6
+ authors:
7
+ - Anton Shevtsov
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-02-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: aws-sdk-ec2
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.298'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.298'
27
+ - !ruby/object:Gem::Dependency
28
+ name: aws-sdk-ecs
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.95'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.95'
41
+ description: Generate ec2 yaml config files to allow ECS cluster to be discovered
42
+ by prometheus monitoring service
43
+ email: shevtsovav@bk.ru
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - CHANGELOG.md
49
+ - README.md
50
+ - lib/ecsd.rb
51
+ - lib/ecsd/config.rb
52
+ - lib/ecsd/constants.rb
53
+ - lib/ecsd/core.rb
54
+ - lib/ecsd/errors.rb
55
+ - lib/ecsd/helpers/common.rb
56
+ - lib/ecsd/helpers/ec2.rb
57
+ - lib/ecsd/helpers/ecs.rb
58
+ - lib/ecsd/runner.rb
59
+ - lib/ecsd/template.rb
60
+ - lib/ecsd/version.rb
61
+ homepage:
62
+ licenses:
63
+ - MIT
64
+ metadata: {}
65
+ post_install_message:
66
+ rdoc_options: []
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: 2.4.0
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ requirements: []
80
+ rubygems_version: 3.1.4
81
+ signing_key:
82
+ specification_version: 4
83
+ summary: ECS(EC2 model) discovery
84
+ test_files: []