Chapter 3 IntroduCtIon to terraform
name = "http_sg_ingress"
tags = {
"Environment" = "dev"
}
# (7 unchanged attributes hidden)
}
XXXXXXXXXXXXXXX------SNIPPED-------XXXXXXXXXXXXXXXXXXXXX
Terraform detected that a new rule has been added with port 8080, and
hence, it provided a plan to reconcile those changes. Before we reconcile,
let’s check if the current terraform.tfstate file contains the changes that we’ve made.
CLI Output 3-33. Terraform shows command grepping for
port 8080.
cmd> terraform show | grep 8080
As can be seen, the preceding output is empty, which means that
our current state doesn’t have the new security group whereas terraform
detected some changes that we performed manually from the AWS GUI.
Let’s run terraform apply to reconcile the changes, and if we execute terraform show and grep for port 8080, we can see that the state has been
reconciled.
CLI Output 3-34. Reconciliation with terraform apply
cmd> terraform apply --auto-approve
cmd> terraform show | grep 8080
from_port = 8080
to_port = 8080
cmd> terraform destroy --auto-approve
118
Chapter 3 IntroduCtIon to terraform
Tip driftctl is an excellent tool that detects, tracks, and alerts on
infrastructure drifts ().
AWS Config is also a very good service provided by AWS to monitor,
detect, and alert on cloud infrastructure changes
3.9 Clean-Up
Just to ensure that we don’t end up getting billed unnecessarily, execute
the following commands to confirm that we’ve deleted all the resources.
CLI Output 3-35. Terraform destroy commands for complete
clean-up
cmd> export AWS_PROFILE=gitops
cmd> cd chapter3/infra/iteration1
cmd> terraform destroy --auto-approve
cmd> cd chapter3/infra/iteration2
cmd> terraform destroy --auto-approve
cmd> cd chapter3/infra/iteration3
cmd> terraform destroy --auto-approve
It is also highly recommended that you visit the EC2 console on
the AWS GUI and double-check if everything is deleted. The Instances
(running) field should show 0.
119

Chapter 3 IntroduCtIon to terraform
Figure 3-6. EC2 instance dashboard screen showing zero running
instances
Note always double-check the region; we’ve been following
us- east- 2 in this chapter.
3.10 Terraform Commands Reference
Listing some terraform commands for reference
120
Chapter 3 IntroduCtIon to terraform
Table 3-1. Terraform Commands Reference
Command
Description
terraform init
Initializes the terraform code by downloading the dependencies
terraform plan
Shows the details about the Crud (create, read, update,
and delete) operations that will be performed on the cloud
infrastructure. uses the -out filename.tfplan flag to output the
plan into a file
terraform apply
accepts and applies the changes that were discussed in the
earlier stage that perform all the Crud operations. uses the
--auto-approve flag to automate
terraform destroy destroys all the resources that were defined. uses the --auto-
approve flag to automate
terraform fmt
formats the terraform code by applying the rules to all the .tf
files under the root directory. uses the -recursive flag to apply
to all .tf files in the subdirectories
terraform validate Validates the terraform configuration files
terraform show
Shows the contents of the .tfstate file in a more readable
manner
3.11 Conclusion
In this chapter, we learned how to create an EC2 server hosting an SSH
and an HTTP service on AWS cloud using Terraform and also learned its
nuances in the process. Terraform is a declarative syntax language, and it
is slightly difficult to formulate our ideas especially when we come from
a background coding in Java, Python, etc. Hence, we learned what are the
best ways to organize our program and the different syntaxes like the
121
Chapter 3 IntroduCtIon to terraform
meta- arguments, output variables, modules, and so on. We also learned
how to manipulate the terraform state by selectively destroying resources
and reconcile the state in case of a drift with the actual deployed
configuration.
In the next chapter, we’ll look at how the terraform state can be
managed remotely rather than storing it locally as local creation inhibits
collaboration and is extremely risky.
122
CHAPTER 4
Introduction to
Terraform Cloud and
Workspaces
In the previous chapter, we were introduced to Terraform using a simple
example of spinning up an EC2 machine on AWS. We also learned about
a few best practices of writing a Terraform code through three different
iterations. However, what we learned in the previous chapter was suitable
for individual testing and development as the terraform state that stores
all the information about the cloud infrastructure that has been set up is
stored locally in the developer’s machine.
Terraform state, as we’ll learn in more detail, is an extremely sensitive
and critical file, and it is certainly not advisable to have it stored in the
local machine. Hence, in this chapter, we’ll explore how we can store the
terraform state in Terraform Cloud, which is an offering by the creators
of terraform. In Terraform Cloud, it is more readily accessible, and we
can very well avoid the trauma of accidental deletion/modification so as
to protect the integrity of the whole Infrastructure-as-Code operation.
We’ll also look at how we can deploy the same terraform code in multiple
environments with minimal tweaking.
© Rohit Salecha 2023
123
R. Salecha, Practical GitOps,
Chapter 4 IntroduCtIon to terraform Cloud and WorkspaCes
4.1 Prerequisites
In addition to the previous chapters, the following is the prerequisite for
this chapter:
–
Terraform Cloud account – For remote management of
the terraform state. Please sign up and create a free
account.
–
4.2 Terraform State Management
When you hit terraform apply, terraform creates/updates/deletes your infrastructure in the cloud in conformance with the instructions that are
provided. Once the apply operation is completed, it also creates a file called terraform.tfstate detailing all the information about the resources that currently exist in your infrastructure. A different way to put it,
terraform.tfstate reflects the implementation of the desired state of your cloud infrastructure. It’s a huge JSON file that we’ve seen in the previous
chapters and is one of the most important files in the entire terraform
operation process. This file is always created in the root folder where
terraform apply has been executed.
The file must follow the CIA triad of security, that is, confidentiality,
integrity, and availability.
• Confidentiality – The terraform state file is a clear-
text JSON file and hence should be kept in a location
where access is limited to a few people. Any secret if
embedded in the terraform file will also be shown in
clear text here.
124

Chapter 4 IntroduCtIon to terraform Cloud and WorkspaCes
• Integrity – Don’t ever try to modify the state file by hand
as it contains details about your cloud infrastructure.
Hence, only the terraform binary must be allowed to
edit it.
• Availability – Terraform while executing plan, apply,
and destroy needs to be able to read that file; hence, it
should be made available to the terraform binary.
Terraform binary while operating on the terraform.tfstate file locks it when in use. During this time, it doesn’t allow anyone to view/edit or even
delete the file. This is Terraform’s way to manage the CIA of the tfstate file on your local machine as illustrated in the following diagram.
Figure 4-1. Terraform state when saved locally
125
Chapter 4 IntroduCtIon to terraform Cloud and WorkspaCes
By now, you must’ve realized why we should not operate terraform
from our local machines when spinning up cloud infrastructure especially
for production environments. Let’s elaborate some of the reasons why:
–
Managing the tfstate file locally makes it less collabora-
tive. If you’re working in a team, then you’d have to
manually share the tfstate file, which is not scalable.
–
Storing the state in the local machine can certainly make
it susceptible to accidental deletions and modifications,
dismantling the CIA triad.
–
In case of a multi-environment setup, we need to main-
tain multiple state files locally, which makes it extremely
difficult to manage.
Hence, we need to keep the state file in a location
–
Which is accessible to authorized individuals
–
Where the complete CIA triad is maintained
–
Where locking of the file should be possible to avoid
multiple people pushing different changes
–
Where multi-environment setup would be easy and
maintainable
4.3 Introduction to Terraform Cloud
I personally don’t believe in silver bullet theories, but for the requirements
discussed previously for terraform state management, Terraform Cloud is
indeed a silver bullet addressing them.
126

Chapter 4 IntroduCtIon to terraform Cloud and WorkspaCes
Note prior to 2019, developers would store terraform state in cloud
storage solutions like s3 buckets in aWs, Cloud storage in GCp, and
so on. this practice is still prevalent even today as it helps in solving
the preceding problems; however, we are exploring terraform Cloud
to access various other features like apI automation and VCs linking,
which we’ll explore in the upcoming chapters.
By configuring your local terraform workspace with Terraform Cloud,
all operations like plan, apply, destroy, etc., are now delegated completely
to Terraform Cloud. Your state as well gets stored securely in Terraform
Cloud as illustrated here.
Figure 4-2. Terraform state stored in Terraform Cloud
127

Chapter 4 IntroduCtIon to terraform Cloud and WorkspaCes
In Terraform Cloud, in order to store the state, we need to make
workspaces, which help in logically segregating all aspects of terraform operation. We can define workspaces for specific environments like
staging, prod, etc., and also assign specific configurations for these
environments. Hence, workspaces make it extremely easy to seamlessly
create separate environments by leveraging the same code base.
4.4 Terraform Cloud – Getting Started
4.4.1 Creating a Workspace
Once you’ve signed up for a free Terraform Cloud account and logged in,
you should be able to see a screen as shown here. We need to click on Start
from scratch.
Figure 4-3. Terraform start from scratch screen
128

Chapter 4 IntroduCtIon to terraform Cloud and WorkspaCes
Note terraform Cloud uI is constantly changing. the screenshots
you’ll see are as of may 11, 2022.
In Terraform Cloud, the first thing that we need to create is an
Organization, so enter a suitable organization name and your email
address and then click Create Organization as shown here.
Note here, possibly, you’ll need to create a unique name for your
organization as practicalgitops may not work.
Figure 4-4. Create Organization screen
On the next screen, it’ll redirect you to select from the three options:
• Version control workflow – Execute terraform directly
from GitHub. Terraform Cloud installs the terraform
application in the repository that we wish to connect.
Triggers are executed whenever there is a git push.
129

Chapter 4 IntroduCtIon to terraform Cloud and WorkspaCes
• CLI-driven workflow – Execute terraform from your
CLI. Connect the CLI terraform binary with terraform
cloud. Manual commands need to be fired as a trigger.
• API-driven workflow – Execute terraform using
Terraform API. Supply the terraform cloud token and
access the terraform cloud environment. Triggers can
be customized to a very high degree.
One common functionality in all the preceding options is that the
terraform state is stored in the Terraform Cloud application.
In this chapter, we’ll do a deep dive into the CLI-driven workflow, and
in Chapter 5, we’ll look at the other two options.
Figure 4-5. Selecting CLI-driven workflow
130

Chapter 4 IntroduCtIon to terraform Cloud and WorkspaCes
On the next screen, add the workspace name as dev with a suitable
description and then click Create workspace.
Figure 4-6. Creating a workspace called dev
4.4.2 Configure Workspace
We’ve got the workspace created but not completely configured; hence,
let’s click Settings ➤ General as shown here.
131

Chapter 4 IntroduCtIon to terraform Cloud and WorkspaCes
Figure 4-7. Access workspace settings
Here, we just need to change one setting as shown in the following.
However, in the next chapter, we’ll need to add an additional setting of the
“Terraform Working Directory.”
–
Lock the Terraform version to 1.0.0
Click Save settings.
132

Chapter 4 IntroduCtIon to terraform Cloud and WorkspaCes
Figure 4-8. Modifying Terraform version
133
Chapter 4 IntroduCtIon to terraform Cloud and WorkspaCes
4.4.3 Workspace Variables
Next, click on the Variables tab on the left side of Settings or simply
browse to
.
134

Chapter 4 IntroduCtIon to terraform Cloud and WorkspaCes
Figure 4-9. Accessing the Terraform Variables section
135


Chapter 4 IntroduCtIon to terraform Cloud and WorkspaCes
Here, we need to add four variables: two environment variables and
two terraform variables.
–
Environment variables
–
AWS Access Key ID: AWS_ACCESS_KEY_ID
–
AWS Secret Access Key: AWS_SECRET_ACCESS_KEY
Figure 4-10. Configuring AWS environment variables
–
Terraform variables
–
AWS Region: region: us-east-2
–
Environment: environment: dev
Figure 4-11. Configuring terraform variables
Your Variables page should have the following details.
136

Chapter 4 IntroduCtIon to terraform Cloud and WorkspaCes
Figure 4-12. Terraform Cloud all variables configured
Environment variables are utilized by Terraform for configuring the
environment in which terraform needs to be run. For example, we’ve
provided the AWS Access Keys as an environment variable to set up the
AWS environment just the way we used the AWS_PROFILE variable in the
previous chapters.
Terraform variables consist of data that terraform requires for its
execution and can be configured in the workspace.
4.4.4 Terraform Login
Next, we need to move our focus on the terminal to fire a very important
command. So yank up your terminal window where Terraform is installed
and execute the command terraform login and follow these steps: 1. Execute the command terraform login.
2. It’ll ask for your permission to store the token in a
file called credentials.tfrc.json.
3. Once you enter “Yes”, it’ll open your browser
and navigate to the following link:
137

Chapter 4 IntroduCtIon to terraform Cloud and WorkspaCes
asking for your permission to create an API token. Provide any name to the
token and click Create API token.
Figure 4-13. Create API token screen
4. Copy the generated token.
138

Chapter 4 IntroduCtIon to terraform Cloud and WorkspaCes
Figure 4-14. Copying the generated token
5. Paste it in the command line where the cursor is
waiting for your input.
139

