Terraform + SageMaker Part 1: Terraform Initialization | by David Hundley | Towards Data Science

Hello there, folks! Today, we’re starting a new series on using Terraform to create resources on AWS SageMaker. I expect you’re likely familiar with what SageMaker is, even if only on a very general level. In a nutshell, SageMaker is AWS’s machine learning service. AWS has poured a lot of energy into this particular service over the years, obviously in response to the explosive growth that the data science / machine learning practice has become across all industries. It’s sort of difficult to keep up with the growth! Even I will readily admit I’m not that familiar with some of the newer stuff like SageMaker Studio, but I’m excited to learn more about it alongside you all in this new series of posts.
Even if you have a general understanding of what SageMaker is, you might not be aware of what Terraform is. Truth be told, I didn’t know about Terraform myself until the company I work for adopted it. Terraform is an “infrastructure as code” product created by HashiCorp to deploy infrastructure resources onto various platforms. It is not an AWS product in and of itself, but it is designed to interact with AWS very well.
Naturally, I’d expect you to ask yourself the question, “Why Terraform?” We’ll cover that in next section, and in the following sections, we’ll start delving into creating your first resource on AWS using Terraform. Transparently, we won’t be touching SageMaker at all in this initial post, but we’re laying the foundation for what will come in subsequent posts. Trust me, you won’t want to miss this post!
With that, let’s jump into why we’re going to use Terraform.
When you interact with the AWS console to do things like create an S3 bucket, provision an EC2 instance, or pretty much everything else, you’re basically issuing software commands to create a resource. The AWS console is great for guiding you through understanding how to properly provision a given resource. But the console isn’t great for creating or destroying resources at mass in one fell swoop. Because the AWS console is basically executing software commands to create these infrastructure resources, we can create software scripts that will automate the creation of these resources just like we would automate anything else we can with software.
So if you’re a learner like me, you might not want to keep your AWS resources running when you’re not using them. Terraform can help you quickly set up or blow away your resources in a matter of seconds if set up correctly. Think about how long it takes just to set up one S3 bucket in the console interface. With Terraform, I can create a whole arsenal of resources in a faster amount of time than a person can create a single S3 bucket in the console. Nice!
If you’ve taken any of the AWS certifications, you might recall that AWS has its own service called Cloud Formation that allows you to do exactly this. You might reasonably ask then why we’d want to use Terraform over Cloud Formation. Don’t get me wrong, Cloud Formation is a great service, but Terraform offers many other benefits over Cloud Formation. A few include the following:
Platform agnostic: Even though we will only be working out of AWS in this series, Terraform can also provision resources on other platforms including Google Cloud Platform, Microsoft Azure, and more. You won’t necessarily be able to use your AWS Terraform scripts on those other platforms, but because Terraform is its own scripting language, that knowledge carries over very well to the other platforms.
State management: Terraform offers some pretty robust options for maintaining the state of your resources. We’ll talk about this more in an upcoming section, but if you were to use one of HashiCorp’s more premium offerings like Terraform Cloud or Terraform Enterprise, you can really do some very advanced stuff with this state management. Speaking of advanced stuff...
Sentinel policies: This is one of these advanced topics and premium features we won’t be covering in this series, but sentinel policies basically enforce certain rules that all Terraform provisioners must abide by. Imagine that you work for a large corporation that requires a specific set of tags for every resource deployed. Creating a sentinel policy can help facilitate that in a very streamlined way!
There are too many benefits to list all here. Though those premium features mentioned above aren’t free, we can use the standard Terraform command line actions at no cost. Granted, the resources you provision onto AWS may not be free, but there is no cost associated with issuing the Terraform commands we’ll be learning about in this series.
Before we get into initializing Terraform, let’s briefly talk about state management.
When we create resources on our platform of choice — AWS in our case — Terraform manages what it has provisioned in the form of a Terraform state file. The Terraform state file is generally a file that ends in the .tfstate suffix and maintains the information of everything that you’ve provisioned to date with your Terraform scripts. If you make an adjustment to any of those scripts, the Terraform CLI commands will analyze the tfstate file to see what action it needs to take. For example, if the resource is not present in the tfstate file, then Terraform will create that new resource for you. If the resource is present but has had some slight configuration changes, Terraform will update the resource appropriately as well as update the tfstate file with that new configuration information.
As you might be able to guess, there are some “catches” with this tfstate file. First, if you are collaborating on a team that is working together to provision various resources using the same tfstate file, you run the risk of basically stepping on each others toes and corrupting your tfstate file. This is a very important consideration, but since this series is going to focus more on single user usage, I’m not going to cover that aspect of state management in this series. This is still an extremely important topic to consider when working amongst multiple people, so I’d advise you to check out Terraform’s website to learn more about state locking to avoid corruption.
The more important “catch” that we will be covering in this post has to do with the nature of the tfstate file itself. As you might be able to guess, the tfstate file may contain very sensitive information as it basically tells the world what you have provisioned out to whatever platform. It may even contain sensitive metadata you would not want exposed to the public. Fortunately, Terraform offers many solutions to keep your tfstate file safe. If there’s one takeaway I hope you get from this paragraph, it’s this: DO NOT post your tfstate file to any public place, especially GitHub. Bad actors are out there and WILL exploit this if they can. But don’t worry! One of the actions we’ll be taking in this post is setting up an S3 bucket to house your tfstate file and be able to remotely interact with it from your local machine.
Alrighty, now we’re ready to jump into the meat of the post! Let’s start by setting up what we need in order to be able to use Terraform properly.
In order to use Terraform, we will need to have the following things:
Installing the Terraform CLI
Setting up credentials on your local machine to interact with AWS
Setting up the S3 bucket for state management
Let’s rapid fire how to do each of these things in the subsections below.
This first point is pretty simple and straightforward. There are lots of ways to install the Terraform CLI, and I’m going to point you to Terraform’s official documentation for that. If you’ve been following along with my other recent posts, I’m actually doing this whole post on an iPad with a Raspberry Pi “computational accessory.” Not only am I writing this post on my iPad, but I used the Textastic app to write the Terraform scripts and then transferred them to my Raspberry Pi to actually execute the Terraform commands. It’s so cool!
Because we will be interacting with our tfstate file located in our remote S3 bucket, you will need to have your local credentials set up properly to interact with your AWS account. If you’ve set up the AWS CLI before, chances are that you’re already good to go. If you haven’t done that before, here’s the official AWS documentation on how to set up those credentials. As with the tfstate file, please be mindful NOT to share these credentials out in public spaces. Again, bad actors WILL exploit them, and they WILL run up your AWS bill. Please treat these credentials as you would your Social Security number.
As for setting up the S3 bucket for the state management, there’s really nothing special we have to do here. Because we’re using your AWS credentials to interact with AWS, you do NOT have to make this a public bucket. In fact, I would highly advise against making the bucket public. (You’re also welcome to use a pre-existing bucket.) Using the AWS console to create a bucket is very straightforward, but here’s the documentation on how to do that just in case. Once your S3 bucket is created, we’re ready to move on. You’ll see how Terraform will make use of this bucket in a forthcoming section.
Okay, hopefully that pre-work process was pretty painless! We’re now ready to start making use of Terraform to create our first resource. To keep things super simple, the only thing we’ll be creating is a super rudimentary IAM role that only has access to list out S3 buckets. The reason I’m selecting to teach this is because a) it is very simple and b) IAM roles are free on AWS. So if you forget to delete the role, no big deal.
(Side note: I PROMISE what we’re doing in this post will NOT expose you to any vulnerability. This isn’t some secret “Trojan horse” thing that would open your AWS account to hackers. If done properly, this IAM role we are creating will basically be able to do nothing. I’ll also teach you how to delete resources with Terraform so that you can guarantee nothing is left lingering in your account that you wouldn’t want there.)
If you would like to see the precise Terraform files I used in this post, check out my GitHub repository here. Even though this does contain the plain text name of the S3 bucket I’m using, you won’t be able to do anything with it because you don’t have my AWS credentials. If you do choose to clone my repo, you will need to update the Terraform backend piece to match your own respective bucket.
We’re not going to delve too much in this post on how Terraform files are structured, but we will cover a few basics here. First, Terraform looks to provision resources within files that end with the .tf suffix. You can technically jam everything you want into a single .tf file. If you’re familiar with Kubernetes YAMLs, the concept is the same here. Even though you technically can jam everything in a single .tf file, it always makes sense to logically separate them out into separate files that make sense as you see fit. (I personally like to bundle individual AWS resources into its own file along with the directly associated IAM policies / roles.)
When initializing Terraform for usage on a given platform, Terraform will always look for a provider block. In this case, we’re using AWS and will be deploying resources into the regionus-east-1. (You can change the region to whatever you’d like, that does not matter.) The other thing it will look for is to see if you would like to make use of a remote backend for state management. Given that we’ve already set up our AWS credentials and bucket, the actual Terraform file to get the ball rolling is very simple. Here’s what my script looks like:
terraform {  backend "s3" {    bucket = "dkhundley-terraform-test"    key    = "terraform-sagemaker-tutorial.tfstate"    region = "us-east-1"  }}provider "aws" {  region = "us-east-1"}
What this script is basically saying is the following: I want to deploy Terraform resources to AWS (region US East 1), and I want to use a remote tfstate file within this given bucket with this key. That key there can be whatever you want to call it. So essentially if you want to emulate what I did, the only thing you should really have to change is the S3 bucket name.
Okay, that’s all we need to do for backend set up! Now, let’s quickly show the script of that IAM role we’re going to be creating with Terraform:
## DATA BLOCKS## ----------------------------------------------------------------# Creating the data that will be used by the IAM policydata "aws_iam_policy_document" "s3_ls_policy_data" {  statement {    actions = [      "s3:ListAllMyBuckets"    ]resources = [      "arn:aws:s3:::*"    ]  }}# Creating the assume role policy datadata "aws_iam_policy_document" "s3_ls_assume_role_data" {  statement {    actions = ["sts:AssumeRole"]principals {      type        = "Service"      identifiers = ["s3.amazonaws.com"]    }  }}## RESOURCE BLOCKS## ----------------------------------------------------------------# Creating the IAM policy using the data block from aboveresource "aws_iam_role_policy" "s3_ls_policy" {  name   = "s3_ls_iam_policy"  policy = data.aws_iam_policy_document.s3_ls_policy_data.json  role   = aws_iam_role.s3_ls_role.id}# Creating the IAM role associated to the resources aboveresource "aws_iam_role" "s3_ls_role" {  name               = "s3_ls_iam_role"  description        = "This role allows for all S3 buckets to be listed."  assume_role_policy = data.aws_iam_policy_document.s3_ls_assume_role_data.json}
Again, we’re not going to delve super deeply into what all this syntax means. At a high level, I want to call out a few things:
Resource blocks: These are the “rubber meets the road” blocks of code that actually provision a desired resource. The string in the first set of quotes defines the resource type as defined by Terraform, so you must use their precise syntax for that. The string in the second set of quotes is the arbitrary name you want to give the resource. Because Terraform allows you to reference that resource in other bits of your Terraform code, it’s always a good idea to give your resource a name you can remember.
Data blocks: These are generally used to provide additional configuration for a resource you plan to provision. In this case, the data blocks provide the configuration for how the IAM policy should be set up.
Comments: Just like with Python, beginning a piece of code with the # symbol will allow you to annotate your scripts with appropriate comments. You can see that I have used them above to descriptive note what each block of code is doing.
Terraform provides an extremely robust library of documentation on how to provision pretty much any resource on AWS. (Because AWS is constantly rolling out new stuff, Terraform might not be able to support a new AWS service on day 1, but they are very good about updating their stuff in new Terraform versions.) To see an example of this, here is the documentation I referenced to create the IAM role itself.
If you’re structuring your code the same way I am, you should now have two Terraform files:
backend.tf
iam.tf
We’ll get into reusable Terraform variables and more in later posts. For now, this simple set up will work just fine to get us started. Let’s now actually see how the Terraform CLI works to provision these resources.
Now it’s time to get to the fun part! Fun because we’ll get to see the fruits of labor come to life very quickly in this last section. To quickly summarize what we’ll be doing in these subsections, let’s rattle off those topics in these bullets below:
Performing a terraform init
Validating and formatting your tf files
Creating your resources on AWS with Terraform
Destroying your resources on AWS with Terraform
As mentioned above, Terraform will first look for the provider and backend to know how it should be properly interacting with your AWS account. In order to get that “communication” started, you’ll first need to perform the terraform init command. It will also download the proper plugins required to deploy the resources out to AWS. If the command has run successfully, this is what you should see:
That’s it for initialization! Let’s move on to validating and formatting your files.
These steps are actually not required, but they help to make your life easier when trying to read / debug your code. By running the terraform validate command, Terraform will make sure that everything is squared away with your Terraform files without actually provisioning anything. If you’ve messed something up, Terraform will tell you what’s wrong. Just so you can see what this looks like, I’m going to comment out a required piece in my Terraform. Let’s see what happens when I run my terraform validate command...
As you can see, the first time I run the validate command outputs very plainly spelled out where I messed up and how to fix it. The second time I run the terraform validate command after I’ve fixed things, Terraform gives me the greenlight that my stuff looks good!
The latter command isn’t at all required, but it’s still nice. By running the terraform fmt command, Terraform will look at your tf files and do what it can to make everything all nice and pretty. This includes lining up all your resource parameters to have the = signs all line up. Again, not required at all, but I personally find it helpful.
With your Terraform files validated, we’re finally ready to use them to provision our resources on AWS! Now if you want to get an understanding of how your resources will change prior to formally creating them, it’s a good idea to run the terraform plan command. This command will plainly spell out how things will change. Here’s an example of what this means with our little IAM role creation here:
If everything looks good to you, you’re clear to create these resources by running the terraform apply command. By running this command without any flags, Terraform will show you a similar output as terraform plan, except it will also prompt you to confirm the changes by entering yes. Upon entering yes, Terraform will appropriately create the resources and note successful creation appropriately.
And there you go! Your first resources created on AWS with Terraform! If you go into the IAM console, you should now be able to see this IAM role we created from within there. Very cool!!!
Of course, I can understand if you would not want to keep this resource around, so let’s finish off this post with destroying what we just created.
As you might be able to guess, destroying resources with Terraform is just as easy as creating them. Instead of running terraform apply, we instead run terraform destroy. Again, you’ll be prompted by Terraform to confirm the destruction of these resources by entering yes, and then Terraform will go ahead and destroy the IAM role we just created.
Poof! Those resources are now gone.
And that wraps up this post for today! I hope you’re not too disappointed we didn’t actually get to anything SageMaker-related today, but I promise you this foundational post is going to serve us very well as we continue along in subsequent posts. Thanks for sticking through this long post! See you in the next one. 😃