Managing your on-premises infrastructure with HashiCorp Terraform - Part 2
Wed, 18 Sep 2024 13:49:37 -0000
|Read Time: 0 minutes
Most of the Terraform content out there targets developers and public cloud. In contrast, this blog focuses on Terraform as an automation tool for system and storage admins and infrastructure engineers operating physical hardware.
- Terraform on-premises infrastructure
- Terraform Dell PowerStore
- Terraform resource dependencies
- Terraform variables
- Terraform Dell PowerMax
This post is the second in a series. In the previous blog post, we provided an introduction to Terraform.
In this blog post, we will focus on Dell PowerStore volumes. We will showcase some examples of popular hardware one can find in a data center. This blog will also highlight the “Declarative” nature of Terraform. Let us get to it!
To follow along with the examples, you can get the Terraform for PowerStore in Dell GitHub page or in Terraform registry. The repository also contains documentation and a folder with examples.
Let us continue with a simple terraform configuration to create a volume. The “variables.tf” we showed in the previous post remains the same. The following is our “main.tf”. It will create an 8GB volume called “alb-tf-test”.
terraform { required_providers { powerstore = { version = "1.1.0" source = "registry.terraform.io/dell/powerstore" } } } provider "powerstore" { username = var.username password = var.password endpoint = var.endpoint insecure = true timeout = var.timeout } resource "powerstore_volume" "test1" { name = "alb-tf-test" size = 8 capacity_unit = "GB" }
After running “terraform init” and “terraform plan” it is the turn of “terraform apply”.
root@alb-terraform:~/powerstore# terraform apply Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # powerstore_volume.test1 will be created + resource "powerstore_volume" "test1" { + app_type = (known after apply) + app_type_other = (known after apply) + appliance_id = (known after apply) + capacity_unit = "GB" + creation_timestamp = (known after apply) + description = (known after apply) + host_group_id = (known after apply) + host_id = (known after apply) + id = (known after apply) + is_replication_destination = (known after apply) + logical_unit_number = (known after apply) + logical_used = (known after apply) + name = "alb-tf-test" + nguid = (known after apply) + node_affinity = (known after apply) + nsid = (known after apply) + performance_policy_id = "default_medium" + protection_policy_id = (known after apply) + sector_size = 512 + size = 8 + state = (known after apply) + type = (known after apply) + volume_group_id = (known after apply) + wwn = (known after apply) } Plan: 1 to add, 0 to change, 0 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes powerstore_volume.test1: Creating... powerstore_volume.test1: Creation complete after 1s [id=da01758b-c009-44c6-9999-11b1b821af61] Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Firstly, it shows changes is about to do and asks for confirmation. Then I pressed yes and in less than a second the volume was created as you can see in this screenshot.
Terraform is a modern infrastructure-as-code tool that uses a declarative syntax. This means that as a user you specify the “end-state” as opposed to the procedure to arrive to that end-state. In other words, you say WHAT you want instead of HOW to get it.
An analogy of this is going to a restaurant and ordering Bolognese pasta instead of giving the waiter a written recipe on how to cook it. As you can imagine the former is much easier and more convenient.
What happens now if we apply the same configuration again. Terraform will check the current state of the PowerStore and it will see if it is different from the desired end-state. Given that the volume exists already it should not do anything. Let us see.
root@alb-terraform:~/powerstore# terraform apply powerstore_volume.test1: Refreshing state... [id=da01758b-c009-44c6-9999-11b1b821af61] 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. Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
Notice how it did not even ask for confirmation because it found no differences.
So, what happens now if we decide to expand that volume. Let us say we want to make it 16 GB. This is what the “powerstore_volume” resource looks like. The rest of our “main.tf” remains the same.
resource "powerstore_volume" "test1" { name = "alb-tf-test" size = 16 capacity_unit = "GB" }
Let us run it and see what happens.
root@alb-terraform:~/powerstore# terraform apply powerstore_volume.test1: Refreshing state... [id=da01758b-c009-44c6-9999-11b1b821af61] Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: ~ update in-place Terraform will perform the following actions: # powerstore_volume.test1 will be updated in-place ~ resource "powerstore_volume" "test1" { + app_type = (known after apply) + app_type_other = (known after apply) ~ appliance_id = "A1" -> (known after apply) ~ creation_timestamp = "2024-05-02T23:44:43.197463+00:00" -> (known after apply) + description = (known after apply) + host_group_id = (known after apply) + host_id = (known after apply) ~ id = "da01758b-c009-44c6-9999-11b1b821af61" -> (known after apply) ~ is_replication_destination = false -> (known after apply) ~ logical_unit_number = 0 -> (known after apply) ~ logical_used = 0 -> (known after apply) name = "alb-tf-test" ~ nguid = "nguid.45d6df86642a6e988ccf0968001eef0f" -> (known after apply) ~ node_affinity = "System_Select_At_Attach" -> (known after apply) ~ nsid = 21902 -> (known after apply) + protection_policy_id = (known after apply) ~ size = 8 -> 16 ~ state = "Ready" -> (known after apply) ~ type = "Primary" -> (known after apply) + volume_group_id = (known after apply) ~ wwn = "naa.68ccf0980045d6df86642a6e981eef0f" -> (known after apply) # (3 unchanged attributes hidden) } Plan: 0 to add, 1 to change, 0 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes powerstore_volume.test1: Modifying... [id=da01758b-c009-44c6-9999-11b1b821af61] powerstore_volume.test1: Modifications complete after 1s [id=da01758b-c009-44c6-9999-11b1b821af61] Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
Notice how it is telling us it is about to change the size of the volume. After the confirmation it went ahead and made the changes. This is what PowerStore Manager shows now.
What about reducing the size? Let us modify the “powerstore_volume” resource to shrink it to 10GB.
resource "powerstore_volume" "test1" { name = "alb-tf-test" size = 10 capacity_unit = "GB" }
Now we run “terraform apply” again. It informs us again of the changes that are about to be made.
What about reducing the size? Let us modify the “powerstore_volume” resource to shrink it to 10GB.
resource "powerstore_volume" "test1" { name = "alb-tf-test" size = 10 capacity_unit = "GB" }
Now we run “terraform apply” again. It informs us again of the changes that are about to be made.
root@alb-terraform:~/powerstore# terraform apply
powerstore_volume.test1: Refreshing state... [id=da01758b-c009-44c6-9999-11b1b821af61]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
~ update in-place
Terraform will perform the following actions:
# powerstore_volume.test1 will be updated in-place
~ resource "powerstore_volume" "test1" {
+ app_type = (known after apply)
+ app_type_other = (known after apply)
~ appliance_id = "A1" -> (known after apply)
~ creation_timestamp = "2024-05-02T23:44:43.197463+00:00" -> (known after apply)
+ description = (known after apply)
+ host_group_id = (known after apply)
+ host_id = (known after apply)
~ id = "da01758b-c009-44c6-9999-11b1b821af61" -> (known after apply)
~ is_replication_destination = false -> (known after apply)
~ logical_unit_number = 0 -> (known after apply)
~ logical_used = 0 -> (known after apply)
name = "alb-tf-test"
~ Nuguid = "nguid.45d6df86642a6e988ccf0968001eef0f" -> (known after apply)
~ node_affinity = "System_Select_At_Attach" -> (known after apply)
~ nsid = 21902 -> (known after apply)
+ protection_policy_id = (known after apply)
~ size = 16 -> 10
~ state = "Ready" -> (known after apply)
~ type = "Primary" -> (known after apply)
+ volume_group_id = (known after apply)
~ won = "naa.68ccf0980045d6df86642a6e981eef0f" -> (known after apply)
# (3 unchanged attributes hidden)
}
Plan: 0 to add, 1 to change, 0 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
powerstore_volume.test1: Modifying... [id=da01758b-c009-44c6-9999-11b1b821af61]
╷
│ Error: Failed to update all parameters of Volume, updated parameters are [] and parameters failed to update are [name,size,protection policy,performance policy, description]
│
│ with powerstore_volume.test1,
│ on main.tf line 18, in resource "powerstore_volume" "test1":
│ 18: resource "powerstore_volume" "test1" {
│
│ Failed to Update : The operation cannot be completed on volume alb-tf-test (id: da01758b-c009-44c6-9999-11b1b821af61) because the requested size 10737418240 is less
│ than the minimum allowed or volume's current size 17179869184.
After the confirmation, it attempts to make the modification, but it fails because volumes cannot be shrunk in PowerStore. Since PowerStore is a REST API-first product, the Terraform provider uses the PowerStore REST API to make changes, the same as the GUI and all other management interfaces. The REST API does not allow us to shrink volumes. So, this is the right expected behaviour.
As a last step for this post, let us run the “terraform destroy”. Now, the Terraform will look at the current state and remove the resources that were previously created. Notice how, once again, it lists the changes to be made and looks for confirmation.
root@alb-terraform:~/powerstore# terraform destroy powerstore_volume.test1: Refreshing state... [id=da01758b-c009-44c6-9999-11b1b821af61] Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: - destroy Terraform will perform the following actions: # powerstore_volume.test1 will be destroyed - resource "powerstore_volume" "test1" { - appliance_id = "A1" -> null - capacity_unit = "GB" -> null - creation_timestamp = "2024-05-02T23:44:43.197463+00:00" -> null - id = "da01758b-c009-44c6-9999-11b1b821af61" -> null - is_replication_destination = false -> null - logical_unit_number = 0 -> null - logical_used = 0 -> null - name = "alb-tf-test" -> null - nguid = "nguid.45d6df86642a6e988ccf0968001eef0f" -> null - node_affinity = "System_Select_At_Attach" -> null - nsid = 21902 -> null - performance_policy_id = "default_medium" -> null - sector_size = 512 -> null - size = 16 -> null - state = "Ready" -> null - type = "Primary" -> null - wwn = "naa.68ccf0980045d6df86642a6e981eef0f" -> null } Plan: 0 to add, 0 to change, 1 to destroy. Do you really want to destroy all resources? Terraform will destroy all your managed infrastructure, as shown above. There is no undo. Only 'yes' will be accepted to confirm. Enter a value: yes powerstore_volume.test1: Destroying... [id=da01758b-c009-44c6-9999-11b1b821af61] powerstore_volume.test1: Destruction complete after 0s Destroy complete! Resources: 1 destroyed.
As expected, in PowerStore Manager we can no longer find the volume.
In this post we have seen the declarative nature of Terraform by creating and modifying a volume in Dell PowerStore. However, provisioning storage typically requires also to create a “host” and to map the “volume” to it. From a Terraform perspective there is a dependency involved, since you cannot attempt to do the mapping until the host exists. In the next post we will learn about defining dependencies between resources.
Author: Alberto Ramos