0%
October 10, 2023

AWS Resources Instanciation in Terraform

aws

cloud

terraform

A Complete EC2 with Security Group

provider "aws" {
  region = "ap-northeast-1"
}

resource "aws_instance" "web" {
  ami                    = "ami-08a706ba5ea257141"
  instance_type          = "t2.micro"
  vpc_security_group_ids = [aws_security_group.web.id]
  user_data              = file("user_data.sh")

  tags = {
    Name  = "WebServer Built by Terraform"
    Owner = "James Lee"
  }
}

resource "aws_default_vpc" "default" {}

resource "aws_security_group" "web" {
  name        = "Webserver James"
  description = "Security Group for Webserver James"
  vpc_id      = aws_default_vpc.default.id

  ingress {
    description = "Allow port HTTP"
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    description = "Allow port HTTPS"
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name  = "WebServer Built by Terraform"
    Owner = "James Lee"
  }
}

Here user_data.sh is:

#!/bin/bash
yum -y update
yum -y install httpd
echo "<h2> Hello World!</h2> <h3>by external file!</h3>" > /var/www/html/index.html
service httpd start
chkconfig httpd on

In resource "aws_instance" "web" {} we can replace user_data = file(user_data.sh) by

user_data = templatefile("user_data.sh.tpl", {
  first_name = "James"
  last_name  = "Lee"
  names      = ["John", "Angel", "David", "Victor", "Frank", "Melissa", "Kitana"]
})

where

// user_data.sh.tpl

#!/bin/bash
yum -y update
yum -y install httpd

cat <<EOF > /var/www/html/index.html
<html>
<h2>Built by Power of <font color="red">Terraform</font></h2><br>

Server Owner is: ${first_name} ${last_name}<br>

%{ for name in names ~}
Hello to ${name} from ${first_name}<br>
%{ endfor ~}

</html>
EOF

service httpd start
chkconfig httpd on

Dynamic Properties

Example of a security group:

