Now that we have configured AWS, we can start with the implementation of the application.
We will use the Spring-Boot Docker tutorial and the ECS Reference Architecture templates as a foundation.
Spring-Boot with Docker Tutorial: https://spring.io/guides/gs/spring-boot-docker/
CloudFormation Template: https://github.com/aws-samples/ecs-refarch-cloudformation
Clone the example application:
Clone the CloudFormation Templates inside your application root folder:
Cleanup the files, so that the file structure looks like this:
Structure
D:.
|
|   Dockerfile
|   pom.xml
|+---cloudformation
|   |   buildspec.yml
|   |   LICENSE
|   |   master.yaml
|   |   NOTICE
|   |   README.md
|   |
|   +---infrastructure
|   |       ecs-cluster.yaml
|   |       lifecyclehook.yaml
|   |       load-balancers.yaml
|   |       security-groups.yaml
|   |       vpc.yaml
|   |
|   +---services
|   |   \---website-service
|   |           service.yaml
|   |       
|   \---tests
|           validate-templates.sh
|
\---src
    +---main
    |   \---java
    |       \---hello
    |               Application.java
    |
    \---test
        \---java
            \---hello
                    HelloWorldConfigurationTests.java
Run the following commands in commandline:
This will be the first version of our application.
We need to adapt the templates since we use different TaskDefinitions and Resources. In addition to that, the memory configuration needs to be adjusted and we make the CloudFormation templates ready for Blue Green Deployment.
Replace your service.yaml with the content below:
Description: >
   Trying out own Service. Changed Memory from 128MiB to 500MiB. Uses ~251MiB on local machine.
Parameters: 
 VPC:
        Description: The VPC that the ECS cluster is deployed to
        Type: AWS::EC2::VPC::Id
        
 Cluster:
        Description: Please provide the ECS Cluster ID that this service should run on
        Type: String
 DesiredCount: 
        Description: How many instances of this task should we run across our cluster?
        Type: Number
        Default: 2
 MaxCount:
        Description: Maximum number of instances of this task we can run across our cluster
        Type: Number
        Default: 3
 Listener:
        Description: The Application Load Balancer listener to register with
        Type: String
 Path: 
        Description: The path to register with the Application Load Balancer
        Type: String
        Default: /
 ECSServiceAutoScalingRoleARN:
        Description: The ECS service auto scaling role ARN
        Type: String
