Terraform, is no doubt very flexible and powerful. The question is, how do we write Terraform code and construct our infrastructure in a reproducible fashion that makes sense? How can we keep code DRY, segment state, and reduce the risk of making changes to our service/stack/infrastructure?
HashiCorp’s infrastructure management tool, Terraform, is no doubt very flexible and powerful. The question is, how do we write Terraform code and construct our infrastructure in a reproducible fashion that makes sense? How can we keep code DRY, segment state, and reduce the risk of making changes to our service/stack/infrastructure?
This talk describes a design pattern to help answer the previous questions. The talk is divided into two sections, with the first section describing and defining the design pattern with a Deployment Example. The second part uses a multi-repository GitHub organization to create a Real World Example of the design pattern.
2. ◉ Introduction
◉ Terraform at Scale?
◉ Gotta Keep ‘em Separated
◉ Reducing Complexity With Style
◉ A Tale of Two Modules
Introduction: Agenda and Takeaways
3. 80%
Percentage of Outages Caused by Changes*
* Source: Behr, K., Kim, G., & Spafford, G. (2013). The Visible Ops Handbook
38. terraform apply (console output cont.)
...
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
The state of your infrastructure has been saved to the path
below. This state is required to modify and destroy your
infrastructure, so keep it safe. To inspect the complete state
use the `terraform show` command.
State path:
Outputs:
api_fqdn = dev-api.devops-demo.xyz
domain_name = devops-demo.xyz
zone_id = Z3QZEABS9HJLIN
39.
40. terraform apply (console output cont.)
...
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
The state of your infrastructure has been saved to the path
below. This state is required to modify and destroy your
infrastructure, so keep it safe. To inspect the complete state
use the `terraform show` command.
State path:
Outputs:
api_fqdn = dev-api.devops-demo.xyz
domain_name = devops-demo.xyz
zone_id = Z3QZEABS9HJLIN
41. outputs.tf
output "api_fqdn" {
value = "${aws_route53_record.api_route53_record.fqdn}"
}
output "domain_name" {
value = "${var.domain_name}"
}
output "zone_id" {
value = "${aws_route53_zone.route53_zone.id}"
}
Output Variables
42. outputs.tf
output "api_fqdn" {
value = "${aws_route53_record.api_route53_record.fqdn}"
}
output "domain_name" {
value = "${var.domain_name}"
}
output "zone_id" {
value = "${aws_route53_zone.route53_zone.id}"
}
Output Variables
43. outputs.tf
output "api_fqdn" {
value = "${aws_route53_record.api_route53_record.fqdn}"
}
output "domain_name" {
value = "${var.domain_name}"
}
output "zone_id" {
value = "${aws_route53_zone.route53_zone.id}"
}
Output Variables
47. Naming Conventions: Resource Names
◉ RESOURCE NAME = RESOURCE TYPE - PROVIDER NAME
resource "aws_security_group" "security_group" {
name = "${var.resource_name}-security-group"
...
48. Naming Conventions: Resource Names (cont.)
resource "aws_s3_bucket" "data_s3_bucket" {
bucket = "${var.env}-data-${var.aws_region}"
}
resource "aws_s3_bucket" "images_s3_bucket" {
bucket = "${var.env}-images-${var.aws_region}"
}
◉ Multiple resources of the same TYPE should have a minimalistic identifier to
differentiate between the two resources.
49. When to Use an Underscore
resource "aws_route53_record" "api_route53_record" {
zone_id = "${aws_route53_zone.route53_zone.zone_id}"
name = "${var.environment}-api.${var.domain_name}"
type = "A"
ttl = "300"
records = ["${var.ip_address}"]
}
◉ Variable Names
◉ Resource Names
◉ Anything Interpolated
50. When to Use a Hyphen
resource "aws_route53_record" "api_route53_record" {
zone_id = "${aws_route53_zone.route53_zone.zone_id}"
name = "${var.environment}-api.${var.domain_name}"
type = "A"
ttl = "300"
records = ["${var.ip_address}"]
}
◉ Resources Being Created
55. Service Module
● Reusable library
● Creates all required resources a service needs to be operational i.e. EC2
instances, S3 bucket, DNS Entries
A Tale of Two Modules
Infrastructure Module
● Single repository comprised of multiple root modules
● This is where Service Modules are instantiated/Terraform is run.
56. Why Infrastructure Modules?
◉ Conditional Statements
○ Don’t exist Are painful (1604)
◉ Segment State
○ Reduce risk of changes
○ Flexible state reference
◉ DRY Terraform Configurations
○ Instantiate reusable modules
◉ Environments Aren’t Recommended (0.9)
◉ Workspaces are a thing (renamed statefile)
61. Account Root Module
Infrastructure Repository Folder Structure
AWS (provider)
|__ production-account (aws-account root module) <~~~~~ YOU ARE HERE
|__ us-east-1 (aws-region root module)
|__ production-us-east-1-vpc (vpc root module)
|__ production (environment root module)
|__ inf-bastion (service root module)
62. terraform.sh (bash wrapper)
Usage: ./templates/account-terraform.sh [apply|destroy|plan|refresh|show]
The following arguments are supported:
apply Refresh the Terraform remote state, "terraform get -update", and "terraform apply"
destroy Refresh the Terraform remote state and destroy the Terraform stack
plan Refresh the Terraform remote state, "terraform get -update", and "terraform plan"
refresh Refresh the Terraform remote state
show Refresh and show the Terraform remote state
89. To Begin Again... From The Beginning
Quote: Waking Life. Dir. Richard Linklater. Fox Searchlight Pictures, 2001. FIlm.
Image: Fight Club. Dir. David Fincher. 20th Century Fox, 1999. Film.
109. ◉ Introduction
◉ Terraform at Scale?
◉ Gotta Keep ‘em Separated
◉ Reducing Complexity With Style
◉ A Tale of Two Modules
Closing: Agenda and Takeaways