Terraform and Chef together | How to provision…

With Hashicorp´s terraform it becomes really easy to provision virtual machines, templates on different providers (AWS, azure, Google, Kubernetes, etc.). When you now deploy your machines you need to configure, patch and manage them afterwards. This is were Chef has strengths. So teaming up terraform with Chef as so called provisioner makes perfect sense. In this post I just want to show how easy this is.

In my example I will deploy a Windows 2012 R2 machine on AWS and install putty, create some folders and check for registry keys on the newly created server. First you need to write a plan (example.tf):

# Set the target provider
provider "aws" {
  region = "eu-west-1"
}

# Set an admin password for the configuration
variable "admin_password" {
  default     = "ChefDemo,1"
}

# Create an new Security Group for WinRM and RDP
resource "aws_security_group" "default" {
  name        = "terraform-windows-server"
  description = "Terraform Windows Security Group"

  # WinRM access from anywhere
  ingress {
    from_port   = 5985
    to_port     = 5985
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 3389
    to_port     = 3389
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  # outbound internet access
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

# Set the right Windows Server 2012R2 AMI
data "aws_ami" "amazon_windows_2012R2" {
  most_recent = true
  owners      = ["amazon"]
  filter {
    name   = "name"
    values = ["Windows_Server-2012-R2_RTM-English-64Bit-Base-*"]
  }
}

# Create a new EC2 instance with Chef as provisioner
resource "aws_instance" "windows" {
  provisioner "chef" {
  server_url      = "https://api.chef.io/organizations/pb_cjo"
  user_name       = "cjohannsen"
  user_key        = "${file("~/Downloads/chef-repo-2/.chef/cjohannsen.pem")}"
  node_name       = "windows-server01"
  run_list        = [ "puttyWindows" ]
  version         = "12.4.1"
  }
  connection {
    type     = "winrm"
    user     = "Administrator"
    password = "ChefDemo,1"
  }
  instance_type = "t2.micro"
  ami           = "${data.aws_ami.amazon_windows_2012R2.image_id}"
  key_name = "cjohannsen"
  security_groups = ["${aws_security_group.default.name}"]
  user_data = <<EOF
    <script>
      winrm quickconfig -q & winrm set winrm/config/winrs @{MaxMemoryPerShellMB="300"} & winrm set winrm/config @{MaxTimeoutms="1800000"} & winrm set winrm/config/service @{AllowUnencrypted="true"} & winrm set winrm/config/service/auth @{Basic="true"}
    </script>
    <powershell>
      netsh advfirewall firewall add rule name="WinRM in" protocol=TCP dir=in profile=any localport=5985 remoteip=any localip=any action=allow
      $admin = [adsi]("WinNT://./administrator, user")
      $admin.psbase.invoke("SetPassword", "${var.admin_password}")
    </powershell>
  EOF
}

# Show the public IP address at the end
output "address" {
  value = "${aws_instance.windows.public_ip}"
}

Pro-Tip: Please use the “file” option in the “user_key” variable as it will fail if you just set the direct path. You can also add recreate_client = true in the provisioner section if you play around and create the same machine more than once.

As you can see I already set the initial run_list and Chef-Server in this plan, so if everything works perfect it should create a Windows 2012R2 machine, install and configure the Chef-Client to use the public available Chef-Server and finally run my cookbook. You can kick off the creation using “terraform apply”:

 

~/terrachef   ᐅ /Applications/terraform/terraform apply  
data.aws_ami.amazon_windows_2012R2: Refreshing state...
aws_security_group.default: Creating...
  description:                           "" => "Terraform Windows Security Group"
  egress.#:                              "" => "1"
  egress.482069346.cidr_blocks.#:        "" => "1"
  egress.482069346.cidr_blocks.0:        "" => "0.0.0.0/0"
  egress.482069346.from_port:            "" => "0"
  egress.482069346.ipv6_cidr_blocks.#:   "" => "0"
  egress.482069346.prefix_list_ids.#:    "" => "0"
  egress.482069346.protocol:             "" => "-1"
  egress.482069346.security_groups.#:    "" => "0"
  egress.482069346.self:                 "" => "false"
  egress.482069346.to_port:              "" => "0"
  ingress.#:                             "" => "2"
  ingress.1138479083.cidr_blocks.#:      "" => "1"
  ingress.1138479083.cidr_blocks.0:      "" => "0.0.0.0/0"
  ingress.1138479083.from_port:          "" => "5985"
  ingress.1138479083.ipv6_cidr_blocks.#: "" => "0"
  ingress.1138479083.protocol:           "" => "tcp"
  ingress.1138479083.security_groups.#:  "" => "0"
  ingress.1138479083.self:               "" => "false"
  ingress.1138479083.to_port:            "" => "5985"
  ingress.2223400681.cidr_blocks.#:      "" => "1"
  ingress.2223400681.cidr_blocks.0:      "" => "0.0.0.0/0"
  ingress.2223400681.from_port:          "" => "3389"
  ingress.2223400681.ipv6_cidr_blocks.#: "" => "0"
  ingress.2223400681.protocol:           "" => "tcp"
  ingress.2223400681.security_groups.#:  "" => "0"
  ingress.2223400681.self:               "" => "false"
  ingress.2223400681.to_port:            "" => "3389"
  name:                                  "" => "terraform-windows-server"
  owner_id:                              "" => "<computed>"
  vpc_id:                                "" => "<computed>"
aws_security_group.default: Creation complete (ID: sg-f565f08c)
aws_instance.windows: Creating...
  ami:                         "" => "ami-d3dee9b5"
  associate_public_ip_address: "" => "<computed>"
  availability_zone:           "" => "<computed>"
  ebs_block_device.#:          "" => "<computed>"
  ephemeral_block_device.#:    "" => "<computed>"
  instance_state:              "" => "<computed>"
  instance_type:               "" => "t2.micro"
  ipv6_addresses.#:            "" => "<computed>"
  key_name:                    "" => "cjohannsen"
  network_interface_id:        "" => "<computed>"
  placement_group:             "" => "<computed>"
  private_dns:                 "" => "<computed>"
  private_ip:                  "" => "<computed>"
  public_dns:                  "" => "<computed>"
  public_ip:                   "" => "<computed>"
  root_block_device.#:         "" => "<computed>"
  security_groups.#:           "" => "1"
  security_groups.1434979247:  "" => "terraform-windows-server"
  source_dest_check:           "" => "true"
  subnet_id:                   "" => "<computed>"
  tenancy:                     "" => "<computed>"
  user_data:                   "" => "cb11cd2726f855ef8e9e335c64997eefea34fac3"
  vpc_security_group_ids.#:    "" => "<computed>"
aws_instance.windows: Still creating... (10s elapsed)
aws_instance.windows: Still creating... (20s elapsed)
aws_instance.windows: Still creating... (30s elapsed)
aws_instance.windows: Still creating... (40s elapsed)
aws_instance.windows: Still creating... (50s elapsed)
aws_instance.windows: Still creating... (1m0s elapsed)
aws_instance.windows: Provisioning with 'chef'...
aws_instance.windows (chef): Connecting to remote host via WinRM...
aws_instance.windows (chef):   Host: 54.194.148.101
aws_instance.windows (chef):   Port: 5985
aws_instance.windows (chef):   User: Administrator
aws_instance.windows (chef):   Password: true
aws_instance.windows (chef):   HTTPS: false
aws_instance.windows (chef):   Insecure: false
aws_instance.windows (chef):   CACert: false
aws_instance.windows: Still creating... (1m10s elapsed)
aws_instance.windows: Still creating... (1m20s elapsed)
aws_instance.windows: Still creating... (1m30s elapsed)
aws_instance.windows: Still creating... (1m40s elapsed)
aws_instance.windows: Still creating... (1m50s elapsed)
aws_instance.windows: Still creating... (2m0s elapsed)
aws_instance.windows: Still creating... (2m10s elapsed)
aws_instance.windows: Still creating... (2m20s elapsed)
aws_instance.windows (chef): Connecting to remote host via WinRM...
aws_instance.windows (chef):   Host: 54.194.148.101
aws_instance.windows (chef):   Port: 5985
aws_instance.windows (chef):   User: Administrator
aws_instance.windows (chef):   Password: true
aws_instance.windows (chef):   HTTPS: false
aws_instance.windows (chef):   Insecure: false
aws_instance.windows (chef):   CACert: false
aws_instance.windows: Still creating... (2m30s elapsed)
aws_instance.windows (chef): Connected!
aws_instance.windows (chef): Downloading Chef Client...
aws_instance.windows: Still creating... (2m40s elapsed)
aws_instance.windows (chef): Installing Chef Client...
aws_instance.windows: Still creating... (2m50s elapsed)
aws_instance.windows: Still creating... (3m0s elapsed)
aws_instance.windows: Still creating... (3m10s elapsed)
aws_instance.windows: Still creating... (3m20s elapsed)
aws_instance.windows: Still creating... (3m30s elapsed)
aws_instance.windows: Still creating... (3m40s elapsed)
aws_instance.windows: Still creating... (3m50s elapsed)
aws_instance.windows (chef): Creating configuration files...
aws_instance.windows: Still creating... (4m0s elapsed)
aws_instance.windows (chef): Generate the private key...
aws_instance.windows: Still creating... (4m10s elapsed)
aws_instance.windows: Still creating... (4m20s elapsed)
aws_instance.windows (chef): Created client[windows-server01]
aws_instance.windows (chef): Cleanup user key...
aws_instance.windows (chef): Starting initial Chef-Client run...
aws_instance.windows (chef): [2017-04-21T07:10:38+00:00] INFO: *** Chef 12.4.1 ***
aws_instance.windows (chef): [2017-04-21T07:10:38+00:00] INFO: Chef-client pid: 2232
aws_instance.windows: Still creating... (4m30s elapsed)
aws_instance.windows: Still creating... (4m40s elapsed)
aws_instance.windows: Still creating... (4m50s elapsed)
aws_instance.windows: Still creating... (5m0s elapsed)
aws_instance.windows (chef): [2017-04-21T07:11:16+00:00] INFO: HTTP Request Returned 404 Object Not Found: error
aws_instance.windows (chef): [2017-04-21T07:11:16+00:00] INFO: Setting the run_list to ["puttyWindows"] from CLI options
aws_instance.windows (chef): [2017-04-21T07:11:16+00:00] INFO: Run List is [recipe[puttyWindows]]
aws_instance.windows (chef): [2017-04-21T07:11:16+00:00] INFO: Run List expands to [puttyWindows]
aws_instance.windows (chef): [2017-04-21T07:11:16+00:00] INFO: Starting Chef Run for windows-server01
aws_instance.windows (chef): [2017-04-21T07:11:16+00:00] INFO: Running start handlers
aws_instance.windows (chef): [2017-04-21T07:11:16+00:00] INFO: Start handlers complete.
aws_instance.windows (chef): [2017-04-21T07:11:17+00:00] INFO: Loading cookbooks [puttyWindows@0.2.2]
aws_instance.windows (chef): [2017-04-21T07:11:18+00:00] INFO: Storing updated cookbooks/puttyWindows/Berksfile.lock in the cache.
aws_instance.windows (chef): [2017-04-21T07:11:18+00:00] INFO: Storing updated cookbooks/puttyWindows/Berksfile in the cache.
aws_instance.windows (chef): [2017-04-21T07:11:18+00:00] INFO: Storing updated cookbooks/puttyWindows/chefignore in the cache.
aws_instance.windows (chef): [2017-04-21T07:11:18+00:00] INFO: Storing updated cookbooks/puttyWindows/metadata.rb in the cache.
aws_instance.windows (chef): [2017-04-21T07:11:18+00:00] INFO: Storing updated cookbooks/puttyWindows/README.md in the cache.
aws_instance.windows (chef): [2017-04-21T07:11:18+00:00] INFO: Storing updated cookbooks/puttyWindows/.kitchen.yml in the cache.
aws_instance.windows (chef): [2017-04-21T07:11:18+00:00] INFO: Storing updated cookbooks/puttyWindows/attributes/default.rb in the cache.
aws_instance.windows (chef): [2017-04-21T07:11:18+00:00] INFO: Storing updated cookbooks/puttyWindows/recipes/default.rb in the cache.
aws_instance.windows (chef): [2017-04-21T07:11:18+00:00] INFO: Processing windows_package[Putty] action install (puttyWindows::default line 9)
aws_instance.windows (chef): [2017-04-21T07:11:18+00:00] INFO: Processing remote_file[C:\chef\cache\package\putty-64bit-0.68-installer.msi] action create (dynamically defined)
aws_instance.windows: Still creating... (5m10s elapsed)
aws_instance.windows (chef): [2017-04-21T07:11:19+00:00] INFO: remote_file[C:\chef\cache\package\putty-64bit-0.68-installer.msi] created file C:\chef\cache\package\putty-64bit-0.68-installer.msi
aws_instance.windows (chef): [2017-04-21T07:11:19+00:00] INFO: remote_file[C:\chef\cache\package\putty-64bit-0.68-installer.msi] updated file contents C:\chef\cache\package\putty-64bit-0.68-installer.msi
aws_instance.windows (chef): [2017-04-21T07:11:20+00:00] INFO: windows_package[Putty] installed Putty at 0.68.0.0
aws_instance.windows (chef): [2017-04-21T07:11:20+00:00] INFO: Processing directory[C://Users//Public//Desktop//Windows] action create (puttyWindows::default line 16)
aws_instance.windows (chef): [2017-04-21T07:11:20+00:00] INFO: directory[C://Users//Public//Desktop//Windows] created directory C://Users//Public//Desktop//Windows
aws_instance.windows (chef): [2017-04-21T07:11:20+00:00] INFO: Processing directory[C://Users//Public//Desktop//Linux] action create (puttyWindows::default line 16)
aws_instance.windows (chef): [2017-04-21T07:11:20+00:00] INFO: directory[C://Users//Public//Desktop//Linux] created directory C://Users//Public//Desktop//Linux
aws_instance.windows (chef): [2017-04-21T07:11:20+00:00] INFO: Processing directory[C://Users//Public//Desktop//Citrix] action create (puttyWindows::default line 16)
aws_instance.windows (chef): [2017-04-21T07:11:20+00:00] INFO: directory[C://Users//Public//Desktop//Citrix] created directory C://Users//Public//Desktop//Citrix
aws_instance.windows (chef): [2017-04-21T07:11:20+00:00] INFO: Processing registry_key[HKEY_LOCAL_MACHINE\SOFTWARE\Example] action create (puttyWindows::default line 21)
aws_instance.windows (chef): [2017-04-21T07:11:20+00:00] INFO: Processing ruby_block[check 32-bit] action run (puttyWindows::default line 27)
aws_instance.windows (chef): Found 32-bit key
aws_instance.windows (chef): [2017-04-21T07:11:20+00:00] INFO: ruby_block[check 32-bit] called
aws_instance.windows (chef): [2017-04-21T07:11:20+00:00] INFO: Processing ruby_block[check 64-bit] action run (puttyWindows::default line 36)
aws_instance.windows (chef): [2017-04-21T07:11:21+00:00] INFO: Chef Run complete in 4.999795 seconds
aws_instance.windows (chef): [2017-04-21T07:11:21+00:00] INFO: Running report handlers
aws_instance.windows (chef): [2017-04-21T07:11:21+00:00] INFO: Report handlers complete
aws_instance.windows (chef): [2017-04-21T07:11:21+00:00] INFO: Sending resource update report (run-id: 13056e88-86b6-4fa3-bee2-e012654faff1)
aws_instance.windows: Creation complete (ID: i-00e94be12e4201687)

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:

address = 54.194.148.101
~/terrachef   ᐅ

Pro-Tip: You can see the Chef-Client run in the logs, if anything fails you will see it. That means you can use terraform as staging or testing environment as well.

Because we defined an output in the plan you can see the public IP address of the newly created machine as a result. When we now connect to the system using RDP there should be a putty available and several folders on the desktop:

This example shows how easy it is to use a terraform definition to kick of a full deployment and configuration with just one CLI command. Please note that you can easily change your target cloud infrastructure to vSphere or azure now by changing this terraform file and it´s provider.

Leave a Reply