Monday, May 18, 2020

My First Infrastructure as Code with Terraform

7:15 AM Posted by Dilli Raj Maharjan 2 comments
Terraform is the infrastructure as code offering from HashiCorp. It is a tool for building, changing, and managing infrastructure in a safe, repeatable way. Operators and Infrastructure teams can use Terraform to manage environments with a configuration language called the HashiCorp Configuration Language (HCL) for human-readable, automated deployments.

Infrastructure as Code

Infrastructure as code is the process of managing infrastructure in a file or files rather than manually configuring resources using a user interface. A resource in this instance is any piece of infrastructure in a given environment, such as a virtual machine, security group, network interface, etc.

A simple workflow for deployment will follow following steps:
  • Scope
  • Author
  • Initialize
  • Plan & apply


Terraform provides following few advantages for operators and organizations of any size.
  • Platform Agnostic
  • State Management
  • Operator Confidence
In my OCI compartment, I am planning to create full set of a web server. A vcn with the name TFDemoVCN and CIDR 192.168.100.0/23 will be created.  Internet gateway TFDEMO_IGW, default security list and defalt routing table will be created to the VCN. Inside the VCN I am planning to create two subnets: PublicSubnet and PrivateSubnet. Subnet PublicSubnet is a public subnet with 192.168.100.0/24 CIDR block and subnet PrivateSubnet is a private subnet with 192.168.101/0/24 CIDR block. All required Security list, Internet Gateway, and route tables. A VM compute instance of shape VM.Standard2.1 will be created and required package for apache web will be created using remote-exec.  Once terraform apply is completed we are ready to access web via a public IP address that is printed as output. 



Basically I have used the following files with terraform.
3. vcn.tf

Execute source and environment variable files to reflect environment variables.
source env-vars


Execute terraform init to initialize the backend and provider plugins..
terraform init


Execute terraform with validate command to validate syntax on all the terraform files
terraform validate


Execute terraform plan to display the plan. With the help of plan command with terraform we will be able to view what terraform is going to create and destroy during apply command. It is best practice to use out option to save the output to the file.
terraform plan -out terraform.plan

...........


Final step is to apply terraform with terraform apply command.
terraform apply 

....

Final output of terraform apply is below.




env-vars

-----------------------------------------------------------------------------------
#!/usr/bin/env bash
### Authentication details
export TF_VAR_tenancy_ocid="ocid1.tenancy.oc1..*********ia"
export TF_VAR_user_ocid="ocid1.user.oc1..**********da"
export TF_VAR_fingerprint="e6:********:51"
export TF_VAR_private_key_path="//Users//dilli//.oci//private.pem"
### Region
export TF_VAR_region="us-ashburn-1"
### Compartment
export TF_VAR_compartment_ocid="ocid1.compartment.oc1..************mq"
### Public/private keys used on the instance
export TF_VAR_ssh_public_key=$(cat /Users/dilli/.ssh/rsa_public)
export TF_VAR_ssh_private_key=$(cat /Users/dilli/.ssh/rsa_private)
-----------------------------------------------------------------------------------

terraform.tfvars

-----------------------------------------------------------------------------------
anywhere    = "0.0.0.0/0"
-----------------------------------------------------------------------------------

vcn.tf

-----------------------------------------------------------------------------------
variable "tenancy_ocid" {}
variable "user_ocid" {}
variable "fingerprint" {}
variable "private_key_path" {}
variable "compartment_ocid" {}
variable "region" {}

variable "anywhere" {
  type = string
}

provider "oci" {
  tenancy_ocid     = var.tenancy_ocid
  user_ocid        = var.user_ocid
  fingerprint      = var.fingerprint
  private_key_path = var.private_key_path
  region           = var.region
}

resource "oci_core_vcn" "TFDemoVCN" {
  cidr_block     = "192.168.100.0/23"
  dns_label      = "TFDemoVCN"
  compartment_id = var.compartment_ocid
  display_name   = "TFDemoVCN"
}

resource "oci_core_internet_gateway" "TFDemo-IGW" {
  compartment_id = var.compartment_ocid
  display_name   = "TFDemo-IGW"
  vcn_id         = oci_core_vcn.TFDemoVCN.id
}

resource "oci_core_default_route_table" "default_route_table" {
  manage_default_resource_id = oci_core_vcn.TFDemoVCN.default_route_table_id
  display_name               = "defaultRouteTable"

  route_rules {
    destination       = var.anywhere
    destination_type  = "CIDR_BLOCK"
    network_entity_id = oci_core_internet_gateway.TFDemo-IGW.id
  }
}


resource "oci_core_default_dhcp_options" "default_dhcp_options" {
  manage_default_resource_id = oci_core_vcn.TFDemoVCN.default_dhcp_options_id
  display_name               = "defaultDhcpOptions"

  // required
  options {
    type        = "DomainNameServer"
    server_type = "VcnLocalPlusInternet"
  }

  // optional
  options {
    type                = "SearchDomain"
    search_domain_names = ["abc.com"]
  }
}

resource "oci_core_default_security_list" "default_security_list" {
  manage_default_resource_id = oci_core_vcn.TFDemoVCN.default_security_list_id
  display_name               = "defaultSecurityList"

  // allow outbound tcp traffic on all ports
  egress_security_rules {
    destination = var.anywhere
    protocol    = "6"
  }

  // allow outbound udp traffic on a port range
  egress_security_rules {
    destination = var.anywhere
    protocol    = "17"        // udp
    stateless   = true

    udp_options {
      min = 319
      max = 320
    }
  }

  // allow inbound ssh traffic
  ingress_security_rules {
    protocol  = "6"         // tcp
    source    = var.anywhere
    stateless = false

    tcp_options {
      min = 22
      max = 22
    }
  }
 // allow http ingress traffic
  ingress_security_rules {
    protocol  = "6"         // tcp
    source    = var.anywhere
    stateless = false

    tcp_options {
      min = 80
      max = 80
    }
  }

  // allow inbound icmp traffic of a specific type
  ingress_security_rules {
    protocol  = 1
    source    = var.anywhere
    stateless = true

    icmp_options {
      type = 3
      code = 4
    }
  }
}

