Deploying a Spring boot app to Fargate with the AWS CDK

Managing your infrastructure can be hard sometimes. In AWS, you can deploy and manage your infrastructure with the AWS console. This is a GUI where you can click buttons and various other inputs to deploy your infrastructure.
The downside of using the console is that it’s really hard to reproduce every step you’ve made. AWS introduced Cloudformation to define your infrastructure-as-code (IaC) which can solve this problem.
Cloudformation allows developers to use YAML or JSON templates to define and deploy every piece of infrastructure in AWS.
This makes your infrastructure reproducable and enables you to automate the provisioning of your resources with code.
The templates are still rather verbose where you must define every configuration detail of your resources (A lot of boilerplate code).
These templates also won’t allow you to add complex conditions or other programming concepts to your deployment.
And the most important thing: You can’t write your infrastructre in your favorite programming language.

Is there a way where we can write actual code in your favorite programming language without the boilerplate code? The answer is: With AWS CDK, we can!

The AWS CDK provides high-level components that allows you to configure cloud resources with REAL code. Instead of creating JSON or YAML files, the CDK allows you to use your favorite programming language like Python, Typescript, C# or Java. This also means that you can use your favorite IDE and use its tooling.
Even though the AWS CDK relies on Cloudformation, it provides us with a higher abstraction with sensible defaults, so you can write less, do more. You can focus on getting things done, instead of writing boilerplate code. It also enables you to apply logic to your deployments and use familiar programming concepts like OOP, conditions, loops etc.

In this blog post, we will help you set up your AWS CDK, create your infrastructure and deploy it along with your Spring boot application while also demonstrating the previously stated advantages of the CDK. We assume you already created a simple Spring boot application and you want to deploy it to AWS Fargate.
Let’s get started!

Setup

Before we actually get started, make sure you have the following installed or set up (assuming you already have maven and a JDK version >=8 installed):

–  Node
– Docker
– A Docker image of your Spring boot application
– A configured AWS profile on your machine (With an account with the needed permissions)

Run the following command in your terminal to install the AWS CDK:


npm install -g aws-cdk
# Verify your installation
cdk --version
            

Defining the infrastructure

Create a directory where you want to define your infrastructure, for example:


// Create the fargate directory
mkdir fargate
            

Generate the base of the infrastructure in the newly created directory by running the following:


cd fargate
cdk init --language java
            

This will create a new maven project with all the files you need to deploy your infrastructure.
At this point in time, the infrastructure will deploy nothing because we haven’t declared any services yet.
Before we can add Fargate to our infrastructure, we should add all the needed dependencies first to the newly generated project.

Add the following to the pom.xml:


<dependency>
    <groupId>software.amazon.awscdk</groupId>
    <artifactId>ec2</artifactId>
    <version>${cdk.version} </version>
</dependency>
<dependency>
    <groupId>software.amazon.awscdk</groupId>
    <artifactId>ecs</artifactId>
    <version>${cdk.version}</version>
</dependency>
<dependency>
    <groupId>software.amazon.awscdk</groupId>
    <artifactId>ecs-patterns</artifactId>
    <version>${cdk.version}</version>
</dependency>
                

You may have noticed that the artifactId’s are similar to the AWS services.
The AWS CDK provides every service as a library which you can add to your project. You may also have noticed the ecs-patterns dependency.
This dependency provides another abstraction on top of the ecs library, which we will discuss later.

Now we have everything ready to actually write some code and create our infrastructure! If you’ve named your directory fargate, the main class will be named FargateApp. This class can be left as it is.
The actual definition of our infrastructure will be coded in the FargateStack class. Here we will define an auto-scaling ECS cluster with Fargate and a public load balancer to run our application, which will look like this:


import software.amazon.awscdk.core.Construct;
import software.amazon.awscdk.core.Stack;
import software.amazon.awscdk.core.StackProps;
import software.amazon.awscdk.services.ec2.*;
import software.amazon.awscdk.services.ecs.*;
import software.amazon.awscdk.services.ecs.patterns.*;
import software.amazon.awscdk.services.elasticloadbalancingv2.*;

public class FargateStack extends Stack {
    public FargateStack(final Construct scope, final String id) {
        this(scope, id, null);
    }