resource "aws_security_group" "web" {
  name        = "Webserver James"
  description = "Security Group for Webserver James"
  vpc_id      = aws_default_vpc.default.id

  ingress {
    description = "Allow port HTTP"
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  dynamic "ingress" {
    for_each = ["80", "8080", "443", "1000", "8443"]
    content {
      description = "Allow port HTTP"
      from_port   = ingress.value
      to_port     = ingress.value
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
    }
  }

  ingress {
    description = "Allow port SSH"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }


  tags = {
    Name  = "WebServer Built by Terraform"
    Owner = "James Lee"
  }
}

Elastic IP

resource "aws_eip" "web" {
  instance = aws_instance.web.id
  tags = {
    Name  = "WebServer Built by Terraform"
    Owner = "James Lee"
  }
}

resource "aws_instance" "web" {
  ami                    = "ami-08a706ba5ea257141"
  instance_type          = "t2.micro"
  vpc_security_group_ids = [aws_security_group.web.id]
  user_data              = file("user_data.sh")

  tags = {
    Name  = "WebServer Built by Terraform"
    Owner = "James Lee"
  }
}

Life Cycle: Create Before Destroy

Since we have attached an elastic IP to an aws instance.

If we specify create_before_destroy = true, then a new instance will be created first, and our elastic IP will be transferred to new instace, this results in almost zero down time of our web server.

resource "aws_instance" "web" {
  ami                    = "ami-08a706ba5ea257141"
  instance_type          = "t2.micro"
  vpc_security_group_ids = [aws_security_group.web.id]
  user_data              = file("user_data.sh")

  tags = {
    Name  = "WebServer Built by Terraform"
    Owner = "James Lee"
  }

  lifecycle {
    create_before_destroy = true
  }
}

Implicit and Explicit Dependencies

resource "aws_instance" "my_web_server" {
  ami                    = "ami-08a706ba5ea257141"
  instance_type          = "t2.micro"
  vpc_security_group_ids = [aws_security_group.general.id]
  tags = {
    Name  = "Server-Web"
    Owner = "James Lee"
  }
  depends_on = [aws_instance.my_db_server, aws_instance.my_app_server]
}

resource "aws_instance" "my_app_server" {
  ami                    = "ami-08a706ba5ea257141"
  instance_type          = "t2.micro"
  vpc_security_group_ids = [aws_security_group.general.id]
  tags = {
    Name  = "Server-App"
    Owner = "James Lee"
  }
  depends_on = [aws_instance.my_db_server]
}

resource "aws_instance" "my_db_server" {
  ami                    = "ami-08a706ba5ea257141"
  instance_type          = "t2.micro"
  vpc_security_group_ids = [aws_security_group.general.id]
  tags = {
    Name  = "Server-Db"
    Owner = "James Lee"
  }
}

resource "aws_security_group" "general" {
  dynamic "ingress" {
    for_each = ["80", "443", "22", "3389"]
    content {
      from_port   = ingress.value
      to_port     = ingress.value
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
    }
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "My Security Group"
  }
}

Random Password in SSM Parameter Store

resource "random_password" "main" {
  length           = 20
  special          = true
  override_special = "#!()_"
}

resource "aws_ssm_parameter" "rds_password" {
  name        = "/prod/prod-my-sql-rds/password"
  type        = "SecureString"
  description = "Master Password"
  value       = random_password.main.result

  tags = {
    environment = "production"
  }
}

Password retrival:

data "aws_ssm_parameter" "rds_password" {
  name = "/prod/prod-my-sql-rds/password"
}

And we use data.aws_ssm_parameter.rds_password.value as the value in attributes.

Random Password in Secrets Managers

resource "random_password" "main" {
  length           = 20
  special          = true
  override_special = "#!()_"
}

resource "aws_secretsmanager_secret" "rds_password" {
  name                    = "/prod/rds/password"
  description             = "Password for my RDS database"
  recovery_window_in_days = 0 // completely delete once set up
}

resource "aws_secretsmanager_secret_version" "rds_password" {
  secret_id     = aws_secretsmanager_secret.rds_password.id
  secret_string = random_password.main.result
}

data "aws_secretsmanager_secret_version" "rds_password" {
  secret_id  = aws_secretsmanager_secret.rds_password.id
  depends_on = [aws_secretsmanager_secret_version.rds_password]
}

output "random_password_rds" {
  value     = aws_secretsmanager_secret_version.rds_password.secret_string
  sensitive = true
}

JSON as Environment Variable Stored in Secrets Managers

We additionally add a database for more data to store.

resource "aws_db_instance" "prod" {
  allocated_storage    = 10
  db_name              = "mydb"
  engine               = "mysql"
  engine_version       = "5.7"
  instance_class       = "db.t3.micro"
  username             = "admin"
  password             = random_password.main.result
  parameter_group_name = "default.mysql5.7"
  skip_final_snapshot  = true
}


resource "random_password" "main" {
  length           = 20
  special          = true
  override_special = "#!()_"
}

resource "aws_secretsmanager_secret" "rds" {
  name                    = "/prod/rds/all"
  description             = "Everything about the rds database"
  recovery_window_in_days = 0 // completely delete once set up
}

resource "aws_secretsmanager_secret_version" "rds" {
  secret_id = aws_secretsmanager_secret.rds.id
  secret_string = jsonencode({
    rds_address  = aws_db_instance.prod.address
    rds_port     = aws_db_instance.prod.port
    rds_username = aws_db_instance.prod.username
    rds_password = random_password.main.result
  })
}

data "aws_secretsmanager_secret_version" "rds" {
  secret_id  = aws_secretsmanager_secret.rds.id
  depends_on = [aws_secretsmanager_secret_version.rds]
}

output "rds_all" {
  value     = jsondecode(aws_secretsmanager_secret_version.rds.secret_string)
  sensitive = true
}

Create VPC and Subnets

provider "aws" {}

resource "aws_vpc" "prod" {
  cidr_block = "10.0.0.0/16"
  tags = {
    Name = "PROD"
  }
}

resource "aws_subnet" "subnet1" {
  vpc_id            = data.aws_vpc.prod.id
  cidr_block        = "10.0.1.0/24"
  availability_zone = data.aws_availability_zones.working.names[0]
  tags = {
    Name = "Subnet1"
  }
}

resource "aws_subnet" "subnet2" {
  vpc_id            = data.aws_vpc.prod.id
  cidr_block        = "10.0.2.0/24"
  availability_zone = data.aws_availability_zones.working.names[1]
  tags = {
    Name = "Subnet2",
    Info = "AZ: ${data.aws_availability_zones.working.names[1]} in Region: ${data.aws_region.current.description}"

  }
}

data "aws_region" "current" {}
data "aws_caller_identity" "current" {}
data "aws_availability_zones" "working" {}
data "aws_vpc" "prod" {
  depends_on = [aws_vpc.prod]
  tags = {
    Name = "PROD"
  }
}


output "region_name" {
  value = data.aws_region.current.name
}

output "region_description" {
  value = data.aws_region.current.description
}

output "account_id" {
  value = data.aws_caller_identity.current.account_id
}

output "availability_zones" {
  value = data.aws_availability_zones.working.names
}

Get the AMI-id of Ubuntu/Amazon Linux by Filtering

provider "aws" {}

data "aws_ami" "latest_ubuntu20" {
  owners      = ["099720109477"]
  most_recent = true
  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
  }
}

data "aws_ami" "latest_amazonlinux" {
  owners      = ["137112412989"]
  most_recent = true
  filter {
    name   = "name"
    values = ["al2023-ami-2023.*-kernel-6.1-x86_64"]
  }
}


output "latest_ubuntu20_ami_id" {
  value = data.aws_ami.latest_ubuntu20.id
}

output "latest_amazonlinux_ami_id" {
  value = data.aws_ami.latest_amazonlinux.id
}

output:

latest_amazonlinux_ami_id = "ami-0fd8f5842685ca887"
latest_ubuntu20_ami_id = "ami-09a81b370b76de6a2"