In this tutorial, we are going to connect Terraform Cloud with a GitHub repository and leverage GitHub actions to provision Cloud infrastructure on AWS. We are going to combine IAC and GitOps approaches and manage our cloud resources via code.
Infrastructure as code(IAC)
The modern way to manage Cloud Infrastructure is to treat it like software. The number of resources that we have to handle is increasing daily along with their elasticity. The scale of the infrastructure is getting bigger and bigger every day and too difficult for people to manage with traditional ways.
Even more, the API-driven cloud environments provide many more options for automation than before. Managing our Infra as Code allows us to benefit from versioning
, consistency
, repeatability
, collaboration
, traceability
while configuring complete solutions in environments that are changing rapidly every day.
GitOps
GitOps is the operational framework that allows us to take the best practices used for application development to infrastructure automation. By combining our Infa as code and GitOps approaches we can now safely operate on our infrastructure by leveraging the same CI/CD tools and automated test pipelines and deployments that we use already.
GitOps provides us the ability and framework to automate our infra provisioning. In practice GitOps
is achieved by combining IAC
, Git repositories
, MR/PR
, and CI/CD
pipelines. First, we define our infra resources as code.
Then, we commit these in Git repositories
that we handle as the source of truth
for our cloud environments. When we need to add or perform an update on our environment, we have to modify our code and create a Merge/Pull Request to let our colleagues review our changes.
After validating our changes we merge to our main branch and let our CI/CD tools do their magic, apply our changes to our infrastructure environments.
Terraform Cloud
Terraform Cloud is a cloud-based platform that makes it easier for teams to work with Terraform. Provides an environment for us to run our infrastructure operations and keeps the shared state in the cloud. Allows us to set environment variables and secrets and integrates nicely with most version control and continuous integration systems.
GitHub Actions
GitHub Actions is an API on GitHub that allows us to orchestrate workflows based on different events that happen right in our GitHub repositories. With GitHub Actions, we can automate and customize our pipelines while GitHub is responsible for their execution. The added value is that our CI/CD and other workflows are now part of our repositories.
Terrafom Cloud + GitHub Actions Setup
In this article, we are going to connect Terrafom Cloud & GitHub Actions together to create our infrastructure management workflows. To follow along you’ll need:
- A GitHub account(https://github.com/join)
- A Terraform Cloud account
- An AWS account
Let’s get to it!
First step set up Terraform Cloud. We need to create a workspace for our infrastructure and connect it with our AWS account, via providing our AWS access credentials. In my example, I created a new workspace for an imaginary organization CourageAI named CourageAI-Infrastructure
. Select API-driven workflow
and create your workspace.
Next, we need to add our AWS access credentials, AWS_ACCESS_KEY_ID
AND AWS_SECRET_ACCESS_KEY
as environment variables in the configuration of the workspace. If you don’t have these, you can create them by following this guide.
Select the Variables
option and set your env vars there, select also the option Sensitive
.
Then navigate to User Settings
on the top right for your user account. Select Tokens
and Create an API token
that we are going to use in our GitHub Actions workflow. Let’s name it GitHub Actions
.
Moving ahead we have to set up a new repository that will hold our infrastructure status as Terraform files and our GitHub Action workflow that will apply our cloud resources in AWS. You can find the repository I created for this demo here.
Before proceeding, we have to add our Terraform Token to our repository. Click Settings
, then Secrets
, and create a new Repository Secret
. Name it as you like, TF_API_TOKEN
in our case, and insert your Terraform Token from the previous step.
Perfect, we are almost ready to try this out.
Let’s have a look at our only & simple main.tf
Terraform file that declares a new EC2 instance
on AWS and our terraform.yaml
GitHub Actions file that declares the workflow to actually apply our infra to AWS.
Our main.tf
file is rather simple. For this demo, we are going to create a tiny EC2 instance to validate that we can trigger this action by just committing and pushing our code to Github.
As you see we define a remote backend
for Terraform and we add our organization and workspace names. Then we just creating a t2.nano EC2 instance with an ubuntu AMI.
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
}
}
backend "remote" {
organization = "CourageAI"
workspaces {
name = "CourageAI-Infrastructure"
}
}
}
provider "aws" {
region = "us-west-2"
}
resource "aws_instance" "server-1" {
ami = "ami-830c94e3"
instance_type = "t2.nano"
tags = {
Name = "server"
}
}
The magic happens in our GitHub Actions workflow file. We trigger this on push to master
branch events or on pull requests
, and the idea is that whenever we have to spawn any new resource in AWS we prepare our Terraform files, we open a new PR targeting the master
branch. After peer reviews and when we are ready to launch the resources, we merge our PR and trigger the workflow.
name: "Terraform"
on:
push:
branches:
- master
pull_request:
jobs:
terraform:
name: "Terraform"
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup Terraform
uses: hashicorp/setup-terraform@v1
with:
# terraform_version: 0.13.0:
cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }}
- name: Terraform Format
id: fmt
run: terraform fmt -check
- name: Terraform Init
id: init
run: terraform init
- name: Terraform Plan
id: plan
if: github.event_name == 'pull_request'
run: terraform plan -no-color
continue-on-error: true
- uses: actions/github-script@0.9.0
if: github.event_name == 'pull_request'
env:
PLAN: "terraform\n${{ steps.plan.outputs.stdout }}"
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const output = `#### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\`
#### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\`
#### Terraform Plan 📖\`${{ steps.plan.outcome }}\`
<details><summary>Show Plan</summary>
\`\`\`${process.env.PLAN}\`\`\`
</details>
*Pusher: @${{ github.actor }}, Action: \`${{ github.event_name }}\`*`;
github.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: output
})
- name: Terraform Plan Status
if: steps.plan.outcome == 'failure'
run: exit 1
- name: Terraform Apply
if: github.ref == 'refs/heads/master' && github.event_name == 'push'
run: terraform apply -auto-approve
Basically, we set up terraform-cli using our TF_API_TOKEN, format our files, init terraform, execute the plan and finally apply the changes.
If the event triggered the action is a PR, we output information regarding the terraform plan that will help our engineers validate indeed that the plan is correct and will generate the required resources. If the event is merge to master
we apply the Terraform configuration.
Time to see it in action! In our repo we’ll create a new branch and make a small change to main.tf
file, for example, change the tag of the instance:
tags = {
Name = "server-1"
}
git checkout -b update-tf
git status
git add main.tf
git commit -m "Modify ec2 instance details"
git push origin update-tf
Alright, back to our GitHub repository we open a new PR with our newly pushed branch.
And shortly, we see that our GitHub Action is triggered!
As discussed earlier we can also view the whole Terraform plan before merging to validate that Terraform will apply the required resources.
Our AWS instance will be created, exactly what we need. Time to merge and finally see this happening.
Merge and then navigate to Actions
tab and check the workflow in action.
After a while, we validate that our GitHub Action was completed successfully and the logs for the terraform apply
step display that is complete.
Head to AWS console and navigate to EC2 Dashboard
on us-west-2
region. You should now see our instance up and running.
We are now ready to trigger any AWS infrastructure-related change via GitHub, how awesome is that?
If you followed along, one last step to actually terminate our demo instance. Head to Terraform Cloud console to our workspace and select Settings
and then Destruction and Deletion
. This destroys all the resources in the workspace, in our case our only EC2 instance. Click Queue destroy plan
and on the next page Confirm & Apply
. After a while, you’ll see your instance in terminated
status.
That’s all folks, hope you enjoyed this demo. We applied the basic GitOps
and Infrastructure as Code
approaches with Terraform
, Terraform Cloud
, GitHub
& Github Actions
to set up quickly a workflow that handles our AWS cloud resources via a GitHub repository.