    public FargateStack(final Construct scope, final String id, final StackProps props) {
        super(scope, id, props);
        // Get the deployment environment from an enviroment variable named "ENV"
        String environment = System.getenv("ENV");

        // This will create a VPC with a NAT-gateway, IGW, default routes and route tables, and a private and public subnet in all availability zones
        Vpc vpc = new Vpc(this, "myVpc");

        // Create the ECS cluster
        Cluster cluster = Cluster.Builder.create(this, "MyCluster").vpc(vpc).build();

        // Create a load-balanced Fargate service and make it public
        ApplicationLoadBalancedFargateService fargateService = ApplicationLoadBalancedFargateService.Builder.create(this, "MyFargateService")
            .cluster(cluster)
            .cpu(1024) // Default is 256, but for a Spring boot app, 1024 is the minimum if we want the app to start within a minute
            .memoryLimitMiB(2048) // Default is 512
            .desiredCount(2) // Default is 1
            .taskImageOptions(ApplicationLoadBalancedTaskImageOptions.builder()
                    // We want to use the DockerFile of our Spring boot App. In this example, the Dockerfile is located in the `app` directory
                    // This will create an ECR repository and upload the docker image to that repository
                    .image(ContainerImage.fromAsset("../app"))
                    .containerPort(8080)// The default is port 80, The Spring boot default port is 8080
                    .build())
            .publicLoadBalancer(true) // Default is false 
            .assignPublicIp(false) // If set to true, it will associate the service to a public subnet 
            .build();

        // For our Spring boot application, we need to configure the health check
        fargateService.getTargetGroup().configureHealthCheck(HealthCheck.builder()
            .healthyHttpCodes("200") // Specify which http codes are considered healthy
            // The load balancer REQUIRES a healthcheck endpoint to determine the state of the app.
            // In this example, we're using the Spring Actuator. Configure this in your app if missing. 
            .path("/actuator/health")
            .port("8080") // The default is port 80
            .build());

        // Configure auto scaling capabilities only when our environment equals "production" 
        if(environment != null && !environment.isEmpty() && environment.equals("production")) {
            // Configure the service auto scaling 
            ScalableTaskCount scalableTask = fargateService.getService().autoScaleTaskCount(EnableScalingProps.builder()
                .minCapacity(2)
                .maxCapacity(6)
                .build()
            );

            // Scale based on the CPU utilization
            scalableTask.scaleOnCpuUtilization("MyCpuBasedScaling", CpuUtilizationScalingProps.builder()
                .targetUtilizationPercent(50) // Scale when the CPU utilization is at 50%
                .build()
            );
        }
    }
}
                

And voila! You’ve just created a highly-available infrastructure for your Spring boot application.
Notice how we could extend our infrastructure for the production environment with auto scaling capablities by using a normal if-statement. By setting the ENV environment variable to “production”, the infrastructure will add the auto scaling feature to our ECS service which makes it scalable.

If you’ve defined CloudFormation templates in the past, some of these Constructs may seem familiar. For example, creating a VPC in CloudFormation requires many components like Routes, Route-tables, CIDR-ranges, Subnets, Gateways and other configuration.
The AWS CDK Constructs can create a single resource or multiple resources with sensible defaults with a single line of code. Instead of configuring all the separate parts of the VPC like in CloudFormation, we only called new VPC(this, "MyVPC") which generates a common VPC configuration. The "MyVPC" part is the construct id of the resource.
Every construct must contain an unique construct id within the scope in which they are created. This will eventually be used by Cloudformation to generate a logical id for the resource.

In my opinion, the most interesting construct is the ApplicationLoadBalancedFargateService construct. This construct was imported by the ecs-patterns library and contains a pattern consisting of multiple services. This allows you to configure your ECS Service, your ECS task and your Application Load Balancer at the same time.
You don’t have to think about the networking part, because it will automatically place your load balancer and your ECS Task in the right subnets based on your configuration.

Constructs empower your team to quickly deploy infrastructure without having to bother with boilerplate configuration. You always have the option to modify defaults or to descend to a lower level of abstraction, but you don’t have to.

Let’s deploy!

Now that our infrastructure is set up, we should Bootstrap our CDK in AWS. (I’ll explain this later)


cdk bootstrap
            

After it’s bootstrapped, we can deploy it by running:


cdk deploy
            

( You may need to manually confirm the deployment )

At the start of this article, I’ve stated that the AWS CDK relies on Cloudformation.
The cdk bootstrap command deploys a CloudFormation stack with resources the toolkit requires for deployment, like a S3 bucket to store the assets.
When you run the deploy command, it generates the CloudFormation template in the cdk.out directory. This template will then be used to deploy the infrastructure stack to CloudFormation.
If you open the FargateStack.template.json, you’ll see what you’ve actually configured with just a few lines of Java code.

If the deployment succeeded: Congratulations! You’re Spring boot application is live and ready to be used by the masses. You can find the load balancer URL in the Outputs and see your application in it’s full glory. You can also check your CloudFormation Stack in the AWS console and obtain the url from there.
It should look something like this: xxx-xxx-xxx.eu-west-1.elb.amazonaws.com

If you want to remove your created deployment/stack, you just have to call:


cdk destroy

Summary

In this blog post, we’ve learned how to deploy a “simple” Spring boot application to Fargate with the help of the AWS CDK in Java.
We’ve also been introduced to AWS CDK and it’s advantages. If you compare the generated Cloudformation template (cdk.out/FargateStack.template.json) with your FargateStack.java class, you’ll notice that you just wrote ~950 lines of JSON “code”, with only ~70 lines of Java.
You’ve also used a condition with the “if-statement” to differentiate your production environment with auto scaling, which isn’t possible with the GUI or (pure) Cloudformation itself.
We’ve only scratched the surface with this post, because we haven’t talked about debugging your application nor have we talked about unit testing your infrastructure.

To learn more about the CDK, you can always refer to the AWS CDK documentation to extend your knowledge.

Jimmy Nguyen

Jimmy Nguyen is sinds 2019 als Software Engineer, met een specialisatie in AWS, in dienst bij Profit4Cloud. Jimmy is OCA, OCP, AWS Solutions Architect Associate en AWS Developer Associate gecertificeerd.

Interesse in onze nieuwste blogs? Registreer dan hier

Frequent brengen wij nieuwe blogs uit. Wanneer u zich hier registreert, worden deze per mail automatisch toegestuurd.

Registreer