Terraform guide 4 - AWS

Terraform: AWS

Our Architecture: What We’re Going to Build

  • Root (orchestrate all)
    • Storage (S3)
    • Networking (gateway, route tables, security group)
    • Compute (2 EC2)

Storage

S3 bucket and random ID

  • environment setup: ~/terraform/AWS/storage
    1
    2
    mkdir -p ~/terraform/AWS/storage
    cd ~/terraform/AWS/storage
  • example
    1. Edit main.tf
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      #---------storage/main.tf---------

      # Create a random id
      resource "random_id" "tf_bucket_id" {
      byte_length = 2
      }

      # Create the bucket
      resource "aws_s3_bucket" "tf_code" {
      bucket = "${var.project_name}-${random_id.tf_bucket_id.dec}"
      acl = "private"

      force_destroy = true
      tags {
      Name = "tf_bucket"
      }
      }
    2. Edit variables.tf
      1
      2
      #----storage/variables.tf----
      variable "project_name" {}
    3. Edit Coutputs.tf
      1
      2
      3
      4
      #----storage/outputs.tf----
      output "bucketname" {
      value = "${aws_s3_bucket.tf_code.id}"
      }
    4. Work with Terraform
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      terraform init
      terraform validate
      # plan the deployment
      export AWS_ACCESS_KEY_ID="[ACCESS_KEY]"
      export AWS_SECRET_ACCESS_KEY="[SECRET_KEY]"
      export AWS_DEFAULT_REGION="us-east-1"
      terraform plan -out=tfplan -var project_name=la-terraform
      # deploy
      terraform apply tfplan

      # clean up
      terraform destroy -auto-approve -var project_name=la-terraform

Root module

  • environment: ~/terraform/AWS
    1
    2
    cd ~/terraform/AWS
    touch {main.tf,variables.tf,outputs.tf,terraform.tfvars}
  • example
    1. Edit main.tf
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      #----root/main.tf-----
      provider "aws" {
      region = "${var.aws_region}"
      }

      # Deploy Storage Resources
      module "storage" {
      source = "./storage"
      project_name = "${var.project_name}"
      }
    2. Edit variables.tf
      1
      2
      3
      4
      5
      #----root/variables.tf-----
      variable "aws_region" {}

      #------ storage variables
      variable "project_name" {}
    3. Edit terraform.tfvars:
      1
      2
      aws_region   = "us-east-1"
      project_name = "la-terraform"
    4. Edit outputs.tf
      1
      2
      3
      4
      5
      6
      #----root/outputs.tf-----

      #----storage outputs------
      output "Bucket Name" {
      value = "${module.storage.bucketname}"
      }
    5. Work with Terraform
      1
      2
      3
      4
      5
      6
      7
      8
      9
      export AWS_ACCESS_KEY_ID="[ACCESS_KEY]"
      export AWS_SECRET_ACCESS_KEY="[SECRET_KEY]]"
      terraform init
      terraform validate
      # deploy the S3 bucket
      terraform apply -auto-approve

      # clean up
      terraform destroy -auto-approve

Networking