Resources:
 Service: 
        Type: AWS::ECS::Service
        DependsOn: ListenerRule
        Properties: 
            Cluster: !Ref Cluster
            Role: !Ref ServiceRole
            DesiredCount: !Ref DesiredCount
            TaskDefinition: !Ref TaskDefinition
            LoadBalancers: 
                - ContainerName: "website-service"
                  ContainerPort: 8080
                  TargetGroupArn: !Ref TargetGroup
            DeploymentConfiguration:
                MaximumPercent: 100
                MinimumHealthyPercent: 50
                
 TaskDefinition:
        Type: AWS::ECS::TaskDefinition
        Properties:
            Family: website-service
            ContainerDefinitions:
                - Name: website-service
                  Essential: true
                  Image: <URI>:1.0.0
                  Memory: 500
                  PortMappings:
                    - ContainerPort: 8080
                  LogConfiguration:
                    LogDriver: awslogs
                    Options:
                        awslogs-group: !Ref AWS::StackName
                        awslogs-region: !Ref AWS::Region
 CloudWatchLogsGroup:
        Type: AWS::Logs::LogGroup
        Properties: 
            LogGroupName: !Ref AWS::StackName
            RetentionInDays: 365  
 TargetGroup:
        Type: AWS::ElasticLoadBalancingV2::TargetGroup
        Properties:
            VpcId: !Ref VPC
            Port: 80
            Protocol: HTTP
            Matcher: 
                HttpCode: 200-299
            HealthCheckIntervalSeconds: 10
            HealthCheckPath: /
            HealthCheckProtocol: HTTP
            HealthCheckTimeoutSeconds: 5
            HealthyThresholdCount: 2
 
 ListenerRule:
        Type: AWS::ElasticLoadBalancingV2::ListenerRule
        Properties:
            ListenerArn: !Ref Listener
            Priority: 1
            Conditions:
                - Field: path-pattern
                  Values: 
                    - !Ref Path
            Actions:
                - TargetGroupArn: !Ref TargetGroup
                  Type: forward
 # This IAM Role grants the service access to register/unregister with the 
 # Application Load Balancer (ALB). It is based on the default documented here:
 # http://docs.aws.amazon.com/AmazonECS/latest/developerguide/service_IAM_role.html
 ServiceRole: 
        Type: AWS::IAM::Role
        Properties:
            RoleName: !Sub ecs-service-${AWS::StackName}
            Path: /
            AssumeRolePolicyDocument: |
                {
                    "Statement": [{
                        "Effect": "Allow",
                        "Principal": { "Service": [ "ecs.amazonaws.com" ]},
                        "Action": [ "sts:AssumeRole" ]
                    }]
                }
            Policies:
                - PolicyName: !Sub ecs-service-${AWS::StackName}
                  PolicyDocument:
                    {
                        "Version": "2012-10-17",
                        "Statement": [{
                                "Effect": "Allow",
                                "Action": [
                                    "ec2:AuthorizeSecurityGroupIngress",
                                    "ec2:Describe*",
                                    "elasticloadbalancing:DeregisterInstancesFromLoadBalancer",
                                    "elasticloadbalancing:Describe*",
                                    "elasticloadbalancing:RegisterInstancesWithLoadBalancer",
                                    "elasticloadbalancing:DeregisterTargets",
                                    "elasticloadbalancing:DescribeTargetGroups",
                                    "elasticloadbalancing:DescribeTargetHealth",
                                    "elasticloadbalancing:RegisterTargets"
                                ],
                                "Resource": "*"
                        }]
                    }
 ServiceScalableTarget:
        Type: "AWS::ApplicationAutoScaling::ScalableTarget"
        Properties:
            MaxCapacity: !Ref MaxCount
            MinCapacity: !Ref DesiredCount
            ResourceId: !Join
                - /
                - - service
                  - !Ref Cluster
                  - !GetAtt Service.Name
            RoleARN: !Ref ECSServiceAutoScalingRoleARN
            ScalableDimension: ecs:service:DesiredCount
            ServiceNamespace: ecs
 ServiceScaleOutPolicy:
        Type : "AWS::ApplicationAutoScaling::ScalingPolicy"
        Properties:
            PolicyName: ServiceScaleOutPolicy
            PolicyType: StepScaling
            ScalingTargetId: !Ref ServiceScalableTarget
            StepScalingPolicyConfiguration:
                AdjustmentType: ChangeInCapacity
                Cooldown: 1800
                MetricAggregationType: Average
                StepAdjustments:
                - MetricIntervalLowerBound: 0
                  ScalingAdjustment: 1
 ServiceScaleInPolicy:
        Type : "AWS::ApplicationAutoScaling::ScalingPolicy"
        Properties:
            PolicyName: ServiceScaleInPolicy
            PolicyType: StepScaling
            ScalingTargetId: !Ref ServiceScalableTarget
            StepScalingPolicyConfiguration:
                AdjustmentType: ChangeInCapacity
                Cooldown: 1800
                MetricAggregationType: Average
                StepAdjustments:
                - MetricIntervalUpperBound: 0
                  ScalingAdjustment: -1
 CPUScaleOutAlarm:
        Type: AWS::CloudWatch::Alarm
        Properties:
            AlarmName: CPU utilization greater than 90%
            AlarmDescription: Alarm if cpu utilization greater than 90% of reserved cpu
            Namespace: AWS/ECS
            MetricName: CPUUtilization
            Dimensions:
            - Name: ClusterName
              Value: !Ref Cluster
            - Name: ServiceName
              Value: !GetAtt Service.Name
            Statistic: Maximum
            Period: '60'
            EvaluationPeriods: '3'
            Threshold: '90'
            ComparisonOperator: GreaterThanThreshold
            AlarmActions:
            - !Ref ServiceScaleOutPolicy
 CPUScaleInAlarm:
        Type: AWS::CloudWatch::Alarm
        Properties:
            AlarmName: CPU utilization less than 70%
            AlarmDescription: Alarm if cpu utilization greater than 70% of reserved cpu
            Namespace: AWS/ECS
            MetricName: CPUUtilization
            Dimensions:
            - Name: ClusterName
              Value: !Ref Cluster
            - Name: ServiceName
              Value: !GetAtt Service.Name
            Statistic: Maximum
            Period: '60'
            EvaluationPeriods: '10'
            Threshold: '70'
            ComparisonOperator: LessThanThreshold
            AlarmActions:
            - !Ref ServiceScaleInPolicy
