My First Experience Coding With Terraform
Over the past four years when deploying cloud infrastructure, I have always worked with/written ARM templates (Azure Resource Manager). I have worked at companies where .NET development is very prevalent and Microsoft products were heavily purchased.
Earlier this year, I took a new job and it required different thinking and logic on how to apply and recommend tool sets due to the new company’s size (it is much larger than my previous employer).
There are many studies that show this approach makes for a happier and more productive teams. I think both Accelerate and The DevOps Handbook show this statistic and probably many more books. So, when I had a team select Terraform, I was excited.
I had worked with Packer in the past, which is made by HashiCorp, the same company that makes Terraform. I had a good experience working with Packer, so I had some background on how documentation was written and places to look if I got stuck. So…I started searching…
No environment variables, what!
One of the first things I learned was that Terraform files do not accept environment variables directly like a Packer file can.
Additionally, you must have a file called variables.tf
which lists all variables to get passed into your main infrastructure deployment file(s). Again, the variables.tf
should have all variable names, but not necessarily their values, which will get passed into your main file(s). You probably don’t want to hardcode values for variables, especially secret things.
The variables.tf
file will look something like this:
\
how do we get started with our main file(s)?
First, we need to specify the cloud platform provider, this can be done in the main file itself or in a file called provider.tf
I chose to put my azure creds in a separate file. This is just preference though; the very important part is to ensure your variables for your azure service principal account are called:
\
- client_id
- client_secret
- tenant_id
If you try to call them anything different your deployment will fail.
Terraform does cool stuff for us in the background when we call it, but it also restricts us a bit since it does a lot of abstraction for us.
It took me a while to figure this issue out…the silliest errors take the longest to find, right? My provider.tf
file looks like this, these lines of code can also be at the top of your main.tf
as well
\
Our main.tf
file, Terraform itself, is not super hard to learn. The documentation from HashiCorp is decent, but it could be improved by having more examples.
A simple block to create a resource group looks like this:
\
Sacrifice #1: No API Versions
If you’ve worked with ARM templates in the past, you’ll notice there is not an API version listed.
In the small research and discovery, I’ve done with Terraform, I haven’t seen a way to pass am API version for each resource…remember each resource in Azure has its own API version (i.e. compute vs. a key vault creation will be different API versions). If your infrastructure is using older Azure features or rarely gets updated, you probably don’t care that you aren’t specifying the API version
If it is possible to specify API versions please leave a comment with a link or example!
Sacrifice #2: Portal Shows “No Deployments”
When deploying actual ARM templates, the Azure portal lets us know a deployment is in progress but Terraform deployments do not show this.
They manage the state of your infrastructure in a totally different way than Azure ARM templates. Terraform will create a state file as it is deploying which holds the description of all your resources.
I won’t get into this in too much detail because there are a lot of articles on the interwebz about it. Just know that this state file holds sensitive secrets, but if you don’t keep it properly the state of your entire infrastructure will be at risk.
Sacrifice #3: Management Of Variables Via Orchestration
Earlier I mentioned you cannot pass environment variables directly into your main.tf
file and you must also have a variables.tf
file which defines all your variables. Again, we don’t want to hardcode things so how can we pass in values?
This becomes an even larger question if we need the ability to get the value or something already deployed and pass that into our template or follow naming conventions that we dynamically generate. The only way I found around this issue is having to write a temp file then delete that file. So I basically use a PowerShell script to orchestration all this stuff for me. Yeah this sucks. Sucks a lot. Especially since Packer can accept environment variables directly.
I am using Terraform v0.11.10 while writing this post and I hope in a future release they make it as easy as Packer so no file needs to be written. Essentially a sucky work around this like for now will be fine but ensure you have the terraform.tfvars
file in your git ignore!
\
Kewlness #1: It’s Simple
Abstraction is a great thing when done properly. So many configuration as code platforms and IaC tools, they are based in a traditional programming language, but to write code in them, you just need to know the abstraction.
All that crazy hardness of programming kind of goes away, until you want to do something a bit crazy. Then you need to know more in-depth techniques, but you’ll be way less afraid of learning them because you’ve built confidence. Since most tools are open source tool, you can also go look at Github for the source code of your favorite tool to learn more about how that company uses the traditional programming language.
The other cool thing Terraform does for us is that is just “knows” about its files assuming you’ve followed their conventions and are in the same directory.
Then we can just run terraform plan and terraform apply and boom there goes my infrastructure. If you don’t keep all terraform code in the same directory, you may need to give terraform the path to it.
In a 105 lines of code with Terraform I can create a resource group, deploy a key vault with N number of secrets, deploy N network interfaces, an availability set, and N virtual machines. Which brings me to my favorite thing about Terraform.
Kewlness #2: Loops
Have you ever written loops in an ARM template? They are a pain and come with silly error messages if you’ve messed them up.
In Terraform however, they are much easier. Let me do my best to explain.
Terraform will take my list of web_secret_names and just loop though however many items are in my list. Remember we still must define web_secret_names in our variables.tf
and it must be of type list for it to be used in loops.
\
Kewlness #3: It’s One Language For Many Cloud Platforms
While we hear cloud agnostic tossed around so much, I’m not sure I would agree with the many people that say Terraform is cloud agnostic.
What I do agree with is the Terraform language is the same for any cloud, but your main.tf
file will always be different from one provider to the next.
For example, we could not take our coding examples above and deploy them in Google Cloud or AWS. However, we can probably easily translate the main.tf
file because we already know how to write Terraform.
You could see the advantage to this if you have services in many cloud providers so do don’t have people learning Azure, AWS and Google Cloud specific languages, instead they learn Terraform and now can read the infrastructure deployments for all cloud providers.