Terraform: An Overview & Introduction

David Chou

Golang Taipei

Streaming Meetup

 

david74.chou @ facebook

david74.chou @ medium

david7482 @ github

What is Infrastructure as Code?

Infrastructure as Code (IaC)

The Process of Managing and Provisioning Computer Data Centers Through Machine-Readable Definition Files

Record cloud resource with IaC tool, not document !

What is Terraform?

Terraform is an open source tool for managing Infrastructure as Code

Terraform facts

  • Developed by HashiCorp
  • Opensource
  • Golang
  • HCL syntax

Core & Plugins

and more...

HCL

A structured configuration language that is both human and machine friendly, and specifically targeted towards DevOps tools, etc.

#An AMI
variable "ami" {
  description = "the AMI to use"
}

/* A multi
   line comment. */
resource "aws_instance" "web" {
  ami               = "${var.ami}"
  count             = 2
  source_dest_check = false

  connection {
    user = "root"
  }
}

Terraform command

Hello Terraform

Terraform

Cloudformation

provider "aws" {
  #Set AWS_ACCESS_KEY_ID
  #AWS_SECRET_ACCESS_KEY env vars
  region = "us-west-2"
}

resource "aws_s3_bucket" "my-cool-bucket" {
  bucket = "my-cool-bucket"
  acl    = "public-read"
}
{
    "Resources" : {
        "my-cool-bucket" : {
            "Type" : "AWS::S3::Bucket",
            "Properties" : {
                "AccessControl" : "PublicRead"
            }
        }
    }
}

Terraform flow

$ terraform init

To initialize a working directory containing Terraform configuration files

$ terraform plan

Terraform performs a refresh,  then determines what actions are necessary to achieve the desired state specified in the configuration files.

$ terraform apply

Apply the changes required to reach the desired state of the configuration

Let's terraform a S3 bucket

main.tf

#Configure aws with a default region
provider "aws" {
  region = "us-east-1"
}

/*Create a demo s3 bucket*/
resource "aws_s3_bucket" "umbocv-demo-bucket" {
  bucket = "umbocv-demo-bucket"

  tags {
    Name = "umbocv-demo-bucket"
    Environment = "internal"
    Purpose = "demo"
  }
}

Terraform plan

Terraform apply

terraform.tfstate

Let's terraform an EC2 with EIP

variables.tf

main.tf

provider "aws" {
  region = "${var.region}"
}

resource "aws_instance" "helloworld" {
  ami           = "${lookup(var.ami, var.region)}"
  instance_type = "t2.micro"
}

resource "aws_eip" "ip" {
  instance = "${aws_instance.helloworld.id}"
}
variable "region" {
  default = "ap-northeast-1"
}

variable "ami" {
  type = "map"

  default = {
    "ap-northeast-1" = "ami-044c1940d801a38d6"
    "us-east-1"      = "ami-015a7a34dba7c99d6"
  }
}

Terraform plan

+ aws_eip.ip
    id:                                               <computed>
    instance:                                    "${aws_instance.helloworld.id}"
    network_interface:                   <computed>
    vpc:                                            <computed>

+ aws_instance.helloworld
    id:                                               <computed>
    ami:                                            "ami-044c1940d801a38d6"
    arn:                                             <computed>
    associate_public_ip_address:  <computed>

 

Let's terraform a VPC

Public and Private multi-AZs VPC with NAT gateway and S3 endpoints

  • CIDR
  • Subnets
  • Internet gateway
  • NAT gateway
  • Routing tables
  • S3 endpoints

variables.tf

apne1.tfvars

usea1.tfvars

variable "region" {
  default = "ap-northeast-1"
}

variable "name" {
  default = ""
}

variable "tags" {
  type    = "map"
  default = {}
}

variable "cidr" {
  default = "0.0.0.0/0"
}

variable "azs" {
  type    = "list"
  default = []
}

variable "public_subnets" {
  type    = "list"
  default = []
}

variable "private_subnets" {
  type    = "list"
  default = []
}
region = "ap-northeast-1"
name = "david74-vpc"
tags = {
  "Environment" = "internal"
  "Purpose" = "test"
}
cidr = "10.0.0.0/16"
azs = ["ap-northeast-1a", "ap-northeast-1c"]
public_subnets = ["10.0.0.0/24", "10.0.1.0/24"]
private_subnets = ["10.0.10.0/24", "10.0.11.0/24"]
region = "us-east-1"
name = "david74-vpc"
tags = {
  "Environment" = "internal"
  "Purpose" = "test"
}
cidr = "10.1.0.0/16"
azs = ["us-east-1a", "us-east-1b"]
public_subnets = ["10.1.0.0/24", "10.1.1.0/24"]
private_subnets = ["10.1.10.0/24", "10.1.11.0/24"]
#VPC
resource "aws_vpc" "main" {
  cidr_block                        = "${var.cidr}"
  enable_dns_support       = true
  enable_dns_hostnames  = true

  tags = "${merge(map("Name", format("%s", var.name)), var.tags)}"
}

