Elastic Beanstalk

Introduction

The manual setup steps taken from the AWS docs on Getting started using Elastic Beanstalk.

Have been automated using the CDK.

Examples follows in Python.

3 Stacks were created.

  1. BeanstalkS3Stack [ uploads assets to S3 ]

  2. BeanstalkAppStack [ creates, deploy new app ]

  3. BeanstalkEnvStack [ creates new environment ]

By default the entry point app.py is created during the init stage of creating the CDK project.

This is where you define which stacks to use.

When you run cdk ls it will then list the stacks you have defined to use in the app.py

In this case it will return the 3 stacks listed above.

App.py

#!/usr/bin/env python3

from aws_cdk import core

from elastic_beanstalk.BeanstalkAppStack import BeanstalkAppStack
from elastic_beanstalk.BeanstalkEnvStack import BeanstalkEnvStack
from elastic_beanstalk.BeanstalkS3Stack import BeanstalkS3Stack

app = core.App()

# a dictionary to store properties
props = {
            'namespace': 'ElasticBeanstalk',
            'application_name':'GettingStartedApp2', 
            'environment_name': 'GettingStartedEnv2',
            'solution_stack_name': '64bit Amazon Linux 2018.03 v2.15.5 running Go 1.14.4',
            's3_asset' : 'assets/go-v1.zip'
        }

s3_bucket = BeanstalkS3Stack(
            app,
            f"{props['namespace']}-s3",
            props
)

beanstalk_app = BeanstalkAppStack(
                app,
                f"{props['namespace']}-app",
                s3_bucket.outputs
)

# the beanstalk app stack has a dependency on the creation of a S3 bucket
beanstalk_app.add_dependency(s3_bucket)

beanstalk_env = BeanstalkEnvStack(
                app,
                f"{props['namespace']}-env",
                props,

)

# the beanstalk environment stack has a dependency on the creation of a beanstalk app
beanstalk_env.add_dependency(beanstalk_app)

app.synth()

BeanstalkS3Stack

In order to store assets to deploy into a Beanstalk environment, a S3 bucket is leveraged.

Here we use the S3 construct aws_s3.

The S3 asset used are taken from the go sample app.

go-v1.zip

from aws_cdk import (
    core,
    aws_s3,
    aws_s3_deployment,
    aws_s3_assets,
)

import os

