Managing cloud infrastructure doesn’t always go smoothly for developers. Choosing which cloud provider to use, configuring the private and public networks, and the required security of the infrastructure aren’t easy tasks, and they consume research, documentation, and implementation time. In addition to that, in that process, many questions arise:
Some teams have a DevOps that can take care of the previous tasks, so developers don’t have to concern themselves. However, this role isn’t always covered in all projects, and the cloud-provisioning tasks fall into the developer’s hands. Whit that in mind, in Xmartlabs, we created an open-source terraform module (available on GitHub) to help the developers create, manage and maintain basic infrastructure in AWS.
Terraform is an open-source infrastructure as code tool developed and maintained by Hashicorp. Using Terraform, developers have the possibility of building, changing, and versioning infrastructure safely and efficiently in a programmatic way.
Why choosing Terraform instead of other solutions (CloudFormation, AWS, SDK, etc.)? Well, mainly because:
The implemented module defines a basic infrastructure in AWS that gives you all the resources required to give cloud provisioning to a basic application. Some of the apps we consider that belong to the category of a basic app are:
AWS provides several ways of creating infrastructure for this kind of application, such as Elastic Container Service (ECS), app runner, etc. The infrastructure that we’ll show here includes the following resources:
One of the reasons why we chose these resources against others is that they give you multiple alternatives to deploy your code. You can dockerize your application if you want, but it isn’t mandatory. Moreover, most of the resources managed in this module are included in the AWS free tier, allowing you to deploy your solution without spending much money.
When you clone the open-source Terraform module repository from GitHub, you’ll see the structure of the project is the following:
environments
: A folder that contains the tfvars
files with the values of the variables for each environment managed by Terraform. Later we’ll explain how to create and modify each file in this folder in more detail.modules
: A folder that contains the terraform modules created by our team to better organize the resources managed in the basic infra module. As the official documentation defines, a module is a container for multiple resources that are used together. Modules can be used to create lightweight abstractions so that you can describe your infrastructure in terms of its architecture rather than directly in terms of physical objects. Following that philosophy, the created modules are
module-cloudwatch
: Module that creates a clodwatch log_group and an IAM instance profile with access to it. You can use this module to store your application logs.module-ec2-linux-web
: Module that creates an EC2 instance where your application will runmodule-network-linux-web
: Module that creates all the networking resources required to deploy infrastructure with a single EC2 machine with exposition to the internet. The created resources include:
module-network-linux-web-db
: Module that creates the same resources that the previous module (module-network-linux-web
), adding a connection between the database and the EC2 instance. That means:
module-ecr
: Module that creates a set of Elastic container Registers (ECRs) useful for docker image storage. This module is useful when your application is dockerized and you want to store multiple versions of the images.module-rd-db
: Module that creates a database in RDS. Optionally, you can also configure a read replica for this database if needed.module-s3
: Module that creates an S3 bucket. Optionally, you can also configure an IAM user with access to this bucket and access and secret key pair.create_machine_script.tmpl
: File with an init script example used in the EC2 machine initialization. In this file, you can configure the installation of all the tools required for your application.main.tf
: Main Terraform module with the declaration of all the resources required for basic infrastructure. In that file, you will see exactly how the created modules are used by the main one. You probably won’t modify this module, but if required, here you can include new modules, modify current ones, etc.outputs.tf
: Definition of the main modules outputs values. All the declared values that will be shown in the terminal after the Terraform commands are executed are on this file.provider.tf
: File to configure the terraform provider (AWS account, bucket to store the Terraform state). Later, we’ll explain how to configure that file to use it in your project.varibles.tf
: File to declare the variables used by the main module. The variables allow you to modify some parameters in the mentioned modules without changing any Terraform code. In this project, the variables were considered generic, which means that you can change them to easily adapt the infrastructure to your project needs and also manage multiple environments without needing to change any Terraform code.Before starting to use the module, you need to meet these requirements:
Before starting to type terraform commands in a terminal, some resources inside the AWS console must be manually created.
terraform.tfstate
, but it can also be stored remotely, which works better in a team environment. We’ll use an S3 bucket to store the state file. Considering that the bucket’s names are globally unique, we recommend naming this bucket as {account-id}-tfstate
because it’s rarely occupied, has contents of your account’s id, and the fact that there is where tfstate is allocated makes it intuitive.At this point, you are ready to clone/copy the repository content and configure it for your project. There are several approaches to doing it. You can create a new repository to handle the infrastructure; you can create a folder (and name it something like infra
) in an already created repository and copy the content inside or even copy the files in a not versioned folder.
Our suggestion is to version the infrastructure code in a repository (a new one or a folder in an existing one). Following this approach, you’ll be able to edit the infrastructure following the good practice of code development (for example, using PRs and code reviews) in a shared way. Moreover, all the resources managed in the main module are designed to keep the secrets and private information secure.
Once you have a template copy, you can configure it for your specific solution. Before changing anything, you must configure your machine's AWS client. A detailed guide about this tool and how to configure it is here; a short explanation about configuring the AWS client is: executing the following commands in a terminal:
aws configure set aws_access_key_id <AWS_ACCESS_KEY_ID>
aws configure set aws_secret_access_key <AWS_SECRET_ACCESS_KEY>
aws configure set region <REGION>
where the parameters AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are related to the created IAM user for Terraform, and the REGION is the AWS region chosen for your project.
Once the client is properly configured, you need to edit the provider.tf
file and change the state bucket
for the name of the created one. If you follow the suggestion made in this blog, you only need to change the string <your-account>
with your AWS account identifier. For example, if your account id is 111111111111
, your provider file should be:
terraform {
required_version = ">= 0.12.0"
backend "s3" {
bucket = "111111111111-tfstate"
key = "terraform.tfstate"
region = "sa-east-1"
}
}
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.9.0"
}
}
}
provider "aws" {
region = var.region
}
If everything is configured correctly, you should be able to run the init command without no problem:
terraform init
The init command is required by Terraform to initialize all the modules required for your solution. You need to execute it the first time you copy the code in your solution or if, for some reason, you change some modules.
One of the purposes of this template is to provide similar infrastructure to multiple environments. It can be handled in several ways; the approach that we suggest is using Terraform workspaces. Using workspaces, you have the possibility of storing multiple state files in the same backend (in that case, the created S3 bucket). The idea is to create a workspace per environment and select it before executing any command related to it.
terraform workspace new <environment>
terraform workspace select <environment>
The workspaces are also stored in the S3 bucket, meaning that everyone in the project with access to it will have access, allowing your team to collaborate. The command to list all the environments is:
terraform worskpace list
For you to create and manage the infrastructure for a specific environment, you need to set the variables for it. You must create a specific file for each environment where you will set these values and overwrite the default ones. The structure of the project suggests keeping these files in the environments
directory. There’s an example file (example.tfvars
) that you can copy, rename, and edit for each environment. Be sure to review the variables.tf
file to see if you’d like to override any other defaults.
Some considerations that you need to keep in mind when you are configuring these values:
create_prefix_for_resources
that appends the prefix '{var.project}_{var.env}'
before each resource name. It’s also useful to identify the environment (or even project) that each resource belongs. Our suggestion is to keep this variable set in True.key_name
that needs to have the RSA key name created before. You can share the same key between multiple environments, but we suggest creating a specific one per environment to reduce vulnerabilities.amiid
that can be a little bit difficult to understand if you don’t have experience working with AWS. An Amazon Machine Image (AMI) is a supported and maintained image provided by AWS that provides the information required to launch an instance (definition extracted from the official documentation). The available AMI ids vary based on the region that you’re using. If you have doubts about which AMI to use, you can take a look at this link.Store a new secret
button.username
: the username to connect to the RDS DB.password
: the password to connect to the RDS DB. Be sure to use a secure password.<project>/<environment>/db-credentials
Now that everything is configured, you can check the “plan” that Terraform will apply. In the plan, you’ll see all the resources that will be created, modified, and destroyed in your AWS account. If it’s the first deployment of the infra, you’ll only see new resources, and maybe this command won’t look useful for you. However, when you are editing existing infrastructure, it will help you to review the changes before applying them. The command to display the plan is:
terraform plan --var-file=environments/<environment>.tfvars
Make sure you’re pointing to the correct environment.
Once you have checked that everything fits your needs, you are ready to apply the plan and impact those changes in the AWS account. That can be easily done with the command:
terraform apply --var-file=environments/<environment>.tfvars
After executing this command, you can go to the AWS console dashboard and see how all the resources were created.
If for some reason, you want to destroy the created environment, you can do it by executing the command.
terraform destroy --var-file=environments/<environment>.tfvars
Some considerations that you need to take care of:
After following this tutorial, you’ll have all the resources required to deploy your basic app in AWS without needing complex manual configuration in the AWS dashboard. You’ll have the possibility of accessing the EC2 instance with SSH and deploying your code there. Suppose you need to modify some environment attribute (for example, increase the storage of the disc attached to the EC2 instance). In that case, you only have to edit the corresponding variable in the var file and run the plan and apply commands again. In that case, you will see only the required changes in the terminal. You can also replicate an environment if required to create a new workspace and a var file.
If you learn a bit more about terraform, you can also edit the current modules, create new ones to create resources that are not covered by this basic module or even create more complex solutions (such as ECS clusters).
We hope this blog and the code in the repository help you easily create AWS infrastructure and avoid some of the common issues that our team has faced in the past. Any contribution to the repository is welcome.