Chapter 4 IntroduCtIon to terraform Cloud and WorkspaCes
Figure 4-15. Pasting the Terraform API token
We’ve now successfully connected our CLI with Terraform Cloud!
Note this is a very sensitive operation; hence, you need to ensure
that access to this file is limited. This is needed only for this
chapter; you can then discard the value by deleting this file.
4.4.5 Running Terraform in Terraform Cloud
Now let’s execute the code that we developed in the third iteration in the
previous chapter not locally but on Terraform Cloud!
140
Chapter 4 IntroduCtIon to terraform Cloud and WorkspaCes
However, before we jump into the execution part, there is a small
change that needs to be explained. We’ve added three new files: backend.
tf, dev.hcl, and prod.hcl.
CLI Output 4-1. Chapter4 directory structure
cmd>cd chapter4/infra
cmd> tree -a
.
├── backend.tf
├── dev.hcl
├── main.tf
├── modules
│ └── securitygroup
│ ├── main.tf
│ ├── output.tf
│ └── variables.tf
├── output.tf
├── prod.hcl
├── providers.tf
├── scripts
│ └── user_data.sh
├── terraform.auto.tfvars
└── variables.tf
3 directories, 12 files
The backend.tf file informs terraform that the state needs to be
stored remotely and the exact location of the states is provided in the dev.
hcl and prod.hcl files for the development and production workspaces, respectively.
141
Chapter 4 IntroduCtIon to terraform Cloud and WorkspaCes
Code Block 4-2. backend.tf file
File: chapter4/infra/backend.tf
1: terraform {
2: backend "remote" {}
3: }
Code Block 4-3. dev.hcl file
File: chapter4/infra/dev.hcl
1: workspaces { name = "dev" }
2: hostname = "app.terraform.io"
3: organization = "practicalgitops"
Through this new configuration file, we are basically telling terraform
to store the terraform.tfstate file in the dev workspace that was created in the practicalgitops organization earlier through the GUI on app.
terraform.io.
Let’s now run our Terraform commands.
Code Block 4-4. Running terraform init
cmd> cd chapter4/infra
cmd> terraform init -backend-config=dev.hcl
Initializing modules...
- generic_sg_egress in modules/securitygroup
- http_sg_ingress in modules/securitygroup
- ssh_sg_ingress in modules/securitygroup
Initializing the backend...
Successfully configured the backend "remote"! Terraform will
automatically
use this backend unless the backend configuration changes.
142
Chapter 4 IntroduCtIon to terraform Cloud and WorkspaCes
XXXXXXXXXXXXXXX------SNIPPED-------XXXXXXXXXXXXXXXXXXXXX
There are three interesting things to note here:
1. The
terraform init command takes an additional
argument ( -backend- config=dev.hcl) specifying the
details about the remote backend configuration.
2. Everything is now being initialized in Terraform
Cloud, and the state file will also be stored there as is
evident from the message Successfully configured
the backend “remote” .
3. Last but most importantly, we are no longer
specifying the AWS Profile credentials! The
command to export the AWS_PROFILE variable
is not needed anymore because terraform will be
utilizing the AWS Credentials stored as environment
variables in Terraform Cloud! Hence, we can get
rid of the AWS credentials from our local machines!
This is happening because of terraform login.
Let’s view the output for terraform plan as shown here.
Code Block 4-5. Running terraform plan
cmd> terraform plan
Running plan in the remote backend. Output will stream here.
Pressing Ctrl-C
will stop streaming the logs, but will not stop the plan
running remotely.
Preparing the remote plan...
143
Chapter 4 IntroduCtIon to terraform Cloud and WorkspaCes
The remote workspace is configured to work with
configuration at
dev relative to the target repository.
Terraform will upload the contents of the following directory,
excluding files or directories as defined by a
.terraformignore file
at /chapter4/infra/.terraformignore (if it is present),
in order to capture the filesystem context the remote workspace
expects:
/chapter4/infra
To view this run in a browser, visit:
https://app.terraform.io/app/practicalgitops/dev/runs/run-
a3a6RtaP35qttEwj
XXXXXXXXXXXXXXX------SNIPPED-------XXXXXXXXXXXXXXXXXXXXX
Let’s understand what exactly is happening here.
1. Terraform will bundle our entire working directory
that is everything under the dev directory into
Terraform Cloud, and hence, it is asking to
add .gitignore equivalent .terraformignore
in case we wish to avoid leakage of sensitive or
unnecessary data.
2. Next, once the code is uploaded, it’ll execute
terraform plan in the cloud and stream the logs
here. We can alternatively also view the live logs
in the link provided:
(ensure your authenticated).
144

Chapter 4 IntroduCtIon to terraform Cloud and WorkspaCes
Note You would be having a slightly different link depending on
the organization and workspace name. also, each run has a globally
unique number.
So let’s open the link and view what it looks like. Terraform Cloud is
spooling the exact same information as we saw in the CLI but in a much
prettier interface.
Figure 4-16. Terraform plan output on Terraform Cloud
Next, let’s execute terraform apply and see what happens.
CLI Output 4-6. Running terraform apply
cmd> terraform apply
Running apply in the remote backend. Output will stream here.
Pressing Ctrl-C
145
Chapter 4 IntroduCtIon to terraform Cloud and WorkspaCes
will cancel the remote apply if it's still pending. If the
apply started it
will stop streaming the logs, but will not stop the apply
running remotely.
Preparing the remote apply...
The remote workspace is configured to work with
configuration at
dev relative to the target repository.
Terraform will upload the contents of the following directory,
excluding files or directories as defined by a
.terraformignore file
at /chapter4/infra/.terraformignore (if it is present),
in order to capture the filesystem context the remote workspace
expects:
/chapter4/infra
To view this run in a browser, visit:
https://app.terraform.io/app/practicalgitops/dev/runs/run-
XPKVvP68Xk4pEB33
XXXXXXXXXXXXXXX------SNIPPED-------XXXXXXXXXXXXXXXXXXXXX
The terraform apply command also spools out an almost identical
output on the CLI; however, if we open the brows
(ensure your authenticated), it looks a bit different.
146

Chapter 4 IntroduCtIon to terraform Cloud and WorkspaCes
Figure 4-17. Output of terraform apply on Terraform Cloud
What’s happening here is that Terraform is awaiting our approval on
the GUI, that is, on the browser to confirm and apply the plan. Earlier
we were getting a cursor on the CLI (which we do get now as well, but
it is recommended to apply from cloud); however, here, it is asking for
approval on the Terraform Cloud portal.
So let’s go ahead and click Confirm & Apply and see what happens.
When you hit the Confirm & Apply button, it’ll ask for a comment; just add
any appropriate comment and click Confirm Plan as shown here.
147

Chapter 4 IntroduCtIon to terraform Cloud and WorkspaCes
Figure 4-18. Comment before applying
We can see that the plan is being applied as shown here.
148

Chapter 4 IntroduCtIon to terraform Cloud and WorkspaCes
Figure 4-19. Output of terraform apply on Terraform Cloud
Once the plan is applied successfully, navigate to the Overview tab and
you should be able to see a very nicely formatted output of everything that
has been created in this plan as shown here.
149


Chapter 4 IntroduCtIon to terraform Cloud and WorkspaCes
Figure 4-20. Terraform Apply Overview showing all
resources created
It also provides the details of the Output in the tab next to the
Resources tab as shown here.
Figure 4-21. Terraform Output field value on Terraform Cloud
150

Chapter 4 IntroduCtIon to terraform Cloud and WorkspaCes
Interestingly, the same output is also streamed in our CLI as
shown here.
Figure 4-22. Terraform Cloud output streamed on CLI
Great! So we are executing Terraform commands from the CLI, and all
operation is happening on Terraform Cloud! Now, we can share this same
source code with other team members. They can then make changes to the
terraform code and deploy them from their machine, and the terraform
state remains shared between them.
Whenever a team member runs terraform apply, the state is first
checked remotely and then updated in the cloud.
Let’s now destroy by using the terraform destroy command as shown
in the following. We can use the --auto-approve flag to override the manual approval in Terraform Cloud for destruction as well as apply.
CLI Output 4-7. Running terraform destroy
cmd> terraform destroy --auto-approve
Running apply in the remote backend. Output will stream here.
Pressing Ctrl-C
151
Chapter 4 IntroduCtIon to terraform Cloud and WorkspaCes
will cancel the remote apply if it's still pending. If the
apply started it
will stop streaming the logs, but will not stop the apply
running remotely.
Preparing the remote apply...
The remote workspace is configured to work with
configuration at
dev relative to the target repository.
Terraform will upload the contents of the following directory,
excluding files or directories as defined by a
.terraformignore file
at /chapter4/infra/.terraformignore (if it is present),
in order to capture the filesystem context the remote workspace
expects:
/chapter4/infra
To view this run in a browser, visit:
https://app.terraform.io/app/practicalgitops/dev/runs/run- gkkgV
av4xNCdcKQU
4.4.6 Terraform Cloud Run and States
We also get a full log of all the terraform commands executed on the
workspace in the Runs tab as shown in the following with the latest being
shown first. This is something we’ll never get if we run from CLI directly.
This helps in understanding how, when, and what is happening to our
infrastructure, something difficult to decipher when using cloud storage as
a remote backend unless we develop our own solution.
152


Chapter 4 IntroduCtIon to terraform Cloud and WorkspaCes
Figure 4-23. Terraform Cloud Run log
The state is now stored in the Terraform Cloud and can be viewed by
navigating to the States tab as shown here.
Figure 4-24. Terraform states log in Terraform Cloud
153

Chapter 4 IntroduCtIon to terraform Cloud and WorkspaCes
All state changes are versioned, and we can view the difference in the
state configurations between two consecutive runs. Let’s click on the first
state to view the changes.
Figure 4-25. Terraform states stored in Terraform Cloud
As can be seen from the following image, Terraform Cloud is displaying
the changes that have happened between the two states. Here, we are
seeing the difference between an apply step and a destroy step. The red
highlights show resources that were destroyed. But as and when we run
our infrastructure using Terraform Cloud, we’ll be able to see all the
changes that are happening.
154

Chapter 4 IntroduCtIon to terraform Cloud and WorkspaCes
Figure 4-26. Terraform Cloud state diff
These exceptional features of having a log of every run and displaying
of state information per run and the difference between runs provide
wonderful insights into our infrastructure changes. If we store our state in
a cloud storage backend, then we won’t get all this information out of the
box. We’ll probably have to upload the state files in a separate software to
view all this information. However, in Terraform Cloud, we get all this out
of the box.
155
Chapter 4 IntroduCtIon to terraform Cloud and WorkspaCes
4.5 Multi-environment with DRY
In the software engineering domain, DRY is a very important concept,
which basically stands for Don’t Repeat Yourself. Infrastructure as Code
metamorphoses your infrastructure projects into software projects, and
hence, this same principle is applicable here as well. So how do we go
about creating Terraform code that is DRY?
4.5.1 Adding Production Environment
Well, we are already doing that with our current code base. The only
additional file that we’ve added is prod.hcl as shown here, which is only pointing to a different workspace, that is, prod.
Code Block 4-8. File: code\infra\prod.hcl
File: code\infra\prod.hcl
1: workspaces { name = "prod" }
2: hostname = "app.terraform.io"
3: organization = "practicalgitops"
Let’s follow the same steps as provided in the “Creating a Workspace”
section to create a new prod workspace.
Tip It is strongly recommended here to use aWs organizations to
create different environments to achieve proper segregation between
dev and prod accounts. We’ll learn about it in more detail in Chapter
8 where we’ll be set up and discuss aWs organizations with a multi-
account strategy completely using terraform.
156

