In my previous post of this series, we saw how we can deploy a Single Page Application on S3 and Cloudfront with CI/CD via Codebuild using aws-cdk
.
Before reading this, I would recommend that you check out my previous post in this series where I have explained the advantages of using the aws-cdk
.
In this post, we shall see how we can deploy a Node application to Elastic Beanstalk and the same Continuous Deployment setup with Codebuild that we had done last time using the CDK.
TLDR; all the code is in this repo if you want to start hacking right away!
Note: This post assumes that you have aws-cli installed and configured an AWS profile with an access and secret key via aws configure
.
We will be creating 3 services for our project.
An Elastic Beanstalk (EB) application that will hold our application and its environments (
develop
,prod
etc.).An EB environment that we will create to deploy our Node app.
A Codebuild project that will trigger whenever your code is pushed or a PR is merged.
Let's start with the EB Application. The code will be as follows:
import * as EB from '@aws-cdk/aws-elasticbeanstalk';
const ebApp = new EB.CfnApplication(this, `${cfg.APP_NAME}-app`, {
applicationName: cfg.APP_NAME,
});
First we import the aws-elasticbeanstalk
package and tell the cdk to create an EB application with the name specified in our configuration. This name is passed via an .env
file and we can add any name we want the application to be.
Note: I have provided an example env file in the repo so you can copy that and change it to the values you prefer.
That was all to creating an EB application. Our next step is setting up an EB environment in which our Node app will be deployed.
These environments are just like the different environments we have during the app development lifecycle. For e.g. develop
for development, and production
for our main application that the end user will interact with.
So let's create the EB environment as follows:
const platform = this.node.tryGetContext('platform');
const options: EB.CfnEnvironment.OptionSettingProperty[] = [
{
namespace: 'aws:autoscaling:launchconfiguration',
optionName: 'IamInstanceProfile',
value: 'aws-elasticbeanstalk-ec2-role',
},
];
const ebEnv = new EB.CfnEnvironment(this, `${cfg.APP_NAME}-env`, {
// default environmentName is `develop` as stated in `config.ts`
environmentName: cfg.APP_STAGE_NAME,
applicationName: ebApp.applicationName,
platformArn: platform,
optionSettings: options,
});
ebEnv.addDependsOn(ebApp);
Let's start with creating the environment using the CfnEnvironment
class. We pass the context and application name as usual and in the last parameter, we're passing a set of props that are required to create our environment. The props that standout currently are platformArn
, optionSettings
and solutionStackName
. Let's go through these.
- The
platformArn
prop is used to specify which system and what application plaform that we will be using. Beanstalk supports many platforms like Node, Python, Ruby etc. and from this we will be adding a platform property in ourcdk.json
file. This will tell Beanstalk to use the given platform.
Currently as a platform we have the following value: arn:aws:elasticbeanstalk:us-east-1::platform/Node.js 12 running on 64bit Amazon Linux 2/5.0.2
. It means that we will be running Amazon Linux 2 with Node 10 support that has been recently released.
The way we get this value from the cdk.json
is in the following manner.
const platform = this.node.tryGetContext('platform');
The method tryGetContext
returns the value of the property that we pass to it. So platform
will return the value of the platform key inside the context
key from cdk.json
.
The
optionSettings
prop is used to provide Beanstalk the EC2 role to create an instance. Without this, we won't be able to create an EC2 instance. We create this using theOptionSettingProperty
object.The last prop
solutionStackName
is the stack we will be using. This will create for us a sample Node app with all the defaults set.
Note: The value in solutionStackName
is not random, but one that AWS provides by default. This is true for all platforms (Node, Python etc.) and you can choose the one you want to for the specific platform you're building.
The last part is the following line:
ebEnv.addDependsOn(ebApp);
This is added to ensure that the EB environment is only created after the EB application. This is necessary as there being no implicit dependency between the two, we have to specify it explicitly as the environment cannot be created without the application.
Now we are move on to the third and final service, i.e. creating a Codebuild project.
First, we create a GitHub repository source that Codebuild can use.
Note: You can create a Bitbucket repo in the same manner as well.
const repo = Codebuild.Source.gitHub({
owner: cfg.REPO_OWNER,
repo: cfg.REPO_NAME,
webhook: true,
webhookFilters: webhooks,
reportBuildStatus: true,
});
The above code will create our repository that will act as a source to our Codebuid Project. We have passed the owner of the repo and the repository name as well.
You must have noticed that we have passed something called webhooks
set to true
and also webhookFilters
. What are those?
Webhook filters allow you to run the build on any branch based on the conditions and action on the branch.
We have added a webhook in the following manner
import * as Codebuild from '@aws-cdk/aws-codebuild';
const webhooks: Codebuild.FilterGroup[] = [
Codebuild.FilterGroup.inEventOf(
Codebuild.EventAction.PUSH,
Codebuild.EventAction.PULL_REQUEST_MERGED
).andHeadRefIs(cfg.BUILD_BRANCH),
];
This webhook states that on PUSH and PULL REQUEST MERGED on the branch specified in our config, initiate the build runner in Codebuild. As an example, we will be using the master
branch. So any push or any PR merge to the master will trigger the build.
Lastly, we shall combine all this in creating our Codebuild project as shown below.
const project = new Codebuild.Project(this, `${cfg.APP_NAME}`, {
buildSpec: Codebuild.BuildSpec.fromSourceFilename('buildspec.yml'),
projectName: `${cfg.APP_NAME}-build`,
environment: {
buildImage: Codebuild.LinuxBuildImage.AMAZON_LINUX_2_3,
computeType: Codebuild.ComputeType.SMALL,
},
source: repo,
timeout: cdk.Duration.minutes(20),
});
Here we are telling Codebuild to create a project for the source that we have added above (via GitHub) and we specify parameters related to the build image.
One last thing left right now. Our Codebuild setup needs access to Beanstalk and its related services to deploy the application and AWS has just the policy for that.
So let's add an AWS Managed policy our codebuild project.
project.role.addManagedPolicy(
IAM.ManagedPolicy.fromAwsManagedPolicyName(
'AWSElasticBeanstalkFullAccess'
)
);
This adds the already created policy AWSElasticBeanstalkFullAccess
by AWS and allows Codebuild to deploy to Beanstalk on our behalf.
So we're done and the only thing required now for us to test is to create a repository with a simple Node application with something like express.
Then replace all the config variables with the one's related to the repository and then run npm run deploy -- --profile <profileName>
where profileName
is the one you configured with the aws-cli
.
I have added a sample buildspec.yml
below that you can tweak and add in your repository.
version: 0.2
phases:
install:
runtime-versions:
python: 3.7
pre_build:
commands:
- echo Installing eb-cli...
- pip3 install awsebcli --upgrade
build:
commands:
- echo Build started on `date`
- eb deploy $EB_STAGE --staged
finally:
- echo Build completed on `date`
Here, I have used Codebuild's environment variables to refer to the EB environment that we will be deploying to. You can add those in the Codebuild build project from the console or directly add it in the file above as configuration (I have done that in the repo!).
Thanks for reading and do spread this post to all the cloud enthusiasts out there! Also do let me know which AWS service to cover next :)
Top comments (3)
@ryands17 i skipped the build part and created a pipeline which directly triggers the elasticbeanstalk
I will try with
eb deploy
alsobuildspec.yml
will be specific for node js project oris above buildspec.yml you have mentioned is generic and will it pick up node build stepany idea about below issue , i am using buildspec.yml as above provided by you -
dev-to-uploads.s3.amazonaws.com/up...
Hi,
It will automatically pick up the Node build step. In the screenshot you have shared, it seems that you are not uploading the Beanstalk related folder required for
eb deploy
to work. Could you try runningeb deploy
from your local machine once to see if it works?