ecs-easy-cluster 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []