In certain instances, we might prefer deploying our applications on an EC2 instance without the burden of managing the associated infrastructure. AWS Elastic Beanstalk provides streamlined deployment, automatic scaling, and application management, enabling developers to concentrate on coding without being concerned about infrastructure configuration and management. So, let's explore how to deploy it using Terraform.
Pre-requisites
Have an IAM User with programmatic access.
Set up your AWS CLI locally.
Install Terraform CLI.
Have an AWS EC2 Key Pair ready to use.
The application
We'll use a standard .NET 6 application, which can be downloaded here. It is important to note that the Procfile
is used to instruct Elastic Beanstalk which applications to run, specifically for the .NET on a Linux environment:
web: dotnet exec ./WeatherApi.dll --urls http://0.0.0.0:5000/
Run the following command to create the artifact to deploy:
mkdir terraform/publish
dotnet publish ./src/WeatherApi.csproj --output "terraform/publish" --configuration "Release" --framework "net6.0" /p:GenerateRuntimeConfigurationFiles=true --runtime linux-x64 --no-self-contained
Compress-Archive -Path terraform/publish/* -DestinationPath terraform/app.zip
Defining the inputs
Create a variables.tf
file containing the following content:
variable "application" {
type = string
}
variable "vpc_id" {
type = string
}
variable "ec2_subnets" {
type = string
}
variable "elb_subnets" {
type = string
}
variable "instance_type" {
type = string
}
variable "keypair" {
type = string
}
variable "bucket" {
type = string
}
variable "health_check_path" {
type = string
}
Publishing the artifact to S3
Create a main.tf
with the following initial content:
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "4.22.0"
}
}
}
provider "aws"{
region = "us-east-2"
}
resource "aws_s3_bucket" "bucket" {
bucket = var.bucket
}
resource "aws_s3_object" "bucket_object" {
bucket = aws_s3_bucket.bucket.id
key = "app-${uuid()}.zip"
source = "app.zip"
}
Adding the Elastic Beanstalk Application and Environment
Add to the main.tf
file the following content:
data "aws_iam_policy_document" "assume_service_role" {
statement {
effect = "Allow"
principals {
type = "Service"
identifiers = ["elasticbeanstalk.amazonaws.com"]
}
actions = ["sts:AssumeRole"]
condition {
test = "StringEquals"
variable = "sts:ExternalId"
values = [
"elasticbeanstalk"
]
}
}
}
resource "aws_iam_role" "service_role" {
name = "aws-elasticbeanstalk-service-role"
assume_role_policy = data.aws_iam_policy_document.assume_service_role.json
}
resource "aws_iam_role_policy_attachment" "AWSElasticBeanstalkEnhancedHealth-attach" {
role = aws_iam_role.service_role.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSElasticBeanstalkEnhancedHealth"
}
resource "aws_iam_role_policy_attachment" "AWSElasticBeanstalkManagedUpdatesCustomerRolePolicy-attach" {
role = aws_iam_role.service_role.name
policy_arn = "arn:aws:iam::aws:policy/AWSElasticBeanstalkManagedUpdatesCustomerRolePolicy"
}
data "aws_iam_policy_document" "assume_role" {
statement {
effect = "Allow"
principals {
type = "Service"
identifiers = ["ec2.amazonaws.com"]
}
actions = ["sts:AssumeRole"]
}
}
resource "aws_iam_role" "role" {
name = "aws-elasticbeanstalk-ec2-role"
assume_role_policy = data.aws_iam_policy_document.assume_role.json
}
resource "aws_iam_role_policy_attachment" "CloudWatchFullAccess-attach" {
role = aws_iam_role.role.name
policy_arn = "arn:aws:iam::aws:policy/CloudWatchFullAccess"
}
resource "aws_iam_role_policy_attachment" "AWSElasticBeanstalkWebTier-attach" {
role = aws_iam_role.role.name
policy_arn = "arn:aws:iam::aws:policy/AWSElasticBeanstalkWebTier"
}
resource "aws_iam_role_policy_attachment" "AWSElasticBeanstalkWorkerTier-attach" {
role = aws_iam_role.role.name
policy_arn = "arn:aws:iam::aws:policy/AWSElasticBeanstalkWorkerTier"
}
resource "aws_iam_role_policy_attachment" "AWSElasticBeanstalkMulticontainerDocker-attach" {
role = aws_iam_role.role.name
policy_arn = "arn:aws:iam::aws:policy/AWSElasticBeanstalkMulticontainerDocker"
}
resource "aws_iam_instance_profile" "instance_profile" {
name = "aws-elasticbeanstalk-ec2-role"
role = aws_iam_role.role.name
}
resource "aws_elastic_beanstalk_application" "application" {
name = var.application
}
resource "aws_elastic_beanstalk_environment" "environment" {
name = "${var.application}-env"
application = aws_elastic_beanstalk_application.application.name
solution_stack_name = "64bit Amazon Linux 2 v2.5.2 running .NET Core"
tier = "WebServer"
setting {
namespace = "aws:ec2:vpc"
name = "VPCId"
value = var.vpc_id
}
setting {
namespace = "aws:ec2:vpc"
name = "Subnets"
value = var.ec2_subnets
}
setting {
namespace = "aws:ec2:vpc"
name = "ELBScheme"
value = "internet facing"
}
setting {
namespace = "aws:ec2:vpc"
name = "ELBSubnets"
value = var.elb_subnets
}
setting {
namespace = "aws:autoscaling:launchconfiguration"
name = "IamInstanceProfile"
value = aws_iam_role.role.name
}
setting {
namespace = "aws:autoscaling:launchconfiguration"
name = "InstanceType"
value = var.instance_type
}
setting {
namespace = "aws:autoscaling:launchconfiguration"
name = "EC2KeyName"
value = var.keypair
}
setting {
namespace = "aws:elasticbeanstalk:environment"
name = "LoadBalancerType"
value = "application"
}
setting {
namespace = "aws:elasticbeanstalk:environment"
name = "ServiceRole"
value = aws_iam_role.service_role.name
}
setting {
namespace = "aws:elasticbeanstalk:environment:proxy"
name = "ProxyServer"
value = "none"
}
setting {
namespace = "aws:elasticbeanstalk:environment:process:default"
name = "HealthCheckPath"
value = var.health_check_path
}
}
Depending on our requirements, we can refer to the documentation for all the available options for the environment. Let's discuss the ones we are using here:
VPCID
: The ID for your Amazon VPC.Subnets
: The IDs of the Auto Scaling group subnet or subnets.ELBScheme
: Specifyinternal
if we wish to create an internal load balancer in your Amazon VPC, which will prevent your Elastic Beanstalk application from being accessed outside your Amazon VPC.ELBSubnets
: The IDs of the subnet or subnets for the elastic load balancer.IamInstanceProfile
: An instance profile enables IAM users and AWS services to access temporary security credentials to make AWS API calls.InstanceType
: The instance type that's used to run our application in an Elastic Beanstalk environment.EC2KeyName
: Key pair used to securely log into your EC2 instance.LoadBalancerType
: The type of load balancer for our environment.ServiceRole
: The service role is the IAM role that Elastic Beanstalk assumes when calling other services on your behalf.HealthCheckPath
: The path that HTTP requests for health checks are sent to.ProxyServer
: Elastic Beanstalk comes with Nginx as a reverse proxy server that we can use if we want.
Adding the Application Version
An Application Version is a specific, labeled iteration of deployable code for an application. Add to the main.tf
file the following content:
resource "aws_elastic_beanstalk_application_version" "version" {
bucket = aws_s3_bucket.bucket.id
key = aws_s3_object.bucket_object.id
application = aws_elastic_beanstalk_application.application.name
name = "${var.application}-app-${uuid()}"
}
Defining the outputs
Create a outputs.tf
file containing the following content:
output "app_version" {
value = aws_elastic_beanstalk_application_version.version.name
}
output "env_name" {
value = aws_elastic_beanstalk_environment.environment.name
}
output "cname" {
value = aws_elastic_beanstalk_environment.environment.cname
}
The Deployment
Run the following commands:
cd terraform
terraform init
terraform plan -out app.tfplan -var="health_check_path=/swagger/index.html" -var="bucket=app-tf-001" -var="keypair=<MY_KEY_PAIR>" -var="instance_type=t2.medium" -var="application=app-tf-001" -var="vpc_id=<MY_VPC>" -var="ec2_subnets=<MY_SUBNETS>" -var="elb_subnets=<MY_SUBNETS>"
terraform apply 'app.tfplan'
Terraform will create the environment, application, and application version, but it will not deploy the application. To do this, we need to use the AWS CLI to update the environment's application version (use the outputs of the previous command):
aws --region us-east-2 elasticbeanstalk update-environment --environment-name <OUTPUT_ENV_NAME> --version-label <OUTPUT_APP_VERSION>
Open the URL http://<OUTPUT_CNAME>/swagger/index.html
to see our application up and running. To clean up all the created resources, execute the following commands:
terraform plan -destroy -out app.tfplan -var="health_check_path=/swagger/index.html" -var="bucket=app-tf-001" -var="keypair=<MY_KEY_PAIR>" -var="instance_type=t2.medium" -var="application=app-tf-001" -var="vpc_id=<MY_VPC>" -var="ec2_subnets=<MY_SUBNETS>" -var="elb_subnets=<MY_SUBNETS>"
terraform apply -destroy 'app.tfplan'
You can see the code and scripts here. Thanks, and happy coding.