Chapter 4 IntroduCtIon to terraform Cloud and WorkspaCes
Keeping all the steps the same as we did for the dev workspace, the
only difference in this new prod workspace is an additional variable that
we are adding in the Terraform Cloud, that is, instance_type variable, and providing it a value of t3.small. We are also changing the environment
variable value to prod. We could also change the region.
Figure 4-27. Terraform Cloud variables
Why are we doing this? Because our production system needs a little
more higher configuration machine. Hence, we can override the value of
any variable specified in the terraform.auto.tfvars file by specifying the same here in Terraform Cloud.
Let’s run the terraform code in our new production environment by
specifying a different backend configuration file this time around.
CLI Output 4-9. Running terraform init error
cmd> cd chapter4/infra
cmd> terraform init -backend-config=prod.hcl
Initializing modules...
Initializing the backend...
╷
│ Error: Backend configuration changed
157
Chapter 4 IntroduCtIon to terraform Cloud and WorkspaCes
│
│ A change in the backend configuration has been detected,
which may require migrating existing state.
│
│ If you wish to attempt automatic migration of the state, use
"terraform init -migrate-state".
│ If you wish to store the current configuration with no
changes to the state, use "terraform init -reconfigure".
When we run terraform init with a different backend configuration,
we get an error because terraform has not been initialized for prod.hcl; it was initialized for the dev environment earlier. Here, we’ve two options:
1. Run
rm -rf .terraform to remove the terraform
configuration so that it can create a new one.
2. Run
terraform init -backend-config=prod.hcl
-reconfigure, which we’ll reconfigure the state.
We’ll, however, proceed with the nondestructive one, that is,
reconfiguring the state as shown here.
CLI Output 4-10. Terraform init reconfigure
cmd> terraform init -backend-config=prod.hcl -reconfigure
cmd> terraform plan
Running plan in the remote backend. Output will stream here.
Pressing Ctrl-C
will stop streaming the logs, but will not stop the plan
running remotely.
XXXXXXXXXXXXXXX------SNIPPED-------XXXXXXXXXXXXXXXXXXXXX
# aws_instance.apache2_server will be created
+ resource "aws_instance" "apache2_server" {
158
Chapter 4 IntroduCtIon to terraform Cloud and WorkspaCes
+ ami = "ami-06c7d6c0
987eaa46c"
XXXXXXXXXXXXXXX------SNIPPED-------XXXXXXXXXXXXXXXXXXXXX
+ instance_type = " t3.small"
+ ipv6_address_count = (known after
apply)
+ ipv6_addresses = (known after
apply)
+ key_name = "gitops"
XXXXXXXXXXXXXXX------SNIPPED-------XXXXXXXXXXXXXXXXXXXXX
cmd> terraform apply --auto-approve
4.5.2 TF Variables Overriding
An interesting observation to note here is that our terraform variables in
the cloud for the instance_type have been successfully overridden.
Terraform Variable has the following order of precedence with the
decreasing order of priority:
1. Terraform Cloud declaration/environment variables
2. Passing variables in CLI, for example, terraform
apply -var “instance_type=t3.small”
3. Passing variable value in the terraform.auto.
tfvars file
4. Passing variable value as “default” in *.tf files
When the terraform apply completes execution, you’ll be able to see a new EC2 instance launched in the production environment.
159

Chapter 4 IntroduCtIon to terraform Cloud and WorkspaCes
In this manner, by configuring a different workspace for different
environments and tweaking the terraform/environment variables, we
can deploy various different environments using the same code base as is
illustrated in the following figure.
Figure 4-28. Terraform Cloud with different workspaces
160

Chapter 4 IntroduCtIon to terraform Cloud and WorkspaCes
4.6 Clean-Up
Just to ensure that we don’t end up getting billed unnecessarily, execute
the following commands to confirm that we’ve deleted all the resources.
CLI Output 4-11. Running terraform destroy
cmd> export AWS_PROFILE=gitops
cmd> cd chapter4/infra
cmd> terraform destroy --auto-approve
It is also highly recommended that you visit the EC2 console on
the AWS GUI and double-check if everything is deleted. The Instances
(running) field should show 0.
Figure 4-29. EC2 running instances status
Note always double-check the region; we’ve been following us-
east- 2 in this chapter.
161

Chapter 4 IntroduCtIon to terraform Cloud and WorkspaCes
4.6.1 Destroying Terraform Cloud Workspaces
It may for some reason or other be necessary to destroy a workspace,
maybe to change the workflow or maybe some sensitive information has
been accidentally committed.
The following steps illustrate how we can go about doing the same.
1. Click on a workspace that you’d like to destroy.
Figure 4-30. Click on the workspace to destroy
2. Then click Settings and then Destruction and
Deletion as shown here.
162

Chapter 4 IntroduCtIon to terraform Cloud and WorkspaCes
Figure 4-31. Click on destruction and deletion
3. Scroll down and click Delete from
Terraform Cloud.
163

Chapter 4 IntroduCtIon to terraform Cloud and WorkspaCes
Figure 4-32. Workspace deletion
4. You’ll be asked to confirm the deletion by entering
the name of the workspace you’re deleting. Enter
the name and then click Delete workspace as
shown here.
164

Chapter 4 IntroduCtIon to terraform Cloud and WorkspaCes
Figure 4-33. Delete workspace confirmation
5. Your workspace will then be deleted along with all
the runs and state information.
6. Similarly destroy your prod workspace as well.
4.7 Terraform Cloud Security Best Practices
Terraform Cloud is now not only storing your state but is also provisioning
your AWS infrastructure. It hence becomes an extremely critical asset for
the organization.
If an adversary gains access to your Terraform Cloud account, they can
1. Gain read-only access to your GitHub repository
2. Delete Cloud workspaces
3. View sensitive information (if any) stored in your
terraform state files
165
Chapter 4 IntroduCtIon to terraform Cloud and WorkspaCes
Hence, it is extremely important to ensure certain necessary security
best practices such as
1. Creating separate organizations for different
environments on a similar note as AWS
organizations
2. Providing access to only limited users:
3. Enabling 2FA for all users:
4. The default session timeout is 20160 minutes; you
might want to reduce it:
5. Monitoring VCS events:
6. Monitoring active sessions of users:
7. Setting a strong password:
8. Creating tokens per user and also regularly
monitoring their usage:
These are some of the best practices that can be followed from a
security perspective.
166
Chapter 4 IntroduCtIon to terraform Cloud and WorkspaCes
4.8 Conclusion
In this chapter, we looked at how we can utilize the Terraform Cloud
utility to store the terraform space in a remote and secure environment.
Terraform Cloud also provides versioning to our terraform state, thereby
allowing us to determine the changes between the previous states, thereby
enabling us to properly version control the infrastructure changes. It
also helps in creating workspaces that are mutually independent of each
other and hence can be used to set up different environments using the
same code base. Finally, we also saw how we can destroy the workspaces
created in the Terraform Cloud as a necessary action in certain unforeseen
conditions.
In this chapter, we saw all about how terraform state can be stored
remotely; however, we needed to fire the commands to execute terraform.
In the next chapter, we’ll look at two different ways in which we can
leverage Terraform Cloud to execute the commands for us by tying it with
our GitHub repository.
167
CHAPTER 5
Terraform Automation
with Git
In the previous chapter, we looked at how terraform state can be stored
remotely to allow for collaboration with a larger team. However, just
having the state configured remotely won’t help much as we also need
the teams to collaborate with their code changes. We need a mechanism
wherein the developers can push their code into an SCM (Source Code
Management) repository and view/approve each other’s changes before
executing terraform. Git allows for full collaborative workflows; hence, in
this chapter, we’ll look at two different methods using GitHub and GitHub
Actions. We can enable collaboration between developers by taking
away their responsibility of manually executing terraform commands
from CLI by leveraging the VCS driven-workflow and the Terraform API-
driven workflow provided by Terraform Cloud, thus automating the entire
terraform workflow. We’ll also finally look at the drawbacks of using the
VCS workflow and why GitHub Actions is a preferable setup.
© Rohit Salecha 2023
169
R. Salecha, Practical GitOps,
Chapter 5 terraform automation with Git
5.1 Prerequisites
In addition to the previous chapters, the following are the prerequisites for
this chapter:
– Git installed and accessible from the command line.
–
provides installation steps for almost all major operating systems.
– It is also assumed that you’ve basic working knowledge
of git commands and managing remote git
repositories.
– GitHub account; we’ll be creating a number of reposito-
ries from this chapter onward to keep learning clean.
–
– You’ve connected GitHub with SSH (optional)
– I’ll be using git mainly from the CLI, and hence, whenever
performing git push, you’ll be asked to enter your GitHub
credentials; if you wish to avoid that, then you can configure
your Git with SSH.
–
is a guide providing instructions to set up the same across all major operating systems.
– DockerHub account
–
170
Chapter 5 terraform automation with Git
5.2 Terraform Automation with GitHub
Till now we’ve been manually executing the commands terraform init,
terraform plan, and terraform apply. In Terraform Cloud, you could
trigger a terraform plan by simply committing into your git repository!
There are absolutely no commands that need to be fired from the
command line other than checking your code into your repository. This
can be accomplished using the VCS (Version Controlled System) feature
of Terraform Cloud where we need to integrate our repository with it and
then everything is managed from Terraform Cloud.
5.2.1 Setting Up the GitHub Repository
Let’s start by first setting up a GitHub repository. Assuming you are already
logged into your GitHub account, navigate to to create a new repository and follow the following steps:
1. Create an appropriate name for your repository. I’ve
chosen to keep the naming convention as section-
chapter(n)
2. Select the appropriate visibility. I’ve chosen it as
Private for now.
3. Click Create repository.
171

Chapter 5 terraform automation with Git
Figure 5-1. Creating GitHub repository
Once your repository has been created, ensure to note down your
repository URL.
If you’ve configured your SSH keys with GitHub, then you can select
the SSH tab and note down the GitHub SSH URL as shown here.
172


Chapter 5 terraform automation with Git
Figure 5-2. GitHub SSH URL
If you’ve not configured SSH and are going to enter your credentials,
then select the HTTPS tab as shown here.
Figure 5-3. GitHub HTTPS URL
Note it is extremely important to know which urL you are using,
whether httpS or SSh, as depending on that, you’ll be authenticated
to Github when checking in your code.
Now, navigate to the following folder location and let’s upload the code
to the GitHub repository that we’ve created.
CLI Output 5-1. Adding code to the GitHub repository
cmd> cd chapter5/vcs
cmd> tree -a
.
173
Chapter 5 terraform automation with Git
├── .gitignore
├── README.md
└── infra
├── main.tf
├── modules
│ └── securitygroup
│ ├── main.tf
│ ├── output.tf
│ └── variables.tf
├── output.tf
├── providers.tf
├── scripts
│ └── user_data.sh
├── terraform.auto.tfvars
└── variables.tf
├── README.md
4 directories, 12 files
cmd> git init
cmd> git add .
cmd> git commit -m "first commit"
cmd> git branch -M main
# Enter proper URL here, If you've not configure SSH then enter
HTTPS URL
cmd> git remote add origin <your_https_or_ssh_url_here>
cmd> git push -u origin main
Visit the GitHub repository URL to confirm if all files have been
uploaded successfully.
174


Chapter 5 terraform automation with Git
5.2.2 Connecting GitHub with Terraform Cloud
Visit the Terraform Cloud application and click on create new workspace
as shown here.
Figure 5-4. Creating new Terraform Cloud workspace
In the new workspace creation screen, select the Version control
workflow as shown here.
Figure 5-5. Selecting Version control workflow
175

Chapter 5 terraform automation with Git
In the Connect to VCS section, select GitHub as shown here.
Figure 5-6. Connecting Terraform Cloud to GitHub.com
When you select GitHub, you’ll be prompted to authorize Terraform
Cloud to access your GitHub account as shown in the following. Click
Authorize Terraform Cloud.
176

Chapter 5 terraform automation with Git
Figure 5-7. Authorizing Terraform Cloud
Note if you’ve pop-up blockers installed, then the preceding step
will fail. please allow popups for this particular site.
Next, there will be another popup that will ask your permission to
install Terraform Cloud in your GitHub repositories as shown here.
Note Do not click on install here as this is an excessive permission
and terraform Cloud will have read access to all your repositories.
please check the next steps.
177

Chapter 5 terraform automation with Git
Figure 5-8. Installing Terraform Cloud to the GitHub repository
Since we don’t wish to provide access to all our repositories, we’ll
follow the following steps:
1. Select radio button Only select repositories.
2. Select the repository we just created in the drop-
down box.
3. Click Install.
178

Chapter 5 terraform automation with Git
Figure 5-9. Selecting specific GitHub repository only
5.2.3 Creating VCS-Driven Workspace
Once the authorization has been completed, you’ll be redirected to the
Terraform Cloud, and the repository that we had selected will now be
shown as follows. Let’s click on it to proceed further.
179

Chapter 5 terraform automation with Git
Figure 5-10. Selecting the GitHub repository connected
Finally, we’ll be on the Configure Settings tab where we need to put in
some additional configurations. Click Advanced options as shown here.
180

Chapter 5 terraform automation with Git
Figure 5-11. Configuring advanced settings
There is a very important configuration that we need to add, and that is
about the working directory as shown here.
1. Enter the name of the Terraform Working
Directory as infra.
2. Click
Create workspace.
181

Chapter 5 terraform automation with Git
Figure 5-12. Configuring Terraform Working Directory
This is a very important configuration because Terraform will be
downloading the entire GitHub repository on its own servers. It’ll by
default run the terraform commands on the root folder, but since there are
no *.tf files, it’ll fail.
Hence, we need to inform Terraform Cloud that all our *.tf files are
actually in the infra folder.
182

Chapter 5 terraform automation with Git
5.2.4 Configuring Environment Variables
Next, we need to configure the variables section as we did in the earlier
chapter. The following are the variables that need to be added as
shown here:
– Environment variables
– AWS Access Key ID: AWS_ACCESS_KEY_ID
– AWS Secret Access Key: AWS_SECRET_ACCESS_KEY
– Terraform variables
– Region: region: us-east-2
– Environment: environment: dev
Figure 5-13. Configuring Terraform and environment variables
Note terraform Cloud ui is constantly changing. the screenshots
you’ll see are as of nov 29, 2021.
183

Chapter 5 terraform automation with Git
5.2.5 Executing Plan in Cloud
Now that we’ve everything configured, let’s visit our workspace and start
the build. When configuring the VCS with Terraform Cloud for the very
first time, we need to trigger the build manually using the Start new plan
button as shown here.
Figure 5-14. Manually triggering Terraform VCS workflow
After clicking Start new plan, it’ll run terraform plan and will show what resources are going to be created as shown here.
184

Chapter 5 terraform automation with Git
Figure 5-15. Terraform plan output in Terraform Cloud
If we scroll down a little, you’ll see that the plan is waiting for our
confirmation and approval just like how we had in the CLI run. Let’s hit
Confirm and Apply.
185


Chapter 5 terraform automation with Git
Figure 5-16. Confirm & Apply terraform plan
Once you click Confirm & Apply, it’ll ask to enter a comment. After a
while, it’ll show the plan being executed and finally complete with all the
resource IDs and our public IP address as shown here.
Figure 5-17. Terraform apply finished
Accessing the IP publicly lands us on the default Apache2 page as
shown here.
186

Chapter 5 terraform automation with Git
Figure 5-18. Accessing public IP
5.2.6 Triggering the VCS
What we saw here was a manual trigger that we executed from the
Terraform Cloud console. Now let’s see how we can trigger it using VCS
or GitHub.
So let’s say another colleague of yours who has access to this repository
wishes to add another EC2 server and wishes to obtain the public IP
address of that server. The following are the additions that they have added
to our existing code base.
Note when you access the code under the vcs/infra folder, you’ll
see that these lines are commented for ease of use. hence, you
just need to uncomment and push the changes to the repository.
uncommented code would look like this as shown here.
Adding a new EC2 named apache2_server_1
Code Block 5-2. Adding new EC2
File: chapter5/vcs/infra/main.tf
XXXXXXXXXXXXXXX------SNIPPED-------XXXXXXXXXXXXXXXXXXXXX
79: }
80:
187
Chapter 5 terraform automation with Git
81: // Uncomment for Section 5.3.4 - Trigger the VCS
82: resource "aws_instance" "apache2_server_1" {
83: ami = data.aws_ami.ubuntu.id
84: instance_type = var.instance_type
85: vpc_security_group_ids = [module.http_sg_ingress.sg_id,
86: module.generic_sg_egress.sg_id,
87: module.ssh_sg_ingress.sg_id]
88: key_name = var.ssh_key_name
89: user_data = file("./scripts/user_data.sh")
90: tags = {
91: env = var.environment
92: Name = "ec2-${local.name-suffix}"
93: }
94:
95: depends_on = [
96: module.generic_sg_egress
97: ]
98: }
Adding a new output block as ip_address_1
Code Block 5-3. Adding an output block for a new EC2
File: chapter5/vcs/infra/output.tf
XXXXXXXXXXXXXXX------SNIPPED-------XXXXXXXXXXXXXXXXXXXXX
6: // Uncomment for Section 5.3.4 - Trigger the VCS
7: output "ip_address_1" {
8: value = aws_instance.apache2_server_1.public_ip
9: }
Let’s now push these new changes to our GitHub repository.
188
Chapter 5 terraform automation with Git
CLI Output 5-4. Pushing new EC2 changes to GitHub
cmd> git status
On branch main
Your branch is up to date with 'origin/main'.
XXXXXXXXXXXXXXX------SNIPPED-------XXXXXXXXXXXXXXXXXXXXX
modified: infra/main.tf
modified: infra/output.tf
no changes added to commit (use "git add" and/or "git
commit -a")
cmd> git add .
cmd> git commit -m "adding a new ec2"
[main c3f37a2] adding a new ec2
2 files changed, 22 insertions(+)
cmd> git push
XXXXXXXXXXXXXXX------SNIPPED-------XXXXXXXXXXXXXXXXXXXXX
remote: Resolving deltas: 100% (3/3), completed with 3 local
objects.
To gitops:practical-gitops/vcs-chapter5.git
3483cdf..c3f37a2 main -> main
As soon as our colleague pushed the changes into the GitHub
repository, we can see the terraform plan getting executed automatically
because of the GitHub push trigger.
The name of the trigger is the same as the commit message that we set.
We can also observe the name of the new server and the output that we
had declared.
189

Chapter 5 terraform automation with Git
Figure 5-19. New EC2 terraform plan
Let’s confirm and apply the plan, and now we have set up Terraform
in the cloud with state being stored remotely and also leveraging the full
collaboration feature of GitHub. The following screenshot shows that the
new server created by our colleague has been set up and running!
190


Chapter 5 terraform automation with Git
Figure 5-20. New EC2 terraform apply finished
The new server is also accessible as the old one!
Figure 5-21. New EC2 publicly accessible
We can further fine-tune the collaboration by ensuring that Terraform
Cloud triggers deployments only from a specific branch by configuring that
branch name as shown here.
1. Go to Settings.
2. Click Version Control.
3. Enter the branch name in the VCS branch field.
191

Chapter 5 terraform automation with Git
Figure 5-22. Fine-tuning branch settings
192

Chapter 5 terraform automation with Git
5.2.7 Destroying in Terraform Cloud
The steps to destroy the plan in Terraform Cloud were already discussed in
the previous chapter. However, they are repeated in brief here:
1. Click Settings.
2. Click Destruction and Deletion.
3. Click
Queue destroy plan.
Figure 5-23. Terraform destroy from Terraform Cloud
It shall ask for confirmation; please confirm by providing the name of
the workspace, and the destroy plan shall be executed.
193
Chapter 5 terraform automation with Git
5.2.8 Drawbacks of Terraform Cloud with VCS
We now have the terraform state stored remotely and also can use
Terraform Cloud VCS with GitHub to leverage collaboration with different
team members. However, the primary drawback of this approach is that
we no longer have control over the entire terraform workflow of init, plan,
apply, and destroy. When there is a change in GitHub, it’ll trigger the
terraform workflow.
Owing to this major limitation
• We cannot perform any linting/testing of the terraform
code prior to deployment. We may have to create a
separate branch wherein the team members may have
to test it locally, but it sort of defeats the purpose.
• We cannot add additional workflows like adding a
security guardrail to check if we’ve not leaked any
sensitive information in our commit or introduced a
security flaw.
Hence, to overcome these flaws, we’ll look at GitHub Actions wherein
we’ll have full control over the entire Terraform workflow process.
5.3 Introduction to GitHub Actions
5.3.1 CI/CD General Workflow
CI/CD operations are all about workflows that automate a certain set of
actions. Tools like Jenkins can help in creating workflows for building,
testing, and deploying software artifacts in an automated fashion. A
general CI/CD workflow would primarily consist of the following steps:
1. Pull the latest code on a trigger event like a git
commit or git push.
194

Chapter 5 terraform automation with Git
2. Build the application or the docker image using
whatever build software that application is using.
3. Push the build code to its artifact repository or the
docker image to the docker repository.
4. Pull the artifacts or the docker image from their
respective repositories and deploy in the staging/
production environment.
5. Run unit tests and security test cases on the
deployed environment.
Additional steps would be present depending on the organization and
what they wish to automate.
For performing these actions on a CI/CD server like Jenkins, we
need to install Jenkins and deploy a Jenkinsfile in our source code. This
Jenkinsfile is then read on a trigger event like git commit/push by Jenkins,
and it starts the entire workflow like as follows.
Figure 5-24. Jenkins stages Refer
195
Chapter 5 terraform automation with Git
5.3.2 Introduction to GitHub Actions
GitHub Actions is a service provided by GitHub.com to help us achieve
a similar workflow as the one created using Jenkins shown previously,
without having to install any software or tool. We only need to write a
YAML file and keep it in a folder named .github/workflows. When an
event like commit, push, pull request, etc., is triggered, GitHub would read
the YAML file containing all the workflow instructions and start executing
those actions, all this without having the headache to install and maintain
a full-fledged CI/CD server.
Just like in Jenkins, we have stages and steps that break down the entire
process; in GitHub Actions, we have jobs and steps. Each action file must
have at least one job, and each job must’ve at least one step.
Tip the files under .github/workflows will have the potential to
execute scripts and even read sensitive information like GithuB_
toKen. hence, it is recommended to add a file called CoDeownerS
where we can specify only certain individuals who can edit the
files stored under the .github/workflows directory (
5.3.3 Sample GitHub Actions YAML
Let’s look at a sample GitHub workflow YAML file.
Code Block 5-5. Sample GitHub Actions workflow file
File: chapter5/sample_gha.yaml
196
Chapter 5 terraform automation with Git
01: name: "Sample GHA Workflow"
02: env:
03: GLOBAL_VARIABLE: "Can be accessed by all jobs"
04: API_TOKEN: ${{ secrets.API_TOKEN }}
05:
06: on:
07: push:
08: branches:
09: - main
10: paths:
11: - "infra/**"
12: pull_request:
13: branches:
14: - main
15:
16: jobs:
17: job1:
18: runs-on: ubuntu-latest
19: env:
20: LOCAL_VARIABLE: "This is a Job1 Variable"
21: defaults:
22: run:
23: working-directory: "job1"
24: steps:
25: - name: Checkout
26: uses: actions/checkout@v2
27:
28: - name: job1_global_variable
29: run: echo "job1 with $GLOBAL_VARIABLE $API_TOKEN"
30:
31: - name: job1_local_variable
32: run: echo "job1 with $LOCAL_VARIABLE"
197
Chapter 5 terraform automation with Git
The preceding sample GitHub Actions workflow file can be divided
into three main parts.
L1–L4: Declaring the name of the workflow file and environment,
secret variables that can be called anywhere throughout the YAML
document.
L6–L14: This is the conditions or the triggers under which this
workflow will be executed. On a push to the main branch and on changes of any file in the infra folder or when a pull request is triggered on the main branch (from any branch), this workflow should be executed.
L16–L32: We are declaring the jobs that need to be executed. Here,
we are declaring a job called job1 for which we are requesting to spin up a temporary Ubuntu (latest) environment. We are declaring an environment
variable (LOCAL_VARIABLE) that will be available only to that particular
job. We are also declaring a working directory job1, which will be the
default entry point for executing any scripts.
Each job can have one or more than one step where we specify the
commands that we want to execute. The very first step on L25 is the
checkout step where we are fetching the latest code that has been checked
in. This code will be stored temporarily in the working directory that has
been defined. After that, whatever commands we wish to execute on the
latest version of the code, we can specify in the succeeding steps. All steps
are executed sequentially by default.
The steps declaration can have either a run command or a uses
attribute.
The checkout step on L25 is having the uses attribute, which is
referring to an action, which is a predefined reusable template of actions
like modules in Terraform.
GitHub Actions has many such predefined actions that can be found in
the GitHub Actions Marketplace (
).
198
Chapter 5 terraform automation with Git
5.3.4 Docker Build Automation
with GitHub Actions
Let’s take a practical example to better understand GitHub Actions by
building and then pushing a Docker image to the DockerHub public
repository.
Before we get on with understanding the code, we’ll need the
following:
–
A new repository named gha-chapter5 or whatever you’d
like to name it.
–
DockerHub Access Token, which can be generated from
the link
once you log into your DockerHub account. Please
generate it and store it as a secret in the new GitHub
repository as shown here.
–
Storing secrets in GitHub:
1. Access Settings of the repository.
2. Click Secrets.
3. Click New repository secret.
Two secrets need to be stored:
• DOCKERHUB_USERNAME
• DOCKERHUB_TOKEN
199

Chapter 5 terraform automation with Git
Figure 5-25. Configuring GitHub Secrets
Let’s have a look at our GitHub Actions workflow file, which will build
and push the docker images to DockerHub.
Code Block 5-6. Workflow file for Docker image deployment
File: chapter5/gha/.github/workflows/app.yaml
01: # Github Actions Workflow file that builds and pushes the
docker images
02: name: practicalgitops.app
03: env:
04: DOCKERHUB_TAG: "${{ secrets.DOCKERHUB_USERNAME }}/
practicalgitops"
05: DOCKERFILE_PATH: app
06:
07: on:
08: push:
09: branches:
10: - main
200
Chapter 5 terraform automation with Git
11: paths:
12: - "app/**"
13: - "!**/README.md"
14:
15: # Job declaration starts.
16: jobs:
17: docker:
18: runs-on: ubuntu-latest
19: steps:
20: - name: Check out code
21: uses: actions/checkout@v2
22:
23: - name: Set up QEMU
24: uses: docker/setup-qemu-action@v1
25:
26: - name: Set up Docker Buildx
27: uses: docker/setup-buildx-action@v1
28:
29: - name: Login to DockerHub
30: uses: docker/login-action@v1
31: with:
32: username: ${{ secrets.DOCKERHUB_USERNAME }}
33: password: ${{ secrets.DOCKERHUB_TOKEN }}
34:
35: - name: Build and push
36: id: docker_build
37: uses: docker/build-push-action@v2
38: with:
39: context: "${{ env.DOCKERFILE_PATH }}"
40: push: true
41: tags: "${{ env.DOCKERHUB_TAG }}:latest"
201
Chapter 5 terraform automation with Git
L1–5: Declaring the workflow name and environment variables that’ll
be required globally.
L7–16: Trigger this workflow whenever there is a push on the
main branch and if there are any changes in the app folder (the folder containing our source code). Also ignore any changes done to README.
md in any of the folders.
L20–27: Check out the latest code and set up the entire docker build
environment.
L29–33: Authenticate to DockerHub in order to be able to push the
latest images in the next step.
L35–41: Build and push the docker image. Here, we are supplying the
context, that is, the folder where Dockerfile is located.
5.3.5 Triggering GitHub Actions
Let’s get to action by first pushing our entire code to the new repository,
and then we’ll trigger the workflow.
CLI Output 5-7. Uploading code to the remote repository for
chapter5 gha
cmd> cd chapter5/gha
cmd> git init
cmd> git add .
cmd> git commit -m "first commit"
cmd> git branch -m main
# Ensure to add your own repository URL here
cmd> git remote add origin <your_https_or_ssh_url_here>
cmd> git push -u origin main
202
Chapter 5 terraform automation with Git
Now that we’ve committed our code to the repository, let’s trigger the
workflow by doing a minor edit to one of our HTML template files. Since
this file is in the app folder, GitHub will sense a change and trigger the
workflow.
Tip You can also manually trigger the workflows from the Github
Gui by adding the workflow_dispatch condition as described here:
Code Block 5-8. HTML template edit
File: chapter5/gha/app/src/main/resources/templates/add-
user.html
13: <body>
14: <div class="container my-5">
15: <h3> Add Users</h3>
16: <div class="card">
17: <div class="card-body">
18: <div class="col-md-10">
Access the file at the chapter5/gha/app/src/main/resources/
templates/add-user.html location and edit L15 by changing Add Users text to Add User or anything you’d like between the <h3> tags as shown previously.
Once this change is done, then simply fire the following commands
again to commit the code to trigger the actions.
203

Chapter 5 terraform automation with Git
CLI Output 5-9. Uploading changes of the HTML template to
trigger docker workflow
cmd> cd chapter5/gha
cmd> git status
On branch main
Your branch is up to date with 'origin/main'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working
directory)
modified: app/src/main/resources/templates/add-
user.html
cmd> git add .
cmd> git commit -m "trigger the workflow"
cmd> git push
Once the commit is pushed, visit the Actions tab and observe the
workflow being initiated as shown here.
Figure 5-26. Docker workflow getting triggered
204

Chapter 5 terraform automation with Git
After a few minutes, you should be able to see our workflow execution
being completed as shown here. All our steps are being shown serially.
Figure 5-27. All workflow stages completing execution
205

Chapter 5 terraform automation with Git
Click on any one step and check out the logs. For example, let’s click on
the Build and Push step to check out the logs.
Figure 5-28. Logs of the Build and Push step
By the end of the last step, our docker image is pushed into DockerHub
and is accessible to download and run. We’ll utilize this image when we
deploy our AWS EKS cluster in the upcoming chapters of this book. In my
case, the DockerHub image is tagged and accessible as
docker pull salecharohit/practicalgitops
() 206
Chapter 5 terraform automation with Git
For the purpose of this book, we’ll be using the image as default. You
are, however, free to use your application image.
5.4 Terraform Automation
with GitHub Actions
We saw how we can automate the process of continuously deploying
changes to our docker image. In a similar fashion, let’s understand how
we can facilitate continuous deployment of terraform changes in our infra
directory.
5.4.1 Code Walkthrough
Let’s view the code of the workflow file for terraform automation.
Note this file will be present in the chapter5/gha/.github/
workflow folder but fully commented so as not to disturb the flow of
earlier sections. please uncomment the file for use in this section.
Code Block 5-10. Terraform automation with GitHub Actions
File: chapter5/gha/.github/workflows/infra.yaml
01:# Github Actions Workflow file that automates the terraform
workflow.
02:# UnComment for section 5.4.1 - Terraform Automation with
Github Actions
03: name: practicalgitops.infra
04:
05: on:
207
Chapter 5 terraform automation with Git
06: push:
07: branches:
08: - main
09: paths:
10: - "infra/**"
11: - "!**/README.md"
12:
13: jobs:
14: terraform:
15: name: "Terraform"
16: runs-on: ubuntu-latest
17: defaults:
18: run:
19: working-directory: "infra"
20: steps:
21: - name: Checkout
22: uses: actions/checkout@v2
23:
24: - name: Setup Terraform
25: uses: hashicorp/setup-terraform@v1
26: with:
27: terraform_version: 1.0.0
28: cli_config_credentials_token: ${{ secrets.TF_API_
TOKEN }}
29:
30: - name: Terraform Init
31: id: init
32: run: terraform init -backend-config=gha.hcl
33:
34: - name: Terraform Validate
35: id: validate
208
Chapter 5 terraform automation with Git
36: run: terraform validate -no-color
37:
38: - name: Terraform Plan
39: id: plan
40: run: terraform plan -no-color
41:
42: - name: Terraform Apply
43: run: terraform apply
L5–11: Is the same as our previous docker automation workflow file
other than the folder name. So here, we want to execute this workflow only
when there is a change in the terraform code in the infra folder (except
README.md files).
L13–19: We are declaring a job called “Terraform” and setting the
working directory as infra. This means that all the commands/steps will be
executed in the infra folder where all terraform files are stored.
L21–28: Pulling the latest code and setting up Terraform in the
temporary Ubuntu container that we’ve spun up for the job.
L30-43: Is the general terraform workflow that we are looking to
automate by firing all the commands terraform init and terraform
validate
5.4.2 Terraform Login Token
Now that we’ve understood the code, let’s look at how we can deploy this
new workflow. For this, there are a couple of changes we need to make and
create a new workspace in terraform.
We need to fetch Terraform Token that we’d created in Section 4.4.4. In
most cases, the token would be stored in the ~/.terraform.d/credentials.
tfrc.json file.
Alternatively, you could simply create a new token by visitin
.
209

Chapter 5 terraform automation with Git
We need to store this Terraform Token as a GitHub secret in the gha-
chapter5 repository that we’d created as shown here.
TF_API_TOKEN
Figure 5-29. Configuring Terraform Token as a GitHub secret
5.4.3 Creating an API-Driven
Terraform Workspace
Next, we need to create another terraform workspace but this time, with
API-driven workflow as shown here.
210

Chapter 5 terraform automation with Git
Figure 5-30. Creating an API-driven terraform workspace
Next, just like the other workspaces, here too, we need to define the
environment terraform variables as shown here:
–
Environment variables
–
AWS Access Key ID: AWS_ACCESS_KEY_ID
–
AWS Secret Access Key: AWS_SECRET_ACCESS_KEY
–
Terraform variables
–
Region: region: us-east-2
–
Environment: environment: dev
211

Chapter 5 terraform automation with Git
Figure 5-31. Terraform workspace environment variables
That’s it! We no longer need to configure the terraform workspace
directory or the terraform version as this is an API-driven workflow!
Now let’s commit the code using the following commands.
CLI Output 5-11. Pushing terraform automation workflow
to remote
cmd> cd chapter5/gha
cmd> git status
On branch main
Your branch is up to date with 'origin/main'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working
directory)
modified: .github/workflows/infra.yaml
cmd> git add .
cmd> git commit -m "adding infra workflow file"
cmd> git push
212

