DevOps Playground: Hands-on with Terraform Cloud
Creating and connecting prerequisite accounts
1) Have a GitHub account
If you don't have a GitHub account already, head over to https://github.com/ and create one for free!
2) Have a Terraform Cloud account
If you don't have a Terraform Cloud account already, head over to https://app.terraform.io/ and create one for free!
You will need to create an Organization within Terraform Cloud, but this can be a personal one (e.g. same name as your username, if available).
3) Connect Terraform Cloud to your GitHub account
Usually right after you create your Organization in Terraform Cloud, Terraform will redirect you to the page to Create a new Workspace.
If this is not the case, select your Organization from the top-left drop-down menu, then click the button + New workspace in the top-right corner.
Under Connect to a version control provider, select GitHub ˅, then click on GitHub.com.
Follow instructions in the pop-up to install Terraform Cloud in your GitHub account, and grant it access to your GitHub account.
(If you are not happy to grant Terraform Cloud access to all of your GitHub repos, you can come back to this step after you have forked the sample Terraform project repo, and grant Terraform Cloud access to that repo only.)
Setting up and Configuring our Pipeline
1) GitHub: Clone provided repository
In this section, you are going to create new repository which contains the (pre-written) Terraform code to provision our web app.
The easiest way to achieve this is to fork the following GitHub repository onto your GitHub account:
-
Navigate to the existing repository, if necessary: https://github.com/DevOpsPlayground/Hands-on-with-Terraform-Cloud.
-
Click on the Fork button (top right). (You may need to then select your own GitHub username.)
You now have your own copy of our sample Terraform project in your GitHub account. This currently has only one branch, master
. We will set up more branches soon.
2) Terraform Cloud: Create New Workspace (prod)
We are going to start by creating a production workspace, which is going to deploy our production infrastructure and webapp.
This can be thought of as the 'live' environment, or the one your end-customers will see (as opposed to ones used for testing and QA).
In our case, the production environment corresponds to the 'master' branch.
- Click on New Workspace in your Organization
ℹ️ If you haven't connected Terraform Cloud to your GitHub account yet, you will need to do so now.
-
Select GitHub as your version control provider
-
Select the repository you forked (probably
Hands-on-with-Terraform-Cloud
).
(You may need to refresh this page, if your repo does not show up.)
(You may need to select your own GitHub username in the drop-down list first.) -
Name the Workspace
2048-prod
. -
Click Create workspace.
3) Terraform Cloud: Configure your Workspace (prod)
-
Wait for the Terraform configuration to be loaded from GitHub.
(This may be instant, or take a few seconds.) -
Click the purple Configure variables button, or the link Variables in the navigation bar, top-right.
-
Set the following Terraform Variables:
Type | Key | Value | HCL? | Sensitive? | Description |
---|---|---|---|---|---|
Terraform Variable | animal |
The animal you have been assigned for this DevOps Playground. | No | No | (optional) |
Terraform Variable | env |
prod |
No | No | (optional) |
ℹ️ For each variable above, you need to click + Add variable, enter the name of the variable as the Key and its value as the Value, then click the purple Save Variable button.
- Set the following Environment Variables:
Type | Key | Value | Sensitive? | Description |
---|---|---|---|---|
Environment Variable | AWS_ACCESS_KEY_ID |
Your AWS Access Key ID. | Yes | (optional) |
Environment Variable | AWS_SECRET_ACCESS_KEY |
Your AWS Secret Access Key. | Yes | (optional) |
ℹ️ For each variable above, you need to click + Add variable, enter the name of the variable as the Key and its value as the Value, then click the purple Save Variable button.
-
In the navigation bar for your workspace, top-right, click Settings ˅ (next to Runs, States, Variables), then General.
-
As Apply Method, set Auto apply.
-
Click the purple Save setting button at the bottom of the page.
4) Terraform Cloud: Deploying your project (prod)
-
In the top-right corner, click Queue plan.
-
As your reason for queueing plan, enter
Initial deployment
(this is optional). -
Click the purple Queue plan button.
You can now see Terraform Cloud run a terraform plan
, where it reads our project configuration from GitHub and the variables we have set, maps out its dependency tree, and installs the necessary providers and modules.
After this is done, it displays a list of resources it plans on creating, and should end its output with the following summary:
Plan: 3 to add, 0 to change, 0 to destroy.
After Terraform Cloud is done with the Plan phase, it automatically runs a terraform apply
, where it takes the plan generated above, and makes all the API calls (to the AWS API) which effect our resources being created.
Note that, had we not selected 'Auto apply' in step 3) 6. above, Terraform Cloud would have asked us to manually confirm the Plan before proceeding to the Apply phase.)
At the end of the Apply phase, Terraform prints any Outputs defined in our project. Here we have one output, url
, the URL under which we can reach our webapp.
You can copy-paste the URL into a new tab in your browser and see the webapp in action!
(You can expand and collapse the different phases in this view. You may need to expand the Apply phase by clicking on Apply finished.)
ℹ️ You may also need to wait up to a minute for the DNS record to become available after your apply finished.
5) GitHub: Create a staging branch
Go back to GitHub.com and go to the repository you have forked before.
-
In your forked GitHub repo, click the grey Branch: master button (left, above the list of files).
-
Enter
staging
. -
Click Create branch: staging from 'master'
You now have a branch called 'staging'. This is commonly used to host code we consider ready to be deployed, but haven't tested to the desired level of confidence yet.
In the next few steps, we will set up a staging environment, which we can use to test the code in our staging branch.
6) Terraform Cloud: Create New Workspace (staging)
A different environment will typically correspond to a new Workspace in Terraform Cloud.
-
Click on New Workspace in your Organization
-
Select GitHub as your version control provider
-
Select the repository you forked (probably
Hands-on-with-Terraform-Cloud
).
(You may need to select your own GitHub username in the drop-down list first.) -
Name the Workspace
2048-staging
. -
Expand the ˅ Advanced options menu.
⚠️ This is an extra step, compared to the 'prod' workspace above. -
Set VCS branch to
staging
. This is the branch we created under step 5) above.
⚠️ This is an extra step, compared to the 'prod' workspace above. -
Click Create workspace.
7) Terraform Cloud: Configure your Workspace (staging)
-
Wait for the Terraform configuration to be loaded from GitHub.
(This may be instant, or take a few seconds.) -
Click the purple Configure variables button, or the link Variables in the navigation bar, top-right.
-
Set the following Terraform Variables:
Type | Key | Value | HCL? | Sensitive? | Description |
---|---|---|---|---|---|
Terraform Variable | animal |
The animal you have been assigned for this DevOps Playground. | No | No | (optional) |
Terraform Variable | env |
staging |
No | No | (optional) |
ℹ️ For each variable above, you need to click + Add variable, enter the name of the variable as the Key and its value as the Value, then click the purple Save Variable button.
- Set the following Environment Variables:
Type | Key | Value | Sensitive? | Description |
---|---|---|---|---|
Environment Variable | AWS_ACCESS_KEY_ID |
Your AWS Access Key ID. | Yes | (optional) |
Environment Variable | AWS_SECRET_ACCESS_KEY |
Your AWS Secret Access Key. | Yes | (optional) |
ℹ️ For each variable above, you need to click + Add variable, enter the name of the variable as the Key and its value as the Value, then click the purple Save Variable button.
-
In the navigation bar, top-right, click Settings ˅, then General.
-
As Apply Method, set Auto apply.
-
Click the purple Save setting button at the bottom of the page.
8) Terraform Cloud: Deploying your project (staging)
-
In the top-right corner, click Queue plan.
-
As your reason for queueing plan, enter
Initial deployment
(this is optional). -
Click the purple Queue plan button.
Same as for production above, Terraform will output the URL under which we can reach our webapp.
📝 Make a note of this, as we will need it for step 9) below.
9) GitHub: Create a dev branch
-
In your forked GitHub repo, click the grey Branch: staging button (left, above the list of files).
-
Enter
dev
. -
Click Create branch: dev from 'staging'
You now have a branch called 'dev'. This is commonly used to host code that is not yet ready to be deployed, but is using by developers to work on new features or fixes.
Though one might want to depending on the situation, we will not set up a staging environment.
10) GitHub: Set up a GitHub Action
-
In your forked GitHub repo, click the grey Branch: dev button (left, above the list of files).
-
Click master to switch to the master branch.
-
Click the grey Create new file button, to the right.
-
Type
.github/workflows/prod-PR.yml
where it says 'Name your file…'.
Note how the text field changes to display the path whenever you enter a slash character. -
Enter the following as the file content, replacing
<staging url>
with the URL for your staging environment (cf. step 8)):
name: prod-PR
on:
pull_request:
branches:
- master
jobs:
staging-healthcheck:
runs-on: ubuntu-latest
steps:
- name: Healthcheck the staging endpoint
run: curl <staging url>
<staging url>
with the actual URL for your staging environment!
- Click the green Commit new file button.
Leave 'Commit directly to themaster
branch' selected.
(It is generally not best practice to commit directly to master, but acceptable in an initial setup phase such as this. We could now protect certain branches, such as staging and master, so that users cannot commit/push directly to those branches, but have to create a pull request instead.)
We have now set up a GitHub Action which will be triggered whenever a pull request (PR) into the master branch is opened.
The only job defined, 'staging-healthcheck', will simply check if the staging environment is reachable. If it is not, that means that the production environment likely won't be reachable either once this new code is deployed, and thus GitHub will veto* this code merge.
(* Note: in our case, GitHub will alert us that some checks haven't passed, but will still allow us to merge the pull request. Checks can be configured to be mandatory in GitHub, under Settings -> Branches -> Branch protection rules. However, they need to have been triggered at least once within the last week in order to be configurable, which is why we cannot yet do this at this stage.)
Testing our Deployment Pipeline
1) Syntax error
In this section, we will introduce a syntax error into our Terraform project (on the 'dev' branch), and try to merge this into Staging (via a 'Pull Request').
Terraform Cloud should automatically check if this new code could be deployed to the staging environment, and let GitHub know if there is an issue. Since we're introducing a syntax error, we expect there to be an issue, and this Pull Request to be "unable to merge".
- In GitHub, open your forked repository, and make sure you're on the 'dev' branch.
- If you're not on the 'dev' branch already, click the grey Branch: master ˅ or Branch: staging ˅ button, then select dev.
-
Click on
security_groups.tf
. -
Click the ✏️ pencil icon, to the right of the buttons labelled Raw, Blame, History.
-
At the end of the file, add a line that says:
kill_all_humans()
This directive is not yet supported in Terraform v0.12, and thus will cause an error during the terraform plan
stage.
-
Click the green Commit changes button at the bottom of the page.
-
Click on Pull requests in the navigation bar at the top.
-
Click either the green Compare & pull request button or the green New pull request button.
-
Set:
- base repository: (if shown) to your (forked) repo (not
DevOpsPlayground/Hands-on-with-Terraform-Cloud
) - head repository: (if shown) to your (forked) repo (not
DevOpsPlayground/Hands-on-with-Terraform-Cloud
) - base: to
staging
- compare: to
dev
.
- base repository: (if shown) to your (forked) repo (not
-
(Optional) Give your PR a name, and add a description.
-
Click the green Create pull request button.
-
Click the green Create pull request button again, if necessary.
Now that we have a Pull Request, Terraform Cloud will jump into action and run a terraform plan
to see if the new code could be deployed.
This is known as a "speculative plan", and cannot be found through the Terraform Cloud UI, but only by following the link in GitHub. (This often leads to confusion among new users.)
In the "Conversation thread" on the Pull Request page, GitHub will say Checking for ability to merge automatically while Terraform Cloud is running the Plan. After the Plan has failed, this message will change to All checks have failed.
We can see details about the checks below this message. (Note that "atlas" is the legacy name of Terraform Cloud.)
To the right of the line that says "Terraform plan errored", you can click on Details to see the speculative plan.
2) Run-time error
In this section, we will introduce an error which does not violate the Terraform syntax, but which will cause issues at run-time (i.e. once the code has been deployed).
Terraform Cloud will again automatically check if this new code could be deployed to the staging environment, and let GitHub know if there is an issue. With this error, Terraform Cloud cannot know that there is a problem, therefore merging into the staging branch will be possible.
However, the GitHub Action we have set up will be able to detect that the staging environment is not reachable, and therefore will veto merging into the master branch later.
- In GitHub, open your forked repository, and make sure you're on the 'dev' branch.
- If you're not on the 'dev' branch already, click the grey Branch: master ˅ or Branch: staging ˅ button, then select dev.
-
Click on
security_groups.tf
. -
Click the ✏️ pencil icon, to the right of the buttons labelled Raw, Blame, History.
-
Remove the line at the end of the file that says:
kill_all_humans()
.
This was clearly not the way to go about that. -
In the second
ingress {
block, change thefrom_port
andto_port
directives to a different number, e.g.
from_port = 20480
to_port = 20480
-
Click the green Commit changes button at the bottom of the page.
-
Click on Pull requests in the navigation bar at the top.
-
Click on the existing Pull Request.
You can see that, again Terraform Cloud is running a speculative plan using the code we are attempting to merge into staging. This time the plan should succeed (click Show all checks and/or Details to see the speculative plan) and we can continue to merge our PR (pull request).
(You may need to refresh the page if the check does not appear / change automatically.)
- Click on Merge pull request, then Confirm merge.
You should see a message that reads "Pull request successfully merged and closed".
If you navigate to the 2048-staging Workspace in Terraform Cloud, you should see a run with name Merge pull request #….
Terraform Cloud automatically started deploying the new code once the Pull Request was merged. This is a very powerful functionality to keep our code base and our environments in sync.
-
Back on GitHub, Click on Pull requests in the navigation bar at the top.
-
Click either the green Compare & pull request button or the green New pull request button.
-
Set:
- base repository: (if shown) to your (forked) repo (not
DevOpsPlayground/Hands-on-with-Terraform-Cloud
) - head repository: (if shown) to your (forked) repo (not
DevOpsPlayground/Hands-on-with-Terraform-Cloud
) - base: to
master
- compare: to
staging
.
- base repository: (if shown) to your (forked) repo (not
-
(Optional) Give your PR a name, and add a description.
-
Click the green Create pull request button.
-
Click the green Create pull request button again, if necessary.
ℹ️ Observe the section about checks. You should see a Terraform Cloud check running on GitHub (as atlas/<your-tfc-workspace>/2048-prod
), as well as the GitHub Action we set up before (as prod-PR / staging-healthcheck (pull_request)
).
(You may need to refresh your page occasionally to see the checks update.)
GitHub should now (after all checks have finished) display the message: Some checks were not successful. Click on Show all checks (if shown) for more information.
The culprit seems to be prod-PR / staging-healthcheck (pull_request), and you can click on Details to see more.
You can explore the different automated checks on your own.
- When you're done exploring, please click the grey Close pull request button at the bottom of the page.
3) Successful deploy
In this section, we will introduce an update to our application which does not cause an error.
Terraform Cloud will again automatically check if this new code could be deployed to the staging environment (it can!), and the GitHub Action we have set up will verify that the staging environment is reachable (once we raise a PR into the master branch).
We will then manually check our staging environment, before merging the tested code from staging (staging
branch) into production (master
branch).
This manual check might be carried out by a team-member with a QA role, who might then approve the PR, before it is merged. The number of 'human' approvals required for a merge can also be specified in GitHub.
- In GitHub, open your forked repository, and make sure you're on the 'dev' branch.
- If you're not on the 'dev' branch already, click the grey Branch: master ˅ or Branch: staging ˅ button, then select dev.
-
Click on
security_groups.tf
. -
Click the ✏️ pencil icon, to the right of the buttons labelled Raw, Blame, History.
-
In the second
ingress {
block, change thefrom_port
andto_port
directives back to2048
, i.e.
from_port = 2048
to_port = 2048
-
Click the green Commit changes button at the bottom of the page.
-
Click on the name of your repo (between Branch: dev ˅ and security_groups.tf, near the top).
- Again, make sure you are still on the 'dev' branch.
-
Click on
user-data.sh.tpl
. -
Click the ✏️ pencil icon, to the right of the buttons labelled Raw, Blame, History.
-
Change the Docker image in the last line to
jeffhemmen/2048-pokemon
, so that the last line reads:
docker run -d -p 2048:80 jeffhemmen/2048-pokemon
-
Click the green Commit changes button at the bottom of the page.
-
Click on Pull requests in the navigation bar at the top.
-
Click either the green Compare & pull request button or the green New pull request button.
-
Set:
- base repository: (if shown) to your (forked) repo (not
DevOpsPlayground/Hands-on-with-Terraform-Cloud
) - head repository: (if shown) to your (forked) repo (not
DevOpsPlayground/Hands-on-with-Terraform-Cloud
) - base: to
staging
- compare: to
dev
.
- base repository: (if shown) to your (forked) repo (not
-
(Optional) Give your PR a name, and add a description.
-
Click the green Create pull request button.
-
Click the green Create pull request button again, if necessary.
-
After all checks have passed, click the green Merge pull request button, then Confirm merge.
You should see a message that reads "Pull request successfully merged and closed".
(If you navigate to the 2048-staging Workspace in Terraform Cloud, you should see a run with name Merge pull request #....
Again, Terraform Cloud automatically started deploying the new code once the Pull Request was merged.)
ℹ️ This is the stage at which manual Quality Assurance might happen, before a release is merged into 'master' and thus Production. Navigate to the URL of your staging environment and assure yourself that the webapp looks fine. (Again, it may take a few minutes until Terraform Cloud has deployed the new version, and updated DNS records have propagated.)
-
Click on Pull requests in the navigation bar at the top.
-
Click either the green Compare & pull request button or the green New pull request button.
-
Set:
- base repository: (if shown) to your (forked) repo (not
DevOpsPlayground/Hands-on-with-Terraform-Cloud
) - head repository: (if shown) to your (forked) repo (not
DevOpsPlayground/Hands-on-with-Terraform-Cloud
) - base: to
master
- compare: to
staging
.
- base repository: (if shown) to your (forked) repo (not
-
(Optional) Give your PR a name, and add a description.
-
Click the green Create pull request button.
-
Click the green Create pull request button again, if necessary.
Both the Terraform Cloud check and the GitHub Action should now run, and return successfully.
- After this, you can click the green Merge pull request button, then Confirm merge, in order to merge your changes into production with a high level of confidence.
If you navigate to the 2048-prod Workspace in Terraform Cloud, you can see the latest version of our app being deployed to production. Once this is done, you can navigate to the URL output at the end of the Apply in order to see the live app. (Again, DNS propagation might take a minute.)
Thank you!
You have now reached the hands-on part of this DevOps Playground.
I hope you enjoyed it and learned something new!
Please don't hesitate to get in touch if you have any feedback or questions about the Playground, or for any other business relating to ECS Digital (it's a really cool place to work!). 🙂
Thanks for coming,
–Jeff Hemmen <jeff.hemmen@ecs-digital.co.uk>
Acknowledgements and Licenses:
I would like to thank the amazing team at ECS Digital for organising all the (boring, to me 😛) stuff around the DPG and allowing me to focus 100% on the tech that I am passionate about!
Also thank you very much to everyone for attending—it would have been rather dull without all of you!
Please help yourselves to pizza 🍕+ 🍺 beer in a minute!
The applications deployed in this DevOps Playground are / are based on existing Docker images and GitHub repositories.
They are:
I would like to express my thanks to the creators / maintainers of the above.