VPC, Internet Gateway, and Route Tables

  • environment: ~/terraform/AWS/networking
    1
    2
    3
    mkdir -p  ~/terraform/AWS/networking
    cd ~/terraform/AWS/networking
    touch {main.tf,variables.tf,outputs.tf,terraform.tfvars}
  • example
    1. Edit main.tf

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      #----networking/main.tf----

      data "aws_availability_zones" "available" {}

      resource "aws_vpc" "tf_vpc" {
      cidr_block = "${var.vpc_cidr}"
      enable_dns_hostnames = true
      enable_dns_support = true

      tags {
      Name = "tf_vpc"
      }
      }

      resource "aws_internet_gateway" "tf_internet_gateway" {
      vpc_id = "${aws_vpc.tf_vpc.id}"

      tags {
      Name = "tf_igw"
      }
      }

      resource "aws_route_table" "tf_public_rt" {
      vpc_id = "${aws_vpc.tf_vpc.id}"

      route {
      cidr_block = "0.0.0.0/0"
      gateway_id = "${aws_internet_gateway.tf_internet_gateway.id}"
      }

      tags {
      Name = "tf_public"
      }
      }

      resource "aws_default_route_table" "tf_private_rt" {
      default_route_table_id = "${aws_vpc.tf_vpc.default_route_table_id}"

      tags {
      Name = "tf_private"
      }
      }

      resource "aws_subnet" "tf_public_subnet" {
      count = 2
      vpc_id = "${aws_vpc.tf_vpc.id}"
      cidr_block = "${var.public_cidrs[count.index]}"
      map_public_ip_on_launch = true
      availability_zone = "${data.aws_availability_zones.available.names[count.index]}"

      tags {
      Name = "tf_public_${count.index + 1}"
      }
      }

      resource "aws_route_table_association" "tf_public_assoc" {
      count = "${aws_subnet.tf_public_subnet.count}"
      subnet_id = "${aws_subnet.tf_public_subnet.*.id[count.index]}"
      route_table_id = "${aws_route_table.tf_public_rt.id}"
      }

      resource "aws_security_group" "tf_public_sg" {
      name = "tf_public_sg"
      description = "Used for access to the public instances"
      vpc_id = "${aws_vpc.tf_vpc.id}"

      #SSH
      ingress {
      from_port = 22
      to_port = 22
      protocol = "tcp"
      cidr_blocks = ["${var.accessip}"]
      }

      #HTTP
      ingress {
      from_port = 80
      to_port = 80
      protocol = "tcp"
      cidr_blocks = ["${var.accessip}"]
      }
      egress {
      from_port = 0
      to_port = 0
      protocol = "-1"
      cidr_blocks = ["0.0.0.0/0"]
      }
      }
    2. Edit variables.tf

      1
      2
      3
      4
      5
      6
      7
      8
      #----networking/variables.tf----
      variable "vpc_cidr" {}

      variable "public_cidrs" {
      type = "list"
      }

      variable "accessip" {}
    3. Edit outputs.tf

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      #-----networking/outputs.tf----

      output "public_subnets" {
      value = "${aws_subnet.tf_public_subnet.*.id}"
      }

      output "public_sg" {
      value = "${aws_security_group.tf_public_sg.id}"
      }

      output "subnet_ips" {
      value = "${aws_subnet.tf_public_subnet.*.cidr_block}"
      }
    4. Edit terraform.tfvars

      1
      2
      3
      4
      5
      6
      vpc_cidr     = "10.123.0.0/16"
      public_cidrs = [
      "10.123.1.0/24",
      "10.123.2.0/24"
      ]
      accessip = "0.0.0.0/0"
    5. Work with Terraform

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      export AWS_ACCESS_KEY_ID="[ACCESS_KEY]"
      export AWS_SECRET_ACCESS_KEY="[SECRET_KEY]]"
      terraform init
      terraform validate
      # deploy network
      terraform apply -auto-approve

      # clean up
      terraform destroy -auto-approve
      rm terraform.tfvars

