Migrating an Springboot Java API: Part 3 - AWS ElasticBeanstalk
Tags: java, docker, azure, aws
Published: 2023-11-10 17:10:29

On the [previous post](https://dev.to/dev-laura/migrating-an-springboot-java-api-part-2-azure-build-3m94) I showed how to setup an Azure DevOps pipeline to push Docker images into ECR.
In this guide I will show how to use AWS Elastic Beanstalk to pull and run these images in a fully fledged application (with database, EC2 servers, load balancer; all handled by EB)

## Pre-requisites
- [AWS-CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html)
- [EB CLI](https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/eb-cli3.html)
- An AWS account with the following permissions: 
    - ECR (Elastic Container Registry)
    - ECS (Elastic Container Service)
    - EC2 (Elastic Compute Cloud)
    - RDS (Relational Database Service)
    - EB (Elastic Beanstalk)

## IAM role
In order to initialize an EB application, I created IAM role with the following permission policies:
```
AdministratorAccess-AWSElasticBeanstalk
AmazonEC2ContainerRegistryFullAccess
EC2InstanceProfileForImageBuilderECRContainerBuilds
AmazonSSMFullAccess
AmazonEC2FullAccess
AWSElasticBeanstalkMulticontainerDocker
AWSElasticBeanstalkRoleECS
```

## Optional - push the redis cache to a private ECR repository
It was better for my project to have a locally available version of the dependencies, so I created an powershell script `create-local-redis.ps1` to push a new version of the redis image
```powershell
aws ecr get-login-password --region AWS_REGION | docker login --username AWS --password-stdin AWS_ACCOUNT_ID.dkr.ecr.AWS_REGION.amazonaws.com

docker pull redis:7.2.1

docker tag redis:7.2.1 AWS_ACCOUNT_ID.dkr.ecr.AWS_REGION.amazonaws.com/local-redis:7.2.1

docker push AWS_ACCOUNT_ID.dkr.ecr.AWS_REGION.amazonaws.com/local-redis:7.2.1
```

## Configuration files for Elastic Beanstalk
I used the configuration for [EB multicontainer](https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/create_deploy_docker_v2config.html)
So I added a `deployment/` folder side-by-side with `MyJavaSolution` with the following:

```
deployment/
├── create-local-redis.ps1
├── Dockerrun.aws.json
├── .ebextensions/
│ │── db-instance-properties.config
│ │── network-load-balancer.config
│
MyJavaSolution/
├── ...
```

### Dockerrun.aws.json
```json
{
    "AWSEBDockerrunVersion": 2,
    "containerDefinitions": [
        {
            "name": "local-redis",
            "image": "AWS_ACCOUNT_ID.dkr.ecr.AWS_REGION.amazonaws.com/local-redis:7.2.1",
            "portMappings": [
                {
                    "hostPort": 6379,
                    "containerPort": 6379
                }
            ],
            "essential": true,
            "memory": 1024
        },
        {
            "name": "customer_DOCKER_REPOSITORY_NAME",
            "image": "AWS_ACCOUNT_ID.dkr.ecr.AWS_REGION.amazonaws.com/customer_DOCKER_REPOSITORY_NAME:Build.BuildNumber",
            "portMappings": [
                {
                    "hostPort": 9001,
                    "containerPort": 9001
                }
            ],
            "links": [
                "local-redis"
            ],
            "essential": true,
            "memory": 1024
        },
        {
            "name": "ADMIN_DOCKER_REPOSITORY_NAME",
            "image": "AWS_ACCOUNT_ID.dkr.ecr.AWS_REGION.amazonaws.com/ADMIN_DOCKER_REPOSITORY_NAME:Build.BuildNumber",
            "portMappings": [
                {
                    "hostPort": 9002,
                    "containerPort": 9002
                }
            ],
            "links": [
                "local-redis"
            ],
            "essential": true,
            "memory": 1024
        }
    ]
}
```

### db-instance-properties.config
```yaml
option_settings:  
  # Sets a decoupled database for the connection
  # It can be created from a specific snapshot using DBSnapshotIdentifier
  # If you are happy with the database, you can choose to decouple it via command line later
  aws:rds:dbinstance:
    DBAllocatedStorage: '5'
    DBDeletionPolicy: Retain
    DBInstanceClass: db.t4g.micro    
    DBSnapshotIdentifier: 'DB_INITIAL_SNAPSHOT'
    HasCoupledDatabase: 'true'
    MultiAZDatabase: 'false'
    DBName: DB_NAME
    DBEngine: DB_ENGINE
    DBEngineVersion: DB_ENGINE_VERSION
    DBUser: DB_USERNAME    
    DBPassword: DB_PASSWORD
```

### network-load-balancer.config
```yaml
option_settings:
  # Set the instance type for EC2 instances to t3.large.
  aws:ec2:instances:
    InstanceTypes: t3.large

  # Set the instance type for Auto Scaling launch configuration to t3.large.
  aws:autoscaling:launchconfiguration:
    InstanceType: t3.large

  # Disable the listener on port 80.
  aws:elbv2:listener:default:
    ListenerEnabled: false

  # Configure Elastic Beanstalk to listen on port 9001.
  aws:elbv2:listener:9001:
      DefaultProcess: default
      ListenerEnabled: 'true'
      Protocol: HTTP
      Rules: null
      SSLCertificateArns: null
      SSLPolicy: null

  # Configure Elastic Beanstalk to listen on port 9002.
  aws:elbv2:listener:9002:
    DefaultProcess: admin
    ListenerEnabled: 'true'
    Protocol: HTTP
    Rules: null
    SSLCertificateArns: null
    SSLPolicy: null

  # Configure default Health endpoint to be Customer health
  aws:elasticbeanstalk:environment:process:default:
    DeregistrationDelay: '20'
    HealthCheckInterval: '15'
    HealthCheckPath: /api/customer/health
    HealthCheckTimeout: '5'
    HealthyThresholdCount: '3'
    MatcherHTTPCode: '200'
    Port: '9001'
    Protocol: HTTP
    StickinessEnabled: 'false'
    StickinessLBCookieDuration: '86400'
    StickinessType: lb_cookie
    UnhealthyThresholdCount: '5'

  # Configure additional Health endpoint to be Admin health
  admin.aws:elasticbeanstalk:environment:process:admin:
    DeregistrationDelay: '20'
    HealthCheckInterval: '15'
    HealthCheckPath: /api/admin/health
    HealthCheckTimeout: '5'
    HealthyThresholdCount: '3'
    MatcherHTTPCode: '200'
    Port: '9002'
    Protocol: HTTP
    StickinessEnabled: 'false'
    StickinessLBCookieDuration: '86400'
    StickinessType: lb_cookie
    UnhealthyThresholdCount: '5'
```

## Inital AWS Elastic Beanstalk setup
Provided the desired images are already in ECR, I first initialized EB after installing AWS and EB CLI with: `eb init`

```powershell
Select a default region
1) us-east-1 : US East (N. Virginia)        
2) us-west-1 : US West (N. California)      
3) us-west-2 : US West (Oregon)
4) eu-west-1 : EU (Ireland)
5) eu-central-1 : EU (Frankfurt)
6) ap-south-1 : Asia Pacific (Mumbai)       
7) ap-southeast-1 : Asia Pacific (Singapore)
8) ap-southeast-2 : Asia Pacific (Sydney)   
9) ap-northeast-1 : Asia Pacific (Tokyo)    
10) ap-northeast-2 : Asia Pacific (Seoul)   
11) sa-east-1 : South America (Sao Paulo)   
12) cn-north-1 : China (Beijing)
13) cn-northwest-1 : China (Ningxia)        
14) us-east-2 : US East (Ohio)
15) ca-central-1 : Canada (Central)
16) eu-west-2 : EU (London)
17) eu-west-3 : EU (Paris)
18) eu-north-1 : EU (Stockholm)
19) eu-south-1 : EU (Milano)
20) ap-east-1 : Asia Pacific (Hong Kong)    
21) me-south-1 : Middle East (Bahrain)      
22) il-central-1 : Middle East (Israel)     
23) af-south-1 : Africa (Cape Town)
24) ap-southeast-3 : Asia Pacific (Jakarta)
25) ap-northeast-3 : Asia Pacific (Osaka)

(default is 3): 19


Enter Application Name
(default is "eb_deploy"): MyJavaAPI-newEnv
Application MyJavaAPI-newEnv has been created.

It appears you are using Docker. Is this correct?
(Y/n): y
Select a platform branch.
1) Docker running on 64bit Amazon Linux 2023
2) ECS running on 64bit Amazon Linux 2023
3) Docker running on 64bit Amazon Linux 2
4) ECS running on 64bit Amazon Linux 2
(default is 1): 2 

Cannot setup CodeCommit because there is no Source Control setup, continuing with initialization
Do you want to set up SSH for your instances?
(Y/n): y

Select a keypair.
1) existing_pair
2) [ Create new KeyPair ]
(default is 1): 2


Type a keypair name.
(Default is aws-eb): MyJavaSolutionAPI_pair
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in C:\Users\Username\.ssh\MyJavaSolutionAPI_pair.
Your public key has been saved in C:\Users\Username\.ssh\MyJavaSolutionAPI_pair.pub.
The key fingerprint is:
SHA256:.../te34 MyJavaSolutionAPI_pair
The key's randomart image is:
...
Enter passphrase:
WARNING: Uploaded SSH public key for "MyJavaSolutionAPI_pair" into EC2 for region ...
```

## Create
* After the AWS Beanstalk Environment was initialized locally, run `eb create` to push the initial changes into the server
```powershell
Enter Environment Name
(default is MyJavaAPI-newEnv): MyJavaAPI-newEnv
Enter DNS CNAME prefix
(default is MyJavaAPI-newEnv): 

Select a load balancer type
1) classic
2) application
3) network
(default is 2): 2


Would you like to enable Spot Fleet requests for this environment? (y/N): n
Creating application version archive "app-231023_153901209577".
Uploading MyJavaAPI/app-231023_153901209577.zip to S3. This may take a while.
```

If the configuration is applied correctly, then it will create a new EB application and environment, with a coupled RDS database, a EC2 instance, an S3 bucket containing the files, an ECS cluster and all necessary security groups.

![EB instance](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p1tlw2r846q8sol4khfv.png)

## Manually deploying a new version on AWS Elastic Beanstalk

### Deploy
If the latest build was 20231026.21:
You can change the version in `Dockerrun.aws.json` and run
```powershell
eb deploy --label "20231026.21" --message "20231026.21" --process
```

This will result in a new application version in the server.

#### Config save
The configuration of a current version can be saved to be reused in different environments.
```powershell
eb config save --cfg my-api-v2-20231026.21 --tags Environment=newEnv
```

And with that we have an environment up and running with our 2 dockerized API images.
In the [next post](https://dev.to/dev-laura/migrating-an-springboot-java-api-part-4-azure-release-1d7f) of this series I will show how to use Azure DevOps pipelines and release to Continuously Deploy this.