├── services
├── product-service
│ ├── src
│ │ ├── users
│ │ ├── Dockerfile
│ │ ├── main.go
│ │ └── Makefile
│ └── service.yaml
└── website-service
│ ├── src
│ ├── users
│ ├── Dockerfile
│ ├── Makefile
│ └── main.go
│ └── service.yaml
├── NOTICE
├── images
├── stack-outputs.png
├── architecture-overview.png
├── cloudformation-launch-stack.png
└── architecture-overview.graffle
│ ├── data.plist
│ ├── image6.pdf
│ ├── image8.pdf
│ ├── image9.pdf
│ ├── image12.pdf
│ └── image14.pdf
├── infrastructure
├── security-groups.yaml
├── load-balancers.yaml
├── vpc.yaml
└── ecs-cluster.yaml
├── master.yaml
├── LICENSE
└── README.md
/services/product-service/src/users:
--------------------------------------------------------------------------------
1 | nobody:x:65534:65534:Nobody:/:
2 |
--------------------------------------------------------------------------------
/services/website-service/src/users:
--------------------------------------------------------------------------------
1 | nobody:x:65534:65534:Nobody:/:
2 |
--------------------------------------------------------------------------------
/NOTICE:
--------------------------------------------------------------------------------
1 | ecs-refarch-cloudformation
2 | Copyright 2011-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 |
--------------------------------------------------------------------------------
/images/stack-outputs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simplesteph/ecs-refarch-cloudformation/HEAD/images/stack-outputs.png
--------------------------------------------------------------------------------
/images/architecture-overview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simplesteph/ecs-refarch-cloudformation/HEAD/images/architecture-overview.png
--------------------------------------------------------------------------------
/images/cloudformation-launch-stack.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simplesteph/ecs-refarch-cloudformation/HEAD/images/cloudformation-launch-stack.png
--------------------------------------------------------------------------------
/images/architecture-overview.graffle/data.plist:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simplesteph/ecs-refarch-cloudformation/HEAD/images/architecture-overview.graffle/data.plist
--------------------------------------------------------------------------------
/images/architecture-overview.graffle/image6.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simplesteph/ecs-refarch-cloudformation/HEAD/images/architecture-overview.graffle/image6.pdf
--------------------------------------------------------------------------------
/images/architecture-overview.graffle/image8.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simplesteph/ecs-refarch-cloudformation/HEAD/images/architecture-overview.graffle/image8.pdf
--------------------------------------------------------------------------------
/images/architecture-overview.graffle/image9.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simplesteph/ecs-refarch-cloudformation/HEAD/images/architecture-overview.graffle/image9.pdf
--------------------------------------------------------------------------------
/images/architecture-overview.graffle/image12.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simplesteph/ecs-refarch-cloudformation/HEAD/images/architecture-overview.graffle/image12.pdf
--------------------------------------------------------------------------------
/images/architecture-overview.graffle/image14.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simplesteph/ecs-refarch-cloudformation/HEAD/images/architecture-overview.graffle/image14.pdf
--------------------------------------------------------------------------------
/services/product-service/src/Dockerfile:
--------------------------------------------------------------------------------
1 | # Start from a small base
2 | FROM scratch
3 |
4 | # Our application requires no privileges
5 | # so run it with a non-root user
6 | ADD users /etc/passwd
7 | USER nobody
8 |
9 | # Our application runs on port 8001
10 | # so allow hosts to bind to that port
11 | EXPOSE 8001
12 |
13 | # Add our application binary
14 | ADD app /app
15 |
16 | # Run our application!
17 | ENTRYPOINT [ "/app" ]
18 |
--------------------------------------------------------------------------------
/services/website-service/src/Dockerfile:
--------------------------------------------------------------------------------
1 | # Start from a small base
2 | FROM scratch
3 |
4 | # Our application requires no privileges
5 | # so run it with a non-root user
6 | ADD users /etc/passwd
7 | USER nobody
8 |
9 | # Our application runs on port 8001
10 | # so allow hosts to bind to that port
11 | EXPOSE 8000
12 |
13 | # Add our application binary
14 | ADD app /app
15 |
16 | # Run our application!
17 | ENTRYPOINT [ "/app" ]
18 |
--------------------------------------------------------------------------------
/services/product-service/src/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "github.com/gin-gonic/gin"
4 |
5 | type product struct {
6 | ID string
7 | Title string
8 | Description string
9 | Price float64
10 | }
11 |
12 | func main() {
13 |
14 | router := gin.Default()
15 |
16 | // Respond to GET requests
17 | router.GET("/products", func(c *gin.Context) {
18 |
19 | products := []product{
20 | product{
21 | ID: "0000-0000-0001",
22 | Title: "Fork Handles",
23 | Description: "Got forks? Worn out ones? You need our all new Fork Handles",
24 | Price: 6.95,
25 | },
26 | product{
27 | ID: "0000-0000-0002",
28 | Title: "Four Candles",
29 | Description: "One candle never enough? You need our new Four Candles bundle",
30 | Price: 3.75,
31 | },
32 | product{
33 | ID: "0000-0000-0003",
34 | Title: "Egg Basket",
35 | Description: "Holds 6 unbroken eggs or 36 broken ones",
36 | Price: 9.99,
37 | },
38 | }
39 |
40 | c.IndentedJSON(200, products)
41 |
42 | })
43 |
44 | // Serve all of the things..
45 | router.Run(":8001")
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/services/product-service/src/Makefile:
--------------------------------------------------------------------------------
1 | all: run
2 |
3 | # This makefile contains some convenience commands for deploying and publishing.
4 |
5 | # For example, to build and run the docker container locally, just run:
6 | # $ make
7 |
8 | # or to publish the :latest version to the specified registry as :1.0.0, run:
9 | # $ make publish version=1.0.0
10 |
11 | name = ecs-refarch-cloudformation/product-service
12 | registry = 275396840892.dkr.ecr.us-east-1.amazonaws.com
13 | version ?= latest
14 |
15 | binary:
16 | $(call blue, "Building Linux binary ready for containerisation...")
17 | docker run --rm -it -v "${GOPATH}":/gopath -v "$(CURDIR)":/app -e "GOPATH=/gopath" -w /app golang:1.7 sh -c 'CGO_ENABLED=0 go build -a --installsuffix cgo --ldflags="-s" -o app'
18 |
19 | image: binary
20 | $(call blue, "Building docker image...")
21 | docker build -t ${name}:${version} .
22 | $(MAKE) clean
23 |
24 | run: image
25 | $(call blue, "Running Docker image locally...")
26 | docker run -i -t --rm -p 8001:8001 ${name}:${version}
27 |
28 | publish:
29 | $(call blue, "Publishing Docker image to registry...")
30 | docker tag ${name}:latest ${registry}/${name}:${version}
31 | docker push ${registry}/${name}:${version}
32 |
33 | clean:
34 | @rm -f app
35 |
36 | define blue
37 | @tput setaf 6
38 | @echo $1
39 | @tput sgr0
40 | endef
41 |
--------------------------------------------------------------------------------
/services/website-service/src/Makefile:
--------------------------------------------------------------------------------
1 | all: run
2 |
3 | # This makefile contains some convenience commands for deploying and publishing.
4 |
5 | # For example, to build and run the docker container locally, just run:
6 | # $ make
7 |
8 | # or to publish the :latest version to the specified registry as :1.0.0, run:
9 | # $ make publish version=1.0.0
10 |
11 | name = ecs-refarch-cloudformation/website-service
12 | registry = 275396840892.dkr.ecr.us-east-1.amazonaws.com
13 | version ?= latest
14 |
15 | binary:
16 | $(call blue, "Building Linux binary ready for containerisation...")
17 | docker run --rm -it -v "${GOPATH}":/gopath -v "$(CURDIR)":/app -e "GOPATH=/gopath" -w /app golang:1.7 sh -c 'CGO_ENABLED=0 go build -a --installsuffix cgo --ldflags="-s" -o app'
18 |
19 | image: binary
20 | $(call blue, "Building docker image...")
21 | docker build -t ${name}:${version} .
22 | $(MAKE) clean
23 |
24 | run: image
25 | $(call blue, "Running Docker image locally...")
26 | docker run -i -t --rm -p 8000:8000 ${name}:${version}
27 |
28 | publish:
29 | $(call blue, "Publishing Docker image to registry...")
30 | docker tag ${name}:latest ${registry}/${name}:${version}
31 | docker push ${registry}/${name}:${version}
32 |
33 | clean:
34 | @rm -f app
35 |
36 | define blue
37 | @tput setaf 6
38 | @echo $1
39 | @tput sgr0
40 | endef
41 |
--------------------------------------------------------------------------------
/services/website-service/src/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "html/template"
6 | "log"
7 | "net/http"
8 | "os"
9 |
10 | "github.com/gin-gonic/gin"
11 | )
12 |
13 | const VERSION string = "1.0.0"
14 |
15 | type Product struct {
16 | ID string
17 | Title string
18 | Description string
19 | Price float64
20 | }
21 |
22 | func main() {
23 |
24 | // Products template
25 | html := `
26 |
27 |
28 | Product Listing
29 |
30 |
31 | Product Listing
32 | {{range .}}
33 | {{.Title}}
34 | ID: {{.ID}}
35 | Description: {{.Description}}
36 | Price: {{.Price}}
37 | {{end}}
38 |
39 |
40 | `
41 | tmpl, err := template.New("product-listing").Parse(html)
42 | if err != nil {
43 | log.Fatalf("Error parsing product listing template: %s", err)
44 | }
45 |
46 | router := gin.Default()
47 | router.SetHTMLTemplate(tmpl)
48 |
49 | // Router handlers
50 | router.GET("/", func(c *gin.Context) {
51 |
52 | product := os.Getenv("PRODUCT_SERVICE_URL")
53 | resp, err := http.Get("http://" + product)
54 | if err != nil {
55 | c.IndentedJSON(500, gin.H{
56 | "status": "error",
57 | "message": "Could not connect to product service",
58 | "detailed": err.Error(),
59 | })
60 | return
61 | }
62 |
63 | defer resp.Body.Close()
64 |
65 | var products []Product
66 | json.NewDecoder(resp.Body).Decode(&products)
67 | c.HTML(200, "product-listing", products)
68 |
69 | })
70 |
71 | // Lets go...
72 | router.Run(":8000")
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/infrastructure/security-groups.yaml:
--------------------------------------------------------------------------------
1 | Description: >
2 | This template contains the security groups required by our entire stack.
3 | We create them in a seperate nested template, so they can be referenced
4 | by all of the other nested templates.
5 |
6 | Parameters:
7 |
8 | EnvironmentName:
9 | Description: An environment name that will be prefixed to resource names
10 | Type: String
11 |
12 | VPC:
13 | Type: AWS::EC2::VPC::Id
14 | Description: Choose which VPC the security groups should be deployed to
15 |
16 | Resources:
17 |
18 | # This security group defines who/where is allowed to access the ECS hosts directly.
19 | # By default we're just allowing access from the load balancer. If you want to SSH
20 | # into the hosts, or expose non-load balanced services you can open their ports here.
21 | ECSHostSecurityGroup:
22 | Type: AWS::EC2::SecurityGroup
23 | Properties:
24 | VpcId: !Ref VPC
25 | GroupDescription: Access to the ECS hosts and the tasks/containers that run on them
26 | SecurityGroupIngress:
27 | # Only allow inbound access to ECS from the ELB
28 | - SourceSecurityGroupId: !Ref LoadBalancerSecurityGroup
29 | IpProtocol: -1
30 | Tags:
31 | - Key: Name
32 | Value: !Sub ${EnvironmentName}-ECS-Hosts
33 |
34 | # This security group defines who/where is allowed to access the Application Load Balancer.
35 | # By default, we've opened this up to the public internet (0.0.0.0/0) but can you restrict
36 | # it further if you want.
37 | LoadBalancerSecurityGroup:
38 | Type: AWS::EC2::SecurityGroup
39 | Properties:
40 | VpcId: !Ref VPC
41 | GroupDescription: Access to the load balancer that sits in front of ECS
42 | SecurityGroupIngress:
43 | # Allow access from anywhere to our ECS services
44 | - CidrIp: 0.0.0.0/0
45 | IpProtocol: -1
46 | Tags:
47 | - Key: Name
48 | Value: !Sub ${EnvironmentName}-LoadBalancers
49 |
50 | Outputs:
51 |
52 | ECSHostSecurityGroup:
53 | Description: A reference to the security group for ECS hosts
54 | Value: !Ref ECSHostSecurityGroup
55 |
56 | LoadBalancerSecurityGroup:
57 | Description: A reference to the security group for load balancers
58 | Value: !Ref LoadBalancerSecurityGroup
59 |
60 |
--------------------------------------------------------------------------------
/infrastructure/load-balancers.yaml:
--------------------------------------------------------------------------------
1 | Description: >
2 | This template deploys an Application Load Balancer that exposes our various ECS services.
3 | We create them it a seperate nested template, so it can be referenced by all of the other nested templates.
4 |
5 | Parameters:
6 |
7 | EnvironmentName:
8 | Description: An environment name that will be prefixed to resource names
9 | Type: String
10 |
11 | VPC:
12 | Type: AWS::EC2::VPC::Id
13 | Description: Choose which VPC the Applicaion Load Balancer should be deployed to
14 |
15 | Subnets:
16 | Description: Choose which subnets the Applicaion Load Balancer should be deployed to
17 | Type: List
18 |
19 | SecurityGroup:
20 | Description: Select the Security Group to apply to the Applicaion Load Balancer
21 | Type: AWS::EC2::SecurityGroup::Id
22 |
23 | Resources:
24 |
25 | LoadBalancer:
26 | Type: AWS::ElasticLoadBalancingV2::LoadBalancer
27 | Properties:
28 | Name: !Ref EnvironmentName
29 | Subnets: !Ref Subnets
30 | SecurityGroups:
31 | - !Ref SecurityGroup
32 | Tags:
33 | - Key: Name
34 | Value: !Ref EnvironmentName
35 |
36 | LoadBalancerListener:
37 | Type: AWS::ElasticLoadBalancingV2::Listener
38 | Properties:
39 | LoadBalancerArn: !Ref LoadBalancer
40 | Port: 80
41 | Protocol: HTTP
42 | DefaultActions:
43 | - Type: forward
44 | TargetGroupArn: !Ref DefaultTargetGroup
45 |
46 | # We define a default target group here, as this is a mandatory Parameters
47 | # when creating an Application Load Balancer Listener. This is not used, instead
48 | # a target group is created per-service in each service template (../services/*)
49 | DefaultTargetGroup:
50 | Type: AWS::ElasticLoadBalancingV2::TargetGroup
51 | Properties:
52 | Name: !Sub ${EnvironmentName}-default
53 | VpcId: !Ref VPC
54 | Port: 80
55 | Protocol: HTTP
56 |
57 | Outputs:
58 |
59 | LoadBalancer:
60 | Description: A reference to the Application Load Balancer
61 | Value: !Ref LoadBalancer
62 |
63 | LoadBalancerUrl:
64 | Description: The URL of the ALB
65 | Value: !GetAtt LoadBalancer.DNSName
66 |
67 | Listener:
68 | Description: A reference to a port 80 listener
69 | Value: !Ref LoadBalancerListener
70 |
71 |
72 |
--------------------------------------------------------------------------------
/master.yaml:
--------------------------------------------------------------------------------
1 | Description: >
2 |
3 | This template deploys a VPC, with a pair of public and private subnets spread
4 | across two Availabilty Zones. It deploys an Internet Gateway, with a default
5 | route on the public subnets. It deploys a pair of NAT Gateways (one in each AZ),
6 | and default routes for them in the private subnets.
7 |
8 | It then deploys a highly available ECS cluster using an AutoScaling Group, with
9 | ECS hosts distributed across multiple Availability Zones.
10 |
11 | Finally, it deploys a pair of example ECS services from containers published in
12 | Amazon EC2 Container Registry (Amazon ECR).
13 |
14 | Last Modified: 22nd September 2016
15 | Author: Paul Maddox
16 |
17 | Resources:
18 |
19 | VPC:
20 | Type: AWS::CloudFormation::Stack
21 | Properties:
22 | TemplateURL: https://s3.amazonaws.com/ecs-refarch-cloudformation/infrastructure/vpc.yaml
23 | Parameters:
24 | EnvironmentName: !Ref AWS::StackName
25 | VpcCIDR: 10.180.0.0/16
26 | PublicSubnet1CIDR: 10.180.8.0/21
27 | PublicSubnet2CIDR: 10.180.16.0/21
28 | PrivateSubnet1CIDR: 10.180.24.0/21
29 | PrivateSubnet2CIDR: 10.180.32.0/21
30 |
31 | SecurityGroups:
32 | Type: AWS::CloudFormation::Stack
33 | Properties:
34 | TemplateURL: https://s3.amazonaws.com/ecs-refarch-cloudformation/infrastructure/security-groups.yaml
35 | Parameters:
36 | EnvironmentName: !Ref AWS::StackName
37 | VPC: !GetAtt VPC.Outputs.VPC
38 |
39 | ALB:
40 | Type: AWS::CloudFormation::Stack
41 | Properties:
42 | TemplateURL: https://s3.amazonaws.com/ecs-refarch-cloudformation/infrastructure/load-balancers.yaml
43 | Parameters:
44 | EnvironmentName: !Ref AWS::StackName
45 | VPC: !GetAtt VPC.Outputs.VPC
46 | Subnets: !GetAtt VPC.Outputs.PublicSubnets
47 | SecurityGroup: !GetAtt SecurityGroups.Outputs.LoadBalancerSecurityGroup
48 |
49 | ECS:
50 | Type: AWS::CloudFormation::Stack
51 | Properties:
52 | TemplateURL: https://s3.amazonaws.com/ecs-refarch-cloudformation/infrastructure/ecs-cluster.yaml
53 | Parameters:
54 | EnvironmentName: !Ref AWS::StackName
55 | InstanceType: t2.large
56 | ClusterSize: 4
57 | VPC: !GetAtt VPC.Outputs.VPC
58 | SecurityGroup: !GetAtt SecurityGroups.Outputs.ECSHostSecurityGroup
59 | Subnets: !GetAtt VPC.Outputs.PrivateSubnets
60 |
61 | ProductService:
62 | Type: AWS::CloudFormation::Stack
63 | Properties:
64 | TemplateURL: https://s3.amazonaws.com/ecs-refarch-cloudformation/services/product-service/service.yaml
65 | Parameters:
66 | VPC: !GetAtt VPC.Outputs.VPC
67 | Cluster: !GetAtt ECS.Outputs.Cluster
68 | DesiredCount: 2
69 | Listener: !GetAtt ALB.Outputs.Listener
70 | Path: /products
71 |
72 | WebsiteService:
73 | Type: AWS::CloudFormation::Stack
74 | Properties:
75 | TemplateURL: https://s3.amazonaws.com/ecs-refarch-cloudformation/services/website-service/service.yaml
76 | Parameters:
77 | VPC: !GetAtt VPC.Outputs.VPC
78 | Cluster: !GetAtt ECS.Outputs.Cluster
79 | DesiredCount: 2
80 | ProductServiceUrl: !Join [ "/", [ !GetAtt ALB.Outputs.LoadBalancerUrl, "products" ]]
81 | Listener: !GetAtt ALB.Outputs.Listener
82 | Path: /
83 |
84 |
85 | Outputs:
86 |
87 | ProductServiceUrl:
88 | Description: The URL endpoint for the product service
89 | Value: !Join [ "/", [ !GetAtt ALB.Outputs.LoadBalancerUrl, "products" ]]
90 |
91 | WebsiteServiceUrl:
92 | Description: The URL endpoint for the website service
93 | Value: !Join ["", [ !GetAtt ALB.Outputs.LoadBalancerUrl, "/" ]]
94 |
--------------------------------------------------------------------------------
/services/product-service/service.yaml:
--------------------------------------------------------------------------------
1 | Description: >
2 | This is an example of a long running ECS service that serves a JSON API of products.
3 |
4 | Parameters:
5 |
6 | VPC:
7 | Description: The VPC that the ECS cluster is deployed to
8 | Type: AWS::EC2::VPC::Id
9 |
10 | Cluster:
11 | Description: Please provide the ECS Cluster ID that this service should run on
12 | Type: String
13 |
14 | DesiredCount:
15 | Description: How many instances of this task should we run across our cluster?
16 | Type: Number
17 | Default: 2
18 |
19 | Listener:
20 | Description: The Application Load Balancer listener to register with
21 | Type: String
22 |
23 | Path:
24 | Description: The path to register with the Application Load Balancer
25 | Type: String
26 | Default: /products
27 |
28 | Resources:
29 |
30 | Service:
31 | Type: AWS::ECS::Service
32 | DependsOn: ListenerRule
33 | Properties:
34 | Cluster: !Ref Cluster
35 | Role: !Ref ServiceRole
36 | DesiredCount: !Ref DesiredCount
37 | TaskDefinition: !Ref TaskDefinition
38 | LoadBalancers:
39 | - ContainerName: "product-service"
40 | ContainerPort: 8001
41 | TargetGroupArn: !Ref TargetGroup
42 |
43 | TaskDefinition:
44 | Type: AWS::ECS::TaskDefinition
45 | Properties:
46 | Family: product-service
47 | ContainerDefinitions:
48 | - Name: product-service
49 | Essential: true
50 | Image: 275396840892.dkr.ecr.us-east-1.amazonaws.com/ecs-refarch-cloudformation/product-service:1.0.0
51 | Memory: 128
52 | PortMappings:
53 | - ContainerPort: 8001
54 | LogConfiguration:
55 | LogDriver: awslogs
56 | Options:
57 | awslogs-group: !Ref AWS::StackName
58 | awslogs-region: !Ref AWS::Region
59 |
60 | CloudWatchLogsGroup:
61 | Type: AWS::Logs::LogGroup
62 | Properties:
63 | LogGroupName: !Ref AWS::StackName
64 | RetentionInDays: 365
65 |
66 | TargetGroup:
67 | Type: AWS::ElasticLoadBalancingV2::TargetGroup
68 | Properties:
69 | VpcId: !Ref VPC
70 | Port: 80
71 | Protocol: HTTP
72 | Matcher:
73 | HttpCode: 200-299
74 | HealthCheckIntervalSeconds: 10
75 | HealthCheckPath: /products
76 | HealthCheckProtocol: HTTP
77 | HealthCheckTimeoutSeconds: 5
78 | HealthyThresholdCount: 2
79 |
80 | ListenerRule:
81 | Type: AWS::ElasticLoadBalancingV2::ListenerRule
82 | Properties:
83 | ListenerArn: !Ref Listener
84 | Priority: 2
85 | Conditions:
86 | - Field: path-pattern
87 | Values:
88 | - !Ref Path
89 | Actions:
90 | - TargetGroupArn: !Ref TargetGroup
91 | Type: forward
92 |
93 | # This IAM Role grants the service access to register/unregister with the
94 | # Application Load Balancer (ALB). It is based on the default documented here:
95 | # http://docs.aws.amazon.com/AmazonECS/latest/developerguide/service_IAM_role.html
96 | ServiceRole:
97 | Type: AWS::IAM::Role
98 | Properties:
99 | RoleName: !Sub ecs-service-${AWS::StackName}
100 | Path: /
101 | AssumeRolePolicyDocument: |
102 | {
103 | "Statement": [{
104 | "Effect": "Allow",
105 | "Principal": { "Service": [ "ecs.amazonaws.com" ]},
106 | "Action": [ "sts:AssumeRole" ]
107 | }]
108 | }
109 | Policies:
110 | - PolicyName: !Sub ecs-service-${AWS::StackName}
111 | PolicyDocument:
112 | {
113 | "Version": "2012-10-17",
114 | "Statement": [{
115 | "Effect": "Allow",
116 | "Action": [
117 | "ec2:AuthorizeSecurityGroupIngress",
118 | "ec2:Describe*",
119 | "elasticloadbalancing:DeregisterInstancesFromLoadBalancer",
120 | "elasticloadbalancing:Describe*",
121 | "elasticloadbalancing:RegisterInstancesWithLoadBalancer",
122 | "elasticloadbalancing:DeregisterTargets",
123 | "elasticloadbalancing:DescribeTargetGroups",
124 | "elasticloadbalancing:DescribeTargetHealth",
125 | "elasticloadbalancing:RegisterTargets"
126 | ],
127 | "Resource": "*"
128 | }]
129 | }
130 |
--------------------------------------------------------------------------------
/services/website-service/service.yaml:
--------------------------------------------------------------------------------
1 | Description: >
2 | This is an example of a long running ECS service that needs to connect to another ECS service (product-service)
3 | via it's load balancer. We use environment variables to pass the URL of the product-service to this one's container(s).
4 |
5 | Parameters:
6 |
7 | VPC:
8 | Description: The VPC that the ECS cluster is deployed to
9 | Type: AWS::EC2::VPC::Id
10 |
11 | Cluster:
12 | Description: Please provide the ECS Cluster ID that this service should run on
13 | Type: String
14 |
15 | DesiredCount:
16 | Description: How many instances of this task should we run across our cluster?
17 | Type: Number
18 | Default: 2
19 |
20 | ProductServiceUrl:
21 | Description: The URL of the Product Service (used to fetch product information)
22 | Type: String
23 |
24 | Listener:
25 | Description: The Application Load Balancer listener to register with
26 | Type: String
27 |
28 | Path:
29 | Description: The path to register with the Application Load Balancer
30 | Type: String
31 | Default: /
32 |
33 | Resources:
34 |
35 | Service:
36 | Type: AWS::ECS::Service
37 | DependsOn: ListenerRule
38 | Properties:
39 | Cluster: !Ref Cluster
40 | Role: !Ref ServiceRole
41 | DesiredCount: !Ref DesiredCount
42 | TaskDefinition: !Ref TaskDefinition
43 | LoadBalancers:
44 | - ContainerName: "website-service"
45 | ContainerPort: 8000
46 | TargetGroupArn: !Ref TargetGroup
47 |
48 | TaskDefinition:
49 | Type: AWS::ECS::TaskDefinition
50 | Properties:
51 | Family: website-service
52 | ContainerDefinitions:
53 | - Name: website-service
54 | Essential: true
55 | Image: 275396840892.dkr.ecr.us-east-1.amazonaws.com/ecs-refarch-cloudformation/website-service:1.0.0
56 | Memory: 128
57 | Environment:
58 | - Name: PRODUCT_SERVICE_URL
59 | Value: !Ref ProductServiceUrl
60 | PortMappings:
61 | - ContainerPort: 8000
62 | LogConfiguration:
63 | LogDriver: awslogs
64 | Options:
65 | awslogs-group: !Ref AWS::StackName
66 | awslogs-region: !Ref AWS::Region
67 |
68 | CloudWatchLogsGroup:
69 | Type: AWS::Logs::LogGroup
70 | Properties:
71 | LogGroupName: !Ref AWS::StackName
72 | RetentionInDays: 365
73 |
74 | TargetGroup:
75 | Type: AWS::ElasticLoadBalancingV2::TargetGroup
76 | Properties:
77 | VpcId: !Ref VPC
78 | Port: 80
79 | Protocol: HTTP
80 | Matcher:
81 | HttpCode: 200-299
82 | HealthCheckIntervalSeconds: 10
83 | HealthCheckPath: /
84 | HealthCheckProtocol: HTTP
85 | HealthCheckTimeoutSeconds: 5
86 | HealthyThresholdCount: 2
87 |
88 | ListenerRule:
89 | Type: AWS::ElasticLoadBalancingV2::ListenerRule
90 | Properties:
91 | ListenerArn: !Ref Listener
92 | Priority: 1
93 | Conditions:
94 | - Field: path-pattern
95 | Values:
96 | - !Ref Path
97 | Actions:
98 | - TargetGroupArn: !Ref TargetGroup
99 | Type: forward
100 |
101 | # This IAM Role grants the service access to register/unregister with the
102 | # Application Load Balancer (ALB). It is based on the default documented here:
103 | # http://docs.aws.amazon.com/AmazonECS/latest/developerguide/service_IAM_role.html
104 | ServiceRole:
105 | Type: AWS::IAM::Role
106 | Properties:
107 | RoleName: !Sub ecs-service-${AWS::StackName}
108 | Path: /
109 | AssumeRolePolicyDocument: |
110 | {
111 | "Statement": [{
112 | "Effect": "Allow",
113 | "Principal": { "Service": [ "ecs.amazonaws.com" ]},
114 | "Action": [ "sts:AssumeRole" ]
115 | }]
116 | }
117 | Policies:
118 | - PolicyName: !Sub ecs-service-${AWS::StackName}
119 | PolicyDocument:
120 | {
121 | "Version": "2012-10-17",
122 | "Statement": [{
123 | "Effect": "Allow",
124 | "Action": [
125 | "ec2:AuthorizeSecurityGroupIngress",
126 | "ec2:Describe*",
127 | "elasticloadbalancing:DeregisterInstancesFromLoadBalancer",
128 | "elasticloadbalancing:Describe*",
129 | "elasticloadbalancing:RegisterInstancesWithLoadBalancer",
130 | "elasticloadbalancing:DeregisterTargets",
131 | "elasticloadbalancing:DescribeTargetGroups",
132 | "elasticloadbalancing:DescribeTargetHealth",
133 | "elasticloadbalancing:RegisterTargets"
134 | ],
135 | "Resource": "*"
136 | }]
137 | }
138 |
139 |
--------------------------------------------------------------------------------
/infrastructure/vpc.yaml:
--------------------------------------------------------------------------------
1 | Description: >
2 | This template deploys a VPC, with a pair of public and private subnets spread
3 | across two Availabilty Zones. It deploys an Internet Gateway, with a default
4 | route on the public subnets. It deploys a pair of NAT Gateways (one in each AZ),
5 | and default routes for them in the private subnets.
6 |
7 | Parameters:
8 |
9 | EnvironmentName:
10 | Description: An environment name that will be prefixed to resource names
11 | Type: String
12 |
13 | VpcCIDR:
14 | Description: Please enter the IP range (CIDR notation) for this VPC
15 | Type: String
16 | Default: 10.192.0.0/16
17 |
18 | PublicSubnet1CIDR:
19 | Description: Please enter the IP range (CIDR notation) for the public subnet in the first Availability Zone
20 | Type: String
21 | Default: 10.192.10.0/24
22 |
23 | PublicSubnet2CIDR:
24 | Description: Please enter the IP range (CIDR notation) for the public subnet in the second Availability Zone
25 | Type: String
26 | Default: 10.192.11.0/24
27 |
28 | PrivateSubnet1CIDR:
29 | Description: Please enter the IP range (CIDR notation) for the private subnet in the first Availability Zone
30 | Type: String
31 | Default: 10.192.20.0/24
32 |
33 | PrivateSubnet2CIDR:
34 | Description: Please enter the IP range (CIDR notation) for the private subnet in the second Availability Zone
35 | Type: String
36 | Default: 10.192.21.0/24
37 |
38 | Resources:
39 |
40 | VPC:
41 | Type: AWS::EC2::VPC
42 | Properties:
43 | CidrBlock: !Ref VpcCIDR
44 | Tags:
45 | - Key: Name
46 | Value: !Ref EnvironmentName
47 |
48 | InternetGateway:
49 | Type: AWS::EC2::InternetGateway
50 | Properties:
51 | Tags:
52 | - Key: Name
53 | Value: !Ref EnvironmentName
54 |
55 | InternetGatewayAttachment:
56 | Type: AWS::EC2::VPCGatewayAttachment
57 | Properties:
58 | InternetGatewayId: !Ref InternetGateway
59 | VpcId: !Ref VPC
60 |
61 | PublicSubnet1:
62 | Type: AWS::EC2::Subnet
63 | Properties:
64 | VpcId: !Ref VPC
65 | AvailabilityZone: !Select [ 0, !GetAZs ]
66 | CidrBlock: !Ref PublicSubnet1CIDR
67 | MapPublicIpOnLaunch: true
68 | Tags:
69 | - Key: Name
70 | Value: !Sub ${EnvironmentName} Public Subnet (AZ1)
71 |
72 | PublicSubnet2:
73 | Type: AWS::EC2::Subnet
74 | Properties:
75 | VpcId: !Ref VPC
76 | AvailabilityZone: !Select [ 1, !GetAZs ]
77 | CidrBlock: !Ref PublicSubnet2CIDR
78 | MapPublicIpOnLaunch: true
79 | Tags:
80 | - Key: Name
81 | Value: !Sub ${EnvironmentName} Public Subnet (AZ2)
82 |
83 | PrivateSubnet1:
84 | Type: AWS::EC2::Subnet
85 | Properties:
86 | VpcId: !Ref VPC
87 | AvailabilityZone: !Select [ 0, !GetAZs ]
88 | CidrBlock: !Ref PrivateSubnet1CIDR
89 | MapPublicIpOnLaunch: false
90 | Tags:
91 | - Key: Name
92 | Value: !Sub ${EnvironmentName} Private Subnet (AZ1)
93 |
94 | PrivateSubnet2:
95 | Type: AWS::EC2::Subnet
96 | Properties:
97 | VpcId: !Ref VPC
98 | AvailabilityZone: !Select [ 1, !GetAZs ]
99 | CidrBlock: !Ref PrivateSubnet2CIDR
100 | MapPublicIpOnLaunch: false
101 | Tags:
102 | - Key: Name
103 | Value: !Sub ${EnvironmentName} Private Subnet (AZ2)
104 |
105 | NatGateway1EIP:
106 | Type: AWS::EC2::EIP
107 | DependsOn: InternetGatewayAttachment
108 | Properties:
109 | Domain: vpc
110 |
111 | NatGateway2EIP:
112 | Type: AWS::EC2::EIP
113 | DependsOn: InternetGatewayAttachment
114 | Properties:
115 | Domain: vpc
116 |
117 | NatGateway1:
118 | Type: AWS::EC2::NatGateway
119 | Properties:
120 | AllocationId: !GetAtt NatGateway1EIP.AllocationId
121 | SubnetId: !Ref PublicSubnet1
122 |
123 | NatGateway2:
124 | Type: AWS::EC2::NatGateway
125 | Properties:
126 | AllocationId: !GetAtt NatGateway2EIP.AllocationId
127 | SubnetId: !Ref PublicSubnet2
128 |
129 | PublicRouteTable:
130 | Type: AWS::EC2::RouteTable
131 | Properties:
132 | VpcId: !Ref VPC
133 | Tags:
134 | - Key: Name
135 | Value: !Sub ${EnvironmentName} Public Routes
136 |
137 | DefaultPublicRoute:
138 | Type: AWS::EC2::Route
139 | DependsOn: InternetGatewayAttachment
140 | Properties:
141 | RouteTableId: !Ref PublicRouteTable
142 | DestinationCidrBlock: 0.0.0.0/0
143 | GatewayId: !Ref InternetGateway
144 |
145 | PublicSubnet1RouteTableAssociation:
146 | Type: AWS::EC2::SubnetRouteTableAssociation
147 | Properties:
148 | RouteTableId: !Ref PublicRouteTable
149 | SubnetId: !Ref PublicSubnet1
150 |
151 | PublicSubnet2RouteTableAssociation:
152 | Type: AWS::EC2::SubnetRouteTableAssociation
153 | Properties:
154 | RouteTableId: !Ref PublicRouteTable
155 | SubnetId: !Ref PublicSubnet2
156 |
157 |
158 | PrivateRouteTable1:
159 | Type: AWS::EC2::RouteTable
160 | Properties:
161 | VpcId: !Ref VPC
162 | Tags:
163 | - Key: Name
164 | Value: !Sub ${EnvironmentName} Private Routes (AZ1)
165 |
166 | DefaultPrivateRoute1:
167 | Type: AWS::EC2::Route
168 | Properties:
169 | RouteTableId: !Ref PrivateRouteTable1
170 | DestinationCidrBlock: 0.0.0.0/0
171 | NatGatewayId: !Ref NatGateway1
172 |
173 | PrivateSubnet1RouteTableAssociation:
174 | Type: AWS::EC2::SubnetRouteTableAssociation
175 | Properties:
176 | RouteTableId: !Ref PrivateRouteTable1
177 | SubnetId: !Ref PrivateSubnet1
178 |
179 | PrivateRouteTable2:
180 | Type: AWS::EC2::RouteTable
181 | Properties:
182 | VpcId: !Ref VPC
183 | Tags:
184 | - Key: Name
185 | Value: !Sub ${EnvironmentName} Private Routes (AZ2)
186 |
187 | DefaultPrivateRoute2:
188 | Type: AWS::EC2::Route
189 | Properties:
190 | RouteTableId: !Ref PrivateRouteTable2
191 | DestinationCidrBlock: 0.0.0.0/0
192 | NatGatewayId: !Ref NatGateway2
193 |
194 | PrivateSubnet2RouteTableAssociation:
195 | Type: AWS::EC2::SubnetRouteTableAssociation
196 | Properties:
197 | RouteTableId: !Ref PrivateRouteTable2
198 | SubnetId: !Ref PrivateSubnet2
199 |
200 | Outputs:
201 |
202 | VPC:
203 | Description: A reference to the created VPC
204 | Value: !Ref VPC
205 |
206 | PublicSubnets:
207 | Description: A list of the public subnets
208 | Value: !Join [ ",", [ !Ref PublicSubnet1, !Ref PublicSubnet2 ]]
209 |
210 | PrivateSubnets:
211 | Description: A list of the private subnets
212 | Value: !Join [ ",", [ !Ref PrivateSubnet1, !Ref PrivateSubnet2 ]]
213 |
214 | PublicSubnet1:
215 | Description: A reference to the public subnet in the 1st Availability Zone
216 | Value: !Ref PublicSubnet1
217 |
218 | PublicSubnet2:
219 | Description: A reference to the public subnet in the 2nd Availability Zone
220 | Value: !Ref PublicSubnet2
221 |
222 | PrivateSubnet1:
223 | Description: A reference to the private subnet in the 1st Availability Zone
224 | Value: !Ref PrivateSubnet1
225 |
226 | PrivateSubnet2:
227 | Description: A reference to the private subnet in the 2nd Availability Zone
228 | Value: !Ref PrivateSubnet2
229 |
--------------------------------------------------------------------------------
/infrastructure/ecs-cluster.yaml:
--------------------------------------------------------------------------------
1 | Description: >
2 | This template deploys an ECS cluster to the provided VPC and subnets
3 | using an Auto Scaling Group
4 |
5 | Parameters:
6 |
7 | EnvironmentName:
8 | Description: An environment name that will be prefixed to resource names
9 | Type: String
10 |
11 | InstanceType:
12 | Description: Which instance type should we use to build the ECS cluster?
13 | Type: String
14 | Default: c4.large
15 |
16 | ClusterSize:
17 | Description: How many ECS hosts do you want to initially deploy?
18 | Type: Number
19 | Default: 4
20 |
21 | VPC:
22 | Description: Choose which VPC this ECS cluster should be deployed to
23 | Type: AWS::EC2::VPC::Id
24 |
25 | Subnets:
26 | Description: Choose which subnets this ECS cluster should be deployed to
27 | Type: List
28 |
29 | SecurityGroup:
30 | Description: Select the Security Group to use for the ECS cluster hosts
31 | Type: AWS::EC2::SecurityGroup::Id
32 |
33 | Mappings:
34 |
35 | # These are the latest ECS optimized AMIs as of February 2017:
36 | #
37 | # amzn-ami-2016.09.f-amazon-ecs-optimized
38 | # ECS agent: 1.14.0
39 | # Docker: 1.12.6
40 | # ecs-init: 1.14.0-2
41 | #
42 | # You can find the latest available on this page of our documentation:
43 | # http://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-optimized_AMI.html
44 | # (note the AMI identifier is region specific)
45 |
46 | AWSRegionToAMI:
47 | us-east-1:
48 | AMI: ami-b2df2ca4
49 | us-east-2:
50 | AMI: ami-832b0ee6
51 | us-west-1:
52 | AMI: ami-dd104dbd
53 | us-west-2:
54 | AMI: ami-022b9262
55 | eu-west-1:
56 | AMI: ami-a7f2acc1
57 | eu-west-2:
58 | AMI: ami-3fb6bc5b
59 | eu-central-1:
60 | AMI: ami-ec2be583
61 | ap-northeast-1:
62 | AMI: ami-c393d6a4
63 | ap-southeast-1:
64 | AMI: ami-a88530cb
65 | ap-southeast-2:
66 | AMI: ami-8af8ffe9
67 | ca-central-1:
68 | AMI: ami-ead5688e
69 |
70 | Resources:
71 |
72 | ECSCluster:
73 | Type: AWS::ECS::Cluster
74 | Properties:
75 | ClusterName: !Ref EnvironmentName
76 |
77 | ECSAutoScalingGroup:
78 | Type: AWS::AutoScaling::AutoScalingGroup
79 | Properties:
80 | VPCZoneIdentifier: !Ref Subnets
81 | LaunchConfigurationName: !Ref ECSLaunchConfiguration
82 | MinSize: !Ref ClusterSize
83 | MaxSize: !Ref ClusterSize
84 | DesiredCapacity: !Ref ClusterSize
85 | Tags:
86 | - Key: Name
87 | Value: !Sub ${EnvironmentName} ECS host
88 | PropagateAtLaunch: true
89 | CreationPolicy:
90 | ResourceSignal:
91 | Timeout: PT15M
92 | UpdatePolicy:
93 | AutoScalingRollingUpdate:
94 | MinInstancesInService: 1
95 | MaxBatchSize: 1
96 | PauseTime: PT15M
97 | WaitOnResourceSignals: true
98 |
99 | ECSLaunchConfiguration:
100 | Type: AWS::AutoScaling::LaunchConfiguration
101 | Properties:
102 | ImageId: !FindInMap [AWSRegionToAMI, !Ref "AWS::Region", AMI]
103 | InstanceType: !Ref InstanceType
104 | SecurityGroups:
105 | - !Ref SecurityGroup
106 | IamInstanceProfile: !Ref ECSInstanceProfile
107 | UserData:
108 | "Fn::Base64": !Sub |
109 | #!/bin/bash
110 | yum install -y aws-cfn-bootstrap
111 | /opt/aws/bin/cfn-init -v --region ${AWS::Region} --stack ${AWS::StackName} --resource ECSLaunchConfiguration
112 | /opt/aws/bin/cfn-signal -e $? --region ${AWS::Region} --stack ${AWS::StackName} --resource ECSAutoScalingGroup
113 |
114 | Metadata:
115 | AWS::CloudFormation::Init:
116 | config:
117 | commands:
118 | 01_add_instance_to_cluster:
119 | command: !Sub echo ECS_CLUSTER=${ECSCluster} >> /etc/ecs/ecs.config
120 | files:
121 | "/etc/cfn/cfn-hup.conf":
122 | mode: 000400
123 | owner: root
124 | group: root
125 | content: !Sub |
126 | [main]
127 | stack=${AWS::StackId}
128 | region=${AWS::Region}
129 |
130 | "/etc/cfn/hooks.d/cfn-auto-reloader.conf":
131 | content: !Sub |
132 | [cfn-auto-reloader-hook]
133 | triggers=post.update
134 | path=Resources.ECSLaunchConfiguration.Metadata.AWS::CloudFormation::Init
135 | action=/opt/aws/bin/cfn-init -v --region ${AWS::Region} --stack ${AWS::StackName} --resource ECSLaunchConfiguration
136 |
137 | services:
138 | sysvinit:
139 | cfn-hup:
140 | enabled: true
141 | ensureRunning: true
142 | files:
143 | - /etc/cfn/cfn-hup.conf
144 | - /etc/cfn/hooks.d/cfn-auto-reloader.conf
145 |
146 | # This IAM Role is attached to all of the ECS hosts. It is based on the default role
147 | # published here:
148 | # http://docs.aws.amazon.com/AmazonECS/latest/developerguide/instance_IAM_role.html
149 | #
150 | # You can add other IAM policy statements here to allow access from your ECS hosts
151 | # to other AWS services. Please note that this role will be used by ALL containers
152 | # running on the ECS host.
153 |
154 | ECSRole:
155 | Type: AWS::IAM::Role
156 | Properties:
157 | Path: /
158 | RoleName: !Sub ${EnvironmentName}-ECSRole-${AWS::Region}
159 | AssumeRolePolicyDocument: |
160 | {
161 | "Statement": [{
162 | "Action": "sts:AssumeRole",
163 | "Effect": "Allow",
164 | "Principal": {
165 | "Service": "ec2.amazonaws.com"
166 | }
167 | }]
168 | }
169 | Policies:
170 | - PolicyName: ecs-service
171 | PolicyDocument: |
172 | {
173 | "Statement": [{
174 | "Effect": "Allow",
175 | "Action": [
176 | "ecs:CreateCluster",
177 | "ecs:DeregisterContainerInstance",
178 | "ecs:DiscoverPollEndpoint",
179 | "ecs:Poll",
180 | "ecs:RegisterContainerInstance",
181 | "ecs:StartTelemetrySession",
182 | "ecs:Submit*",
183 | "logs:CreateLogStream",
184 | "logs:PutLogEvents",
185 | "ecr:BatchCheckLayerAvailability",
186 | "ecr:BatchGetImage",
187 | "ecr:GetDownloadUrlForLayer",
188 | "ecr:GetAuthorizationToken"
189 | ],
190 | "Resource": "*"
191 | }]
192 | }
193 |
194 | ECSInstanceProfile:
195 | Type: AWS::IAM::InstanceProfile
196 | Properties:
197 | Path: /
198 | Roles:
199 | - !Ref ECSRole
200 |
201 | Outputs:
202 |
203 | Cluster:
204 | Description: A reference to the ECS cluster
205 | Value: !Ref ECSCluster
206 |
207 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright 2016 Amazon Web Services, Inc.
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Deploying Microservices with Amazon ECS, AWS CloudFormation, and an Application Load Balancer
2 |
3 | This reference architecture provides a set of YAML templates for deploying microservices to [Amazon EC2 Container Service (Amazon ECS)](http://docs.aws.amazon.com/AmazonECS/latest/developerguide/Welcome.html) with [AWS CloudFormation](https://aws.amazon.com/cloudformation/).
4 |
5 | You can launch this CloudFormation stack in the US East (N. Virginia) Region in your account:
6 |
7 | [](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/new?stackName=Production&templateURL=https://s3.amazonaws.com/ecs-refarch-cloudformation/master.yaml)
8 |
9 | ## Overview
10 |
11 | 
12 |
13 | The repository consists of a set of nested templates that deploy the following:
14 |
15 | - A tiered [VPC](http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_Introduction.html) with public and private subnets, spanning an AWS region.
16 | - A highly available ECS cluster deployed across two [Availability Zones](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html) in an [Auto Scaling](https://aws.amazon.com/autoscaling/) group.
17 | - A pair of [NAT gateways](http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/vpc-nat-gateway.html) (one in each zone) to handle outbound traffic.
18 | - Two interconnecting microservices deployed as [ECS services](http://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs_services.html) (website-service and product-service).
19 | - An [Application Load Balancer (ALB)](https://aws.amazon.com/elasticloadbalancing/applicationloadbalancer/) to the public subnets to handle inbound traffic.
20 | - ALB path-based routes for each ECS service to route the inbound traffic to the correct service.
21 | - Centralized container logging with [Amazon CloudWatch Logs](http://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/WhatIsCloudWatchLogs.html).
22 |
23 | ## Why use AWS CloudFormation with Amazon ECS?
24 |
25 | Using CloudFormation to deploy and manage services with ECS has a number of nice benefits over more traditional methods ([AWS CLI](https://aws.amazon.com/cli), scripting, etc.).
26 |
27 | #### Infrastructure-as-Code
28 |
29 | A template can be used repeatedly to create identical copies of the same stack (or to use as a foundation to start a new stack). Templates are simple YAML- or JSON-formatted text files that can be placed under your normal source control mechanisms, stored in private or public locations such as Amazon S3, and exchanged via email. With CloudFormation, you can see exactly which AWS resources make up a stack. You retain full control and have the ability to modify any of the AWS resources created as part of a stack.
30 |
31 | #### Self-documenting
32 |
33 | Fed up with outdated documentation on your infrastructure or environments? Still keep manual documentation of IP ranges, security group rules, etc.?
34 |
35 | With CloudFormation, your template becomes your documentation. Want to see exactly what you have deployed? Just look at your template. If you keep it in source control, then you can also look back at exactly which changes were made and by whom.
36 |
37 | #### Intelligent updating & rollback
38 |
39 | CloudFormation not only handles the initial deployment of your infrastructure and environments, but it can also manage the whole lifecycle, including future updates. During updates, you have fine-grained control and visibility over how changes are applied, using functionality such as [change sets](https://aws.amazon.com/blogs/aws/new-change-sets-for-aws-cloudformation/), [rolling update policies](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-updatepolicy.html) and [stack policies](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/protect-stack-resources.html).
40 |
41 | ## Template details
42 |
43 | The templates below are included in this repository and reference architecture:
44 |
45 | | Template | Description |
46 | | --- | --- |
47 | | [master.yaml](master.yaml) | This is the master template - deploy it to CloudFormation and it includes all of the others automatically. |
48 | | [infrastructure/vpc.yaml](infrastructure/vpc.yaml) | This template deploys a VPC with a pair of public and private subnets spread across two Availability Zones. It deploys an [Internet gateway](http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_Internet_Gateway.html), with a default route on the public subnets. It deploys a pair of NAT gateways (one in each zone), and default routes for them in the private subnets. |
49 | | [infrastructure/security-groups.yaml](infrastructure/security-groups.yaml) | This template contains the [security groups](http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_SecurityGroups.html) required by the entire stack. They are created in a separate nested template, so that they can be referenced by all of the other nested templates. |
50 | | [infrastructure/load-balancers.yaml](infrastructure/load-balancers.yaml) | This template deploys an ALB to the public subnets, which exposes the various ECS services. It is created in in a separate nested template, so that it can be referenced by all of the other nested templates and so that the various ECS services can register with it. |
51 | | [infrastructure/ecs-cluster.yaml](infrastructure/ecs-cluster.yaml) | This template deploys an ECS cluster to the private subnets using an Auto Scaling group. |
52 | | [services/product-service/service.yaml](services/product-service/service.yaml) | This is an example of a long-running ECS service that serves a JSON API of products. For the full source for the service, see [services/product-service/src](services/product-service/src).|
53 | | [services/website-service/service.yaml](services/website-service/service.yaml) | This is an example of a long-running ECS service that needs to connect to another service (product-service) via the load-balanced URL. We use an environment variable to pass the product-service URL to the containers. For the full source for this service, see [services/website-service/src](services/website-service/src). |
54 |
55 | After the CloudFormation templates have been deployed, the [stack outputs](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/outputs-section-structure.html) contain a link to the load-balanced URLs for each of the deployed microservices.
56 |
57 | 
58 |
59 | ## How do I...?
60 |
61 | ### Get started and deploy this into my AWS account
62 |
63 | You can launch this CloudFormation stack in the US East (N. Virginia) Region in your account:
64 |
65 | [](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/new?stackName=Production&templateURL=https://s3.amazonaws.com/ecs-refarch-cloudformation/master.yaml)
66 |
67 | ### Customize the templates
68 |
69 | 1. [Fork](https://github.com/awslabs/ecs-refarch-cloudformation#fork-destination-box) this GitHub repository.
70 | 2. Clone the forked GitHub repository to your local machine.
71 | 3. Modify the templates.
72 | 4. Upload them to an Amazon S3 bucket of your choice.
73 | 5. Either create a new CloudFormation stack by deploying the master.yaml template, or update your existing stack with your version of the templates.
74 |
75 | ### Create a new ECS service
76 |
77 | 1. Push your container to a registry somewhere (e.g., [Amazon ECR](https://aws.amazon.com/ecr/)).
78 | 2. Copy one of the existing service templates in [services/*](/services).
79 | 3. Update the `ContainerName` and `Image` parameters to point to your container image instead of the example container.
80 | 4. Increment the `ListenerRule` priority number (no two services can have the same priority number - this is used to order the ALB path based routing rules).
81 | 5. Copy one of the existing service definitions in [master.yaml](master.yaml) and point it at your new service template. Specify the HTTP `Path` at which you want the service exposed.
82 | 6. Deploy the templates as a new stack, or as an update to an existing stack.
83 |
84 | ### Setup centralized container logging
85 |
86 | By default, the containers in your ECS tasks/services are already configured to send log information to CloudWatch Logs and retain them for 365 days. Within each service's template (in [services/*](services/)), a LogGroup is created that is named after the CloudFormation stack. All container logs are sent to that CloudWatch Logs log group.
87 |
88 | You can view the logs by looking in your [CloudWatch Logs console](https://console.aws.amazon.com/cloudwatch/home?#logs:) (make sure you are in the correct AWS region).
89 |
90 | ECS also supports other logging drivers, including `syslog`, `journald`, `splunk`, `gelf`, `json-file`, and `fluentd`. To configure those instead, adjust the service template to use the alternative `LogDriver`. You can also adjust the log retention period from the default 365 days by tweaking the `RetentionInDays` parameter.
91 |
92 | For more information, see the [LogConfiguration](http://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_LogConfiguration.html) API operation.
93 |
94 | ### Change the ECS host instance type
95 |
96 | This is specified in the [master.yaml](master.yaml) template.
97 |
98 | By default, [t2.large](https://aws.amazon.com/ec2/instance-types/) instances are used, but you can change this by modifying the following section:
99 |
100 | ```
101 | ECS:
102 | Type: AWS::CloudFormation::Stack
103 | Properties:
104 | TemplateURL: ...
105 | Parameters:
106 | ...
107 | InstanceType: t2.large
108 | InstanceCount: 4
109 | ...
110 | ```
111 |
112 | ### Adjust the Auto Scaling parameters for ECS hosts and services
113 |
114 | The Auto Scaling group scaling policy provided by default launches and maintains a cluster of 4 ECS hosts distributed across two Availability Zones (min: 4, max: 4, desired: 4).
115 |
116 | It is ***not*** set up to scale automatically based on any policies (CPU, network, time of day, etc.).
117 |
118 | If you would like to configure policy or time-based automatic scaling, you can add the [ScalingPolicy](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-as-policy.html) property to the AutoScalingGroup deployed in [infrastructure/ecs-cluster.yaml](infrastructure/ecs-cluster.yaml#L69).
119 |
120 | As well as configuring Auto Scaling for the ECS hosts (your pool of compute), you can also configure scaling each individual ECS service. This can be useful if you want to run more instances of each container/task depending on the load or time of day (or a custom CloudWatch metric). To do this, you need to create [AWS::ApplicationAutoScaling::ScalingPolicy](http://docs.aws.amazon.com/pt_br/AWSCloudFormation/latest/UserGuide/aws-resource-applicationautoscaling-scalingpolicy.html) within your service template.
121 |
122 | ### Deploy multiple environments (e.g., dev, test, pre-production)
123 |
124 | Deploy another CloudFormation stack from the same set of templates to create a new environment. The stack name provided when deploying the stack is prefixed to all taggable resources (e.g., EC2 instances, VPCs, etc.) so you can distinguish the different environment resources in the AWS Management Console.
125 |
126 | ### Change the VPC or subnet IP ranges
127 |
128 | This set of templates deploys the following network design:
129 |
130 | | Item | CIDR Range | Usable IPs | Description |
131 | | --- | --- | --- | --- |
132 | | VPC | 10.180.0.0/16 | 65,536 | The whole range used for the VPC and all subnets |
133 | | Public Subnet | 10.180.8.0/21 | 2,041 | The public subnet in the first Availability Zone |
134 | | Public Subnet | 10.180.16.0/21 | 2,041 | The public subnet in the second Availability Zone |
135 | | Private Subnet | 10.180.24.0/21 | 2,041 | The private subnet in the first Availability Zone |
136 | | Private Subnet | 10.180.32.0/21 | 2,041 | The private subnet in the second Availability Zone |
137 |
138 | You can adjust the CIDR ranges used in this section of the [master.yaml](master.yaml) template:
139 |
140 | ```
141 | VPC:
142 | Type: AWS::CloudFormation::Stack
143 | Properties:
144 | TemplateURL: !Sub ${TemplateLocation}/infrastructure/vpc.yaml
145 | Parameters:
146 | EnvironmentName: !Ref AWS::StackName
147 | VpcCIDR: 10.180.0.0/16
148 | PublicSubnet1CIDR: 10.180.8.0/21
149 | PublicSubnet2CIDR: 10.180.16.0/21
150 | PrivateSubnet1CIDR: 10.180.24.0/21
151 | PrivateSubnet2CIDR: 10.180.32.0/21
152 | ```
153 |
154 | ### Update an ECS service to a new Docker image version
155 |
156 | ECS has the ability to perform rolling upgrades to your ECS services to minimize downtime during deployments. For more information, see [Updating a Service](http://docs.aws.amazon.com/AmazonECS/latest/developerguide/update-service.html).
157 |
158 | To update one of your services to a new version, adjust the `Image` parameter in the service template (in [services/*](services/) to point to the new version of your container image. For example, if `1.0.0` was currently deployed and you wanted to update to `1.1.0`, you could update it as follows:
159 |
160 | ```
161 | TaskDefinition:
162 | Type: AWS::ECS::TaskDefinition
163 | Properties:
164 | ContainerDefinitions:
165 | - Name: your-container
166 | Image: registry.example.com/your-container:1.1.0
167 | ```
168 |
169 | After you've updated the template, update the deployed CloudFormation stack; CloudFormation and ECS handle the rest.
170 |
171 | To adjust the rollout parameters (min/max number of tasks/containers to keep in service at any time), you need to configure `DeploymentConfiguration` for the ECS service.
172 |
173 | For example:
174 |
175 | ```
176 | Service:
177 | Type: AWS::ECS::Service
178 | Properties:
179 | ...
180 | DesiredCount: 4
181 | DeploymentConfiguration:
182 | MaximumPercent: 200
183 | MinimumHealthyPercent: 50
184 | ```
185 |
186 | ### Add a new item to this list
187 |
188 | If you found yourself wishing this set of frequently asked questions had an answer for a particular problem, please [submit a pull request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/). The chances are that others will also benefit from having the answer listed here.
189 |
190 | ## Contributing
191 |
192 | Please [create a new GitHub issue](https://github.com/awslabs/ecs-refarch-cloudformation/issues/new) for any feature requests, bugs, or documentation improvements.
193 |
194 | Where possible, please also [submit a pull request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/) for the change.
195 |
196 | ## License
197 |
198 | Copyright 2011-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
199 |
200 | Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at
201 |
202 | [http://aws.amazon.com/apache2.0/](http://aws.amazon.com/apache2.0/)
203 |
204 | or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
205 |
206 |
--------------------------------------------------------------------------------