This is basically a slighty modified version of a tutorial by Anton Putra, which I've changed to use modules (with pinned versions for future compatibility) instead of plain resources, so it is easier to read. I've also added the code for creating and assigning appropriate IAM Role and Instance Profile to ASG instances, so you can manage them with SSM.
General settings
First of all, you'll have to set the region, which you would like the resources to be deployed in:
terraform { required_providers { aws = { source = "hashicorp/aws" version = "5.31.0" } } } provider "aws" { region = "eu-west-1" }
VPC
Then, you'll create the VPC and all of its resources (security groups are dependant on eachother, so they are created as resources first and then rules are created as modules)
locals { ... vpc_cidr = "10.0.0.0/16" azs = slice(data.aws_availability_zones.available.names, 0, 2) tags = { ManagedBy = "Terraform" } ... } data "aws_availability_zones" "available" {} module "vpc" { source = "terraform-aws-modules/vpc/aws" version = "5.4.0" name = "alb-vpc" cidr = local.vpc_cidr azs = local.azs private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k)] public_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 4)] enable_nat_gateway = true single_nat_gateway = true tags = local.tags } resource "aws_security_group" "ec2" { name = "ec2-sg" vpc_id = module.vpc.vpc_id tags = local.tags } resource "aws_security_group" "alb" { name = "alb-sg" vpc_id = module.vpc.vpc_id tags = local.tags } module "alb-sg-rules" { source = "terraform-aws-modules/security-group/aws" version = "5.1.0" create_sg = false security_group_id = aws_security_group.alb.id ingress_cidr_blocks = ["0.0.0.0/0"] ingress_rules = ["http-80-tcp", "https-443-tcp"] egress_with_source_security_group_id = [ { from_port = 8080 to_port = 8080 protocol = "tcp" description = "App port" source_security_group_id = aws_security_group.ec2.id }, { from_port = 8081 to_port = 8081 protocol = "tcp" description = "Full healthcheck" source_security_group_id = aws_security_group.ec2.id } ] } module "ec2-sg-rules" { source = "terraform-aws-modules/security-group/aws" version = "5.1.0" create_sg = false security_group_id = aws_security_group.ec2.id ingress_with_source_security_group_id = [ { from_port = 8080 to_port = 8080 protocol = "tcp" description = "App port" source_security_group_id = aws_security_group.alb.id }, { from_port = 8081 to_port = 8081 protocol = "tcp" description = "Full healthcheck" source_security_group_id = aws_security_group.alb.id } ] egress_cidr_blocks = ["0.0.0.0/0"] // these are needed for instance to be able to initiate a connection to SSM egress_rules = ["https-443-tcp"] }
AMI
After this, you'll have to create launch template for your ASG. I'll use Packer to create the AMI, as provided in Anton's post. You'll have to create the following files:
files/my-app.service
[Unit] Description=My App After=network.target StartLimitIntervalSec=0 [Service] Type=simple ExecStart=/home/ubuntu/go/bin/my-app User=ubuntu Environment=GIN_MODE=release Restart=always RestartSec=1 [Install] WantedBy=multi-user.target
scripts/bootstrap.sh
#!/bin/bash set -e sudo add-apt-repository ppa:longsleep/golang-backports sudo apt-get update sudo apt-get install -y golang-go go install github.com/antonputra/tutorials/lessons/127/my-app@main
my-app.pkr.hcl
packer { required_plugins { amazon = { version = "v1.2.9" source = "github.com/hashicorp/amazon" } } } source "amazon-ebs" "my-app" { ami_name = "my-app-{{ timestamp }}" instance_type = "t3a.small" // this isn't the instance type of ASG, it's the type of temp instance Packer will use to build an AMI off of region = "eu-west-1" // change this to the region of your resources subnet_id = "subnet-074a32b171778af28" // change this to any public subnet in the specified region, for example, you can use the one from default VPC source_ami_filter { filters = { name = "ubuntu/images/*ubuntu-jammy-22.04-amd64-server-*" root-device-type = "ebs" virtualization-type = "hvm" } most_recent = true owners = ["099720109477"] } ssh_username = "ubuntu" tags = { Name = "My-App", ManagedBy = "Packer" } } build { sources = ["source.amazon-ebs.my-app"] provisioner "file" { destination = "/tmp" source = "files" } provisioner "shell" { script = "scripts/bootstrap.sh" } provisioner "shell" { inline = [ "sudo mv /tmp/files/my-app.service /etc/systemd/system/my-app.service", "sudo systemctl start my-app", "sudo systemctl enable my-app" ] } }
Then, run these commands to initialize and build your AMI using Packer:
packer init my-app.pkr.hcl packer build my-app.pkr.hcl
Additional parameters
After this, you'll have to create/import keypair and decide on the instance type, you'd like to use for your ASG. You'll also have to create Route53 public hosted zone and update nameservers for your domain with the ones provided in NS record, which will be automatically created in the Route53 zone. Then, you'll add these values to locals:
locals { ... domain_name = "https-alb.pp.ua" keypair_name = "devops" instance_type = "t3a.micro" ami_id = "ami-06f69317847054bb5" ... }
ALB with HTTPS
You can now also create ALB with support for HTTPS and a target group, which will be attached to ASG:
module "acm" { source = "terraform-aws-modules/acm/aws" version = "5.0.0" domain_name = local.domain_name zone_id = data.aws_route53_zone.public.zone_id validation_method = "DNS" tags = local.tags } module "alb" { source = "terraform-aws-modules/alb/aws" version = "9.4.0" name = "alb" vpc_id = module.vpc.vpc_id subnets = module.vpc.public_subnets security_groups = [aws_security_group.alb.id] enable_deletion_protection = false target_groups = { asg = { name = "asg-tg" port = 8080 protocol = "HTTP" vpc_id = module.vpc.vpc_id create_attachment = false health_check = { enabled = true port = 8081 interval = 30 protocol = "HTTP" path = "/health" matcher = "200" healthy_threshold = 3 unhealthy_threshold = 3 } } } listeners = { http-https-redirect = { port = 80 protocol = "HTTP" redirect = { port = "443" protocol = "HTTPS" status_code = "HTTP_301" } }, https = { port = 443 protocol = "HTTPS" ssl_policy = "ELBSecurityPolicy-2016-08" // set to allow most clients, should be change to newer one certificate_arn = module.acm.acm_certificate_arn forward = { target_group_key = "asg" } } } tags = local.tags } resource "aws_route53_record" "alb" { name = local.domain_name type = "A" zone_id = data.aws_route53_zone.public.zone_id alias { name = module.alb.dns_name zone_id = module.alb.zone_id evaluate_target_health = false } }
ASG
And, finally, you'll create the ASG itself:
resource "aws_iam_service_linked_role" "autoscaling" { aws_service_name = "autoscaling.amazonaws.com" description = "A service linked role for autoscaling" custom_suffix = "ssm" provisioner "local-exec" { command = "sleep 10" } tags = local.tags } module "asg" { source = "terraform-aws-modules/autoscaling/aws" version = "7.3.1" name = "asg" use_name_prefix = false vpc_zone_identifier = module.vpc.private_subnets min_size = 1 // automatically set as desired max_size = 3 launch_template_name = "my-app" launch_template_use_name_prefix = false update_default_version = true image_id = local.ami_id instance_type = local.instance_type key_name = local.keypair_name security_groups = [aws_security_group.ec2.id] create_traffic_source_attachment = true traffic_source_identifier = module.alb.target_groups["asg"].arn service_linked_role_arn = aws_iam_service_linked_role.autoscaling.arn service_linked_role_arn = aws_iam_service_linked_role.autoscaling.arn create_iam_instance_profile = true iam_instance_profile_name. = "ssm-instance-profile" iam_role_name = "ssm-role" iam_role_path = "/ec2/" iam_role_description = "SSM role example" iam_role_tags = local.tags iam_role_policies = { AmazonSSMManagedInstanceCore = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" } block_device_mappings = [ { # Root volume device_name = "/dev/xvda" no_device = 0 ebs = { delete_on_termination = true encrypted = true volume_size = 1 volume_type = "gp3" } } ] scaling_policies = { avg-cpu-policy-greater-than-80 = { policy_type = "TargetTrackingScaling" estimated_instance_warmup = 300 target_tracking_configuration = { predefined_metric_specification = { predefined_metric_type = "ASGAverageCPUUtilization" } target_value = 80.0 } } } tags = local.tags }
You can optionally add output, to see complete healthcheck URL after all of the resources are created:
output "custom_domain" { value = "https://${module.acm.distinct_domain_names[0]}/ping" // will output only first domain supplied }
Top comments (0)