Chapter 5 terraform automation with Git
Once your code is pushed, you won’t see any action being executed,
but you should be able to see two workflows under the Actions tab as
shown here.
Figure 5-32. Both GitHub Actions workflows showing up on GitHub
5.4.4 Executing API-Driven Workflow
We now need to provide the name of the workspace that we’ve created in
gha.hcl as shown in the following. Ensure to put the complete name as
shown here.
Code Block 5-12. Configuring the workspace name
File: chapter5/gha/infra/gha.hcl
1: workspaces { name = "gha-chapter5" }
2: hostname = "app.terraform.io"
3: organization = "practicalgitops"
Let’s commit and push the code after making the changes to gha.hcl.
213

Chapter 5 terraform automation with Git
CLI Output 5-13. Pushing the workspace configuration file to
remote for triggering workflow
cmd> cd chapter5/gha
cmd> git status
On branch main
Your branch is up to date with 'origin/main'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working
directory)
modified: infra/gha.hcl
cmd> git add .
cmd> git commit -m "adding workspace name"
cmd> git push
Once the code is committed, if you navigate to the Actions tab, you
should be able to see our new workflow has started as shown here.
Figure 5-33. Workflow actions triggered
Click on the workflow, and you should see the execution of all the
terraform commands as shown here.
214

