ecs-easy-cluster 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6deb5e083c0a0a6c90456bb28a6538ecb009b87a
4
+ data.tar.gz: d14a41c0c460cef5c5cb1c466b36ff4352d27a34
5
+ SHA512:
6
+ metadata.gz: 332a2068fbfe7169e026d490182b22dbef5fc70c45211660054585f33abe92254dbe10cca8493761da0733b5fb3aa8cd90613317441acf8234592c0f82cb58ca
7
+ data.tar.gz: 4fbdfe7f61a815c8339b695a58fa19edb20f802160dd581695441587fd2e9a3c1d99258f28debe10a80b757ad1abf79be6e42687026cd7ada9e62691b65e4fae
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in ecs-easy-cluster.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2016 metheglin
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,72 @@
1
+ # Ecs::Easy::Cluster
2
+
3
+ ecs-easy-cluster is a easy clustering tool for AWS ECS.
4
+ This tool focuses on executing tiny scripts, jobs or batches without considering the running environment.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ gem 'ecs-easy-cluster'
11
+
12
+ And then execute:
13
+
14
+ $ bundle
15
+
16
+ Or install it yourself as:
17
+
18
+ $ gem install ecs-easy-cluster
19
+
20
+ ## Usage
21
+
22
+ ```
23
+ require "ecs-easy-cluster"
24
+
25
+ #
26
+ # Set basic info: credentials and region
27
+ #
28
+ configure = Ecs::Easy::Configure.new do |c|
29
+ c.profile = "your-aws-profile"
30
+ c.region = "your-aws-region"
31
+ end
32
+
33
+ #
34
+ # Define ec2 instance profile
35
+ #
36
+ instance = Ecs::Easy::Instance.new do |i|
37
+ i.type = "t2.nano"
38
+ i.keypair = "your-keypair"
39
+ i.azs = "ap-northeast-1a,ap-northeast-1c"
40
+ i.subnets = "subnet-00000000,subnet-11111111"
41
+ i.vpc = "vpc-00000000"
42
+ i.image_id = "ami-00000000"
43
+ i.security_group = "sg-00000000"
44
+ end
45
+
46
+ #
47
+ # Define the scale setting of your cluster
48
+ #
49
+ cluster = Ecs::Easy::Cluster::MemScale.new("cluster-name", configure) do |c|
50
+ c.max_instances = 2
51
+ c.min_instances = 1
52
+ c.instance = instance
53
+ end
54
+
55
+ #
56
+ # Make your task running
57
+ #
58
+ cluster.make_task_running!("your-task-definition-name")
59
+
60
+ #
61
+ # Shrink your scaled instances
62
+ # ------------
63
+ # !! CAUTION !!
64
+ # This command terminates redundant instances even if some tasks are running on them.
65
+ #
66
+ cluster.shrink!
67
+ ```
68
+
69
+ ## Memo
70
+
71
+ ecs/easy/cluster/cloudformation_template.json.erb is refered from the link below.
72
+ https://github.com/aws/amazon-ecs-cli/blob/master/ecs-cli/modules/aws/clients/cloudformation/template.go
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'ecs/easy/cluster/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "ecs-easy-cluster"
8
+ spec.version = Ecs::Easy::Cluster::VERSION
9
+ spec.authors = ["metheglin"]
10
+ spec.email = ["pigmybank@gmail.com"]
11
+ spec.summary = %q{easy-to-use AWS ECS Cluster.}
12
+ spec.description = %q{easy-to-use AWS ECS Cluster.}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_runtime_dependency "aws-sdk", "~> 2.4"
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.6"
24
+ spec.add_development_dependency "rake"
25
+ end
@@ -0,0 +1,38 @@
1
+ require "ecs/easy/cluster/version"
2
+
3
+ require "ostruct"
4
+ require "aws-sdk"
5
+
6
+ require "ecs/easy/configure"
7
+ require "ecs/easy/instance"
8
+
9
+ # configure = Ecs::Easy::Configure.new do |c|
10
+ # c.profile = ""
11
+ # c.region = ""
12
+ # end
13
+ # instance = Ecs::Easy::Instance.new do |i|
14
+ # i.instance_type = "t2.nano"
15
+ # i.keypair = "your-keypair"
16
+ # i.azs = "ap-northeast-1a,ap-northeast-1c"
17
+ # i.subnets = "subnet-00000000,subnet-11111111"
18
+ # i.vpc = "vpc-00000000"
19
+ # i.image_id = "ami-00000000"
20
+ # i.security_group = "sg-00000000"
21
+ # end
22
+ # cluster = Ecs::Easy::Cluster::MemScale.new("cluster-name", configure) do |c|
23
+ # c.max_instances = 2
24
+ # c.min_instances = 1
25
+ # c.instance = instance
26
+ # end
27
+ # cluster.make_task_running!("your-task-definition-name")
28
+
29
+ module Ecs
30
+ module Easy
31
+ module Cluster
32
+
33
+ autoload :Base, File.expand_path("../cluster/base.rb", __FILE__)
34
+ autoload :MemScale, File.expand_path("../cluster/mem_scale.rb", __FILE__)
35
+
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,168 @@
1
+ module Ecs::Easy::Cluster
2
+ class Base
3
+
4
+ TEMPLATE_PATH = File.expand_path("../config/cloudFormation_template.json", __FILE__)
5
+ TEMPLATE_BODY = File.read( TEMPLATE_PATH )
6
+
7
+ attr_reader :name, :configure, :ecs_client, :stack_name, :cfn_client
8
+ attr_accessor :instance, :min_instances, :max_instances
9
+
10
+ def initialize( name, configure )
11
+ @name = name
12
+ @configure = configure
13
+ @ecs_client = Aws::ECS::Client.new(
14
+ region: configure.region,
15
+ credentials: configure.credentials
16
+ )
17
+ @stack_name = configure.cfn_stack_name_prefix + name
18
+ @cfn_client = Aws::CloudFormation::Client.new(
19
+ region: configure.region,
20
+ credentials: configure.credentials
21
+ )
22
+
23
+ # default
24
+ @min_instances = 1
25
+ @max_instances = 1
26
+
27
+ yield( self ) if block_given?
28
+ end
29
+
30
+ # Check if the cluster exists
31
+ # A cluster existence should be decided as cloudformation stack existence
32
+ def exists?
33
+ res = cfn_client.describe_stacks( stack_name: stack_name )
34
+ return res.stacks.length > 0
35
+ rescue => e
36
+ return false
37
+ end
38
+
39
+ # Check if all the container instances on the cluster are ready
40
+ def ready?
41
+ return false unless exists?
42
+ instance_arns = ecs_client.list_container_instances(cluster: name).container_instance_arns
43
+ return false if instance_arns.nil? or instance_arns.empty?
44
+ res = ecs_client.describe_container_instances(
45
+ cluster: name,
46
+ container_instances: instance_arns
47
+ )
48
+ # unless res.respond_to?("container_instances") or
49
+ # res.container_instances.nil?
50
+ # return false
51
+ # end
52
+ res.container_instances.all? {|i| i.status == "ACTIVE" }
53
+ end
54
+
55
+ # Check if the task exists on the cluster
56
+ def task_exists?( task )
57
+ task_arns = ecs_client.list_tasks(cluster: name).task_arns
58
+ task_arns.include?( task )
59
+ end
60
+
61
+ def num_instances
62
+ ecs_client.list_container_instances(cluster: name).container_instance_arns.length
63
+ end
64
+
65
+ # Wait until all the container instances ready
66
+ def wait_until_ready( retry_count=30, sleep_sec=2 )
67
+ retry_count.times do
68
+ break if ready?
69
+ sleep sleep_sec
70
+ end
71
+ end
72
+
73
+ def wait_until_task_running( arn )
74
+ raise "Task Not Found. Possibly the task is already stopped." unless task_exists?( arn )
75
+ ecs_client.wait_until(:tasks_running, tasks: [arn])
76
+ end
77
+
78
+ def run_task!( task_definition, overrides={} )
79
+ res = ecs_client.run_task(
80
+ cluster: name,
81
+ task_definition: task_definition,
82
+ overrides: overrides
83
+ )
84
+ end
85
+
86
+ def up!
87
+ unless exists?
88
+ ecs_client.create_cluster( cluster_name: name )
89
+
90
+ params = instance.cfn_parameters( name, "AsgMaxSize" => min_instances.to_s)
91
+ cfn_client.create_stack(
92
+ stack_name: stack_name,
93
+ template_body: TEMPLATE_BODY,
94
+ parameters: params,
95
+ capabilities: ["CAPABILITY_IAM"],
96
+ on_failure: "DELETE",
97
+ )
98
+ cfn_client.wait_until(
99
+ :stack_create_complete,
100
+ stack_name: stack_name
101
+ )
102
+ end
103
+
104
+ return exists?
105
+ end
106
+
107
+ def destroy!
108
+ cfn_client.delete_stack(stack_name: stack_name)
109
+ cfn_client.wait_until(
110
+ :stack_delete_complete,
111
+ stack_name: stack_name
112
+ )
113
+ end
114
+
115
+ def scale!
116
+ size = (num_instances+1 <= max_instances) ? num_instances+1 : max_instances
117
+
118
+ params = instance.cfn_parameters( name, "AsgMaxSize" => size.to_s)
119
+ cfn_client.update_stack(
120
+ stack_name: stack_name,
121
+ template_body: TEMPLATE_BODY,
122
+ parameters: params,
123
+ capabilities: ["CAPABILITY_IAM"],
124
+ )
125
+ cfn_client.wait_until(
126
+ :stack_update_complete,
127
+ stack_name: stack_name
128
+ )
129
+
130
+ # Check the scale completion every 2 seconds until max 30 times
131
+ 30.times do
132
+ return true if num_instances == size
133
+ sleep 2
134
+ end
135
+
136
+ return false
137
+ rescue Aws::Waiters::Errors::WaiterFailed => e
138
+ puts e
139
+ end
140
+
141
+ # Deregister some container instances if they are idling
142
+ # TODO: Support idling check of container instances
143
+ def shrink!
144
+ params = instance.cfn_parameters( name, "AsgMaxSize" => min_instances.to_s)
145
+ cfn_client.update_stack(
146
+ stack_name: stack_name,
147
+ template_body: TEMPLATE_BODY,
148
+ parameters: params,
149
+ capabilities: ["CAPABILITY_IAM"],
150
+ )
151
+ cfn_client.wait_until(
152
+ :stack_update_complete,
153
+ stack_name: stack_name
154
+ )
155
+ rescue Aws::Waiters::Errors::WaiterFailed => e
156
+ puts e
157
+ end
158
+
159
+ private
160
+ def fail_reason( failures )
161
+ reasons = failures.map {|f| f.reason }
162
+ if reasons.include?( "RESOURCE:MEMORY" )
163
+ return "RESOURCE:MEMORY"
164
+ end
165
+ end
166
+
167
+ end
168
+ end
@@ -0,0 +1,517 @@
1
+ {
2
+ "AWSTemplateFormatVersion": "2010-09-09",
3
+ "Description": "AWS CloudFormation template to create resources required to run tasks on an ECS cluster.",
4
+ "Mappings": {
5
+ "VpcCidrs": {
6
+ "vpc": {"cidr" : "10.0.0.0/16"},
7
+ "pubsubnet1": {"cidr" : "10.0.0.0/24"},
8
+ "pubsubnet2": {"cidr" :"10.0.1.0/24"}
9
+ }
10
+ },
11
+ "Parameters": {
12
+ "EcsAmiId": {
13
+ "Type": "String",
14
+ "Description": "ECS EC2 AMI id",
15
+ "Default": ""
16
+ },
17
+ "EcsInstanceType": {
18
+ "Type": "String",
19
+ "Description": "ECS EC2 instance type",
20
+ "Default": "t2.micro",
21
+ "AllowedValues": [
22
+ "t2.nano",
23
+ "t2.micro",
24
+ "t2.small",
25
+ "t2.medium",
26
+ "t2.large",
27
+ "m3.medium",
28
+ "m3.large",
29
+ "m3.xlarge",
30
+ "m3.2xlarge",
31
+ "m4.large",
32
+ "m4.xlarge",
33
+ "m4.2xlarge",
34
+ "m4.4xlarge",
35
+ "m4.10xlarge",
36
+ "c4.large",
37
+ "c4.xlarge",
38
+ "c4.2xlarge",
39
+ "c4.4xlarge",
40
+ "c4.8xlarge",
41
+ "c3.large",
42
+ "c3.xlarge",
43
+ "c3.2xlarge",
44
+ "c3.4xlarge",
45
+ "c3.8xlarge",
46
+ "r3.large",
47
+ "r3.xlarge",
48
+ "r3.2xlarge",
49
+ "r3.4xlarge",
50
+ "r3.8xlarge",
51
+ "i2.xlarge",
52
+ "i2.2xlarge",
53
+ "i2.4xlarge",
54
+ "i2.8xlarge",
55
+ "g2.2xlarge",
56
+ "g2.8xlarge",
57
+ "d2.xlarge",
58
+ "d2.2xlarge",
59
+ "d2.4xlarge",
60
+ "d2.8xlarge"
61
+ ],
62
+ "ConstraintDescription": "must be a valid EC2 instance type."
63
+ },
64
+ "KeyName": {
65
+ "Type": "AWS::EC2::KeyPair::KeyName",
66
+ "Description": "Optional - Name of an existing EC2 KeyPair to enable SSH access to the ECS instances",
67
+ "Default": ""
68
+ },
69
+ "VpcId": {
70
+ "Type": "String",
71
+ "Description": "Optional - VPC Id of existing VPC. Leave blank to have a new VPC created",
72
+ "Default": "",
73
+ "AllowedPattern": "^(?:vpc-[0-9a-f]{8}|)$",
74
+ "ConstraintDescription": "VPC Id must begin with 'vpc-' or leave blank to have a new VPC created"
75
+ },
76
+ "SubnetIds": {
77
+ "Type": "CommaDelimitedList",
78
+ "Description": "Optional - Comma separated list of two (2) existing VPC Subnet Ids where ECS instances will run. Required if setting VpcId.",
79
+ "Default": ""
80
+ },
81
+ "AsgMaxSize": {
82
+ "Type": "Number",
83
+ "Description": "Maximum size and initial Desired Capacity of ECS Auto Scaling Group",
84
+ "Default": "1"
85
+ },
86
+ "SecurityGroup": {
87
+ "Type": "String",
88
+ "Description": "Optional - Existing security group to associate the container instances. Creates one by default.",
89
+ "Default": ""
90
+ },
91
+ "SourceCidr": {
92
+ "Type": "String",
93
+ "Description": "Optional - CIDR/IP range for EcsPort - defaults to 0.0.0.0/0",
94
+ "Default": "0.0.0.0/0"
95
+ },
96
+ "EcsPort" : {
97
+ "Type" : "String",
98
+ "Description" : "Optional - Security Group port to open on ECS instances - defaults to port 80",
99
+ "Default" : "80"
100
+ },
101
+ "VpcAvailabilityZones": {
102
+ "Type": "CommaDelimitedList",
103
+ "Description": "Optional - Comma-delimited list of VPC availability zones in which to create subnets. Required if setting VpcId.",
104
+ "Default": ""
105
+ },
106
+ "EcsCluster" : {
107
+ "Type" : "String",
108
+ "Description" : "ECS Cluster Name",
109
+ "Default" : "default"
110
+ }
111
+ },
112
+ "Conditions": {
113
+ "CreateVpcResources": {
114
+ "Fn::Equals": [
115
+ {
116
+ "Ref": "VpcId"
117
+ },
118
+ ""
119
+ ]
120
+ },
121
+ "CreateSecurityGroup": {
122
+ "Fn::Equals": [
123
+ {
124
+ "Ref": "SecurityGroup"
125
+ },
126
+ ""
127
+ ]
128
+ },
129
+ "CreateEC2LCWithKeyPair": {
130
+ "Fn::Not": [
131
+ {
132
+ "Fn::Equals": [
133
+ {
134
+ "Ref": "KeyName"
135
+ },
136
+ ""
137
+ ]
138
+ }
139
+ ]
140
+ },
141
+ "CreateEC2LCWithoutKeyPair": {
142
+ "Fn::Equals": [
143
+ {
144
+ "Ref": "KeyName"
145
+ },
146
+ ""
147
+ ]
148
+ },
149
+ "UseSpecifiedVpcAvailabilityZones": {
150
+ "Fn::Not": [
151
+ {
152
+ "Fn::Equals": [
153
+ {
154
+ "Fn::Join": [
155
+ "",
156
+ {
157
+ "Ref": "VpcAvailabilityZones"
158
+ }
159
+ ]
160
+ },
161
+ ""
162
+ ]
163
+ }
164
+ ]
165
+ }
166
+ },
167
+ "Resources": {
168
+ "Vpc": {
169
+ "Condition": "CreateVpcResources",
170
+ "Type": "AWS::EC2::VPC",
171
+ "Properties": {
172
+ "CidrBlock": {
173
+ "Fn::FindInMap": ["VpcCidrs", "vpc", "cidr"]
174
+ }
175
+ }
176
+ },
177
+ "PubSubnetAz1": {
178
+ "Condition": "CreateVpcResources",
179
+ "Type": "AWS::EC2::Subnet",
180
+ "Properties": {
181
+ "VpcId": {
182
+ "Ref": "Vpc"
183
+ },
184
+ "CidrBlock": {
185
+ "Fn::FindInMap": ["VpcCidrs", "pubsubnet1", "cidr"]
186
+ },
187
+ "AvailabilityZone": {
188
+ "Fn::If": [
189
+ "UseSpecifiedVpcAvailabilityZones",
190
+ {
191
+ "Fn::Select": [
192
+ "0",
193
+ {
194
+ "Ref": "VpcAvailabilityZones"
195
+ }
196
+ ]
197
+ },
198
+ {
199
+ "Fn::Select": [
200
+ "0",
201
+ {
202
+ "Fn::GetAZs": {
203
+ "Ref": "AWS::Region"
204
+ }
205
+ }
206
+ ]
207
+ }
208
+ ]
209
+ }
210
+ }
211
+ },
212
+ "PubSubnetAz2": {
213
+ "Condition": "CreateVpcResources",
214
+ "Type": "AWS::EC2::Subnet",
215
+ "Properties": {
216
+ "VpcId": {
217
+ "Ref": "Vpc"
218
+ },
219
+ "CidrBlock": {
220
+ "Fn::FindInMap": ["VpcCidrs", "pubsubnet2", "cidr"]
221
+ },
222
+ "AvailabilityZone": {
223
+ "Fn::If": [
224
+ "UseSpecifiedVpcAvailabilityZones",
225
+ {
226
+ "Fn::Select": [
227
+ "1",
228
+ {
229
+ "Ref": "VpcAvailabilityZones"
230
+ }
231
+ ]
232
+ },
233
+ {
234
+ "Fn::Select": [
235
+ "1",
236
+ {
237
+ "Fn::GetAZs": {
238
+ "Ref": "AWS::Region"
239
+ }
240
+ }
241
+ ]
242
+ }
243
+ ]
244
+ }
245
+ }
246
+ },
247
+ "InternetGateway": {
248
+ "Condition": "CreateVpcResources",
249
+ "Type": "AWS::EC2::InternetGateway"
250
+ },
251
+ "AttachGateway": {
252
+ "Condition": "CreateVpcResources",
253
+ "Type": "AWS::EC2::VPCGatewayAttachment",
254
+ "Properties": {
255
+ "VpcId": {
256
+ "Ref": "Vpc"
257
+ },
258
+ "InternetGatewayId": {
259
+ "Ref": "InternetGateway"
260
+ }
261
+ }
262
+ },
263
+ "RouteViaIgw": {
264
+ "Condition": "CreateVpcResources",
265
+ "Type": "AWS::EC2::RouteTable",
266
+ "Properties": {
267
+ "VpcId": {
268
+ "Ref": "Vpc"
269
+ }
270
+ }
271
+ },
272
+ "PublicRouteViaIgw": {
273
+ "Condition": "CreateVpcResources",
274
+ "DependsOn": "AttachGateway",
275
+ "Type": "AWS::EC2::Route",
276
+ "Properties": {
277
+ "RouteTableId": {
278
+ "Ref": "RouteViaIgw"
279
+ },
280
+ "DestinationCidrBlock": "0.0.0.0/0",
281
+ "GatewayId": {
282
+ "Ref": "InternetGateway"
283
+ }
284
+ }
285
+ },
286
+ "PubSubnet1RouteTableAssociation": {
287
+ "Condition": "CreateVpcResources",
288
+ "Type": "AWS::EC2::SubnetRouteTableAssociation",
289
+ "Properties": {
290
+ "SubnetId": {
291
+ "Ref": "PubSubnetAz1"
292
+ },
293
+ "RouteTableId": {
294
+ "Ref": "RouteViaIgw"
295
+ }
296
+ }
297
+ },
298
+ "PubSubnet2RouteTableAssociation": {
299
+ "Condition": "CreateVpcResources",
300
+ "Type": "AWS::EC2::SubnetRouteTableAssociation",
301
+ "Properties": {
302
+ "SubnetId": {
303
+ "Ref": "PubSubnetAz2"
304
+ },
305
+ "RouteTableId": {
306
+ "Ref": "RouteViaIgw"
307
+ }
308
+ }
309
+ },
310
+ "EcsSecurityGroup": {
311
+ "Condition": "CreateSecurityGroup",
312
+ "Type": "AWS::EC2::SecurityGroup",
313
+ "Properties": {
314
+ "GroupDescription": "ECS Allowed Ports",
315
+ "VpcId": {
316
+ "Fn::If": [
317
+ "CreateVpcResources",
318
+ {
319
+ "Ref": "Vpc"
320
+ },
321
+ {
322
+ "Ref": "VpcId"
323
+ }
324
+ ]
325
+ },
326
+ "SecurityGroupIngress" : [ {
327
+ "IpProtocol" : "tcp",
328
+ "FromPort" : { "Ref" : "EcsPort" },
329
+ "ToPort" : { "Ref" : "EcsPort" },
330
+ "CidrIp" : { "Ref" : "SourceCidr" }
331
+ } ]
332
+ }
333
+ },
334
+ "EcsInstancePolicy": {
335
+ "Type": "AWS::IAM::Role",
336
+ "Properties": {
337
+ "AssumeRolePolicyDocument": {
338
+ "Version": "2012-10-17",
339
+ "Statement": [
340
+ {
341
+ "Effect": "Allow",
342
+ "Principal": {
343
+ "Service": [
344
+ "ec2.amazonaws.com"
345
+ ]
346
+ },
347
+ "Action": [
348
+ "sts:AssumeRole"
349
+ ]
350
+ }
351
+ ]
352
+ },
353
+ "Path": "/",
354
+ "ManagedPolicyArns": [
355
+ "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role"
356
+ ]
357
+ }
358
+ },
359
+ "EcsInstanceProfile": {
360
+ "Type": "AWS::IAM::InstanceProfile",
361
+ "Properties": {
362
+ "Path": "/",
363
+ "Roles": [
364
+ {
365
+ "Ref": "EcsInstancePolicy"
366
+ }
367
+ ]
368
+ }
369
+ },
370
+ "EcsInstanceLc": {
371
+ "Condition": "CreateEC2LCWithKeyPair",
372
+ "Type": "AWS::AutoScaling::LaunchConfiguration",
373
+ "Properties": {
374
+ "ImageId": { "Ref" : "EcsAmiId" },
375
+ "InstanceType": {
376
+ "Ref": "EcsInstanceType"
377
+ },
378
+ "AssociatePublicIpAddress": true,
379
+ "IamInstanceProfile": {
380
+ "Ref": "EcsInstanceProfile"
381
+ },
382
+ "KeyName": {
383
+ "Ref": "KeyName"
384
+ },
385
+ "SecurityGroups": {
386
+ "Fn::If": [
387
+ "CreateSecurityGroup",
388
+ [ {
389
+ "Ref": "EcsSecurityGroup"
390
+ } ],
391
+ [ {
392
+ "Ref": "SecurityGroup"
393
+ } ]
394
+ ]
395
+ },
396
+ "UserData": {
397
+ "Fn::Base64": {
398
+ "Fn::Join": [
399
+ "",
400
+ [
401
+ "#!/bin/bash\n",
402
+ "echo ECS_CLUSTER=",
403
+ {
404
+ "Ref": "EcsCluster"
405
+ },
406
+ " >> /etc/ecs/ecs.config\n"
407
+ ]
408
+ ]
409
+ }
410
+ }
411
+ }
412
+ },
413
+ "EcsInstanceLcWithoutKeyPair": {
414
+ "Condition": "CreateEC2LCWithoutKeyPair",
415
+ "Type": "AWS::AutoScaling::LaunchConfiguration",
416
+ "Properties": {
417
+ "ImageId": { "Ref" : "EcsAmiId" },
418
+ "InstanceType": {
419
+ "Ref": "EcsInstanceType"
420
+ },
421
+ "AssociatePublicIpAddress": true,
422
+ "IamInstanceProfile": {
423
+ "Ref": "EcsInstanceProfile"
424
+ },
425
+ "SecurityGroups": {
426
+ "Fn::If": [
427
+ "CreateSecurityGroup",
428
+ [ {
429
+ "Ref": "EcsSecurityGroup"
430
+ } ],
431
+ [ {
432
+ "Ref": "SecurityGroup"
433
+ } ]
434
+ ]
435
+ },
436
+ "UserData": {
437
+ "Fn::Base64": {
438
+ "Fn::Join": [
439
+ "",
440
+ [
441
+ "#!/bin/bash\n",
442
+ "echo ECS_CLUSTER=",
443
+ {
444
+ "Ref": "EcsCluster"
445
+ },
446
+ " >> /etc/ecs/ecs.config\n"
447
+ ]
448
+ ]
449
+ }
450
+ }
451
+ }
452
+ },
453
+ "EcsInstanceAsg": {
454
+ "Type": "AWS::AutoScaling::AutoScalingGroup",
455
+ "Properties": {
456
+ "VPCZoneIdentifier": {
457
+ "Fn::If": [
458
+ "CreateVpcResources",
459
+ [
460
+ {
461
+ "Fn::Join": [
462
+ ",",
463
+ [
464
+ {
465
+ "Ref": "PubSubnetAz1"
466
+ },
467
+ {
468
+ "Ref": "PubSubnetAz2"
469
+ }
470
+ ]
471
+ ]
472
+ }
473
+ ],
474
+ {
475
+ "Ref": "SubnetIds"
476
+ }
477
+ ]
478
+ },
479
+ "LaunchConfigurationName": {
480
+ "Fn::If": [
481
+ "CreateEC2LCWithKeyPair",
482
+ {
483
+ "Ref": "EcsInstanceLc"
484
+ },
485
+ {
486
+ "Ref": "EcsInstanceLcWithoutKeyPair"
487
+ }
488
+ ]
489
+ },
490
+ "MinSize": "1",
491
+ "MaxSize": {
492
+ "Ref": "AsgMaxSize"
493
+ },
494
+ "DesiredCapacity": {
495
+ "Ref": "AsgMaxSize"
496
+ },
497
+ "Tags": [
498
+ {
499
+ "Key": "Name",
500
+ "Value": {
501
+ "Fn::Join": [
502
+ "",
503
+ [
504
+ "ECS Instance - ",
505
+ {
506
+ "Ref": "AWS::StackName"
507
+ }
508
+ ]
509
+ ]
510
+ },
511
+ "PropagateAtLaunch": "true"
512
+ }
513
+ ]
514
+ }
515
+ }
516
+ }
517
+ }
@@ -0,0 +1,37 @@
1
+ {
2
+ "t2.nano": { "mem": 0.5 },
3
+ "t2.micro": { "mem": 1 },
4
+ "t2.small": { "mem": 2 },
5
+ "t2.medium": { "mem": 4 },
6
+ "t2.large": { "mem": 8 },
7
+
8
+ "m4.large": { "mem": 8 },
9
+ "m4.xlarge": { "mem": 16 },
10
+ "m4.2xlarge": { "mem": 32 },
11
+ "m4.4xlarge": { "mem": 64 },
12
+ "m4.10xlarge": { "mem": 160 },
13
+
14
+ "m3.medium": { "mem": 3.75 },
15
+ "m3.large": { "mem": 7.5 },
16
+ "m3.xlarge": { "mem": 15 },
17
+ "m3.2xlarge": { "mem": 30 },
18
+
19
+ "c4.large": { "mem": 3.75 },
20
+ "c4.xlarge": { "mem": 7.5 },
21
+ "c4.2xlarge": { "mem": 15 },
22
+ "c4.4xlarge": { "mem": 30 },
23
+ "c4.8xlarge": { "mem": 60 },
24
+
25
+ "c3.large": { "mem": 3.75 },
26
+ "c3.xlarge": { "mem": 7.5 },
27
+ "c3.2xlarge": { "mem": 15 },
28
+ "c3.4xlarge": { "mem": 30 },
29
+ "c3.8xlarge": { "mem": 60 },
30
+
31
+ "r3.large": { "mem": 15.25 },
32
+ "r3.xlarge": { "mem": 30.5 },
33
+ "r3.2xlarge": { "mem": 61 },
34
+ "r3.4xlarge": { "mem": 122 },
35
+ "r3.8xlarge": { "mem": 244 }
36
+
37
+ }
@@ -0,0 +1,61 @@
1
+ module Ecs::Easy::Cluster
2
+ class MemScale < Base
3
+
4
+ EC2_PROFILE_PATH = File.expand_path("../config/ec2_profile.json", __FILE__)
5
+ INSTANCE_TYPES = JSON.parse(File.read( EC2_PROFILE_PATH ))
6
+
7
+ def make_task_running!( task_definition, overrides={} )
8
+ unless exists?
9
+ unless up!
10
+ raise "Failed to create the new cluster. You should check the CloudFormation events."
11
+ end
12
+ end
13
+
14
+ res = nil
15
+ 3.times do
16
+ wait_until_ready
17
+ res = run_task!( task_definition, overrides )
18
+
19
+ if res.failures.empty?
20
+ break
21
+ else # Failure because some reasons
22
+ puts res.failures
23
+ case fail_reason(res.failures)
24
+ when "RESOURCE:MEMORY"
25
+ puts "No enough memory on current container instances to execute this task. Add another container instance automatically."
26
+
27
+ if num_instances >= max_instances
28
+ raise "Could\'t scale more instances because it reaches maximum instances. You should upgrade the maximum number of instance to execute multiple tasks at the same time."
29
+ end
30
+ unless acceptable_task?( task_definition )
31
+ raise "Could\'t accept this task because of the lack of memory. You should upgrade ec2 instance type."
32
+ end
33
+
34
+ scale!
35
+ else
36
+ raise "Unknown reason: #{res.failures}"
37
+ end
38
+ end
39
+ end
40
+
41
+ res
42
+ end
43
+
44
+ def acceptable_task?( task_definition )
45
+ # It preserves 128MB for ecs-agent
46
+ required_memory( task_definition ) <= current_instance_memory - (128*1024*1024)
47
+ end
48
+
49
+ private
50
+ def required_memory( task_definition )
51
+ res = ecs_client.describe_task_definition( task_definition: task_definition )
52
+ container_mems = res.task_definition.container_definitions.map(&:memory)
53
+ total_mem_mb = container_mems.inject(0){|sum,n| sum + n}
54
+ total_mem_mb * 1024 * 1024 # byte
55
+ end
56
+
57
+ def current_instance_memory
58
+ INSTANCE_TYPES[instance.type]["mem"] * 1024 * 1024 * 1024 # byte
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,7 @@
1
+ module Ecs
2
+ module Easy
3
+ module Cluster
4
+ VERSION = "0.0.1"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,33 @@
1
+ module Ecs
2
+ module Easy
3
+ class Configure
4
+
5
+ attr_accessor :profile,
6
+ :access_key,
7
+ :secret_key,
8
+ :region,
9
+ :compose_project_name_prefix,
10
+ :compose_service_name_prefix,
11
+ :cfn_stack_name_prefix
12
+
13
+ def initialize **params
14
+ @compose_project_name_prefix = ""
15
+ @compose_service_name_prefix = "ecscompose-service-"
16
+ @cfn_stack_name_prefix = "amazon-ecs-cli-setup-"
17
+
18
+ params.each do |k,v|
19
+ self.send("#{k}=", v) if self.methods.include?(k)
20
+ end
21
+ yield( self ) if block_given?
22
+ end
23
+
24
+ def credentials
25
+ @credentials ||= profile ?
26
+ Aws::SharedCredentials.new( profile_name: profile ) :
27
+ Aws::Credentials.new( access_key, secret_key )
28
+ @credentials
29
+ end
30
+
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,63 @@
1
+ module Ecs
2
+ module Easy
3
+ class Instance
4
+
5
+ attr_accessor :type,
6
+ :keypair,
7
+ :azs, # availability zones
8
+ :subnets,
9
+ :vpc,
10
+ :image_id,
11
+ :security_group
12
+
13
+ def initialize **params
14
+ params.each do |k,v|
15
+ self.send("#{k}=", v) if self.methods.include?(k)
16
+ end
17
+ yield( self ) if block_given?
18
+ end
19
+
20
+ # Generate the parameters for cloudformation
21
+ def cfn_parameters( cluster_name, params={} )
22
+ base_params = [
23
+ {
24
+ parameter_key: "EcsAmiId",
25
+ parameter_value: image_id,
26
+ },
27
+ {
28
+ parameter_key: "EcsInstanceType",
29
+ parameter_value: type,
30
+ },
31
+ {
32
+ parameter_key: "KeyName",
33
+ parameter_value: keypair,
34
+ },
35
+ {
36
+ parameter_key: "VpcId",
37
+ parameter_value: vpc,
38
+ },
39
+ {
40
+ parameter_key: "SubnetIds",
41
+ parameter_value: subnets,
42
+ },
43
+ {
44
+ parameter_key: "SecurityGroup",
45
+ parameter_value: security_group,
46
+ },
47
+ {
48
+ parameter_key: "EcsCluster",
49
+ parameter_value: cluster_name,
50
+ },
51
+ ]
52
+ params.each do |k,v|
53
+ base_params << ({
54
+ "parameter_key" => k,
55
+ "parameter_value" => v,
56
+ })
57
+ end
58
+ base_params
59
+ end
60
+
61
+ end
62
+ end
63
+ end
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ecs-easy-cluster
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - metheglin
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-01-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: aws-sdk
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.4'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.6'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.6'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: easy-to-use AWS ECS Cluster.
56
+ email:
57
+ - pigmybank@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - Gemfile
64
+ - LICENSE.txt
65
+ - README.md
66
+ - Rakefile
67
+ - ecs-easy-cluster.gemspec
68
+ - lib/ecs/easy/cluster.rb
69
+ - lib/ecs/easy/cluster/base.rb
70
+ - lib/ecs/easy/cluster/config/cloudformation_template.json
71
+ - lib/ecs/easy/cluster/config/ec2_profile.json
72
+ - lib/ecs/easy/cluster/mem_scale.rb
73
+ - lib/ecs/easy/cluster/version.rb
74
+ - lib/ecs/easy/configure.rb
75
+ - lib/ecs/easy/instance.rb
76
+ homepage: ''
77
+ licenses:
78
+ - MIT
79
+ metadata: {}
80
+ post_install_message:
81
+ rdoc_options: []
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ requirements: []
95
+ rubyforge_project:
96
+ rubygems_version: 2.5.1
97
+ signing_key:
98
+ specification_version: 4
99
+ summary: easy-to-use AWS ECS Cluster.
100
+ test_files: []