Replace the URI placeholder with the link to your ECR.
Example:
Image: :1.0.0
Image: 222222222222.dkr.ecr.eu-central-1.amazonaws.com/springio/gs-spring-boot-docker:1.0.0
DesiredCount: Amount of your services tasks that should run simultaneously
MaximumPercent: Upper limit of your services tasks in RUNNING or PENDING state during a deployment, as a percentage of the desired number of tasks
MinimumHealthyPercent: Lower limit of your services tasks that MUST remain in the RUNNING state during a deployment, as a percentage of the desired number of tasks
services.yaml
DeploymentConfiguration:
    MaximumPercent: 100
    MinimumHealthyPercent: 50
This configuration forces CloudFormation to have a maximum of 100% of DesiredCount tasks (2 tasks) running and keep at least 50% of DesiredCount tasks (1 task) running during a deployment.
Example:
| Start | new TaskDefinition | deploy new task | check health | deploy new task | |
| Task #1 (version 1) | Task #1 (version 2) | Task #1 (version 2) | Task #1 (version 2) | Task #1 (version 2) | |
| Task #2 (version 1) | Task #2 (version 1) | Task #2 (version 1) | Task #2 (version 1) | Task #2 (version 2) | 
The application from the tutorial uses more memory than the application previously defined in the template.
To see how much memory a docker image needs just start it locally and check its memory usage:
d:\UserFiles\bgally\Documents\Volluto\gs-spring-boot-docker>docker stats CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS d11f221c9c70 confident_wiles 0.46% 257.1MiB / 1.934GiB 12.98% 2.09kB / 469B 627kB / 0B 29
In this case, the application uses around 257 MiB of memory. To make sure our application has enough headroom we just double that amount to 500 MiB in the TaskDefinition.
Replace your master.yaml with the content below:
master.yaml
Description: >
 This template deploys a VPC, with a pair of public and private subnets spread 
 across two Availabilty Zones. It deploys an Internet Gateway, with a default 
 route on the public subnets. It deploys a pair of NAT Gateways (one in each AZ), 
 and default routes for them in the private subnets.
 It then deploys a highly available ECS cluster using an AutoScaling Group, with 
 ECS hosts distributed across multiple Availability Zones. 
 Finally, it deploys a pair of example ECS services from containers published in 
 Amazon EC2 Container Registry (Amazon ECR).
 Last Modified: 22nd September 2016
 Author: Paul Maddox <pmaddox@amazon.com>