Chapter 5 terraform automation with Git
Figure 5-34. Execution of terraform commands as part of the GitHub
workflow
If you click on the Terraform Apply stage, you’ll see that the GitHub
action execution stage is waiting for our input. However, this input needs
to be processed on the Terraform Cloud application.
215

Chapter 5 terraform automation with Git
Figure 5-35. Terraform apply stage approval
We’ll need to now visit the app.terraform.io application to confirm the
apply as shown here.
216

Chapter 5 terraform automation with Git
Figure 5-36. Confirming terraform apply triggered using
Github Actions
Once apply is confirmed, you should be able to see the logs streaming
on both ends as shown here.
217

Chapter 5 terraform automation with Git
Figure 5-37. Terraform apply logs streaming on both ends
That’s it! Our execution is complete!
It’s worth noting that in this API-Driven workflow, we got started very
quickly as we did not have to configure the workspaces except for the
environment variables. Everything is now being governed through GitHub
Actions.
5.5 Clean-Up
Clean-up for this section should be fairly simple as here we have executed
everything from Terraform Cloud. Navigate to Settings ➤ Destruction and
Deletion of the workspace that we wish to destroy and then click Queue
Destroy Plan. It’ll ask for your confirmation like as follows.
218


Chapter 5 terraform automation with Git
Figure 5-38. Terraform destroy on cloud confirmation
Do this for all the workspaces created.
It is also highly recommended that you visit the EC2 console on
the AWS GUI and double-check if everything is deleted. The Instances
(running) field should show 0.
Figure 5-39. EC2 running status
219
Chapter 5 terraform automation with Git
Note always double-check the region; we’ve been following us-
east- 2 in this chapter.
5.6 Conclusion
In this chapter, we looked at how Git can be used as a single point of truth
for managing your application as well as your infrastructure through code.
By leveraging the various features of Git, we can establish well-defined
processes and procedures to push code smoothly in an automated fashion.
We also looked at the two ways in which terraform code can be deployed
using Git: one through the VCS driven and the other API driven. VCS
driven is good as long as all the checks and testing are happening at the
developers end, which is pretty rare. Hence, API-driven workflow helps
in baking in the checks and tests within the GitHub Actions workflow files
like terraform validate, terraform plan, etc., which gives a greater level of control.
Now that we’ve learned about Terraform, Terraform Cloud, and
GitHub Actions as individual components, it is now time to stitch all of
them together to create a complete GitOps pipeline, which is what we’ll
see in the next chapter, so stay tuned!
220
CHAPTER 6
Practical GitOps
So far, we looked at Terraform, Terraform Cloud, and GitHub Actions
working in tandem. In this chapter, we’ll work on establishing a complete
workflow involving multiple branches (staging and main) and a peer
review process using GitHub Pull Requests. Without this complete
workflow, the GitOps process is incomplete as we need to know at every
commit what infrastructure changes are happening. We need to review
every PR (Pull Request) that clearly describes the terraform plan to show what changes are being proposed as part of that PR.
We’ll be using the same code base as earlier, of creating an EC2
instance on AWS; however, the workflow will be modified a little. We’ll be
adding some if conditions within our workflow file to have finer control over the execution of terraform commands.
We’ll be first deploying our changes into a staging environment
where complete end-to-end testing will be performed. Once the staging
environment changes are working as expected, we’ll then push the
changes into the production environment. This two-stage workflow adds
necessary redundancy as infrastructure changes need to be made with
caution and speed.
© Rohit Salecha 2023
221
R. Salecha, Practical GitOps,
Chapter 6 praCtiCal GitOps
6.1 Prerequisites
This chapter requires prerequisites from the previous chapters; however,
if you’ve jumped here directly (assuming you already have a working
knowledge of AWS, Terraform, Terraform Cloud, and GitHub Actions),
then you’ll need the following information handy:
–
Git and Terraform installed in your path
–
GitHub account
–
AWS credentials to set as environment variables in Terraform
Cloud workspaces:
–
AWS_ACCESS_KEY_ID
–
AWS_SECRET_ACCESS_KEY
–
Terraform Cloud account and Terraform Cloud Token to be
configured as GitHub Secret:
–
TF_API_TOKEN
6.2 Staging Environment Workflow
6.2.1 High-Level Plan
The following diagram will help us in understanding the staging
environment workflow.
222

Chapter 6 praCtiCal GitOps
Figure 6-1. High-level workflow for staging environment
• The developer forks the existing repository and creates
a new branch, let’s call it feature and then adds some
changes to the existing code base.
• After successfully testing the code by running it from
their own machine, they check in the changes in the
feature branch in the remote repository.
• They then create a new Pull Request (PR) to allow
merging of the changes into the staging environment
and assign a reviewer.
• When they raise the PR, the GitHub Actions workflow
will execute three steps:
• terraform init -backend-config=staging.hcl
• terraform validate
• terraform plan
223
Chapter 6 praCtiCal GitOps
• The reviewer will be able to view the output of the
preceding commands, and the primary interest for
the reviewer will be the output of the terraform plan
command, which will show them what's adding/
deleting/updating, etc.
• If the reviewer is happy with the changes, they’ll
approve the changes and then squash and merge them
into the staging branch.
• Once there is a push into the staging branch, the
workflow will execute again with all the preceding
three steps but with the addition of one more, that is,
terraform apply.
• With this, the changes will then start getting applied in
the staging environment in Terraform Cloud.
6.2.2 Code Walkthrough
We’ll be reusing the code from the previous chapter with the only
difference being in the workflow file and the backend.tf file.
CLI Output 6-1. Directory structure of the workflow folder
cmd> cd chapter6/.github/workflows
cmd> tree -a
.
├── app.yaml
├── infra-staging.yaml
└── infra-prod.yaml
0 directories, 3 files
224
Chapter 6 praCtiCal GitOps
In this chapter, we have three workflow files:
–
app.yaml – As discussed in the previous chapter, this file
orchestrates workflow for application Docker image
creation.
–
Infra-staging.yaml – This is a modified version of the
workflow file that we had created for terraform automa-
tion. However, as we’ll see later, it's modified specifically
for the staging environment.
–
Infra-prod.yaml – Same as staging but configured for
prod workflow.
Let’s look at the Infra-staging.yaml file.
Code Block 6-2. L03–27 explanation for infra-staging.yaml
File: code\.github\workflows\infra-staging.yaml
01: name: practicalgitops.staging.infra
02:
03: on:
04: push:
05: branches:
06: - staging
07: paths:
08: - "infra/**"
09: - "!**/README.md"
10: pull_request:
11: branches:
12: - staging
13: paths:
14: - "infra/**"
15: - "!**/README.md"
225
Chapter 6 praCtiCal GitOps
16:
17: jobs:
18: terraform:
19: name: "Terraform"
20: runs-on: ubuntu-latest
21: defaults:
22: run:
23: working-directory: "infra"
24: steps:
25: - name: Checkout
26: uses: actions/checkout@v2
27:
28: - name: Uncomment Terraform Cloud Backend
Configuration file
29: id: uncomment
30: run: sed -i 's/^#*//' backend.tf
L3–15: The conditions here are scouting for changes in the infra
directory (except for the README.md file). This file will be triggered by
either a push or a pull in the staging branch only.
L28–30: The backend.tf file, which is used for instructing terraform where to look for the terraform state file, is commented in the source code
as shown here.
CLI Output 6-3. Content of backend.tf commented
cmd> cat chapter6/infra/backend.tf
//Donot uncomment this file. This file will be uncommented only
in Github Actions
#terraform {
# backend "remote" {}
#}
226