class BeanstalkS3Stack(core.Stack):
    def __init__(self, scope: core.Construct, id: str,props, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        # the asset is uploaded to the cdktoolkit-stagingbucket
        s3_bucket_asset = aws_s3_assets.Asset(
            self,
            "s3-asset",
            path=os.path.abspath(props['s3_asset'])
        )

        # debugging print s3 object url to console output
        output = core.CfnOutput(
            self,
            "S3_object_url",
            value=s3_bucket_asset.s3_object_url,
            description="S3 object url"
        )

        output = core.CfnOutput(
            self,
            "S3_object_key",
            value=s3_bucket_asset.s3_object_key,
            description="S3 object key"
        )

        output = core.CfnOutput(
            self,
            "S3_bucket_name",
            value=s3_bucket_asset.s3_bucket_name,
            description="S3 bucket name"
        )

        self.output_props = props.copy()
        self.output_props['s3bucket_name'] = s3_bucket_asset.s3_bucket_name
        self.output_props['s3bucket_obj_key'] = s3_bucket_asset.s3_object_key


    # pass objects to another stack

    @property
    def outputs(self):
        return self.output_props

Create Application

BeanstalkAppStack

Creating a new application involves using the Construct taken from aws_elasticbeanstalk

from aws_cdk import (
    core,
    aws_elasticbeanstalk as elastic_beanstalk,
    aws_s3
)


class BeanstalkAppStack(core.Stack):

    def __init__(self, scope: core.Construct, id: str, props, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        def createApplication(application_name):
            elastic_beanstalk.CfnApplication(
                self,
                "Elastic-Beanstalk",
                application_name=application_name,
                description="AWS Elastic Beanstalk Demo",
            )            

        createApplication(props['application_name'])

        BeanstalkAppVersionStack(self,"BeanstalkAppVersionStack",props,**kwargs)

class BeanstalkAppVersionStack(core.NestedStack):
    def __init__(self, scope: core.Construct, id: str, props, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)
        
        
        app_version = elastic_beanstalk.CfnApplicationVersion(
            self,
            "application_version",
            application_name=props['application_name'],
            source_bundle=elastic_beanstalk.CfnApplicationVersion.SourceBundleProperty(
                s3_bucket=props['s3bucket_name'],
                s3_key=props['s3bucket_obj_key']
            ),
            
        )

BeanstalkAppVersionStack

In the code above, a new Beanstalk Application is created.

BeanstalkAppVersionStack is defined as a nested stack.

This stack deploys a new application version.

Defining a nested stack allows the support of dependencies.

BeanstalkAppVersionStack has a dependency on BeanstalkAppStack

You can’t deploy a new version when you don’t have an application.

Create Environment

BeanstalkEnvStack

from aws_cdk import (
    core,
    aws_elasticbeanstalk as elastic_beanstalk
)

import boto3

class BeanstalkEnvStack(core.Stack):

    def createEnvironment(self,application_name,environment_name,solution_stack_name):

        # get the latest beanstalk application version

        client = boto3.client('elasticbeanstalk')

        application_versions = client.describe_application_versions(
            ApplicationName=application_name
        )

        version_label = None

        if(len(application_versions['ApplicationVersions'])> 0):
            version_label = application_versions['ApplicationVersions'][0]['VersionLabel']

        beanstalk_env_config_template = elastic_beanstalk.CfnConfigurationTemplate(
                self,
                "Elastic-Beanstalk-Env-Config",
                application_name=application_name,
                solution_stack_name=solution_stack_name,
                option_settings=[
                                    elastic_beanstalk.CfnConfigurationTemplate.ConfigurationOptionSettingProperty(
                                        namespace="aws:autoscaling:asg",option_name="MinSize",value="2"
                                    ),

                                    elastic_beanstalk.CfnConfigurationTemplate.ConfigurationOptionSettingProperty(
                                        namespace="aws:autoscaling:asg",option_name="MaxSize",value="4"
                                    )
                                ]
                                
        )
        # configure the environment for auto-scaling
        beanstalk_env = elastic_beanstalk.CfnEnvironment(
            self,
            "Elastic-Beanstalk-Environment",
            application_name=application_name,
            environment_name=environment_name,
            solution_stack_name=solution_stack_name,
            version_label=version_label,
            
            option_settings=[
                                elastic_beanstalk.CfnEnvironment.OptionSettingProperty(
                                    namespace="aws:autoscaling:asg",option_name="MinSize",value="2"
                                ),
                                 elastic_beanstalk.CfnEnvironment.OptionSettingProperty(
                                    namespace="aws:autoscaling:asg",option_name="MaxSize",value="4"
                                ),
                                
                            ]
        )

        
      

    def __init__(self, scope: core.Construct, id: str, props, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        self.createEnvironment(props['application_name'],props['environment_name'],props['solution_stack_name'])

Diff

Run a cdk diff to see what changes will be applied in AWS.

Synth

If you wish to see what the CloudFormation templates look like.

cdk synth [stack-name].

By default these are stored inside the cdk.out directory.

Deploy

Wildcards are supported when using cdk deploy.

So, cdk deploy Beanstalk* will work.

Validate

Once deployed, you should be able to reach your new Beanstalk application using the public facing dynamic url.

In this case a welcome web page will open.

Destroy/clean up

To avoid un-necessary AWS costs, destroy the Beanstalk resources.

cdk destroy Beanstalk*

Last updated on 8 Jul 2020
Published on 8 Jul 2020