SkillAgentSearch skills...

SpringBootFargateCloudFormationSample

Just another Spring Boot & Docker & ECR & Fargate & RDS & SSM Parameter Store & CloudFormation sample

Install / Use

/learn @chtzuehlke/SpringBootFargateCloudFormationSample
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Let's play with AWS Fargate (and a Spring Boot test workload)

High Level Overview

Vertical architecture slice:

  • Application Load Balancer (TLS-termination)
  • Containerized Java Spring Boot web application (with JDBC persistence) running as a AWS Fargate service
  • Amazon RDS for MySQL and Flyway for database migration

All automated - from zero to cloud in 13'

Pre-Conditions:

  • AWS CLI installed & configured (sufficient IAM permissions, default region configured)
  • Default VPC is present in the default AWS region
  • Docker is installed and running
  • Linux-like environment or macOS (bash, curl, sed, ...)
  • openssl installed (used for password generation)
  • General advice: read - and understand - all shell scripts and CloudFormation YAML templates before executing them

Automated AWS dev env setup (CloudFormation stacks for ECR, ALB, Fargate Service & RDS)

Disclaimer:

  • Not production ready yet (e.g. automation scripts w/o error handling)
  • Costs (until teardown): approx. 75 USD/month

Steps:

./setup.sh dev
./curl-loop.sh dev

Deploy new application version to dev env (mvn build, docker push & Fargate deploy)

Steps:

./update.sh dev

Automated AWS dev env teardown (delete all CloudFormation stacks)

Disclaimer:

  • Not production ready yet (e.g. automation scripts w/o error handling)
  • All data lost (docker images, logs, database content)!!!

Steps:

./teardown.sh dev

Let's go to production

High Level Overview

AWS setup (dev, test & prod)

Disclaimer:

  • Not production ready yet (e.g. automation scripts w/o error handling)
  • Costs (until teardown): approx. 3*75 USD/month

Steps:

./setup.sh dev
./setup-n.sh test dev
./setup-n.sh prod test

Deployment to dev (mvn build, docker push & Fargate deploy)

Steps:

./update.sh dev

Staging from dev to test

Steps:

./update-n.sh test dev

Staging from test to prod

Steps:

./update-n.sh prod test

AWS teardown (dev, test & prod)

Disclaimer:

  • Not production ready yet (e.g. automation scripts w/o error handling)
  • All data lost (docker images, logs, database content)!!!

Steps:

./teardown-n.sh prod
./teardown-n.sh test
./teardown.sh dev

Behind the scenes (CloudFormation and AWS CLI)

CloudFormation Stacks

The AWS resources will be created in multiple CloudFormation stacks which rely on each other (via exported outputs and Fn::ImportValue).

Security Groups

Security Groups

With properly configured VPC Security Groups, these requirements will be met:

  • End user web browsers can only communicate with the application load balancer (HTTPS, port 443 - currently also 80 for testing w/o ACM certificates)
  • Only the application load balancer can communicate with the Spring Boot web application (HTTP, port 8080)
  • Only the Spring Boot web application can communicate with the database (MySQL, port 3306)

CloudFormation YAML template (some details omitted):

...
Resources:
  ApplicationSG:
    Type: "AWS::EC2::SecurityGroup"
    Properties:
      GroupDescription: HTTP 8080
      SecurityGroupIngress:
        - FromPort: 8080
          IpProtocol: tcp
          SourceSecurityGroupId: !GetAtt LoadBalancerSG.GroupId
          ToPort: 8080
      VpcId: !Ref VPC

  DatabaseSG:
    Type: "AWS::EC2::SecurityGroup"
    Properties:
      GroupDescription: MySQL
      SecurityGroupIngress:
        - FromPort: 3306
          IpProtocol: tcp
          SourceSecurityGroupId: !GetAtt ApplicationSG.GroupId
          ToPort: 3306
      VpcId: !Ref VPC

  LoadBalancerSG:
    Type: "AWS::EC2::SecurityGroup"
    Properties:
      GroupDescription: HTTP and HTTPS
      SecurityGroupIngress:
        - CidrIp: "0.0.0.0/0"
          FromPort: 443
          IpProtocol: tcp
          ToPort: 443
        - CidrIp: "0.0.0.0/0"
          FromPort: 80
          IpProtocol: tcp
          ToPort: 80
      VpcId: !Ref VPC
  ...	      

Create the CloudFormation stack via AWS CLI:

DEFAULT_VPC_ID=$(aws ec2 describe-vpcs --query 'Vpcs[?IsDefault==`true`].VpcId' --output text)
SUBNET_IDS=$(aws ec2 describe-subnets --query "Subnets[?VpcId==\`$DEFAULT_VPC_ID\`].SubnetId" --output text | sed 's/[[:space:]]/,/g')

aws cloudformation create-stack \
    --stack-name samplewebworkload-net-dev \
    --template-body file://cloudformation/securitygroups.yaml \
    --parameters ParameterKey=Subnets,ParameterValue=\"$SUBNET_IDS\" \
                 ParameterKey=VPC,ParameterValue=$DEFAULT_VPC_ID

Application Load Balancer

Load Balancer

A properly configured Application Load Balancer meets the following requirements:

  • TLSListeer: terminates HTTPS traffic and is associated with an (already manually created) AWS Certificate Manager TLS certificate
  • TargetGroup: forwards all traffic to the Spring Boot Fargate service via HTTP
  • A Route53 CNAME record can refer the CNAME of the Application Load Balancer (can be configured manually in a subsequent step)