Chapter 6 praCtiCal GitOps
Why have I commented this file?
This is required to be commented because as per the high-level plan we
discussed earlier, the developer needs to run terraform from their system as
well. If we keep the backend.tf configured, then the developer will need to
configure their own workspace in Terraform Cloud and maintain the access,
which would be an added responsibility on the developers.
Hence, it's best that the developers be provided a separate
environment where they can test their terraform code independent of
Terraform Cloud.
The following figure depicts the scenario.
Figure 6-2. Staging, prod, and dev environments of a developer
227
Chapter 6 praCtiCal GitOps
In this scenario, we’ll always have the staging and prod environments
live, and updates would be continuously pushed between them. The dev
environment for the developer will be created and destroyed at will.
There will be a certain level of synergy between the staging and prod
environments with the delta being changes in EC2 configurations (for
price sensitivity) and secrets (which we’ll learn in a separate chapter in
more detail).
As shown earlier in Section 3.7, if we wish to destroy resources in
staging and prod environments, we need not run terraform destroy; we can
quite simply comment out the code and then run terraform apply.
Hence, it is necessary to keep the backend.tf file commented and used
only during the workflow where we are uncommenting the file on L28–30.
Code Block 6-4. L29–47 explanation for infra-staging.yaml
File: chapter6\.github\workflows\infra-staging.yaml
38: - name: Terraform Init
39: id: init
40: run: terraform init -backend-config=staging.hcl
41:
42: - name: Terraform Validate
43: id: validate
44: run: terraform validate -no-color
45:
46: - name: Terraform Plan
47: id: plan
48: if: github.event_name == 'pull_request'
49: run: terraform plan -no-color
50: continue-on-error: true
228
Chapter 6 praCtiCal GitOps
L46–50: We saw previously how terraform plan is being executed.
However, in this scenario, we wish to run terraform plan whenever there is a PR raised for merging code. This is required because the reviewer
needs to see what's going to be executed. The entire output is available
for the reviewer to understand what are the changes being proposed in
this PR.
Code Block 6-5. L49–79 explanation for infra-staging.yaml
File: chapter6\.github\workflows\infra-staging.yaml
52: - name: Terraform Plan Output
53: uses: actions/github-script@v6
54: if: github.event_name == 'pull_request'
55: env:
56: PLAN: "terraform\n${{ steps.plan.outputs.
stdout }}"
57: with:
58: github-token: ${{ secrets.GITHUB_TOKEN }}
59: script: |
60: const output = `## Terraform Staging
Infra Plan
61:
62: #### Terraform Initialization \`${{ steps.
init.outcome }}\`
63: #### Terraform Validation \`${{ steps.
validate.outcome }}\`
64: #### Terraform Plan \`${{ steps.plan.
outcome }}\`
65:
66: <details><summary>Show Plan</summary>
229
Chapter 6 praCtiCal GitOps
67: ${process.env.PLAN}
68: </details>
69:
70: *Pusher: @${{ github.actor }}, Action: \`${{
github.event_name }}\`*`;
71:
72: github.rest.issues.createComment ({
73: issue_number: context.issue.number,
74: owner: context.repo.owner,
75: repo: context.repo.repo,
76: body: output
77: })
78:
79: - name: Terraform Plan Status
80: if: steps.plan.outcome == 'failure'
81: run: exit 1
82:
83: - name: Terraform Apply
84: if: github.ref == 'refs/heads/staging' && github.
event_name == 'push'
85: run: terraform apply
86:
L52–77: This is a GitHub Script that we are running wherein we
are capturing the output of Terraform Init, Terraform Validate, and
Terraform Plan. If you recall, as per our review process, the reviewer
needs to be able to see the terraform plan output to get an idea of what is
changing in the PR. This script helps in outputting that information in a
proper format, which we’ll see later. If you still don’t understand what’s
happening, just hold on till we actually see this in action.
230
Chapter 6 praCtiCal GitOps
L79–81: Since on L50 we had instructed the workflow to continue
execution even in the case our terraform plan is failing, we need to
perform a check somewhere.
L83: Here, we are performing terraform apply only when there is a
push to the staging branch. This git push could either be through a PR or
by directly pushing into the repository. This is where we need to restrict
access to the branch/repository so that only approved Pull Requests can
be merged into the staging branch. Whenever an approved Pull Request is
merged, there’ll be a push to the staging branch, and our terraform apply
will hence be executed.
The Infra-prod.yaml file is an exact replica of the preceding file with the only difference being that all operations are configured to work on the
main branch rather than the staging branch.
6.2.3 Set Up Terraform Staging Workspace
Although we’ve set up a terraform workspace multiple times by now and
you would be quite familiar with it, we’ll still repeat the steps one last time.
Visit the Terraform Cloud application and click on New Workspace.
Then select API-driven workspace as shown below.
231

Chapter 6 praCtiCal GitOps
Figure 6-3. Creating an API-driven workspace
Configure the name as staging. Once the workspace is created, visit the
variables tab and configure the following variables as shown:
–
Environment variables
–
AWS Access Key ID: AWS_ACCESS_KEY_ID
–
AWS Secret Access Key: AWS_SECRET_ACCESS_KEY
–
Terraform variables
–
Region: region: us-east-2
–
Environment: environment: staging
232

Chapter 6 praCtiCal GitOps
Figure 6-4. Configuring workspace variables
Note if you ever get a terraform version–related error, you can
modify the version in settings ➤ General and select the appropriate
terraform version, preferably 1.0.0 as the code is compatible with this
version at the time of writing this book.
6.2.4 Set Up a GitHub Repository
Now that our terraform workspace is configured, let’s set up a new GitHub
repository where all the GitHub actions will be executed.
Create a new GitHub repository with an appropriate name and
configure the following secrets:
–
TF_API_TOKEN (mandatory)
–
DOCKERHUB_TOKEN (optional)
–
DOCKERHUB_USERNAME (optional)
233

Chapter 6 praCtiCal GitOps
Figure 6-5. GitHub Secrets configuration
Note We at present do not require the Dockerhub credentials as we
won’t be modifying the Docker images any further. hence, i’ve kept it
optional throughout the book.
Let’s upload our code into the main branch of the repository using the
following commands.
Ensure you have the correct GitHub repository URL.
CLI Output 6-6. Uploading the code of Chapter o GitHub cmd> git init
cmd> git add .
cmd> git commit -m "first commit"
cmd> git branch -M main
cmd> git remote add origin <github-url>
cmd> git push -u origin main
You should be able to see the three actions being configured as part of
our repository as shown here.
234

Chapter 6 praCtiCal GitOps
Figure 6-6. All actions related to our repository
6.2.5 Executing Staging Workflow
Let’s configure the name of the Terraform Cloud workspace in the staging.
hcl file as shown here.
Code Block 6-7. Workspace name configuration
File: chapter6\infra\staging.hcl
1: workspaces { name = "staging" }
2: hostname = "app.terraform.io"
3: organization = "practicalgitops"
Now in order to execute our workflow, we’ll need to create a staging
branch and push the changes in that branch. Follow the following
commands, which will execute the entire process.
235
Chapter 6 praCtiCal GitOps
CLI Output 6-8. Pushing code into the staging branch
cmd> git status
On branch main
Your branch is up to date with 'origin/main'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working
directory)
modified: infra/staging.hcl
no changes added to commit (use "git add" and/or "git
commit -a")
cmd> git checkout -b staging
Switched to a new branch 'staging'
cmd(staging)> git add .
cmd(staging)> git commit -m "executing workflow"
[staging 0eec509] executing workflow
1 file changed, 1 insertion(+), 1 deletion(-)
cmd(staging)> git push origin staging
Enumerating objects: 7, done.
XXXXXXXXXXXXXXX------SNIPPED-------XXXXXXXXXXXXXXXXXXXXX
To github.com:practical-gitops/chapter6.git
* [new branch] staging -> staging
As soon as we push our changes into the git repository, a new branch
called staging is created, and the practicalgitops.staging.infra workflow is executed as shown here.
236


Chapter 6 praCtiCal GitOps
Figure 6-7. Staging branch workflow executing
The workflow will run through all the stages except terraform plan, as
we’ve configured it to run only during Pull Requests as shown here.
Figure 6-8. Terraform apply for staging branch workflow
As can be seen from the preceding screenshot, the workflow has
paused its execution and is waiting for our approval on Terraform Cloud as
shown here.
237


Chapter 6 praCtiCal GitOps
Figure 6-9. Staging branch workflow waiting for approval on
Terraform Cloud
Let’s go ahead and approve the plan so that our staging environment is
up and running.
Figure 6-10. Staging branch workflow after terraform apply
238

Chapter 6 praCtiCal GitOps
If you would like to disable the manual approval process in Terraform
Cloud and allow the plan to execute once approved in GitHub, then you
can visit Settings ➤ General and scroll down to the Apply Method section
shown in the following where you can select the Auto apply check box and
save. By default, the Manual apply setting is configured.
Figure 6-11. Disabling manual apply in terraform workspace
configuration
6.3 Prod Environment Workflow
Now that our staging environment is up and running and everything looks
good, let’s go ahead and set up our production environment as well.
We’ll need a separate prod terraform workspace, which can be set up
using the same instructions as discussed in the previous section. The only
difference that will be needed is that we’ll need a separate set of keys and a
separate region for deployment.
So I’ve created a new terraform workspace called prod and configured
the necessary environment variables as shown in the following. My prod
environment will run in the us-east-1 region.
239

Chapter 6 praCtiCal GitOps
Figure 6-12. Prod environment workspace configuration
6.3.1 SSH Key-Pair Generation
Before we go ahead and execute our prod workflow, there is one tiny step
that we need to perform on the AWS Console. We’ve configured our EC2
instance with a key pair with name gitops as discussed in Section 2.3.7.
However, that key pair was created in the us-east-2 region, and here, we
are spinning up the server in the us-east-1 region where this key pair is not available.
Hence, we need to create this one manually using the steps outlined in
the following.
Note Going forward, we may not require to have an ssh key as
we’ll be spinning up an eKs cluster and not any eC2 machine.
Execute the following commands, which will generate the new AWS
key pair and also save it to your disk.
240
Chapter 6 praCtiCal GitOps
CLI Output 6-9. AWS SSH key-pair generation
cmd> export AWS_PROFILE=gitops
cmd> aws --region us-east-1 ec2 create-key-pair \
--key-name "gitops" \
--query 'KeyMaterial' > gitops.pem
cmd> ls -al gitops.pem
-rw-r--r-- 1 ubuntu ubuntu 1707 Dec 2 17:55 gitops.pem
Now let’s go ahead and execute our prod workflow for which we’ll need
to first edit the prod.hcl as shown in the following with the name of the
workspace created for the prod environment.
Code Block 6-10. Prod workspace backend configuration
File: chapter6\infra\prod.hcl
1: workspaces { name = "prod" }
2: hostname = "app.terraform.io"
3: organization = "practicalgitops"
CLI Output 6-11. Checking-in prod.hcl for prod workflow trigger
cmd(staging)> git checkout main
cmd> git status
On branch main
Your branch is up to date with 'origin/main'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working
directory)
modified: infra/prod.hcl
241

Chapter 6 praCtiCal GitOps
no changes added to commit (use "git add" and/or "git
commit -a")
cmd> git add .
cmd> git commit -m "executing prod workflow"
cmd> git push origin main
Once the code is checked in, our prod workflow will get kickstarted as
shown here.
Figure 6-13. Prod workflow being triggered
Just like the staging workflow, here too, the process is awaiting our
approval on Terraform Cloud. Let’s confirm the execution on Terraform
Cloud and allow the terraform apply to build the prod environment.
So now we have our staging as well as our prod environment up and
running with two servers in two different regions (and/or two different
AWS accounts).
242

Chapter 6 praCtiCal GitOps
Figure 6-14. Prod workspace apply completed
6.4 Complete Workflow
Now that we are having a server live on the Internet, one of our colleagues
wishes to add one more. They’ll need to follow the following process:
–
Clone the repository and switch to the staging branch.
–
Make their changes.
–
Test the changes by running terraform from their local
machine in a dev environment that is completely isolated
from the staging and prod environments.
–
Create another branch; let’s call it feature1 branch.
–
Commit all the changes into the feature1 branch.
243
Chapter 6 praCtiCal GitOps
–
Push the changes into the remote
repository/feature1 branch.
–
Create a Pull Request from the feature1 to the stag-
ing branch.
–
Assign a reviewer and wait for approval.
–
Once the changes are reviewed and approved, the
reviewer shall merge and squash the changes into the
staging branch.
–
Our staging branch workflow will execute and set up our
new server into the staging environment.
–
Once everything is fine in the staging environment, then
we’ll create another PR from staging to main.
–
Once again, there will be a PR review process.
–
Once review is approved, changes will be merged into the
main branch, and our prod execution flow will be
triggered.
The following diagram is a depiction of the entire process described
previously.
244

Chapter 6 praCtiCal GitOps
Figure 6-15. Complete workflow diagram
6.4.1 New Feature Request
Let’s practically experience the workflow described previously. Let’s say
one of our colleagues wishes to set up another server. They’ll first need to
add the following code in the main.tf file and the output.tf file as shown in
the following.
Note For simplicity’s sake, i’ve already added that code in both files
in a commented format. You’ve only got to uncomment it. standard
drill :-).
245
Chapter 6 praCtiCal GitOps
Code Block 6-12. Adding EC2 instance in main.tf
File: chapter6\infra\main.tf
XXXXXXXXXXXXXXX------SNIPPED-------XXXXXXXXXXXXXXXXXXXXX
81: # Uncomment for Section 6.4.1 - New Feature Request
82: resource "aws_instance" "apache2_server_1" {
83: ami = data.aws_ami.ubuntu.id
84: instance_type = var.instance_type
85: vpc_security_group_ids = [module.http_sg_ingress.sg_id,
86: module.generic_sg_egress.sg_id,
87: module.ssh_sg_ingress.sg_id]
88: key_name = var.ssh_key_name
89: user_data = file("./scripts/user_data.sh")
90: tags = {
91: env = var.environment
92: Name = "ec2-${local.name-suffix}"
93: }
94:
95: depends_on = [
96: module.generic_sg_egress
97: ]
98: }
Code Block 6-13. Adding output configuration in output.tf
File: chapter6\infra\output.tf
XXXXXXXXXXXXXXX------SNIPPED-------XXXXXXXXXXXXXXXXXXXXX
6: # Uncomment for Section 6.4.1 - New Feature Request
7: output "ip_address_1" {
8: value = aws_instance.apache2_server_1.public_ip
9: }
246
Chapter 6 praCtiCal GitOps
6.4.2 Developer Testing
Now before we check in the code, let’s first run it on our system and ensure
that the code we are pushing in is running correctly.
One small change that you’ll observe when running the terraform
commands is that this time, terraform will ask for region and environment
as these two variables were hard-coded in our terraform workspace and
we’ve not specified the value of these variables anywhere in our code base.
Hence, there are two ways we can provide this information:
1. When we execute terraform apply, it’ll ask for this
information interactively.
2. We can specify it as a CLI argument for
terraform apply.
I don't prefer interactive interfaces; hence, I’ll go ahead with option 2,
that is, to specify it as CLI arguments.
Note ensure that you are not specifying the same value as staging
or prod, else there’ll be conflicts. in practice, a new aWs organization
should be created for testing purposes to ensure proper isolation.
The following commands will set up a dev environment in the us-
west- 1 region. Since we do not have an SSH key in that region, we first
need to create one. It is strongly recommended to have a separate key per
region/account.
CLI Output 6-14. Testing environment set up for local terraform
execution
cmd> export AWS_PROFILE=gitops
cmd> aws --region us-west-1 ec2 create-key-pair \
--key-name "gitops" \
247
Chapter 6 praCtiCal GitOps
--query 'KeyMaterial' > gitops_dev.pem
cmd> terraform init
cmd> terraform plan -var="environment=dev"
-var="region=us-west-1"
cmd> terraform apply --auto-approve \
-var="environment=dev" -var="region=us-west-1"
aws_instance.apache2_server: Creating...
aws_instance.apache2_server_1: Creating...
XXXXXXXXXXXXXXX------SNIPPED-------XXXXXXXXXXXXXXXXXXXXX
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Outputs:
ip_address = "13.57.212.113"
ip_address_1 = "54.183.78.83"
cmd> terraform destroy --auto-approve \
-var="environment=dev" -var="region=us-west-1"
Note ensure to execute the destroy command before moving ahead
as you’ll be billed for two eC2 instances.
6.4.3 Create Pull Request – Dev
Now that our code is running fine and it created two servers as expected,
let's go ahead and create another branch feature1 and check in these
changes in it. Then push the changes to the remote repository using the
following commands.
248

