Deploying a web application on AWS Elastic Beanstalk can sometimes require environment-specific customizations, such as configuring a database, modifying server settings, or installing additional software. Elastic Beanstalk extensions, or .ebextensions, are a set of configuration files used to customize and configure AWS Elastic Beanstalk environments. These files allow us easily tailor our environment to accommodate these requirements, ensuring a seamless deployment and optimal performance for your application.
To use .ebextensions, create a folder named .ebextensions
in the root directory of our source bundle. Inside this folder, add one or more configuration files with a .config
extension. These files should be written in YAML or JSON format and contain instructions to customize your AWS Elastic Beanstalk environment. When you deploy your application, Elastic Beanstalk will read and apply the configurations specified in these files.
The sections of those .config
files can be grouped into three categories:
Options settings: Used to change any AWS Elastic Beanstalk configuration option.
Custom resources: Let us define additional AWS resources beyond the functionality provided by configuration options. You can add and configure any resources supported by AWS CloudFormation.
Software customization: Let us configure the EC2 instances. Every time a server is launched in your environment, AWS Elastic Beanstalk runs the operations defined to prepare the operating system.
In a previous post, we discussed How to Deploy a Windows Service on AWS Elastic Beanstalk using Terraform. Let's add a couple of requirements to our previous exercise:
Disable the operative system firewall.
Redirect HTTP traffic to HTTPS at the load balancer level.
Create a .ebextensions
folder at the solution level, and within it, create a disable-firewall.config
file with the following content:
files:
"C:\\scripts\\firewall.ps1":
content: |
Set-NetFirewallProfile -Enabled False
commands:
disable_firewall:
command: powershell.exe -ExecutionPolicy Bypass -Command "C:\\scripts\\firewall.ps1"
ignoreErrors: false
waitAfterCompletion: 5
Files
and Commands
are known as Keys, and all of them are:
Packages
: To download and install prepackaged applications and components.Sources
: To download an archive file from a public URL and unpack it in a target directory.Files
: To create files on the EC2 instance.Commands
: To execute commands on the EC2 instance.Services
: To define which services should be started or stopped when the instance is launched.Container commands
: To execute commands that affect your application source code. Container commands run after the application and web server have been set up and the application version archive has been extracted, but before the application version is deployed.
To redirect HTTP traffic to HTTPS, we need to modify our Terraform main.tf file to add the listener on port 433:
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "4.22.0"
}
}
}
provider "aws"{
region = "us-east-2"
}
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_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"
}
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 = var.platform
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:process:default"
name = "HealthCheckPath"
value = var.health_check_path
}
setting {
namespace = "aws:elbv2:listener:443"
name = "Protocol"
value = "HTTPS"
}
setting {
namespace = "aws:elbv2:listener:443"
name = "SSLCertificateArns"
value = var.ssl_certificate
}
setting {
namespace = "aws:elbv2:listener:443"
name = "SSLPolicy"
value = "ELBSecurityPolicy-2016-08"
}
}
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()}"
}
Under .ebextensions
folder, add an alb-http-to-https-redirection.config
file as follows:
Resources:
AWSEBV2LoadBalancerListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
LoadBalancerArn:
Ref: AWSEBV2LoadBalancer
Port: 80
Protocol: HTTP
DefaultActions:
- Type: redirect
RedirectConfig:
Host: "#{host}"
Path: "/#{path}"
Port: "443"
Protocol: "HTTPS"
Query: "#{query}"
StatusCode: "HTTP_301"
The resources that AWS Elastic Beanstalk creates for our environment can be found here. To create the bundle for deployment, execute the following commands:
dotnet publish ./src/WeatherApi/WeatherApi.csproj --output "terraform/publish/webapi" --configuration "Release" --framework "net6.0" /p:GenerateRuntimeConfigurationFiles=true --runtime win-x64 --no-self-contained
Compress-Archive -Path terraform/publish/webapi/* -DestinationPath terraform/bundle/site.zip
dotnet publish ./src/WeatherWs/WeatherWs.csproj --output "terraform/bundle/ws" --configuration "Release" --framework "net6.0" /p:GenerateRuntimeConfigurationFiles=true --runtime win-x64 --no-self-contained
copy .\install.ps1 .\terraform\bundle
copy .\aws-windows-deployment-manifest.json .\terraform\bundle
mkdir .\terraform\bundle\.ebextensions
copy .\.ebextensions\* .\terraform\bundle\.ebextensions
Compress-Archive -Path terraform/bundle/* -DestinationPath terraform/app.zip
The contents of the bundle will appear as follows:
|-- .ebextensions
|-- ws
|-- aws-windows-deployment-manifest.json
|-- install.ps1
`-- site.zip
Run the terraform scripts with 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>" -var="platform=64bit Windows Server 2019 v2.11.3 running IIS 10.0" -var ssl_certificate="<MY_SSL_CERTIFICATE>"
terraform apply 'app.tfplan'
And deploy the application version with the following command:
aws --region us-east-2 elasticbeanstalk update-environment --environment-name <OUTPUT_ENV_NAME> --version-label <OUTPUT_APP_VERSION>
In conclusion, AWS Elastic Beanstalk's .ebextensions provide a powerful and flexible way to customize your environment to meet specific requirements. By following the steps outlined in this article, you can easily configure various aspects of your application, such as disabling the operating system firewall and redirecting HTTP traffic to HTTPS. With a better understanding of .ebextensions, you can now further explore and leverage their capabilities to enhance your application's deployment and performance. You can find several config
files under this repository. The code and scripts used in this article are available here. Thanks, and happy coding.