Yohan Beschi
Developer, Cloud Architect and DevOps Advocate
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:
- Spring Boot in AWS Elastic Beanstalk with AWS CloudFormation
- Handling secrets with AWS Elastic BeanStalk
- AWS Elastic Beanstalk Blue/Green deployment with Ansible.
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.