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.