Security, and the count attribute

  • environment: ~/terraform/AWS/networking
    1
    2
    3
    mkdir -p  ~/terraform/AWS/networking
    cd ~/terraform/AWS/networking
    touch {main.tf,variables.tf,outputs.tf,terraform.tfvars}
  • example
    1. Edit main.tf
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      #----networking/main.tf----

      data "aws_availability_zones" "available" {}

      resource "aws_vpc" "tf_vpc" {
      cidr_block = "${var.vpc_cidr}"
      enable_dns_hostnames = true
      enable_dns_support = true

      tags {
      Name = "tf_vpc"
      }
      }

      resource "aws_internet_gateway" "tf_internet_gateway" {
      vpc_id = "${aws_vpc.tf_vpc.id}"

      tags {
      Name = "tf_igw"
      }
      }

      resource "aws_route_table" "tf_public_rt" {
      vpc_id = "${aws_vpc.tf_vpc.id}"

      route {
      cidr_block = "0.0.0.0/0"
      gateway_id = "${aws_internet_gateway.tf_internet_gateway.id}"
      }

      tags {
      Name = "tf_public"
      }
      }

      resource "aws_default_route_table" "tf_private_rt" {
      default_route_table_id = "${aws_vpc.tf_vpc.default_route_table_id}"

      tags {
      Name = "tf_private"
      }
      }

      resource "aws_subnet" "tf_public_subnet" {
      count = 2
      vpc_id = "${aws_vpc.tf_vpc.id}"
      cidr_block = "${var.public_cidrs[count.index]}"
      map_public_ip_on_launch = true
      availability_zone = "${data.aws_availability_zones.available.names[count.index]}"

      tags {
      Name = "tf_public_${count.index + 1}"
      }
      }

      resource "aws_route_table_association" "tf_public_assoc" {
      count = "${aws_subnet.tf_public_subnet.count}"
      subnet_id = "${aws_subnet.tf_public_subnet.*.id[count.index]}"
      route_table_id = "${aws_route_table.tf_public_rt.id}"
      }

      resource "aws_security_group" "tf_public_sg" {
      name = "tf_public_sg"
      description = "Used for access to the public instances"
      vpc_id = "${aws_vpc.tf_vpc.id}"

      #SSH
      ingress {
      from_port = 22
      to_port = 22
      protocol = "tcp"
      cidr_blocks = ["${var.accessip}"]
      }

      #HTTP
      ingress {
      from_port = 80
      to_port = 80
      protocol = "tcp"
      cidr_blocks = ["${var.accessip}"]
      }

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

Variables and outputs

  1. Edit variables.tf
    1
    2
    3
    4
    5
    6
    7
    8
    #----networking/variables.tf----
    variable "vpc_cidr" {}

    variable "public_cidrs" {
    type = "list"
    }

    variable "accessip" {}
  2. Edit outputs.tf
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    #-----networking/outputs.tf----

    output "public_subnets" {
    value = "${aws_subnet.tf_public_subnet.*.id}"
    }

    output "public_sg" {
    value = "${aws_security_group.tf_public_sg.id}"
    }

    output "subnet_ips" {
    value = "${aws_subnet.tf_public_subnet.*.cidr_block}"
    }
  3. Edit terraform.tfvars
    1
    2
    3
    4
    5
    6
    vpc_cidr     = "10.123.0.0/16"
    public_cidrs = [
    "10.123.1.0/24",
    "10.123.2.0/24"
    ]
    accessip = "0.0.0.0/0"
  4. Work with Terraform
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    export AWS_ACCESS_KEY_ID="[ACCESS_KEY]"
    export AWS_SECRET_ACCESS_KEY="[SECRET_KEY]]"
    terraform init
    terraform validate
    # deploy network
    terraform apply -auto-approve

    # clean up
    terraform destroy -auto-approve
    rm terraform.tfvars

Root module

  • environment: ~/terraform/AWS
  • example
    1. Edit main.tf
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      provider "aws" {
      region = "${var.aws_region}"
      }

      # Deploy Storage Resources
      module "storage" {
      source = "./storage"
      project_name = "${var.project_name}"
      }

      # Deploy Networking Resources
      module "networking" {
      source = "./networking"
      vpc_cidr = "${var.vpc_cidr}"
      public_cidrs = "${var.public_cidrs}"
      accessip = "${var.accessip}"
      }
    2. Edit variables.tf
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      #----root/variables.tf-----
      variable "aws_region" {}

      #------ storage variables
      variable "project_name" {}

      #-------networking variables
      variable "vpc_cidr" {}
      variable "public_cidrs" {
      type = "list"
      }
      variable "accessip" {}
    3. Edit terraform.tfvars
      1
      2
      3
      4
      5
      6
      7
      8
      aws_region   = "us-east-1"
      project_name = "la-terraform"
      vpc_cidr = "10.123.0.0/16"
      public_cidrs = [
      "10.123.1.0/24",
      "10.123.2.0/24"
      ]
      accessip = "0.0.0.0/0"
    4. Work with Terraform
      1
      2
      3
      4
      5
      6
      7
      8
      export AWS_ACCESS_KEY_ID="[ACCESS_KEY]"
      export AWS_SECRET_ACCESS_KEY="[SECRET_KEY]]"
      terraform init
      terraform validate
      terraform apply -auto-approve

      # clean up
      terraform destroy -auto-approve

Compute

AMI data, key pair, and the file function

  • environment: ~/terraform/AWS/compute
    1
    2
    3
    mkdir -p  ~/terraform/AWS/compute
    cd ~/terraform/AWS/compute
    touch {main.tf,variables.tf,outputs.tf}
  • create SSH key
    1
    ssh-keygen
  • example
    1. Edit main.tf
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      #----compute/main.tf#----
      data "aws_ami" "server_ami" {
      most_recent = true

      owners = ["amazon"]

      filter {
      name = "name"
      values = ["amzn-ami-hvm*-x86_64-gp2"]
      }
      }

      resource "aws_key_pair" "tf_auth" {
      key_name = "${var.key_name}"
      public_key = "${file(var.public_key_path)}"
      }
    2. Edit variables.tf
      1
      2
      3
      4
      #----compute/variables.tf----
      variable "key_name" {}

      variable "public_key_path" {}
    3. Work with Terraform
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      export AWS_ACCESS_KEY_ID="[ACCESS_KEY]"
      export AWS_SECRET_ACCESS_KEY="[SECRET_KEY]]"
      terraform init
      terraform validate
      # replace key_name and public_key_path for your own values
      terraform plan -out=tfplan -var 'key_name=tfkey' -var 'public_key_path=/home/myUser/.ssh/id_rsa.pub'
      terraform apply -auto-approve

      # clen up
      terraform destroy -auto-approve

EC2 Instance

  1. Edit main.tf
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    #-----compute/main.tf#-----
    data "aws_ami" "server_ami" {
    most_recent = true

    owners = ["amazon"]

    filter {
    name = "name"
    values = ["amzn-ami-hvm*-x86_64-gp2"]
    }
    }

    resource "aws_key_pair" "tf_auth" {
    key_name = "${var.key_name}"
    public_key = "${file(var.public_key_path)}"
    }

    data "template_file" "user-init" {
    count = 2
    template = "${file("${path.module}/userdata.tpl")}"

    vars {
    firewall_subnets = "${element(var.subnet_ips, count.index)}"
    }
    }

    resource "aws_instance" "tf_server" {
    count = "${var.instance_count}"
    instance_type = "${var.instance_type}"
    ami = "${data.aws_ami.server_ami.id}"

    tags {
    Name = "tf_server-${count.index +1}"
    }

    key_name = "${aws_key_pair.tf_auth.id}"
    vpc_security_group_ids = ["${var.security_group}"]
    subnet_id = "${element(var.subnets, count.index)}"
    user_data = "${data.template_file.user-init.*.rendered[count.index]}"
    }
  2. Edit userdata.tpl
    1
    2
    3
    4
    5
    #!/bin/bash
    yum install httpd -y
    echo "Subnet for Firewall: ${firewall_subnets}" >> /var/www/html/index.html
    service httpd start
    chkconfig httpd on
  3. Edit variables.tf
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    #-----compute/variables.tf

    variable "key_name" {}

    variable "public_key_path" {}

    variable "subnet_ips" {
    type = "list"
    }

    variable "instance_count" {}

    variable "instance_type" {}

    variable "security_group" {}

    variable "subnets" {
    type = "list"
    }
  4. Edit outputs.tf
    1
    2
    3
    4
    5
    6
    7
    8
    9
    #-----compute/outputs.tf-----

    output "server_id" {
    value = "${join(", ", aws_instance.tf_server.*.id)}"
    }

    output "server_ip" {
    value = "${join(", ", aws_instance.tf_server.*.public_ip)}"
    }

Root module

  1. Edit main.tf
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    provider "aws" {
    region = "${var.aws_region}"
    }
    # Deploy Storage Resources
    module "storage" {
    source = "./storage"
    project_name = "${var.project_name}"
    }

    # Deploy Networking Resources
    module "networking" {
    source = "./networking"
    vpc_cidr = "${var.vpc_cidr}"
    public_cidrs = "${var.public_cidrs}"
    accessip = "${var.accessip}"
    }

    # Deploy Compute Resources
    module "compute" {
    source = "./compute"
    instance_count = "${var.instance_count}"
    key_name = "${var.key_name}"
    public_key_path = "${var.public_key_path}"
    instance_type = "${var.server_instance_type}"
    subnets = "${module.networking.public_subnets}"
    security_group = "${module.networking.public_sg}"
    subnet_ips = "${module.networking.subnet_ips}"
    }
  2. Edit variables.tf
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    #----root/variables.tf-----
    variable "aws_region" {}

    #------ storage variables
    variable "project_name" {}

    #-------networking variables
    variable "vpc_cidr" {}
    variable "public_cidrs" {
    type = "list"
    }
    variable "accessip" {}

    #-------compute variables
    variable "key_name" {}
    variable "public_key_path" {}
    variable "server_instance_type" {}
    variable "instance_count" {
    default = 1
    }
  3. Edit outputs.tf
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    #----root/outputs.tf-----

    #----storage outputs------

    output "Bucket Name" {
    value = "${module.storage.bucketname}"
    }

    #---Networking Outputs -----
    output "Public Subnets" {
    value = "${join(", ", module.networking.public_subnets)}"
    }

    output "Subnet IPs" {
    value = "${join(", ", module.networking.subnet_ips)}"
    }

    output "Public Security Group" {
    value = "${module.networking.public_sg}"
    }

    #---Compute Outputs ------
    output "Public Instance IDs" {
    value = "${module.compute.server_id}"
    }

    output "Public Instance IPs" {
    value = "${module.compute.server_ip}"
    }
  4. Edit terraform.tfvars
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    aws_region   = "us-west-1"
    project_name = "la-terraform"
    vpc_cidr = "10.123.0.0/16"
    public_cidrs = [
    "10.123.1.0/24",
    "10.123.2.0/24"
    ]
    accessip = "0.0.0.0/0"
    key_name = "tf_key"
    public_key_path = "/home/cloud_user/.ssh/id_rsa.pub"
    server_instance_type = "t2.micro"
    instance_count = 2
  5. work with Terraform:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    export AWS_ACCESS_KEY_ID="[ACCESS_KEY]"
    export AWS_SECRET_ACCESS_KEY="[SECRET_KEY]"
    terraform init
    terraform validate
    terraform plan
    terraform apply

    # clean up
    terraform destroy