Chapter 6 praCtiCal GitOps
CLI Output 6-15. Pushing changes into the feature1 branch
cmd> git status
On branch main
Your branch is up to date with 'origin/main'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working
directory)
modified: infra/main.tf
modified: infra/output.tf
no changes added to commit (use "git add" and/or "git
commit -a")
cmd> git checkout -b feature1
cmd( feature1)> git add .
cmd feature1> git commit -m "new ec2 added"
cmd feature1> git push origin feature1
Let’s visit our GitHub repository online and create a Pull Request by
clicking on the tab as shown.
Figure 6-16. Clicking the Pull requests tab
249


Chapter 6 praCtiCal GitOps
Click New pull request as shown here.
Figure 6-17. Clicking New pull request
Now we need to select the branches that we need to merge as part of
this Pull Request as shown here.
1. Select the base branch as staging.
2. Select the compare branch as feature1.
3. Add an appropriate comment and click Create pull
request.
Figure 6-18. Creating a Pull Request between staging and feature1
branches
250

Chapter 6 praCtiCal GitOps
Next is a very important step as we need to inform the team what this
Pull Request is all about.
1. Provide a descriptive title.
2. Add comments describing what is changing in this
Pull Request.
3. Click
Create pull request.
Figure 6-19. Pull Request comments
251

Chapter 6 praCtiCal GitOps
As soon as the Pull Request is created, our GitHub action gets executed
as shown here.
Figure 6-20. GitHub Actions triggered after Pull Request
Finally, once the GitHub action execution is complete, you should see
the output of the GitHub Script as shown in the following where it shows
all our stages.
Terraform Initialization, Terraform Validation, and Terraform Plan are successfully completed.
252

Chapter 6 praCtiCal GitOps
Figure 6-21. Terraform init, validate, and plan review screen
As you scroll down, you should be able to see the entire plan as
shown here.
253


Chapter 6 praCtiCal GitOps
Figure 6-22. Viewing the part of terraform plan
If everything is fine, then we can go ahead and merge the Pull Request
by clicking on the Merge pull request button as shown here.
Figure 6-23. Clicking Merge pull request
Once the PR is merged, navigate to the Actions tab and you’ll see
GitHub Actions executing the merge request. This time, the workflow will
perform terraform apply.
254


Chapter 6 praCtiCal GitOps
Figure 6-24. GitHub actions triggered upon merging Pull Requests
If you navigate to the Terraform Cloud application’s staging workspace,
you should see that our plan is awaiting our approval.
Figure 6-25. Terraform Cloud staging workspace awaiting approval
Let’s go ahead and apply the plan. After a few minutes, you can see in
the output section, it should show two IP addresses.
255


Chapter 6 praCtiCal GitOps
Figure 6-26. Terraform Output Two IPs of staging workspace
6.4.4 Create Pull Request – Prod
Once everything is working fine in the staging environment, we are
now ready to create a Pull Request and deploy the changes in the Prod
environment.
Visit the Pull Request page again, but this time around
1. Select the base branch as main.
2. Select the compare branch as staging.
3. Click
Create pull request.
Figure 6-27. Creating a Pull Request between main and staging
branches
256

Chapter 6 praCtiCal GitOps
Exactly the same procedure can be followed while creating the PR,
and as shown in the following, our GitHub Actions will be executed
again as there is a Pull Request received on the main branch. Hence, our
practicalgitops.prod.infra workflow is now being executed as shown here.
Figure 6-28. GitHub Action being executed on the prod branch
after PR
Let’s confirm and merge the Pull Request after merging our next
GitHub action in execution to perform terraform apply just like the
previous steps.
If we now navigate to our Terraform Cloud application, we can see our
terraform apply is waiting for approval.
257


Chapter 6 praCtiCal GitOps
Figure 6-29. Prod workspace planned for terraform apply
Let’s go ahead and confirm the terraform apply, and in a few minutes,
we’ll have our changes pushed in the prod environment as shown here.
Figure 6-30. Prod environment up and running
258

Chapter 6 praCtiCal GitOps
We now have two EC2 servers deployed in two different regions (and
environments) through the GitOps pipeline that we’ve created!
6.5 Clean-Up
Clean-up for this section should be fairly simple as here we have executed
everything from Terraform Cloud. Navigate to Settings ➤ Destruction and
Deletion of the workspace that we wish to destroy and then click Queue
destroy plan. It’ll ask for your confirmation like as follows.
Figure 6-31. Destroying resources in the prod environment
Please perform the same for staging as well.
Note Do not delete the staging and prod workspaces created in
this chapter as we’ll be reusing them in the upcoming chapters.
however, ensure you delete the resources by applying the destroy
plan discussed previously.
259

Chapter 6 praCtiCal GitOps
It is also highly recommended that you visit the EC2 console on
the AWS GUI and double-check if everything is deleted. The Instances
(running) field should show 0 for all regions.
Figure 6-32. Reconfirming the number of running instances in the
EC2 dashboard
Note always double-check the region; we’ve been following us-
east- 2, us-east-1, and us-west-1 in this chapter.
6.6 Conclusion
In this chapter, we looked at how an end-to-end GitOps workflow can
be orchestrated using GitHub SCM and GitHub Actions with a multi-
environment strategy. This workflow makes it easy to audit and control the
changes that are being pushed into our AWS infrastructure. This workflow
is also quite extensible as we’ll see in the later chapters, where we can
add various different checks like security and validation checks in an
automated fashion.
260
Chapter 6 praCtiCal GitOps
The customized output of terraform plan provides a good insight for
reviewers to be able to make proper decisions.
With this, we’ve completed the first part (i.e., Part A) of this book where
I wanted to practically demonstrate the GitOps process and the different
tools and techniques that can be used to facilitate it.
In Part B of this book, we’ll use this process and implement it for a
practical application built using the Spring Boot technology and run it in
an AWS EKS environment, communicate with an AWS RDS Postgres server,
and utilize a combination of an AWS Load Balancer and AWS Route53 to
expose the application to the outside world. All the AWS technologies,
that is, AWS EKS, RDS, Route53, and the Load Balancer, will be created
using Terraform, managed in Terraform Cloud, and orchestrated through
GitHub Actions. We’ll look at different aspects of managing this complex
infrastructure like Authentication, Authorization, Secrets Management,
Security Tooling, and Operations all in all through our GitOps process that
we’ve completed till now.
261
CHAPTER 7
Spring Boot App
on AWS EKS
In this chapter, we’ll look at the architecture and code of the deployment
of a working application developed in Spring Boot. We saw the process
of building and pushing the docker image of the application using
GitHub Actions in the previous chapter. Now we’ll use that docker image
in a Kubernetes deployment and deploy a complete AWS EKS (Elastic
Kubernetes Service) and PostgreSQL RDS using Terraform.
7.1 Prerequisites
From here onward, we’ll be utilizing the same setup for all the upcoming
chapters; hence, in addition to all the previous requirements, we’ll need
the following:
–
A top-level domain/subdomain name – This is very
important as most operations depend on it. I’ll be
discussing how you can configure the same.
–
kubectl utility – kubectl is the default utility to talk with
the Kubernetes service that we’ll be spinning up using
Terraform. Recommended to have version 1.20 or above.
As of the writing of the book, my kubectl version is 1.23.6:
–
© Rohit Salecha 2023
263
R. Salecha, Practical GitOps,
Chapter 7 Spring Boot app on aWS eKS
7.1.1 GitHub Repository
From now onward, we’ll utilize a single GitHub repository for the rest of
the book. Hence, it is important to create a folder in the root directory
wherever you’ve unzipped the repository.
I’ll name the folder pgitops, and for every chapter, we’ll copy the
code from the respective folder into this folder and initialize the GitHub
repository from here itself. Name of this folder and repository is arbitrary.
The following commands will help you in the *nix system; however, feel
free to use any GUI software for overwriting the files if CLI doesn’t work
for you.
CLI Output 7-1. Copying code into a single directory
cmd> mkdir pgitops
cmd> cp -a chapter7/. pgitops/
cmd> cd pgitops
# Ensure the copy command worked as expected by executing
tree command
cmd> tree -L 2
.
├── README.md
├── app
│ ├── Dockerfile
│ ├── README.md
│ ├── docker-compose.yaml
│ ├── pom.xml
│ ├── src
│ └── target
└── infra
├── README.md
├── app.tf
264
Chapter 7 Spring Boot app on aWS eKS
├── backend.tf
├── dns.tf
├── eks.tf
├── kubernetes.tf
├── modules
├── networking.tf
├── prod.hcl
├── providers.tf
├── rds.tf
├── staging.hcl
├── terraform.auto.tfvars
└── variables.tf
5 directories, 18 files
Let’s now create a GitHub repository and upload our code in it. I’ve
created a repo called pgitops, and using the following commands, I’ve
uploaded the code into the GitHub repository.
CLI Output 7-2. Pushing code into a new repo
cmd> git init
cmd> git add .
cmd> git commit -m "first commit"
cmd> git branch -M main
cmd> git remote add origin [email protected]:<username>/
pgitops.git
cmd> git push -u origin main
You can verify that the code has been pushed by visiting the GitHub
repository on the browser. Also open the Actions tab to view that our
actions are now ready for deployment.
265

Chapter 7 Spring Boot app on aWS eKS
Figure 7-1. Verifying that code is pushed to GitHub
Next, we need to add the Terraform Token that was discussed in
the previous chapters, which the GitHub Actions utilize to execute the
Terraform API in the TF cloud.
The following image highlights the steps that can be utilized to
configure TF_API_TOKEN as a secret.
1. Access the Settings page of your repository.
2. Click Secrets ➤ Actions.
3. Add TF_API_TOKEN as the Name and the value of
the token.
266

Chapter 7 Spring Boot app on aWS eKS
Figure 7-2. Adding TF_API_TOKEN
Terraform Cloud Workspace
We’ll be reusing the prod and staging Terraform Cloud workspaces (API
driven) created in Chapter
The only small change that we need to make is to remove manual
approval required every time Terraform is triggered because now we are
controlling everything from our GitHub.
This can be easily configured by visiting Settings ➤ General Settings
➤ Execution Mode ➤ Auto Apply as shown here.
267

Chapter 7 Spring Boot app on aWS eKS
Figure 7-3. Switching to Auto apply
7.1.2 DNS Configuration
In order to fully understand the potential or any cloud technology and for
the best experience, it’s worthwhile to play around with domain names.
It’s strongly recommended that you get one if you don’t have one and put
it to use if you have it. I shall hence consider two possibilities, either of
which is fine to follow for the remainder of this book. I shall explain both
possibilities as follows.
Route53 is a service provided by AWS to help us manage our DNS
records. Traditionally, DNS records are managed through a file called
“Zone” files, and Route53 makes use of what are called “Hosted Zones,”
which are a container of DNS records on similar lines of the traditional
zone files. Whenever a DNS Hosted Zone is created, AWS provides a set
of nameserver records (four records), which need to be configured in
your root domain. The way these records will be configured is different
depending on your choice of configuring a domain name or a subdomain
name, which we’ll discuss next.
268
Chapter 7 Spring Boot app on aWS eKS
Note it doesn’t matter from where you’ve purchased your DnS
domain or where you are currently maintaining your DnS records to
follow ahead. the only thing that is expected is that you’ve got the
ability to update the DnS records at your current hosting provider in
order to follow the following steps. For example, i had purchased my
domain rohitsalecha.com from goDaddy.com; however, i am currently
hosting my domain on aWS route53, which is chargeable and not
included in the free tier. in order to do that, i had to update the default
nameserver records in goDaddy with the ones provided by aWS (after
configuring my hosted zone in aWS route53).
As discussed earlier, there are primarily two ways in which we can
configure our Route53 Hosted Zones: either as a top-level domain or as a
subdomain.
Top-Level Domain
If you wish to configure a top-level domain name for deploying our
application, then fire the following command, replacing rohitsalecha.com
with your domain name.
CLI Output 7-3. AWS Route53 Hosted Zone for TLD
cmd> export AWS_PROFILE=gitops
cmd> aws route53 create-hosted-zone --name rohitsalecha.com
--caller-reference 2022-05-15-22:35
{
XXXXXXXXXXXXXXX------SNIPPED-------XXXXXXXXXXXXXXXXXXXXX
"DelegationSet": {
"NameServers": [
"ns-849.awsdns-42.net",
269
Chapter 7 Spring Boot app on aWS eKS
"ns-290.awsdns-36.com",
"ns-1129.awsdns-13.org",
"ns-1713.awsdns-22.co.uk"
]
}
}
The command output provides four different nameserver values that
need to be configured with your DNS provider by overriding their current
values. You’ll need to refer to your hosting/DNS provider documentation
to be able to do the same.
For example, if you have GoDaddy as your DNS provider, then follow
this g
Note if you’ve done for top-level domain, do not repeat this for
subdomain.
Subdomain
If you already own a top-level domain and wish to utilize a subdomain
only, then you fire the following command and replace your subdomain
with gitops.rohitsalecha.com.
CLI Output 7-4. AWS Route53 Hosted Zone for subdomain
cmd> export AWS_PROFILE=gitops
cmd> aws route53 create-hosted-zone --name gitops.rohitsalecha.
com --caller-reference 2022-05-15-24:35
{
XXXXXXXXXXXXXXX------SNIPPED-------XXXXXXXXXXXXXXXXXXXXX
270
Chapter 7 Spring Boot app on aWS eKS
"DelegationSet": {
"NameServers": [
"ns-537.awsdns-03.net",
"ns-449.awsdns-56.com",
"ns-1848.awsdns-39.co.uk",
"ns-1415.awsdns-48.org"
]
}
}
The command will output a set of nameservers, which you need to add
as a record just like how you would add any other record in your DNS zone.
You’ll need to refer to the documentation of your DNS service provider on
how to perform the same.
For example, in GoDaddy, this is how you can add an NS as a record:
Note Both the preceding techniques have different implications; for
top-level domain, we are modifying the primary nameserver records,
whereas for subdomain, we are only adding a nameserver record
against the subdomain. this adding of nameservers as a record in
your DnS zone is also called subdomain delegation. effectively we
are delegating the subdomain to another provider. an important point
to note here is that not all DnS providers support this.
Delete Route53 Records
While we are doing things manually here, in the next chapter, we are going
to use Terraform to create hosted zones for us. If you’ve configured your
subdomain/top-level domain as per your requirement, then you skip the
following step.
271
Chapter 7 Spring Boot app on aWS eKS
If in case you’d like to change your mind, that is, if you’ve created a top-
level domain as a hosted zone record and wish to move to a subdomain
hosted zone or vice versa, then simply fire the following commands to
delete your hosted zone record.
CLI Output 7-5. Listing and deleting Route53 Hosted Zones
# List Hosted Zones configured in the account
cmd> export AWS_PROFILE=gitops
cmd> aws route53 list-hosted-zones
{
"HostedZones": [
{
"Id": "/hostedzone/Z0239613358THNF4E3XP9",
"Name": "gitops.rohitsalecha.com.",
"CallerReference": "2022-02-20-24:35",
"Config": {
"PrivateZone": false
},
"ResourceRecordSetCount": 2
}
]
}
# Delete the Hosted Zone record
cmd> aws route53 delete-hosted-zone --id Z0239613358THNF4E3XP9
{
"ChangeInfo": {
"Id": "/change/C025940016DSY04M1XJ6W",
"Status": "PENDING",
"SubmittedAt": "2022-05-19T17:12:25.570000+00:00"
}
}
272

Chapter 7 Spring Boot app on aWS eKS
Warning Before moving ahead, ensure that you have your route53
records configured either as a subdomain or as a top-level domain.
7.2 Solution Architecture
Figure 7-4. High-level solution architecture
Note only main components are displayed here. representation of
many other small components has been skipped for simplicity.
The preceding diagram shows a high-level architecture of the solution that
we’ll be building. Other than Route53, rest assured everything will be set
up using Terraform in this chapter. A brief description follows:
–
We’ll be creating a VPC with three subnets, namely,
public, private, and database.
–
Public subnet will have resources that need to be
externally facing like the AWS Application Load
Balancer, which will be spun up as an ingress controller.
273
Chapter 7 Spring Boot app on aWS eKS
–
Our Kubernetes Nodes (managed by us) will be spun
up in the private subnet and will not be accessible from
the Internet.
–
The master node of Kubernetes is completely managed
by AWS, and hence, we have no control over its
placement. However, the Kubernetes API endpoint will
be available over 443 on the Internet for authentication
to the EKS cluster.
–
Our PostgreSQL database is living in the database
subnet, which is a separate subnet having additional
security controls.
–
The connectivity between the public subnet and the
private subnet is established using a NAT gateway.
There is no NAT gateway to route traffic between
database subnet and public subnet; hence, it is
sufficiently isolated.
–
We’ll be utilizing AWS Certificate Manager (ACM) to
generate the public SSL certificates for our application.
Our Terraform code will not only generate the
certificates but also validate the certificates by creating
necessary Route53 name records.
Let’s dive into the infra folder where all our terraform code resides,
and let’s try to have an approximate mapping of the different files with the
preceding solution architecture that we are building.
CLI Output 7-6. Contents of the infra folder
cmd> cd infra
cmd> tree -L 1
.
274
Chapter 7 Spring Boot app on aWS eKS
├── README.md
├── app.tf
├── backend.tf
├── dns.tf
├── eks.tf
├── kubernetes.tf
├── modules
├── networking.tf
├── prod.hcl
├── providers.tf
├── rds.tf
├── staging.hcl
├── terraform.auto.tfvars
└── variables.tf
1 directory, 13 files
–
app.tf – Defines Kubernetes deployments, services, and
ingresses that are required to be deployed in the EKS
environment.
–
dns.tf – DNS configurations and Load Balancer
mapping required.
–
eks.tf – Declaring our master and worker nodes that are
needed to set up the entire Kubernetes environment.
–
kubernetes.tf – This is a Kubernetes provider file that
needs to be maintained separately and provides
configuration information on how Terraform can
authenticate to Kubernetes to deploy Kubernetes
resources like pods, services, etc.
–
modules – Consists of reusable components for the
infrastructure.
275

Chapter 7 Spring Boot app on aWS eKS
–
networking.tf – Defines the creation of VPC and
Security Groups necessary for deploying EKS and RDS.
–
rds.tf – Creates the PostgreSQL database and the
necessary secrets.
7.3 Networking
Let’s first run through how the network plumbing is happening using
Terraform. The following diagram gives a glimpse of what we are setting up
using Terraform from a network perspective.
Figure 7-5. High-level network architecture
Note i’ve captured only the primary components for simplicity.
there are many other components like the naCLs (network access
Control Lists), route tables, Security groups, etc., which are not
shown here but are created in our terraform code.
276
Chapter 7 Spring Boot app on aWS eKS
So the code defined in the pgitops\infra\networking.tf creates the
following:
–
A VPC with a CIDR range of 10.0.0.0/16.
–
An Internet gateway to route traffic to the Internet.
–
A public subnet with a NAT gateway to route all
Internet ingress/egress traffic.
–
A private subnet wherein resources can access the
Internet through the NAT gateway but not vice versa;
that is, nobody can access the resources defined in the
private subnet from the Internet.
–
A database subnet where we can optionally restrict
access to only within the VPC.
All the code related to networking can be found in the networking.tf file
as follows.
Code Block 7-7. Contents of the networking.tf file
File: pgitops\infra\networking.tf
XXXXXXXXXXXXXXX------SNIPPED-------XXXXXXXXXXXXXXXXXXXXX
28: module "clustername" {
29: source = "./modules/clustername"
30:
31: environment = var.environment
32: region = var.region
33: org_name = var.org_name
34: }
35:
277
Chapter 7 Spring Boot app on aWS eKS
36: # Create a VPC with all the associated network plumbing
37: module "networking" {
38: source = "./modules/vpc"
39:
40: clustername = module.clustername.cluster_name
41: environment = var.environment
42: vpc_cidr = var.vpc_cidr
43: public_subnets_cidr = var.public_subnets_cidr
44: private_subnets_cidr = var.private_subnets_cidr
45: database_subnets_cidr = var.database_subnets_cidr
46: region = var.region
47: availability_zones = data.aws_availability_zones.
availability_zones.names
48:
49: depends_on = [module.clustername]
50: }
XXXXXXXXXXXXXXX------SNIPPED-------XXXXXXXXXXXXXXXXXXXXX
L28–34: Here, we are creating a simple string that helps differentiate
uniquely the environment, the region, and the name of the organization
running this terraform code. For example, if for gitops organization we
are deploying in us-east-1 in the staging environment, then we can have
the clustername as gitops-us-east-1-staging. This string can be used to
concatenate with various resources for better identification.
L37–50: Defining the entire VPC requirements by providing the
environment, CIDR block for the VPC, and public, private, and database
subnets. Region and the availability zones in which this VPC should be
deployed. The values of the variables being passed are all set in terraform.
auto.tfvars files. This file holds the values of all variables that can be changed depending on the environment which we wish to deploy.
278
Chapter 7 Spring Boot app on aWS eKS
7.3.1 Network Module
Let’s dive into the module/vpc folder and take a look at the
implementation of the networking module that was discussed previously.
Code Block 7-8. Contents of the VPC module
File: pgitops\infra\modules\vpc\main.tf
01: # Create a VPC
02: module "vpc" {
03: source = "terraform-aws-modules/vpc/aws"
04: version = "3.12.0"
05:
06: name = "${var.clustername}-vpc"
07: cidr = var.vpc_cidr
08: enable_dns_hostnames = true
09: enable_dns_support = true
10: enable_nat_gateway = true
11: single_nat_gateway = true
12: enable_vpn_gateway = var.enable_vpn_gateway
13: azs = var.availability_zones
14: private_subnets = var.private_subnets_cidr
15: public_subnets = var.public_subnets_cidr
16: database_subnets = var.database_subnets_cidr
17: create_database_subnet_group = true
L3: Terraform maintains a registry of modules developed and
maintained by folks at HashiCorp and can be accessed a
. These modules can be easily integrated into our code by supplying the expected
input values and are developed with best practices. We’ll be making
extensive use of two such modules developed by Terraform internally:
279
Chapter 7 Spring Boot app on aWS eKS
VPC Module –
EKS Module –
RDS Module –
It’s strongly recommended to use these modules as a best practice. As
you’ll see, I’ve created quite a lot of modules that are simple to follow and
reusable for our current requirements.
Note Before updating the versions of these modules, ensure to read
through the corresponding release notes on github as there can be
breaking changes. hence, for this book, i’ve frozen the versions of the
VpC and eKS modules as can be seen in the code.
7.3.2 Network Tags
AWS EKS internally uses tags attached to resources to perform discovery;
hence, it’s very important to add the correct tags for operational reasons.
Code Block 7-9. Tags attached to VPC
File: pgitops\infra\modules\vpc\main.tf
XXXXXXXXXXXXXXX------SNIPPED-------XXXXXXXXXXXXXXXXXXXXX
18:
19: tags = {
20: "kubernetes.io/cluster/${var.clustername}" = "shared"
21: "Name" = "${var.
cluster
name}-vpc"
280
Chapter 7 Spring Boot app on aWS eKS
22: "Environment" = var.
environment
23: terraform-managed = "true"
24: }
25:
26: public_subnet_tags = {
27: "kubernetes.io/cluster/${var.clustername}" = "shared"
28: "kubernetes.io/role/elb" = "1"
29: }
30:
31: private_subnet_tags = {
32: "kubernetes.io/cluster/${var.clustername}" = "shared"
33: "kubernetes.io/role/internal-elb" = "1"
34: }
35: }
L19–34: While working with EKS, it is mandatory to add a tag as
kubernetes.io/cluster/$cluster_name is used for networking discovery and
management by AWS internally. Similarly, when creating Load Balancers
in addition to this tag, we are required to add kubernetes.io/role/elb for external facing and kubernetes.io/role/internal-elb for internal Application Load Balancers. We’ll understand a bit more about the relation of this tag
with load balancers in Section 7.7.
In addition to the preceding tags, I’ve added a few optional tags like
environment and terraform-managed for better understanding of the
resource placement and ownership. Both these tags will appear in all
resources that are created through Terraform. This is important as when
your infrastructure grows, there may arise situations where you need
to manually create/update/delete infrastructure. In such situations, it
becomes easier to understand which infrastructure was created manually
and which was created through Terraform.
Reference:
281
Chapter 7 Spring Boot app on aWS eKS
7.3.3 Security Groups
In addition to the preceding network configuration in the pgitops\infra\
networking.tf, we’ve also specified two security groups as shown here.
Code Block 7-10. Security Groups that need to be attached
File: pgitops\infra\networking.tf
XXXXXXXXXXXXXXX------SNIPPED-------XXXXXXXXXXXXXXXXXXXXX
52: # PGSQL Ingress Security Group Module
53: module "pgsql_sg_ingress" {
54: source = "./modules/securitygroup"
55:
56: sg_name = "pgsql_sg_ingress"
57: sg_description = "Allow Port 5432 from within the VPC"
58: environment = var.environment
59: vpc_id = module.networking.vpc_id
60: type = "ingress"
61: from_port = 5432
62: to_port = 5432
63: protocol = "tcp"
64: cidr_blocks = [var.vpc_cidr]
65:
66: depends_on = [module.networking]
67: }
68:
69: # Generic Egress Security Group Module
70: module "generic_sg_egress" {
71: source = "./modules/securitygroup"
72:
73: sg_name = "generic_sg_egress"
282
Chapter 7 Spring Boot app on aWS eKS
74: sg_description = "Allow servers to connect to outbound
internet"
75: environment = var.environment
76: vpc_id = module.networking.vpc_id
77: type = "egress"
78: from_port = 0
79: to_port = 65535
80: protocol = "tcp"
81: cidr_blocks = ["0.0.0.0/0"]
82:
83: depends_on = [module.networking]
84: }
L53–67: The first security group is created to allow all communication
within the VPC with the 5432 database port. This is a very open
configuration and can be limited to a specific CIDR, which we’ll see in the
later chapters.
L70–84: This is a generic security group that allows outbound access to
any resource it is attached to.
7.4 Database
Our application needs a database to store the data recorded by the users.
In this section, we will learn about how we can set up a PostgreSQL