KEMBAR78
Terraform: An Overview & Introduction | PDF
TERRAFORM
C++ :: 2000
PYTHON :: 2000...
PHP & JS :: 2004...
ACTIONSCRIPT :: 2006-09
GO :: 2016...
Lee Trout
DEVOPS @ YHAT
LEAD ENGINEER @ WRW
COFOUNDER @ EEX
CONSULTANT @ NATGEO
PYTHON @ WAPO
DEGREE IN COMPUTER ANIMATION
NO ONE IS SO TERRIBLY DECEIVED AS HE
WHO DOES NOT HIMSELF SUSPECT IT.
Soren Kierkegaard
I AM A TERRAFORM EXPERT. TRUST ME.
TERRAFORM IS COOL
Me
ACTUALLY I'M JUST A FAN
HELLO TERRAFORM
OVERVIEW
▸ What is Terraform
▸ Code Sample & Compare to Cloud Formation
▸ Create AWS Resources
▸ S3 Bucket
▸ EC2 Instance in VPC
▸ Compose with Digital Ocean Resources
▸ Create Droplet
▸ Grant S3 Access to Droplet
KEEP
CALMAND
AUTOMATE
ALL THE THINGS
HELLO TERRAFORM
DEVOPS @ YHAT
HELLO TERRAFORM
WHAT IS TERRAFORM
▸ Infrastructure as code
▸ A tool to manage virtual server life cycles (AWS, VMWare, etc)
▸ A tool to manage supporting services (DNS, Email)
▸ A tool to manage system servies (MySQL, PostgreSQL)
▸ Configuration files can be HCL or JSON
▸ Created by Hashicorp (Vagrant et al.)
▸ Written in Go
HELLO TERRAFORM
WHAT DOES TERRAFORM LOOK LIKE?
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"
}
HELLO TERRAFORM
TERRAFORM
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"
}
}
}
}
CLOUD FORMATION
HELLO TERRAFORM
TERRAFORM
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"
}
}
}
}
CLOUD FORMATION
The name to reference
within terraform files
The name of the bucket
Generated name:
stackname-my-cool-bucket-abc123id
LET'S TERRAFORM AN
S3 BUCKET
HELLO TERRAFORM
MAIN.TF
# Configure aws with a default region
provider "aws" {
region = "us-east-1"
}
# Create a demo s3 bucket
resource "aws_s3_bucket" "chadev_tf_demo" {
bucket = "chadev-tf-demo"
tags {
Name = "Terraform demo for ChaDev"
Environment = "Prod"
TF = "yes"
}
}
HELLO TERRAFORM
TERRAFORM PLAN
+ aws_s3_bucket.chadev_tf_demo
acl: "" => "private"
arn: "" => "<computed>"
bucket: "" => "chadev-tf-demo"
force_destroy: "" => "0"
hosted_zone_id: "" => "<computed>"
region: "" => "<computed>"
tags.#: "" => "3"
tags.Environment: "" => "Prod"
tags.Name: "" => "Terraform demo for ChaDev"
tags.TF: "" => "yes"
website_domain: "" => "<computed>"
website_endpoint: "" => "<computed>"
Plan: 1 to add, 0 to change, 0 to destroy.
HELLO TERRAFORM
TERRAFORM APPLY
aws_s3_bucket.chadev_tf_demo: Creating...
acl: "" => "private"
arn: "" => "<computed>"
bucket: "" => "chadev-tf-demo"
force_destroy: "" => "0"
hosted_zone_id: "" => "<computed>"
region: "" => "<computed>"
tags.#: "" => "3"
tags.Environment: "" => "Prod"
tags.Name: "" => "Terraform demo for ChaDev"
tags.TF: "" => "yes"
website_domain: "" => "<computed>"
website_endpoint: "" => "<computed>"
aws_s3_bucket.chadev_tf_demo: Creation complete
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
State path: terraform.tfstate
HELLO TERRAFORM
TERRAFORM STATE
"version": 1,
"serial": 1,
"modules": [
{
"path": [
"root"
],
"outputs": {},
"resources": {
"aws_s3_bucket.chadev_tf_demo": {
"type": "aws_s3_bucket",
"primary": {
"id": "chadev-tf-demo",
"attributes": {
"acl": "private",
"arn": "arn:aws:s3:::chadev-tf-demo",
"bucket": "chadev-tf-demo",
"cors_rule.#": "0",
"force_destroy": "false",
"hosted_zone_id": "Z3AQBSTGFYJSTF",
"id": "chadev-tf-demo",
"policy": "",
"region": "us-east-1",
"tags.#": "3",
"tags.Environment": "Prod",
"tags.Name": "Terraform demo for ChaDev",
"tags.TF": "yes",
"website.#": "0"
HELLO TERRAFORM
TERRAFORM STATE
▸ JSON
▸ Updated when Terraform runs
▸ Refreshed before an operation takes place
▸ Backwards compatible between TF versions
HELLO TERRAFORM
TANGENT: USING REMOTE STATE AS A DEPENDENCY
resource "terraform_remote_state" "core" {
backend = "s3"
config {
bucket = "a-tf-state-bucket"
key = "hosted/core.json"
region = "us-east-1"
}
}
resource "aws_route53_record" "my-awesome-subdomain.yhat.com" {
zone_id = "${terraform_remote_state.core.output.yhat-zone-id}"
name = "my-awesome-subdomain.yhat.com"
type = "A"
ttl = "60"
records = [
"${aws_eip.my-elastic-ip.public_ip}"
]
}
LET'S TERRAFORM A
SERVER
HELLO TERRAFORM
CREATING A SERVER
# Create a key pair for our servers
resource "aws_key_pair" "chadev_demo" {
key_name = "chadev_demo_key"
public_key = "ssh-rsa ..."
}
# Create a demo server
resource "aws_instance" "chadev_demo_server" {
# CentOS 7 (x86_64) with Updates HVM
ami = "ami-6d1c2007"
instance_type = "t2.medium"
key_name = "${aws_key_pair.chadev_demo.key_name}"
tags {
Name = "ChaDev Demo Server"
TF = "yes"
}
}
HELLO TERRAFORM
CREATING A SERVER
# Create a key pair for our servers
resource "aws_key_pair" "chadev_demo" {
key_name = "chadev_demo_key"
public_key = "ssh-rsa ..."
}
# Create a demo server
resource "aws_instance" "chadev_demo_server" {
# CentOS 7 (x86_64) with Updates HVM
ami = "ami-6d1c2007"
instance_type = "t2.medium"
key_name = "${aws_key_pair.chadev_demo.key_name}"
tags {
Name = "ChaDev Demo Server"
TF = "yes"
}
}
Get the name of the key by referencing the
output of the aws_key_pair.
Notice we're using the variable name we define.
HELLO TERRAFORM
TERRAFORM PLAN
+ aws_instance.chadev_demo_server
...
+ aws_key_pair.chadev_demo
...
Plan: 2 to add, 0 to change, 0 to destroy.
HELLO TERRAFORM
CREATING A SERVER
Error applying plan:
1 error(s) occurred:
* aws_instance.chadev_demo_server: Error launching source instance: VPCResourceNotSpecified: The
specified instance type can only be used in a VPC. A subnet ID or network interface ID is required
to carry out the request.
status code: 400, request id:
HELLO TERRAFORM
CREATING A SERVER
Terraform does not automatically rollback in the face of errors.
Instead, your Terraform state file has been partially updated with
any resources that successfully completed. Please address the error
above and apply again to incrementally change your infrastructure.
HELLO TERRAFORM
TERRAFORM PLAN
Plan: 1 to add, 0 to change, 0 to destroy.
LET'S TERRAFORM A
VPC
HELLO TERRAFORM
PARTS OF A VPC
▸ VPC definition (CIDR block)
▸ Subnets
▸ Internet gateway to allow our subnet to route to the internet
▸ Routing table to define how traffic is routed
▸ Main route table association to tell the vpc what route table to use by default
HELLO TERRAFORM
THE VPC
########
# VPC
########
resource "aws_vpc" "chadev_demo" {
cidr_block = "10.20.0.0/16"
enable_dns_hostnames = true
tags {
Name = "ChaDev Demo VPC"
Env = "Prod"
TF = "yes"
}
}
HELLO TERRAFORM
THE SUBNET
# Create a subnet for our instances
resource "aws_subnet" "chadev_demo_1a" {
vpc_id = "${aws_vpc.chadev_demo.id}"
cidr_block = "10.20.0.0/24"
availability_zone = "us-east-1a"
map_public_ip_on_launch = true
tags {
Name = "ChaDev Subnet AZ 1a"
Env = "Prod"
TF = "yes"
}
}
HELLO TERRAFORM
THE INTERNET GATEWAY
# Create an internet gateway so we can talk to the world
resource "aws_internet_gateway" "main" {
vpc_id = "${aws_vpc.chadev_demo.id}"
tags {
Name = "ChaDev VPC IGW"
Env = "Prod"
TF = "yes"
}
}
HELLO TERRAFORM
THE ROUTE TABLE
# Replace the default routing table with one explicitly defined here
resource "aws_route_table" "chadev_demo" {
vpc_id = "${aws_vpc.chadev_demo.id}"
# Send all traffic not matched by other routing rules to the internet
route {
cidr_block = "0.0.0.0/0"
gateway_id = "${aws_internet_gateway.main.id}"
}
tags {
Name = "ChaDev Main RTB"
Env = "Prod"
TF = "yes"
}
}
HELLO TERRAFORM
THE ROUTE TABLE ASSOCIATION
# Assign our route table as the default route table
resource "aws_main_route_table_association" "main" {
vpc_id = "${aws_vpc.yhat-tf-demo.id}"
route_table_id = "${aws_route_table.yhat-tf-default.id}"
}
HELLO TERRAFORM
MAIN.TF
# Configure aws with a default region
provider "aws" {
region = "us-east-1"
}
# Create a demo s3 bucket
resource "aws_s3_bucket" "chadev_tf_demo" {
bucket = "chadev-tf-demo"
tags {
Name = "Terraform demo for ChaDev"
Environment = "Prod"
TF = "yes"
}
}
# Create a key pair for our servers
resource "aws_key_pair" "chadev_demo" {
key_name = "chadev_demo_key"
public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDGrwdl2hRk2JvzfoCgZFegX/qlGyaVZRxKJCleGwKRTjG/RPORj6lZnpGPRzKmhwcN78qXDmgGWY7cfaApuLC6lFZNissBeCsjDLFmsnyev+7EwZu3Bt05q7goFKtoaZ2oU2Ijv6TIQ0yj4LKEaBkHAuJgddwvFScoPDt35JB89NXybM9kXMWsiNB3vHLGmYs9qcC/CC7hlY9Y8oHqJRe+1MfbULp6YAgIM4iLD5/rKjUdnyhUSllipuDeECTVuwHK+9LWIAdT8+ZIJe1mVI/ZyDDKk2YDlWPvUrXmVfLMoXeVil7mEe88I9iZtRJMTmzY3oHOHowGkzJ8JRnLmWaF yhat@s-MacBook-Pro.local"
}
# Create a demo server
resource "aws_instance" "chadev_demo_server" {
# CentOS 7 (x86_64) with Updates HVM
ami = "ami-6d1c2007"
instance_type = "t2.micro"
key_name = "${aws_key_pair.chadev_demo.key_name}"
tags {
Name = "ChaDev Demo Server"
TF = "yes"
}
}
########
# VPC
########
resource "aws_vpc" "chadev_demo" {
cidr_block = "10.20.0.0/16"
enable_dns_hostnames = true
tags {
Name = "ChaDev Demo VPC"
Env = "Prod"
TF = "yes"
}
}
# Create a subnet for our instances
resource "aws_subnet" "chadev_demo_1a" {
vpc_id = "${aws_vpc.chadev_demo.id}"
cidr_block = "10.20.0.0/24"
availability_zone = "us-east-1a"
map_public_ip_on_launch = true
tags {
Name = "ChaDev Subnet AZ 1a"
Env = "Prod"
TF = "yes"
}
}
# Create an internet gateway so we can talk to the world
resource "aws_internet_gateway" "main" {
vpc_id = "${aws_vpc.chadev_demo.id}"
tags {
Name = "ChaDev VPC IGW"
Env = "Prod"
TF = "yes"
}
}
# Replace the default routing table with one explicitly defined here
resource "aws_route_table" "chadev_demo" {
vpc_id = "${aws_vpc.chadev_demo.id}"
# Send all traffic not matched by other routing rules to the internet
route {
cidr_block = "0.0.0.0/0"
gateway_id = "${aws_internet_gateway.main.id}"
}
tags {
Name = "ChaDev Main RTB"
Env = "Prod"
TF = "yes"
}
}
# Assign our route table as the default route table
resource "aws_main_route_table_association" "main" {
vpc_id = "${aws_vpc.yhat-tf-demo.id}"
route_table_id = "${aws_route_table.yhat-tf-default.id}"
}
HELLO TERRAFORM
ORGANIZING TERRAFORM FILES
▸ Terraform looks for all files ending in .tf
▸ I personally prefix the files with the provider where applicable
▸ Let's put the vpc in aws.vpc.tf
▸ Let's put the rest in aws.demo.tf
HELLO TERRAFORM
TERRAFORM APPLY
Error applying plan:
1 error(s) occurred:
* aws_instance.chadev_demo_server: Error launching source instance: VPCResourceNotSpecified: The
specified instance type can only be used in a VPC. A subnet ID or network interface ID is required
to carry out the request.
status code: 400, request id:
HELLO TERRAFORM
SPECIFY SUBNET AND SECURITY GROUPS
vpc_security_group_ids = [
"${aws_security_group.chadev_demo.id}",
]
subnet_id = "${aws_subnet.chadev_demo_1a.id}"
LAUNCH & REVIEW
HELLO TERRAFORM
TERRAFORM WILL GRAPH DEPENDENCIES
▸ terraform graph | dot -Tpng > graph.png
HELLO TERRAFORM
TERRAFORM GRAPH | DOT -TPDF > GRAPH.PDF
aws_instance.chadev_demo_server
aws_key_pair.chadev_demo
aws_security_group.chadev_demo aws_subnet.chadev_demo_1a aws_internet_gateway.main
aws_vpc.chadev_demo
provider.aws
aws_main_route_table_association.main
aws_route_table.chadev_demo
aws_s3_bucket.chadev_tf_demo
COMPOSE
HELLO TERRAFORM
CREATE DIGITAL OCEAN SSH KEY & DROPLET
# Create a new SSH key
resource "digitalocean_ssh_key" "chadev_demo" {
name = "ChaDev Demo"
public_key = "${file("/Users/yhat/.ssh/id_rsa.pub")}"
}
# Create a new Web droplet in the nyc2 region
resource "digitalocean_droplet" "web" {
image = "ubuntu-14-04-x64"
name = "chadev-demo"
region = "nyc2"
size = "512mb"
}
HELLO TERRAFORM
CREATE DIGITAL OCEAN SSH KEY & DROPLET
# Create a new SSH key
resource "digitalocean_ssh_key" "chadev_demo" {
name = "ChaDev Demo"
public_key = "${file("/Users/yhat/.ssh/id_rsa.pub")}"
}
# Create a new Web droplet in the nyc2 region
resource "digitalocean_droplet" "web" {
image = "ubuntu-14-04-x64"
name = "chadev-demo"
region = "nyc2"
size = "512mb"
}
HELLO TERRAFORM
CREATE DIGITAL OCEAN SSH KEY & DROPLET
# Set the variable value in *.tfvars file
# or using -var="do_token=..." CLI option
variable "do_token" {}
# Configure the DigitalOcean Provider
provider "digitalocean" {
token = "${var.do_token}"
}
HELLO TERRAFORM
TERRAFORM.TFVARS
# Set the variable value in *.tfvars file
# or using -var="do_token=..." CLI option
variable "do_token" {}
# Configure the DigitalOcean Provider
provider "digitalocean" {
token = "${var.do_token}"
}
HELLO TERRAFORM
AWS.DEMO.TF
# Upload our html file to our bucket
resource "aws_s3_bucket_object" "object" {
bucket = "${aws_s3_bucket.chadev_tf_demo.id}"
key = "index.html"
source = "./index.html"
}
HELLO TERRAFORM
AWS.DEMO.TF
# Create a demo s3 bucket
resource "aws_s3_bucket" "chadev_tf_demo" {
bucket = "chadev-tf-demo-1"
website {
index_document = "index.html"
}
tags {
Name = "Terraform demo for ChaDev"
Environment = "Prod"
TF = "yes"
}
}
HELLO TERRAFORM
CURL THE ENDPOINT
$ curl chadev-tf-demo-1.s3.amazonaws.com/
<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>AccessDenied</Code><Message>Access Denied</Message><RequestId>EC6CC75BE6AA9040</
RequestId><HostId>iIBLHlo4RWzMFE4Sh3AVXv0qD2umr7G7qpI1GaMbCnFmmQv/ZP4KeoeIpWqwC8II</HostId></Error>
HELLO TERRAFORM
ALLOW ACCESS FROM DIGITAL OCEAN
policy = <<EOF
{
"Version": "2008-10-17",
"Id": "S3PolicyId1",
"Statement": [
{
"Sid": "IPAllow",
"Effect": "Allow",
"Principal": {
"AWS": "*"
},
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::chadev-tf-demo-1/*",
"arn:aws:s3:::chadev-tf-demo-1"
],
"Condition": {
"IpAddress": {
"aws:SourceIp": "${digitalocean_droplet.chadev_demo.ipv4_address}/32"
}
}
}
]
}
EOF
HELLO TERRAFORM
ALLOW ACCESS FROM DIGITAL OCEAN
"Condition": {
"IpAddress": {
"aws:SourceIp": "${digitalocean_droplet.chadev_demo.ipv4_address}/32"
}
}
HELLO TERRAFORM
aws_instance.chadev_demo_server
aws_key_pair.chadev_demo
aws_security_group.chadev_demo aws_subnet.chadev_demo_1a
provider.aws
aws_internet_gateway.main
aws_vpc.chadev_demo
aws_main_route_table_association.main
aws_route_table.chadev_demo
aws_s3_bucket.chadev_tf_demo
digitalocean_droplet.chadev_demo
aws_s3_bucket_object.object
digitalocean_ssh_key.chadev_demo
provider.digitalocean
HELLO TERRAFORM
aws_ecr_repository.boxr-ops2
provider.aws
aws_ecr_repository.tasty-aptly aws_ecr_repository.tasty-yummy
aws_ecr_repository_policy.boxr-ops2aws_ecr_repository_policy.tasty-aptly aws_ecr_repository_policy.tasty-yummy aws_ecs_cluster.tasty
aws_ecs_task_definition.tasty
aws_ecs_task_definition.tasty-aptly
aws_iam_access_key.tasty-ecs-container
aws_ecs_task_definition.tasty-yummy
aws_eip.metrics-server-eip
aws_instance.metrics-server
aws_iam_user.tasty-ecs-container
aws_iam_instance_profile.metrics-server
aws_iam_role.yhat-build
aws_iam_instance_profile.tasty-ecs-profile
aws_iam_role.tasty-ecs aws_iam_policy.tasty-ecs aws_iam_policy.yhat-build-access
aws_iam_policy_attachment.tasty-ecs-attach aws_iam_policy_attachment.yhat-build-access-attach
aws_iam_role.lambda-tasty
aws_iam_role_policy.tasty-lambdaaws_iam_user_policy.tasty-ecs-user-policy
aws_instance.tasty-container-server
aws_security_group.tasty-container-server aws_subnet.yhat-tf-demo-1a
aws_route53_delegation_set.godaddy
aws_route53_record.20d99ed1-sciencecluster-yhat-com
aws_route53_zone.yhat-com
aws_route53_record.20d99ed1-scienceops-yhat-com aws_route53_record.blog-yhat-com aws_route53_record.c-yhat-com aws_route53_record.conda-yhat-com aws_route53_record.email-yhat-com
aws_route53_record.et-s-yhat-com
aws_route53_record.help-yhat-com aws_route53_record.mx-yhat-com aws_route53_record.rodeo-updates-yhat-com aws_route53_record.rodeo-yhat-com aws_route53_record.slack-yhat-com aws_route53_record.txt2-yhat-com aws_route53_record.www-yhat-com aws_route53_record.yhat-com
aws_s3_bucket.yhat-build aws_s3_bucket.yhat-security-review aws_s3_bucket.yhat-tasty aws_s3_bucket.yhat-tasty-ppa aws_s3_bucket.yhat-tasty-yum
aws_security_group.metrics-server
aws_vpc.yhat-tf-demo
aws_internet_gateway.main
provider.aws
aws_main_route_table_association.main
aws_route_table.yhat-tf-default
provider.aws
THANK YOU
THERE'S SO MUCH MORE BUT WE'RE OUT OF TIME

Terraform: An Overview & Introduction

  • 1.
  • 2.
    C++ :: 2000 PYTHON:: 2000... PHP & JS :: 2004... ACTIONSCRIPT :: 2006-09 GO :: 2016... Lee Trout DEVOPS @ YHAT LEAD ENGINEER @ WRW COFOUNDER @ EEX CONSULTANT @ NATGEO PYTHON @ WAPO DEGREE IN COMPUTER ANIMATION
  • 3.
    NO ONE ISSO TERRIBLY DECEIVED AS HE WHO DOES NOT HIMSELF SUSPECT IT. Soren Kierkegaard I AM A TERRAFORM EXPERT. TRUST ME.
  • 4.
  • 5.
    HELLO TERRAFORM OVERVIEW ▸ Whatis Terraform ▸ Code Sample & Compare to Cloud Formation ▸ Create AWS Resources ▸ S3 Bucket ▸ EC2 Instance in VPC ▸ Compose with Digital Ocean Resources ▸ Create Droplet ▸ Grant S3 Access to Droplet KEEP CALMAND AUTOMATE ALL THE THINGS
  • 7.
  • 8.
    HELLO TERRAFORM WHAT ISTERRAFORM ▸ Infrastructure as code ▸ A tool to manage virtual server life cycles (AWS, VMWare, etc) ▸ A tool to manage supporting services (DNS, Email) ▸ A tool to manage system servies (MySQL, PostgreSQL) ▸ Configuration files can be HCL or JSON ▸ Created by Hashicorp (Vagrant et al.) ▸ Written in Go
  • 9.
    HELLO TERRAFORM WHAT DOESTERRAFORM LOOK LIKE? 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" }
  • 10.
    HELLO TERRAFORM TERRAFORM 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" } } } } CLOUD FORMATION
  • 11.
    HELLO TERRAFORM TERRAFORM 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" } } } } CLOUD FORMATION The name to reference within terraform files The name of the bucket Generated name: stackname-my-cool-bucket-abc123id
  • 12.
  • 13.
    HELLO TERRAFORM MAIN.TF # Configureaws with a default region provider "aws" { region = "us-east-1" } # Create a demo s3 bucket resource "aws_s3_bucket" "chadev_tf_demo" { bucket = "chadev-tf-demo" tags { Name = "Terraform demo for ChaDev" Environment = "Prod" TF = "yes" } }
  • 14.
    HELLO TERRAFORM TERRAFORM PLAN +aws_s3_bucket.chadev_tf_demo acl: "" => "private" arn: "" => "<computed>" bucket: "" => "chadev-tf-demo" force_destroy: "" => "0" hosted_zone_id: "" => "<computed>" region: "" => "<computed>" tags.#: "" => "3" tags.Environment: "" => "Prod" tags.Name: "" => "Terraform demo for ChaDev" tags.TF: "" => "yes" website_domain: "" => "<computed>" website_endpoint: "" => "<computed>" Plan: 1 to add, 0 to change, 0 to destroy.
  • 15.
    HELLO TERRAFORM TERRAFORM APPLY aws_s3_bucket.chadev_tf_demo:Creating... acl: "" => "private" arn: "" => "<computed>" bucket: "" => "chadev-tf-demo" force_destroy: "" => "0" hosted_zone_id: "" => "<computed>" region: "" => "<computed>" tags.#: "" => "3" tags.Environment: "" => "Prod" tags.Name: "" => "Terraform demo for ChaDev" tags.TF: "" => "yes" website_domain: "" => "<computed>" website_endpoint: "" => "<computed>" aws_s3_bucket.chadev_tf_demo: Creation complete Apply complete! Resources: 1 added, 0 changed, 0 destroyed. State path: terraform.tfstate
  • 16.
    HELLO TERRAFORM TERRAFORM STATE "version":1, "serial": 1, "modules": [ { "path": [ "root" ], "outputs": {}, "resources": { "aws_s3_bucket.chadev_tf_demo": { "type": "aws_s3_bucket", "primary": { "id": "chadev-tf-demo", "attributes": { "acl": "private", "arn": "arn:aws:s3:::chadev-tf-demo", "bucket": "chadev-tf-demo", "cors_rule.#": "0", "force_destroy": "false", "hosted_zone_id": "Z3AQBSTGFYJSTF", "id": "chadev-tf-demo", "policy": "", "region": "us-east-1", "tags.#": "3", "tags.Environment": "Prod", "tags.Name": "Terraform demo for ChaDev", "tags.TF": "yes", "website.#": "0"
  • 17.
    HELLO TERRAFORM TERRAFORM STATE ▸JSON ▸ Updated when Terraform runs ▸ Refreshed before an operation takes place ▸ Backwards compatible between TF versions
  • 18.
    HELLO TERRAFORM TANGENT: USINGREMOTE STATE AS A DEPENDENCY resource "terraform_remote_state" "core" { backend = "s3" config { bucket = "a-tf-state-bucket" key = "hosted/core.json" region = "us-east-1" } } resource "aws_route53_record" "my-awesome-subdomain.yhat.com" { zone_id = "${terraform_remote_state.core.output.yhat-zone-id}" name = "my-awesome-subdomain.yhat.com" type = "A" ttl = "60" records = [ "${aws_eip.my-elastic-ip.public_ip}" ] }
  • 19.
  • 20.
    HELLO TERRAFORM CREATING ASERVER # Create a key pair for our servers resource "aws_key_pair" "chadev_demo" { key_name = "chadev_demo_key" public_key = "ssh-rsa ..." } # Create a demo server resource "aws_instance" "chadev_demo_server" { # CentOS 7 (x86_64) with Updates HVM ami = "ami-6d1c2007" instance_type = "t2.medium" key_name = "${aws_key_pair.chadev_demo.key_name}" tags { Name = "ChaDev Demo Server" TF = "yes" } }
  • 21.
    HELLO TERRAFORM CREATING ASERVER # Create a key pair for our servers resource "aws_key_pair" "chadev_demo" { key_name = "chadev_demo_key" public_key = "ssh-rsa ..." } # Create a demo server resource "aws_instance" "chadev_demo_server" { # CentOS 7 (x86_64) with Updates HVM ami = "ami-6d1c2007" instance_type = "t2.medium" key_name = "${aws_key_pair.chadev_demo.key_name}" tags { Name = "ChaDev Demo Server" TF = "yes" } } Get the name of the key by referencing the output of the aws_key_pair. Notice we're using the variable name we define.
  • 22.
    HELLO TERRAFORM TERRAFORM PLAN +aws_instance.chadev_demo_server ... + aws_key_pair.chadev_demo ... Plan: 2 to add, 0 to change, 0 to destroy.
  • 23.
    HELLO TERRAFORM CREATING ASERVER Error applying plan: 1 error(s) occurred: * aws_instance.chadev_demo_server: Error launching source instance: VPCResourceNotSpecified: The specified instance type can only be used in a VPC. A subnet ID or network interface ID is required to carry out the request. status code: 400, request id:
  • 24.
    HELLO TERRAFORM CREATING ASERVER Terraform does not automatically rollback in the face of errors. Instead, your Terraform state file has been partially updated with any resources that successfully completed. Please address the error above and apply again to incrementally change your infrastructure.
  • 25.
    HELLO TERRAFORM TERRAFORM PLAN Plan:1 to add, 0 to change, 0 to destroy.
  • 26.
  • 27.
    HELLO TERRAFORM PARTS OFA VPC ▸ VPC definition (CIDR block) ▸ Subnets ▸ Internet gateway to allow our subnet to route to the internet ▸ Routing table to define how traffic is routed ▸ Main route table association to tell the vpc what route table to use by default
  • 28.
    HELLO TERRAFORM THE VPC ######## #VPC ######## resource "aws_vpc" "chadev_demo" { cidr_block = "10.20.0.0/16" enable_dns_hostnames = true tags { Name = "ChaDev Demo VPC" Env = "Prod" TF = "yes" } }
  • 29.
    HELLO TERRAFORM THE SUBNET #Create a subnet for our instances resource "aws_subnet" "chadev_demo_1a" { vpc_id = "${aws_vpc.chadev_demo.id}" cidr_block = "10.20.0.0/24" availability_zone = "us-east-1a" map_public_ip_on_launch = true tags { Name = "ChaDev Subnet AZ 1a" Env = "Prod" TF = "yes" } }
  • 30.
    HELLO TERRAFORM THE INTERNETGATEWAY # Create an internet gateway so we can talk to the world resource "aws_internet_gateway" "main" { vpc_id = "${aws_vpc.chadev_demo.id}" tags { Name = "ChaDev VPC IGW" Env = "Prod" TF = "yes" } }
  • 31.
    HELLO TERRAFORM THE ROUTETABLE # Replace the default routing table with one explicitly defined here resource "aws_route_table" "chadev_demo" { vpc_id = "${aws_vpc.chadev_demo.id}" # Send all traffic not matched by other routing rules to the internet route { cidr_block = "0.0.0.0/0" gateway_id = "${aws_internet_gateway.main.id}" } tags { Name = "ChaDev Main RTB" Env = "Prod" TF = "yes" } }
  • 32.
    HELLO TERRAFORM THE ROUTETABLE ASSOCIATION # Assign our route table as the default route table resource "aws_main_route_table_association" "main" { vpc_id = "${aws_vpc.yhat-tf-demo.id}" route_table_id = "${aws_route_table.yhat-tf-default.id}" }
  • 33.
    HELLO TERRAFORM MAIN.TF # Configureaws with a default region provider "aws" { region = "us-east-1" } # Create a demo s3 bucket resource "aws_s3_bucket" "chadev_tf_demo" { bucket = "chadev-tf-demo" tags { Name = "Terraform demo for ChaDev" Environment = "Prod" TF = "yes" } } # Create a key pair for our servers resource "aws_key_pair" "chadev_demo" { key_name = "chadev_demo_key" public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDGrwdl2hRk2JvzfoCgZFegX/qlGyaVZRxKJCleGwKRTjG/RPORj6lZnpGPRzKmhwcN78qXDmgGWY7cfaApuLC6lFZNissBeCsjDLFmsnyev+7EwZu3Bt05q7goFKtoaZ2oU2Ijv6TIQ0yj4LKEaBkHAuJgddwvFScoPDt35JB89NXybM9kXMWsiNB3vHLGmYs9qcC/CC7hlY9Y8oHqJRe+1MfbULp6YAgIM4iLD5/rKjUdnyhUSllipuDeECTVuwHK+9LWIAdT8+ZIJe1mVI/ZyDDKk2YDlWPvUrXmVfLMoXeVil7mEe88I9iZtRJMTmzY3oHOHowGkzJ8JRnLmWaF yhat@s-MacBook-Pro.local" } # Create a demo server resource "aws_instance" "chadev_demo_server" { # CentOS 7 (x86_64) with Updates HVM ami = "ami-6d1c2007" instance_type = "t2.micro" key_name = "${aws_key_pair.chadev_demo.key_name}" tags { Name = "ChaDev Demo Server" TF = "yes" } } ######## # VPC ######## resource "aws_vpc" "chadev_demo" { cidr_block = "10.20.0.0/16" enable_dns_hostnames = true tags { Name = "ChaDev Demo VPC" Env = "Prod" TF = "yes" } } # Create a subnet for our instances resource "aws_subnet" "chadev_demo_1a" { vpc_id = "${aws_vpc.chadev_demo.id}" cidr_block = "10.20.0.0/24" availability_zone = "us-east-1a" map_public_ip_on_launch = true tags { Name = "ChaDev Subnet AZ 1a" Env = "Prod" TF = "yes" } } # Create an internet gateway so we can talk to the world resource "aws_internet_gateway" "main" { vpc_id = "${aws_vpc.chadev_demo.id}" tags { Name = "ChaDev VPC IGW" Env = "Prod" TF = "yes" } } # Replace the default routing table with one explicitly defined here resource "aws_route_table" "chadev_demo" { vpc_id = "${aws_vpc.chadev_demo.id}" # Send all traffic not matched by other routing rules to the internet route { cidr_block = "0.0.0.0/0" gateway_id = "${aws_internet_gateway.main.id}" } tags { Name = "ChaDev Main RTB" Env = "Prod" TF = "yes" } } # Assign our route table as the default route table resource "aws_main_route_table_association" "main" { vpc_id = "${aws_vpc.yhat-tf-demo.id}" route_table_id = "${aws_route_table.yhat-tf-default.id}" }
  • 34.
    HELLO TERRAFORM ORGANIZING TERRAFORMFILES ▸ Terraform looks for all files ending in .tf ▸ I personally prefix the files with the provider where applicable ▸ Let's put the vpc in aws.vpc.tf ▸ Let's put the rest in aws.demo.tf
  • 35.
    HELLO TERRAFORM TERRAFORM APPLY Errorapplying plan: 1 error(s) occurred: * aws_instance.chadev_demo_server: Error launching source instance: VPCResourceNotSpecified: The specified instance type can only be used in a VPC. A subnet ID or network interface ID is required to carry out the request. status code: 400, request id:
  • 36.
    HELLO TERRAFORM SPECIFY SUBNETAND SECURITY GROUPS vpc_security_group_ids = [ "${aws_security_group.chadev_demo.id}", ] subnet_id = "${aws_subnet.chadev_demo_1a.id}"
  • 37.
  • 38.
    HELLO TERRAFORM TERRAFORM WILLGRAPH DEPENDENCIES ▸ terraform graph | dot -Tpng > graph.png
  • 39.
    HELLO TERRAFORM TERRAFORM GRAPH| DOT -TPDF > GRAPH.PDF aws_instance.chadev_demo_server aws_key_pair.chadev_demo aws_security_group.chadev_demo aws_subnet.chadev_demo_1a aws_internet_gateway.main aws_vpc.chadev_demo provider.aws aws_main_route_table_association.main aws_route_table.chadev_demo aws_s3_bucket.chadev_tf_demo
  • 40.
  • 42.
    HELLO TERRAFORM CREATE DIGITALOCEAN SSH KEY & DROPLET # Create a new SSH key resource "digitalocean_ssh_key" "chadev_demo" { name = "ChaDev Demo" public_key = "${file("/Users/yhat/.ssh/id_rsa.pub")}" } # Create a new Web droplet in the nyc2 region resource "digitalocean_droplet" "web" { image = "ubuntu-14-04-x64" name = "chadev-demo" region = "nyc2" size = "512mb" }
  • 43.
    HELLO TERRAFORM CREATE DIGITALOCEAN SSH KEY & DROPLET # Create a new SSH key resource "digitalocean_ssh_key" "chadev_demo" { name = "ChaDev Demo" public_key = "${file("/Users/yhat/.ssh/id_rsa.pub")}" } # Create a new Web droplet in the nyc2 region resource "digitalocean_droplet" "web" { image = "ubuntu-14-04-x64" name = "chadev-demo" region = "nyc2" size = "512mb" }
  • 44.
    HELLO TERRAFORM CREATE DIGITALOCEAN SSH KEY & DROPLET # Set the variable value in *.tfvars file # or using -var="do_token=..." CLI option variable "do_token" {} # Configure the DigitalOcean Provider provider "digitalocean" { token = "${var.do_token}" }
  • 45.
    HELLO TERRAFORM TERRAFORM.TFVARS # Setthe variable value in *.tfvars file # or using -var="do_token=..." CLI option variable "do_token" {} # Configure the DigitalOcean Provider provider "digitalocean" { token = "${var.do_token}" }
  • 46.
    HELLO TERRAFORM AWS.DEMO.TF # Uploadour html file to our bucket resource "aws_s3_bucket_object" "object" { bucket = "${aws_s3_bucket.chadev_tf_demo.id}" key = "index.html" source = "./index.html" }
  • 47.
    HELLO TERRAFORM AWS.DEMO.TF # Createa demo s3 bucket resource "aws_s3_bucket" "chadev_tf_demo" { bucket = "chadev-tf-demo-1" website { index_document = "index.html" } tags { Name = "Terraform demo for ChaDev" Environment = "Prod" TF = "yes" } }
  • 48.
    HELLO TERRAFORM CURL THEENDPOINT $ curl chadev-tf-demo-1.s3.amazonaws.com/ <?xml version="1.0" encoding="UTF-8"?> <Error><Code>AccessDenied</Code><Message>Access Denied</Message><RequestId>EC6CC75BE6AA9040</ RequestId><HostId>iIBLHlo4RWzMFE4Sh3AVXv0qD2umr7G7qpI1GaMbCnFmmQv/ZP4KeoeIpWqwC8II</HostId></Error>
  • 49.
    HELLO TERRAFORM ALLOW ACCESSFROM DIGITAL OCEAN policy = <<EOF { "Version": "2008-10-17", "Id": "S3PolicyId1", "Statement": [ { "Sid": "IPAllow", "Effect": "Allow", "Principal": { "AWS": "*" }, "Action": "s3:*", "Resource": [ "arn:aws:s3:::chadev-tf-demo-1/*", "arn:aws:s3:::chadev-tf-demo-1" ], "Condition": { "IpAddress": { "aws:SourceIp": "${digitalocean_droplet.chadev_demo.ipv4_address}/32" } } } ] } EOF
  • 50.
    HELLO TERRAFORM ALLOW ACCESSFROM DIGITAL OCEAN "Condition": { "IpAddress": { "aws:SourceIp": "${digitalocean_droplet.chadev_demo.ipv4_address}/32" } }
  • 51.
  • 52.
    HELLO TERRAFORM aws_ecr_repository.boxr-ops2 provider.aws aws_ecr_repository.tasty-aptly aws_ecr_repository.tasty-yummy aws_ecr_repository_policy.boxr-ops2aws_ecr_repository_policy.tasty-aptlyaws_ecr_repository_policy.tasty-yummy aws_ecs_cluster.tasty aws_ecs_task_definition.tasty aws_ecs_task_definition.tasty-aptly aws_iam_access_key.tasty-ecs-container aws_ecs_task_definition.tasty-yummy aws_eip.metrics-server-eip aws_instance.metrics-server aws_iam_user.tasty-ecs-container aws_iam_instance_profile.metrics-server aws_iam_role.yhat-build aws_iam_instance_profile.tasty-ecs-profile aws_iam_role.tasty-ecs aws_iam_policy.tasty-ecs aws_iam_policy.yhat-build-access aws_iam_policy_attachment.tasty-ecs-attach aws_iam_policy_attachment.yhat-build-access-attach aws_iam_role.lambda-tasty aws_iam_role_policy.tasty-lambdaaws_iam_user_policy.tasty-ecs-user-policy aws_instance.tasty-container-server aws_security_group.tasty-container-server aws_subnet.yhat-tf-demo-1a aws_route53_delegation_set.godaddy aws_route53_record.20d99ed1-sciencecluster-yhat-com aws_route53_zone.yhat-com aws_route53_record.20d99ed1-scienceops-yhat-com aws_route53_record.blog-yhat-com aws_route53_record.c-yhat-com aws_route53_record.conda-yhat-com aws_route53_record.email-yhat-com aws_route53_record.et-s-yhat-com aws_route53_record.help-yhat-com aws_route53_record.mx-yhat-com aws_route53_record.rodeo-updates-yhat-com aws_route53_record.rodeo-yhat-com aws_route53_record.slack-yhat-com aws_route53_record.txt2-yhat-com aws_route53_record.www-yhat-com aws_route53_record.yhat-com aws_s3_bucket.yhat-build aws_s3_bucket.yhat-security-review aws_s3_bucket.yhat-tasty aws_s3_bucket.yhat-tasty-ppa aws_s3_bucket.yhat-tasty-yum aws_security_group.metrics-server aws_vpc.yhat-tf-demo aws_internet_gateway.main provider.aws aws_main_route_table_association.main aws_route_table.yhat-tf-default provider.aws
  • 53.
    THANK YOU THERE'S SOMUCH MORE BUT WE'RE OUT OF TIME