diff --git a/terraform_fundmentals/09-provisioners.md b/terraform_fundmentals/09-provisioners.md index 48fd5b9..3af15c9 100644 --- a/terraform_fundmentals/09-provisioners.md +++ b/terraform_fundmentals/09-provisioners.md @@ -1,6 +1,6 @@ # Lab: Using Terraform Provisioners -Duration: ?? minutes +Duration: 20 minutes In this challenge, you will create a Azure Virtual Machine but this time layer in Terraform Provisioners to configure the machines as part the Terraform apply. @@ -11,16 +11,11 @@ In this challenge, you will create a Azure Virtual Machine but this time layer i ## Background -Terraform [provisioners](https://www.terraform.io/docs/provisioners/index.html) help you do additional setup and configuration when a resource is created or destroyed. -You can move files, run shell scripts, and install software. +Terraform [provisioners](https://www.terraform.io/docs/provisioners/index.html) help you do additional setup and configuration when a resource is created or destroyed. You can move files, run shell scripts, and install software. -Provisioners are not intended to maintain desired state and configuration for existing resources. -For that purpose, you should use one of the many tools for configuration management, such as [Chef](https://www.chef.io/chef/), [Ansible](https://www.ansible.com/), and PowerShell [Desired State Configuration](https://docs.microsoft.com/en-us/powershell/dsc/overview/overview). -Terraform includes [provisioners](https://www.terraform.io/docs/language/resources/provisioners/index.html) for Chef, Habitat, Puppet, and Salt. +Provisioners are not intended to maintain desired state and configuration for existing resources. For that purpose, you should use one of the many tools for configuration management, such as [Chef](https://www.chef.io/chef/), [Ansible](https://www.ansible.com/), and PowerShell [Desired State Configuration](https://docs.microsoft.com/en-us/powershell/dsc/overview/overview). -An imaged-based infrastructure, such as images created with [Packer](https://www.packer.io), can eliminate much of the need to configure resources when they are created. -In this common scenario, Terraform is used to provision infrastructure based on a custom image. -The image is managed as code. +An imaged-based infrastructure, such as images created with [Packer](https://www.packer.io), can eliminate much of the need to configure resources when they are created. In this common scenario, Terraform is used to provision infrastructure based on a custom image. The image is managed as code. ## How To @@ -69,21 +64,19 @@ resource "azurerm_virtual_machine" "vm" { ``` As this example shows, you can define more than one provisioner in a resource block. -The [file](https://www.terraform.io/docs/provisioners/file.html) and [remote-exec](https://www.terraform.io/docs/provisioners/remote-exec.html) providers are used to perform two simple setup tasks: -- File copies a python file from the machine that is running Terraform to the new VM instance. -- Remote-exec runs commands to start a python flask app. +The [file](https://www.terraform.io/docs/provisioners/file.html) and [remote-exec](https://www.terraform.io/docs/provisioners/remote-exec.html) provisioners are used to perform two simple setup tasks: + +- File copies a python file from the machine that is running Terraform to the new VM instance. +- Remote-exec runs commands to start a python flask app. Both providers need a [connection](https://www.terraform.io/docs/provisioners/connection.html) to the new virtual machine to do their jobs. -To simplify things, the example uses password authentication. -In practice, you are more likely to use SSH keys (or for WinRM connections, use certificates) to authenticate. + +To simplify things, the example uses password authentication. In practice, you are more likely to use SSH keys (or for WinRM connections, use certificates) to authenticate. ### Running provisioners -Provisioners run when a resource is created, or a resource is destroyed. -Provisioners do not run during update operations. -The example configuration for this section defines two provisioners that run only when a new virtual machine instance is created. -If the virtual machine instance is later modified or destroyed, the provisioners will not run. +Provisioners run when a resource is created, or a resource is destroyed. Provisioners do not run during update operations. The example configuration for this section defines two provisioners that run only when a new virtual machine instance is created. If the virtual machine instance is later modified or destroyed, the provisioners will not run. For this lab, the full configuration is: @@ -174,7 +167,7 @@ resource "azurerm_network_interface" "nic" { ip_configuration { name = "${var.prefix}NICConfg" subnet_id = azurerm_subnet.subnet.id - private_ip_address_allocation = "dynamic" + private_ip_address_allocation = "Dynamic" public_ip_address_id = azurerm_public_ip.publicip.id } } @@ -250,39 +243,48 @@ output "app-URL" { To run the example configuration with provisioners: -1. Copy the configuration to a file named `main.tf`. It should be the only `.tf` file in the folder. -2. Create a `terraform.tfvars` with the following items, replacing **`###`** with your initials - ```hcl - prefix = "###" - location = "East US" - admin_username = "testadmin" - admin_password = "Password1234!" - ``` -3. Create a file named `hello.py`. - 1. In the editor, add the following text: - ```py - from flask import Flask - import requests - - app = Flask(__name__) - - import requests - @app.route('/') - def hello_world(): - return """ - - - Kittens - - - User Image - - """ - ``` - 1. Save the file and close the editor. -4. Run `terraform init` -5. Run `terraform plan` -6. Run `terraform apply`. When prompted to continue, answer `yes`. +1. Create a directory to hold the configuration called `provisioners`, and the files for the example: + +```bash +mkdir provisioners && cd provisioners + +touch main.tf +touch terraform.tfvars +touch hello.py +``` + +2. Copy the configuration to a file named `main.tf`. It should be the only `.tf` file in the folder. + +3. Populate the `terraform.tfvars` with the following items, replacing **`###`** with your initials + +```hcl +prefix = "###" +location = "East US" +admin_username = "testadmin" +admin_password = "Password1234!" +``` + +4. Populate the file named `hello.py`. + +```py +from flask import Flask +app = Flask(__name__) +@app.route('/') +def hello_world(): + return """ + + + Kittens + + + User Image + +""" +``` + +5. Run `terraform init` +6. Run `terraform plan` +7. Run `terraform apply`. When prompted to continue, answer `yes`. The following sample output has been truncated to show only the end of the output added by the provisioners (your actual output may differ slightly): @@ -310,25 +312,21 @@ Continue the procedure from above by doing the following: ### Failed provisioners and tainted resources -Provisioners sometimes fail to run properly. -By the time the provisioner is run, the resource has already been physically created. -If the provisioner fails, the resource will be left in an unknown state. -When this happens, Terraform will generate an error and mark the resource as "tainted." -A resource that is tainted isn't considered safe to use. +Provisioners sometimes fail to run properly. By the time the provisioner is run, the resource has already been physically created. If the provisioner fails, the resource will be left in an unknown state. + +When this happens, Terraform will generate an error and mark the resource as "tainted." A resource that is tainted isn't considered safe to use. -When you generate your next execution plan, Terraform will not attempt to restart provisioning on the tainted resource because it isn't guaranteed to be safe. -Instead, Terraform will remove any tainted resources and create new resources, attempting to provision them again after creation. +When you generate your next execution plan, Terraform will not attempt to restart provisioning on the tainted resource because it isn't guaranteed to be safe. Instead, Terraform will remove any tainted resources and create new resources, attempting to provision them again after creation. -You might wonder why Terraform doesn't destroy the tainted resource during apply, to avoid leaving a resource in an unknown state. -Terraform doesn't roll back tainted resources because that action was not in the execution plan. -The execution plan says that a resource will be created, but not that it might be deleted. -If you create an execution plan with a tainted resource, however, the plan will clearly state that the resource will be destroyed because it is tainted. +You might wonder why Terraform doesn't destroy the tainted resource during apply, to avoid leaving a resource in an unknown state. Terraform doesn't roll back tainted resources because that action was not in the execution plan. -For our tainting lab, we will use our `vm` from the above configuration. -We will list our resources, taint the appropriate `vm` resource, and apply. +The execution plan says that a resource will be created, but not that it might be deleted. If you create an execution plan with a tainted resource, however, the plan will clearly state that the resource will be destroyed because it is tainted. + +For our recreation lab, we will use our `vm` from the above configuration. +We will list our resources, and then replace the appropriate `vm` resource with a plan and apply. List the resources in our state: -```shell +```bash terraform state list ``` @@ -341,35 +339,24 @@ azurerm_virtual_network.vnet ``` Taint our VM: -```shell -terraform taint azurerm_virtual_machine.vm +```bash +terraform plan -replace=azurerm_virtual_machine.vm -out vmreplace.tfplan ``` ```text -Resource instance azurerm_virtual_machine.vm has been marked as tainted. +Plan: 1 to add, 0 to change, 1 to destroy. ``` Run an `apply` and confirm: -```shell -terraform apply -``` - -```text -Plan: 1 to add, 0 to change, 1 to destroy. -... -Outputs: - -app-URL = http://ccqpublicipprovision.eastus.cloudapp.azure.com:8000 +```bash +terraform apply vmreplace.tfplan ``` -Go to the URL provided in the output. -Which image do you see now? +Go to the URL provided in the output. Which image do you see now? ### Destroy Provisioners -Provisioners can also be defined that run only during a destroy operation. -These are known as [destroy-time provisioners](https://www.terraform.io/docs/language/resources/provisioners/syntax.html#destroy-time-provisioners). -Destroy provisioners are useful for performing system cleanup, extracting data, etc. +Provisioners can also be defined that run only during a destroy operation. These are known as [destroy-time provisioners](https://www.terraform.io/docs/language/resources/provisioners/syntax.html#destroy-time-provisioners).Destroy provisioners are useful for performing system cleanup, extracting data, etc. The following code snippet shows how a destroy provisioner is defined: diff --git a/terraform_fundmentals/10-graph.md b/terraform_fundmentals/10-graph.md index 1b04b11..1e859be 100644 --- a/terraform_fundmentals/10-graph.md +++ b/terraform_fundmentals/10-graph.md @@ -10,7 +10,14 @@ present this data in DOT format, which is used by GraphVis and similar programs ## Task 1 -### Step 7.1.1 +Go into an existing configuration directory and redeploy the configuration if you destroyed it earlier. + +```bash +cd /root/workstation/terraform/azure +terraform apply -auto-approve +``` + +### Use the Graph Command Run `terraform graph` in your terraform directory and note the output. @@ -28,6 +35,6 @@ digraph { } ``` -### Step 7.1.2 +### View the Graph -Paste that output into [webgraphviz](http://www.webgraphviz.com) to get a visual representation of dependencies that Terraform creates for your configuration. +Paste the output into [webgraphviz](http://www.webgraphviz.com) to get a visual representation of dependencies that Terraform creates for your configuration. diff --git a/terraform_fundmentals/11-meta-arguments.md b/terraform_fundmentals/11-meta-arguments.md index d3b666d..e3b545d 100644 --- a/terraform_fundmentals/11-meta-arguments.md +++ b/terraform_fundmentals/11-meta-arguments.md @@ -1,16 +1,16 @@ -# Lab 8: Meta-Arguments +# Lab: Meta-Arguments Duration: 10 minutes -So far, we've already used arguments to configure your resources. These arguments are used by the provider to specify things like the image to use, and the type of instance to provision. Terraform also supports a number of _Meta-Arguments_, which changes the way Terraform configures the resources. For instance, it's not uncommon to provision multiple copies of the same resource. We can do that with the _count_ argument. +So far, we've already used arguments to configure your resources. These arguments are used by the provider to specify things like the image to use, and the type of instance to provision. Terraform also supports a number of _Meta-Arguments_, which change the way Terraform configures the resources. For instance, it's not uncommon to provision multiple copies of the same resource. We can do that with the _count_ argument. -- Task 1: Change the number of Virtul Machines with `count` +- Task 1: Change the number of Virtual Machines with `count` - Task 2: Modify the rest of the configuration to support multiple instances -- Task 3: Add variable interpolation to the count arguement +- Task 3: Add variable interpolation to the count argument ## Task 1: Change the number of Azure Virtual Machines with `count` -### Step 8.1.1 +We are going to reuse the configuration in the `azure` directory from the exercises `02-basic-configuration` and `03-virtual_machine`. If you don't have those configurations, they are in the appendix of this lab. Add a count argument to the Azure Virtual Machine resource in `main.tf` with a value of 2. Also adjust the value of `name` to incrementally add a number to the end of each instances name: @@ -22,18 +22,15 @@ resource "azurerm_virtual_machine" "training" { location = azurerm_resource_group.training.location resource_group_name = azurerm_resource_group.training.name network_interface_ids = [azurerm_network_interface.training[count.index].id] - vm_size = "Standard_F2" -# ... leave the rest of the resource block unchanged... -} - -The name of the storage disk also needs to be updated to reflect the use of count: + vm_size = "Standard_D2s_v4" +# ... storage_os_disk { name = "${var.prefix}disk-${count.index + 1}" # ... leave the rest of the resource block unchanged... ``` -as well as the public_ip, network interface +We also need to update the `azurerm_public_ip` and `azurerm_network_interface` resources to support the additional virtual machines. ```hcl resource "azurerm_public_ip" "training" { @@ -93,19 +90,21 @@ You should see two dns addresses in the outputs, one for each virtual machine. Plan: 2 to add, 0 to change, 1 to destroy. ``` -## Task 3: Add variable interpolation to the count arguement +## Task 3: Add variable interpolation to the count argument ### Step 8.3.1 Update `variables.tf` to add a new variable definition, and use it: ```hcl -# ... variable "num_vms" { + type = number default = 2 } ``` + Update `main.tf` + ```hcl resource "azurerm_public_ip" "training" { count = var.num_vms @@ -117,17 +116,13 @@ resource "azurerm_network_interface" "training" { resource "azurerm_virtual_machine" "training" { count = var.num_vms - name = "${var.prefix}vm-${count.index + 1}" # ... ``` -The solution builds on our previous discussion of variables. We must create a -variable to hold our count so that we can reference that count in our -resource. Next, we replace the value of the count parameter with the variable -interpolation. Finally, we interpolate the current count (+ 1 because it's -zero-indexed) and the variable itself. +The solution builds on our previous discussion of variables. We must create a variable to hold our count so that we can reference that count in our resource. Next, we replace the value of the count parameter with the variable +interpolation. Finally, we interpolate the current count (+ 1 because it's zero-indexed) and the variable itself. Remember to also add the variable declaration to your `terraform.tfvars` accordingly. @@ -144,9 +139,157 @@ terraform apply ``` ```text -No changes. Infrastructure is up-to-date. +No changes. Your infrastructure matches the configuration. + +Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed. +``` + +## Appendix + +Here is the full configuration for the `azure` deployment before this lab in case you need it. It has been condensed into a single file for simplicity. + +**`main.tf`** + +```hcl +variable "location" { + type = string + description = "The Azure Region in which all resources in this example should be created. Defaults to East US." + default = "East US" +} + +variable "resource_group_name" { + type = string + description = "The name of the resource group in which all resources in this example should be created." +} + +variable "EnvironmentTag" { + type = string + description = "The environment tag for all resources in this example." +} + +variable "prefix" { + type = string + description = "The prefix which should be used for all resources in this example. Set to your initials." +} + +variable "computer_name" { + type = string + description = "The name of the virtual machine." +} + +variable "admin_username" { + type = string + description = "The username of the virtual machine." +} + +variable "admin_password" { + type = string + description = "The password of the virtual machine." +} + +locals { + common_tags = { + environment = var.EnvironmentTag + service_name = "Automation" + owner = "Cloud Team" + createdby = "terraform" + } +} + +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "training" { + name = var.resource_group_name + location = var.location +} + +resource "azurerm_virtual_network" "training" { + name = "azureusernsbvn" + address_space = ["10.0.0.0/16"] + location = azurerm_resource_group.training.location + resource_group_name = azurerm_resource_group.training.name +} -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. +resource "azurerm_subnet" "training" { + name = "azureusernsbsub" + resource_group_name = azurerm_resource_group.training.name + virtual_network_name = azurerm_virtual_network.training.name + address_prefixes = ["10.0.2.0/24"] +} + +resource "azurerm_public_ip" "training" { + name = "azureusernsbip" + location = azurerm_resource_group.training.location + resource_group_name = azurerm_resource_group.training.name + allocation_method = "Dynamic" + idle_timeout_in_minutes = 30 + domain_name_label = "azureusernsbdomain" +} + +resource "azurerm_network_interface" "training" { + name = "azureusernsbni" + location = azurerm_resource_group.training.location + resource_group_name = azurerm_resource_group.training.name + + ip_configuration { + name = "azureusernsbip" + subnet_id = azurerm_subnet.training.id + private_ip_address_allocation = "Static" + private_ip_address = "10.0.2.5" + public_ip_address_id = azurerm_public_ip.training.id + } +} + +resource "azurerm_virtual_machine" "training" { + name = "${var.prefix}vm" + location = azurerm_resource_group.training.location + resource_group_name = azurerm_resource_group.training.name + network_interface_ids = [azurerm_network_interface.training.id] + vm_size = "Standard_D2s_v4" + + delete_os_disk_on_termination = true + delete_data_disks_on_termination = true + + storage_image_reference { + publisher = "Canonical" + offer = "UbuntuServer" + sku = "16.04-LTS" + version = "latest" + } + storage_os_disk { + name = "${var.prefix}disk" + caching = "ReadWrite" + create_option = "FromImage" + managed_disk_type = "Standard_LRS" + } + os_profile { + computer_name = var.computer_name + admin_username = var.admin_username + admin_password = var.admin_password + } + + os_profile_linux_config { + disable_password_authentication = false + } + + tags = local.common_tags +} + +output "public_dns" { + value = azurerm_public_ip.training.fqdn +} +``` + +**`terraform.tfvars`** + +```hcl +resource_group_name = "nsb-resourcegroup" +EnvironmentTag = "staging" +prefix = "###" # change to your initials +location = "East US" +computer_name = "myserver" +admin_username = "testadmin" +admin_password = "Password1234!" ``` diff --git a/terraform_fundmentals/12-destroy.md b/terraform_fundmentals/12-destroy.md index e69de29..49a91a5 100644 --- a/terraform_fundmentals/12-destroy.md +++ b/terraform_fundmentals/12-destroy.md @@ -0,0 +1,17 @@ +# Lab: Destroy + +Duration: 5 minutes + +We've already used `terraform destroy` to destroy our infrastructure, but let's do it again to make sure everything is gone. + +- Task 1: Run the Destroy Command + +## Task 1: Run the Destroy Command + +From the `azure` directory, run the following command: + +```bash +terraform destroy +``` + +Review and approve the destroy plan.