Managing Infrastructure with Ease

Yifan Fu
CreditorWatch
Published in
9 min readSep 13, 2021
Photo by Kirill Sh on Unsplash

Infrastructure in the old days

Infrastructure management was not an easy job to do in the old days. Ops engineers usually need to predict the workload of the production environment and plan ahead for the next few years as the infrastructure changes are usually difficult to implement. In order to add a new machine, they will need to raise the request to their manager and buy the hardware, assemble them, install all the operating system and software, and then finally add to the cluster. Guess what, that’s just the very first part of the infrastructure management, you could face power outage, hardware failures, misconfiguration, etc. To solve this problem, Infrastructure as Code (IaC) comes into place.

Infrastructure as Code allows you to provision your infrastructures using code either in an imperative way or a declarative way, instead of some manual work. In this article, we are going to discuss 3 topics:

  1. Terraform, https://www.terraform.io/
  2. 3 Musketeers, https://3musketeers.io/
  3. GitHub Actions, https://github.com/features/actions

With all three, we are going to manage our infrastructure on the cloud nice and easily. We are going to deploy our infrastructure on AWS as an example but since terraform supports a large number of providers, you will be able to deploy your infrastructure to almost any cloud provider includes but not limited to AWS, Azure, Google Cloud Platform, etc.

Terraform

In the DevOps world, we have different IaC tools for different purposes. Firstly we have the provisioning tools like Terraform and CloudFormation to allow you to deploy infrastructure on your cloud provider. Secondly, the configuration tools like Ansible and Puppet help you managing the software installed on your machine. Thirdly the Docker or Vagrant are server templating tools that acting as an abstract layer to run your application in a separated environment.

Imperative language just like developers, need to know what to be developed, what to be tested, and what to be deployed, while declarative language is more like a product owner, they don’t really care what programing language you are using, how do you make that particular feature working, why you add !important everywhere (just kidding). What declarative language really care about is the final state, and terraform is a declarative language. In the terraform, you only describe the desired final state you need, and let terraform figure out what needs to be deployed, what needs to be removed. So DevOps just acting like a product owner in the terraform world, you tell what needs to be done and terraform is a good friend to help you provision all the resources you’d like to have on your cloud.

Terraform is using HCL which stands for HashiCorp Configuration Language. It’s a simple, nice language that declares resources in blocks. Here is the format:

<block_type> <resource_type> <resource_name> {
<arguments>
}

Let’s have a real example, shall we?

resource "random_uuid" "yifan_random" {
}

resource "aws_s3_bucket" "yifan_bucket" {
bucket = "yifan-bucket-${random_uuid.yifan_random.result}"
acl = "private"

depends_on = [
random_uuid.yifan_random
]

tags = {
Name = "yifan-bucket-${random_uuid.yifan_random.result}"
Environment = var.Environment
}
}

In the example above, we have defined two blocks, both of which are resources. In the first block, we defined a random_uuid resource and give it a name as yifan_random. In the second block, we defined an aws_s3_bucket resource that will help you to create an s3 bucket on AWS. Between the curly brackets are the arguments you are going to pass into the resource. In this example, I’m using the reference ID of random UUID as the bucket name.

By default, Terraform will store all the states file locally, which is a good idea if you are managing your terraform stack by yourself. We will use s3 in our example so every DevOps engineer can contribute their code to our infrastructure repository and deploy without any problem.

terraform {
backend "s3" {
bucket = "yifanfu"
key = "tfstate"
}
}

In the code block above, we will use the s3 bucket named yifanfu as the location of our state file stored. You have more options to store your file, please check the official documentation for more options.

