This document introduces the use of the EB CLI (Elastic Beanstalk CLI) to manage the lifecycles of applications deployed on AWS Elastic Beanstalk. The standard usage is described for a nodeJS application.
Prerequisite:
- Python 2 version 2.7, or Python 3 version
- AWS CLI. That's technically not required, but it makes things easier. EB CLI relies on the same credentials files managed by the AWS CLI. To install the AWS CLI, please refer to my AWS CLI Guide.
Hopefully, you're using a MAC (for Windows users, good luck). For MAC:
brew update
brew install awsebcli
The EB CLI uses the same credentials than the AWS CLI, so make sure that you have those credentials set up on your machine. To set that up, please refer to my AWS CLI Guide.
A version is the actual code that is deployed to EB. There is no need to specify it explicitely. Each time the eb deploy
command is executed, a new version is automatically created. To deploy code to EB, an application with an environment must exist. An application can have an arbitrary number of environments (e.g., dev, test, prod). Each of those environment can be configured differently.
If you're after the step-by-step manual to setup your NodeJS project in EBS, then refer to the Pragmatic setup in the Annexes.
-
Create your local project the way you would normally do it (e.g., your typical NodeJS server).
WARNING: If you're deploying a web server, don't forget to listen to the correct port. The correct port is accessible via the environment variable
PORT
(e.g., NodeJS Express:app.listen(process.env.PORT)
). -
Create the application in AWS and initialize the project:
eb init --profile <your-profile-name>
-
Edit the
branch-defaults.master.environment
property in the.elasticbeanstalk/config.yml
file (set it to something likemy-app-test
,my-app-staging
ormy-app-prod
. The reason why you should prefix the environment with your something similar tomy-app
is explained in the Step 4 - Create an environment section). -
Create a new
.ebextensions
folder and add the necessary.config
files to setup your stack (ref. to Step 2 - Stack configuration). -
Create a new
.ebignore
to disconnect Git (copy/paste the content of the .gitignore in it). -
Create a new environment in AWS:
eb create <environment-name> --cname <cname>
where:
<environment-name>
is the same as the environment set in step #3.<cname>
must be a unique name that is used to create a unique URL as follow:<cname>.<region>.elasticbeanstalk.com
-
Later down the track, when you have new changes to deploy, use:
eb deploy
The EB CLI has a strongly opiniated way to work with Git that you must be aware of if you don't want to pull your hair out wondering why your deployments don't seem to do anything.
By default, when an application is using Git, only committed changed are deployed. The typical issue occurs when you've added a lot of changed to your app, but you've forgotten to execute git commit -am 'your new changes description'
before executing eb deploy
.
There are two ways to override this default behavior:
-
Use
eb deploy --staged
. This command deploys the changes in the staged tree rather than the head. This allows to not be forced to commit the changes before deploying, but it still requires to add the latest changes to the staged tree (git add .
). Because I mainly uses nodejs, my suggestions to nodejs readers is to leverage npm scripts to automate the deployments. For example, add something similar to this in your package.json:"scripts": { "deploy": "git add . & eb deploy --staged" }
With this, you can use the command
npm run deploy
with the peace of mind that what you see in your text editor is what's being deployed. -
Add an
.ebignore
file. Adding this file under the application root directory breaks the connection between git and the deployment command. Copy/paste what is normally in the .gitignore, so you can stop to worrying about what has been commited or not before a deployment.
This step serves two purposes:
- It initializes your local project with a new
.elasticbeanstalk
folder containing aconfig.yml
(see example under The.elasticbeanstalk/config.yml
section). - It creates the Elastic Beanstalk application in your AWS account. That's why it requires a valid AWS profile set up for that account on your local machine.
To complete this step, run the following command:
eb init --profile <your-profile-name>
This command contains some smart to detect the project stack (e.g., PHP, nodsejs). If it does not, pleae refer to the list of supported stack here.
When this command is completed, edit the .elasticbeanstalk/config.yml
and set the branch-defaults.master.environment
property to the whatever name makes sense to you (personally, I like using the application name followed by '-test' or '-prod'. I also use that name for the CNAME).
This step consists in configuring aspects of the application such as autoscaling, environment variable as well as settings specific to your stack. Depending on the application stack, there might be different tasks that have to run before starting the application. For example, to use a nodeJS server, the stack might need to start it using npm start
, otherwise, the application might not be able to respond though it has been successfully deployed (this usually results in a 502 Bad Gateway error message from the ngninx load balancer). To configure the extra steps:
- Create a new
.ebextensions
folder under the application root directory. - Add as many .config files as you need to configure the application (the name of the files does not matter. eb CLI processed them all. What matters is their content). Choosing between one or many .config file depends on your personal preferences or requirements.
- Configure the .config files. All of them must be structured as follow:
option_settings: - namespace: This might be optional. It depends on the setting. option_name: Name of your option. value: Option value. - namespace: Another one option_name: Another one value: Option Another one
There are two types of option settings:
-
General options: Those options helps configure EB itself (scaling, environment variables, security groups, ...). The list of all the options can be found here: https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/command-options-general.html. The following is shows how to set up an environment variable as well as the autoscaling:
option_settings: - namespace: aws:elasticbeanstalk:application:environment option_name: HELLO value: WORLD - namespace: aws:autoscaling:asg option_name: MinSize value: 1 - namespace: aws:autoscaling:asg option_name: MaxSize value: 2
Alternatively, this .config file can be broken down using a slightly different API:
austoscaling.config:
option_settings: aws:autoscaling:asg: MinSize: 1 MaxSize: 1
envvar.config:
option_settings: aws:elasticbeanstalk:application:environment: HELLO: WORLD
-
Stack specific options: Based on the stack you've deployed, there might be specific options required to configure it. You can find all of them at https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/command-options-specific.html. For example, an Express NodeJS app that is not using an app.js or a server.js as its starting script needs a start script in its package.json similar to this:
"scripts": { "start": "NODE_ENV=production node index.js" }
Where
index.js
is the file containing the JS code starting your Express server.IMPORTANT: IGNORE THE NEXT LINE IF YOU'RE USING LINUX 2 OS. JUMP TO THE LINUX 2 OS SECTION BELOW:
In that case, you have to add under the
.ebextensions
folder a .config file similar to this:whatevernameyoulike.config
option_settings: - namespace: aws:elasticbeanstalk:container:nodejs option_name: NodeCommand value: "npm start"
LINUX 2 (RECOMMENDED): With the latest OS, the
aws:elasticbeanstalk:container:nodejs
option is not supported anymore. This is replaced with the following process:- Create a new
Procfile
file in your project's root. - Add the following line:
web: npm start
- Create a new
As explained in the previous section Before we start you must know about EB CLI and Git, Git plays an important role
An environment is a contained set of resources (e.g., load balancer, security group, EC2 instances) needed to host your app. There can be an arbitrary amount of environments (e.g., dev, test, prod). An application can be deployed if there is not at least one environment.
To create an environment, make sure you've followed this step's prerequisites and then run the following command:
eb create <environment-name> --cname <cname> --elb-type <load-balancer-type>
Where:
<environment-name>
is the same as what has been set up in thebranch-defaults.master.environment
property in the.elasticbeanstalk/config.yml
file. Set it to something likemy-app-test
,my-app-staging
ormy-app-prod
. The reason why we recommmend you prefix the environment with something similar tomy-app
is because that label is also used to name the EC2 instances. This will become all very confusing when you list your EC2 instances.<cname>
is used to create the HTTP endpoint for the load balancer. That endpoint is follows this schema: cname.aws-region.elasticbeanstalk.com. This cname must be globally unique.--elb-type <load-balancer-type>
changes the default load balancer (classic, application (default), network). This is optional.
For a list of all the available options to create a new environment, please refer to https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/eb3-create.html#eb3-createoptions.
WARNING: This step is the ONLY CHANGE YOU HAVE to choose which load balancer you need (classic, application (default), network). Once you've choosen that LB type, you can't change it. However, you can change its configuration during each deployment.
To see how to create environments on existing VPC please refer to section How to create an environment with an existing VPC?
This step is automatically done after the environment creation (via eb create
), so you only have to use it for subsequent changes. Use this command to start a new deployment:
eb deploy
IMPORTANT: If you're using git, this only deploys the last commited changes, unless you're using a
.ebignore
file as described in Before we start you must know about EB CLI and Git.
To deploy the changes that are in the staged tree, use:
eb deploy --staged
TIPS: To avoid missing changes, add scripts to automate your deployments as described previously in the Before we start you must know about EB CLI and Git section.
Those commands deploy the application to the current environment in the current account.
This step is optional. If you need to change the configuration (e.g., the min or max number of instances), sue this command:
eb config
---
branch-defaults:
master:
environment: "your-app-name-test"
group_suffix: null
global:
application_name: "your-app-name"
branch: null
default_ec2_keyname: null
default_platform: "64bit Amazon Linux 2 v5.6.4 running Node.js 14"
default_region: "ap-southeast-2"
include_git_submodules: true
instance_profile: null
platform_name: null
platform_version: null
profile: "your-awscli-profile-name"
repository: null
sc: "git"
workspace_type: "Application"
Notice:
default_platform: "64bit Amazon Linux 2 v5.6.4 running Node.js 14"
. This sets up the underlying OS. To list all the available platforms, use this command:aws elasticbeanstalk list-available-solution-stacks
Though it is possible to use CloudFormation to manage an EB stack, that can be quite challenging due to the fact that the deployment of new code changes should also be manage by CF. Indeed, if CF is used to set up the EB stack and the EB CLI use subsequently used to deploy code, then any other changes made with CF will overide all changes made with the Eb CLI. That's why I personaly prefer to stick with the EB CLI to manage the lifecycles of the EB stack.
In any case, the next section shows how to create a CF template to create a nodeJS EB stack.
AWSTemplateFormatVersion: '2010-09-09'
Description:
Sets of resources required to provision your application.
Parameters:
Environment:
Description: 'The operating environment'
Type: String
Default: dev
AllowedValues: [ dev, prod ]
ApplicationName:
Type: String
Default: my-super-app
Conditions:
isProd: !Equals [ !Ref Environment, prod]
Resources:
yourApplication:
Type: AWS::ElasticBeanstalk::Application
Properties:
ApplicationName: !Ref ApplicationName
Description: AWS Elastic Beanstalk microservice that does something awesome.
yourConfigurationTemplate:
Type: AWS::ElasticBeanstalk::ConfigurationTemplate
Properties:
ApplicationName: !Ref yourApplication
Description: AWS ElasticBeanstalk Sample Configuration Template
OptionSettings:
- Namespace: aws:autoscaling:asg
OptionName: MinSize
Value: '1'
- Namespace: aws:autoscaling:asg
OptionName: MaxSize
Value: '2'
- Namespace: aws:elasticbeanstalk:environment
OptionName: EnvironmentType
Value: LoadBalanced
SolutionStackName: 64bit Amazon Linux 2018.03 v4.12.0 running Node.js
yourEnvironment:
Type: AWS::ElasticBeanstalk::Environment
Properties:
ApplicationName: !Ref yourApplication
EnvironmentName: !If [isProd, 'prod', 'test']
Description: AWS ElasticBeanstalk application environment
CNAMEPrefix : !Join [ '-', [ !Ref ApplicationName, !Ref Environment ] ]
TemplateName: !Ref yourConfigurationTemplate
A list of all the supported solution stack name (SolutionStackName
) can be found Elastic Beanstalk Supported Platforms.
To deploy this template, use the AWS CLI with this command:
aws cloudformation deploy --profile <your-profile-name> --template-file <path-to-template>.yml --stack-name <your-stack-name> --capabilities CAPABILITY_IAM --parameter-overrides Environment=staging",
Original doc: https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/using-features.CNAMESwap.html
- Create a new environement using the same project but a different name.
- Upon creation, check that everything is working fine.
- Swap URLs.
This is either a serious or benign issue:
- Benign issue: This often happens when your healthcheck endpoint is badly configured. By default, the healthcheck is hosted on port 80 on pathname
/
. If you have no endpoint listening on that port and path, then AWS EBS will infer your API is in severe state because no traffic can reach it. If that's the case, simply create an echo endpoint on port 80 for pathname/
. If on the other hand, you do have a dedicated healthcheck endpoint which is not hosted on port 80 for pathname/
, then you must configure that endpoint in your AWS EBS configuration. - Serious issue. Your app might be down. Test it manually to confirm. If that's the case, check the logs to figure out what's going on.
This error message appears in your browser when you're hitting your Web App.
Potential reasons:
- Check that your web server is configured so it starts. For example, in NodeJS, you need to add a config so that the
npm start
command is executed when the server starts (ref. to Step 2 - Stack configuration for more details). - Your web server is not listening on the right port. The right HTTP port listener is defined in the environment variable
PORT
(e.g., with an Express NodeJS server, use this code snippet to start the server:app.listen(process.env.PORT)
).
If none of those reasons can explain this issue, download the logs via the AWS Console.
This configuation is maintained in the .elasticbeanstalk/config.yml
file under the default_platform
property. If the OS upgrade is too far ahead of the current version, you must proceed to a Blue/Green deployments.
Features/Facets | App Engine | Elastic Beanstalk |
---|---|---|
Ease of use | Very easy | Easy to painful |
Scalability | High | High |
HTTPS | Out-of-the-box with no config | Have to be manually configured. SSL must be provided |
Can scale to 0 | Yes | No |
https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/command-options-general.html
Use the default_platform
property under the .elasticbeanstalk/config.yml
file.
Add a new .config
file under the .ebextensions
folder similar to this:
option_settings:
aws:elasticbeanstalk:application:environment:
HELLO: world
HELL: yeah
To use environment variable locally, I would recommend to use shell script similar to this:
#!/bin/sh
export HELLO=world
export HELL=yeah
And then use something like source env.sh
.
Add the following .config
file under the .ebextensions
folder:
option_settings:
aws:elasticbeanstalk:cloudwatch:logs:
StreamLogs: true
eb config
Under the aws:autoscaling:asg
section, update one of the following config:
MaxSize
MinSize
Add a new .config
file under the .ebextensions
folder similar to this:
option_settings:
aws:elb:listener:
ListenerProtocol: TCP
InstancePort: 8081
aws:elb:listener:3000:
ListenerProtocol: TCP
InstancePort: 8081
This config setup the default port (i.e., 80) to redirect TCP traffic onto port 8081 on the EC2 instance as well as port 3000.
Let's assume the following:
- The VPC is called
vpc-a
and it is located Sydney which has only two AZs. - There are four existing subnets :
subnet-private-a
for AZ-1subnet-private-b
for AZ-2subnet-public-a
for AZ-1subnet-public-b
for AZ-2
This use case covers scenarios where the resources accessible via the EB stack cannot be accessed from the internet (i.e., publicly) for security reasons. Both the load balancer and the EC2 instances live in the private subnets.
eb create <environment-name> --cname <cname> --elb-type application \
--vpc.id vpc-a \
--vpc.elbsubnets subnet-private-a,subnet-private-b \
--vpc.ec2subnets subnet-private-a,subnet-private-b \
eb create <environment-name> --cname <cname> --elb-type application \
--vpc.id vpc-a \
--vpc.elbsubnets subnet-public-a,subnet-public-b \
--vpc.ec2subnets subnet-private-a,subnet-private-b \
--vpc.elbpublic
Notice that even though the load balancer is in a public subnet, you still need to toggle its public access via the --vpc.elbpublic
option.
Add the new ingress rules in the security group that allows traffic between the LB and the EC2 instance by adding a new .config
file under the .ebextensions
folder. The following example shows how to add public access on port 8081 for both IPv4 and IPv6:
Resources:
PublicIPv4Traffic:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: {"Fn::GetAtt" : ["AWSEBSecurityGroup", "GroupId"]}
IpProtocol: tcp
FromPort: 8081
ToPort: 8081
CidrIp: 0.0.0.0/0
PublicIPv6Traffic:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: {"Fn::GetAtt" : ["AWSEBSecurityGroup", "GroupId"]}
IpProtocol: tcp
FromPort: 8081
ToPort: 8081
CidrIpv6: ::/0
Command | Description |
---|---|
eb --help> |
Show all the available eb CLI's commands. |
eb init --profile <your-profile-name> |
Initializes the project. |
eb list |
Lists all the EB environments created for the current AWS Account(1). |
eb create |
Creates a new environment for the current AWS Account(1). |
eb deploy |
Deploys the latest git commit to the active environment (to switch environment, please refer to command eb use). |
eb deploy --staged |
Deploys the staged git changes to the active environment (to switch environment, please refer to command eb use). |
eb terminate |
Deletes the current environments with all its resources, except the S3 buckets. |
eb use <environment name> |
Switches to another environment. |
eb config |
Configure the current environment. |
(1) The current AWS account is defined via the profile field in the .elasticbeanstalk/config.yml.
- Packer Builder
- Single Container Docker
- Multicontainer Docker
- Preconfigured Docker
- Go
- Java SE
- Java with Tomcat
- .NET on Windows Server with IIS
- Node.js
- PHP
- Python
- Ruby
The main strategy detailed below consists in not putting the .ebextensions
and .elasticbeanstalk
folders under source control and to a large extend, not really care about them. Instead, their files are described in environment specific folders (e.g., env/test
, env/prod
) which are put under source control. Those files overrides the .ebextensions
and .elasticbeanstalk
before each eb create or eb deploy operations via NPM scripts.
- Create your local Web app normally, and don't forget to set the port properly (e.g., NodeJS:
app.listen(process.env.PORT)
). - Add the following config to your
.gitignore
:
# Elastic Beanstalk Files
.ebextensions/*
.elasticbeanstalk/*
The configuration files that should normally be added under those folders are moved into a custom
env
folder in order to support different configurations that are environment specific (cf. step #6).
- Create a new
.ebignore
file, and copy/paste the content of the.gitignore
in it, EXCEPT the extra lines from step #2. We need the content of both the.ebextensions
and.elasticbeanstalk
to deploy the app. - Create a new Application in AWS with this command:
eb init --profile <your-profile-name>
- Edit the
branch-defaults.master.environment
property in the.elasticbeanstalk/config.yml
file (e.g.,my-app-test
) as well as the other fields (refer to the.elasticbeanstalk/config.yml
section). - Instead of creating an
.ebextensions
folder create a newenv
folder with as many environments folder as you need (e.g.,test
,prod
). In each of those folders:- Create the
.config
files you would have configured under the.ebextensions
folder, and make them specific to each environment. - Copy/paste the
.elasticbeanstalk/config.yml
and make it specific to each environment. - Create a new
env.local
file with something similar to:
#!/bin/sh export ENV=test export CNAME=my-app-test
- Create the
- Add a new script in the
package.json
file as follow:"scripts": { "reset-folders": "rm -rf .ebextensions && mkdir .ebextensions || true && rm -rf .elasticbeanstalk && mkdir .elasticbeanstalk || true", "setup": "npm run reset-folders && cp -a env/$ENV/*.config .ebextensions && cp -a env/$ENV/*.yml .elasticbeanstalk", "create": "npm run setup && eb create $CNAME --cname $CNAME --elb-type application", "deploy": "npm run setup && eb deploy", "create:test": "source ./env/test/env.local && npm run create", "create:prod": "source ./env/prod/env.local && npm run create", "deploy:test": "source ./env/test/env.local && npm run deploy", "deploy:prod": "source ./env/prod/env.local && npm run deploy" }
- Create a
Procfile
in your project's root folder as explained in the Step 2 - Stack configuration section. - Create a new environment in AWS or deploy with
npm run create:<env>
ornpm run deploy:<env>
.