Deployment of a Spring Boot application using AWS Elastic Beanstalk, Amazon Route 53 and AWS Certificate Manager

Background
The focus of the blog post is the usage of the AWS command line and not the creation of a Spring Boot application. So, the following prerequisites exist:
- You are already familiar with the development of Spring Boot applications. If this is not the case, a good place to start is Spring.io.
- You have already an AWS account, otherwise it is possible to register for the free tier which includes 5 GB S3 storage and 750 hours of EC2 usage.
- You have an installed version of the AWS cli on your local machine, otherwise this might help you.
- You configured the AWS cli on your local machine with your AWS account details, otherwise checkout the documentation here.
So, before we start and get our hands dirty, a little background information about the things we like to achieve.
First of all, a Spring Boot service is deployed to AWS Elastic Beanstalk using the AWS cli to make the service accessible in the cloud using http. Afterwards, a new domain name is made available through Amazon Route 53 and the load balancer of the AWS Elastic Beanstalk environment is configured to accept only https traffic to be able to call the service using a user-friendly url using https.
As an example service, I use a simple service that retrieves a list of persons using the endpoint /person/get. Regarding the development of the Spring Boot application, only one aspect is specific to AWS Elastic Beanstalk, namely the usage of port 5000 for the server.port property. This port is the standard port that is assigned for web applications in AWS Elastic Beanstalk.
But what exactly is AWS Elastic Beanstalk? Amazon describes the service in the following way: “Elastic Beanstalk is the fastest and simplest way to deploy your application on AWS. Elastic Beanstalk provisions and operates the infrastructure and manages the application stack (platform) for you, so you don’t have to spend the time or develop the expertise.” This means that AWS Elastic Beanstalk is one of the easiest ways to set up a cloud environment for web applications consisting of load balancers, security groups, EC2 instances and many more components that otherwise would have to be set up separately.
Overview
The figure shows the components that will be used in the following sections:
- AWS Elastic Beanstalk is hosted in the eu-west-1 availability zone and consits of an AWS Classic Load Balancer which is Internet facing with a public IP address. In addition to the load balancer, it consists of several components like a VPC where all of the other AWS resources are hosted, a network access control list and a security group to restrict access (not shown here for the sake of simplicity), an auto scaling group which is able to scale the Amazon EC2 virtual machines based on metrics like cpu utilization. The Amazon EC2 instance is the virtual machine where the Spring Boot service is hosted.
- Amazon Route 53 is the DNS server which will host a user-frindly DNS name for our service.
- AWS Certificate Manager managed the certificate which is used to access our service through https.
- Amazon S3 is used to manage the code of our Spring Boot service.
- Amazon Cloud Watch provides log functionality and monitoring capabilities for our Amazon EC2 instance.
Please note that using Amazon Route 53 is outside of the AWS free tier (see prizes).
Deployment to AWS Elastic Beanstalk
So, let’s start by creating an application that can be hosted on AWS Elastic Beanstalk later on:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
aws elasticbeanstalk create-application --application-name PersonService { "Application": { "ApplicationArn": "arn:aws:elasticbeanstalk:eu-west-1:292384238617:application/PersonService", "ApplicationName": "PersonService", "DateCreated": "2019-04-21T08:17:07.896Z", "DateUpdated": "2019-04-21T08:17:07.896Z", "ConfigurationTemplates": [], "ResourceLifecycleConfig": { "VersionLifecycleConfig": { "MaxCountRule": { "Enabled": false, "MaxCount": 200, "DeleteSourceFromS3": false }, "MaxAgeRule": { "Enabled": false, "MaxAgeInDays": 180, "DeleteSourceFromS3": false } } } } } |
As we can see, our application has been created in the eu-west-1 region (our default region which has been specified during the set-up of the AWS cli on the local machine). For simplicity, we did not used any configuration templates to customize the environment and did not activate any lifecycle configuration.
Now, we have to create an environment for our application. Every environment in AWS Elastic Beanstalk is created using a specific solution stack (Java, node.js, Go, etc.). So, so we now need to find out the name of the solution stack for our Java based Spring Boot service using the AWS cli:
1 |
aws elasticbeanstalk list-available-solution-stacks |
As a result, the available list of solution stacks (omitted here) is returned and the solution stack with the name “64bit Amazon Linux 2018.03 v2.8.2 running Java 8” will be used for creating our runtime environment later on. An AWS Elastic Beanstalk environment can be compared to a stage in the software development lifecycle, so typically a development -, integration – and production environment will be created for any application.
Let’s create a development environment called PersonService-dev for our PersonService application:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
aws elasticbeanstalk create-environment --application-name PersonService --environment-name PersonService-dev --group-name dev --solution-stack-name "64bit Amazon Linux 2018.03 v2.8.2 running Java 8" { "EnvironmentName": "PersonService-dev", "EnvironmentId": "e-8enmyw47g2", "ApplicationName": "PersonService", "SolutionStackName": "64bit Amazon Linux 2018.03 v2.8.2 running Java 8", "PlatformArn": "arn:aws:elasticbeanstalk:eu-west-1::platform/Java 8 running on 64bit Amazon Linux/2.8.2", "DateCreated": "2019-04-21T10:11:25.209Z", "DateUpdated": "2019-04-21T10:11:25.209Z", "Status": "Launching", "Health": "Grey", "Tier": { "Name": "WebServer", "Type": "Standard", "Version": "1.0" }, "EnvironmentArn": "arn:aws:elasticbeanstalk:eu-west-1:292384238617:environment/PersonService/PersonService-dev" } |
As you can see, our newly created environment has the unique id e-8enmyw47g2 and is created in the background (status “Launching”). As soon it is available its health will become “Green”.
Before we can actually deploy our Spring Boot service to AWS Elastic Beanstalk, we have to create an S3 bucket where your application code can be uploaded:
1 2 3 4 |
aws elasticbeanstalk create-storage-location { "S3Bucket": "elasticbeanstalk-eu-west-1-292384238617" } |
As a result, the name of the newly created S3 bucket is returned. After running a local maven build, we can now upload the created jar using the given bucket location:
1 |
aws s3 cp PersonService\target\PersonService-0.0.1-SNAPSHOT.jar s3://"elasticbeanstalk-eu-west-1-292384238617 |
After the upload is complete, the code is ready to be deployed. So, in the first step we first create a new application version that will be activated in the second step.
1 |
aws elasticbeanstalk create-application-version --application-name PersonService --version-label v0.0.1 --source-bundle S3Bucket="elasticbeanstalk-eu-west-1-292384238617",S3Key="PersonService-0.0.1-SNAPSHOT.jar" --process |
1 |
aws elasticbeanstalk update-environment --environment-name PersonService-dev --version-label v0.0.1 |
Before our deployed Spring Boot service can be accessed, we need to find out the hosts name where the service is located:
1 |
aws elasticbeanstalk describe-environments --environment-names PersonService-dev |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
{ "Environments": [ { "EnvironmentName": "PersonService-dev", "EnvironmentId": "e-8enmyw47g2", "ApplicationName": "PersonService", "VersionLabel": "v0.0.1", "SolutionStackName": "64bit Amazon Linux 2018.03 v2.8.2 running Java 8", "PlatformArn": "arn:aws:elasticbeanstalk:eu-west-1::platform/Java 8 running on 64bit Amazon Linux/2.8.2", "EndpointURL": "<a class="external-link" href="http://awseb-e-8-awsebloa-1edi9feaj6flr-181762183.eu-west-1.elb.amazonaws.com/">h</a>ttp://awseb-e-8-AWSEBLoa-1EDI9FEAJ6FLR-181762183.eu-west-1.elb.amazonaws.com", .... } ] } |
Now we are able to call our Spring Boot microservice using HTTP and the endpoint URL that has been returned namely http://awseb-e-8-awsebloa-1edi9feaj6flr-181762183.eu-west-1.elb.amazonaws.com/person/get
Using Amazon Route 53
Though, our service is now available to the public, the URL is not very user-friendly and the usage of https for accessing the service would be preferable.
Due to the fact, that my company Novatec Consulting GmbH already had a registered top-level domain called novatec-playground53.de. If you do not have any top-level domains, you can buy or transfer them via the AWS console using Amazon Route 53.
For mapping any sub-domain to our AWS Elastic Beanstalk environment, you have to find out the id of the hosted zone (“A hosted zone is a container for records, and records contain information about how you want to route traffic for a specific domain, such as example.com, and its subdomains like acme.example.com or zenith.example.com“) of your top-level domain:
1 2 3 4 5 6 7 8 9 10 11 |
aws route53 list-hosted-zones { "HostedZones": [ { "Id": "/hostedzone/Z3AK3VCZD2MCZE", "Name": "novatec-playground53.de.", .... } ] } |
Then we have to find out the current CNAME of our environment using the command line:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
aws elasticbeanstalk describe-environments --environment-names PersonService-dev { "Environments": [ { "EnvironmentName": "PersonService-dev", "EnvironmentId": "e-8enmyw47g2", "ApplicationName": "PersonService", "VersionLabel": "v0.0.1", "SolutionStackName": "64bit Amazon Linux 2018.03 v2.8.2 running Java 8", "CNAME": "PersonService-dev.tvumy5sv4s.eu-west-1.elasticbeanstalk.com", "Health": "Green", ... } ] } |
Now a new subdomain, e.g. pkt-dev.novatec-playground53.de, can be mapped to the CNAME of our PersonService-dev environment using the following command:
1 |
aws route53 change-resource-record-sets --hosted-zone-id Z3AK3VCZD2MCZE --change-batch file://C:/Users/pkt/create_route53_cname.json |
Content of the create_route53_cname.json file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
{ "Comment": "CREATE a record ", "Changes": [{ "Action": "CREATE", "ResourceRecordSet": { "Name": "pkt-dev.novatec-playground53.de", "Type": "CNAME", "TTL": 300, "ResourceRecords": [{ "Value": "PersonService-dev.tvumy5sv4s.eu-west-1.elasticbeanstalk.com" }] } }] } |
Basically, the content of the file means that the CNAME pkt-dev.novatec-playground53.de is mapped to the CNAME PersonService-dev.tvumy5sv4s.eu-west-1.elasticbeanstalk.com and made available in the hosted zone with the given id.
As soon as the change has been processed, we are able to call our Spring Boot service using HTTP and the endpoint URL http://pkt-dev.novatec-playground53.de/person/get
Using AWS Certificate Manager
So far, so good. In the last part, we like to allow only https calls to access our service. This is where AWS Certificate Manager is used. So, we have to request a certificate for our pkt-dev.novatec-playground53.de subdomain:
1 2 3 4 |
aws acm request-certificate --domain-name pkt-dev.novatec-playground53.de { "CertificateArn": "arn:aws:acm:eu-west-1:292384238617:certificate/60bf5cc7-c3b6-45d0-bce7-b1234567890" } |
Now, we finally like to assign the requested certificate to port 443 and disable port 80, so only https traffic is able to access our service.
The most easiest way to achieve this, is not to use the AWS command line, but to use a config file namely listeners.config (YAML format) in a directory named .ebextensions as part of our source code with the following content:
1 2 3 4 5 6 7 8 |
option_settings: aws:elb:listener:443: ListenerProtocol: HTTPS SSLCertificateId: arn:aws:acm:eu-west-1:292384238617:certificate/60bf5cc7-c3b6-45d0-bce7-b1234567890 InstancePort: 80 InstanceProtocol: HTTP aws:elb:listener: ListenerEnabled: false |
The config file has the effect that a https listener for the load balancer in the AWS Elastic Beanstalk environment is enabled and associated with the given certificate and the http listener is disabled. So http traffic is no longer allowed and our service can only accessed using the url https://pkt-dev.novatec-playground53.de/person/get. Note, the same functionality can be achieved using the AWS cli, but you need to modify the load balancer as well as the associated security group accordingly which is a little more complicated. By the way, AWS Elastic Beanstalk uses a classic load balancer – the old way of balancing load on application level (OSI level 7) and on network level (OSI level 4).
Conclusion
As you could see, it is not that complicated to deploy a simple Spring Boot service and make it available in AWS in a secure fashion. AWS Elastic Beanstalk, Amazon Route 53 and AWS Certificate Manager are the services to get this job done. Using the AWS cli you are able to repeat the set-up for different environments and settings in an easy way.
It has to be noted that the deployment process is only feasible for service which are not changed very often. In a typical development project another setup with an automatic build would be used, but this is a topic for another blog post.
Recent posts