Terraform provides a great registry (https://registry.terraform.io/) that allows you to use predefined resources and modules. Again we use AWS as an example. AWS is one of the providers in terraform registry. In the AWS provider, you can find the documentation, which listed all the resources that this provider provides you.

Resources that AWS provider provides

In each of the resources, you can find the list of supported arguments you can pass in and the list of attributes that will be exported and reuse in other resources.

Terraform module is a collection of resources that are ready to use. You can find lots of modules provided by official providers or even third parties. You can but you don’t have to just use modules from the official terraform registry. There are a couple of different ways you can use your module. You can include a module that:

  1. is in a local directory
  2. in the terraform registry
  3. in a GitHub repository
  4. in a BitBucket repository
  5. in a generic git repository that you are accessible
  6. in an HTTP URL
  7. in an S3 bucket
  8. and in a GCS bucket

Terraform gives your flexibility to reuse your existing code across all your projects, and even with other developers.

There are a few terraform common commands that you’ll need in your daily works.

  1. terraform init is command is letting terraform to analyse your code and download the required resource to your local
  2. terraform plan is a command that dry run your IaC code and tell you what will be added, what will be removed before actually apply to the real environment
  3. terraform apply is a command that does the job, it will either create or remove resource on your cloud platform to ensure the status of your cloud is aligned with the resources you declared in your terraform code.
  4. terraform show this shows the resource you have been created in your cloud provider.
  5. terraform destroy is very self-explanatory, which will remove the entire stack on your cloud provider.

You can always find more useful commands on the official documentation.

3 Musketeers

3 Musketeers is the way to allow your infrastructure to be deployed via different platforms. There are 3 musketeers:

  1. Docker
  2. Compose
  3. Make

With these 3 musketeers, you would be able to run your code on almost all the different systems/CICD pipelines. The idea of 3 musketeers is to reduce the dependency on the host you are going to run your code. All the jobs will be packed into the Makefile and all the dependencies will be pre-installed in the docker image you are going to use.

Let’s have a real example that use 3 musketeers way to deploy our resources on AWS using Terraform.

Terraform101 is a simple repository that demonstrates the process of how we deploying terraform code to our cloud platform (in this case we are going to use AWS again). As you can see, we have the Makefile that acts as a recipe.

In this Makefile we have listed 5 main steps we are going to use for deploying our infrastructure. If we look at each command, we are not actually running terraform commands on our host, in fact, we do not even download any terraform binary into our host. We are just using a docker-compose command to run the terraform command within a docker container.

Let’s see what we have in that docker-compose.yaml file.

In that file, we are using terraform official docker image to run our terraform command. The official terraform docker image can be found here:

We are doing a volume mount, and passed in the environment variables we required to the container too. I’ll cover the environment variables in the next section.

GitHub Actions

GitHub Actions is a CI/CD pipeline that allows you to do some post jobs after you make some changes to your master branch (this is just an example, you can define more actions in the workflow file).

In the example repository above, we have defined two workflows where one is for up and one is for down. The up workflow allows you to deploy your infrastructure on AWS and the down workflow allows you to destroy your resources on AWS. Let’s how they work.

Firstly, we give it a name, let’s say up , and then we give the environment variables. We always wanted to keep our secrets secret, so we should never expose them in a repo, especially a public repository.

Within GitHub, we can easily store our secrets under repository settings, the secrets shown below.

Secrets

Once we store our secrets here, we won’t need to hard code them into the codebase, and we can use those secrets in our codebase in the following format.

env:  AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }}  AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}  AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

Let’s get back to the workflow. After the environment section, we have the event trigger section which we can define when is the workflow need to be triggered. In my case, I need this up workflow to be triggered once we merge PR to master, or we push code directly to master (it’s ok to do here as this is just an example repository, but you should never do this if you are working in a production project.)

on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

Then we defined the jobs in the workflow. We have only a single job in this workflow, but we have multiple steps in this job. In order to run the job, we need to tell GitHub what environment do we need our code runs on. I chose ubuntu-latest as it has Makefile, Docker, docker-compose already built-in. Here is the complete list of the machines you can choose from:

GitHub Actions runs on machines

The steps are pretty standard, just init, plan, apply, and output. Thanks to the 3 Musketeers, we can run the same code across pretty much all the environments.

Here is an example of a workflow that had been executed.

You can also expand each section to see the output of each step.

Since this is just a demo repository I don’t want to keep it running on my personal AWS account, we can manually run the down workflow to remove all the resources from our cloud platform.

For checking all the code, please click the link to this public repository on GitHub.

Hope you enjoy the reading and feel free to connect me on Linkedin.

--

--