/*
tags = {
  "Name" = "david74-vpc"
  "Environment" = "internal"
  "Purpose" = "test"
}
*/
#Internet gateway
resource "aws_internet_gateway" "igw" {
  vpc_id = "${aws_vpc.main.id}"

  tags = "${merge(map("Name", format("%s", var.name)), var.tags)}"
}

Create VPC

Create Internet Gateway

#NAT gateway
resource "aws_eip" "nat" {
  vpc = true
  depends_on = ["aws_internet_gateway.igw"]

  tags = "${merge(map("Name", format("%s-nat", var.name)), var.tags)}"
}

resource "aws_nat_gateway" "ngw" {
  allocation_id = "${aws_eip.nat.id}"
  subnet_id      = "${aws_subnet.public.0.id}"

  tags = "${merge(map("Name", format("%s", var.name)), var.tags)}"
}

Create NAT Gateway

/* Private subnet */
resource "aws_subnet" "private" {
  count = "${length(var.azs)}"

  vpc_id                   = "${aws_vpc.main.id}"
  availability_zone = "${element(var.azs, count.index)}"
  cidr_block             = "${element(var.private_subnets, count.index)}"
  map_public_ip_on_launch = false

  tags = "${merge(map("Name", format("%s-private-%s", var.name, element(var.azs, count.index))), var.tags)}"
}

/* Public subnet */
resource "aws_subnet" "public" {
  count = "${length(var.azs)}"

  vpc_id                    = "${aws_vpc.main.id}"
  availability_zone  = "${element(var.azs, count.index)}"
  cidr_block              = "${element(var.public_subnets, count.index)}"
  map_public_ip_on_launch = true

  tags = "${merge(map("Name", format("%s-public-%s", var.name, element(var.azs, count.index))), var.tags)}"
}
azs = ["ap-northeast-1a", "ap-northeast-1c"]
public_subnets = ["10.0.0.0/24", "10.0.1.0/24"]
private_subnets = ["10.0.10.0/24", "10.0.11.0/24"]

Create Subnets

#Public route tables
resource "aws_route_table" "public" {
  vpc_id = "${aws_vpc.main.id}"

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

  tags = "${merge(map("Name", format("%s-public", var.name)), var.tags)}"
}

resource "aws_route_table_association" "public" {
  count = "${length(var.azs)}"

  subnet_id      = "${element(aws_subnet.public.*.id, count.index)}"
  route_table_id = "${aws_route_table.public.id}"
}

Create Route Tables

$ terraform apply -var-file=apne1.tfvars

What if we have multiple regions to provision?

$ terraform workspace new apne1

$ terraform apply -var-file=apne1.tfvars

$ terraform workspace new uses1

$ terraform apply -var-file=uses1.tfvars

Teamwork on Terraform?

Terraform Remote Backend

  • By default, .tfstate file stores on local disk
  • Save .tfstate file to a remote backend
  • Easier for state sharing and team work
  • CI/CD

S3 Remote Backend

terraform {
  backend "s3" {
    region                    = "us-west-2"
    bucket                   = "david74-terraform-remote-state-storage"
    key                         = "terraform.tfstate"
    dynamodb_table  = "terraform-state-lock-dynamo"
    encrypt                  = true
    workspace_key_prefix = "david74-demo"
  }
}

Advanced topics

Terraform module

provider "aws" {
  region  = "${var.requester_region}"
  profile = "${var.requester_aws_profile}"
}

module "vpc_peering" {
  source = "./vpc-peering"

  allow_remote_vpc_dns_resolution = "${var.allow_remote_vpc_dns_resolution}"

  # Requester Data
  requester_vpc_id = "${var.requester_vpc_id}"

  # Accepter Data
  accepter_aws_profile = "${var.accepter_aws_profile}"
  accepter_region      = "${var.accepter_region}"
  accepter_vpc_id      = "${var.accepter_vpc_id}"
}

Terraform import

  • Bring existing resources under terraform management
  • Major difference with AWS Cloudformation
resource "aws_instance" "temp" {
  /* ... */
}

$ terraform import aws_instance.temp i-abcd1234

Terraform State Mircomanagement

Terraform State Mircomanagement

$ terraform state show ADDRESS

$ terraform state mv SRC DEST

$ terraform state rm ADDRESS

Everything as Code,
but how to test?

Terratest is a Go library that makes it easier to write automated tests for your infrastructure code.

 

 

Recap

Any Question?

Terraform: An Overview & Introduction

By Ting-Li Chou

Terraform: An Overview & Introduction

  • 131