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