Resources:
 VPC:
        Type: AWS::CloudFormation::Stack
        Properties:
            TemplateURL: <URI>/cloudformation/infrastructure/vpc.yaml
            Parameters:
                EnvironmentName: !Ref AWS::StackName
                VpcCIDR:            10.180.0.0/16
                PublicSubnet1CIDR:  10.180.8.0/21
                PublicSubnet2CIDR:  10.180.16.0/21
                PrivateSubnet1CIDR: 10.180.24.0/21
                PrivateSubnet2CIDR: 10.180.32.0/21
            Tags: 
                -
                    Key: "Organisation"
                    Value: "Qualysoft"
                -
                    Key: "Project"
                    Value: "Volluto"
 SecurityGroups:
        Type: AWS::CloudFormation::Stack
        Properties:
            TemplateURL:  <URI>/cloudformation/infrastructure/security-groups.yaml
            Parameters: 
                EnvironmentName: !Ref AWS::StackName
                VPC: !GetAtt VPC.Outputs.VPC 
            Tags: 
                -
                    Key: "Organisation"
                    Value: "Qualysoft"
                -
                    Key: "Project"
                    Value: "Volluto"
 ALB:
        Type: AWS::CloudFormation::Stack
        Properties:
            TemplateURL:  <URI>/cloudformation/infrastructure/load-balancers.yaml
            Parameters:
                EnvironmentName: !Ref AWS::StackName
                VPC: !GetAtt VPC.Outputs.VPC
                Subnets: !GetAtt VPC.Outputs.PublicSubnets
                SecurityGroup: !GetAtt SecurityGroups.Outputs.LoadBalancerSecurityGroup
            Tags: 
                -
                    Key: "Organisation"
                    Value: "Qualysoft"
                -
                    Key: "Project"
                    Value: "Volluto"
                
 ECS:
        Type: AWS::CloudFormation::Stack
        Properties:
            TemplateURL:  <URI>/cloudformation/infrastructure/ecs-cluster.yaml
            Parameters: 
                EnvironmentName: !Ref AWS::StackName
                InstanceType: t2.large
                ClusterSize: 2
                VPC: !GetAtt VPC.Outputs.VPC
                SecurityGroup: !GetAtt SecurityGroups.Outputs.ECSHostSecurityGroup
                Subnets: !GetAtt VPC.Outputs.PrivateSubnets
            Tags: 
                -
                    Key: "Organisation"
                    Value: "Qualysoft"
                -
                    Key: "Project"
                    Value: "Volluto" 
  
 WebsiteService:
        Type: AWS::CloudFormation::Stack
        Properties:
            TemplateURL: <URI>/cloudformation/services/website-service/service.yaml
            Parameters:
                VPC: !GetAtt VPC.Outputs.VPC
                Cluster: !GetAtt ECS.Outputs.Cluster
                DesiredCount: 2
                Listener: !GetAtt ALB.Outputs.Listener 
                Path: /
                ECSServiceAutoScalingRoleARN: !GetAtt ECS.Outputs.ECSServiceAutoScalingRole
            Tags: 
                -
                    Key: "Organisation"
                    Value: "Qualysoft"
                -
                    Key: "Project"
                    Value: "Volluto" 
 LifecycleHook:
        Type: AWS::CloudFormation::Stack
        Properties:
            TemplateURL:  <URI>/cloudformation/infrastructure/lifecyclehook.yaml
            Parameters:
                Cluster: !GetAtt ECS.Outputs.Cluster
                ECSAutoScalingGroupName: !GetAtt ECS.Outputs.ECSAutoScalingGroupName
            Tags: 
                -
                    Key: "Organisation"
                    Value: "Qualysoft"
                -
                    Key: "Project"
                    Value: "Volluto" 
Outputs:
 WebsiteServiceUrl: 
        Description: The URL endpoint for the website service
        Value: !Join ["", [ !GetAtt ALB.Outputs.LoadBalancerUrl, "/" ]]
The reference architecture template has resource definitions that we do not need.
The master.yaml above defines only the necessary resources.
Replace the placeholder with the link to your S3 Bucket
Example:
TemplateURL: /cloudformation/infrastructure/lifecyclehook.yaml
TemplateURL: https://s3.eu-central-1.amazonaws.com/qstutorialbucket/cloudformation/infrastructure/lifecyclehook.yaml
This allows our finance department to determine the costs of our project.
Tags:
     -  Key: "Organisation"
        Value: "Qualysoft"
    -   Key: "Project"
        Value: "Volluto"
Steps to connect to CodeCommit:
Add all files: git add .
Commit changes: git commit -m ""
Push to remote repository: git push origin master
Tag the current commit: git tag
Tag an older commit: git tag
Push a tag to remote repository: git push origin
Delete a tag from remote repository: git push origin :
Delete a tag from local repository: git tag -d
The application is now ready to be built and deployed.