Teil 2 - Hands-on: Java Applikation und CloudFormation Templates

TECH BLOG

Nun da wir AWS fertig konfiguriert haben, können wir mit der Implementierung der Applikation starten.

Wir werden das Spring-Boot Docker Tutorial und die ECS Reference Architecture Templates als Grundlage verwenden.

Spring-Boot Docker Tutorial: https://spring.io/guides/gs/spring-boot-docker/

CloudFormation Template: https://github.com/aws-samples/ecs-refarch-cloudformation


Docker und Java Spring-Boot

Klone die Beispielapplikation:

CloudFormation Templates

Klone die CloudFormation Templates in das Stammverzeichnis der Applikation

Verzeichnis aufräumen

Räume das Verzeichnis auf, sodass die Verzeichnisstruktur folgendermaßen aussieht:

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

Testen und Hochladen des Docker Images in das ECR

Bauen der jar
  • mvn clean install
Bauen des Docker Images
  • mvn dockerfile:build
Image lokal ausführen
  • docker run -p 8080:8080 -t springio/gs-spring-boot-docker
Container stoppen
  • docker ps
  • docker stop
Hochladen des Docker Images in das ECR

Führe folgende Kommandos in der Eingabeaufforderung (cmd) aus:

  • aws ecr get-login --no-include-email
    Dieses Kommando gibt ein Login Kommando zurück. Copy & Paste in die Eingabeaufforderung und führe es aus
  • docker tag springio/gs-spring-boot-docker:latest:1.0.0
  • docker push :1.0.0

Dies wird die erste Version unserer Applikation sein.

Ändern der CloudFormation Templates

Wir müssen die Templates abändern, da wir andere TaskDefinitions und nur einen Bruchteil der Ressourcen verwenden. Zusätzlich werden wir die Speichereinstellung des Containers anpassen und das Template für Blue Green Deployment vorbereiten.

service.yaml

Ersetze dein service.yaml mit dem unten angezeigten Inhalt:

service.yaml

 Own Service. Changed Memory from 128MiB to 500MiB. Uses ~257MiB 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

TaskDefinition Image

Ersetze den URI Platzhalter mit dem Link zum ECR.

Beispiel:

Image: :1.0.0

Image: 222222222222.dkr.ecr.eu-central-1.amazonaws.com/springio/gs-spring-boot-docker:1.0.0


Blue-Green Deployment

DesiredCount: Anzahl der Service Tasks, die gleichzeitig laufen dürfen
MaximumPercent: Obere Grenze der Service Tasks, welche im RUNNING oder PENDING Status während des Deployments sind. Angegeben als Prozentsatz des DesiredCounts.
MinimumHealthyPercent: Untere Grenze der Service Tasks, welche im RUNNING Status während des Deployments sein müssen. Angegeben als Prozentsatz des DesiredCounts.

services.yaml

DeploymentConfiguration:
    MaximumPercent: 100
    MinimumHealthyPercent: 50

Diese Konfiguration zwingt CloudFormation maximal 100% des DesiredCounts (2 Tasks) gleichzeitig auszuführen und mindestens 50% des DesiredCounts (1 Task) während eines Deployments im Status RUNNING zu lassen.

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)


Speicher

Die Applikation aus dem Tutorial benötigt mehr Speicher als die Applikation aus dem Template.
Um zu sehen wie viel Speicher das Docker Image benötigt, kann man es lokal starten und den Speicherverbrauch überprüfen:

  • mvn clean install
  • mvn dockerfile:build
  • docker run -p 8080:8080 -t springio/gs-spring-boot-docker
  • docker stats


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 diesem Fall benötigt die Applikation rund 257MiB Speicher. Um sicherzugehen, dass unsere Applikation genug Platz nach oben hat, verdoppeln wir diesen Wert überschlagsmäßig auf 500MiB und tragen diesen in der TaskDefinition ein.

master.yaml

Ersetze dein master.yaml mit dem unten angezeigten Inhalt:

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, "/" ]]

Reduzierung der Ressourcen

Das Reference Architecture Template enthält einige Ressourcen die wir in diesem Tutorial nicht benötigen.

Das oben angezeigte master.yaml definiert daher nur die nötigen Ressourcen.

TemplateURL

Ersetze den Platzhalte mit dem Link zu deinem S3 Bucket

Beispiel:

TemplateURL: /cloudformation/infrastructure/lifecyclehook.yaml

TemplateURL: https://s3.eu-central-1.amazonaws.com/qstutorialbucket/cloudformation/infrastructure/lifecyclehook.yaml

Tags hinzufügen

Dies erlaubt unserer Finanzabteilung die Kosten zuzuordnen:

Tags:
     -  Key: "Organisation"
        Value: "Qualysoft"
    -   Key: "Project"
        Value: "Volluto"

Git

Verbindung zu CodeCommit herstellen

Schritte um eine Verbindung zu CodeCommit herzustellen:

  • Generiere Git Zugangsdaten für deinen IAM User (NICHT Jenkins). Lade die Zugangsdaten herunter und speichere sie auf deinem Rechner.
  • Initialisiere ein git Repository: git init
  • Füge eine Remote URL hinzu, welche auf das CodeCommit Repository zeigt: git remote add origin
  • Wenn nach dem Usernamen und Passwort gefragt wird, benutze die Zugangsdaten die zuvor erstellt wurden.

Commit (Einchecken)

Füge alle Dateien zum Index hinzu: git add .

Commit Änderungen (lokal einchecken): git commit -m ""

Lokale Änderungen auf das Remote Repository hochladen: git push origin master

Tag (Markieren/Markierung)

Tag den aktuellen Commit: git tag

Tag einen älteren Commit: git tag

Lade einen Tag ins Remote Repository hoch: git push origin

Lösche einen Tag aus dem Remote Repository: git push origin :

Lösche einen Tag aus dem Lokalen Repository: git tag -d

Die Applikation kann ab diesem Punkt gebaut und bereitgestellt werden.

Bernhard Gally

Über den Autor:

Bernhard Gally
Junior Dev Ops Engineer

Bernhard Gally ist neben seinem Bachelor-Studium der Informatik an FH Technikum Wien als Junior Dev Ops Engineer bei Qualysoft tätig.

Next

Teil 3 - Hands on: Jenkins

25.10.2018, 15:37
#

Der letzte Schritt besteht darin, eine Build & Deployment Pipeline zu erstellen, welche automatisch das Docker Image erstellt und dieses im ECS Cluster startet.

Mehr erfahren
Previous

Teil 1 - Hands-On: Wie man ein Microservice auf AWS mit einem Klick aufsetzen kann

04.07.2018, 15:28
#

In dieser Tutorialserie werden wir einen Microservice mit einem Klick in die Amazon Web Services Cloud einbinden. Um dies zu erreichen, werden wir unser AWS-Konto einrichten und CodeCommit, S3, SNS/SQS und ECR entsprechend unseren Anforderungen konfigurieren. Dann werden wir den Microservice von Spring's Spring Boot Docker verwenden und die Amazon Reference Architecture für CloudFormation modifizieren, um unseren Service bereitzustellen.Um Jenkins mit AWS zu verbinden, werden wir verschiedene Plugins verwenden, die als Schnittstellen fungieren. Schließlich werden wir eine Declarative Pipeline schreiben, die unseren Code von CodeCommit auscheckt, ihn erstellt, testet und mithilfe der modifizierten Referenzarchitektur in CloudFormation implementiert.

Mehr erfahren