Accept
This website is using cookies. More details

Yohan Beschi
Developer, Cloud Architect and DevOps Advocate

Always deploying the latest AWS Elastic Beanstalk Solution Stack

Always deploying the latest AWS Elastic Beanstalk Solution Stack

We have already spent quite some time to configure AWS Elastic Beanstalk correctly and particularly on managed updates. Unfortunately, we can still deploy an older Solution Stack while deploying a new version of our application.

With AWS CloudFormation we can define the AWS Elastic Beanstalk Solution Stack we want to use this way:

1
2
3
4
5
6
7
ConfigurationTemplate:
  Type: AWS::ElasticBeanstalk::ConfigurationTemplate
  Properties:
    ApplicationName: !Ref ElasticBeanstalkApplication
    SolutionStackName: !Ref BackendSolutionStackName
    OptionSettings:
      - [...]

The value for the Parameter BackendSolutionStackName must come from somewhere, like SSM Parameter store or in our case an Ansible variable. To be able to use the latest solution stack version, we need to change the value manually no matter where it is stored. The solution stack name is a value like: “64bit Amazon Linux 2 v3.1.5 running Corretto 11”, and for most platforms AWS releases a new version on a monthly basis, which in this end is a real hassle for Operations. We want to set everything up and only do something for major updates.

To solve this issue, we have to use an AWS CloudFormation Custom Resource which will retrieve the latest solution stack version (based on a pattern we will define) and return the solution stack name in the Custom Resource response.

We will start with the application and infrastructure described in the previous articles:

All the source code presented in this article is available in a Github repository.

An AWS CloudFormation Custom Resource

AWS CloudFormation Custom Resources are AWS Lambdas executed following CloudFormation stacks lifecycle (create, update and delete).

What we want is to retrieve the latest Solution Stack Name for a specific Platform and return it in the Custom Resource response. For example, if we want to use Corretto 11 on Amazon Linux 2, the Solution Stack Name is as follow: “64bit Amazon Linux 2 v3.1.5 running Corretto 11” and what changes between versions is the “v3.1.5” part. Therefore, we can simply define a regex pattern (64bit Amazon Linux 2 v(.*) running Corretto 11) which will match the latest version.

What we need can be done with a very simple function, everything else is part of a Custom Resource boiler plate code (backend-common.cfn.yml):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import json
import re
import boto3

def get_latest_eb_stack_name(eb_stack_name_pattern):
  eb_client = boto3.client('elasticbeanstalk')
  # Returns a list of the available solution stack names,
  # with the public version first and then in reverse chronological order.
  stacks = eb_client.list_available_solution_stacks()['SolutionStacks']

  compiled_pattern = re.compile(f'^{eb_stack_name_pattern}$')

  for stack_name in stacks:
    if compiled_pattern.match(stack_name):
      print(f'EB Solution Stack Name: {stack_name}')
      return {'EbStackName': stack_name}

  return {'EbStackName': 'Stack Not Found'}

This Lambda being quite short, it can be directly included in a CloudFormation template.

Concerning the Lambda IAM execution role, we only need the usual permissions to write into AWS CloudWatch, and the permission elasticbeanstalk:ListAvailableSolutionStacks to retrieve all the available Solution Stacks.

As the Lambda is created in the backend-common.cfn.yml stack, to use it in the backend-eb.cfn.yml stack as a custom resource, we need to store its ARN in SSM Parameter.

1
2
3
4
5
6
7
8
9
10
11
12
Parameters:
  SsmEbSolutionStackNameFinderLambdaArnKey:
    Type: String
  [...]
Resources:
  [...]
  SsmEbSolutionStackNameFinderLambdaArn:
    Type: AWS::SSM::Parameter
    Properties:
      Type: String
      Name: !Ref SsmEbSolutionStackNameFinderLambdaArnKey
      Value: !GetAtt EbSolutionStackNameFinderLambda.Arn

We could simply hardcode this value:

1
!Sub arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:eb-solution-stack-finder

but it is far from being a good practice.

Now all the preparation work is done. We can move on to next step, creating the Custom Resource and using its Response.

Creating and using the Custom Resource

Creating the Custom Resource is quite straight forward.

1
2
3
4
5
6
7
8
9
10
11
12
13
Parameters:
  BackendSolutionStackNamePattern:
    Type: String
  SsmEbSolutionStackNameFinderLambdaArnKey:
    Type: AWS::SSM::Parameter::Value<String>

Resources:

  EbSolutionStackFinder:
    Type: Custom::EbSolutionStackNameRetriever
    Properties:
      ServiceToken: !Ref SsmEbSolutionStackNameFinderLambdaArnKey
      StackNamePattern: !Ref BackendSolutionStackNamePattern

Finally, we can use the Solution Stack Name returned by the Custom Resource:

1
2
3
4
5
6
7
8
9
Resources:
  [...]
  ConfigurationTemplate:
    Type: AWS::ElasticBeanstalk::ConfigurationTemplate
    Properties:
      ApplicationName: !Ref ElasticBeanstalkApplication
      SolutionStackName: !GetAtt EbSolutionStackFinder.EbStackName
      OptionSettings:
        - [...]

Conclusion

For once, everything was extremely simple. Now, until we upgrade to a newer version of the JDK or AWS decides to create Amazon Linux 3, we will not have to worry about the Solution Stack version used. On one side, managed updates will update running instances on a weekly basis if a new version is available, and on the other, if we release a new version of our application, the latest platform version will be picked up when deploying our CloudFormation stack.

Now it’s your turn!

Schedule a 1-on-1 with an ARHS Cloud Expert today!