Provisioning AWS Infrastructure for Security and Continuous Delivery with Terraform and Elastic Beanstalk
In this workshop you will learn how to provision infrastructure in AWS using tools for automating everything. We will cover how to use Terraform for provisioning basic infrastructure on AWS, including VPCs, networking, security groups (firewall'ish) and deployment of applications on Elastic Beanstalk in an autoscaled and load balanced environment. We will also set up a hosted database and a bastion host (jump host) for connecting to servers inside your private subnet. As a bonus you will learn how to handle secrets when working in an environment built for continuous delivery.
Slides: https://steinim.github.io/slides/aws-terraform-workshop/
Mac OSX (preferable) or Linux. If you have a Windows machine, please set up a Linux virtual machine. You can use Vagrant for this.
Go to: https://aws.amazon.com/free and sign up for a free account.
Tip: If you already have an account and use gmail and want to make a new account for this workshop you can add + before the @ in your email-address. Example: [email protected]
- Go to: https://console.aws.amazon.com/iam/
Users|Add user
- Check
Programmatic access
andAWS Management Console access
- Attach
Administrator Access
to the user - Sign out of root account and sign in with the newly created user
Go to: https://console.aws.amazon.com/iam/home?region=eu-west-2#/users
- Click on your newly created user
- Go to
Security Credentials
and upload your SSH public key underSSH keys for AWS CodeCommit
cat ~/.ssh/id_rsa.pub | pbcopy
Go to: https://console.aws.amazon.com/iam/home?region=eu-west-2#/users
- Click on your newly created user
- Go to
Security Credentials
and pressCreate access key
- Copy your credentials to a file or download the .csv file (NB! You will only see your secret key once)
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
brew update
brew install envchain
envchain --set aws AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_DEFAULT_REGION
Note: AWS_DEFAULT_REGION = eu-west-2
https://github.com/sorah/envchain
# OSX. Others see: https://gnupg.org/download/
brew install gpg
https://www.passwordstore.org/
echo 'export PASSWORD_STORE_DIR=~/.password-store' >> ~/.bashrc
. ~/.bashrc
# OSX. Others see: https://www.passwordstore.org/
brew install pass
echo "source /usr/local/etc/bash_completion.d/pass" >> ~/.bashrc
gpg --full-generate-key # Accept all defaults
gpg --list-secret-keys --keyid-format LONG
From the list of GPG keys, copy the GPG key ID you'd like to use. In this example, the GPG key ID is 3AA5C34371567BD2
:
gpg --list-secret-keys --keyid-format LONG
sec 4096R/3AA5C34371567BD2 2016-03-10 [expires: 2017-03-10]
uid Hubot
ssb 4096R/42B317FD4BA89E7A 2016-03-10
Paste the text below, substituting in the GPG key ID you'd like to use. In this example, the GPG key ID is 3AA5C34371567BD2
:
pass init 3AA5C34371567BD2
pass add AWS_ACCESS_KEY_ID
pass add AWS_SECRET_ACCESS_KEY
pass add AWS_DEFAULT_REGION
pass show AWS_DEFAULT_REGION
https://www.terraform.io/intro/getting-started/install.html
# OSX. Others see: https://www.terraform.io/intro/getting-started/install.html
brew install terraform
https://github.com/nsbno/cloud-tools
# OSX. Others see: https://golang.org/doc/install#install
brew install go
Add the following to your .bashrc
export GOPATH=<path-to-your-sourcecode>/go
export GOBIN=$GOPATH/bin
PATH=$GOBIN:$PATH
export PATH
Note:
path-to-your-sourcecode/go
should point to an empty folder you create to store your go code in.
. ~/.bashrc
mkdir -p $GOPATH/{bin,pkg,src/github.com/nsbno,vendor}
go get github.com/nsbno/cloud-tools # Ignore the warning message
cd $GOPATH/src/github.com/nsbno/cloud-tools
./deps.sh
./make.sh
brew install s3cmd # OSX. Others see: https://tecadmin.net/install-s3cmd-manage-amazon-s3-buckets/
sudo easy_install pip # You may have to install Python, easy_install and pip
pip install awscli awsebcli
https://www.terraform.io/docs/providers/aws/index.html
In this task we will initialize the Terraform environment and run our first plan
git checkout start
-
Go to
infrastructure/test
-
Create a s3 bucket for you remote state
envchain aws s3cmd --region eu-west-2 mb s3://<unique-bucket-name>
- Create the following files
# backend.tf
terraform {
backend "s3" {
bucket = "<unique-bucket-name>"
key = "infrastructure/test/terraform.tfstate"
region = "eu-west-2"
}
}
# main.tf
provider "aws" {
region = "${var.region}"
}
# vars.tf
variable "region" {}
variable "env" {}
- Initialize the backend
envchain aws terraform init # OSX
../../env.sh terraform init # Linux
(You may have to wait a while for the S3 bucket to become available)
- Create the following file
# cloud-config.yml
vars:
- name: TF_VAR_env
value: test
- name: TF_VAR_region
value: eu-west-2
- Plan
envchain aws terraform-wrapper plan # OSX
../../env.sh terraform-wrapper plan # Linux
git checkout task1
In this task we will create a VPC, an internet gateway and grant the VPC internet access on its main route table.
VPC module `infrastructure/modules/vpc/`
# main.tf
resource "aws_vpc" "vpc" {
cidr_block = "${var.vpc_cidr}"
enable_dns_hostnames = "${var.enable_dns_hostnames}"
tags { Name = "${var.vpc_name}" }
}
# Create an internet gateway
resource "aws_internet_gateway" "ig" {
vpc_id = "${aws_vpc.vpc.id}"
tags { Name = "${var.ig_name}" }
}
# Grant the VPC internet access on its main route table
resource "aws_route" "internet_access_route" {
route_table_id = "${aws_vpc.vpc.main_route_table_id}"
destination_cidr_block = "${var.route_destination_cidr_block}"
gateway_id = "${aws_internet_gateway.ig.id}"
}
---
# vars.tf
variable "aws_region" {}
variable "vpc_name" {}
variable "enable_dns_hostnames" { default = "true" }
variable "ig_name" {}
variable "vpc_cidr" {}
variable "route_destination_cidr_block" { default = "0.0.0.0/0" }
---
# outputs.tf
output "vpc_id" {
value = "${aws_vpc.vpc.id}"
}
output "internet_gateway_id" {
value = "${aws_internet_gateway.ig.id}"
}
VPC project `infrastructure/test/`
# main.tf
---
module "vpc" {
source = "../modules/vpc"
aws_region = "${var.region}"
vpc_name = "${var.env}_vpc"
vpc_cidr = "10.0.0.0/16"
ig_name = "${var.env}_ig"
}
envchain aws terraform get --update # OSX
../../env.sh terraform get --update # Linux
# OSX
envchain aws terraform-wrapper plan
envchain aws terraform-wrapper apply
# Linux
../../env.sh terraform-wrapper plan
../../env.sh terraform-wrapper apply
git checkout task2
In this task we will set up software defined networking (SDN) with subnets, route tables, internet gateway and NAT.
Subnets `infrastructure/modules/subnet/`
# main.tf
resource "aws_subnet" "subnet" {
vpc_id = "${var.vpc_id}"
count = "${var.number_of_subnets}"
cidr_block = "${lookup(var.cidr_blocks, "zone_${count.index}")}"
availability_zone = "${lookup(var.zones, "zone_${count.index}")}"
map_public_ip_on_launch = "${var.map_public_ip_on_launch}"
tags { Name = "${var.name}_subnet_${lookup(var.zones, "zone_${count.index}")}" }
}
---
# vars.tf
variable "vpc_id" {}
variable "number_of_subnets" { default = 2 }
variable "cidr_blocks" { type = "map" }
variable "map_public_ip_on_launch" {}
variable "name" {}
variable "zones" {
default = {
zone_0 = "eu-west-2a"
zone_1 = "eu-west-2b"
}
}
---
# outputs.tf
output "subnet_ids" {
value = ["${aws_subnet.subnet.*.id}"]
}
Route table for VPC default internet gateway `infrastructure/modules/ig-route-table/`
# main.tf
resource "aws_route_table" "internet_gateway_route_table" {
vpc_id = "${var.vpc_id}"
route {
cidr_block = "${var.cidr_block}"
gateway_id = "${var.internet_gateway_id}"
}
tags { Name = "${var.env}_internet_gateway_route_table" }
}
---
# vars.tf
variable "vpc_id" {}
variable "env" {}
variable "internet_gateway_id" {}
variable "cidr_block" { default = "0.0.0.0/0" }
---
# outputs.tf
output "internet_gateway_route_table_id" {
value = "${aws_route_table.internet_gateway_route_table.id}"
}
Route table association `infrastructure/modules/route-table-association/`
# main.tf
resource "aws_route_table_association" "route-table-association" {
count = "${var.number_of_subnets}"
subnet_id = "${element(var.subnet_ids, count.index)}"
route_table_id = "${var.route_table_id}"
}
---
# vars.tf
variable "number_of_subnets" { default = 2 }
variable "subnet_ids" { type = "list" }
variable "route_table_id" {}
NAT gateway `infrastructure/modules/nat/`
# main.tf
resource "aws_nat_gateway" "nat" {
count = "1"
allocation_id = "${var.nat_eip_allocation_id}"
subnet_id = "${var.public_subnet_ids[0]}"
}
---
# vars.tf
variable "nat_gateway_enabled" {
description = "set to 1 to create nat gateway instances for private subnets"
default = 0
}
variable "public_subnet_ids" { type = "list" }
variable "nat_eip_allocation_id" {}
---
# outputs.tf
output "nat_id" {
value = "${aws_nat_gateway.nat.id}"
}
output "public_ip" {
value = "${aws_nat_gateway.nat.public_ip}"
}
First add an elastic ip to your account. Go to https://eu-west-2.console.aws.amazon.com/vpc/home and click 'Allocate new address'
NAT route table `infrastructure/modules/nat-route-table/`
# main.tf
resource "aws_route_table" "nat_route_table" {
vpc_id = "${var.vpc_id}"
tags { Name = "${var.env}_nat_gateway_route_table" }
}
resource "aws_route" "default_route" {
route_table_id = "${aws_route_table.nat_route_table.id}"
destination_cidr_block = "0.0.0.0/0"
nat_gateway_id = "${var.nat_id}"
}
---
# vars.tf
variable "vpc_id" {}
variable "nat_id" {}
variable "env" {}
---
# outputs.tf
output "nat_route_table_id" {
value = "${aws_route_table.nat_route_table.id}"
}
Main project `infrastructure/test/`
# main.tf
...
module "public_subnets" {
source = "../modules/subnet"
vpc_id = "${module.vpc.vpc_id}"
cidr_blocks = "${var.public_subnets_cidr_blocks}"
name = "${var.env}_public"
map_public_ip_on_launch = "true"
}
module "public_ig_route_table" {
source = "../modules/ig-route-table"
internet_gateway_id = "${module.vpc.internet_gateway_id}"
vpc_id = "${module.vpc.vpc_id}"
env = "${var.env}"
}
module "public_route_table_association" {
source = "../modules/route-table-association"
subnet_ids = ["${module.public_subnets.subnet_ids}"]
route_table_id = "${module.public_ig_route_table.internet_gateway_route_table_id}"
}
module "private_subnets" {
source = "../modules/subnet"
vpc_id = "${module.vpc.vpc_id}"
cidr_blocks = "${var.private_subnets_cidr_blocks}"
name = "${var.env}_private"
map_public_ip_on_launch = "false"
}
module "nat" {
source = "../modules/nat"
nat_eip_allocation_id = "${var.nat_eip_allocation_id}"
public_subnet_ids = ["${module.public_subnets.subnet_ids}"]
}
module "private_nat_route_table" {
source = "../modules/nat-route-table"
nat_id = "${module.nat.nat_id}"
vpc_id = "${module.vpc.vpc_id}"
env = "${var.env}"
}
module "private_route_table_association" {
source = "../modules/route-table-association"
subnet_ids = ["${module.private_subnets.subnet_ids}"]
route_table_id = "${module.private_nat_route_table.nat_route_table_id}"
---
# vars.tf
...
variable "public_subnets_cidr_blocks" {
default = {
zone_0 = "10.0.1.0/24"
zone_1 = "10.0.2.0/24"
}
}
variable "private_subnets_cidr_blocks" {
default = {
zone_0 = "10.0.3.0/24"
zone_1 = "10.0.4.0/24"
}
}
variable "nat_eip_allocation_id" { default = "eipalloc-XXXXXXXXXXXXX" } # Substitute with your own eip-id
envchain aws terraform get --update # OSX
../../env.sh terraform get --update # Linux
# OSX
envchain aws terraform-wrapper plan
envchain aws terraform-wrapper apply
# Linux
../../env.sh terraform-wrapper plan
../../env.sh terraform-wrapper apply
git checkout task3
In this task we will set up a bastion host and a security group allowing ssh access
Security group `infrastructure/modules/security-groups/`
# main.tf
resource "aws_security_group" "bastion_sg" {
vpc_id = "${var.vpc_id}"
name = "${var.env}_bastion_sg"
tags { Name = "${var.env}_bastion_sg" }
}
resource "aws_security_group_rule" "ssh_ingress_rule" {
type = "ingress"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["${var.cidr_block}"]
security_group_id = "${aws_security_group.bastion_sg.id}"
}
---
# vars.tf
variable "vpc_id" {}
variable "env" {}
variable "cidr_block" {}
---
# outputs.tf
output "bastion_sg_id" {
value = "${aws_security_group.bastion_sg.id}"
}
SSH key pair `infrastructure/modules/key-pair/`
# main.tf
resource "aws_key_pair" "auth" {
key_name = "${var.key_name}"
public_key = "${var.public_key}"
}
---
# vars.tf
variable "key_name" {}
variable "public_key" {}
---
# outputs.tf
output "id" {
value = "${aws_key_pair.auth.id}"
}
Bastion host `infrastructure/modules/instance/`
# main.tf
resource "aws_instance" "instance" {
ami = "${var.ami}"
instance_type = "${var.instance_type}"
key_name = "${var.key_pair_id}"
subnet_id = "${var.subnet_id}"
associate_public_ip_address = "${var.associate_public_ip_address}"
source_dest_check = "${var.source_dest_check}"
vpc_security_group_ids = [ "${var.security_group_id}" ]
}
---
# vars.tf
variable "name" {}
variable "ami" {}
variable "instance_type" { default = "t2.micro" }
variable "key_pair_id" {}
variable "subnet_id" {}
variable "associate_public_ip_address" { default = false }
variable "source_dest_check" { default = true }
variable "security_group_id" {}
---
# outputs.tf
output "public_ip" {
value = "${aws_instance.instance.public_ip}"
}
Main project `infrastructure/test/`
# main.tf
...
module "security_groups" {
source = "../modules/security-groups"
vpc_id = "${module.vpc.vpc_id}"
env = "${var.env}"
cidr_block = "0.0.0.0/0"
}
module "key_pair" {
source = "../modules/key-pair"
key_name = "${var.env}"
public_key = "${var.public_key}"
}
module "bastion" {
source = "../modules/instance"
name = "${var.env}_bastion"
ami = "${var.bastion_ami}"
instance_type = "t2.nano"
key_pair_id = "${module.key_pair.id}"
subnet_id = "${element(module.public_subnets.subnet_ids, 0)}"
associate_public_ip_address = "${var.bastion_associate_public_ip_address}"
security_group_id = "${module.security_groups.bastion_sg_id}"
source_dest_check = "false"
}
---
# vars.tf
variable "bastion_associate_public_ip_address" { default = true }
variable "bastion_ami" { default = "ami-dff017b8" }
variable "public_key" {}
---
# cloud-config.yml
vars:
- name: TF_VAR_env
value: test
- name: TF_VAR_region
value: eu-west-2
- name: TF_VAR_public_key
value: "<publik key>"
envchain aws terraform get --update # OSX
../../env.sh terraform get --update # Linux
# OSX
envchain aws terraform-wrapper plan
envchain aws terraform-wrapper apply
# Linux
../../env.sh terraform-wrapper plan
../../env.sh terraform-wrapper apply
ssh ec2-user@$(envchain aws terraform output --module=bastion public_ip)
git checkout task4
In this task we will set up a hosted database.
pass generate --no-symbols hello/test/db_password
Database subnet group `infrastructure/modules/db-subnet-group/`
# main.tf
resource "aws_db_subnet_group" "db_subnet_group" {
name = "${var.db_subnet_group_name}"
description = "${var.db_subnet_group_name}"
subnet_ids = ["${var.private_subnet_ids}"]
}
---
# vars.tf
variable "db_subnet_group_name" {}
variable "private_subnet_ids" { type = "list" }
---
# outputs.tf
output "db_subnet_group_id" {
value = "${aws_db_subnet_group.db_subnet_group.id}"
}
Main project `infrastructure/test/`
# main.tf
...
module "db_subnet_group" {
source = "../modules/db-subnet-group"
db_subnet_group_name = "${var.env}_db_subnet_group"
private_subnet_ids = ["${module.private_subnets.subnet_ids}"]
}
envchain aws terraform get --update # OSX
../../env.sh terraform get --update # Linux
# OSX
envchain aws terraform-wrapper plan
envchain aws terraform-wrapper apply
# Linux
../../env.sh terraform-wrapper plan
../../env.sh terraform-wrapper apply
app-infrastructure
folder structure.
# backend.tf
terraform {
backend "s3" {
bucket = "<unique-bucket-name>"
key = "infrastructure/test/hello/terraform.tfstate"
region = "eu-west-2"
}
}
secret-vars:
- name: TF_VAR_db_password
key: hello/test/db_password
vars:
- name: TF_VAR_env
value: test
- name: AWS_DEFAULT_REGION
value: eu-west-2
envchain aws terraform init
Database `app-infrastructure/modules/rds/`
# main.tf
data "aws_vpc" "vpc" {
tags { Name = "${var.env}_vpc" }
}
resource "aws_db_instance" "db" {
name = "${var.db_name}"
identifier = "${var.db_identifier}"
engine = "${var.db_engine}"
engine_version = "${var.db_engine_version}"
instance_class = "${var.db_instance_class}"
username = "${var.db_username}"
password = "${var.db_password}"
vpc_security_group_ids = ["${aws_security_group.db_sg.id}"]
db_subnet_group_name = "${var.db_subnet_group_id}"
backup_retention_period = "${var.backup_retention_period}"
availability_zone = "${var.availability_zone}"
multi_az = "${var.multi_az}"
backup_window = "${var.backup_window}"
maintenance_window = "${var.maintenance_window}"
allocated_storage = "${var.allocated_storage}"
storage_type = "${var.storage_type}"
apply_immediately = "${var.apply_immediately}"
skip_final_snapshot = "${var.skip_final_snapshot}"
license_model = "${var.license_model}"
tags {
Name = "${var.db_name_tag}"
}
}
resource "aws_security_group" "db_sg" {
vpc_id = "${data.aws_vpc.vpc.id}"
name = "${var.db_sg_name}"
description = "${var.db_sg_name}"
tags { Name = "${var.db_sg_name}" }
}
---
# vars.tf
variable "env" {}
variable "db_name" {}
variable "db_name_tag" { default = "" }
variable "db_subnet_group_id" {}
variable "db_identifier" {}
variable "db_engine" {}
variable "db_engine_version" {}
variable "db_instance_class" {}
variable "db_username" {}
variable "db_password" {}
variable "db_sg_name" {}
variable "backup_retention_period" { default = "0" }
variable "availability_zone" { default = "eu-west-2a" }
variable "multi_az" { default = "false" }
variable "backup_window" {}
variable "maintenance_window" { default = "Wed:03:55-Wed:04:25" }
variable "allocated_storage" {}
variable "storage_type" {}
variable "apply_immediately" {}
variable "skip_final_snapshot" { default = "true" }
variable "license_model" { default = "" }
---
# outputs.tf
output "db_security_group_id" {
value = "${aws_security_group.db_sg.id}"
}
output "address" {
value = "${aws_db_instance.db.address}"
}
Main project `app-infrastructure/test/`
# main.tf
module "db" {
source = "../modules/rds"
env = "${var.env}"
db_name = "${var.env}_${var.appname}"
db_identifier = "${var.env}-${var.appname}-rds"
db_sg_name = "${var.env}_${var.appname}_db_sg"
db_engine = "mysql"
db_engine_version = "5.7.21"
db_instance_class = "${var.db_instance_class}"
db_username = "root"
db_password = "${var.db_password}"
db_subnet_group_id = "${var.env}_db_subnet_group"
backup_retention_period = "${var.backup_retention_period}"
multi_az = "${var.multi_az}"
backup_window = "${var.backup_window}"
maintenance_window = "${var.maintenance_window}"
allocated_storage = "${var.allocated_storage}"
storage_type = "${var.storage_type}"
apply_immediately = "${var.apply_immediately}"
}
---
# vars.tf
variable "env" {}
variable "appname" { default = "hello" }
variable "db_password" {}
variable "backup_retention_period" { default = "0" }
variable "multi_az" { default = "false" }
variable "backup_window" { default = "02:17-02:47" }
variable "maintenance_window" { default = "Wed:03:55-Wed:04:25" }
variable "allocated_storage" { default = 10 }
variable "db_instance_class" { default = "db.t2.micro" }
variable "storage_type" { default = "gp2" }
variable "apply_immediately" { default = "true" }
envchain aws terraform get --update # OSX
../../env.sh terraform get --update # Linux
# OSX
envchain aws terraform-wrapper plan
envchain aws terraform-wrapper apply
# Linux
../../env.sh terraform-wrapper plan
../../env.sh terraform-wrapper apply
git checkout task5
In this task we will set up security groups between the bastion host and apps, and between apps and the database.
Security groups `app-infrastructure/modules/security-groups/`
# main.tf
data "aws_vpc" "vpc" {
tags { Name = "${var.env}_vpc" }
}
data "aws_security_group" "bastion_security_group" {
name = "${var.env}_bastion_sg"
}
resource "aws_security_group" "app_security_group" {
vpc_id = "${data.aws_vpc.vpc.id}"
name = "${var.app_security_group_name}"
description = "${var.app_security_group_name}"
tags { Name = "${var.app_security_group_name}" }
}
# Bastion -> App
resource "aws_security_group_rule" "from_bastion_to_app_egress_security_rule" {
type = "egress"
from_port = 22
to_port = 22
protocol = "tcp"
security_group_id = "${data.aws_security_group.bastion_security_group.id}"
source_security_group_id = "${aws_security_group.app_security_group.id}"
}
resource "aws_security_group_rule" "from_bastion_to_app_ingress_security_rule" {
type = "ingress"
from_port = 22
to_port = 22
protocol = "tcp"
security_group_id = "${aws_security_group.app_security_group.id}"
source_security_group_id = "${data.aws_security_group.bastion_security_group.id}"
}
# App -> DB
resource "aws_security_group_rule" "from_app_to_db_ingress_security_rule" {
type = "ingress"
from_port = "${var.db_port}"
to_port = "${var.db_port}"
protocol = "tcp"
security_group_id = "${var.db_security_group_id}"
source_security_group_id = "${aws_security_group.app_security_group.id}"
}
# App -> DB
resource "aws_security_group_rule" "from_app_to_db_egress_security_rule" {
type = "egress"
from_port = "${var.db_port}"
to_port = "${var.db_port}"
protocol = "tcp"
security_group_id = "${aws_security_group.app_security_group.id}"
source_security_group_id = "${var.db_security_group_id}"
}
---
# vars.tf
variable "env" {}
variable "app_security_group_name" {}
variable "db_security_group_id" {}
variable "db_port" {}
Main project `app-infrastructure/test/`
# main.tf
module "security-groups" {
source = "../modules/security-groups"
env = "${var.env}"
app_security_group_name = "${var.env}_${var.appname}_app_sg"
db_security_group_id = "${module.db.db_security_group_id}"
db_port = "3306"
}
envchain aws terraform get --update # OSX
../../env.sh terraform get --update # Linux
# OSX
envchain aws terraform-wrapper plan
envchain aws terraform-wrapper apply
# Linux
../../env.sh terraform-wrapper plan
../../env.sh terraform-wrapper apply
git checkout task6
In this task we will configure an Elastic Beanstalk project and deploy an application. We will do this as a walkthrough, so check out the solution: git checkout task7
-
Go to the root of the project folder
-
Type
envchain aws eb init
-
Answer the questions
16) eu-west-2 : EU (London)
Application Name: helloworld
11) Java
1) Java 8
CodeCommit? (y/N) (default is n): N
SSH: Y
Keypair 1) test
- Build the Java app:
cd app && mvn clean install && cd -
- ./create.sh test
- Browse to https://eu-west-2.console.aws.amazon.com/elasticbeanstalk/ to watch progress.
- Browse to http://test-helloworld.eu-west-2.elasticbeanstalk.com/
envchain aws eb terminate test-helloworld
cd app-infrastructure/test
envchain aws terraform-wrapper destroy -force
cd ../../infrastructure/test
envchain aws terraform-wrapper destroy -force
NB❗️Also remember to release your Elastic IP!