Cloudformation vs. Terraform
Dmytro
DevOps engineer
https://github.com/dimahavrylevych
???
Will we talk about


Why can`t I do it with Chef/Puppet/Ansible?
Procedural
vs
Declarative
Ansible
Terraform
- ec2:
count: 10
image: ami-v1
instance_type: t2.micro
resource "aws_instance" "example" {
count = 10
ami = "ami-v1"
instance_type = "t2.micro"
}
10
10
Ansible
Terraform
- ec2:
count: 15
image: ami-v1
instance_type: t2.micro
resource "aws_instance" "example" {
count = 15
ami = "ami-v1"
instance_type = "t2.micro"
}
?
?
25
15
CF and TF under the hood
Developed by HashiCorp

TF is free
There is an enterprise version also
You can write on HashiCorp Configuration Language (HCL) or JSON
Feature 1
Plan and execution
provider "aws" {
region = "us-east-1"
}
resource "aws_security_group" "allow_all" {
name = "allow_all"
description = "Allow all inbound traffic"
ingress {
from_port = 0
to_port = 65535
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
tags {
Name = "allow_all"
}
}
$ terraform plan
------------------------------------------------------------------------
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
+ aws_security_group.allow_all
id: <computed>
description: "Allow all inbound traffic"
egress.#: <computed>
ingress.#: "1"
ingress.1403647648.cidr_blocks.#: "1"
ingress.1403647648.cidr_blocks.0: "0.0.0.0/0"
ingress.1403647648.description: ""
ingress.1403647648.from_port: "0"
ingress.1403647648.ipv6_cidr_blocks.#: "0"
ingress.1403647648.protocol: "tcp"
ingress.1403647648.security_groups.#: "0"
ingress.1403647648.self: "false"
ingress.1403647648.to_port: "65535"
name: "allow_all"
owner_id: <computed>
tags.%: "1"
tags.Name: "allow_all"
vpc_id: <computed>
Plan: 1 to add, 0 to change, 0 to destroy.
Feature 2
*.tfstate file
{
"version": 3,
"terraform_version": "0.10.7",
"serial": 1,
"lineage": "889c8c2d-f157-4f78-ab91-c3730bc89e63",
"modules": [
{
"path": [
"root"
],
"outputs": {},
"resources": {
"aws_security_group.allow_all": {
"type": "aws_security_group",
"depends_on": [],
"primary": {
"id": "sg-h59hta4",
"attributes": {
"description": "Allow all inbound traffic",
"egress.#": "0",
"id": "sg-f8afea8a",
"ingress.#": "1",
"ingress.1403647648.cidr_blocks.#": "1",
"ingress.1403647648.cidr_blocks.0": "0.0.0.0/0",
"ingress.1403647648.description": "",
"ingress.1403647648.from_port": "0",
"ingress.1403647648.ipv6_cidr_blocks.#": "0",
"ingress.1403647648.protocol": "tcp",
"ingress.1403647648.security_groups.#": "0",
"ingress.1403647648.self": "false",
"ingress.1403647648.to_port": "65535",
"name": "allow_all",
"owner_id": "0123456789",
"tags.%": "1",
"tags.Name": "allow_all",
"vpc_id": "vpc-blabla1234"
},
"meta": {},
"tainted": false
},
"deposed": [],
"provider": ""
}
},
"depends_on": []
}
]
}
Feature 3
Storing state on remote and locking
lock = {
backend = "dynamodb"
config {
state_file_id = "application"
}
}
remote_state = {
backend = "s3"
config {
encrypt = "true"
bucket = "backend-bucket"
key = "terraform.tfstate"
region = "us-east-1"
}
}
Feature 4
Different provides support
















Feature 5
$ terraform graph

Feature 6
Modules
Building reusable rockets is hard. Building reusable Terraform code is not.
Image by SpaceX.
(c) blog.gruntwork.io
variable "min_size" {
description = "The minimum number of EC2 Instances in the ASG"
}
variable "max_size" {
description = "The maximum number of EC2 Instances in the ASG"
}
resource "aws_autoscaling_group" "example" {
launch_configuration = "${aws_launch_configuration.example.id}"
availability_zones = ["${data.aws_availability_zones.all.names}"]
min_size = "${var.min_size}"
max_size = "${var.max_size}"
(...)
}
resource "aws_launch_configuration" "example" {
image_id = "ami-2d39803a"
instance_type = "t2.micro"
(...)
}
resource "aws_elb" "example" {
name = "terraform-asg-example"
security_groups = ["${aws_security_group.elb.id}"]
availability_zones = ["${data.aws_availability_zones.all.names}"]
(...)
}
module "frontend" {
source = "/modules/frontend-app"
min_size = 1
max_size = 2
}
/modules/frontend/main.tf
/main.tf
CLI power
$ terraform
Usage: terraform [--version] [--help] <command> [args]
The available commands for execution are listed below.
The most common, useful commands are shown first, followed by
less common or more advanced commands. If you're just getting
started with Terraform, stick with the common commands. For the
other commands, please read the help and docs before usage.
Common commands:
apply Builds or changes infrastructure
console Interactive console for Terraform interpolations
destroy Destroy Terraform-managed infrastructure
fmt Rewrites config files to canonical format
get Download and install modules for the configuration
graph Create a visual graph of Terraform resources
import Import existing infrastructure into Terraform
init Initialize a new or existing Terraform configuration
output Read an output from a state file
plan Generate and show an execution plan
providers Prints a tree of the providers used in the configuration
push Upload this Terraform module to Terraform Enterprise to run
refresh Update local state file against real resources
show Inspect Terraform state or plan
taint Manually mark a resource for recreation
untaint Manually unmark a resource as tainted
validate Validates the Terraform files
version Prints the Terraform version
workspace Workspace management
All other commands:
debug Debug output management (experimental)
force-unlock Manually unlock the terraform state
state Advanced state management
$ terraform
Common commands:
init
plan
apply
graph
show
destroy
taint
state
import
output
AWS Clouformation
CF is also free
Supports all AWS resources
You can write
on JSON
or YAML
You can even write with Python/Ruby DSL
from troposphere import Output, Ref, Template
from troposphere.s3 import Bucket, PublicRead
t = Template()
s3bucket = t.add_resource(Bucket("S3Bucket", AccessControl=PublicRead))
print(t.to_json())
Feature 1
Centilized points of control
Region Name | Endpoint |
---|---|
US East (Ohio) | cloudformation.us-east-2.amazonaws.com |
US East (N. Virginia) | cloudformation.us-east-1.amazonaws.com |
US West (N. California) | cloudformation.us-west-1.amazonaws.com |
US West (Oregon) | cloudformation.us-west-2.amazonaws.com |
Asia Pacific (Mumbai) | cloudformation.ap-south-1.amazonaws.com |
Asia Pacific (Seoul) | cloudformation.ap-northeast-2.amazonaws.com |
Asia Pacific (Singapore) | cloudformation.ap-southeast-1.amazonaws.com |
Asia Pacific (Sydney) | cloudformation.ap-southeast-2.amazonaws.com |
Asia Pacific (Tokyo) | cloudformation.ap-northeast-1.amazonaws.com |
Canada (Central) | cloudformation.ca-central-1.amazonaws.com |
EU (Frankfurt) | cloudformation.eu-central-1.amazonaws.com |
EU (Ireland) | cloudformation.eu-west-1.amazonaws.com |
EU (London) | cloudformation.eu-west-2.amazonaws.com |
South America (São Paulo) | cloudformation.sa-east-1.amazonaws.com |
Feature 2
CLI/API/SDK/UI
$ aws cloudformation update-stack \
--stack-name my-perfect-stack-v404 \
--use-previous-template \
--parameters file://./params.json
https://cloudformation.us-east-1.amazonaws.com/
?Action=UpdateStack
&StackName=my-perfect-stack-v404
&TemplateBody=[Template Document]
&Parameters.member.1.ParameterKey=AvailabilityZone
&Parameters.member.1.ParameterValue=us-east-1a
&Version=2010-05-15
&SignatureVersion=2
&Timestamp=2010-07-27T22%3A26%3A28.000Z
&AWSAccessKeyId=verysecretkey
&Signature=signature
CreateStackRequest createStackRequest = new CreateStackRequest()
.withStackName(“my-perfect-stack-v404")
.withTemplateBody(templateAsString)
.withParameters(
new Parameter().withParameterKey("InstanceCount").withParameterValue(“3"),
new Parameter().withParameterKey("InstanceType").withParameterValue(“m3.large"));
client.updateStack(createStackRequest);
Feature 3
Designer

Feature 4
Change sets

Feature 5
Nested stacks and cross-stack reference

Outputs:
TSSG:
Value: !Ref TroubleShootingSG
Export:
Name: AccountSG
EC2Instance:
Type: AWS::EC2::Instance
Properties:
SecurityGroups:
- !ImportValue AccountSG
Feature 6
Roles specialization

Feature 8
AWS-Specific Parameter Types
AWS::EC2::AvailabilityZone::Name
AWS::EC2::Image::Id
AWS::EC2::Instance::Id
AWS::EC2::KeyPair::KeyName
AWS::EC2::SecurityGroup::GroupName
AWS::EC2::SecurityGroup::Id
AWS::EC2::Subnet::Id
AWS::EC2::Volume::Id
AWS::EC2::VPC::Id
AWS::Route53::HostedZone::Id
List<AWS::EC2::AvailabilityZone::Name>
List<AWS::EC2::Image::Id>
List<AWS::EC2::Instance::Id>
List<AWS::EC2::SecurityGroup::GroupName>
List<AWS::EC2::SecurityGroup::Id>
List<AWS::EC2::Subnet::Id>
List<AWS::EC2::Volume::Id>
List<AWS::EC2::VPC::Id>
List<AWS::Route53::HostedZone::Id>

Side-by-side
GCE
Azure
Open-stack
Supports AWS Services only
Other clouds support


Maturity


Now on version 0.10.7
Updates 2-4 times/month
Has 912 open items
Huge community
AWS centric
Updates 5-6 times/month
Has 2110 open items
Closed source
Existing resources


The current implementation of Terraform import can only import resources into the state. It does not generate configuration
Doesn`t have build-in functionality.
But there is a CloudFormer
Structering


Modules
Plugins
Multi-layer stack with references
Updates


Preview changes with $ terraform plan
Execute changes with
$ terraform apply
Could be updated via ChangeSet or direcrly.
Supports changes preview
Formats


Hashicorp Config Language (HCL)
JSON
YAML
JSON
Python /Ruby DSLs
Questions?
Cloudformation vs Terraform
By Dima Havrylevych
Cloudformation vs Terraform
- 1,040