-----------------------------------------------------------------------------------

subnet.tf

-----------------------------------------------------------------------------------
data "oci_identity_availability_domain" "ad" {
  compartment_id = "${var.tenancy_ocid}"
  ad_number      = 1
}

// A regional subnet will not specify an Availability Domain
resource "oci_core_subnet" "PublicSubnet" {
  cidr_block        = "192.168.100.0/24"
  display_name      = "PublicSubnet"
  dns_label         = "PublicSubnet"
  compartment_id    = var.compartment_ocid
  vcn_id            = oci_core_vcn.TFDemoVCN.id
  security_list_ids = [oci_core_vcn.TFDemoVCN.default_security_list_id]
  route_table_id    = oci_core_vcn.TFDemoVCN.default_route_table_id
  dhcp_options_id   = oci_core_vcn.TFDemoVCN.default_dhcp_options_id
}

// An AD based subnet will supply an Availability Domain
resource "oci_core_subnet" "PrivateSubnet" {
  availability_domain = data.oci_identity_availability_domain.ad.name
  cidr_block          = "192.168.101.0/24"
  display_name        = "PrivateSubnet"
  dns_label           = "PrivateSubnet"
  compartment_id      = var.compartment_ocid
  vcn_id              = oci_core_vcn.TFDemoVCN.id
  security_list_ids   = [oci_core_vcn.TFDemoVCN.default_security_list_id]
  route_table_id      = oci_core_vcn.TFDemoVCN.default_route_table_id
  dhcp_options_id     = oci_core_vcn.TFDemoVCN.default_dhcp_options_id
  prohibit_public_ip_on_vnic = true
}
-----------------------------------------------------------------------------------

compute.tf

-----------------------------------------------------------------------------------
variable "ssh_public_key" {}
variable "ssh_private_key" {}

variable "instance_shape" {
  default = "VM.Standard2.1"
}

variable "instance_ocpus" {
  default = 1
}

variable "instance_image_ocid" {
  type = map

  default = {
    # See https://docs.us-phoenix-1.oraclecloud.com/images/
    # Oracle-provided image "Oracle-Linux-7.5-2018.10.16-0"
    us-phoenix-1 = "ocid1.image.oc1.phx.aaaaaaaaoqj42sokaoh42l76wsyhn3k2beuntrh5maj3gmgmzeyr55zzrwwa"

    us-ashburn-1   = "ocid1.image.oc1.iad.aaaaaaaageeenzyuxgia726xur4ztaoxbxyjlxogdhreu3ngfj2gji3bayda"
    eu-frankfurt-1 = "ocid1.image.oc1.eu-frankfurt-1.aaaaaaaaitzn6tdyjer7jl34h2ujz74jwy5nkbukbh55ekp6oyzwrtfa4zma"
    uk-london-1    = "ocid1.image.oc1.uk-london-1.aaaaaaaa32voyikkkzfxyo4xbdmadc2dmvorfxxgdhpnk6dw64fa3l4jh7wa"
  }
}

variable "db_size" {
  default = "50" # size in GBs
}

resource "oci_core_instance" "TFDemoInstance" {
  availability_domain = data.oci_identity_availability_domain.ad.name
  compartment_id      = var.compartment_ocid
  display_name        = "TFDemoInstance"
  shape               = var.instance_shape

  shape_config {
    ocpus = var.instance_ocpus
  }
  create_vnic_details {
    subnet_id        = oci_core_subnet.PublicSubnet.id
    display_name     = "Primaryvnic"
    assign_public_ip = true
    hostname_label   = "TFDemo1"
  }
   source_details {
    source_type = "image"
    source_id   = var.instance_image_ocid[var.region]
  }
  metadata = {
    ssh_authorized_keys = var.ssh_public_key
    //user_data           = "${base64encode(file("./userdata/bootstrap"))}"
  }
  timeouts {
    create = "60m"
  }
}

resource "null_resource" "remote-exec" {

  provisioner "remote-exec" {
    connection {
      agent       = false
      timeout     = "30m"
      host        = oci_core_instance.TFDemoInstance.public_ip
      user        = "opc"
      private_key = var.ssh_private_key
    }

    inline = [
        "echo 'This instance was provisioned by Terraform.' | sudo tee /etc/motd",
        "sudo yum -y update",
        "sudo yum -y install httpd",
        "echo 'This is index page' | sudo tee -a /var/www/html/index.html",
        "sudo firewall-cmd --permanent --add-service=http",
        "sudo firewall-cmd --reload",
        "sudo systemctl start httpd",
       "sudo systemctl enable httpd"
    ]
  }

}

-----------------------------------------------------------------------------------


output.tf

-----------------------------------------------------------------------------------
output "vcn_id" {
  description = "ocid of created VCN. "
  value       = oci_core_vcn.TFDemoVCN.id
}

output "subnet_ids" {
  description = "ocid of subnet ids. "
  value       = oci_core_subnet.PublicSubnet.id
}

output "instance_public_ips" {
  value = [oci_core_instance.TFDemoInstance.public_ip]
}

-----------------------------------------------------------------------------------

2 comments: