-
Notifications
You must be signed in to change notification settings - Fork 17
Expand file tree
/
Copy pathstack.yaml
More file actions
727 lines (684 loc) · 26.9 KB
/
stack.yaml
File metadata and controls
727 lines (684 loc) · 26.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
# == ecsfs ====================================================================
# Independently Scalable Multi-Container Microservices Architecture on Fargate
# =============================================================================
# This CloudFormation stack shows how to deploy a full-stack application
# consisting of a backend, a frontend and an nginx server. Each defined as an
# independent Fargate service. The backend will auto-scale between 1 and 3.
#
# Two articles have been written explaining what this stack is for:
#
# - https://medium.com/@eulersson/microservices-on-fargate-part1-f26a318827a8
# - https://medium.com/@eulersson/microservices-on-fargate-part2-f29c6d4d708f
#
# How to deploy this YAML template
# --------------------------------
#
# Either using command line (1) or from the web console (2).
#
# 1. From the command line.
# First you will need to install and configure the AWS CLI. Read the docs:
#
# - https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html
# - https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html
#
# TL;DR Basically you will need to install it with pip:
#
# pip install awscli --upgrade --user
#
# And configure it (specify your default region too so you don't have to type
# it on each subsequent command):
#
# aws configure
#
# To actually deploy the stack you have two choices (a) and (b)...
#
# a) If you have no hosted zones set up (associated with a Route 53 domain):
#
# aws cloudformation create-stack \
# --stack-name ecsfs \
# --template-body file://$(pwd)/stack.yaml \
# --capabilities CAPABILITY_NAMED_IAM \
# --parameters ParameterKey=HostedZoneName,ParameterValue=
#
# b) If you have a hosted zone, you can pass it in an the application will
# be available under the subdomain ecsfs.<your-hosted-zone-name>, e.g.
# ecsfs.example.com. Simply pass the parameter flag instead of leaving it
# empty:
#
# --parameters ParameterKey=HostedZoneName,ParameterValue=foo.com.
# (!) the final dot is needed ^
#
# 2. From the CloudFormation section on your AWS web console.
# - Click the "Create Stack" button.
# - Click on "Choose File" and upload this stack.yaml file.
# - Give the Stack a name: "ecsfs".
# - In the parameters section, you will see "HostedZoneName". It is up to you
# if you want to use one of your hosted zones (domains) for instance
# 'foo.com.' so the application would then be configured to run on a
# subdomain of it (ecsfs.foo.com). You can leave it empty.
# - Click "Next".
# - Click "Next" one more time.
# - On the "Capabilities" section check the box "I acknowledge that..."
#
# Deleting all the resources that have been created
# -------------------------------------------------
# Either from the web console or from CLI. To do it from the web console go to
# the CloudFormation section and delete it there. The command line equivalent
# is:
#
# aws cloudformation delete-stack --stack-name ecsfs
#
# PARAMETERS ==================================================================
# Options the user can provide when deploying the stack that can be accessed
# within the resource definitions.
Parameters:
HostedZoneName:
Type: String
Description:
(Optional) If you have a domain available registered with Route 53 you
can type it (e.g. 'foo.com.'; do not miss the final dot!). Then a DNS
record gets created on subdomain ecsfs.foo.com which will route to the
load balancer (the entry point of this application).
# CONDITIONS ==================================================================
# Allows to define boolean variables that we can use to conditionally build
# some resources. You can set conditions for resource building by adding the
# Conditions yaml property under the resource.
Conditions:
HasHostedZoneName: !Not [ !Equals [ !Ref HostedZoneName, '']]
Resources:
# VIRTUAL PRIVATE CLOUD (VPC) ===============================================
# A VPC is simply a logically isolated chunk of the AWS Cloud.
#
# Our VPC has two public subnetworks since it's a requirement for an
# Application Load Balancer. The nginx container will use them too.
#
# Then we will isolate backend and frontend to a private subnet so they can't
# be reached directly from the Internet.
#
# You will the word CIDR in various places, it is used for subnet masking.
#
# CIDR blocks describe the Network ID and IP ranges to assign in our
# I subnets. It basically tells what part of the address is reserved for
# IPs and what part is for the network ID.
#
# E.g. 10.0.0.0/24 would mean that the first 3 octets (3 x 8 = 24) are
# going to be exclusively defining the Network ID, which would result in
# all the IPs that are given out would start with 10.0.0.x.
#
# This video explains it very well:
#
# - IPv4 Addressing: Network IDs and Subnet Masks
# https://youtu.be/XQ3T14SIlV4
#
VPC:
Type: AWS::EC2::VPC
Properties:
EnableDnsSupport: true
EnableDnsHostnames: true
CidrBlock: 10.0.0.0/16
Tags: # You can give
- Key: Name # pretty names to
Value: ECSFS VPC # your resources.
PublicSubnetOne:
Type: AWS::EC2::Subnet
Properties:
# Select the first availability zone on our current region.
AvailabilityZone: !Select # !Select chooses an item from a list.
- 0 # First availability zone, since...
- Fn::GetAZs: !Ref AWS::Region # ...a region has various zones (list).
CidrBlock: 10.0.0.0/24
VpcId: !Ref VPC
PublicSubnetTwo:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Select
- 1 # Second availability zone under the same region.
- Fn::GetAZs: !Ref AWS::Region
CidrBlock: 10.0.1.0/24
VpcId: !Ref VPC
PrivateSubnet:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Select
- 0
- Fn::GetAZs: !Ref AWS::Region
CidrBlock: 10.0.2.0/24
VpcId: !Ref VPC
# NETWORK SETUP: ROUTING AND SUBNETTING =====================================
# Let's revisit the main elements that comform a subnet and how we are going
# to use them in our application.
#
# - Internet Gateway:
#
# Allows communication between the containers and the internet. All the
# outbound traffic goes through it. In AWS it must get attached to a VPC.
#
# All requests from a instances runnning on the public subnet must be
# routed to the internet gateway. This is done by defining routes on
# route tables.
#
# - Network Address Translation (NAT) Gateway:
#
# When an application is running on a private subnet it cannot talk to
# the outside world. The NAT Gateway remaps the IP address of the packets
# sent from the private instance assigning them a public IP so when the
# service the instance wants to talk you replies, the NAT can receive the
# information (since the NAT itself is public-facing and rechable from
# the Internet) and hand it back to the private instance.
#
# An Elastic IP needs to be associated with each NAT Gateway we create.
#
# The reason why we traffic private tasks' traffic through a NAT is so
# tasks can pull the images from Docker Hub whilst keeping protection
# since connections cannot be initiated from the Internet, just outbound
# traffic will be allowed through the NAT.
#
# - Routes and Route Tables:
#
# Route tables gather together a set of routes. A route describes where
# do packets need to go based on rules. You can for instance send
# any packets with destination address starting with 10.0.4.x to a NAT
# while others with destination address 10.0.5.x to another NAT or
# internet gateway (I cannot find a proper example, I apologize). You can
# describe both in and outbound routes.
#
# The way we associate a route table with a subnet is by using "Subnet
# Route Table Association" resources, pretty descriptive.
#
# Public routing ------------------------------------------------------------
InternetGateway:
Type: AWS::EC2::InternetGateway
GatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId: !Ref InternetGateway
VpcId: !Ref VPC
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
PublicRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
PublicSubnetOneRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicRouteTable
SubnetId: !Ref PublicSubnetOne
PublicSubnetTwoRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicRouteTable
SubnetId: !Ref PublicSubnetTwo
# Private routing -----------------------------------------------------------
NatElasticIP:
Type: AWS::EC2::EIP
NatGateway:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt NatElasticIP.AllocationId
SubnetId: !Ref PublicSubnetOne
PrivateRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
PrivateRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PrivateRouteTable
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref NatGateway
PrivateSubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PrivateRouteTable
SubnetId: !Ref PrivateSubnet
# SECURITY ==================================================================
# A security group shared by all containers running on Fargate. Security
# groups act as firewalls between inbound and outbound communications of the
# instances we run.
#
# The stack has one security group with two ingress (inbound traffic) rules:
#
# 1. To allow traffic coming from the Application Load Balancer.
# (PublicLoadBalancerSecurityGroup)
#
# 2. To allow traffic between running containers.
# (FargateContainerSecurityGroup)
#
FargateContainerSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Access to Fargate containers.
VpcId: !Ref VPC
IngressFromPublicALBSecurityGroup:
Type: AWS::EC2::SecurityGroupIngress
Properties:
Description: Ingress from the public Application Load Balancer.
GroupId: !Ref FargateContainerSecurityGroup
IpProtocol: -1 # Means all protocols (TCD, UDP or any ICMP/ICMPv6 number).
SourceSecurityGroupId: !Ref PublicLoadBalancerSecurityGroup
IngressFromSelfSecurityGroup:
Type: AWS::EC2::SecurityGroupIngress
Properties:
Description: Ingress from other containers in the same security group.
GroupId: !Ref FargateContainerSecurityGroup
IpProtocol: -1
SourceSecurityGroupId: !Ref FargateContainerSecurityGroup
PublicLoadBalancerSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Access to the public facing load balancer.
VpcId: !Ref VPC
SecurityGroupIngress:
- CidrIp: 0.0.0.0/0 # Allows all IPs. Traffic from anywhere.
IpProtocol: -1
# LOAD BALANCER =============================================================
# The Application Load Balancer (ALB) is the single point of contact for
# clients (users). Its duty is to relay the request to the right running task
# (think of a task as an instance for now).
#
# In our case all requests on port 80 are forwarded to nginx task.
#
# To configure a load balancer we need to specify a listener and a target
# group. The listener is described through rules, where you can specify
# different targets to route to based on port or URL. The target group is the
# set of resources that would receive the routed requests from the ALB.
#
# This target group will be managed by Fargate and every time a new instance
# of nginx spins up then it will register it automatically on this group, so
# we don't have to worry about adding instances to the target group at all.
#
# Read more:
#
# https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html
#
TargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Name: ecsfs-target-group
Port: 80
Protocol: HTTP
TargetType: ip
VpcId: !Ref VPC
ListenerHTTP:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- TargetGroupArn: !Ref TargetGroup
Type: forward
LoadBalancerArn: !Ref LoadBalancer
Port: 80
Protocol: HTTP
LoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Scheme: internet-facing
SecurityGroups:
- !Ref PublicLoadBalancerSecurityGroup
Subnets:
- !Ref PublicSubnetOne
- !Ref PublicSubnetTwo
# If a hosted zone got specified when running this stack, we create a
# subdomain on that zone and route it to the load balancer. For instance, say
# 'example.com.' is specified as HostedZoneName, then all the traffic going to
# ecsfs.example.com would go to the load balancer.
DNSRecord:
Type: AWS::Route53::RecordSet
Condition: HasHostedZoneName
Properties:
HostedZoneName: !Ref HostedZoneName
Name: !Join ['.', [ecsfs, !Ref HostedZoneName]]
Type: A
AliasTarget:
DNSName: !GetAtt LoadBalancer.DNSName
HostedZoneId: !GetAtt LoadBalancer.CanonicalHostedZoneID
# ELASTIC CONTAINER SERVICE =================================================
# ECS is a container management system. It basically removes the headache of
# having to setup and provision another management infrastructure such as
# Kubernetes or similar.
#
# You define your application in ECS through **task definitions**, they act
# as blueprints which describe what containers to use, ports to open, what
# launch type to use (EC2 instances or Fargate), and what memory and CPU
# requirements need to be met.
#
# Then a service is in charge of taking those tasks definitions to generate
# and manage running processes from them in a **cluster**. Those running
# processes instanciated by the service are called **tasks**.
#
# Key ideas:
# * A cluster is a grouping of resources: services, task definitions, etc...
# * On a _task definition_...
# - You can describe one or more containers.
# - Desired CPU and memory needed to run that process.
# * A service takes a _task definition_ and instanciates it into running _tasks_.
# * _Task definitions_ and _services_ are configured per-cluster.
# * _Tasks_ run in a cluster.
# * Auto-scaling is configured on the service-level.
#
# Learn more:
#
# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/Welcome.html
#
# Cluster -------------------------------------------------------------------
ECSCluster:
Type: AWS::ECS::Cluster
Properties:
ClusterName: ecsfs-cluster
# Logging -------------------------------------------------------------------
# Throws all logs from tasks within our cluster under the same group. There
# is one log stream per task running. An aggregated result can be viewed from
# the web console under the page for the service the task is part of.
LogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: ecsfs-logs
# IAM Roles -----------------------------------------------------------------
# We need to allow Fargate to perform specific actions on our behalf.
#
# - ECS Task Execution Role: This role enables AWS Fargate to pull container
# images from Amazon ECR and to forward logs to Amazon CloudWatch Logs.
#
# - ECS Auto Scaling Role: Role needed to perform the scaling operations on
# our behalf, that is, to change the desired count state on the services.
#
# Read more:
#
# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html
# https://serverfault.com/questions/854413/confused-by-the-role-requirement-of-ecs
#
ExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: ecsfs-execution-role
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service: ecs-tasks.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
AutoScalingRole:
Type: AWS::IAM::Role
Properties:
RoleName: backend-auto-scaling-role
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service: ecs-tasks.amazonaws.com
Action: sts:AssumeRole
# Task Definitions ----------------------------------------------------------
BackendTaskDefinition:
Type: AWS::ECS::TaskDefinition
DependsOn: LogGroup
Properties:
Family: ecsfs-backend-td
Cpu: 256
Memory: 1024
NetworkMode: awsvpc
RequiresCompatibilities:
- FARGATE
ExecutionRoleArn: !Ref ExecutionRole
ContainerDefinitions:
- Name: ecsfs-backend-container
Image: eulersson/ecsfs-backend
PortMappings:
- ContainerPort: 5000
LogConfiguration:
LogDriver: awslogs
Options:
awslogs-group: ecsfs-logs
awslogs-region: !Ref AWS::Region
awslogs-stream-prefix: backend
FrontendTaskDefinition:
Type: AWS::ECS::TaskDefinition
DependsOn: LogGroup
Properties:
Family: ecsfs-frontend-td
Cpu: 256
Memory: 512
NetworkMode: awsvpc
RequiresCompatibilities:
- FARGATE
ExecutionRoleArn: !Ref ExecutionRole
ContainerDefinitions:
- Name: ecsfs-frontend-container
Image: eulersson/ecsfs-frontend
PortMappings:
- ContainerPort: 3000
LogConfiguration:
LogDriver: awslogs
Options:
awslogs-group: ecsfs-logs
awslogs-region: !Ref AWS::Region
awslogs-stream-prefix: frontend
NginxTaskDefinition:
Type: AWS::ECS::TaskDefinition
DependsOn: LogGroup
Properties:
Family: ecsfs-nginx-td
Cpu: 256
Memory: 512
NetworkMode: awsvpc
RequiresCompatibilities:
- FARGATE
ExecutionRoleArn: !Ref ExecutionRole
ContainerDefinitions:
- Name: ecsfs-nginx-container
Image: eulersson/ecsfs-nginx
PortMappings:
- ContainerPort: 80
LogConfiguration:
LogDriver: awslogs
Options:
awslogs-group: ecsfs-logs
awslogs-region: !Ref AWS::Region
awslogs-stream-prefix: nginx
# Service Discovery ---------------------------------------------------------
# In our application, we want the backend to be reachable at
# ecsfs-backend.local, the frontend at ecsfs-backend.local, etc... You can
# see the names are suffixed with .local. In AWS we can create a
# PrivateDnsService resource and add services to them, and that would produce
# the aforementioned names, that is, <service_name>.<private_dns_namespace>.
#
# By creating various DNS names under the same namespace, services that get
# assigned those names can talk between them, i.e. the frontend talking to
# a backend, or nginx talking to the frontend.
#
# The IPs for each service task are dynamic, they change, and sometimes more
# than task might be running for the same service... so... how do we associate
# the DNS name with the right task? Well we don't! Fargate does it all for us.
#
# There is a whole section on the documentation explaining it in detail:
#
# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/service-discovery.html
#
LocalNamespace:
Type: AWS::ServiceDiscovery::PrivateDnsNamespace
Properties:
Vpc: !Ref VPC
Name: local
NginxLocalDiscoveryService:
Type: AWS::ServiceDiscovery::Service
Properties:
Name: ecsfs-nginx
HealthCheckCustomConfig:
FailureThreshold: 1
DnsConfig:
DnsRecords:
- Type: A
TTL: 60
NamespaceId: !GetAtt LocalNamespace.Id
BackendLocalDiscoveryService:
Type: AWS::ServiceDiscovery::Service
Properties:
Name: ecsfs-backend
HealthCheckCustomConfig:
FailureThreshold: 1
DnsConfig:
DnsRecords:
- Type: A
TTL: 60
NamespaceId: !GetAtt LocalNamespace.Id
FrontendLocalDiscoveryService:
Type: AWS::ServiceDiscovery::Service
Properties:
Name: ecsfs-frontend
HealthCheckCustomConfig:
FailureThreshold: 1
DnsConfig:
DnsRecords:
- Type: A
TTL: 60
NamespaceId: !GetAtt LocalNamespace.Id
# Services ------------------------------------------------------------------
BackendService:
Type: AWS::ECS::Service
Properties:
ServiceName: ecsfs-backend-service
Cluster: !Ref ECSCluster
LaunchType: FARGATE
DesiredCount: 1
ServiceRegistries: # And that's how you associate ecsfs-backend.local!
- RegistryArn: !GetAtt BackendLocalDiscoveryService.Arn
NetworkConfiguration:
AwsvpcConfiguration:
AssignPublicIp: DISABLED
SecurityGroups:
- !Ref FargateContainerSecurityGroup
Subnets:
- !Ref PrivateSubnet
TaskDefinition: !Ref BackendTaskDefinition
FrontendService:
Type: AWS::ECS::Service
Properties: # Associates it with the DNS name ecsfs-frontend.local.
ServiceName: ecsfs-frontend-service
Cluster: !Ref ECSCluster
LaunchType: FARGATE
DesiredCount: 1
ServiceRegistries:
- RegistryArn: !GetAtt FrontendLocalDiscoveryService.Arn
NetworkConfiguration:
AwsvpcConfiguration:
AssignPublicIp: DISABLED
SecurityGroups:
- !Ref FargateContainerSecurityGroup
Subnets:
- !Ref PrivateSubnet
TaskDefinition: !Ref FrontendTaskDefinition
# The application load balancer routes the requests to the nginx service,
# therefore we need to wait for the ALB to finish before we can actually spin
# up the nginx service.
NginxService:
Type: AWS::ECS::Service
DependsOn: ListenerHTTP
Properties:
ServiceName: ecsfs-nginx-service
Cluster: !Ref ECSCluster
LaunchType: FARGATE
DesiredCount: 1
ServiceRegistries: # Associate it with ecsfs-nginx.local DNS name.
- RegistryArn: !GetAtt NginxLocalDiscoveryService.Arn
NetworkConfiguration:
AwsvpcConfiguration:
AssignPublicIp: ENABLED
SecurityGroups:
- !Ref FargateContainerSecurityGroup
Subnets:
- !Ref PublicSubnetOne
- !Ref PublicSubnetTwo
TaskDefinition: !Ref NginxTaskDefinition
LoadBalancers:
- ContainerName: ecsfs-nginx-container
ContainerPort: 80
TargetGroupArn: !Ref TargetGroup
# AUTO-SCALING -------------------------------------------------------------
# We are just interested in scaling the backend. For scaling a service you
# need to define a *Scalable Target*, which is where you specify *what*
# service do you want to scale, and a *ScalingPolicy*, where you describe
# *how* and *when* do you want to scale it.
#
# There's two modes when scaling a service, we use 'Target Tracking Scaling',
# in which you specify a target value for a metric (say for instance 75% of
# CPU usage) and then Fargate would spin more instances when the average of
# all the tasks running that service exceed the threshold.
#
# In our case we will scale the backend between 1 and 3 instances and we will
# specify a target CPU usage percentage of 50%.
#
# Usually each service task spits out metrics every 1 minute. You can see
# these metrics on the CloudWatch page on the AWS web console. Use that for
# inspecting how Fargate reacts to changes when you stress the application.
#
# Read more:
#
# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/service-autoscaling-targettracking.html
#
# Specifies a resource that Application Auto Scaling can scale. In our case
# it's just the backend.
AutoScalingTarget:
Type: AWS::ApplicationAutoScaling::ScalableTarget
Properties:
MinCapacity: 1
MaxCapacity: 3
ResourceId: !Join ['/', [service, !Ref ECSCluster, !GetAtt BackendService.Name]]
ScalableDimension: ecs:service:DesiredCount
ServiceNamespace: ecs
RoleARN: !GetAtt AutoScalingRole.Arn
# Describes the rules for ECS to check and decide when it should scale up or
# down a service. In our application we just scale the backend.
AutoScalingPolicy:
Type: AWS::ApplicationAutoScaling::ScalingPolicy
Properties:
PolicyName: BackendAutoScalingPolicy
PolicyType: TargetTrackingScaling
ScalingTargetId: !Ref AutoScalingTarget
TargetTrackingScalingPolicyConfiguration:
PredefinedMetricSpecification:
PredefinedMetricType: ECSServiceAverageCPUUtilization
ScaleInCooldown: 10
ScaleOutCooldown: 10
TargetValue: 50
# STRESSING THE APPLICATION =================================================
# You can use the 'ab' unix command (Apache Benchmark) to send many requests
# to you application load balancer and see how Fargate starts scaling up the
# backend service.
#
# First go to the web console under the EC2 page and look for the Load
# Balancers category.
#
# In there look for the DNS name. You can also click the *Outputs* tab from
# the CloudFormation stack to see that URL. It should look like:
#
# http://ecsfs-loadb-1g27mx21p6h8d-1015414055.us-west-2.elb.amazonaws.com/
#
# Then run the following command to stress the application. It will perform
# 10,000 requests (1 per second) printing all the responses.
#
# ab -n 10000 -c 1 -v 3 http://<application_load_balancer_dns_name>/
#
# I noticed that CloudWatch will wait until it has 3 consecutive measurements
# (metric points) exceeding the target value specified (50%). It is then when
# it then when it sets an alarm and Fargate reacts by adding extra running
# tasks until the metrics stabilize.
#
# If then the CPU decreases and doesn't need many tasks running anymore it
# would wait some minutes (around 15 metric points, that is, 15min) to start
# scaling down.
# OUTPUTS =====================================================================
# The entries defined as outputs will show in the stack page in the Amazon
# web console under the CloudFormation section. We expose the load balancer DNS
# name so you can copy-paste it on your browser to see the app running:
#
# http://<your-application-load-balancer-dns-name>
#
Outputs:
LoadBalancerDNSName:
Description: Copy and paste this value into your browser to access the app.
Value: !GetAtt LoadBalancer.DNSName