rubycfn 0.4.10 → 0.5.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -1
- data/Gemfile.lock +1 -1
- data/README.md +43 -67
- data/bin/rubycfn +17 -73
- data/lib/cli_methods.rb +2 -2
- data/lib/rubycfn/version.rb +1 -1
- data/templates/.env +2 -0
- data/templates/.env.acceptance +1 -0
- data/templates/.env.dependencies.rspec +6 -0
- data/templates/.env.development +1 -0
- data/templates/.env.production +1 -0
- data/templates/.env.rspec +1 -0
- data/templates/.env.test +1 -0
- data/templates/{.gitignore.erb → .gitignore} +3 -0
- data/templates/{.rubocop.yml.erb → .rubocop.yml} +14 -1
- data/templates/{Gemfile.erb → Gemfile} +0 -1
- data/templates/README.md +57 -0
- data/templates/{Rakefile.erb → Rakefile} +15 -8
- data/templates/bootstrap/dependency_stack.rb +49 -0
- data/templates/config.yaml +65 -0
- data/templates/lib/aws_helper/aws_sdk.rb +30 -0
- data/templates/{compiler.rb.erb → lib/aws_helper/compiler.rb} +15 -9
- data/templates/{dependencies.rb.erb → lib/aws_helper/dependencies.rb} +5 -3
- data/templates/{deploy.rb.erb → lib/aws_helper/deploy.rb} +6 -4
- data/templates/lib/aws_helper/helpers.rb +3 -0
- data/templates/{main_aws_helper.rb.erb → lib/aws_helper/main.rb} +0 -0
- data/templates/{upload_stack.rb.erb → lib/aws_helper/upload_stack.rb} +15 -6
- data/templates/lib/core/applications.rb +479 -0
- data/templates/lib/core/assume_role.rb +40 -0
- data/templates/lib/core/classes.rb +25 -0
- data/templates/{core_compile.rb.erb → lib/core/compile.rb} +1 -0
- data/templates/lib/core/dependencies.rb +22 -0
- data/templates/{core_deploy.rb.erb → lib/core/deploy.rb} +20 -10
- data/templates/lib/core/git.rb +15 -0
- data/templates/lib/core/init.rb +173 -0
- data/templates/{core_upload.rb.erb → lib/core/upload.rb} +0 -0
- data/templates/{main.rb.erb → lib/main.rb} +8 -6
- data/templates/lib/shared_concerns/global_variables.rb +56 -0
- data/templates/{helper_methods.rb.erb → lib/shared_concerns/helper_functions.rb} +0 -0
- data/templates/lib/shared_concerns/helper_methods.rb +3 -0
- data/templates/{shared_methods.rb.erb → lib/shared_concerns/shared_methods.rb} +9 -0
- data/templates/lib/stacks/acm_stack/certificate_manager.rb +79 -0
- data/templates/{new_stack.rb.erb → lib/stacks/acm_stack/main.rb} +3 -4
- data/templates/lib/stacks/ecs_stack/ecs_cluster.rb +344 -0
- data/templates/lib/stacks/ecs_stack/lifecycle_hook.rb +188 -0
- data/templates/lib/stacks/ecs_stack/load_balancer.rb +68 -0
- data/templates/{ecs_stack.rb.erb → lib/stacks/ecs_stack/main.rb} +2 -1
- data/templates/{project_stack.rb.erb → lib/stacks/parent_stack/main.rb} +2 -2
- data/templates/lib/stacks/parent_stack/parent.rb +18 -0
- data/templates/lib/stacks/vpc_stack/infra_vpc.rb +193 -0
- data/templates/{vpc_stack.rb.erb → lib/stacks/vpc_stack/main.rb} +1 -2
- data/templates/{parent_stack_spec.rb.erb → spec/lib/parent_spec.rb} +2 -5
- data/templates/{spec_helper.rb.erb → spec/spec_helper.rb} +2 -2
- metadata +54 -44
- data/format.vim +0 -3
- data/templates/.env.erb +0 -4
- data/templates/.env.production.erb +0 -6
- data/templates/.env.rspec.erb +0 -6
- data/templates/.env.test.erb +0 -6
- data/templates/.gitlab-ci.yml.erb +0 -75
- data/templates/aws_sdk.rb.erb +0 -18
- data/templates/core_diff.rb.erb +0 -59
- data/templates/ecs_stack_concern.rb.erb +0 -20
- data/templates/global_variables.rb.erb +0 -16
- data/templates/helpers.rb.erb +0 -7
- data/templates/new_concern.rb.erb +0 -10
- data/templates/project_concern.rb.erb +0 -26
- data/templates/subnets.rb.erb +0 -18
- data/templates/vpc_concerns.rb.erb +0 -87
- data/templates/vpc_spec.rb.erb +0 -39
@@ -0,0 +1,344 @@
|
|
1
|
+
module EcsStack
|
2
|
+
module EcsCluster
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
included do
|
5
|
+
parameter :vpc,
|
6
|
+
description: "VPC ID"
|
7
|
+
parameter :subnets,
|
8
|
+
description: "Subnets for ECS"
|
9
|
+
parameter :ecs_ami,
|
10
|
+
description: "ECS-Optimized AMI ID",
|
11
|
+
type: "AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>",
|
12
|
+
default: "/aws/service/ecs/optimized-ami/amazon-linux/recommended/image_id"
|
13
|
+
|
14
|
+
variable :cluster_size,
|
15
|
+
default: "1",
|
16
|
+
value: infra_config["environments"][environment]["cluster_size"]
|
17
|
+
|
18
|
+
variable :cluster_instance_type,
|
19
|
+
default: "t2.micro",
|
20
|
+
value: infra_config["environments"][environment]["cluster_instance_type"]
|
21
|
+
|
22
|
+
resource :ecs_cluster,
|
23
|
+
type: "AWS::ECS::Cluster"
|
24
|
+
|
25
|
+
resource :ecs_auto_scaling_group,
|
26
|
+
type: "AWS::AutoScaling::AutoScalingGroup" do |r|
|
27
|
+
r.property(:vpc_zone_identifier) { :subnets.ref.fnsplit(",") }
|
28
|
+
r.property(:launch_configuration_name) { :ecs_launch_configuration.ref }
|
29
|
+
r.property(:min_size) { cluster_size.to_i }
|
30
|
+
r.property(:max_size) { cluster_size.to_i }
|
31
|
+
r.property(:desired_capacity) { cluster_size.to_i }
|
32
|
+
r.property(:tags) do
|
33
|
+
[
|
34
|
+
{
|
35
|
+
"Key": "Name",
|
36
|
+
"Value": "#{environment} ECS host",
|
37
|
+
"PropagateAtLaunch": true
|
38
|
+
}
|
39
|
+
]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
resource :ecs_launch_configuration,
|
44
|
+
metadata: {
|
45
|
+
"AWS::CloudFormation::Init": {
|
46
|
+
"config": {
|
47
|
+
"packages": {
|
48
|
+
"yum": {
|
49
|
+
"collectd": []
|
50
|
+
}
|
51
|
+
},
|
52
|
+
"commands": {
|
53
|
+
"01_add_instance_to_cluster": {
|
54
|
+
"command": "echo ECS_CLUSTER=${SfsEcsCluster} >> /etc/ecs/ecs.config".fnsub
|
55
|
+
},
|
56
|
+
"02_enable_cloudwatch_agent": {
|
57
|
+
"command": "/opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -c ssm:${EcsCloudWatchParameter} -s".fnsub
|
58
|
+
}
|
59
|
+
},
|
60
|
+
"files": {
|
61
|
+
"/etc/cfn/cfn-hup.conf": {
|
62
|
+
"mode": 256,
|
63
|
+
"owner": "root",
|
64
|
+
"group": "root",
|
65
|
+
"content": "[main]\nstack=${AWS::StackId}\nregion=${AWS::Region}\n".fnsub
|
66
|
+
},
|
67
|
+
"/etc/cfn/hooks.d/cfn-auto-reloader.conf": {
|
68
|
+
"content": "[cfn-auto-reloader-hook]\ntriggers=post.update\npath=Resources.EcsLaunchConfiguration.Metadata.AWS::CloudFormation::Init\naction=/opt/aws/bin/cfn-init -v --region ${AWS::Region} --stack ${AWS::StackName} --resource EcsLaunchConfiguration\n".fnsub
|
69
|
+
}
|
70
|
+
},
|
71
|
+
"services": {
|
72
|
+
"sysvinit": {
|
73
|
+
"cfn-hup": {
|
74
|
+
"enabled": true,
|
75
|
+
"ensureRunning": true,
|
76
|
+
"files": [
|
77
|
+
"/etc/cfn/cfn-hup.conf",
|
78
|
+
"/etc/cfn/hooks.d/cfn-auto-reloader.conf"
|
79
|
+
]
|
80
|
+
}
|
81
|
+
}
|
82
|
+
}
|
83
|
+
}
|
84
|
+
}
|
85
|
+
},
|
86
|
+
type: "AWS::AutoScaling::LaunchConfiguration" do |r|
|
87
|
+
r.property(:image_id) { :ecs_ami.ref }
|
88
|
+
r.property(:key_name) { "sfs-keypair-#{environment}" }
|
89
|
+
r.property(:instance_type) { cluster_instance_type }
|
90
|
+
r.property(:security_groups) do
|
91
|
+
[
|
92
|
+
:ecs_host_security_group.ref,
|
93
|
+
:load_balancer_security_group.ref # Not sure if necessary
|
94
|
+
]
|
95
|
+
end
|
96
|
+
r.property(:iam_instance_profile) { :ecs_instance_profile.ref }
|
97
|
+
r.property(:user_data) { "#!/bin/bash\nyum install -y https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm\nyum install -y https://s3.amazonaws.com/amazoncloudwatch-agent/amazon_linux/amd64/latest/amazon-cloudwatch-agent.rpm\nyum install -y aws-cfn-bootstrap hibagent \n/opt/aws/bin/cfn-init -v --region ${AWS::Region} --stack ${AWS::StackName} --resource EcsLaunchConfiguration\n/opt/aws/bin/cfn-signal -e $? --region ${AWS::Region} --stack ${AWS::StackName} --resource EcsAutoScalingGroup\n/usr/bin/enable-ec2-spot-hibernation\n".fnsub.fnbase64 }
|
98
|
+
end
|
99
|
+
|
100
|
+
ecs_ssm_parameter_value = {
|
101
|
+
"logs": {
|
102
|
+
"force_flush_interval": 5,
|
103
|
+
"logs_collected": {
|
104
|
+
"files": {
|
105
|
+
"collect_list": [
|
106
|
+
{
|
107
|
+
"file_path": "/var/log/messages",
|
108
|
+
"log_group_name": "${SfsEcsCluster}-/var/log/messages",
|
109
|
+
"log_stream_name": "{instance_id}",
|
110
|
+
"timestamp_format": "%b %d %H:%M:%S"
|
111
|
+
},
|
112
|
+
{
|
113
|
+
"file_path": "/var/log/dmesg",
|
114
|
+
"log_group_name": "${SfsEcsCluster}-/var/log/dmesg",
|
115
|
+
"log_stream_name": "{instance_id}"
|
116
|
+
},
|
117
|
+
{
|
118
|
+
"file_path": "/var/log/docker",
|
119
|
+
"log_group_name": "${SfsEcsCluster}-/var/log/docker",
|
120
|
+
"log_stream_name": "{instance_id}",
|
121
|
+
"timestamp_format": "%Y-%m-%dT%H:%M:%S.%f"
|
122
|
+
},
|
123
|
+
{
|
124
|
+
"file_path": "/var/log/ecs/ecs-init.log",
|
125
|
+
"log_group_name": "${SfsEcsCluster}-/var/log/ecs/ecs-init.log",
|
126
|
+
"log_stream_name": "{instance_id}",
|
127
|
+
"timestamp_format": "%Y-%m-%dT%H:%M:%SZ"
|
128
|
+
},
|
129
|
+
{
|
130
|
+
"file_path": "/var/log/ecs/ecs-agent.log.*",
|
131
|
+
"log_group_name": "${SfsEcsCluster}-/var/log/ecs/ecs-agent.log",
|
132
|
+
"log_stream_name": "{instance_id}",
|
133
|
+
"timestamp_format": "%Y-%m-%dT%H:%M:%SZ"
|
134
|
+
},
|
135
|
+
{
|
136
|
+
"file_path": "/var/log/ecs/audit.log",
|
137
|
+
"log_group_name": "${SfsEcsCluster}-/var/log/ecs/audit.log",
|
138
|
+
"log_stream_name": "{instance_id}",
|
139
|
+
"timestamp_format": "%Y-%m-%dT%H:%M:%SZ"
|
140
|
+
}
|
141
|
+
]
|
142
|
+
}
|
143
|
+
}
|
144
|
+
},
|
145
|
+
"metrics": {
|
146
|
+
"append_dimensions": {
|
147
|
+
"AutoScalingGroupName": "${!aws:AutoScalingGroupName}",
|
148
|
+
"InstanceId": "${!aws:InstanceId}",
|
149
|
+
"InstanceType": "${!aws:InstanceType}"
|
150
|
+
},
|
151
|
+
"metrics_collected": {
|
152
|
+
"collectd": {
|
153
|
+
"metrics_aggregation_interval": 60
|
154
|
+
},
|
155
|
+
"disk": {
|
156
|
+
"measurement": [
|
157
|
+
"used_percent"
|
158
|
+
],
|
159
|
+
"metrics_collection_interval": 60,
|
160
|
+
"resources": [
|
161
|
+
"/"
|
162
|
+
]
|
163
|
+
},
|
164
|
+
"mem": {
|
165
|
+
"measurement": [
|
166
|
+
"mem_used_percent"
|
167
|
+
],
|
168
|
+
"metrics_collection_interval": 60
|
169
|
+
},
|
170
|
+
"statsd": {
|
171
|
+
"metrics_aggregation_interval": 60,
|
172
|
+
"metrics_collection_interval": 10,
|
173
|
+
"service_address": ":8125"
|
174
|
+
}
|
175
|
+
}
|
176
|
+
}
|
177
|
+
}
|
178
|
+
|
179
|
+
resource :ecs_cloud_watch_parameter,
|
180
|
+
type: "AWS::SSM::Parameter" do |r|
|
181
|
+
r.property(:description) { "ECS" }
|
182
|
+
r.property(:name) { "AmazonCloudWatch-${SfsEcsCluster}-ECS".fnsub }
|
183
|
+
r.property(:type) { "String" }
|
184
|
+
r.property(:value) { JSON.pretty_generate(ecs_ssm_parameter_value).fnsub }
|
185
|
+
end
|
186
|
+
|
187
|
+
resource :ecs_host_security_group,
|
188
|
+
type: "AWS::EC2::SecurityGroup" do |r|
|
189
|
+
r.property(:vpc_id) { :vpc.ref }
|
190
|
+
r.property(:group_description) { "Access to the ECS hosts and the tasks/containers that run on them" }
|
191
|
+
r.property(:security_group_ingress) do
|
192
|
+
[
|
193
|
+
{
|
194
|
+
"SourceSecurityGroupId": :load_balancer_security_group.ref,
|
195
|
+
"IpProtocol": -1
|
196
|
+
}
|
197
|
+
]
|
198
|
+
end
|
199
|
+
r.property(:tags) do
|
200
|
+
[
|
201
|
+
{
|
202
|
+
"Key": "Name",
|
203
|
+
"Value": "#{environment}-ECS-Hosts"
|
204
|
+
}
|
205
|
+
]
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
resource :load_balancer_security_group,
|
210
|
+
type: "AWS::EC2::SecurityGroup" do |r|
|
211
|
+
r.property(:vpc_id) { :vpc.ref }
|
212
|
+
r.property(:group_description) { "Access to the load balancer that sits in front of ECS" }
|
213
|
+
r.property(:security_group_ingress) do
|
214
|
+
[
|
215
|
+
{
|
216
|
+
"CidrIp": "0.0.0.0/0",
|
217
|
+
"IpProtocol": -1
|
218
|
+
}
|
219
|
+
]
|
220
|
+
end
|
221
|
+
r.property(:tags) do
|
222
|
+
[
|
223
|
+
{
|
224
|
+
"Key": "Name",
|
225
|
+
"Value": "#{environment}-ECS-LoadBalancers"
|
226
|
+
}
|
227
|
+
]
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
ecs_role_assume_role_policy_document = {
|
232
|
+
"Statement": [
|
233
|
+
{
|
234
|
+
"Action": "sts:AssumeRole",
|
235
|
+
"Effect": "Allow",
|
236
|
+
"Principal": {
|
237
|
+
"Service": "ec2.amazonaws.com"
|
238
|
+
}
|
239
|
+
}
|
240
|
+
]
|
241
|
+
}
|
242
|
+
|
243
|
+
ecs_role_policy_document = {
|
244
|
+
"Statement": [{
|
245
|
+
"Effect": "Allow",
|
246
|
+
"Action": [
|
247
|
+
"ecs:CreateCluster",
|
248
|
+
"ecs:DeregisterContainerInstance",
|
249
|
+
"ecs:DiscoverPollEndpoint",
|
250
|
+
"ecs:Poll",
|
251
|
+
"ecs:RegisterContainerInstance",
|
252
|
+
"ecs:StartTelemetrySession",
|
253
|
+
"ecs:Submit*",
|
254
|
+
"ecr:BatchCheckLayerAvailability",
|
255
|
+
"ecr:BatchGetImage",
|
256
|
+
"ecr:GetDownloadUrlForLayer",
|
257
|
+
"ecr:GetAuthorizationToken"
|
258
|
+
],
|
259
|
+
"Resource": "*"
|
260
|
+
}]
|
261
|
+
}
|
262
|
+
|
263
|
+
resource :ecs_role,
|
264
|
+
type: "AWS::IAM::Role" do |r|
|
265
|
+
r.property(:path) { "/" }
|
266
|
+
r.property(:role_name) { "#{environment}-ECSRole-${AWS::Region}".fnsub }
|
267
|
+
r.property(:assume_role_policy_document) { JSON.pretty_generate(ecs_role_assume_role_policy_document) }
|
268
|
+
r.property(:managed_policy_arns) do
|
269
|
+
[
|
270
|
+
"arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforSSM",
|
271
|
+
"arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy",
|
272
|
+
"arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role"
|
273
|
+
]
|
274
|
+
end
|
275
|
+
r.property(:policies) do
|
276
|
+
[
|
277
|
+
{
|
278
|
+
"PolicyName": "ecs-service",
|
279
|
+
"PolicyDocument": JSON.pretty_generate(ecs_role_policy_document)
|
280
|
+
}
|
281
|
+
]
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
resource :ecs_instance_profile,
|
286
|
+
type: "AWS::IAM::InstanceProfile" do |r|
|
287
|
+
r.property(:path) { "/" }
|
288
|
+
r.property(:roles) do
|
289
|
+
[
|
290
|
+
:ecs_role.ref
|
291
|
+
]
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
resource :ecs_service_auto_scaling_role,
|
296
|
+
type: "AWS::IAM::Role" do |r|
|
297
|
+
r.property(:assume_role_policy_document) do
|
298
|
+
{
|
299
|
+
"Version": "2012-10-17",
|
300
|
+
"Statement": {
|
301
|
+
"Action": [
|
302
|
+
"sts:AssumeRole"
|
303
|
+
],
|
304
|
+
"Effect": "Allow",
|
305
|
+
"Principal": {
|
306
|
+
"Service": [
|
307
|
+
"application-autoscaling.amazonaws.com"
|
308
|
+
]
|
309
|
+
}
|
310
|
+
}
|
311
|
+
}
|
312
|
+
end
|
313
|
+
r.property(:path) { "/" }
|
314
|
+
r.property(:policies) do
|
315
|
+
[
|
316
|
+
{
|
317
|
+
"PolicyName": "ecs-service-autoscaling",
|
318
|
+
"PolicyDocument": {
|
319
|
+
"Statement": {
|
320
|
+
"Effect": "Allow",
|
321
|
+
"Action": [
|
322
|
+
"application-autoscaling:*",
|
323
|
+
"cloudwatch:DescribeAlarms",
|
324
|
+
"cloudwatch:PutMetricAlarm",
|
325
|
+
"ecs:DescribeServices",
|
326
|
+
"ecs:UpdateService"
|
327
|
+
],
|
328
|
+
"Resource": "*"
|
329
|
+
}
|
330
|
+
}
|
331
|
+
}
|
332
|
+
]
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
output :ecs_cluster,
|
337
|
+
value: :ecs_cluster.ref
|
338
|
+
output :ecs_cluster_arn,
|
339
|
+
value: :ecs_cluster.ref(:arn)
|
340
|
+
output :ecs_auto_scaling_role_arn,
|
341
|
+
value: :ecs_service_auto_scaling_role.ref(:arn)
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
@@ -0,0 +1,188 @@
|
|
1
|
+
module EcsStack
|
2
|
+
module LifecycleHook
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
## Drains instances on termination
|
5
|
+
included do
|
6
|
+
resource :ecs_lifecycle_notification_topic,
|
7
|
+
depends_on: :lifecycle_handler_function,
|
8
|
+
type: "AWS::SNS::Topic" do |r|
|
9
|
+
r.property(:subscription) do
|
10
|
+
[
|
11
|
+
{
|
12
|
+
"Endpoint": :lifecycle_handler_function.ref(:arn),
|
13
|
+
"Protocol": "lambda"
|
14
|
+
}
|
15
|
+
]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
resource :instance_terminating_hook,
|
20
|
+
depends_on: :ecs_lifecycle_notification_topic,
|
21
|
+
type: "AWS::AutoScaling::LifecycleHook" do |r|
|
22
|
+
r.property(:auto_scaling_group_name) { :ecs_auto_scaling_group.ref }
|
23
|
+
r.property(:default_result) { "ABANDON" }
|
24
|
+
r.property(:heartbeat_timeout) { 900 }
|
25
|
+
r.property(:lifecycle_transition) { "autoscaling:EC2_INSTANCE_TERMINATING" }
|
26
|
+
r.property(:notification_target_arn) { :ecs_lifecycle_notification_topic.ref }
|
27
|
+
r.property(:role_arn) { :autoscaling_notification_role.ref(:arn) }
|
28
|
+
end
|
29
|
+
|
30
|
+
resource :autoscaling_notification_role,
|
31
|
+
type: "AWS::IAM::Role" do |r|
|
32
|
+
r.property(:assume_role_policy_document) do
|
33
|
+
{
|
34
|
+
"Version": "2012-10-17",
|
35
|
+
"Statement": [
|
36
|
+
{
|
37
|
+
"Effect": "Allow",
|
38
|
+
"Principal": {
|
39
|
+
"Service": [
|
40
|
+
"autoscaling.amazonaws.com"
|
41
|
+
]
|
42
|
+
},
|
43
|
+
"Action": [
|
44
|
+
"sts:AssumeRole"
|
45
|
+
]
|
46
|
+
}
|
47
|
+
]
|
48
|
+
}
|
49
|
+
end
|
50
|
+
r.property(:managed_policy_arns) { ["arn:aws:iam::aws:policy/service-role/AutoScalingNotificationAccessRole"] }
|
51
|
+
end
|
52
|
+
|
53
|
+
resource :lambda_execution_role,
|
54
|
+
type: "AWS::IAM::Role" do |r|
|
55
|
+
r.property(:policies) do
|
56
|
+
[
|
57
|
+
{
|
58
|
+
"PolicyName": "lambda-inline",
|
59
|
+
"PolicyDocument": {
|
60
|
+
"Version": "2012-10-17",
|
61
|
+
"Statement": [
|
62
|
+
{
|
63
|
+
"Effect": "Allow",
|
64
|
+
"Action": [
|
65
|
+
"autoscaling:CompleteLifecycleAction",
|
66
|
+
"logs:CreateLogGroup",
|
67
|
+
"logs:CreateLogStream",
|
68
|
+
"logs:PutLogEvents",
|
69
|
+
"ec2:DescribeInstances",
|
70
|
+
"ec2:DescribeInstanceAttribute",
|
71
|
+
"ec2:DescribeInstanceStatus",
|
72
|
+
"ec2:DescribeHosts",
|
73
|
+
"ecs:ListContainerInstances",
|
74
|
+
"ecs:SubmitContainerStateChange",
|
75
|
+
"ecs:SubmitTaskStateChange",
|
76
|
+
"ecs:DescribeContainerInstances",
|
77
|
+
"ecs:UpdateContainerInstancesState",
|
78
|
+
"ecs:ListTasks",
|
79
|
+
"ecs:DescribeTasks",
|
80
|
+
"sns:Publish",
|
81
|
+
"sns:ListSubscriptions"
|
82
|
+
],
|
83
|
+
"Resource": "*"
|
84
|
+
}
|
85
|
+
]
|
86
|
+
}
|
87
|
+
}
|
88
|
+
]
|
89
|
+
end
|
90
|
+
r.property(:assume_role_policy_document) do
|
91
|
+
{
|
92
|
+
"Version": "2012-10-17",
|
93
|
+
"Statement": [
|
94
|
+
{
|
95
|
+
"Effect": "Allow",
|
96
|
+
"Principal": {
|
97
|
+
"Service": [
|
98
|
+
"lambda.amazonaws.com"
|
99
|
+
]
|
100
|
+
},
|
101
|
+
"Action": [
|
102
|
+
"sts:AssumeRole"
|
103
|
+
]
|
104
|
+
}
|
105
|
+
]
|
106
|
+
}
|
107
|
+
end
|
108
|
+
r.property(:managed_policy_arns) do
|
109
|
+
[
|
110
|
+
"arn:aws:iam::aws:policy/service-role/AutoScalingNotificationAccessRole"
|
111
|
+
]
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
resource :lambda_invoke_permission,
|
116
|
+
type: "AWS::Lambda::Permission" do |r|
|
117
|
+
r.property(:function_name) { :lifecycle_handler_function.ref }
|
118
|
+
r.property(:action) { "lambda:InvokeFunction" }
|
119
|
+
r.property(:principal) { "sns.amazonaws.com" }
|
120
|
+
r.property(:source_arn) { :ecs_lifecycle_notification_topic.ref }
|
121
|
+
end
|
122
|
+
|
123
|
+
resource :lifecycle_handler_function,
|
124
|
+
type: "AWS::Lambda::Function" do |r|
|
125
|
+
r.property(:environment) do
|
126
|
+
{
|
127
|
+
"Variables": {
|
128
|
+
"CLUSTER": :sfs_ecs_cluster.ref
|
129
|
+
}
|
130
|
+
}
|
131
|
+
end
|
132
|
+
r.property(:code) do
|
133
|
+
{
|
134
|
+
"ZipFile": {
|
135
|
+
"Fn::Join": [
|
136
|
+
"\n",
|
137
|
+
[
|
138
|
+
"import boto3,json,os,time",
|
139
|
+
"ec2Client = boto3.client('ec2')",
|
140
|
+
"ecsClient = boto3.client('ecs')",
|
141
|
+
"autoscalingClient = boto3.client('autoscaling')",
|
142
|
+
"snsClient = boto3.client('sns')",
|
143
|
+
"lambdaClient = boto3.client('lambda')",
|
144
|
+
"def publishSNSMessage(snsMessage,snsTopicArn):",
|
145
|
+
" response = snsClient.publish(TopicArn=snsTopicArn,Message=json.dumps(snsMessage),Subject='reinvoking')",
|
146
|
+
"def setContainerInstanceStatusToDraining(ecsClusterName,containerInstanceArn):",
|
147
|
+
" response = ecsClient.update_container_instances_state(cluster=ecsClusterName,containerInstances=[containerInstanceArn],status='DRAINING')",
|
148
|
+
"def tasksRunning(ecsClusterName,ec2InstanceId):",
|
149
|
+
" ecsContainerInstances = ecsClient.describe_container_instances(cluster=ecsClusterName,containerInstances=ecsClient.list_container_instances(cluster=ecsClusterName)['containerInstanceArns'])['containerInstances']",
|
150
|
+
" for i in ecsContainerInstances:",
|
151
|
+
" if i['ec2InstanceId'] == ec2InstanceId:",
|
152
|
+
" if i['status'] == 'ACTIVE':",
|
153
|
+
" setContainerInstanceStatusToDraining(ecsClusterName,i['containerInstanceArn'])",
|
154
|
+
" return 1",
|
155
|
+
" if (i['runningTasksCount']>0) or (i['pendingTasksCount']>0):",
|
156
|
+
" return 1",
|
157
|
+
" return 0",
|
158
|
+
" return 2",
|
159
|
+
"def lambda_handler(event, context):",
|
160
|
+
" ecsClusterName=os.environ['CLUSTER']",
|
161
|
+
" snsTopicArn=event['Records'][0]['Sns']['TopicArn']",
|
162
|
+
" snsMessage=json.loads(event['Records'][0]['Sns']['Message'])",
|
163
|
+
" lifecycleHookName=snsMessage['LifecycleHookName']",
|
164
|
+
" lifecycleActionToken=snsMessage['LifecycleActionToken']",
|
165
|
+
" asgName=snsMessage['AutoScalingGroupName']",
|
166
|
+
" ec2InstanceId=snsMessage['EC2InstanceId']",
|
167
|
+
" checkTasks=tasksRunning(ecsClusterName,ec2InstanceId)",
|
168
|
+
" if checkTasks==0:",
|
169
|
+
" try:",
|
170
|
+
" response = autoscalingClient.complete_lifecycle_action(LifecycleHookName=lifecycleHookName,AutoScalingGroupName=asgName,LifecycleActionToken=lifecycleActionToken,LifecycleActionResult='CONTINUE')",
|
171
|
+
" except BaseException as e:",
|
172
|
+
" print(str(e))",
|
173
|
+
" elif checkTasks==1:",
|
174
|
+
" time.sleep(5)",
|
175
|
+
" publishSNSMessage(snsMessage,snsTopicArn)"
|
176
|
+
]
|
177
|
+
]
|
178
|
+
}
|
179
|
+
}
|
180
|
+
end
|
181
|
+
r.property(:handler) { "index.lambda_handler" }
|
182
|
+
r.property(:role) { :lambda_execution_role.ref(:arn) }
|
183
|
+
r.property(:runtime) { "python3.6" }
|
184
|
+
r.property(:timeout) { 10 }
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module EcsStack
|
2
|
+
module LoadBalancer
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
included do
|
5
|
+
parameter :public_subnets,
|
6
|
+
description: "List of Public EC2 Subnets for the ALB"
|
7
|
+
|
8
|
+
resource :ecs_load_balancer,
|
9
|
+
type: "AWS::ElasticLoadBalancingV2::LoadBalancer" do |r|
|
10
|
+
r.property(:name) { environment }
|
11
|
+
r.property(:subnets) { :public_subnets.ref.fnsplit(",") }
|
12
|
+
r.property(:security_groups) do
|
13
|
+
[
|
14
|
+
:ecs_host_security_group.ref,
|
15
|
+
:load_balancer_security_group.ref # Not sure if necessary
|
16
|
+
]
|
17
|
+
end
|
18
|
+
r.property(:tags) do
|
19
|
+
[
|
20
|
+
{
|
21
|
+
"Key": "Name",
|
22
|
+
"Value": "#{environment}_ecs_loadbalancer"
|
23
|
+
}
|
24
|
+
]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
resource :load_balancer_listener,
|
29
|
+
type: "AWS::ElasticLoadBalancingV2::Listener" do |r|
|
30
|
+
r.property(:load_balancer_arn) { :ecs_load_balancer.ref }
|
31
|
+
r.property(:port) { 80 }
|
32
|
+
r.property(:protocol) { "HTTP" }
|
33
|
+
r.property(:default_actions) do
|
34
|
+
[
|
35
|
+
{
|
36
|
+
"Type": "forward",
|
37
|
+
"TargetGroupArn": :default_target_group.ref
|
38
|
+
}
|
39
|
+
]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
resource :default_target_group,
|
44
|
+
type: "AWS::ElasticLoadBalancingV2::TargetGroup" do |r|
|
45
|
+
r.property(:name) { "#{environment}-default" }
|
46
|
+
r.property(:vpc_id) { :vpc.ref }
|
47
|
+
r.property(:port) { 80 }
|
48
|
+
r.property(:protocol) { "HTTP" }
|
49
|
+
end
|
50
|
+
|
51
|
+
output :ecs_load_balancer,
|
52
|
+
description: "ECS Application Load Balancer",
|
53
|
+
value: :ecs_load_balancer.ref
|
54
|
+
|
55
|
+
output :ecs_load_balancer_url,
|
56
|
+
description: "URL of the ECS ALB",
|
57
|
+
value: :ecs_load_balancer.ref("DNSName")
|
58
|
+
|
59
|
+
output :ecs_load_balancer_listener,
|
60
|
+
description: "ECS Port 80 listener",
|
61
|
+
value: :load_balancer_listener.ref
|
62
|
+
|
63
|
+
output :ecs_load_balancer_hosted_zone_id,
|
64
|
+
description: "Canonical Hosted Zone ID of the ALB",
|
65
|
+
value: :ecs_load_balancer.ref("CanonicalHostedZoneID")
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -1,11 +1,12 @@
|
|
1
1
|
module EcsStack
|
2
2
|
extend ActiveSupport::Concern
|
3
3
|
include Rubycfn
|
4
|
-
|
5
4
|
included do
|
6
5
|
include Concerns::GlobalVariables
|
7
6
|
include Concerns::SharedMethods
|
8
7
|
include EcsStack::EcsCluster
|
8
|
+
include EcsStack::LifecycleHook
|
9
|
+
include EcsStack::LoadBalancer
|
9
10
|
|
10
11
|
description generate_stack_description("EcsStack")
|
11
12
|
end
|
@@ -1,11 +1,11 @@
|
|
1
|
-
module
|
1
|
+
module InfraStack
|
2
2
|
extend ActiveSupport::Concern
|
3
3
|
include Rubycfn
|
4
4
|
|
5
5
|
included do
|
6
6
|
include Concerns::GlobalVariables
|
7
7
|
include Concerns::SharedMethods
|
8
|
-
include
|
8
|
+
include InfraStack::Parent
|
9
9
|
|
10
10
|
description generate_stack_description("ParentStack")
|
11
11
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require_relative "../../core/applications"
|
2
|
+
|
3
|
+
module InfraStack
|
4
|
+
module Parent
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
included do
|
7
|
+
generate_bootstrap_parameters
|
8
|
+
|
9
|
+
resource :vpc_stack,
|
10
|
+
type: "AWS::CloudFormation::Stack" do |r|
|
11
|
+
r.property(:template_url) { "vpcstack" }
|
12
|
+
r.property(:tags) { default_tags }
|
13
|
+
end
|
14
|
+
|
15
|
+
create_applications
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|