CloudFormation YAML template (some details omitted):

...
Resources:
  LoadBalancer:
    Type: "AWS::ElasticLoadBalancingV2::LoadBalancer"
    Properties:
      LoadBalancerAttributes:
        - Key: idle_timeout.timeout_seconds
          Value: 60
      Scheme: internet-facing
      SecurityGroups:
        - "Fn::ImportValue": !Sub ${NetworkStack}-LoadBalancerSG
      Subnets: !Split
        - ","
        - "Fn::ImportValue": !Sub ${NetworkStack}-Subnets

  TLSListener:
    Type: "AWS::ElasticLoadBalancingV2::Listener"
    Properties:
      Certificates:
        - CertificateArn: !Ref CertificateArn
      DefaultActions:
        - TargetGroupArn: !Ref TargetGroup
          Type: forward
      LoadBalancerArn: !Ref LoadBalancer
      Port: 443
      Protocol: HTTPS

  TargetGroup:
    Type: "AWS::ElasticLoadBalancingV2::TargetGroup"
    Properties:
      HealthCheckIntervalSeconds: 30
      HealthCheckPath: /
      HealthCheckTimeoutSeconds: 5
      HealthyThresholdCount: 2
      Port: 8080
      Protocol: HTTP
      TargetGroupAttributes:
        - Key: deregistration_delay.timeout_seconds
          Value: 60
      TargetType: ip
      UnhealthyThresholdCount: 4
      VpcId: !ImportValue
        "Fn::Sub": ${NetworkStack}-VPC
  ...

Create the CloudFormation stack via AWS CLI:

SSL_CERT_ARN="arn:aws:acm:eu-west-1:20...:certificate/73..." #your ACM cert ARN

aws cloudformation create-stack \
    --stack-name samplewebworkload-lb-dev \
    --template-body file://cloudformation/applicationloadbalancer.yaml \
    --parameters ParameterKey=CertificateArn,ParameterValue="$SSL_CERT_ARN" \
                 ParameterKey=NetworkStack,ParameterValue=samplewebworkload-net-dev

RDS MySQL Database

Load Balancer

CloudFormation YAML template (some details omitted):

...
Resources:
  DB:
    Type: "AWS::RDS::DBInstance"
    DeletionPolicy: Delete
    Properties:
      AllocatedStorage: "5"
      DBInstanceClass: db.t3.micro
      DBName: db
      DBSubnetGroupName: !Ref DBSubnetGroup
      Engine: MySQL
      MasterUserPassword: !Ref MasterUserPassword
      MasterUsername: masteruser
      VPCSecurityGroups:
        - "Fn::ImportValue": !Sub ${NetworkStack}-DatabaseSG

  DBSubnetGroup:
    Type: "AWS::RDS::DBSubnetGroup"
    Properties:
      DBSubnetGroupDescription: db subnet group
      SubnetIds: !Split
        - ","
        - "Fn::ImportValue": !Sub ${NetworkStack}-Subnets
  ...	        

First, create a new random password (via openssl, for now) and also store the password in SSM Parameter Store as a secure string (via AWS CLI):

PASS=$(openssl rand -hex 20)
DB_PASSWORD_PARAM_NAME="dev.db.rand.pass"
aws ssm put-parameter --overwrite --name $DB_PASSWORD_PARAM_NAME --type SecureString --value "$PASS"

Create the CloudFormation stack via AWS CLI:

aws cloudformation create-stack \
    --stack-name samplewebworkload-db-dev \
    --template-body file://cloudformation/database.yaml \
    --parameters ParameterKey=MasterUserPassword,ParameterValue=$PASS \
                 ParameterKey=NetworkStack,ParameterValue=samplewebworkload-net-dev

Private Docker Registry

Private Docker Registry

For application deployment, a private docker registry is required. Elastic Container Registry (ECR) will do the job.

CloudFormation YAML template (some details omitted):

...
Resources:
  DockerRepo:
    Type: "AWS::ECR::Repository"
    Properties:
      RepositoryName: !Ref "AWS::StackName"
  ...

Create the CloudFormation stack via AWS CLI:

aws cloudformation create-stack \
    --stack-name samplewebworkload-repo-dev \
    --template-body file://cloudformation/ecr.yaml

Now, let's build and push the docker image:

./mvnw clean install dockerfile:build

LOCAL_TAG=chtzuehlke/sample-web-workload:latest
REMOTE_TAG=$(./get-stack-output.sh samplewebworkload-repo-dev DockerRepoUrl):version1

docker tag $LOCAL_TAG $REMOTE_TAG

$(aws ecr get-login --no-include-email)    
docker push $REMOTE_TAG

Spring Boot Application

It's time to setup our Spring Boot Fargate Service:

  • One FargateService with one task (TaskDefinition) with one container (ContainerDefinitions) with the Spring Boot application
  • The docker image is already waiting in ECR for deployment
  • MySQL host and port configuration will be provided via environment variables
  • As the MySQL password is stored in AWS Systems Manager Parameter Store as secure string, we will pass the parameter name to the application via an environment variable (SSM Parameter Store secrets are unfortunately not yet supported in CloudFormation container definitions)
  • The application will leverage the AWS SDK for Java to lookup the password in SSM Parameter

Related Skills

View on GitHub
GitHub Stars8
CategoryDevelopment
Updated2y ago
Forks5

Languages

Shell

Security Score

70/100

Audited on Apr 5, 2023

No findings