In this blog, I will provide a comprehensive guide on how to set up a Continuous Integration/Continuous Deployment (CI/CD) pipeline for an AWS Elastic Beanstalk application using the AWS Cloud Development Kit (CDK). I will break down the code into smaller sections, explaining each part's purpose.
Step 1: Project Structure and Setup
The project is structured into three primary files:
stacks/eb-codepipeline-stack.ts
config/config.yaml
bin/main.ts
This organized structure makes it easier to manage the deployment process and its configurations.
stacks/eb-codepipeline-stack.ts
: This file contains the code to define the AWS infrastructure for your Elastic Beanstalk application and the CI/CD pipeline using AWS CDK.config/config.yaml
: This file stores the configuration values for different environments, such as development (dev) and production (prod).bin/main.ts
: This is the entry point for the CDK app, where we create the app and instantiate the CDK stack based on the environment specified.
Now, let's dive into the details of the code.
Step 2: Elastic Beanstalk Configuration
In the eb-codepipeline-stack.ts
file, we start by configuring the Elastic Beanstalk environment. This is where your web application will be hosted. Let's break down the code:
Section 2.1: Asset Configuration
const webAppZipArchive = new Asset(this, 'expressjs-app-zip', {
path: `${__dirname}/../express-app`,
});
Here, we create an asset named 'expressjs-app-zip' from your application code, which is stored in the 'express-app'
directory.
Section 2.2: Application Creation
const app = new CfnApplication(this, 'eb-application', {
applicationName: appName,
});
We define the Elastic Beanstalk application by providing it with a name.
Section 2.3: Application Version Configuration
const appVersionProps = new CfnApplicationVersion(this, 'eb-app-version', {
applicationName: appName,
sourceBundle: {
s3Bucket: webAppZipArchive.s3BucketName,
s3Key: webAppZipArchive.s3ObjectKey,
},
});
appVersionProps.addDependency(app); // here we want to create app before its version
Here we're creating a version of your Elastic Beanstalk application, capturing the code from an S3 bucket and associating it with the application named 'appName'
.
The appVersionProps.addDependency(app)
line ensures that the Elastic Beanstalk Application is created before its associated Application Version.
Section 2.4: Create instance profile
const instanceRole = new Role(
this,
`${appName}-aws-elasticbeanstalk-ec2-role`,
{
assumedBy: new ServicePrincipal('ec2.amazonaws.com'),
}
);
const managedPolicy = ManagedPolicy.fromAwsManagedPolicyName(
'AWSElasticBeanstalkWebTier'
);
instanceRole.addManagedPolicy(managedPolicy);
const instanceProfileName = `${appName}-instance-profile`;
const instanceProfile = new CfnInstanceProfile(this, instanceProfileName, {
instanceProfileName: instanceProfileName,
roles: [instanceRole.roleName],
});
Here we're creating an instance profile for the Elastic Beanstalk application with an AWS-managed policy 'AWSElasticBeanstalkWebTier'
to ensure it has proper permissions.
Section 2.5: Create Option Setting properties
const optionSettingProperties: CfnEnvironment.OptionSettingProperty[] = [
{
namespace: 'aws:autoscaling:launchconfiguration',
optionName: 'IamInstanceProfile',
value: instanceProfileName,
},
{
namespace: 'aws:autoscaling:asg',
optionName: 'MinSize',
value: minSize || DEFAULT_SIZE,
},
{
namespace: 'aws:autoscaling:asg',
optionName: 'MaxSize',
value: maxSize || DEFAULT_SIZE,
},
{
namespace: 'aws:ec2:instances',
optionName: 'InstanceTypes',
value: instanceTypes || DEFAULT_INSTANCE_TYPE,
},
];
// here we're adding ssl properties if ARN defined
if (sslCertificateArn) {
optionSettingProperties.push(
{
namespace: 'aws:elasticbeanstalk:environment',
optionName: 'LoadBalancerType',
value: 'application',
},
{
namespace: 'aws:elbv2:listener:443',
optionName: 'ListenerEnabled',
value: 'true',
},
{
namespace: 'aws:elbv2:listener:443',
optionName: 'SSLCertificateArns',
value: sslCertificateArn,
},
{
namespace: 'aws:elbv2:listener:443',
optionName: 'Protocol',
value: 'HTTPS',
}
);
}
Here we're setting an Option Setting Properties for the Elastic Beanstalk application environment.
Section 2.6: Create ElasticBeanstalk Environment
const ebEnvironment = new CfnEnvironment(this, 'eb-environment', {
environmentName: envName,
applicationName: appName,
solutionStackName: '64bit Amazon Linux 2 v5.8.0 running Node.js 18',
optionSettings: optionSettingProperties,
versionLabel: appVersionProps.ref,
});
Here we're creating an Elastic Beanstalk environment for hosting an application, specifying its name, associated application, runtime stack, and various configurations like instance types and SSL certificates.
Section 2.7: Output ElasticBeanstalk Environment (ALB) endpoint URL
new CfnOutput(this, 'eb-url-endpoint', {
value: ebEnvironment.attrEndpointUrl,
description: 'URL endpoint for the elasticbeanstalk',
});
The above code sets up an application version, pointing to the asset created earlier, which represents your application code.
The code also includes creating an instance profile, defining environment options, handling SSL certificates if provided, and outputting URL.
Step 3: CodePipeline Configuration
Moving on to the CI/CD pipeline configuration. This code sets up a CodePipeline with Source, Build, and Deployment stages.
Section 3.1: Source Stage Configuration
const sourceOutput = new Artifact();
const sourceAction = new GitHubSourceAction({
actionName: 'GitHub', // we're using github here, can be codecommit, bitbucket etc
owner: githubRepoOwner,
repo: githubRepoName,
branch: branch,
oauthToken: SecretValue.secretsManager(githubAccessTokenName),
output: sourceOutput,
});
Here, we configure the source stage. It uses a GitHub repository as the source, which can be triggered based on the specified branch.
Section 3.2: Build Stage Configuration
const buildOutput = new Artifact();
const buildProject = new Project(this, 'codebuild-project', {
buildSpec: BuildSpec.fromObject({
version: '0.2',
phases: {
install: {
commands: ['npm install'],
},
build: {
commands: ['npm run build'],
},
},
artifacts: {
files: ['**/*'],
},
}),
environment: {
buildImage: LinuxBuildImage.STANDARD_5_0,
},
});
In this section, we set up the Build stage. It defines a CodeBuild project with build specifications, including installing dependencies and building the application.
Section 3.3: Deployment Stage Configuration
const deployAction = new ElasticBeanstalkDeployAction({
actionName: 'ElasticBeanstalk',
applicationName: appName,
environmentName: envName || 'eb-nodejs-app-environment',
input: projectType === 'ts' ? buildOutput : sourceOutput,
});
The deployment stage is configured to deploy the application to Elastic Beanstalk. It depends on whether your project is TypeScript or JavaScript.
Step 4: Pipeline (CodePipeline) Creation
Finally, we create the pipeline by specifying the order of stages based on the project type:
const jsProject = [
{ stageName: 'Source', actions: [sourceAction] },
{ stageName: 'Deploy', actions: [deployAction] },
];
const tsProject = [
{ stageName: 'Source', actions: [sourceAction] },
{ stageName: 'Build', actions: [buildAction] },
{ stageName: 'Deploy', actions: [deployAction] },
];
const stages = projectType === 'ts' ? tsProject : jsProject;
const codePipeline = new Pipeline(this, 'codepipeline', {
pipelineName: pipelineName,
artifactBucket: getPipelineBucket,
stages,
});
Here, we define the pipeline stages based on the project type. If it's TypeScript, it includes the Build stage; otherwise, it skips it.
Step 5: Configuration in config/config.yaml
The config.yaml
file contains configuration values for your AWS CDK setup, specifically for different environments, such as dev (development) and prod (production). Here's a more detailed breakdown of the config.yaml
file:
environmentType: # define the type of environment (e.g., "dev" or "prod").
githubRepoName: # provide the name of the GitHub repository for your source code (Nodjes).
githubRepoOwner: # specify the owner of the GitHub repository.
githubAccessTokenName: # name of the secret in AWS Secrets Manager storing your GitHub access token.
projectType: ts # specify the project type as either "ts" (TypeScript) or "js" (JavaScript).
dev: # configuration specific to the development environment.
stackName: # define the name of the CDK stack for the dev environment.
branch: # specify the branch to trigger the pipeline for the dev environment.
pipelineBucket: # name of the S3 bucket to store artifacts for the dev environment (make sure you create one before deploying)
pipelineConfig:
name: # specify a name for the pipeline configuration in the dev environment.
minSize: 1 # minimum size for the Elastic Beanstalk environment in the dev environment.
maxSize: 1 # maximum size for the Elastic Beanstalk environment in the dev environment.
instanceTypes: t2.micro # Define the instance type for the Elastic Beanstalk environment in the dev environment.
ebEnvName: # specify the name of the Elastic Beanstalk environment in the dev environment.
ebAppName: # specify the name of the Elastic Beanstalk application in the dev environment.
# similarly, you can configure the "prod" environment with the same set of parameters.
In the config.yaml
file, you can define configuration values for both development and production environments. These values are essential for customizing your AWS CDK application to suit different stages of your deployment pipeline. Make sure to adjust these settings according to your project requirements for each environment.
This configuration file plays a crucial role in the flexibility and customization of your CDK setup, allowing you to maintain separate configurations for different environments.
Step 6: App Synthesis and Deployment
In the main.ts
file, we create a CDK app and instantiate the CDK stack based on the environment, either dev or prod:
if (devProps?.stackName?.length) {
const devStackName = devProps.stackName;
new EbCodePipelineStack(app, devStackName, devProps);
} else {
const prodStackName = prodProps?.stackName;
new EbCodePipelineStack(app, prodStackName, prodProps);
}
app.synth();
This code initializes the CDK app and the stack based on the chosen environment, allowing you to create a CI/CD pipeline for your Elastic Beanstalk application.
In conclusion, this blog post detailed the code used to create a CI/CD pipeline for AWS Elastic Beanstalk using AWS CDK. We walked through setting up Elastic Beanstalk, configuring the pipeline, and explained each section of the code to help you understand how to deploy your application with ease.
Here's the GitHub repo link to the code. ๐