In the previous post, we went over how to provision a server with Rails, how to attach a static IP address to the server, and how to set up an SSH on the server.
In this post, we'll discuss how to clean up what's now become a pretty crowded directory. We'll do that through abstracting out our key pieces of infrastructure into easy-to-reuse modules.
It may be helpful to check out the helper files associated with this series, while following along! If you're just starting with this chapter, it may also be helpful to check out the other Terraform tutorials.
If you're interested in topics like this, why not subscribe to the blog?
What are Modules?
We've created a number of resources over the past few chapters. EC2 Instances, Elastic IP addresses, Security Groups, SSH Key Pairs… We have all those things in the root directory.
If we want to make large changes to any of these, we have to jump between a lot of files. If we want to create another server with the same configuration, we need to duplicate all of that code.
As an infrastructure becomes larger, you can imagine this would get pretty hard to manage!
This is where modules come to the rescue! Modules essentially allow us to take a collection of resources and data sources and create a larger, custom resource out of the combination. We can define input variables and output variables to this module so that we can change large parts of the infrastructure with very little effort and then pull out all of the useful information.
Modules are basically your dreams, come to life.
Let's start off by copying the project directory from the last chapter:
$ cp -R 2-installing-rails/ 3-5-modules/
We'll then make a directory called
rails-server and move some files into it:
$ mkdir rails-server $ mv aws_ami.tf rails-server/ $ mv eip.tf rails-server/ $ mv main.tf rails-server/ $ mv outputs.tf rails-server/ $ rm key_pair.tf
Modules can take input variables that modify the behavior of the resources they contain. We describe these variables through a
variables.tf file and define their values as attributes when we instantiate the module.
Let's create a file in the location of
There's no need to
.gitignore this file, like we did the other
variables.tf. It doesn't contain any secret keys or passwords.
Now that we have these variables, we can make use of them by modifying the file
These variables are used just like the variables we used to define our AWS secrets and give them to the AWS provider.
A few things to note:
A variable with the type
listcan be used by wrapping it in square brackets (
These variables are not global and are in a different scope from the variables used in the parent infrastructure.
Defaults for variables can be set to make interacting with the module less verbose
It's time to create a new
main.tf file in root directory of
3-5-modules/. This time we'll make use of the module we've just built instead of the raw AWS resources.
We'll actually make two servers to prepare us for running a production application:
terraform init and
terraform apply to create your new servers!
What we've done
There are a few things happening here:
We start off by making a key pair for connecting to the new servers. This is placed outside of the modules so that only one
aws_key_pairresource is created (instead of two).
my-rails-server-2using the new
rails-servermodule we've created. The module source attribute is simply the path to the directory that stores the module files.
We've used the variables we defined in
rails-server/variables.tfto easily configure the nested resources within each instantiated module.
Let's create a root
outputs.tf file to create some useful CLI outputs for our new servers.
Notice that we moved the old
outputs.tf file to
rails-server/outputs.tf in the initial setup. That file had defined an output named
server-ip that we can now utilize on our instantiated modules.
To see the value of these variable in the CLI, we can create an
outputs.tf in the root
3-5-modules/ folder so that Terraform will output the module attributes we care about:
terraform apply one last time will give us what we're interested in!
aws_key_pair.my-rails-key: Refreshing state... (ID: my_rails_key) aws_security_group.allow_ssh: Refreshing state... (ID: sg-f067688c) aws_security_group.allow_outbound: Refreshing state... (ID: sg-62646b1e) data.aws_ami.ubuntu: Refreshing state... data.aws_ami.ubuntu: Refreshing state... aws_instance.my-test-instance: Refreshing state... (ID: i-0ebf9d64f80fe9912) aws_instance.my-test-instance: Refreshing state... (ID: i-0da963040524110f7) aws_eip.test-eip: Refreshing state... (ID: eipalloc-c9edf9f4) aws_eip.test-eip: Refreshing state... (ID: eipalloc-1deffb20) Apply complete! Resources: 0 added, 0 changed, 0 destroyed. Outputs: server-ip-1 = 184.108.40.206 server-ip-2 = 220.127.116.11
Congratulations! You've set up two Rails-ready servers that are entirely documented through code, while keeping things DRY and easy to read. You're well on your way to having a production-ready Rails environment that's well documented, easy to modify and source controlled!
Destroy it all
We're building infrastructure as code. It costs money to rent servers and we don't have any to spare. Let's destroy this infrastructure until the next chapter to save a little money.
$ terraform destroy
Get used to destroying resources you don't need! It's both fun and cost efficient. This is infrastructure as code, after all.
Wrapping it up
In this chapter, we created a module
rails-server and used it to instantiate two fully-provisioned servers. We utilized input variables to quickly modify module resources and output variables to grab the data we were interested in.
In the next chapter, we're going to expand this idea further by creating a running, production-ready Rails application environment through Terraform configurations.