Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Meet Terraform

Michael Heap
September 30, 2017

Meet Terraform

We have a ton of options for provisioning machines once we have an SSH connection available, but how do we get those machines in the first place?

Meet Terraform from Hashicorp, your infrastructure automation engine. Providing a declarative interface for your infrastructure, you can define what you need and let Terraform take care of the rest.

In this talk, we'll take a look at what Terraform can do for you, walking through your first Terraform configuration all the way to writing your own modules to encapsulate your infrastructure across multiple deployments.

Michael Heap

September 30, 2017
Tweet

More Decks by Michael Heap

Other Decks in Technology

Transcript

  1. INFRASTRUCTURE AS CODE resource "aws_instance" "web" { ami = "ami-70728c08"

    instance_type = "t2.micro" tags { Name = "HelloWorld" } }
  2. Alicloud Archive Arukas AWS Bitbucket CenturyLinkCloud Chef Circonus Cloudflare CloudStack

    Cobbler Consul Datadog DigitalOcean DNS DNSMadeEasy DNSimple Docker Dyn External Fastly GitHub Gitlab Google Cloud Grafana Heroku HTTP Icinga2 Ignition InfluxDB Kubernetes Librato Local Logentries Mailgun New Relic Nomad NS1 Microsoft Azure Microsoft Azure (Legacy ASM) MySQL 1&1 Oracle Public Cloud OpenStack OpsGenie OVH Packet PagerDuty PostgreSQL PowerDNS ProfitBricks RabbitMQ Rancher Random Rundeck Scaleway SoftLayer StatusCake Spotinst Template Terraform Terraform Enterprise TLS Triton UltraDNS Vault VMware vCloud Director VMware vSphere
  3. $ 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 env Workspace management 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 Terraform working directory 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 Atlas 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
  4. $ 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 env Workspace management 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 Terraform working directory 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 Atlas 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
  5. $ 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 env Workspace management 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 Terraform working directory 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 Atlas 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
  6. CREATE AN INSTANCE (MAIN.TF) resource "aws_instance" "web" { ami =

    "ami-70728c08" instance_type = "t2.micro" tags { Name = "HelloWorld" } }
  7. CREATE AN INSTANCE resource "aws_instance" "web" { ami = "ami-70728c08"

    instance_type = "t2.micro" tags { Name = "HelloWorld" } }
  8. CREATE AN INSTANCE resource "aws_instance" "web" { ami = "ami-70728c08"

    instance_type = "t2.micro" tags { Name = "HelloWorld" } }
  9. CREATE AN INSTANCE resource "aws_instance" "web" { ami = "ami-70728c08"

    instance_type = "t2.micro" tags { Name = "HelloWorld" } }
  10. CREATE AN INSTANCE resource "aws_instance" "web" { ami = "ami-70728c08"

    instance_type = "t2.micro" tags { Name = "HelloWorld" } }
  11. CREATE AN INSTANCE resource "aws_instance" "web" { ami = "ami-70728c08"

    instance_type = "t2.micro" tags { Name = "HelloWorld" } }
  12. TERRAFORM PLAN $ terraform plan Plugin reinitialization required. Please run

    "terraform init". Reason: Could not satisfy plugin requirements. Plugins are external binaries that Terraform uses to access and manipulate resources. The configuration provided requires plugins which can't be located, don't satisfy the version constraints, or are otherwise incompatible. 1 error(s) occurred: * provider.aws: no suitable version installed version requirements: "(any version)" versions installed: none Terraform automatically discovers provider requirements from your configuration, including providers used in child modules. To see the requirements and constraints from each module, run "terraform providers".
  13. TERRAFORM INIT $ terraform init Initializing provider plugins... - Checking

    for available provider plugins on https://releases.hashicorp.com... - Downloading plugin for provider "aws" (1.0.0)... The following providers do not have any version constraints in configuration, so the latest version was installed. To prevent automatic upgrades to new major versions that may contain breaking changes, it is recommended to add version = "..." constraints to the corresponding provider blocks in configuration, with the constraint strings suggested below. * provider.aws: version = "~> 1.0" Terraform has been successfully initialized! You may now begin working with Terraform. Try running "terraform plan" to see any changes that are required for your infrastructure. All Terraform commands should now work.
  14. 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_instance.web id: <computed> ami: "ami-70728c08" associate_public_ip_address: <computed> source_dest_check: "true" subnet_id: <computed> tags.%: "1" tags.Name: "HelloWorld" Plan: 1 to add, 0 to change, 0 to destroy.
  15. TERRAFORM APPLY $ terraform apply aws_instance.web: Creating... ami: "" =>

    "ami-70728c08" instance_type: "" => "t2.micro" source_dest_check: "" => "true" subnet_id: "" => "<computed>" tags.%: "" => "1" tags.Name: "" => "HelloWorld" tenancy: "" => "<computed>" volume_tags.%: "" => "<computed>" vpc_security_group_ids.#: "" => "<computed>" aws_instance.web: Still creating... (10s elapsed) aws_instance.web: Still creating... (20s elapsed) aws_instance.web: Creation complete after 22s (ID: i-0c0b4a732311b8bce) Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
  16. TERRAFORM PLAN $ terraform plan Refreshing Terraform state in-memory prior

    to plan... The refreshed state will be used to calculate this plan, but will not be persisted to local or remote state storage. aws_instance.web: Refreshing state... (ID: i-0c0b4a732311b8bce) ------------------------------------------------------------------------ No changes. Infrastructure is up-to-date. This means that Terraform did not detect any differences between your configuration and real physical resources that exist. As a result, no actions need to be performed.
  17. TERRAFORM.TFSTATE { "version": 3, "terraform_version": "0.10.4", "serial": 2, "lineage": "348d2909-5e43-4421-bd65-4f7ab6b11632",

    "modules": [ { "path": [ "root" ], "outputs": {}, "resources": { "aws_instance.web": { "type": "aws_instance", "depends_on": [], "primary": { "id": "i-0fc83946a097d8e27", "attributes": { "ami": "ami-70728c08", "associate_public_ip_address": "true", "availability_zone": "us-west-2a", "disable_api_termination": "false", "ebs_block_device.#": "0", "ebs_optimized": "false", "ephemeral_block_device.#": "0", "iam_instance_profile": "", "id": "i-0fc83946a097d8e27", "instance_state": "running", "instance_type": "t2.small", "ipv6_addresses.#": "0", "key_name": "", "monitoring": "false", "network_interface.#": "0", "network_interface_id": "eni-de1229f0", "primary_network_interface_id": "eni-de1229f0", "private_dns": "ip-172-31-21-4.us-west-2.compute.internal", "private_ip": "172.31.21.4", "public_dns": "ec2-34-215-64-201.us-west-2.compute.amazonaws.com", "public_ip": "34.215.64.201", "root_block_device.#": "1", "root_block_device.0.delete_on_termination": "true", "root_block_device.0.iops": "100", "root_block_device.0.volume_size": "8", "root_block_device.0.volume_type": "gp2", "security_groups.#": "1", "security_groups.3814588639": "default", "source_dest_check": "true", "subnet_id": "subnet-3e5ef758", "tags.%": "1", "tags.Name": "HelloWorld", "tenancy": "default", "volume_tags.%": "0", "vpc_security_group_ids.#": "0" }, "meta": { "e2bfb730-ecaa-11e6-8f88-34363bc7c4c0": { "create": 600000000000, "delete": 600000000000, "update": 600000000000 }, "schema_version": "1" }, "tainted": false }, "deposed": [], "provider": "" } }, "depends_on": [] } ] } "aws_instance.web": { "type": "aws_instance", "depends_on": [], "primary": { "id": "i-0fc83946a097d8e27", "attributes": { "ami": "ami-70728c08", "associate_public_ip_address": "true", "availability_zone": "us-west-2a", "disable_api_termination": "false", "ebs_block_device.#": "0", "ebs_optimized": "false", "ephemeral_block_device.#": "0", "iam_instance_profile": "", "id": "i-0fc83946a097d8e27",
  18. SCALING UP resource "aws_instance" "web" { count = "3" ami

    = "ami-70728c08" instance_type = "t2.micro" tags { Name = "HelloWorld" } }
  19. SCALING UP resource "aws_instance" "web" { count = "3" ami

    = "ami-70728c08" instance_type = "t2.micro" tags { Name = "HelloWorld" } }
  20. 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_instance.web[1] <snip> ami: "ami-70728c08" tags.Name: "HelloWorld" + aws_instance.web[2] <snip> ami: "ami-70728c08" tags.Name: “HelloWorld" Plan: 2 to add, 0 to change, 0 to destroy.
  21. ADDING A LOAD BALANCER resource "aws_elb" "lb" { name =

    "example-lb" availability_zones = ["us-west-2a"] instances = ["${aws_instance.web.*.id}"] listener { instance_port = "8000" instance_protocol = "http" lb_port = "80" lb_protocol = "http" } }
  22. ADDING A LOAD BALANCER resource "aws_elb" "lb" { name =

    "example-lb" availability_zones = ["us-west-2a"] instances = ["${aws_instance.web.*.id}"] listener { instance_port = "8000" instance_protocol = "http" lb_port = "80" lb_protocol = "http" } }
  23. OUTPUTS.TF $ terraform refresh aws_instance.web[0]: Refreshing state... (ID: i-0c0b4a732311b8bce) aws_instance.web[1]:

    Refreshing state... (ID: i-0b8bde17992d4dc0c) aws_instance.web[2]: Refreshing state... (ID: i-06e74e8dc04eb1843) aws_elb.lb: Refreshing state... (ID: example-elb) Outputs: lb_dns = example-elb-1640688516.us-west-2.elb.amazonaws.com
  24. OUTPUTS.TF $ terraform output -json { "lb_dns": { "sensitive": false,

    "type": "string", "value": "example-elb-1640688516.us-west-2.elb.amazonaws.com" } }
  25. TERRAFORM DESTROY $ terraform destroy An execution plan has been

    generated and is shown below. Resource actions are indicated with the following symbols: - destroy Terraform will perform the following actions: - aws_elb.lb - aws_instance.web[0] - aws_instance.web[1] - aws_instance.web[2] Plan: 0 to add, 0 to change, 4 to destroy. Do you really want to destroy? Terraform will destroy all your managed infrastructure, as shown above. There is no undo. Only 'yes' will be accepted to confirm. Enter a value:
  26. SCALING UP resource "aws_instance" "web" { count = "3" ami

    = "ami-70728c08" instance_type = "t2.micro" tags { Name = "HelloWorld" } }
  27. SCALING UP resource "aws_instance" "web" { count = "3" ami

    = "ami-70728c08" instance_type = "t2.micro" tags { Name = "web" } }
  28. SCALING UP resource "aws_instance" "web" { count = "3" ami

    = "ami-70728c08" instance_type = "t2.micro" tags { Name = "web-${count.index}" } }
  29. SCALING UP resource "aws_instance" "web" { count = "3" ami

    = "ami-70728c08" instance_type = "t2.micro" tags { Name = "web-${count.index}" Environment = "PirateNinjas" } }
  30. SCALING UP resource "aws_elb" "lb" { name = "example-lb" …snip…

    listener { …snip… } tags { Environment = "PirateNinjas" } }
  31. SCALING UP resource "aws_instance" "web" { count = "3" ami

    = "ami-70728c08" instance_type = "t2.micro" tags { Name = "web-${count.index}" Environment = "${var.environment}" } }
  32. SCALING UP resource "aws_elb" "lb" { name = "lb-${var.environment}" …snip…

    listener { …snip… } tags { Environment = "${var.environment}" } }
  33. CREATING A MODULE $ tree . !"" main.tf !"" output.tf

    #"" variables.tf $ tree . #"" demo !"" main.tf !"" output.tf #"" variables.tf
  34. USING A MODULE (LOCAL) module "pirate" { source = "./demo"

    environment = "Pirate" } module "ninja" { source = "./demo" environment = "Ninja" }
  35. USING A MODULE (REGISTRY) module "pirate" { source = "mheap/aws/full-env"

    environment = "Pirate" } module "ninja" { source = "mheap/aws/full-env" environment = "Ninja" } https://registry.terraform.io/
  36. USING A MODULE (GIT) module "pirate" { source = "git::https://hashicorp.com/consul.git?

    ref=1.0.3" environment = "Pirate" } module "ninja" { source = “git::https://hashicorp.com/consul.git? ref=1.8.14" environment = "Ninja" }
  37. BAD . !"" demo $ !"" main.tf $ !"" output.tf

    $ #"" variables.tf !"" main.tf !"" outputs.tf #"" terraform.tfstate
  38. GOOD . !"" environments $ !"" ninja $ $ !""

    main.tf $ $ !"" output.tf $ $ #"" terraform.tfstate $ #"" pirate $ !"" main.tf $ !"" output.tf $ #"" terraform.tfstate #"" modules #"" demo !"" main.tf !"" output.tf #"" variables.tf
  39. RUNNING ANSIBLE resource "aws_instance" "web" { count = "3" ami

    = "ami-70728c08" instance_type = "t2.micro" tags { …snip… } provisioner "local-exec" { command = "ansible-playbook -u ubuntu --private-key ./aws-key.pem -i '$ {self.public_ip},' playbook.yml" } }
  40. RUNNING ANSIBLE resource "aws_instance" "web" { …snip… provisioner "remote-exec" {

    connection { type = "ssh" user = "root" password = "${var.root_password}" } inline = [ "sudo apt-get install nginx my-app", "sudo systemctl restart nginx" ] } }
  41. REMOTE STATE (S3) terraform { backend "s3" { bucket =

    "mybucket" key = "path/to/my/key" region = “us-west-2" } }
  42. REMOTE STATE (CONSUL) terraform { backend "consul" { address =

    "demo.consul.io" path = "example_app/ terraform_state" } }
  43. MIX/MATCH PROVIDERS E.G. HEROKU + DNSIMPLE provider "heroku" { email

    = "[email protected]" api_key = "${var.heroku_api_key}" } provider "dnsimple" { token = "${var.dnsimple_token}" account = "${var.dnsimple_account}" } resource "heroku_app" "default" { … } resource "dnsimple_record" "foobar" { domain = "${var.dnsimple_domain}" name = "" value = “${heroku_app.default.heroku_hostname}" type = "CNAME" ttl = 3600 }
  44. READ ONLY DATA PROVIDERS E.G. AWS_AMI FOR SEARCHING AMIS data

    "aws_ami" "ubuntu" { most_recent = true filter { name = "name" values = ["ubuntu/images/hvm-ssd/ubuntu-xenial-16.04-amd64-server-*"] } filter { name = "virtualization-type" values = ["hvm"] } owners = ["099720109477"] # Canonical }
  45. TESTING AWSSPEC / INSPEC require "spec_helper" before do @client =

    Aws::EC2::Client.new @ec2 = Aws::EC2::Resource.new(client: @client) end describe "Instances" do subject { @ec2.instances.count } it { is_expected.to eq(3) } end