Cloudnatives Irvine
Feb 2020
● Cloud Engineer, Kong
● Previously Zenedge, DreamHost, Blizzard
● Nginx/OpenResty, Terraform contributor
● Various shenanigans, holliganery, nogoodknickery
3
resource “aws_instance” “foo” {
count = 1
instance_type = “m5.large”
tags = [
“foo”
]
}
resource “aws_instance” “foo” {
count = 3
instance_type = “m5.large”
tags = [
“foo”
]
}
6
module "servers" {
source = "./app-cluster"
servers = 5
}
● Abstraction
● Parameterization
● Re-use
● Versioning
● Decoupling
8
9
● aws_acm_certificate
● aws_route53_record
● aws_acm_certificate_validation
● aws_kms_key
● aws_launch_configuration
● aws_autoscaling_group
● aws_lb
● aws_instance
● aws_iam_role_policy_attachment
● aws_kms_alias
● aws_kms_grant
● aws_subnet_ids
● aws_caller_identity
● aws_vpc
● aws_route53_zone
● aws_lb_listener
● aws_lb_target_group
● aws_route53_record
● aws_security_group_rule
● aws_vpc_endpoint_service
● aws_iam_policy
● aws_s3_bucket
● aws_iam_role
● aws_iam_service_linked_role
● aws_vpc_endpoint_service_allowed_
principal
● aws_route
● aws_db_subnet_group
● aws_elasticache_subnet_group
10
module "worker-pool" {
source = "worker"
servers = 3
volume_size = 20
instance_type = “m5.large”
}
module "batch-pool" {
source = "worker"
servers = 2
volume_size = 500
instance_type = “c5.xlarge”
}
12
13
● Testing internal implementation
● New feature sets
● Parameter/call design deprecation
● Provider lifecycle support
14
module "consul" {
source = "hashicorp/consul/aws"
version = "0.0.5"
servers = 3
}
17
● Terraform Registry
● Git (GitHub)
● Mercurial
module "vpc" {
source = "git@github.com:corp/tfmodule-vpc.git ?ref=master"
}
19
module "vpc" {
source = "git@github.com:corp/tfmodule-vpc.git?ref=${var.vpc-version}"
}
21
22
type module struct {
Source string `yaml:"source"`
Version string `yaml:"version"`
}
cmd := exec.Command("git", "clone",
"--single-branch", "--depth=1", "-b",
version, repository, moduleName)
$ cat kong/worker/env/dev/us-west-2.yml
module:
source: "git@github.com:Kong/tf-module.git"
version: "v0.23"
24
$ cat kong/worker/env/dev/us-west-2.yml
module:
source: "git@github.com:Kong/tf-module.git"
version: "v0.23"
$ cat kong/worker/env/dev/us-west-2.yml
module:
source: "git@github.com:Kong/tf-module.git"
version: "v0.23"
$ cat kong/worker/env/ dev/us-west-2.yml
module:
source: "git@github.com:Kong/tf-module.git"
version: "v0.23"
$ cat kong/worker/env/dev/ us-west-2.yml
module:
source: "git@github.com:Kong/tf-module.git"
version: "v0.23"
$ deploy.sh [module] [vpc] [region] [environment]
30
resource “aws_lb” “foo” {
...
}
output “lb_dns_name” {
value = aws_lb.foo.dns_name
}
module "foo" {
source = "../foo"
}
module “bar” {
source = “../bar”
lb = module.foo.lb_dns_name
}
Module Caller
● Terraform manages lifecycle/dependency
● State file size/sync time
● Circular dependencies
● Terraform lifecycle limitations
32
data “aws_lb” “foo” {
name = “foo”
}
resource “foo” “bar” {
value = data.aws_lb.foo.dns_name
}
● Smaller state file
● Shorter sync time
● Provider-based definition of infrastructure
● Problem: missing data at runtime...
34
35
36
37
module:
source: "git@github.com:Kong/tf-module.git"
version: "v0.23"
depends:
- vpc
- controller
● Each module has zero or more dependents
● Each module has zero or more dependencies
● Modules with no unresolved dependencies can execute
simultaneously
● … this is starting to sound a little familiar
38
● Collect all module dependencies
● Sort via tsort
● Apply modules in order of response
40
41
$ cat worker.yml
module:
source: "git@github.com:Kong/tf-module.git"
version: "v0.23"
depends:
- vpc
- controller
$ cat grafana.yml
module:
source: "git@github.com:Kong/tf-module.git"
version: "v0.23"
depends:
- worker
- prometheus
$ ./deps.sh
vpc
controller
worker
vault
es
bastion
prometheus
jenkins
httpbin
public-lb
logstash
grafana
$ deploy.sh [module] [vpc] [region] [environment]
$ for module in $(./deps.sh); do
deploy.sh [module] [vpc] [region] [environment]
done
● Small modules
● Version pinning (what do mean, no more go on green?)
● Indirect (data) dependencies
● Module vs caller - twice the commits!
45
46
47
48
module “job” {
source = “./kong/module/nomad-job”
resources = {
memory = 100
cpu = 100
}
image = “nginx”
count = 3
template {
...
}
ports {
...
}
}
50
module "job" {
source = "../nomad-job"
name = "grafana"
public = true
jobspec = templatefile(
format("%s/grafana.nomad.tmpl", path.module),
{
tag = "latest",
}
)
}
resource "aws_route53_record" "main" {
count = var.public == true ? 1 : 0
zone_id = data.aws_route53_zone.zone.zone_id
name = var.name
type = "A"
alias {
name = data.aws_lb.worker.dns_name
zone_id = data.aws_lb.worker.zone_id
evaluate_target_health = false
}
}
resource "aws_route53_record" "main" {
count = var.public == true ? 1 : 0
zone_id = data.aws_route53_zone.zone.zone_id
name = var.name
type = "A"
alias {
name = data.aws_lb.worker.dns_name
zone_id = data.aws_lb.worker.zone_id
evaluate_target_health = false
}
}
● Re-using existing deployment pipelines
● Very strong integration of Hashicorp tools
● Nomad-powered lifecycle management
○ Canary / blue-green
○ Native Consul integration
○ Native Vault integration
54
55

Managing Terraform Module Versioning and Dependencies