SlideShare a Scribd company logo
Terraform 0.9 + good practices
Radek Simko
@radeksimko
Terraform 0.9 + good practices
@radeksimko
@radeksimko
Terraform 0.9 + good practices
Quick 101
+ less known features
File
resource "google_compute_instance" "server" {
name = "server"
machine_type = "g1-small"
zone = "us-central1-a"
disk {
image = "ubuntu-1404-trusty-v20160114e"
}
network_interface { network = "default" }
}
resource "dnsimple_record" "hello" {
domain = "example.com"
name = "server"
value = "${google_compute_instance.server.network_interface.0.address}"
type = "A"
}
Terminal
$ terraform plan
+ google_compute_instance.server
can_ip_forward: "false"
create_timeout: "4"
disk.#: "1"
disk.0.auto_delete: "true"
disk.0.disk_encryption_key_sha256: "<computed>"
disk.0.image: "ubuntu-1404-trusty-v20160114e"
machine_type: "g1-small"
metadata_fingerprint: "<computed>"
name: "server"
network_interface.#: "1"
network_interface.0.address: "<computed>"
network_interface.0.name: "<computed>"
self_link: "<computed>"
tags_fingerprint: "<computed>"
zone: "us-central1-a"
...
Plan: 2 to add, 0 to change, 0 to destroy.
Terminal
$ terraform apply
google_compute_instance.server: Creating...
can_ip_forward: "" => "false"
create_timeout: "" => "4"
disk.#: "" => "1"
disk.0.auto_delete: "" => "true"
disk.0.disk_encryption_key_sha256: "" => "<computed>"
disk.0.image: "" => "ubuntu-1404-trusty-v20160114e"
machine_type: "" => "g1-small"
metadata_fingerprint: "" => "<computed>"
name: "" => "server"
...
zone: "" => "us-central1-a"
google_compute_instance.server: Still creating... (10s elapsed)
google_compute_instance.server: Still creating... (20s elapsed)
...
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
terraform state mv
File
resource "aws_s3_bucket" "old_bucket" {
bucket = "my_best_bucket_ever"
acl = "private"
}
File
resource "aws_s3_bucket" "new_bucket" {
bucket = "my_best_bucket_ever"
acl = "private"
}
Terminal
$ terraform state mv 
aws_s3_bucket.old_bucket 
aws_s3_bucket.new_bucket
terraform graph
Terminal
$ terraform graph | dot -Tpng > graph.png
$ open graph.png
@radeksimko
@radeksimko
More about graphs?
https://www.youtube.com/watch?v=Ce3RNfRbdZ0
@radeksimko
File
resource "aws_iam_access_key" "radek" {
user = "${aws_iam_user.radek.name}"
pgp_key = "keybase:radeksimko"
}
resource "aws_iam_user" "radek" {
name = "radeksimko"
path = "/users/"
}
File
resource "aws_iam_user_login_profile" "radek" {
user = "${aws_iam_user.radek.name}"
pgp_key = "keybase:radeksimko"
}
Terminal
$ echo "aws_iam_access_key.radek.encrypted_secret" | 
terraform console | 
base64 -D | 
gpg -d
Fksg+R3JjH5nAfTqV1XuLBIPBDv/hj8sGQpeJnhA
Terminal
$ terraform apply
...
AccessDenied: Access Denied
status code: 403, request id: D1CB93351D412243, host id:
EHSNCCkNwvEGx/9E0S2FBXmrq/QNxHKyuihgqd7JEYcjr/9QInV2sTSeA3
r3g5bkBlLX67BltfY=
Terminal
$ TF_LOG=DEBUG TF_LOG_PATH=~/tf.log terraform apply
File
-----------------------------------------------------
2017/04/16 19:41:47 [DEBUG] [aws-sdk-go] DEBUG: Request s3/ListObjects Details:
---[ REQUEST POST-SIGN ]-----------------------------
GET /?prefix=env%3A%2F HTTP/1.1
Host: tf-rsimko-test.s3.amazonaws.com
User-Agent: aws-sdk-go/1.8.10 (go1.8; darwin; amd64) APN/1.0 HashiCorp/1.0 Terraform/0.9.4-dev
Authorization: AWS4-HMAC-SHA256 Credential=...
-----------------------------------------------------
2017/04/16 19:41:48 [DEBUG] [aws-sdk-go] DEBUG: Response s3/ListObjects Details:
---[ RESPONSE ]--------------------------------------
HTTP/1.1 403 Forbidden
Connection: close
Transfer-Encoding: chunked
Content-Type: application/xml
Date: Sun, 16 Apr 2017 18:41:47 GMT
Server: AmazonS3
...
<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>AccessDenied</Code><Message>Access
Denied</Message><RequestId>D0CB93351D412243</RequestId><HostId>EHSNCCkNwvEGx/9E0S2FBXmrq/QNwHKyuihgqd7JEYcjr/9QInV2sT
SeA3r3g5bkBlLX67BltfY=</HostId></Error>
-----------------------------------------------------
File
-----------------------------------------------------
2017/04/16 19:41:47 [DEBUG] [aws-sdk-go] DEBUG: Request s3/ListObjects Details:
---[ REQUEST POST-SIGN ]-----------------------------
GET /?prefix=env%3A%2F HTTP/1.1
Host: tf-rsimko-test.s3.amazonaws.com
User-Agent: aws-sdk-go/1.8.10 (go1.8; darwin; amd64) APN/1.0 HashiCorp/1.0 Terraform/0.9.4-dev
Authorization: AWS4-HMAC-SHA256 Credential=...
-----------------------------------------------------
2017/04/16 19:41:48 [DEBUG] [aws-sdk-go] DEBUG: Response s3/ListObjects Details:
---[ RESPONSE ]--------------------------------------
HTTP/1.1 403 Forbidden
Connection: close
Transfer-Encoding: chunked
Content-Type: application/xml
Date: Sun, 16 Apr 2017 18:41:47 GMT
Server: AmazonS3
...
<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>AccessDenied</Code><Message>Access
Denied</Message><RequestId>D0CB93351D412243</RequestId><HostId>EHSNCCkNwvEGx/9E0S2FBXmrq/QNwHKyuihgqd7JEYcjr/9QInV2sT
SeA3r3g5bkBlLX67BltfY=</HostId></Error>
-----------------------------------------------------
0.8
(13th December)
@radeksimko
0.8 (13th
December)
● terraform console
Terminal
$ terraform console
@radeksimko
0.8 (13th
December)
● terraform console
● Version requirements
File
terraform {
required_version = "> 0.9.0"
}
Terminal
$ terraform plan
The currently running version of Terraform doesn't meet the
version requirements explicitly specified by the configuration.
Please use the required version or update the configuration.
Note that version requirements are usually set for a reason, so
we recommend verifying with whoever set the version requirements
prior to making any manual changes.
Module: root
Required version: > 0.9.0
Current version: 0.8.0
@radeksimko
0.8 (13th
December)
● terraform console
● Version requirements
● Conditional values
File
resource "google_compute_instance" "server" {
name = "server"
machine_type = "${var.env == "dev" ? "g1-small" : "n1-standard-2"}"
...
}
File
resource "google_compute_instance" "server" {
count = "${var.env == "dev" ? 0 : 3}"
...
}
@radeksimko
Conditional Values
● If-statements for single values within Terraform
● Enables on/off of resources (by using count)
● The beginning of more logic in Terraform configs
@radeksimko
0.8 (13th
December)
● terraform console
● Version requirements
● Conditional values
● Nomad and Vault providers
File
provider "nomad" {
address = "nomad.mycompany.com"
region = "us-east-2"
}
resource "nomad_job" "app" {
jobspec = "${file("${path.module}/job.hcl")}"
}
File
provider "vault" {}
data "vault_generic_secret" "rundeck_auth" {
path = "secret/rundeck_auth"
}
provider "rundeck" {
url = "http://rundeck.example.com/"
auth_token = "${data.vault_generic_secret.rundeck_auth.data["auth_token"]}"
}
@radeksimko
State Management
@radeksimko
POST / HTTP/1.1
Host: ec2.eu-west-2.amazonaws.com
User-Agent: aws-sdk-go/1.8.13
Authorization: ...
Content-Type: ...
Action=RunInstances&...
ImageId=ami-63342007&InstanceType=
t2.micro...
HTTP/1.1 200 OK
Connection: close
Content-Type: ...
Server: AmazonEC2
<?xml version="1.0" encoding="UTF-8"?>
<RunInstancesResponse
xmlns="http://ec2.amazonaws.com/doc/2016-11-1
5/">
…
<instanceId>i-02228e28962ee478b</instanceId>
@radeksimko
Plz change this tag
from old to new, Ta!
For which
instance?
Few days later...
@radeksimko
Existing resources
● Lookups via tags or names
○ Not everything AWS supports tags
○ Not every provider supports tags
○ Tags may not be unique
○ Unique names don’t allow graceful recreation
○ 1 extra lookup API call to get the real unique ID for modify/delete API calls
● Storing unique ID in a state (Terraform)
○ Guaranteed to be unique
○ Provider-agnostic solution
@radeksimko
Worst case scenario
● Duplicate/wrong tag/name
■ Changed wrong resource
■ Destroyed wrong resource
● Drifted/decoupled state
○ Duplicate infrastructure
○ Original (production?) resources intact
0.9
(15th March)
@radeksimko
0.9 (15th
March)
● Remote Backends
● State Locking
● Environments
● terraform init
● Data sources interpolate-able in count
@radeksimko
Remote State <= 0.8
● Awkward "remote config" command
● Users could accidentally run Terraform without remote config
● Configuration only via CLI
● Local cache of state stored in .terraform/terraform.tfstate
● Changing remote configuration was manual
Terminal
$ # Terraform <= 0.8, BEFORE REMOTE BACKENDS
$ terraform remote config 
-backend=S3 
-backend-config="bucket=<bucket>" 
-backend-config="key=<path to file>"
...
@radeksimko
Remote Backends
● Subsumes "remote state", enables locking & environments
● Configure from tf files, or CLI
● Detects configuration change
● Forces new users of a TF configuration to initialize
● One command to init them all: terraform init
File
terraform {
backend "s3" {
bucket = "mybucket"
key = "path/to/terraform.tfstate"
region = "us-east-1"
}
}
Terminal
$ terraform plan
Backend reinitialization required. Please run "terraform init".
Reason: Initial configuration of the requested backend "s3"
The "backend" is the interface that Terraform uses to store state,
perform operations, etc. If this message is showing up, it means that the
Terraform configuration you're using is using a custom configuration for
the Terraform backend.
Changes to backend configurations require reinitialization. This allows
Terraform to setup the new configuration, copy existing state, etc. This is
only done during "terraform init". Please run that command now then try again.
If the change reason above is incorrect, please verify your configuration
hasn't changed and try again. At this point, no changes to your existing
configuration or state have been made.
Failed to load backend: Initialization required. Please see the error message above.
Terminal
$ terraform init
@radeksimko
Remote Backends
● One command to init: terraform init
● Automatic detection of backend change (set, change, unset)
● No state stored locally at all
● Always gitignore .terraform folder
@radeksimko
terraform init
● Init has existed since Terraform 0.1
● Used to just setup folder structure for new projects
● Now the single source of init, safe to run multiple times
● Initializes backend, downloads modules, creates folders
● One day: downloads providers, verifies versions, ...
Terminal
$ terraform apply
Acquiring state lock. This may take a few moments…
...
Apply complete! Resources: 1 added, 0 changed, 0
destroyed.
...
Releasing state lock. This may take a few moments...
Terminal
$ terraform apply
Error locking state: Error acquiring the state lock
...
Lock Info:
ID: 551a1f24-7fe1-75b1-ea09-2e5d9b4bf71b
Path: gds-radek-tfstate-demo/terraform.tfstate
Operation: OperationTypeApply
Who: radeksimko@RadekMac
Version: 0.9.4
Created: 2017-04-18 13:52:24.622630378 +0000 UTC
Info:
@radeksimko
State Locking
● Implemented by remote backend
○ Consul
○ S3
● Automatic locking of state on write operations
● If unlock fails, error is shown with lock ID
○ terraform force-unlock LOCK_ID [DIR]
● Doesn't lock against concurrent reads
File
terraform {
backend "s3" {
bucket = "mybucket"
key = "path/to/my/key"
region = "us-east-1"
lock_table = "terraform-lock"
}
}
Terminal
$ terraform env new dev
Created and switched to environment "dev"!
You're now on a new, empty environment. Environments isolate their state,
so if you run "terraform plan" Terraform will not see any existing state
for this configuration.
@radeksimko
Environments
● State namespace scoped
● Implemented by backend
○ Consul
○ S3
File
data "aws_availability_zones" "available" {}
# Create 1 subnet per availability zone
resource "aws_subnet" "primary" {
count = "${length(data.aws_availability_zones.available.names)}"
availability_zone = "${data.aws_availability_zones.available.names[count.index]}"
...
}
Good Practices
@radeksimko
Good Practices
● Use Remote State
○ Do NOT keep your state locally
○ Do NOT commit your state to CVS
● S3 backend
○ Enable bucket versioning
○ Limit access to the bucket (IAM + bucket policies)
○ Consider KMS
● Repo structures based on a few principles …
○ No silver bullet solution
@radeksimko
1. Team’s responsibilities (trusted)
● Network team
○ VPN, VPC, Routing
● InfoSec team
○ KMS, IAM, SGs
● Ops team(s)
○ VPC, subnets, jumpbox, NAT, DNS
● Dev team(s)
○ LBs, ASGs, EC2, internal DNS (Consul), ...
@radeksimko
1. Team’s responsibilities
● ...
● Ops team(s)
○ VPC, subnets, jumpbox, NAT, DNS, ASG, LBs, EC2
● Dev team(s)
○ Internal DNS (Consul), K8S (Pods, RCs, SVCs), Docker, Nomad
@radeksimko
1. Team’s responsibilities (small org)
● Ops team
○ VPN, VPC, Routing, KMS, IAM, SGs, VPC, subnets, jumpbox, NAT, DNS
● Dev team(s)
○ LBs, EC2
@radeksimko
2. Blast Radius
● Limit impact via separate tfstate/directory
○ Stateful services
○ High impact (shared) resources (VPN, VPC, subnets)
@radeksimko
3. Resiliency
● Why do you span across regions?
● What if a region goes down?
● Store tfstate close to the real infrastructure
@radeksimko
4. Lifecycle
● How often are you going to change things?
○ Expected maintenance for pet-like services
● e.g.
○ Github Enterprise
○ Databases (SQL/NoSQL, MQs)
@radeksimko
Helpful primitives
● modules
○ Local
○ Remote (git, Github, Mercurial, HTTP, S3, …)
● variables
● outputs
● remote state data source
@radeksimko
Module or separate state?
● Module
○ Logical group of resources (Lambda-based app, K8S app)
○ Set of recommendations / best practices users may follow
○ But don’t try to catch every use-case
■ out-of-module resources sometimes work just fine
● Separate state
○ Separate lifecycle
○ Real isolation based on mentioned principles
File
module "consul" {
source = "github.com/hashicorp/consul/terraform/aws"
servers = 3
}
output "consul_address" {
value = "${module.consul.server_address}"
}
File
# github.com/hashicorp/consul -> terraform/aws/*.tf
variable "servers" {
servers = 3
}
…
output "server_address" {
value = "${aws_instance.server.0.public_dns}"
}
File
data "terraform_remote_state" "vpc" {
backend = "atlas"
config {
name = "hashicorp/vpc-prod"
}
}
resource "aws_instance" "foo" {
# ...
subnet_id = "${data.terraform_remote_state.vpc.subnet_id}"
}
File
resource "aws_security_group" "app" {
name = "${var.env}_app"
description = "My Awesome Application"
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
security_groups = ["${aws_security_group.jumpbox.id}"]
}
}
File
# Preferred way
resource "aws_security_group" "app" {
name = "${var.env}_app"
description = "My Awesome Application"
}
resource "aws_security_group_rule" "app_from_jumpbox" {
type = "ingress"
from_port = 443
to_port = 443
protocol = "tcp"
source_security_group_id = "${aws_security_group.jumpbox.id}"
security_group_id = "${aws_security_group.app.id}"
}
@radeksimko
Environments
@radeksimko
Environments
https://www.terraform.io/docs/state/environments.html#best-practices
@radeksimko
Environments
https://www.terraform.io/docs/state/environments.html#best-practices
@radeksimko
Environments
● Comfortable w/ conditional values + new remote backends?
○ Single folder
● Local modules may help
○ but keep in mind they’re symlinked
@radeksimko
Thanks!
Questions?
@radeksimko

