In an earlier recipe, you learned how to manage different environments with Terraform, which is great. But how do we test for changes before applying them?
Terraform has a great internal mechanism that allows us to plan for changes by comparing what our infrastructure code wants and what the remote state includes. That way, we can safely check whether what we thought was a minor modification in our code has in fact a destructive impact (sometimes, some parameters in a resource trigger a full destruction of the resource!).
We'll cover different ways of anticipating, simulating, and targeting changes in our infrastructure, as an added safety check before applying the changes for good.
To step through this recipe, you will need the following:
Let's start with a simple CoreOS machine on AWS. We know the AMI ID, we want a single t2.micro
host. Let's put that information in the variables.tf
file:
variable "aws_coreos_ami" { default = "ami-85097ff6" } variable "cluster_size" { default = "1" description = "Number of nodes in the cluster" } variable "aws_instance_type" { default = "t2.micro" description = "Instance type" }
The simplest aws_instance
resource we can make is the following in instances.tf
:
resource "aws_instance" "coreos" { count = "${var.cluster_size}" ami = "${var.aws_coreos_ami}" instance_type = "${var.aws_instance_type}" key_name = "${aws_key_pair.admin_key.key_name}" associate_public_ip_address = true tags { Name = "coreos_${count.index+1}" } }
Until now, we've used terraform apply
for immediate action. There's another command: terraform plan
. It does what it says. It plans for changes, but doesn't apply them:
$ 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. The Terraform execution plan has been generated and is shown below. [...] + aws_instance.coreos ami: "ami-85097ff6" [...] + aws_key_pair.admin_key [...] Plan: 2 to add, 0 to change, 0 to destroy.
So, by planning before applying, we can know what's about to happen to our infrastructure. We're happy about an instance with the right AMI being created, so let's terraform apply
.
Now the infrastructure is created, if you run a plan again, it will say nothing should be modified:
$ 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_key_pair.admin_key: Refreshing state... (ID: admin_key) aws_instance.coreos: Refreshing state... (ID: i-0f9106905e74a29f7) No changes. Infrastructure is up-to-date. This means that Terraform could not detect any differences between your configuration and the real physical resources that exist. As a result, Terraform doesn't need to do anything.
A normally operating infrastructure should always be in a state where a terraform plan
doesn't want to change anything.
Now let's say we need our infrastructure to evolve, and create an S3 bucket. That would look like this in a file named s3.tf
:
resource "aws_s3_bucket" "bucket" { bucket = "iacbook" tags { Name = "IAC Book Bucket" } }
We're not sure about what's about to happen, so let's plan with Terraform, so it's telling us exactly what it's intending to do:
$ terraform plan Refreshing Terraform state in-memory prior to plan... [...] aws_key_pair.admin_key: Refreshing state... (ID: admin_key) aws_instance.coreos: Refreshing state... (ID: i-0f9106905e74a29f7) [...] + aws_s3_bucket.bucket bucket: "iacbook" tags.Name: "IAC Book Bucket" [...] Plan: 1 to add, 0 to change, 0 to destroy.
The plan looks good—it seems to want to create an S3 bucket named the way we want! Let's terraform apply
this and move on.
We now wonder what would happen if we were to change the number of instances. That's the cluster_size
variable, currently set to 1
. Instead of messing with the code, we can test the impact of changing that value directly from the command line:
$ terraform plan -var 'cluster_size="2"' [...] + aws_instance.coreos.1 ami: "ami-85097ff6" instance_type: "t2.micro" tags.Name: "coreos_2" [...] Plan: 1 to add, 0 to change, 0 to destroy.
Good news! It looks like increasing the cluster_size
value has the intended effect: creating a new instance.
Now, we wonder legitimately what would be the effect of changing the instance type, from t2.micro
to t2.medium
:
$ terraform plan -var aws_instance_type="t2.medium" [...] -/+ aws_instance.coreos [...] instance_type: "t2.micro" => "t2.medium" (forces new resource) Plan: 1 to add, 0 to change, 1 to destroy.
Ouch! Changing the instance type seems to be a destructive action. Let's work on that later, and add the change to a new file named plan.tfvars
:
aws_instance_type="t2.medium"
We know we'd like to propose to change the number of instances to 2
, so let's add that to the same file:
aws_instance_type="t2.medium" cluster_size="2"
We can now test against this file containing all our changes, using the -var-file
option:
$ terraform plan -var-file=plan.tfvars -/+ aws_instance.coreos.0 instance_type: "t2.micro" => "t2.medium" (forces new resource) tags.Name: "coreos_1" => "coreos_1" [...] + aws_instance.coreos.1 instance_type: "t2.medium" tags.Name: "coreos_2" [...] Plan: 2 to add, 0 to change, 1 to destroy.
Good! You learn that our first instance will be destroyed and recreated to move from t2.micro
to t2.medium
, and that a second instance will be created with the same values. Let's not apply this, as added fees will be incurred.
Our colleague asks us if we're sure our proposed changes have no impact specifically on the S3 bucket. Terraform allows us to get an answer to that question very specifically by targeting the resource directly in the planning phase:
$ terraform plan -var-file=plan.tfvars -target="aws_s3_bucket.bucket" [...] aws_s3_bucket.bucket: Refreshing state... (ID: iacbook) [...] No changes. Infrastructure is up-to-date. [...]
Our colleague is happy, and we're now sure that this change will do exactly what's intended. We can submit this change for review.
3.137.184.3