More Related Content

PPTX
Infrastructure-as-Code (IaC) Using Terraform (Advanced Edition)
PDF
Best Practices of Infrastructure as Code with Terraform
PDF
Terraform Best Practices - DevOps Unicorns 2019
PPTX
Terraform
PDF
Advanced Terraform
PDF
Terraform
PDF
Terraform
PPTX
Terraformで始めるInfrastructure as Code
Infrastructure-as-Code (IaC) Using Terraform (Advanced Edition)
Best Practices of Infrastructure as Code with Terraform
Terraform Best Practices - DevOps Unicorns 2019
Terraform
Advanced Terraform
Terraform
Terraform
Terraformで始めるInfrastructure as Code

What's hot (20)

PDF
Everything as Code with Terraform
PPTX
Final terraform
PDF
Building infrastructure as code using Terraform - DevOps Krakow
PDF
Red hat ansible automation technical deck
PPTX
Introduction To Terraform
PDF
DevOps with Ansible
PDF
A Hands-on Introduction on Terraform Best Concepts and Best Practices
PDF
Ansible
PDF
Ansible Automation Platform.pdf
PDF
Ansible Introduction
PPTX
Azure ad の導入を検討している方へ ~ active directory の構成パターンと正しい認証方式の選択~
PDF
[AWSマイスターシリーズ] AWS CloudFormation
PPTX
A brief introduction to IaC with Terraform by Kenton Robbins (codeHarbour May...
PPTX
PDF
Infrastructure as Code with Terraform
PDF
Terraform in deployment pipeline
PPTX
Effective terraform
PPTX
Terraform Modules and Continuous Deployment
PDF
nexus helm 설치, docker/helm repo 설정과 예제
PDF
Terraform: An Overview & Introduction
Everything as Code with Terraform
Final terraform
Building infrastructure as code using Terraform - DevOps Krakow
Red hat ansible automation technical deck
Introduction To Terraform
DevOps with Ansible
A Hands-on Introduction on Terraform Best Concepts and Best Practices
Ansible
Ansible Automation Platform.pdf
Ansible Introduction
Azure ad の導入を検討している方へ ~ active directory の構成パターンと正しい認証方式の選択~
[AWSマイスターシリーズ] AWS CloudFormation
A brief introduction to IaC with Terraform by Kenton Robbins (codeHarbour May...
Infrastructure as Code with Terraform
Terraform in deployment pipeline
Effective terraform
Terraform Modules and Continuous Deployment
nexus helm 설치, docker/helm repo 설정과 예제
Terraform: An Overview & Introduction
Ad

Similar to Terraform 0.9 + good practices (20)

PDF
Declarative & workflow based infrastructure with Terraform
PDF
PDF
Building infrastructure with Terraform (Google)
PDF
Infrastructure as Code with Terraform
PDF
Infrastructure as Code with Terraform
PDF
Workshop Infrastructure as Code - Suestra
PDF
Refactoring Infrastructure Code
PPTX
Iniciando com Terraform
PDF
Managing GCP Projects with Terraform (devfest Pisa 2018)
PPTX
"Continuously delivering infrastructure using Terraform and Packer" training ...
PDF
Terraform introduction
PDF
Infrastructure As Code With Terraform
PDF
Refactoring terraform
PPTX
terraform cours intéressant et super fort
PDF
Terraform - Taming Modern Clouds
PPTX
An intro to Docker, Terraform, and Amazon ECS
PDF
Infrastructure-as-code: bridging the gap between Devs and Ops
PPTX
IaC and Immutable Infrastructure with Terraform, Сергей Марченко
PDF
Terraform Introduction
PDF
Terraform at Scale - All Day DevOps 2017
Declarative & workflow based infrastructure with Terraform
Building infrastructure with Terraform (Google)
Infrastructure as Code with Terraform
Infrastructure as Code with Terraform
Workshop Infrastructure as Code - Suestra
Refactoring Infrastructure Code
Iniciando com Terraform
Managing GCP Projects with Terraform (devfest Pisa 2018)
"Continuously delivering infrastructure using Terraform and Packer" training ...
Terraform introduction
Infrastructure As Code With Terraform
Refactoring terraform
terraform cours intéressant et super fort
Terraform - Taming Modern Clouds
An intro to Docker, Terraform, and Amazon ECS
Infrastructure-as-code: bridging the gap between Devs and Ops
IaC and Immutable Infrastructure with Terraform, Сергей Марченко
Terraform Introduction
Terraform at Scale - All Day DevOps 2017
Ad

More from Radek Simko (7)

PDF
Terraforming the Kubernetes Land
PDF
Infrastructure as Code in Google Cloud
PDF
Mirror API introduction
PDF
ok glass, bring me a mojito...
PDF
ok glass, what you (really) can do? (for GDG Prague)
PDF
Chrome & Opera Extensions - GUG SPŠ Tábor
PDF
MongoDB - IF2012
Terraforming the Kubernetes Land
Infrastructure as Code in Google Cloud
Mirror API introduction
ok glass, bring me a mojito...
ok glass, what you (really) can do? (for GDG Prague)
Chrome & Opera Extensions - GUG SPŠ Tábor
MongoDB - IF2012

Recently uploaded (20)

PPTX
Odoo POS Development Services by CandidRoot Solutions
PDF
How to Choose the Right IT Partner for Your Business in Malaysia
PDF
Audit Checklist Design Aligning with ISO, IATF, and Industry Standards — Omne...
PDF
System and Network Administration Chapter 2
PDF
Upgrade and Innovation Strategies for SAP ERP Customers
PDF
Wondershare Filmora 15 Crack With Activation Key [2025
PDF
2025 Textile ERP Trends: SAP, Odoo & Oracle
PPTX
CHAPTER 2 - PM Management and IT Context
PDF
Nekopoi APK 2025 free lastest update
PPTX
ISO 45001 Occupational Health and Safety Management System
PPTX
history of c programming in notes for students .pptx
PDF
Claude Code: Everyone is a 10x Developer - A Comprehensive AI-Powered CLI Tool
PDF
AI in Product Development-omnex systems
PPT
Introduction Database Management System for Course Database
PDF
T3DD25 TYPO3 Content Blocks - Deep Dive by André Kraus
PPTX
Lecture 3: Operating Systems Introduction to Computer Hardware Systems
PDF
Odoo Companies in India – Driving Business Transformation.pdf
PPTX
ManageIQ - Sprint 268 Review - Slide Deck
PDF
How to Migrate SBCGlobal Email to Yahoo Easily
PPTX
Introduction to Artificial Intelligence
Odoo POS Development Services by CandidRoot Solutions
How to Choose the Right IT Partner for Your Business in Malaysia
Audit Checklist Design Aligning with ISO, IATF, and Industry Standards — Omne...
System and Network Administration Chapter 2
Upgrade and Innovation Strategies for SAP ERP Customers
Wondershare Filmora 15 Crack With Activation Key [2025
2025 Textile ERP Trends: SAP, Odoo & Oracle
CHAPTER 2 - PM Management and IT Context
Nekopoi APK 2025 free lastest update
ISO 45001 Occupational Health and Safety Management System
history of c programming in notes for students .pptx
Claude Code: Everyone is a 10x Developer - A Comprehensive AI-Powered CLI Tool
AI in Product Development-omnex systems
Introduction Database Management System for Course Database
T3DD25 TYPO3 Content Blocks - Deep Dive by André Kraus
Lecture 3: Operating Systems Introduction to Computer Hardware Systems
Odoo Companies in India – Driving Business Transformation.pdf
ManageIQ - Sprint 268 Review - Slide Deck
How to Migrate SBCGlobal Email to Yahoo Easily
Introduction to Artificial Intelligence

Terraform 0.9 + good practices

  • 7. Quick 101 + less known features
  • 8. File resource "google_compute_instance" "server" { name = "server" machine_type = "g1-small" zone = "us-central1-a" disk { image = "ubuntu-1404-trusty-v20160114e" } network_interface { network = "default" } } resource "dnsimple_record" "hello" { domain = "example.com" name = "server" value = "${google_compute_instance.server.network_interface.0.address}" type = "A" }
  • 9. Terminal $ terraform plan + google_compute_instance.server can_ip_forward: "false" create_timeout: "4" disk.#: "1" disk.0.auto_delete: "true" disk.0.disk_encryption_key_sha256: "<computed>" disk.0.image: "ubuntu-1404-trusty-v20160114e" machine_type: "g1-small" metadata_fingerprint: "<computed>" name: "server" network_interface.#: "1" network_interface.0.address: "<computed>" network_interface.0.name: "<computed>" self_link: "<computed>" tags_fingerprint: "<computed>" zone: "us-central1-a" ... Plan: 2 to add, 0 to change, 0 to destroy.
  • 10. Terminal $ terraform apply google_compute_instance.server: Creating... can_ip_forward: "" => "false" create_timeout: "" => "4" disk.#: "" => "1" disk.0.auto_delete: "" => "true" disk.0.disk_encryption_key_sha256: "" => "<computed>" disk.0.image: "" => "ubuntu-1404-trusty-v20160114e" machine_type: "" => "g1-small" metadata_fingerprint: "" => "<computed>" name: "" => "server" ... zone: "" => "us-central1-a" google_compute_instance.server: Still creating... (10s elapsed) google_compute_instance.server: Still creating... (20s elapsed) ... Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
  • 12. File resource "aws_s3_bucket" "old_bucket" { bucket = "my_best_bucket_ever" acl = "private" }
  • 13. File resource "aws_s3_bucket" "new_bucket" { bucket = "my_best_bucket_ever" acl = "private" }
  • 14. Terminal $ terraform state mv aws_s3_bucket.old_bucket aws_s3_bucket.new_bucket
  • 16. Terminal $ terraform graph | dot -Tpng > graph.png $ open graph.png
  • 20. File resource "aws_iam_access_key" "radek" { user = "${aws_iam_user.radek.name}" pgp_key = "keybase:radeksimko" } resource "aws_iam_user" "radek" { name = "radeksimko" path = "/users/" }
  • 21. File resource "aws_iam_user_login_profile" "radek" { user = "${aws_iam_user.radek.name}" pgp_key = "keybase:radeksimko" }
  • 22. Terminal $ echo "aws_iam_access_key.radek.encrypted_secret" | terraform console | base64 -D | gpg -d Fksg+R3JjH5nAfTqV1XuLBIPBDv/hj8sGQpeJnhA
  • 23. Terminal $ terraform apply ... AccessDenied: Access Denied status code: 403, request id: D1CB93351D412243, host id: EHSNCCkNwvEGx/9E0S2FBXmrq/QNxHKyuihgqd7JEYcjr/9QInV2sTSeA3 r3g5bkBlLX67BltfY=
  • 25. File ----------------------------------------------------- 2017/04/16 19:41:47 [DEBUG] [aws-sdk-go] DEBUG: Request s3/ListObjects Details: ---[ REQUEST POST-SIGN ]----------------------------- GET /?prefix=env%3A%2F HTTP/1.1 Host: tf-rsimko-test.s3.amazonaws.com User-Agent: aws-sdk-go/1.8.10 (go1.8; darwin; amd64) APN/1.0 HashiCorp/1.0 Terraform/0.9.4-dev Authorization: AWS4-HMAC-SHA256 Credential=... ----------------------------------------------------- 2017/04/16 19:41:48 [DEBUG] [aws-sdk-go] DEBUG: Response s3/ListObjects Details: ---[ RESPONSE ]-------------------------------------- HTTP/1.1 403 Forbidden Connection: close Transfer-Encoding: chunked Content-Type: application/xml Date: Sun, 16 Apr 2017 18:41:47 GMT Server: AmazonS3 ... <?xml version="1.0" encoding="UTF-8"?> <Error><Code>AccessDenied</Code><Message>Access Denied</Message><RequestId>D0CB93351D412243</RequestId><HostId>EHSNCCkNwvEGx/9E0S2FBXmrq/QNwHKyuihgqd7JEYcjr/9QInV2sT SeA3r3g5bkBlLX67BltfY=</HostId></Error> -----------------------------------------------------
  • 26. File ----------------------------------------------------- 2017/04/16 19:41:47 [DEBUG] [aws-sdk-go] DEBUG: Request s3/ListObjects Details: ---[ REQUEST POST-SIGN ]----------------------------- GET /?prefix=env%3A%2F HTTP/1.1 Host: tf-rsimko-test.s3.amazonaws.com User-Agent: aws-sdk-go/1.8.10 (go1.8; darwin; amd64) APN/1.0 HashiCorp/1.0 Terraform/0.9.4-dev Authorization: AWS4-HMAC-SHA256 Credential=... ----------------------------------------------------- 2017/04/16 19:41:48 [DEBUG] [aws-sdk-go] DEBUG: Response s3/ListObjects Details: ---[ RESPONSE ]-------------------------------------- HTTP/1.1 403 Forbidden Connection: close Transfer-Encoding: chunked Content-Type: application/xml Date: Sun, 16 Apr 2017 18:41:47 GMT Server: AmazonS3 ... <?xml version="1.0" encoding="UTF-8"?> <Error><Code>AccessDenied</Code><Message>Access Denied</Message><RequestId>D0CB93351D412243</RequestId><HostId>EHSNCCkNwvEGx/9E0S2FBXmrq/QNwHKyuihgqd7JEYcjr/9QInV2sT SeA3r3g5bkBlLX67BltfY=</HostId></Error> -----------------------------------------------------
  • 30. @radeksimko 0.8 (13th December) ● terraform console ● Version requirements
  • 32. Terminal $ terraform plan The currently running version of Terraform doesn't meet the version requirements explicitly specified by the configuration. Please use the required version or update the configuration. Note that version requirements are usually set for a reason, so we recommend verifying with whoever set the version requirements prior to making any manual changes. Module: root Required version: > 0.9.0 Current version: 0.8.0
  • 33. @radeksimko 0.8 (13th December) ● terraform console ● Version requirements ● Conditional values
  • 34. File resource "google_compute_instance" "server" { name = "server" machine_type = "${var.env == "dev" ? "g1-small" : "n1-standard-2"}" ... }
  • 35. File resource "google_compute_instance" "server" { count = "${var.env == "dev" ? 0 : 3}" ... }
  • 36. @radeksimko Conditional Values ● If-statements for single values within Terraform ● Enables on/off of resources (by using count) ● The beginning of more logic in Terraform configs
  • 37. @radeksimko 0.8 (13th December) ● terraform console ● Version requirements ● Conditional values ● Nomad and Vault providers
  • 38. File provider "nomad" { address = "nomad.mycompany.com" region = "us-east-2" } resource "nomad_job" "app" { jobspec = "${file("${path.module}/job.hcl")}" }
  • 39. File provider "vault" {} data "vault_generic_secret" "rundeck_auth" { path = "secret/rundeck_auth" } provider "rundeck" { url = "http://rundeck.example.com/" auth_token = "${data.vault_generic_secret.rundeck_auth.data["auth_token"]}" }
  • 41. @radeksimko POST / HTTP/1.1 Host: ec2.eu-west-2.amazonaws.com User-Agent: aws-sdk-go/1.8.13 Authorization: ... Content-Type: ... Action=RunInstances&... ImageId=ami-63342007&InstanceType= t2.micro... HTTP/1.1 200 OK Connection: close Content-Type: ... Server: AmazonEC2 <?xml version="1.0" encoding="UTF-8"?> <RunInstancesResponse xmlns="http://ec2.amazonaws.com/doc/2016-11-1 5/"> … <instanceId>i-02228e28962ee478b</instanceId>
  • 42. @radeksimko Plz change this tag from old to new, Ta! For which instance? Few days later...
  • 43. @radeksimko Existing resources ● Lookups via tags or names ○ Not everything AWS supports tags ○ Not every provider supports tags ○ Tags may not be unique ○ Unique names don’t allow graceful recreation ○ 1 extra lookup API call to get the real unique ID for modify/delete API calls ● Storing unique ID in a state (Terraform) ○ Guaranteed to be unique ○ Provider-agnostic solution
  • 44. @radeksimko Worst case scenario ● Duplicate/wrong tag/name ■ Changed wrong resource ■ Destroyed wrong resource ● Drifted/decoupled state ○ Duplicate infrastructure ○ Original (production?) resources intact
  • 46. @radeksimko 0.9 (15th March) ● Remote Backends ● State Locking ● Environments ● terraform init ● Data sources interpolate-able in count
  • 47. @radeksimko Remote State <= 0.8 ● Awkward "remote config" command ● Users could accidentally run Terraform without remote config ● Configuration only via CLI ● Local cache of state stored in .terraform/terraform.tfstate ● Changing remote configuration was manual
  • 48. Terminal $ # Terraform <= 0.8, BEFORE REMOTE BACKENDS $ terraform remote config -backend=S3 -backend-config="bucket=<bucket>" -backend-config="key=<path to file>" ...
  • 49. @radeksimko Remote Backends ● Subsumes "remote state", enables locking & environments ● Configure from tf files, or CLI ● Detects configuration change ● Forces new users of a TF configuration to initialize ● One command to init them all: terraform init
  • 50. File terraform { backend "s3" { bucket = "mybucket" key = "path/to/terraform.tfstate" region = "us-east-1" } }
  • 51. Terminal $ terraform plan Backend reinitialization required. Please run "terraform init". Reason: Initial configuration of the requested backend "s3" The "backend" is the interface that Terraform uses to store state, perform operations, etc. If this message is showing up, it means that the Terraform configuration you're using is using a custom configuration for the Terraform backend. Changes to backend configurations require reinitialization. This allows Terraform to setup the new configuration, copy existing state, etc. This is only done during "terraform init". Please run that command now then try again. If the change reason above is incorrect, please verify your configuration hasn't changed and try again. At this point, no changes to your existing configuration or state have been made. Failed to load backend: Initialization required. Please see the error message above.
  • 53. @radeksimko Remote Backends ● One command to init: terraform init ● Automatic detection of backend change (set, change, unset) ● No state stored locally at all ● Always gitignore .terraform folder
  • 54. @radeksimko terraform init ● Init has existed since Terraform 0.1 ● Used to just setup folder structure for new projects ● Now the single source of init, safe to run multiple times ● Initializes backend, downloads modules, creates folders ● One day: downloads providers, verifies versions, ...
  • 55. Terminal $ terraform apply Acquiring state lock. This may take a few moments… ... Apply complete! Resources: 1 added, 0 changed, 0 destroyed. ... Releasing state lock. This may take a few moments...
  • 56. Terminal $ terraform apply Error locking state: Error acquiring the state lock ... Lock Info: ID: 551a1f24-7fe1-75b1-ea09-2e5d9b4bf71b Path: gds-radek-tfstate-demo/terraform.tfstate Operation: OperationTypeApply Who: radeksimko@RadekMac Version: 0.9.4 Created: 2017-04-18 13:52:24.622630378 +0000 UTC Info:
  • 57. @radeksimko State Locking ● Implemented by remote backend ○ Consul ○ S3 ● Automatic locking of state on write operations ● If unlock fails, error is shown with lock ID ○ terraform force-unlock LOCK_ID [DIR] ● Doesn't lock against concurrent reads
  • 58. File terraform { backend "s3" { bucket = "mybucket" key = "path/to/my/key" region = "us-east-1" lock_table = "terraform-lock" } }
  • 59. Terminal $ terraform env new dev Created and switched to environment "dev"! You're now on a new, empty environment. Environments isolate their state, so if you run "terraform plan" Terraform will not see any existing state for this configuration.
  • 60. @radeksimko Environments ● State namespace scoped ● Implemented by backend ○ Consul ○ S3
  • 61. File data "aws_availability_zones" "available" {} # Create 1 subnet per availability zone resource "aws_subnet" "primary" { count = "${length(data.aws_availability_zones.available.names)}" availability_zone = "${data.aws_availability_zones.available.names[count.index]}" ... }
  • 63. @radeksimko Good Practices ● Use Remote State ○ Do NOT keep your state locally ○ Do NOT commit your state to CVS ● S3 backend ○ Enable bucket versioning ○ Limit access to the bucket (IAM + bucket policies) ○ Consider KMS ● Repo structures based on a few principles … ○ No silver bullet solution
  • 64. @radeksimko 1. Team’s responsibilities (trusted) ● Network team ○ VPN, VPC, Routing ● InfoSec team ○ KMS, IAM, SGs ● Ops team(s) ○ VPC, subnets, jumpbox, NAT, DNS ● Dev team(s) ○ LBs, ASGs, EC2, internal DNS (Consul), ...
  • 65. @radeksimko 1. Team’s responsibilities ● ... ● Ops team(s) ○ VPC, subnets, jumpbox, NAT, DNS, ASG, LBs, EC2 ● Dev team(s) ○ Internal DNS (Consul), K8S (Pods, RCs, SVCs), Docker, Nomad
  • 66. @radeksimko 1. Team’s responsibilities (small org) ● Ops team ○ VPN, VPC, Routing, KMS, IAM, SGs, VPC, subnets, jumpbox, NAT, DNS ● Dev team(s) ○ LBs, EC2
  • 67. @radeksimko 2. Blast Radius ● Limit impact via separate tfstate/directory ○ Stateful services ○ High impact (shared) resources (VPN, VPC, subnets)
  • 68. @radeksimko 3. Resiliency ● Why do you span across regions? ● What if a region goes down? ● Store tfstate close to the real infrastructure
  • 69. @radeksimko 4. Lifecycle ● How often are you going to change things? ○ Expected maintenance for pet-like services ● e.g. ○ Github Enterprise ○ Databases (SQL/NoSQL, MQs)
  • 70. @radeksimko Helpful primitives ● modules ○ Local ○ Remote (git, Github, Mercurial, HTTP, S3, …) ● variables ● outputs ● remote state data source
  • 71. @radeksimko Module or separate state? ● Module ○ Logical group of resources (Lambda-based app, K8S app) ○ Set of recommendations / best practices users may follow ○ But don’t try to catch every use-case ■ out-of-module resources sometimes work just fine ● Separate state ○ Separate lifecycle ○ Real isolation based on mentioned principles
  • 72. File module "consul" { source = "github.com/hashicorp/consul/terraform/aws" servers = 3 } output "consul_address" { value = "${module.consul.server_address}" }
  • 73. File # github.com/hashicorp/consul -> terraform/aws/*.tf variable "servers" { servers = 3 } … output "server_address" { value = "${aws_instance.server.0.public_dns}" }
  • 74. File data "terraform_remote_state" "vpc" { backend = "atlas" config { name = "hashicorp/vpc-prod" } } resource "aws_instance" "foo" { # ... subnet_id = "${data.terraform_remote_state.vpc.subnet_id}" }
  • 75. File resource "aws_security_group" "app" { name = "${var.env}_app" description = "My Awesome Application" ingress { from_port = 443 to_port = 443 protocol = "tcp" security_groups = ["${aws_security_group.jumpbox.id}"] } }
  • 76. File # Preferred way resource "aws_security_group" "app" { name = "${var.env}_app" description = "My Awesome Application" } resource "aws_security_group_rule" "app_from_jumpbox" { type = "ingress" from_port = 443 to_port = 443 protocol = "tcp" source_security_group_id = "${aws_security_group.jumpbox.id}" security_group_id = "${aws_security_group.app.id}" }
  • 80. @radeksimko Environments ● Comfortable w/ conditional values + new remote backends? ○ Single folder ● Local modules may help ○ but keep in mind they’re symlinked