Книга: Practical GitOps
На главную: Предисловие
Дальше: 3.9 Clean-Up

Practical GitOps

Infrastructure Management

Using Terraform, AWS,

and GitHub Actions

Rohit Salecha

Practical GitOps: Infrastructure Management Using Terraform, AWS, and

GitHub Actions

Rohit Salecha

Mumbai, India

ISBN-13 (pbk): 978-1-4842-8672-2

ISBN-13 (electronic): 978-1-4842-8673-9

Copyright © 2023 by Rohit Salecha

This work is subject to copyright. All rights are reserved by the Publisher, whether the whole or part of the material is concerned, specifically the rights of translation, reprinting, reuse of illustrations, recitation, broadcasting, reproduction on microfilms or in any other physical way, and transmission or information storage and retrieval, electronic adaptation, computer software, or by similar or dissimilar methodology now known or hereafter developed.

Trademarked names, logos, and images may appear in this book. Rather than use a trademark symbol with every occurrence of a trademarked name, logo, or image we use the names, logos, and images only in an editorial fashion and to the benefit of the trademark owner, with no intention of infringement of the trademark.

The use in this publication of trade names, trademarks, service marks, and similar terms, even if they are not identified as such, is not to be taken as an expression of opinion as to whether or not they are subject to proprietary rights.

While the advice and information in this book are believed to be true and accurate at the date of publication, neither the authors nor the editors nor the publisher can accept any legal responsibility for any errors or omissions that may be made. The publisher makes no warranty, express or implied, with respect to the material contained herein.

Managing Director, Apress Media LLC: Welmoed Spahr

Acquisitions Editor: James Robinson-Prior

Development Editor: James Markham

Coordinating Editor: Jessica Vakili

Cover image designed by Freepik (www.freepik.com)

Distributed to the book trade worldwide by Springer Science+Business Media New York, 1 NY

Plaza, New York, NY 10004. Phone 1-800-SPRINGER, fax (201) 348-4505, e-mail orders-ny@

springer-sbm.com, or visit www.springeronline.com. Apress Media, LLC is a California LLC and the sole member (owner) is Springer Science + Business Media Finance Inc (SSBM Finance Inc).

SSBM Finance Inc is a Delaware corporation.

For information on translations, please e-mail [email protected]; for reprint, paperback, or audio rights, please e-mail [email protected].

Apress titles may be purchased in bulk for academic, corporate, or promotional use. eBook versions and licenses are also available for most titles. For more information, reference our Print and eBook Bulk Sales web page at http://www.apress.com/bulk-sales.

Any source code or other supplementary material referenced by the author in this book is available to readers on the Github repository: https://github.com/Apress/Practical-GitOps.

For more detailed information, please visit http://www.apress.com/source-code.

Printed on acid-free paper

Table of Contents

About the Author �������������������������������������������������������������������������������xiii About the Technical Reviewers ����������������������������������������������������������xv Preface ���������������������������������������������������������������������������������������������xvii Chapter 1:

iii

Table of ConTenTs

Chapter 2:

iv

Table of ConTenTs

Chapter 3:

v

Table of ConTenTs

Chapter 4:

Chapter 5:

vi

Table of ConTenTs

Chapter 6:

vii

Table of ConTenTs

Chapter 7:

viii

Table of ConTenTs

Chapter 8:

ix

Table of ConTenTs

Chapter 9:

x

Table of ConTenTs

xi

Table of ConTenTs

Annexure A: Manually Delete Resources ������������������������������������������505

Annexure B: Terraform Cloud Destroy Problem ��������������������������������511

Annexure C: Code Compatibility on OS ���������������������������������������������515

Index �������������������������������������������������������������������������������������������������519

xii

About the Author

Rohit Salecha is a technology geek who loves

to explore anything that runs and understands

binary. As a security engineer, he is passionate

about learning the length, breadth, and depth

of technology.

Being more on the defensive side, he has

evangelized secure software development at

various organizations for more than a decade.

He is ridiculously driven by the “everything

as code” mantra and strongly believes that

the security team must strive toward making

themselves irrelevant.

In his free time, he is either reading books or watching movies. He is a

fitness freak who loves to jog, swim, and cycle on different terrains.

xiii

About the Technical Reviewers

Gaurav Raje is a Senior Product Manager at Amazon in the Returns,

ReCommerce, and Sustainability group. Previously, he was an Experience

Owner Lead at USAA where he led an Agile team and actively monitored

the backlog by discussing product features with a cross-functional team

including IT, Legal and Compliance, and Leadership. He implemented

features that provided automated solutions and reduced the manual

dependency on USAA’s customer fulfillment centers.

Prior to joining USAA, Gaurav was a Manager at PwC Consulting,

where his main projects consisted of performing gap assessments of small

Asian banks by reviewing their procedures, resources, and technology and

facilitating workshops with client stakeholders as necessary; subsequently,

he produced recommendations (procedural enhancements and a

technology implementation) to address the gaps; these recommendations

resulted in future sales opportunities.

Gaurav holds an MBA from Georgetown University and a BS in

electrical and computer engineering from the University of Colorado at

Boulder.

Anand Tiwari is an Information Security Professional. He loves to

play with open source tools and is more inclined toward defense than

offense in the information security domain. He works with the operation

and development team, solving challenges between the security and

DevOps teams.

xv

Preface

In 2018, I heard the term “Kubernetes” for the first time, and the only

understanding I had of AWS was that we can run machines in the cloud

that can be accessed from anywhere. Being in the security domain, hearing

new technology terms and learning about these technologies has been an

undying passion of mine.

It took me two years to understand DevOps, and I created a small

hobby project called Practical DevOps lab (www.rohitsalecha.com/

project/practical_devops/) to help people understand the core

underlying technologies that make up the umbrella term.

After completing this, I asked myself what’s next?

So I decided to level up, and the result is this book. I wanted to explore

cloud and cloud-native technologies like AWS and Kubernetes and how

they can be set up using Infrastructure as Code like Terraform.

I wouldn’t say that I am an expert at any of these technologies

because that was never my aim. My aim was to have a basic hands-

on understanding of them, and in this book, I am sharing that limited

knowledge that I have because being from the security domain, it’s my

job to help organizations secure their assets, but if I don’t have a basic

understanding of what they are, then I wouldn’t be doing my job well.

Since this is a totally practical and hands-on book, it would be

recommended to use it as an ebook as it’ll help in working faster. We’ll

be dealing with technologies like AWS Route53, AWS EKS (Kubernetes),

and AWS ELB, which do not fall under the free tier categories, and hence,

there’ll be charges (>$15) associated when you execute the code provided

in this book.

xvii

CHAPTER 1

What Is GitOps?

1.1 The Era of DevOps

Businesses today have expanded into different horizons and industries

with the enablement of technology. Today, we have folks delivering food at

home by simply placing an order on an application in a hand-held device

and streaming your favorite movie/sitcom on whatever device you wish to

see. All these have been possible because people are exploring different

ways to solve real-world problems with the help of technology.

An interesting question to ask at this juncture is who is pushing whom?

Is it that new ways to do business are pushing technology to the edge or is

it because we have high-end technology that businesses are thriving? This

is a typical chicken-and-egg question as to which came first: the egg or the

chicken?

Frankly speaking, it doesn’t matter who is pushing whom, but one

thing purely common between the two is “agility”; everyone wants things

to be done faster. But then going fast can be risky, and hence, people

also look for stability. Gone are the days when system administrators

would cut-short their holidays to support the festive surges. Thanks to

technology platforms like AWS (Amazon Web Services) and the like, which

are also popularly known as cloud computing platforms, businesses and

technology can achieve speed with resilience and reliability.

© Rohit Salecha 2023

1

R. Salecha, Practical GitOps,

Chapter 1 What Is GItOps?

This culture of being able to deliver software with agility and reliability

gave rise to the term which we call “DevOps.” The influence that the

DevOps methodology has had on the people, process, and technology,

the three pillars of success for any organization, is tremendous and

revolutionary. DevOps brings about a change in which businesses

operate. The IT (information technology) team has sort of broken out

of the shackles of being boring, slow, and always taking the blame for

failures. Many blogs/personas describe DevOps as a methodology that

brings Dev and Ops together, which is certainly true from a technology

perspective. However, I believe that DevOps has brought business and

technology to become equal stakeholders in the success of organizations.

Peter Sondergaard in the Gartner Symposium, 2013, declared that “Every

company is a technology company,” which implies that the success of

every business is tied to the success of your technological processes

and tools.

1.1.1 What Problems Is DevOps Addressing?

Before understanding what DevOps is, let’s rewind our clocks back and

understand how software development functioned a few decades ago. In

ancient times of IT, back when we’d just fixed the Y2K bug, the Waterfall

model of software development was very popular and most widely

accepted. The model was quite simple as illustrated here.

2

Chapter 1 What Is GItOps?

Figure 1-1. Waterfall SDLC model

1. A project would be kicked off, and a business

analysis team would visit the client and jot down all

the requirements, which would take around one to

two months.

2. Once all the requirements are captured, the

architecture team would convert these requirements

into a system design and provide a road map for

software development.

3. Based on the preceding two stages, the software

development team would then start writing the code

and test it out in their local systems, which would

easily take a minimum of three to four months to

complete. The project management team would

track the progress of the development against the

requirements specified and also the budget.

3

Chapter 1 What Is GItOps?

4. Once a good majority of the requirements have been

done, there would be a pilot testing by deploying the

application in a staging environment.

5. Finally, if everything goes well, which seldom

happens, the application would then be delivered to

the client by deploying in the client’s environment.

What are the challenges/limitations for this model?

1. Lengthy build cycles lead to slow and sluggish

outputs.

2. Disparate environments of development and

production lead to unexpected outages.

3. Extremely focused skills where developers do

not wish to interfere in operations and vice versa.

This hinders transparency in processes and huge

dependencies.

4. The day the software is delivered, everyone involved

in that part of the application is expected to be on

the floor waiting to quickly identify if something

unexpected is observed.

5. Scaling up with manually created infrastructure is a

big challenge as downtimes are required.

6. Implementing backup and disaster recovery

strategies is expensive as it involves maintaining

two copies of infrastructure (maybe a little scaled-

down one) and also ensuring syncing of the data

between them.

4

Chapter 1 What Is GItOps?

1.2 Introduction to DevOps

DevOps is a methodology that brings together three practices that work

in tandem:

• Continuous integration

• Continuous delivery/deployment

• Continuous monitoring

Understanding each of these practices individually will help us understand

DevOps, which shall also lay groundwork for explaining the term “GitOps.”

1.2.1 Continuous Integration

Continuous integration as described by Martin Fowler (

) is a software development practice where different members of the team integrate

changes frequently from at least a one to multiple changes in a day.

There are various tools that can be used for this purpose, namely, Git,

CVS, Mercurial, SVN, etc.

The primary purpose of all these tools is to

Ensure version control of all the objects/files that

are saved

Provide necessary tooling to resolve merge conflicts so

as to reduce issues arising owing to integration

Provide traceability of changes so as to understand its

impact on the running systems

Allow the implementation of a transparent and flexible

peer review process

Accelerate failure detection by implementing

automated testing

5

Chapter 1 What Is GItOps?

The preceding diagram illustrates the practice of continuous

integration where developers working on different parts of the project,

that is, Frontend and Backend, are all checking the application code with

the Dockerfiles and Kubernetes administrators checking the Kubernetes

Manifests into the repository. The illustration makes use of the tool called

“Git,” originally developed by Linus Torvalds in 2005, which so far is the

most widely used software for version controlling and also the tool of

choice for this book. The key element about Git is that it is “distributed,”

meaning there is a local copy on your machine and a main copy stored

somewhere on your remote servers.

In our illustration, the red dot lines signify the developer’s machine

where the entire source code of the project resides along with the current

changes that they have made. The solid lines in the shape of the cloud

signify the central repository where the code is finally merged from all the

developers. Hence, a copy of the code will always be present in the central

repository and in each developer’s machine.

6

Chapter 1 What Is GItOps?

Figure 1-2. Distributed Git model

1.2.2 Continuous Delivery/Deployment

Deployment is the practice of pulling the source code from a repository

and performing actions that are necessary to package and run the

application on a desired system.

7

Chapter 1 What Is GItOps?

Continuous deployment (CD) is a software engineering approach

in which software changes are delivered frequently through automated

deployment processes.

A dedicated CI/CD tool/server will pull the source code and execute

a series of commands that build, package, and deploy the application.

Popular CI/CD tools/servers/services are Jenkins, Azure DevOps, CircleCI,

GitHub Actions, etc.

The developers write a series of actions that are performed on serial

or parallel execution stages mimicking the movement of a conveyor belt

running in a factory. These different stages when constituted together are

called a “Pipeline,” and since they are written in a file that is also usually

checked in the repository, it gives rise to a technique called “Pipeline

as Code.”

Generally, these instructions/actions are written in a

declarative syntax.

Let’s take a simple example. Let’s say I am running an application on a

Kubernetes cluster where docker images of the applications are executed

as a containerized application. However, the images need to be built

and pushed into a central repository whose location is specified in the

Kubernetes manifest file. This requires the developers/administrators to

do the following:

1. Pull the source code from the repository.

2. Perform automated QA/security testing.

3. Build an image with the latest version of the code:

docker build -t testimage/repository:version.

4. Push the built image into a container repository:

docker push testimage/repository:version

5. Once the images are pushed into the repository, the

Kubernetes administrators apply their manifest files.

8

Chapter 1 What Is GItOps?

This process is called continuous delivery as we are continuously

delivering the changes to the central docker repository.

An interesting thing to note here is that in the continuous delivery

process, a human intervention is needed to execute the application as

indicated in step 5 where an administrator needs to apply the Kubernetes

manifest files.

Continuous deployment is a more advanced process where upon

thoroughly testing, the changes are directly deployed into the Kubernetes

environment without any human intervention, something which we’ll

explore in more detail.

Figure 1-3. Continuous delivery pipeline

The preceding image illustrates the complete process where the docker

image building is automated as described in the following:

1. Developers push their application code and

Kubernetes administrators their Kubernetes

manifests into their respective repositories once

development and testing are complete.

9

Chapter 1 What Is GItOps?

2. The CI/CD server, GitHub Actions in our case,

which is listening for a commit event, pulls the code

into a temporary location.

3. Upon receiving the trigger of a commit event, the

tool starts the execution of the pipeline, and as

described in the figure, we are performing two

actions:

a. Docker Build – Which builds a docker image from the

Dockerfile present in the repository.

b. Set up a test environment and perform all QA/security/

integration tests.

c. Docker Push – Pushes the docker image to a central

repository once all tests are successfully completed.

4. Deploying Kubernetes manifest files:

a. Continuous delivery – A Kubernetes administrator would

receive a notification of the pipeline execution being

completed and would apply the manifest files manually from

a system using “kubectl” utility.

b. Continuous deployment – In contrast to the continuous

delivery step, here, the Kubernetes manifest files would

be deployed in an automated fashion through a different

pipeline wherein the developer would simply push their

Kubernetes manifest files into the source code repository and

the deployment pipeline would get triggered. As a matter of

fact, this is the core idea that’ll be discussed in this book.

10

Chapter 1 What Is GItOps?

1.2.3 Continuous Monitoring

Feedback is one of the most important parts of any process and is

certainly a big deal in DevOps. As we deploy faster, we are able to see the

new features quickly and rapidly; however, what to do when things go

wrong? In the case of an application running in a pod in a Kubernetes

environment, there are many things that can go wrong like no taint

matching, no node available for scheduling, etc. How do we debug this real

time without having to actually SSH into the cluster? How do we know if a

node is exhausting its memory, or the CPU is spiking? How can we get all

those details real time?

This is where the practice of continuous monitoring becomes an

extremely important part of DevOps as it gives real-time feedback on how

our infrastructure is behaving.

Figure 1-4. Continuous monitoring using Prometheus, Grafana, and

ELK Stack

11

Chapter 1 What Is GItOps?

The preceding image illustrates the idea of continuous monitoring

where we are using two popular solutions for monitoring the Kubernetes

cluster.

1. Prometheus and Grafana – Primarily used for

monitoring performance parameters like memory,

CPU utilization, etc. Prometheus is the data store

that pulls the requisite information, and Grafana

is the dashboarding solution for viewing the

information stored in Prometheus.

2. Elasticsearch and Kibana – Primarily used for

viewing the log events near real time like access

logs, error logs, etc. Elasticsearch is used for storing

the unstructured data pushed in through various

solutions like Logstash, Filebeat, etc. Kibana is the

visualization layer for viewing the data stored in

Elasticsearch.

Note the use cases described previously for eLK, Grafana, and

prometheus are limited for the purpose of this book. there are many

other advanced usages of these tools that the reader is expected to

understand independently.

These are only a few examples of the stages that organizations employ

to automate their deployment pipeline. Other possible stages that can

occur between continuous integration and continuous monitoring are as

follows:

Continuous testing – Testing the code changes through

automated testing using tools like Selenium.

12

Chapter 1 What Is GItOps?

Continuous verification – Verifying the results of testing

against the acceptance criteria, which are also codified

in the pipeline.

Continuous security – Leveraging the security toolsets

like SAST (Static Application Security Testing), SCA

(Software Composition Analysis), DAST (Dynamic

Application Security Testing), Compliance as Code,

Container Scanning, etc., as different stages within the

DevOps pipeline to derive a higher level of security

assurance before the code hits production. This is also

popularly known as the DevSecOps movement.

All in all DevOps tries to answer all the issues that we’ve observed in

the waterfall model of software development. DevOps is a methodology to

• Push our changes faster by reducing the build time

• Create a multi-environment synergy by having

least possible differences between the staging and

production environments

• Foster a multi-skilled culture where developers and

operations understand each other’s job

• Reduce dependencies between individuals by

attempting to automate as much as is possible

• Ensure scalability of infrastructure without downtimes

• Provide a way to collect and correlate the metrics for

our infrastructure so as to take appropriate actions in

case of a disaster

13

Chapter 1 What Is GItOps?

1.3 Infrastructure As Code (IaC)

Now that we’ve got a good understanding of what DevOps is all about, it’s

important to understand one more key term, which in my opinion is the

enabler of DevOps, and that is “Infrastructure as Code.” The best way to

understand this term is to take a use-case example and understand how

this requirement was solved over a period.

Let’s say we wish to run a simple HTML page using Apache Web Server

on an Ubuntu operating system (OS). The tech stack is quite simple, but

let’s understand how people managed to execute this over time.

1.3.1 Evolution of Server Infrastructure

Let’s start from the time when the client-server architecture started gaining

dominance over the mainframe technology.

1995–2005

Purchase a bare-metal server and place it in a stack in a

data center.

Install the Ubuntu OS by logging in directly from the

terminal using a CD drive.

Once the OS is installed, configure the network and

other necessary components.

Then install Apache using the “apt-get” command.

Configure the IP address manually and statically and

serve the web page on the address.

All kinds of updates, backups, etc., needed to be done

by hand and were extremely tedious.

Time taken to prepare the server: ~1–2 weeks.

14

Chapter 1 What Is GItOps?

Then in the new decade, virtualization technologies began to shape up

the landscape where in a single bare-metal device, multiple OS could be

installed and configured. With virtualization, the steps changed a bit.

2005–2015

Create a virtual machine using your favorite

virtualization technology.

Download the Ubuntu ISO and configure it as a virtual

disk to install the virtual machine.

Once the OS is installed, configure the network,

which is slightly easier especially in the virtualization

environment as compared to the manual steps.

Then install Apache using the “apt-get” command and

serve the web page on the IP address of the virtual machine.

Create virtual hosts for serving multiple websites on the

same server owing to shrinking IP address space.

Time taken to prepare the server: ~2–3 days.

In the past decade, people started realizing that it’s quite futile to

spin up a full-blown server with its own memory, disk, CPU, etc., just for

serving a few HTML pages even if it’s virtualized. Hence, they started using

containerization technology to quickly spin up and tear down containers.

Note all code files can be cloned from the following repository on

Github:

Once downloaded/cloned, they’ll be accessible as per the chapter

number associated with the code. so for accessing the code of

Chapter , you’ll need to open the folder “chapter1”; for accessing the source code of Chapter , you’ll need to open the folder

“chapter2” and so on.

15

Chapter 1 What Is GItOps?

2015–Present

The following is a sample Dockerfile that takes the

base image of the Ubuntu container and installs the

necessary components.

Code Block 1-1. Dockerfile for Apache2

File: chapter1\Dockerfile

1: FROM ubuntu

2: RUN apt-get update

3: RUN apt-get install -y apache2

4: RUN apt-get install -y apache2-utils

5: RUN apt-get clean

6: EXPOSE 80 CMD ["apache2ctl", "-D", "FOREGROUND"]

Once the Dockerfile is ready, its first build, and a

customized image is created, it is then pushed into a

central repository.

Then a container orchestration system like Docker

Swarm, Kubernetes, Mesos, etc., is used to execute as a

container application. In this manner, thousands and

millions of small websites can be containerized and

deployed in a matter of seconds.

Time taken to prepare the container: ~2–5 minutes.

Organizations also started utilizing cloud computing

like AWS wherein in a matter of few clicks, you can

get a virtual machine on the Internet with everything

pre-configured; all you’ve got to do is deploy your

application.

16

Chapter 1 What Is GItOps?

As an alternative to clicks on console, you can also

utilize tools like Terraform to spin up resources on

different cloud platforms. The following sample

Terraform code deploys an EC2 instance on the AWS

environment.

Code Block 1-2. Terraform code for EC2

File: chapter1\ec2.tf

17: resource "aws_instance" "app_server" {

18: ami = "ami-00399ec92321828f5"

19: instance_type = "t2.micro"

20: tags = {

21: env = "test"

22: }

23: }

Time taken to prepare the server: ~5–10 minutes.

The advantage that the new age technology holds, be it either

containers or cloud computing, they can all be codified in a simple

declarative format as shown in the code snippets. This gives the ability to

version control our infrastructure in a declarative manner and be able to

track, update, and manage everything from the version control software,

for example, “Git,” which will be followed in this book.

1.3.2 Tools of Trade – IaC

In the IaC space, we primarily have two types of tools that solve two

different problems:

Infrastructure provisioning tools – This set of tools is

primarily used for setting up the base infrastructure

17

Chapter 1 What Is GItOps?

like servers with a particular OS, for example, setting

up an Ubuntu OS or a Red Hat OS. Vagrant, Puppet,

Terraform, CloudFormation, and Docker are some

examples of infrastructure provisioning tools that can

spin up a server either locally or remotely (on cloud).

Infrastructure configuration tools – This set of tools

specializes in configuring the infrastructure once it

is provisioned. For example, we may set up an EC2

instance on AWS using Terraform, but we’ll need to use

a configuration tool like Ansible, Chef, or basic Shell

scripts to install the required dependencies like the

Apache2 server and set up the virtual hosts.

In this book, we’ll be exploring Terraform with AWS in much more

detail with chapters dedicated to both topics.

1.3.3 Mutable vs. Immutable Infrastructure

Change being the only constant is bound to happen to our infrastructure

as demanded by our applications’ requirements. Hence, when dealing

with infrastructure in a fast-moving DevOps environment, there is a very

important distinction that needs to be made with respect to the life span of

the infrastructure that we are deploying.

However, the degree and frequency of changes required, and the effort

required to implement these changes, is what defines our infrastructure to

be a mutable or an immutable infrastructure.

Mutable Infrastructure

• Infrastructure that, once provisioned, cannot

be destroyed owing to various reasons like high

dependency and explicit manual configurations;

examples include, but not limited to, Mail, Exchange,

Identity, and Database servers.

18

Chapter 1 What Is GItOps?

These systems also undergo very minimal changes

throughout their life span. For example, if you need

to update a database server, then you don’t have to

completely destroy and create a new one from scratch.

You’ve just got to update its version by retaining

the data.

Hence, the degree of change needed is very low,

frequency of changes is not that often, but the efforts

required to implement the changes are generally

very high.

Immutable Infrastructure

Infrastructure that has limited or no dependency on

other components that are running; for example, in a

microservice architecture, if one service goes down, it

doesn’t affect the other services.

Infrastructure that is better to recreate than update.

For example, a Front-End reactjs application running

in docker needs to update itself to a newer version. It

makes no sense to update the code from within the

running docker container. It is much better to replace

the running container with a newer image containing

the updated version.

Hence, the degree and frequency of changes needed is

quite high; however, the efforts required to implement

these changes are generally very low.

It is extremely important to understand these terms and be able to

visualize their implementation. These terms are also quite commonly

elucidated as pets vs. cattle. If you view a server (containerized or virtualized) as something that can be replaced at any time, then it’s like a cattle sort of

19

Chapter 1 What Is GItOps?

representing tens or thousands of servers performing a similar job. However,

if you view a server as an indispensable part of your daily operations, then it’s

a pet representing something that’s done quite uniquely.

Another very practical example is Kubernetes. The master plane

components of Kubernetes can be considered as a pet as doing any kind of

changes here can lead to drastic effects and hence need to be treated as a

mutable infrastructure. The deployments or workloads can be considered as

a cattle or an immutable resource, which can be quickly replaced/updated.

We’ll explore more about Kubernetes and its AWS equivalent, that is,

AWS EKS, in more detail in this book.

1.3.4 State in IaC

State in computer systems is a collection of information about a program or

an object stored either in a file or memory. The information represents the

current state of the program/object and helps in identifying what future state

would look like by diffing between the current and the desired information.

If we are spinning up infrastructure resources using IaC technologies,

then it’s extremely important to store their state as without that

information, we won’t know how resources were created and how they

would handle changes.

Let’s take a simple example of how Terraform manages its state when

creating a simple EC2 instance on AWS. (We’ll explore Terraform in much

more detail in chapters ahead).

A sample terraform.state file where Terraform state is stored is

shown here.

Code Block 1-3. Terraform state

{

"version": 4,

"terraform_version": "1.0.7",

20

Chapter 1 What Is GItOps?

"serial": 1,

"lineage": "2d17ac77-08f2-94d9-e1e5-991fc1e2fe12",

"outputs": {},

"resources": [

{

"mode": "managed",

"type": "aws_instance",

"name": "app_server",

"provider": "provider[\"registry.terraform.io/hashicorp/

aws\"]",

"instances": [

{

"schema_version": 1,

"attributes": {

"ami": "ami-00399ec92321828f5",

"arn": "arn:aws:ec2:us-

east- 2:871811778330:instance/i-0bc3c235e3991243b",

"associate_public_ip_address": true,

"availability_zone": "us-east-2c",

"capacity_reservation_specification": [

{

XXXXXXXX ---- SNIPPED FOR BREVITY------ XXXXXXXXX

This file is created once “terraform apply” is executed and its operation

of creating an EC2 instance on AWS is completed as illustrated in

Figur

The state stores information about the instance ID, network interface

ID, and much more details that are required when modifications are to be

made to this server or to destroy this instance.

21

Chapter 1 What Is GItOps?

Figure 1-5. Executing Terraform apply

When the resource needs to be destroyed or any new resource needs to

be added, Terraform will first reference the terraform.state file and provide information about what is going to change. Once that is done, it’ll then go

ahead and execute the necessary changes.

22

Chapter 1 What Is GItOps?

Figure 1-6. Executing terraform destroy

We’ll discuss in detail how to manage the terraform state in the best

possible way; however, for now, understanding the importance of state is

extremely necessary from the GitOps perspective.

1.4 Problems with Continuous Delivery

So we saw the difference between continuous deployment and continuous

delivery and that in the latter, a manual step is required to reflect the changes

in production whereas in the former, an automated pipeline needs to be set

up wherein changes are directly pushed without any manual approval.

Most organizations generally start the DevOps movement in

continuous delivery and then move up to continuous deployment as a

mature automated process needs to be in place for the same.

23

Chapter 1 What Is GItOps?

Organizations like Netflix, Google, Facebook, Amazon, etc.,

continuously deploy their changes into production without any manual

intervention. All the testing for functionality, security, and compliance is

added as part of the deployment pipeline.

Let’s recall our DevOps diagram that employs a continuous delivery

pipeline and raise some questions regarding the environment in which our

application gets deployed.

Figure 1-7. A sample continuous delivery pipeline

1. Developers push the code into the source code

repository.

2. Once the code is pushed into the repository, GitHub

Action is triggered, which builds and pushes the

docker images to the docker repository.

24

Chapter 1 What Is GItOps?

3. Once the pipeline is executed, the Kubernetes

administrator will pull the Kubernetes manifest files

from the repository.

4. Post pulling the code, they will apply the manifest

files into the respective Kubernetes cluster for the

environments.

Now let’s analyze the potential problems/issues with this approach:

Manual deployment – As with all activities done

manually, there is always an element of human error.

No state management: In this scenario, the Kubernetes

cluster would be created by hand, and all deployments

are done by hand, albeit through the manifest files.

However, being able to track the actual state with the

manifest files would be a big challenge in itself. Hence,

there’ll be a huge gap between the current state and the

desired state, which in many cases leads to uncertainty.

Also, not just about the Kubernetes cluster but the

entire infrastructure that has been set up on cloud if

done through console/CLI will be unmanageable and

untraceable.

CLI vs. cloud: Every Kubernetes administrator will

have their own choice of operation; some would

want to manage through CLI, while others would

be comfortable with the GUI. This will lead to

inconsistencies in the way the cluster and its resources

are managed, leading to confusion.

If manual processes exist especially in managing the IT infrastructure

resources, there’ll always be inconsistencies leading to possible

outages/issues.

25

Chapter 1 What Is GItOps?

1.5 Introduction to GitOps

As we saw, DevOps is more of a combination of culture, tools, and

processes to develop and deliver business applications faster. GitOps is

a more technical term that encourages everything to have the “Git” as

the single source of truth. By everything, I mean your application as well

as your infrastructure must be deployed from Git. For teams following

DevOps, their application code is already maintained in Git; however, their

infrastructure is more or less deployed by hand.

Hence, GitOps stresses more on having your infrastructure deployed

directly from Git, providing flexibility to developers to be able to choose

their own deployment strategies and taking the load off the system

administrators to manage and deploy complex environments like staging,

production, backup, and disaster recovery.

GitOps is a process to manage your infrastructure deployments in an

auditable, repeatable, version-controlled, and reliable manner. GitOps will

help you create a “snapshot” of your infrastructure in the form of a “state,”

allowing you to make decisions while designing and deploying changes.

It’ll help you scale up/scale in whenever required and manage almost all

aspects of your cloud infrastructure through a few lines of code.

The following are some mandatory requirements to implement a

GitOps process for the infrastructure that is responsible for running

business applications:

Your infrastructure must be defined in a declarative

format and stored in a version-controlled system.

The state of the infrastructure must be recorded in a

well- defined format.

All infrastructure changes must be done through code only.

Any new changes in the infrastructure must be

approved by identifying the difference between the

current state and the desired state.

26

Chapter 1 What Is GItOps?

Drifts in state changes that occur owing to manual

infrastructure modifications must be identified and

reconciled.

In a typical GitOps process, the entire infrastructure should be

captured in Git and deployed through a CI/CD process. The application

deployment should also trigger the Kubernetes manifest files being applied

without human intervention. The following image captures the “raw” idea

of how this can be in an automated fashion.

Figure 1-8. Typical GitOps process

27

Chapter 1 What Is GItOps?

Running in parallel to the DevOps pipeline, the GitOps pipeline is

responsible for initializing, creating, and managing the infrastructure on

which the applications are being deployed in an auditable, repeatable, and

reliable manner.

What we are discussing here is a PUSH-based GitOps wherein

GitHub Actions is being used to PUSH the changes into the deployment

environment like AWS or GCP.

However, there exists a PULL-based GitOps that is governed by

an operator that is listening for changes and then deploys them in the

environment. Argo CD, Flux CD, etc., are tools that enable a PULL-based

GitOps that needs to be installed in the Kubernetes environment. Once

installed, a webhook configuration will listen for changes in the Git

repository and PULL the Kubernetes manifests that need to be deployed.

28

Chapter 1 What Is GItOps?

Figure 1-9. PULL-based GitOps

These PULL-based GitOps tools are more suitable for an exclusive

Kubernetes-based environment only. Some of these tools can also deploy

an entire Kubernetes cluster in an environment of your choice, that is, on

premises, AWS, GCP, etc.

In this book, I’ll, however, be discussing a PUSH-based approach

using Terraform, GitHub Actions, and AWS EKS wherein we’ll not only

be managing Kubernetes resources in code but also the supporting AWS

infrastructure like a PostgreSQL database, DNS configurations, HTTPS

certificates, Load Balancer, and many more.

29

Chapter 1 What Is GItOps?

The following is the GitOps pipeline we shall build and also

understand the different components in different chapters.

Figure 1-10. Complete GitOps pipeline

1.6 Conclusion

In this chapter, we looked at what DevOps is and how it has solved some

of the biggest challenges in software development. Infrastructure as Code

added to the speed and agility of DevOps, and to utilize it better, GitOps

movement was started. GitOps helps in creating a continuous deployment

pipeline to deploy the changes faster, reliably, and efficiently.

In the next chapter, we’ll look at how we can create an EC2 instance on

AWS using Console as well as CLI to elaborate how easy it has now become

to spin up a server on AWS.

30

CHAPTER 2

Introduction to AWS

2.1 Prerequisites

AWS CLI will be explored in this chapter, and hence, to log in using the

CLI, we’ll need to install the awscli tool, which can be installed on your

favorite operating system by following the instructions provided on the

official AWS documentation:

I am currently using AWS CLI Version 2.7.9.

Note All the code in this book will work only on AWS CLI Version 2.X

and above.

2.2 Introduction to AWS

In this chapter, we’ll introduce ourselves to AWS (Amazon Web Services),

one of the most popular cloud service providers. This chapter will help us

build context for the next chapter as well; hence, even if you’re familiar

with AWS, do at least skim through this chapter.

© Rohit Salecha 2023

31

R. Salecha, Practical GitOps,

ChApter 2 IntroduCtIon to AWS

I love history, and hence, before I tend to explain a topic, I’d like to

provide a historical overview of what happened before using this new

technology/technique as, in my opinion, it helps to understand things

better. In the previous chapter, we learned about the evolution of the IT

infrastructure from a bare-metal server to a docker container and an EC2

instance running on the AWS cloud. We also discussed the challenges

faced with the bare-metal infrastructure or even the virtualization of the

infrastructure resources. However, here, I’d like to specifically talk about

one of the most important challenges with infrastructure resources, which

is scalability.

Imagine you are the infrastructure director of an e-commerce site

that is selling some amazing products. This e-commerce site owing to

its popularity has a high demand during the daytime, and by nightfall,

the demand reduces considerably. To put it in numbers , during the day,

roughly seven to eight servers are needed to serve the load, whereas by

nightfall, only two to three servers are enough to keep the site up and

running. How do you scale with such a demand? Purchasing ten servers

and keeping them operational only for 12 hours? This can be expensive

operationally and from a capital expenditure perspective, both.

Now let’s say some organization living up in the sky gives you a sort of a

magic wand that gives you the ability to spin up and tear down servers with

a single whoosh (or maybe a command). You’ve just got to log in into their

console, do some clicks, or fire some commands (we’ll see both in this

chapter) and voilà, you have your servers ready for deployment!

This is the power of what is popularly known as “cloud computing.”

This capability helps in making our demands more scalable wherein we

can spin up and tear down as many servers as needed being charged only

for the consumption.

32

ChApter 2 IntroduCtIon to AWS

AWS (Amazon Web Services) is one of the most popular “cloud

computing” service providers wherein one of the major services that they

provide is Elastic Compute Cloud or popularly known as EC2, which is a very well-thought-out name. EC2 machines can be configured with an

operating system of our choice, a range of CPU, memory, and plenty of

other configurations.

Let’s jump right in and start creating our own EC2 machine on AWS

using the AWS GUI and through the CLI interface. Why are we using both?

As stated earlier, there is a specific set of information that I require to

capture to proceed with writing our Terraform scripts in the next chapter.

I’d like you to get sensitized to that information so that it’ll become easier

for you to write terraform scripts.

2.2.1 Create an AWS Account

Let’s first go ahead and create our AWS account. If you already have an

account, feel free to skip this section. Navigate to the URL

and create a new account using an email address and strong password. Using the free account will provide us $300 of free credit

for a period of 12 months albeit certain services that we may use like AWS

EKS and AWS Route53. While signing up, you’ll be required to enter your

credit card details as well.

Note providing a cost estimation at this stage will be a bit difficult

as I am not sure how much time you’d keep the resources on. All

resources have an hourly rate, and hence, it all depends on how

many hours you keep your eKS/rdS on. Let’s assume that you’d be

running along with the book and quickly delete the resources once

you're done exploring. In such a scenario, it should cost you roughly

about $15 or more.

33

ChApter 2 IntroduCtIon to AWS

Tip You can manage multiple AWS accounts using different email

addresses. If you are concerned about the number of mailboxes to

monitor, many email providers allow you to create aliases for the

same mailbox. For example, if you use Gmail, you can create an

account using the email Sub Addressing trick like someone+gitops@

gmail.com, which is a new account for AWS, but all communication

will land on [email protected], helping you manage multiple

accounts with a single inbox.

Once you’ve created an account, you’ll be signed in as a root user in

AWS. It is strongly recommended to apply two-factor authentication on

your root account and create a separate user with the requisite permissions.

Figur is the expected screen with best practices being followed upon browsing the URL

Figure 2-1. Expected IAM dashboard

One more very important feature to enable is the Cost Explorer,

which can be enabled by browsing t

licking Enable Cost Explorer as shown in the following. Though we are in a free tier account, this will help us analyze the

expenditure in much more detail.

34

ChApter 2 IntroduCtIon to AWS

Figure 2-2. Enable Cost Explorer

Create a new user by navigating t

licking Add Users. Ensure to enable the programmatic and password access as shown here.

Figure 2-3. Add User screen

35

ChApter 2 IntroduCtIon to AWS

Next, click on Permissions and select Attach existing policies directly

and attach the Administrator Access as shown here.

Figure 2-4. Attach Policy and Permissions screen

Next, click Tags and add a tag as “Name=Gitops”. Next, click Review

and you will see the final screen as shown here.

Tip ever wondered why it is recommended to not to use a root

user? one of the reasons is that you cannot define permissions on

a root account. hence, it is extremely important to create a new user

and assign it permissions.

36

ChApter 2 IntroduCtIon to AWS

Figure 2-5. Create User Final screen

Finally, click Create User to complete the setup. You should be able to

see a screen as shown here.

Figure 2-6. Access Keys Download screen

37

ChApter 2 IntroduCtIon to AWS

Save the Access Keys by downloading the CSV file as we’ll be needing

it. Ensure this file is stored in a secure location as it contains extremely

sensitive information.

2.2.2 Log in into the AWS Account

Let’s log in using the new user both through the UI and the CLI.

Open the CSV file and copy the “Console Login Link” in a browser

window. Bookmark this link as we’ll need to use this to log into our

account all the time. We’ll no longer be required to log in as the “root” user.

Figure 2-7. Access Keys CSV file

Sign into the AWS account using the credentials created.

38

ChApter 2 IntroduCtIon to AWS

Figure 2-8. AWS Sign-In screen

Let’s configure our AWS CLI profile by executing the command and

entering the information as shown here using the CSV file we downloaded

earlier.

CLI Output 2-1. AWS CLI profile configuration

cmd> aws configure --profile gitops

AWS Access Key ID [None]: AKIATLYRVHDFIBCUOUEA

AWS Secret Access Key [None]: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

XXXXXXXXXX

Default region name [None]: us-east-2

Default output format [None]: json

39

ChApter 2 IntroduCtIon to AWS

Let’s confirm if we’ve configured our information correctly by

executing the following command.

CLI Output 2-2. AWS CLI login confirmation

cmd> aws sts get-caller-identity --profile gitops

{

"UserId": "AIDATLYRVHDFFW7RHDCIP",

"Account": "231428405450",

"Arn": "arn:aws:iam::231428405450:user/gitops"

}

Now that our accounts have been created, let’s create an EC2 machine,

which is the AWS’ virtual machine offering.

2.3 Creating an EC2 Machine – GUI

Now that we’ve everything set up, let’s create our first EC2 machine from

the GUI or the AWS Console to better understand the power of cloud

computing.

2.3.1 Default Region Selection

In AWS, selecting a region is very important for most services. All

instructions in the book will be provided for Ohio (us-east-2) as the

default region. So ensure you select the region as Ohio from the drop-

down box on the top-right corner as shown in the following before moving

into any of the steps.

40

ChApter 2 IntroduCtIon to AWS

Note AWS region selection is completely arbitrary as all the

services that we’ll be exploring in this book are available in all the

regions. It totally depends on the user to select which region they are

comfortable with; however, once you do select, please continue to

use it as services like AWS eKS, rdS, openSearch, etc., are region

specific.

Figure 2-9. AWS Default Region Selection screen

Once logged in, enter the term ec2 in the search bar on top as

shown here.

Figure 2-10. AWS Search Bar EC2 screen

You’ll be greeted with an EC2 launch console where you can click on

the Launch Instance button as shown here.

41

ChApter 2 IntroduCtIon to AWS

Figure 2-11. AWS Launch Instance screen

The page will navigate to a section where you can select the base

operating system that is required for spinning up our VM as shown here.

Figure 2-12. Selecting Base Operating System screen

42

ChApter 2 IntroduCtIon to AWS

2.3.2 Instance Type Selection

Scroll down below till you see the Ubuntu image and then click Select as shown here.

One information to note, as highlighted in yellow, is the AMI ID, which

is ami-00399ec92321828f5.

Figure 2-13. Selecting Ubuntu OS screen

On the next screen, we’ll be asked to choose the configuration of

the virtual machine. For now, to save money, we’ll go ahead with the t2.

micro instance as shown in the following and then click Next: Configure

Instance Details.

43

ChApter 2 IntroduCtIon to AWS

Figure 2-14. Instance Selection screen

Tip In AWS, Cpu selection is the most important aspect as rAM and

bandwidth depend on it. the higher the Cpu, the higher is the rAM

and bandwidth. however, you might find certain instances that are

memory optimized not following this rule.

2.3.3 Instance Configuration

In this screen, we can configure the instance-level details like which

network it’ll belong to and what it needs to install at startup. As can be

seen in the following screenshot, the default VPC (virtual private cloud) is

configured for the instance’s deployment.

No action needed here; we just need to scroll down to a very important

section.

44

ChApter 2 IntroduCtIon to AWS

Figure 2-15. VPC Selection screen

Now we need to configure the instance to run an Apache2 web server

on port 80 upon launch. This can be done by adding our commands to set

up an Apache2 server in the “User Data” section using the script as shown

in Figure .

Tip the “user data” section is a place where you can “bootstrap” your

servers with necessary dependencies using scripts for the respective

operating systems. Bash for *nix and powerShell for Windows.

Code Block 2-3. User data script

File: chapter2\user_data.sh

1: #!/bin/bash

2:

3: sudo apt update -y

4: sudo apt install apache2 -y

45

ChApter 2 IntroduCtIon to AWS

Step 1: Add the preceding script in the “User Data” section.

Step 2: Click “Next: Add Storage”.

Figure 2-16. User Data Script Configuration

2.3.4 Instance Storage

The next section is to add Storage, that is, adding a separate disk to store any data. We’ll keep the defaults here and then click Next: Add Tags.

46

ChApter 2 IntroduCtIon to AWS

Figure 2-17. Storage Configuration screen

2.3.5 Tags

Adding tags is very important especially when you’re working in a cloud

environment. If you remember when we created a new account, I asked

you to enable a setting called Enable: Cost Explorer. This functionality

relies heavily on tags to provide us proper billing information.

Aside from billing, tags help us perform granular selections that can

then be applied for authentication, authorization, configuration, and

termination. So let’s click Add Tag and add a simple tag.

47

ChApter 2 IntroduCtIon to AWS

Figure 2-18. Adding Tags screen

Let’s add the following tags as shown here.

name: gitops-ec2

environment: testing

After adding the tags, click Next: Configure Security Group.

Figure 2-19. Adding name and environment tags screen

48

ChApter 2 IntroduCtIon to AWS

2.3.6 Security Groups

Next is a very important section from security perspective wherein we

configure which ports/services we would like to configure on our EC2

machine. The following screenshot shows that AWS is suggesting opening

the SSH port for remote login.

Tip If you're familiar with Windows Firewall in Windows or

iptables in Linux wherein we are restricting only certain ports to

be listening on our machine, security groups are almost similar in

functionality. It is AWS’ way of ensuring that no insecure services are

running by default.

Figure 2-20. Adding Security Groups screen

However, we are installing an Apache2 web server, and by default, it

runs on port 80. Hence, we need to allow port 80 to be listening as well for

which we’ll need to add a rule to allow port 80 as well.

49

ChApter 2 IntroduCtIon to AWS

Step 1: Add a good name to the security group, let’s call it gitops-ec2-

external.

Step 2: Add a rule to allow port 80 from all IPs 0.0.0.0/0; just select HTTP from the drop-down box.

Step 3: Click Review and Launch.

Figure 2-21. Configuring Security Groups ports 80 and 22

2.3.7 SSH Key-Pair Generation

This is the final review screen where we can review all the information

which we’ve configured so far. Once all information has been reviewed,

you can click Launch as shown here.

50

ChApter 2 IntroduCtIon to AWS

Figure 2-22. Final Review screen

Upon clicking Launch, you’ll be asked to create an SSH key pair as

shown here. This key will be needed to log in to the EC2 machine we’ve

launched. Hence, it’s also very important and needs to be performed.

51

ChApter 2 IntroduCtIon to AWS

Figure 2-23. SSH Key Pair creation screen

From the drop-down that says Choose an existing key pair, select

Create a new key pair and add a name as gitops.

Step 1: Create a new key pair.

Step 2: Enter a name as gitops, keeping the key-pair type as default,

that is, RSA.

Step 3: Click Download Key Pair.

Note please keep the downloaded key pair safe and secure. Also,

do make a note of the name of this key pair as it’ll be required to

reference in the future.

52

ChApter 2 IntroduCtIon to AWS

Figure 2-24. Configure Key Pair Name and Download screen

Once the key pair has been downloaded, click on Launch Instances to

finally launch the instance.

53

ChApter 2 IntroduCtIon to AWS

Figure 2-25. Launch Instance after downloading the key pair

2.3.8 Launching EC2 Instance

It takes about one to two minutes for the instance to launch and get

configured properly. Let’s click View Instance to view the details like the IP Address of the instance.

54

ChApter 2 IntroduCtIon to AWS

Figure 2-26. Launch Status screen

Upon clicking View Instance, you’ll be redirected to the page where all

the information about the instance can be seen. One detail that we require

is the IP Address, which you can view by scrolling a bit toward the right.

Figure 2-27. Copy the IP address

Let’s copy the IP and paste it into our browser and see the magic

unfold! We’ve successfully launched an Ubuntu system with Apache2

server running with a few clicks that would take anywhere between 10 and

15 minutes! This is the power of cloud computing!

55

ChApter 2 IntroduCtIon to AWS

Figure 2-28. Accessing EC2 from the browser

2.3.9 Terminating EC2 Instance

Let’s terminate this instance by following these steps:

Step 1: Click on “Instance State”.

Step 2: Click on “Terminate Instance”.

Figure 2-29. Terminating EC2 Instance

56

ChApter 2 IntroduCtIon to AWS

Note It’ll take about five minutes to terminate the instance.

2.4 Creating an EC2 Machine – CLI

We saw how it is possible to spin up a virtual machine on AWS cloud with

an Ubuntu OS and an Apache2 web server with a few clicks. Not only was

it easy but the entire process is also very much parameterized where you

can simply specify the parameters and spin up a system of your choice.

However, doing via GUI has its own limitation, the most important one

being lacking automation. To automate this process of creating an

EC2 instance, we need the AWS CLI tool that was used to configure the

access keys.

2.4.1 Configuring CLI Environment

The base command to create a running EC2 machine on AWS is the

following.

CLI Output 2-4. Configuring CLI environment

cmd> export AWS_PROFILE=gitops

cmd> aws ec2 run-instances

When working with multiple profiles, it is a good practice to either

initialize the CLI environment by exporting the AWS_PROFILE variable or

adding –profile, for example, aws ec2 run-instances --profile.

Now there are five parameters that we need in order to execute the

same Apache2 virtual machine that we spun up on AWS through the GUI

in the earlier section. They are

57

ChApter 2 IntroduCtIon to AWS

AMI ID – ami-00399ec92321828f5

Instance Type – t2.micro

Key Name – gitops

User Data – chapter2\user_data.sh

Security Group ID – We’ll need to fetch

Network/Subnet ID – We’ll need to fetch

2.4.2 Fetching Security Group ID

So first, let’s fetch the security group ID which we’d created in the earlier

section.

CLI Output 2-5. Fetching security group ID

cmd> aws ec2 describe-security-groups --query

'SecurityGroups[*].[GroupName,GroupId]'

[

[

"gitops-ec2-external",

"sg-085db3a64b72894be"

],

[

"default",

"sg-f687edba"

]

]

Using the aws ec2 describe-security-groups command, we are getting

the details of all the security groups that are configured for the account

in the default region, that is, us-east-2. However, we are only interested

58

ChApter 2 IntroduCtIon to AWS

in the security group ID, and hence, we’ve formatted the output using

the --query parameter of AWS CLI to fetch only the GroupName and the GroupID.

2.4.3 Fetching Subnet ID

Next, in a similar manner, let’s retrieve the SubnetID of the subnet where

we need to deploy our EC2 instance. A subnet is a logical grouping of

networks within a single VPC. AWS creates a subnet per zone as can be

seen in the following. The US-East region has three zones, and hence,

we are having three different subnets. We can, however, select any of the

subnets to deploy our EC2 machine in.

CLI Output 2-6. Fetching subnet ID

cmd> aws ec2 describe-subnets --query 'Subnets[*].

[SubnetId,AvailabilityZone]'

[

[

"subnet-79b62812",

"us-east-2a"

],

[

"subnet-060bdf7b",

"us-east-2b"

],

[

"subnet-49764905",

"us-east-2c"

]

]

59

ChApter 2 IntroduCtIon to AWS

So far, we’ve got the following information:

AMI ID – ami-00399ec92321828f5

Instance Type – t2.micro

Key Name – gitops

Security Group ID – sg-085db3a64b72894be

Network/Subnet ID – subnet-79b62812

User Data – chapter2\user_data.sh

2.4.4 Launching EC2 Instance

Now we are ready to run the command that will create our EC2 instance

from the CLI!

CLI Output 2-7. Launching EC2 from CLI

cmd> aws ec2 run-instances --image-id ami-00399ec92321828f5

--count 1 \

--instance-type t2.micro --key-name gitops \

--security-group-ids sg-085db3a64b72894be \

--subnet-id subnet-79b62812 --user-data file://chapter2/

user_data.sh

{

"Groups": [],

"Instances": [

{

"AmiLaunchIndex": 0,

"ImageId": "ami-00399ec92321828f5",

"InstanceId": " i-06adc4012f2b88810",

"InstanceType": "t2.micro",

60

ChApter 2 IntroduCtIon to AWS

"KeyName": "gitops",

"LaunchTime": "2021-10-23T13:28:22.000Z",

"Monitoring": {

"State": "disabled"

},

"Placement": {

"AvailabilityZone": "us-east-2a",

"GroupName": "",

"Tenancy": "default"

},

"PrivateDnsName": "ip-172-31-5-130.us-east-2.

compute.internal",

"PrivateIpAddress": "172.31.5.130",

"ProductCodes": [],

"PublicDnsName": "",

"State": {

"Code": 0,

"Name": "pending"

}

--------SNIP-----------------

}

Take note of the “instance-id”, which is “i-06adc4012f2b88810”, which

we’ll need to query the IP address of our server.

2.4.5 Accessing EC2 Instance

Now here, we need to query for the IP address of a specific instance using

the instance ID we received. The following command applies the “-filter”

flag against two attributes, that is, instance-state and instance-id, with their

respective values and then queries for the PublicIpAddress field.

61

ChApter 2 IntroduCtIon to AWS

CLI Output 2-8. Obtain the IP address through describe instances

cmd> aws ec2 describe-instances

--filters \

"Name=instance-state-name,Values=running" \

"Name=instance-id,Values=i-06adc4012f2b88810" \

--query 'Reservations[*].Instances[*].[PublicIpAddress]' \

--output text

18.217.95.186

Let’s run a curl command to verify if the instance is indeed running. As

shown here, the same is verified.

CLI Output 2-9. Curl the IP address

cmd> curl -I http://18.217.95.186

HTTP/1.1 200 OK

Date: Sat, 23 Oct 2021 17:01:56 GMT

Server: Apache/2.4.41 (Ubuntu)

Last-Modified: Sat, 23 Oct 2021 13:29:27 GMT

ETag: "2aa6-5cf051de7a237"

Accept-Ranges: bytes

Content-Length: 10918

Vary: Accept-Encoding

Content-Type: text/html

2.4.6 Terminating EC2 Instance

Let’s destroy the instance that we started earlier using the following

command by first querying for the instance ID and then running the

terminate- instances command.

62

ChApter 2 IntroduCtIon to AWS

CLI Output 2-10. Terminating the EC2 instance through CLI

cmd> aws ec2 describe-instances \

--filters \

"Name=instance-state-name,Values=running" \

--query 'Reservations[*].Instances[*].[InstanceId]' \

--output text

i-06adc4012f2b88810

cmd> aws ec2 terminate-instances --instance-id

i-06adc4012f2b88810

{

"TerminatingInstances": [

{

"CurrentState": {

"Code": 32,

"Name": "shutting-down"

},

"InstanceId": "i-06adc4012f2b88810",

"PreviousState": {

"Code": 16,

"Name": "running"

}

}

]

}

63

ChApter 2 IntroduCtIon to AWS

2.5 Clean-Up

Just to ensure that we don’t end up getting billed unnecessarily, you

visit the EC2 console on the AWS GUI and double-check if everything is

deleted. The Instances (running) field should show 0 and ensure you’ve

selected the correct region. If it’s still showing, execute the commands

specified in CLI Output

Figure 2-30. EC2 Instances running is zero

Note Always double-check the region; we’ve been following

us- east- 2 in this chapter.

2.6 Conclusion

In this way, we saw how AWS makes it easy to set up infrastructure in a matter

of minutes in case of GUI and seconds in case of using CLI. It may, however,

be easy to perform the preceding actions, but when it comes to setting up a

production-grade environment, it cannot be (or rather should not be) done

using either GUI or using CLI as it is difficult to keep track of what’s running

and while doing interconnections with the different systems, etc.

64

ChApter 2 IntroduCtIon to AWS

At times, running infrastructure in the cloud can get expensive if you

don’t know what exactly is running as against what is needed. Hence,

in the next chapter, we’ll introduce ourselves to a tool called Terraform,

which will make our lives a lot easier as it’ll help us in not only creating

the infrastructure but also in managing the state as well as facilitating

interconnections between different systems and AWS services.

65

CHAPTER 3

Introduction

to Terraform

In the previous chapters, we looked at how an EC2 machine can be created

using the GUI as the CLI interface. In this chapter, we’ll understand what

Terraform is and how using Terraform we’ll create the exact same EC2

machine with the exact same configuration without using CLI/GUI consoles.

3.1 Prerequisites

From here onward, I’ll be showing a lot of code snippets that can be

downloaded from the GitHub link shared earlier. It is recommended that

you get hold of the following before moving ahead as all will be required

henceforth if you wish to follow along with the book:

A code editor. I am using Visual Studio Code (VS

Code), which can be downloaded and installed from

the following link:

Terraform plugin by HashiCorp for VS Code can be

installed from here or from within VS Code itself:

© Rohit Salecha 2023

67

R. Salecha, Practical GitOps,

Chapter 3 IntroduCtIon to terraform

Terraform binary installed in the path. The following

link can be used to download and install terraform

amd add terraform binary in the path. The code

discussed in the book currently supports Terraform

>= 1.2.0:

3.2 Introduction to Terraform

When we wish to set up something, there are primarily two aspects to

it: provisioning and configuring. We provision a home using bricks and

mortar and then configure it using different pieces of furniture like bed,

sofa, etc.

In the infrastructure world, when we say we need to create an Apache2

web server, we first need to provision an underlying operating system and

its binaries and then configure the server so that it can run as expected.

Tools like Terraform, Ansible, Chef, Puppet, SaltStack, AWS

CloudFormation, etc., help us in provisioning and configuring

infrastructure resources in the cloud (and on premises, e.g., OpenShift)

through simple lines of declarative code. Declarative programming is

a method of writing instructions that define the goal of the program. It

doesn’t involve itself on the how part. For example, in terraform, the

following few lines simply say that we want an EC2 instance to be up and

running. We don’t know or rather we don’t really care how it’ll be done.

Code Block 3-1. Iteration1 code for spinning up an EC2 machine

File: chapter3\infra\iteration1\main.tf

19: resource "aws_instance" "apache2_server" {

20: ami = "ami-00399ec92321828f5"

21: instance_type = "t2.micro"

68

Chapter 3 IntroduCtIon to terraform

22: user_data = << EOF

23: #!/bin/bash

24: sudo apt update -y

25: sudo apt install apache2 -y

26: EOF

27: tags = {

28: env = "dev"

29: }

30: }

Terraform as a tool is developed in Go and has three main components

as shown here.

Figure 3-1. Terraform Core Components

• Terraform Configuration Files – Files containing the

HCL formatted declarative code providing instructions

of what needs to be done. These files have the .tf

extension.

69

Chapter 3 IntroduCtIon to terraform

• Providers – They are plugins that terraform relies on to

interact with the APIs of different IaaS (Infrastructure as

a Service) providers like AWS, GCP, and Azure or even

SaaS (Software as a Service) providers like Datadog or

PaaS (Platform as a Service) providers like Kubernetes,

which we’ll explore in detail in this book. Information

on how to start/stop/destroy the different resources/

services is baked into these providers.

• Terraform State – After taking inputs from the

configuration files and the providers, terraform

creates a state file that is a pure JSON document

wherein it maps the complete infrastructure resources

that’ve been created with all different metadata like

timestamp, configuration information like IP address,

resource identifiers, etc. The terraform state file has a

.tfstate extension.

3.3 Getting Started with Terraform

The best way to understand Terraform or any programming language/tool

is to take a use case and execute it. So we’ll look at the third way in which

an AWS EC2 machine can be provisioned using Terraform!

We’ll understand the different aspects of the terraform code like

resources, modules, variables, data elements, output, etc., through

three iterations. All the iterations would have the same result difference

would be the way we are writing the code which matters a lot, especially

when you are writing in declarative format. Each iteration would be an

improvisation over the previous one with the third iteration being the best,

most independent, scalable, and conformant with best practices.

Before we begin, let’s confirm a few things.

70

Chapter 3 IntroduCtIon to terraform

A. You’ve downloaded the code repository. You

should’ve the following folder format and you’ll

need to open the specific chapter that we are

learning.

CLI Output 3-2. Code folders

cmd> tree -d -L 1

├── chapter1

├── chapter2

├── chapter3

├── chapter4

├── chapter5

├── chapter6

├── chapter7

├── chapter8

├── chapter9

├── chapter10

10 directories

B. You’ve installed Terraform version 1.0.0 or greater

and authenticated with AWS as shown here.

CLI Output 3-3. Terraform Getting Started

cmd> terraform --version

Terraform v1.0.7

on linux_amd64

cmd> export AWS_PROFILE=gitops

cmd> aws sts get-caller-identity

{

"UserId": "AIDATLYRVHDFFW7RHDCIP",

71

Chapter 3 IntroduCtIon to terraform

"Account": "231428405450",

"Arn": "arn:aws:iam::231428405450:user/gitops"

}

3.4 Iteration #1

The following 26 lines of code will spin up an EC2 server for us on AWS

using all the defaults. Here, it can be seen that we’ve not provided the VPC

ID; hence, Terraform will query the default VPC in AWS and set up the

server in it.

CLI Output 3-4. main.tf for iteration1

File: chapter3\infra\iteration1\main.tf

01: # Setting and locking the Dependencies

02: terraform {

03: required_providers {

04: aws = {

05: source = "hashicorp/aws"

06: version = "4.10"

07: }

08: }

09:

10: required_version = ">= 1.1.0"

11: }

12:

13: # AWS Provider configuration

14: provider "aws" {

15: region = "us-east-2"

16: }

17:

72

Chapter 3 IntroduCtIon to terraform

18: # AWS EC2 Resource creation

19: resource "aws_instance" "apache2_server" {

20: ami = "ami-00399ec92321828f5"

21: instance_type = "t2.micro"

22: user_data = "${file("user_data.sh")}"

23: tags = {

24: env = "dev"

25: }

26: }

L02-11: Sort of the primary starting point of execution, like main()

where the provider dependencies are checked and downloaded. As

stated, earlier providers are nothing but plugins that fit into the terraform

architecture. Here, we are declaring that this code will run only for

Terraform version >= 1.1.0, and we are going to use the AWS provider for

the specific version. If we were to use GCP, then we’d add the GCP-related

information. We’ll be adding many more providers as we proceed ahead.

L14-16: Every provider needs some information for configuration. The

AWS provider needs information about the region, access keys, tokens, etc.

The complete documentation of the AWS provider is available here:

.

For our requirement, we only need to set the region as we’ve already

configured the AWS CLI and authenticated using it; we only need to export

the environment variable AWS_PROFILE before running terraform.

L19-30: In terraform, every resource needs to be configured separately.

Here, we’re configuring the AWS EC2 machine with all the information that

we’d seen earlier like the AMI ID, instance type, and the user data startup

script that is being loaded using the file function of Terraform. When we

start building on terraform, we’ll be mainly working on these resources,

and you can imagine each resource to be a Lego piece. The entire Lego will

be built by juxtaposition of these pieces.

73

Chapter 3 IntroduCtIon to terraform

3.4.1 Terraform Working Directory

Let’s now run this code and explore the different terraform commands.

Before running any terraform command, it is important to understand

what terraform considers as the root of the project or the terraform working directory. As shown in the following, we must first change into

the directory that contains the terraform code, which is the iteration1

directory for us.

All the *.tf files in this directory will be clubbed and executed together by terraform, and hence, this directory becomes the root directory or the terraform working directory.

CLI Output 3-5. Terraform’s current working directory

cmd> cd chapter3/infra/iteration1

cmd> ls -l

total 4

-rw-r--r-- 1 ubuntu ubuntu 476 Oct 30 11:57 main.tf

-rw-r--r-- 1 ubuntu ubuntu 59 Oct 30 11:57 user_data.sh

Terraform doesn’t apply recursively; for example, if we had a folder

called child under the iteration1 folder and we apply terraform on the

iteration1 folder, then any *.tf files inside the child directory will not apply!

CLI Output 3-6. Terraform applies to only *.tf files in a single folder cmd> cd chapter3/infra/iteration1

cmd> tree -a iteration1

iteration1 <------ Root Directory

├── child <------ Child Directory

│ └── child.tf <------ Terraform will not include

└── main.tf <------ Terraform will include

└── user_data.sh

1 directory, 3 files

74

Chapter 3 IntroduCtIon to terraform

Tip You can, however, use the flag -chdir to change the root directory to something else; for example, the terraform -chdir=child

apply command will consider the child directory as the root directory.

But still, it’ll not read the *.tf files in the parent directory!

3.4.2 Terraform init

Let’s now execute the first command, which is terraform init, as shown here.

Tip always export the profile using the aWS_profILe environment

variable as it becomes easy to follow which aWS environment we are

working on. the values in the environment variables have taken the

highest precedence in terraform’s execution.

CLI Output 3-7. Output for terraform init

cmd> export AWS_PROFILE=gitops

cmd> cd chapter3/infra/iteration1

cmd> terraform init

Initializing the backend...

Initializing provider plugins...

- Finding hashicorp/aws versions matching "~> 3.27"...

- Installing hashicorp/aws v3.63.0...

- Installed hashicorp/aws v3.63.0 (signed by HashiCorp)

Terraform has created a lock file .terraform.lock.hcl to record

the provider selections it made previously. Include this file

in your version control repository so that Terraform can

75

Chapter 3 IntroduCtIon to terraform

guarantee to make the same selections by default when you run

"terraform init" in the future.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running

"terraform plan" to see any changes that are required for your

infrastructure. All Terraform commands should now work.

If you ever set or change modules or backend configuration for

Terraform, rerun this command to reinitialize your working

directory. If you forget, other commands will detect it and

remind you to do so if necessary.

terraform init initializes the entire terraform ecosystem by

downloading and installing the dependencies. As can be seen from the

preceding command output, it basically downloads the AWS provider of

the specific version and creates a .terraform.lock.hcl file, which contains the information about all the dependencies and their hashes. Terraform init

command also creates a .terraform folder where the actual dependency is installed as shown in the following in the output of the tree command.

CLI Output 3-8. Folder structure after terraform init

cmd> tree -a

.

├── .terraform

│ └── providers

│ └── registry.terraform.io

│ └── hashicorp

│ └── aws

│ └── 4.10.0

│ └── linux_amd64

│ └── terraform-provider- aws_

v4.10.0_x5

76

Chapter 3 IntroduCtIon to terraform

├── .terraform.lock.hcl

└── main.tf

└── user_data.sh

7 directories, 4 files

3.4.3 Terraform Plan

Now that our project has been initialized, let’s execute the next command,

that is, terraform plan.

The terraform plan command provides a detailed output about all

the resources that are going to be added, removed, or updated with

appropriate signs:

+ – The plus symbol indicates that a resource will

be added.

- – The hyphen indicates that the resource will be

destroyed.

~ – The tilde indicates that the resource will be

updated.

In the preceding plan execution, we can only see + symbol since this is

the very first time we’ve applied the plan.

CLI Output 3-9. Terraform Plan output

cmd> terraform plan

Terraform used the selected providers to generate the following

execution plan. Resource actions are indicated with the

following symbols:

+ create

77

Chapter 3 IntroduCtIon to terraform

Terraform will perform the following actions:

# aws_instance.apache2_server will be created

+ resource "aws_instance" "apache2_server" {

+ ami = "ami-00399ec92321828f5"

+ arn = (known after apply)

+ associate_public_ip_address = (known after apply)

+ availability_zone = (known after apply)

+ cpu_core_count = (known after apply)

+ cpu_threads_per_core = (known after apply)

+ disable_api_termination = (known after apply)

+ ebs_optimized = (known after apply)

+ get_password_data = false

+ host_id = (known after apply)

+ id = (known after apply)

+ instance_initiated_shutdown_behavior = (known after apply)

+ instance_state = (known after apply)

+ instance_type = "t2.micro"

+ ipv6_address_count = (known after apply)

+ ipv6_addresses = (known after apply)

+ key_name = (known after apply)

+ monitoring = (known after apply)

+ outpost_arn = (known after apply)

+ password_data = (known after apply)

+ placement_group = (known after apply)

+ placement_partition_number = (known after apply)

+ primary_network_interface_id = (known after apply)

+ private_dns = (known after apply)

+ private_ip = (known after apply)

+ public_dns = (known after apply)

+ public_ip = (known after apply)

+ secondary_private_ips = (known after apply)

78

Chapter 3 IntroduCtIon to terraform

+ security_groups = (known after apply)

+ source_dest_check = true

+ subnet_id = (known after apply)

+ tags = {

+ "env" = "dev"

}

+ tags_all = {

+ "env" = "dev"

}

+ tenancy = (known after apply)

+ user_data = "7bdaf2f31b227

2f40c05104fc1ce

987cdcdbafd5"

+ user_data_base64 = (known after apply)

+ vpc_security_group_ids = (known after apply)

+ capacity_reservation_specification {

+ capacity_reservation_preference = (known after apply)

+ capacity_reservation_target {

+ capacity_reservation_id = (known after apply)

}

}

+ ebs_block_device {

+ delete_on_termination = (known after apply)

+ device_name = (known after apply)

+ encrypted = (known after apply)

+ iops = (known after apply)

+ kms_key_id = (known after apply)

+ snapshot_id = (known after apply)

+ tags = (known after apply)

+ throughput = (known after apply)

+ volume_id = (known after apply)

79

Chapter 3 IntroduCtIon to terraform

+ volume_size = (known after apply)

+ volume_type = (known after apply)

}

+ enclave_options {

+ enabled = (known after apply)

}

+ ephemeral_block_device {

+ device_name = (known after apply)

+ no_device = (known after apply)

+ virtual_name = (known after apply)

}

+ metadata_options {

+ http_endpoint = (known after apply)

+ http_put_response_hop_limit = (known after apply)

+ http_tokens = (known after apply)

}

+ network_interface {

+ delete_on_termination = (known after apply)

+ device_index = (known after apply)

+ network_interface_id = (known after apply)

}

+ root_block_device {

+ delete_on_termination = (known after apply)

+ device_name = (known after apply)

+ encrypted = (known after apply)

+ iops = (known after apply)

+ kms_key_id = (known after apply)

+ tags = (known after apply)

80

Chapter 3 IntroduCtIon to terraform

+ throughput = (known after apply)

+ volume_id = (known after apply)

+ volume_size = (known after apply)

+ volume_type = (known after apply)

}

}

Plan: 1 to add, 0 to change, 0 to destroy.

────────────────────────────────────────────

────────────────────────────────────────────

────────────────────────────────────────────

─────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so

Terraform can't guarantee to take exactly these actions if you

run "terraform apply" now.

Tip terraform plan outputs can run into hundreds and even

thousands of lines; hence, if you wish to save the output, then you

can use the command terraform plan -out plan.tfplan to save the

plan that is not readable by a traditional editor but does contain all

the configuration information.

From the plan, it’s quite discernable that terraform is not only creating

the AWS EC2 instance but also creating everything associated with it like

the EBS volume, root volume, and network interfaces just to name a few.

81

Chapter 3 IntroduCtIon to terraform

3.4.4 Terraform Apply

Let’s now look at the final command: terraform apply. Upon firing the

terraform apply command, it’ll spool out the entire plan first and then wait for our input as shown here.

Let’s enter yes to execute our plan.

CLI Output 3-10. Terraform apply output

cmd> terraform apply

XXXXXXXXXXXX---SNIPPED----XXXXXXXXXXXX

Do you want to perform these actions?

Terraform will perform the actions described above.

Only 'yes' will be accepted to approve.

Enter a value: yes

aws_instance.apache2_server: Creating...

aws_instance.apache2_server: Still creating... [10s elapsed]

aws_instance.apache2_server: Still creating... [20s elapsed]

aws_instance.apache2_server: Still creating... [30s elapsed]

aws_instance.apache2_server: Creation complete after 35s

[id=i-0a2a8acd8cd1385c5]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

That’s it, our EC2 instance has now been created with just a few lines of

code! That is the power of infrastructure as code.

3.4.5 Terraform State File

We can now see that a new file has been created called terraform.state

whose contents are as follows. The tfstate file stores all the information about the resources that we need and terraform requires to store to

82

Chapter 3 IntroduCtIon to terraform

perform any other actions like update or deletion. Feel free to scroll

through the next two pages as I just wanted to share here what the

terraform state looks like.

CLI Output 3-11. Terraform tfstate file

cmd> cat terraform.tfstate

{

"version": 4,

"terraform_version": "1.1.0",

"serial": 1,

"lineage": "1c277ed5-56d2-74b8-5037-026d4dac8a76",

"outputs": {},

"resources": [

{

"mode": "managed",

"type": "aws_instance",

"name": "apache2_server",

"provider": "provider[\"registry.terraform.io/hashicorp/

aws\"]",

"instances": [

{

"schema_version": 1,

"attributes": {

"ami": "ami-00399ec92321828f5",

"arn": "arn:aws:ec2:us-

east- 2:XXXXXXXXXXXX:instance/i-0a2a8acd8cd1385c5",

"associate_public_ip_address": true,

"availability_zone": "us-east-2b",

83

Chapter 3 IntroduCtIon to terraform

"capacity_reservation_specification": [

{

"capacity_reservation_preference": "open",

"capacity_reservation_target": []

}

],

"cpu_core_count": 1,

"cpu_threads_per_core": 1,

"credit_specification": [

{

"cpu_credits": "standard"

}

],

"disable_api_termination": false,

"ebs_block_device": [],

"ebs_optimized": false,

"enclave_options": [

{

"enabled": false

}

],

"ephemeral_block_device": [],

"get_password_data": false,

"hibernation": false,

"host_id": null,

"iam_instance_profile": "",

"id": "i-0a2a8acd8cd1385c5",

"instance_initiated_shutdown_behavior": "stop",

"instance_state": "running",

"instance_type": "t2.micro",

"ipv6_address_count": 0,

84

Chapter 3 IntroduCtIon to terraform

"ipv6_addresses": [],

"key_name": "",

"launch_template": [],

"metadata_options": [

{

"http_endpoint": "enabled",

"http_put_response_hop_limit": 1,

"http_tokens": "optional"

}

],

"monitoring": false,

"network_interface": [],

"outpost_arn": "",

"password_data": "",

"placement_group": "",

"placement_partition_number": null,

"primary_network_interface_id":

"eni-038a548d78778b9c6",

"private_dns": "ip-172-31-16-189.us-east-2.compute.

internal",

"private_ip": "172.31.16.189",

"public_dns": "ec2-34-209-221-82.us-east-2.compute.

amazonaws.com",

"public_ip": "34.209.221.82",

"root_block_device": [

{

"delete_on_termination": true,

"device_name": "/dev/sda1",

"encrypted": false,

"iops": 100,

"kms_key_id": "",

85

Chapter 3 IntroduCtIon to terraform

"tags": {},

"throughput": 0,

"volume_id": "vol-01e32fc3ab4908837",

"volume_size": 8,

"volume_type": "gp2"

}

],

"secondary_private_ips": [],

"security_groups": [

"default"

],

"source_dest_check": true,

"subnet_id": "subnet-3abb6842",

"tags": {

"env": "dev"

},

"tags_all": {

"env": "dev"

},

"tenancy": "default",

"timeouts": null,

"user_data":

"7bdaf2f31b2272f40c05104fc1ce987cdcdbafd5",

"user_data_base64": null,

"volume_tags": null,

"vpc_security_group_ids": [

"sg-f32c11d8"

]

},

86

Chapter 3 IntroduCtIon to terraform

"sensitive_attributes": [],

"private": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"

}

]

}

]

}

We can also find the IP address of the EC2 machine by querying using

the following command.

CLI Output 3-12. Querying for public IP address from tfstate

cmd> cat terraform.tfstate | grep "public_ip"

"associate_public_ip_address": true,

"public_ip": " 34.209.221.82",

3.4.6 Terraform Destroy

Let’s now destroy the EC2 machine and everything else created with it

using a simple command: terraform destroy.

CLI Output 3-13. Terraform Destroy command output

cmd> terraform destroy

aws_instance.apache2_server: Refreshing state...

[id=i-0a2a8acd8cd1385c5]

Terraform used the selected providers to generate the following

execution plan. Resource actions are indicated with the

following symbols:

- destroy

87

Chapter 3 IntroduCtIon to terraform

Terraform will perform the following actions:

# aws_instance.apache2_server will be destroyed

- resource "aws_instance" "apache2_server" {

- ami = "ami-00399ec923

21828f5" -> null

- arn = "arn:aws:ec2:us-

east- 2:87181177

8330:instance/

i-0a2a8acd8

cd1385c5"

-> null

- associate_public_ip_address = true -> null

- availability_zone = "us- east- 2b"

-> null

- cpu_core_count = 1 -> null

- cpu_threads_per_core = 1 -> null

- disable_api_termination = false -> null

- ebs_optimized = false -> null

- get_password_data = false -> null

- hibernation = false -> null

- id = "i-0a2a8acd8cd13

85c5" -> null

- instance_initiated_shutdown_behavior = "stop" -> null

- instance_state = "running" ->

null

- instance_type = "t2.micro" -> null

- ipv6_address_count = 0 -> null

- ipv6_addresses = [] -> null

- monitoring = false -> null

- primary_network_interface_id = "eni-038a548d78

778b9c6" -> null

88

Chapter 3 IntroduCtIon to terraform

- private_dns = "ip-172-31-16-

189.us-east-2.

compute.

internal"

-> null

- private_ip = "172.31.16.189"

-> null

- public_dns = "ec2-34-209-221-

82.us-east-2.

compute.

amazonaws.com"

-> null

- public_ip = "34.209.221.82"

-> null

- secondary_private_ips = [] -> null

- security_groups = [

- "default",

] -> null

- source_dest_check = true -> null

- subnet_id = "subnet-3abb6842"

-> null

- tags = {

- "env" = "dev"

} -> null

- tags_all = {

- "env" = "dev"

} -> null

- tenancy = "default" ->

null

89

Chapter 3 IntroduCtIon to terraform

- user_data = "7bdaf2f31b227

2f40c05104fc1ce

987cdcdbafd5"

-> null

- vpc_security_group_ids = [

- "sg-f32c11d8",

] -> null

XXXXXXXXXXXXXXX------SNIPPED-------XXXXXXXXXXXXXXXXXXXXX

Plan: 0 to add, 0 to change, 1 to destroy.

Do you really want to destroy all resources?

Terraform will destroy all your managed infrastructure, as

shown above.

There is no undo. Only 'yes' will be accepted to confirm.

Enter a value: yes

aws_instance.apache2_server: Destroying...

[id=i-0a2a8acd8cd1385c5]

aws_instance.apache2_server: Still destroying...

[id=i-0a2a8acd8cd1385c5, 10s elapsed]

aws_instance.apache2_server: Still destroying...

[id=i-0a2a8acd8cd1385c5, 20s elapsed]

aws_instance.apache2_server: Still destroying...

[id=i-0a2a8acd8cd1385c5, 30s elapsed]

aws_instance.apache2_server: Destruction complete after 37s

Destroy complete! Resources: 1 destroyed.

90

Chapter 3 IntroduCtIon to terraform

Note from now onward, to save the environment and to avoid

making you turn multiple pages just to skip the output of terraform

commands, I shall be omitting them (terraform output commands)

altogether except for a few places where required.

Tip We can use the --auto-approve flag to avoid manually entering

yes, each time running terraform apply and destroy. the following are

the full commands:

terraform apply --auto-approve

terraform destroy --auto-approve

3.5 Iteration #2

Now that we’ve seen the complete execution of the first iteration including

terraform init, plan, apply, and destroy, let’s try to understand what the

problems are associated with this iteration. The problems in the first

iteration are as follows:

A. We must use an extra command to identify the IP

address of the machine.

B. Our machine is still not accessible over the public

Internet as we did not apply the security groups.

C. All the values like the region, instance type, and

environment are being hard-coded, and if we wish

to change them, then we’ll have to modify the main

code, which is not recommended.

91

Chapter 3 IntroduCtIon to terraform

D. We’d also like to have a common naming

convention for all resources such that it’s easier

to identify which region and environment they

belong to.

E. Currently, the AMI ID that we are using is also

hard-coded, and we’d like to fetch the latest version

of Ubuntu for which we need to access the AWS

documentation, which is quite a manual process.

How will we solve the preceding problems? The following are the

answers to the corresponding questions in the same order:

A. We’ll be using output variables of terraform.

B. We’ll create new security groups.

C. We’ll be using a separate file called terraform.auto.

tfvars to store our variables.

D. We’ll make use of local variables where we can

combine multiple variables and use the expression

language syntax to have a uniform naming

convention.

E. We’ll use the data element in terraform to fetch the

latest Ubuntu AMI.

Let’s view the code! Since the code blocks are getting bigger, I shall

break them down.

3.5.1 Terraform Variables

Just like every programming language, terraform also supports variables

that can hold values. There are multiple ways in which we can pass the

variables to terraform through *.tfvars file and the variable declaration

syntaxes as shown here.

92

Chapter 3 IntroduCtIon to terraform

Code Block 3-14. terraform.auto.tfvars file with variables and values File: chapter3\infra\iteration2\terraform.auto.tfvars

1: environment = "dev"

2: region = "us-east-2"

3: instance_type = "t2.micro"

Code Block 3-15. main.tf file containing variable declarations

File: chapter3\infra\iteration2\main.tf

01: # Variable Declaration

02: variable "environment" {

03: description = "The environment e.g uat or prod or dev"

04: type = string

05: }

06:

07: variable "region" {

08: description = "The region where we wish to deploy to"

09: type = string

10: }

11:

12: variable "instance_type" {

13: description = "EC2 instance type"

14: type = string

15: }

16:

17: locals {

18: name-suffix = "${var.region}-${var.environment}"

19: }

93

Chapter 3 IntroduCtIon to terraform

L01–15: We are now declaring the variables of different types in main.tf

and their values in a file called terraform.auto.tfvars. If we wish to modify attributes like EC2 instance type or environment or region, we only need to

modify the terraform.auto.tfvars file. This file is imported automatically

when the terraform apply command is executed; hence, we don’t need to

specify anything on the command line.

Terraform supports various variable formats like number, lists, maps,

and boolean.

L17–19: If we wish to add an expression to a variable like adding a

suffix/prefix, then we can make use of a locals declaration wherein we can declare variables that support expression syntaxes. Here, we’ve declared

a variable called name-suffix, which is a concatenation of region and

environment with a hyphen.

3.5.2 Terraform Data Source

While working with terraform, there can arise multiple instances where we

may need to read data from an already-existing resource or a repository. The

data is what we need to declare in order to perform any such read operation.

Code Block 3-16. The usage of the data declaration for a read

operation

File: chapter3\infra\iteration2\main.tf

38: # Data element fetching the AMI ID of Ubuntu 20.04

39: data "aws_ami" "ubuntu" {

40: most_recent = true

41: filter {

42: name = "name"

43: values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-

amd64- server-*"]

44: }

94

Chapter 3 IntroduCtIon to terraform

45: filter {

46: name = "virtualization-type"

47: values = ["hvm"]

48: }

49: # AWS ID of Canonical organisation

50: owners = ["099720109477"]

51: }

L38-51: Using the resources element, we can perform Create/Update/

Delete operations in terraform, but what about Read? As discussed earlier,

there may arise situations where during the terraform execution, we may

be required to read data of existing resources in the cloud provider. This can be achieved using the data element that is applied on a resource called

aws_ami. Since AWS has hundreds of AMI (Amazon Machine Images) IDs, we are applying a filter to narrow down to what we need using the filter

and the owner elements.

3.5.3 Terraform Resource

Resources are the actual infrastructure objects that need to be declared in

terraform and are at the heart of the entire tool. Here, we are declaring two

resources: Security Groups and an EC2 Instance.

Code Block 3-17. Security group resource creation definitions

File: chapter3\infra\iteration2\main.tf

53: # Creating Security Group with ingress/egress rules

54: resource "aws_security_group" "public_http_sg" {

55: name = "public_http_sg-${local.name-suffix}"

56:

95

Chapter 3 IntroduCtIon to terraform

57: # Allow all inbound traffic to port 80

58: ingress {

59: from_port = 80

60: to_port = 80

61: protocol = "tcp"

62: cidr_blocks = ["0.0.0.0/0"]

63: }

64:

65: # Allow EC2 to be connected from internet

66: egress {

67: from_port = 0

68: to_port = 65535

69: protocol = "tcp"

70: cidr_blocks = ["0.0.0.0/0"]

71: }

72:

73: tags = {

74: "Environment" = var.environment

75: "visibility" = "public"

76: }

77: }

L53-77: We are declaring the resource for creating a security

group, which is declaring two rules: an ingress rule to allow inbound

communication on port 80 from anywhere in the world and an egress rule

for the EC2 machine to connect to the outside world. It’s only through

this rule that we’ve been able to access our EC2 machine from over the

Internet. You might find it difficult to understand why I have added the

egress rule. Why would the EC2 machine want to communicate with

the outside world? The simple answer is to contact the Ubuntu package

repository to download the Apache2 server packages!

96

Chapter 3 IntroduCtIon to terraform

Code Block 3-18. AWS EC2 instance creation

File: chapter3\infra\iteration2\main.tf

79: # AWS EC2 Resource creation

80: resource "aws_instance" "apache2_server" {

81: ami = data.aws_ami.ubuntu.id

82: instance_type = var.instance_type

83: vpc_security_group_ids = [aws_security_group.public_

http_sg.id]

84: user_data = "${file("user_data.sh")}"

85: tags = {

86: env = var.environment

87: Name = "ec2-${local.name-suffix}"

88: }

89: }

L80-89: The resource definition for EC2 was discussed earlier;

however, there is a slight change here where we’ve modified the way we

pass the value to the ami parameter on L81. We are now populating the

value with AMI ID from the data source declared earlier. We’ve also added

reference to the security group ID on L83 that we’ve added earlier.

3.5.4 Terraform Output

The output variable allows us to retrieve any attribute about any resource

that has been created using the terraform script.

97

Chapter 3 IntroduCtIon to terraform

Code Block 3-19. Terraform output declaration with public IP

File: chapter3\infra\iteration2\main.tf

91: # Output the Public IP Address

92: output "ip_address" {

93: value = aws_instance.apache2_server.public_ip

94: }

L91-94: Here, we are retrieving the value of the public_ip variable in the terraform state that we’ve seen earlier.

Let’s execute this terraform script using the following commands and

test if everything is working as expected. Only output pertaining to the

section needed is being shown here for brevity.

CLI Output 3-20. Execution of iteration #2 script

cmd> export AWS_PROFILE=gitops

cmd> terraform init

cmd> terraform plan

cmd> terraform apply --auto-approve

XXXXXXXXXXXXXXX------SNIPPED-------XXXXXXXXXXXXXXXXXXXXX

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

Outputs:

ip_address = "52.13.99.15"

cmd> curl -I http://34.217.31.143

HTTP/1.1 200 OK

Date: Wed, 03 Nov 2021 14:29:54 GMT

Server: Apache/2.4.41 (Ubuntu)

Last-Modified: Wed, 03 Nov 2021 14:27:59 GMT

ETag: "2aa6-5cfe33780883b"

98

Chapter 3 IntroduCtIon to terraform

Accept-Ranges: bytes

Content-Length: 10918

Vary: Accept-Encoding

Content-Type: text/html

cmd> terraform destroy --auto-approve

Hence, we are now able to access our EC2 machine and improvise the

code a little with terraform variables, data sources, and terraform output!

3.6 Iteration #3

In the second iteration, we were able to make it a little better from a coding

perspective; however, there have been a few things that were missing and

some best practices that we need to instill. The following are the issues and

suggestions that we’ll need to implement:

A. We are still missing the SSH rule in the security

group, because of which, we can’t SSH into your EC2

instances.

B. The security group resource code is getting

repetitive, and a lot of the information remains

common or not much used. Hence, it’s best if there

is a way that we can abstract that information and

focus on only what is needed. The abstraction

should be such that it is reusable as well.

C. Till now, we’ve been using a single .tf file to add

all the functionality; however, as we keep adding

additional functionality, our code will grow, and

keeping everything in a single file will become

problematic.

99

Chapter 3 IntroduCtIon to terraform

D. We need to ensure that the security groups

are always created before the EC2 instance is

instantiated because our startup script user_data.sh

won’t work if the security groups are not defined.

How will we solve the preceding problems? The following are the

answers to the corresponding questions in the same alphabetical order:

A. We’ll need to create a new SSH security group with

port mappings to the SSH port 22.

B. Since we are now repeating the creation of security

groups, we can modularize it. Terraform allows a

common functionality to be loaded as a module,

which is nothing but a very crude equivalent of a

function. A function takes inputs and spools out

output. We’ll see how a terraform module also does

almost the same thing.

C. We’ll need to segregate the code into different files

as a best practice if we need to continue to add more

functionality.

D. By using the input variables and module outputs, we

can define dependencies implicitly. In Terraform,

we can define dependencies in two ways implicitly

and explicitly:

Using input/output variables, dependencies can be

made implicit.

Using the depends_on keyword, we can make depen-

dencies more explicit to influence the order of execu-

tion. We’ll be exploring the role of this keyword in

more detail through practical examples in upcoming

chapters.

100

Chapter 3 IntroduCtIon to terraform

The new set of changes is not just code-level changes but a completely

different structure in which we’ve written the code. So let’s view the

structure that is shown here.

CLI Output 3-21. New structure for iteration #3

cmd> cd chapter3/infra/iteration3

cmd> tree -a

.

├── main.tf

├── modules

│ └── securitygroup

│ ├── main.tf

│ ├── output.tf

│ └── variables.tf

├── output.tf

├── providers.tf

├── scripts

│ └── user_data.sh

├── terraform.auto.tfvars

└── variables.tf

3 directories, 9 files

The variables.tf and terraform.auto.tfvars files contain the input variables and their values.

The output.tf contains all the output variables that need

to be seen on the CLI output.

The provider.tf contains the provider definitions and the

dependencies information.

101

Chapter 3 IntroduCtIon to terraform

The main.tf file contains the actual implementation of

our requirements using the different resources and data

elements.

The modules folder contains the different modules that

we’ve defined. Each module consists of the follow-

ing files:

main.tf – Contains the implementation and declara-

tion of the resources that need to be created

variables.tf – Contains the input variable declarations

for the variables being defined in main.tf

output.tf – The output information that may be

needed like the id number of the resource

The scripts folder contains the startup/user data scripts

that are needed to be executed.

3.6.1 Terraform Modules

Modules in terraform can be considered as a raw equivalent of functions

in imperative languages. We combine a set of common resource creation

functionality into one single unit for reusability. It is the only way to make

terraform reusable.

In terraform for module declaration, it is mandatory to have the

module in lieu of the resource keyword and the source, which can take various different values like folder/directory path, GIT URL, HTTP/HTTPS

URL, or any FQDN (fully qualified domain name) syntax. Here, we are

feeding the values to the input variables.

Let’s have a closer look at the main.tf file and the files under the

module directory to understand the changes that have been made.

102

Chapter 3 IntroduCtIon to terraform

Code Block 3-22. Module declarations and invocation

File: chapter3\infra\iteration3\main.tf

XXXXXXXXXXXXXXX------SNIPPED-------XXXXXXXXXXXXXXXXXXXXX

19:

20: # HTTP Ingress Security Group Module

21: module "http_sg_ingress" {

22: source = "./modules/securitygroup"

23:

24: sg_name = "http_sg_ingress"

25: sg_description = "Allow Port 80 from anywhere"

26: environment = var.environment

27: type = "ingress"

28: from_port = 80

29: to_port = 80

30: protocol = "tcp"

31: cidr_blocks = ["0.0.0.0/0"]

32: }

33:

34: # Generic Egress Security Group Module

35: module "generic_sg_egress" {

XXXXXXXXXXXXXXX------SNIPPED-------XXXXXXXXXXXXXXXXXXXXX

46: }

47:

48: # SSH Ingress Security Group Module

49: module "ssh_sg_ingress" {

XXXXXXXXXXXXXXX------SNIPPED-------XXXXXXXXXXXXXXXXXXXXX

60: }

61:

103

Chapter 3 IntroduCtIon to terraform

62: # AWS EC2 Resource creation

63: resource "aws_instance" "apache2_server" {

64: ami = data.aws_ami.ubuntu.id

65: instance_type = var.instance_type

66: vpc_security_group_ids = [module.http_sg_ingress.sg_id,

67: module.generic_sg_egress.sg_id,

68: module.ssh_sg_ingress.sg_id]

69: key_name = var.ssh_key_name

70: user_data = file("scripts/user_data.sh")

71: tags = {

72: env = var.environment

73: Name = "ec2-${local.name-suffix}"

74: }

75:

76: depends_on = [

77: module.generic_sg_egress

78: ]

79: }

L21–32: Since the security group resource is going to be invoked

multiple times, we’ve created a module and specified the location of the

module using the source keyword on L22.

L66–68: The output of the security group modules, that is, the security

group ID, is being fed to the EC2 resource.

L69: Adding the name of the SSH key that we created in the previous

chapter from the GUI. The variable ssh_key_name holds the name of the key, and the value is defined in the terraform.auto.tfvars file.

L76–78: Declaration of the depends_on meta-variable where we are explicitly setting the dependency on the module.generic_sg_egress

module as we need the egress rules to be created before the server

starts up.

104

Chapter 3 IntroduCtIon to terraform

Next, let’s look at the main.tf file of the security groups module, which contains the complete implementation of security group creation.

Code Block 3-23. Module implementation code

File: chapter3\infra\iteration3\modules\securitygroup\main.tf

01: # AWS Security Group definition

02: resource "aws_security_group" "security_group" {

03: name = var.sg_name

04: description = var.sg_description

05: tags = {

06: "Environment" = var.environment

07: }

08: }

09:

10: # AWS Security Group Rules definition

11: resource "aws_security_group_rule" "security_group_rule" {

12: type = var.type

13: from_port = var.from_port

14: to_port = var.to_port

15: protocol = var.protocol

16: cidr_blocks = var.cidr_blocks

17: security_group_id = aws_security_group.security_group.id

18: }

L02–08: In contrast to the original code for creating the security groups

that we’d seen here, we are declaring two resources instead of a single

resource. This is a terraform best practice as it helps in easy decoupling

where we are declaring the security group and the rules of that security

group separately. So here, we are first declaring the security group alone.

L11–18: We are declaring the rules for the said security group and

assigning references to the security group created on L17.

105

Chapter 3 IntroduCtIon to terraform

The variables.tf and outputs.tf files contain the input and output variables for this module, respectively.

Let’s get to action and execute this terraform script using the following

commands.

Note Before firing the following commands, ensure you have the

key that was created in the previous chapter as we’ll be needing it to

SSh into our eC2 instance.

CLI Output 3-24. Executing code of iteration #3

cmd> export AWS_PROFILE=gitops

cmd> cd chapter3/infra/iteration3

cmd> terraform init

cmd> terraform plan

cmd> terraform apply --auto-approve

XXXXXXXXXXXXXXX------SNIPPED-------XXXXXXXXXXXXXXXXXXXXX

Apply complete! Resources: 7 added, 0 changed, 0 destroyed.

Outputs:

ip_address = "18.222.72.137"

cmd> curl -I http://18.222.72.137

HTTP/1.1 200 OK

Date: Mon, 08 Nov 2021 09:40:48 GMT

Server: Apache/2.4.41 (Ubuntu)

XXXXXXXXXXXXXXX------SNIPPED-------XXXXXXXXXXXXXXXXXXXXX

cmd> chmod 600 gitops.pem

cmd> ssh -i gitops.pem [email protected]

106

Chapter 3 IntroduCtIon to terraform

The authenticity of host '18.222.72.137 (18.222.72.137)' can't

be established.

ECDSA key fingerprint is SHA256:xucxKFUDt0z+dCxJ3R+SM0NcEANaosT

tGLLUV8Tlcgc.

Are you sure you want to continue connecting (yes/no/

[fingerprint])? yes

Welcome to Ubuntu 20.04.3 LTS (GNU/Linux 5.11.0-1020-

aws x86_64)

XXXXXXXXXXXXXXX------SNIPPED-------XXXXXXXXXXXXXXXXXXXXX

3.7 Selective Destroy

There may arise situations where we do not wish to destroy the entire

infrastructure, but rather just a small part of it. For example, in our

previous example, we wish to destroy the EC2 instance that we’ve created

without destroying the security groups that are declared.

The best way to be able to do this is to comment out the portion we

wish to destroy and then perform terraform apply.

Note // and # are the acceptable comment characters for terraform

compilers.

Let’s run through a practical example to understand how this would

work. Open the main.tf file in the iteration3 folder and comment out the

apache2_server resource from the main.tf file and the output variable

from the output.tf file as shown here.

107

Chapter 3 IntroduCtIon to terraform

Code Block 3-25. Commenting out EC2 declaration

File: chapter3\infra\iteration3\main.tf

XXXXXXXXXXXXXXX------SNIPPED-------XXXXXXXXXXXXXXXXXXXXX

61:

62: # AWS EC2 Resource creation

63: // resource "aws_instance" "apache2_server" {

64: // ami = data.aws_ami.ubuntu.id

65: // instance_type = var.instance_type

66: // vpc_security_group_ids = [module.http_sg_ingress.sg_id,

67: // module.generic_sg_egress.sg_id,

68: // module.ssh_sg_ingress.sg_id]

69: // key_name = var.ssh_key_name

70: // user_data = file("scripts/user_data.sh")

71: // tags = {

72: // env = var.environment

73: // Name = "ec2-${local.name-suffix}"

74: // }

75:

76: // depends_on = [

77: // module.generic_sg_egress

78: // ]

79: // }

Code Block 3-26. Commenting out terraform output declaration

File: chapter3\infra\iteration3\output.tf

1: // # Output the Public IP Address

2: // output "ip_address" {

3: // value = aws_instance.apache2_server.public_ip

4: // }

108

Chapter 3 IntroduCtIon to terraform

Next, follow the following commands and observe that the EC2

machine will get destroyed after we run terraform apply.

CLI Output 3-27. Executing code after commenting the EC2

instance

cmd> export AWS_PROFILE=gitops

cmd> cd chapter3/infra/iteration3

cmd> terraform apply --auto-approve

XXXXXXXXXXXXXXX------SNIPPED-------XXXXXXXXXXXXXXXXXXXXX

Terraform used the selected providers to generate the following

execution plan. Resource actions are indicated with the

following symbols:

- destroy

Terraform will perform the following actions:

# aws_instance.apache2_server will be destroyed

- resource "aws_instance" "apache2_server" {

XXXXXXXXXXXXXXX------SNIPPED-------XXXXXXXXXXXXXXXXXXXXX

}

Plan: 0 to add, 0 to change, 1 to destroy.

Changes to Outputs:

- ip_address = "13.58.114.103" -> null

aws_instance.apache2_server: Destroying...

[id=i-03ad2902551bfd91d]

aws_instance.apache2_server: Still destroying...

[id=i-03ad2902551bfd91d, 10s elapsed]

aws_instance.apache2_server: Still destroying...

[id=i-03ad2902551bfd91d, 20s elapsed]

aws_instance.apache2_server: Still destroying...

[id=i-03ad2902551bfd91d, 30s elapsed]

109

Chapter 3 IntroduCtIon to terraform

aws_instance.apache2_server: Destruction complete after 35s

Apply complete! Resources: 0 added, 0 changed, 1 destroyed.

From the preceding output, it is clearly visible that Terraform

destroyed our EC2 instance without destroying the security groups that

were associated with it.

Let’s finally destroy everything so that we don’t end up getting billed

unnecessarily.

CLI Output 3-28. Terraform destroys everything

cmd> cd chapter3/infra/iteration3

cmd> terraform destroy --auto-approve

3.7.1 Terraform Destroy Protection

Destroying a terraform resource is a one-way street; there is absolutely

nothing one can do once it is destroyed other than spinning it up again.

That generally is not a problem for immutable resources like EC2 servers

or docker containers; in fact, they are designed to be flexible in all

scenarios, even accidental deletion.

However, certain mutable resources like a Database server must be

protected from accidental deletion. There are multiple ways possible to do this using AWS CLI and the GUI console, but how can this be done

using terraform?

The answer is by using the life cycle meta-argument as shown in the

following, L80–82.

Note this code is not available in the repository; it is only for

demonstration purposes here.

110

Chapter 3 IntroduCtIon to terraform

Code Block 3-29. Terraform prevent destroy life cyle

meta-argument

63: resource "aws_instance" "apache2_server" {

64: ami = data.aws_ami.ubuntu.id

65: instance_type = var.instance_type

66: vpc_security_group_ids = [module.http_sg_ingress.sg_id,

67: module.generic_sg_egress.sg_id,

68: module.ssh_sg_ingress.sg_id]

69: key_name = var.ssh_key_name

70: user_data = file("scripts/user_data.sh")

71: tags = {

72: env = var.environment

73: Name = "ec2-${local.name-suffix}"

74: }

75:

76: depends_on = [

77: module.generic_sg_egress

78: ]

79:

80: lifecycle {

81: prevent_destroy = true

82: }

83:

84: }

Here, when we do terraform destroy, terraform refuses to destroy

our EC2 instance because of the life cycle meta-argument as shown in the

following output.

111

Chapter 3 IntroduCtIon to terraform

CLI Output 3-30. Deletion protection terraform apply

cmd> terraform destroy --auto-approve

XXXXXXXXXXXXXXX------SNIPPED-------XXXXXXXXXXXXXXXXXXXXX

│ Error: Instance cannot be destroyed

│ on main.tf line 63:

│ 63: resource "aws_instance" "apache2_server" {

Resource aws_instance.apache2_server has lifecycle.prevent_

destroy set, but the plan calls for this resource to be

destroyed. To avoid this error and continue with the

│ plan, either disable lifecycle.prevent_destroy or reduce the

scope of the plan using the -target flag.

To delete the resource, we’ll need to flip the boolean from true to false:

prevent_destroy = false.

Hence, in this manner, accidental deletions can be avoided.

3.8 Terraform Drift

Drift means moving away from the normal state, which very well resonates

with the concept of terraform drift. When we hit apply, we are moving

our desired state defined in *.tf files to current state stored in *.tfstate file. Anything that is managed and defined in *.tf files can be managed

by terraform, but what about changes that are happening to resources

outside of terraform on the resources that are created using terraform? For

example, if we have an EC2 instance with security groups created using

terraform and we edit one of the security groups to add one more rule from

GUI. How would terraform behave in this scenario?

112

Chapter 3 IntroduCtIon to terraform

Terraform will attempt to reconcile with the state that is currently

stored in the .tfstate file and the actual configuration that is created in the cloud.

Let’s take a practical example to understand how exactly Terraform will

do this reconciliation.

For this, we’ll need access to the AWS GUI, and we’ll also need to spin

up the EC2 machine using the iteration #3 code.

Let’s first spin up our EC2 machine using terraform by executing the

following commands.

CLI Output 3-31. Executing terraform for iteration #3

cmd> export AWS_PROFILE=gitops

cmd> cd chapter3/infra/iteration3

cmd> terraform apply --auto-approve

Now let’s head to our AWS GUI console and search and navigate to the

Security Groups section as shown here.

113

Chapter 3 IntroduCtIon to terraform

Figure 3-2. Accessing Security Groups in AWS GUI

In this section, we can see all the rules that were created by terraform.

We now need to edit the http group.

1. Select the http_sg_ingress rule.

2. Click on the Actions tab and then click Edit

inbound rules.

114

Chapter 3 IntroduCtIon to terraform

Figure 3-3. Editing Security Groups from AWS GUI

In the edit screen:

1. Click Add rule.

2. Select Custom TCP and enter the port number as

8080 and CIDR as 0.0.0.0/0.

3. Click Save rules.

Figure 3-4. Adding port 8080 as an additional security group rule

Let’s confirm that our rule has been added by viewing the screen here.

115

Chapter 3 IntroduCtIon to terraform

Figure 3-5. Rule edition confirmation

This addition was done manually from the GUI. Let’s head back to our

terminal and now execute terraform plan as shown here.

CLI Output 3-32. Terraform plan for reconciliation with changes

made in AWS GUI

cmd> terraform plan

XXXXXXXXXXXXXXX------SNIPPED-------XXXXXXXXXXXXXXXXXXXXX

Note: Objects have changed outside of Terraform

Terraform detected the following changes made outside of

Terraform since the last "terraform apply":

# module.generic_sg_egress.aws_security_group_rule.security_

group_rule has been changed

~ resource "aws_security_group_rule" "security_group_rule" {

id = "sgrule-1495426135"

+ ipv6_cidr_blocks = []

+ prefix_list_ids = []

# (7 unchanged attributes hidden)

}

116

Chapter 3 IntroduCtIon to terraform

# module.http_sg_ingress.aws_security_group.security_group

has been changed

~ resource "aws_security_group" "security_group" {

id = "sg-064becd5995aae1bb"

~ ingress = [

+ {

+ cidr_blocks = [

+ "0.0.0.0/0",

]

+ description = ""

+ from_port = 8080

+ ipv6_cidr_blocks = []

+ prefix_list_ids = []

+ protocol = "tcp"

+ security_groups = []

+ self = false

+ to_port = 8080

},

+ {

+ cidr_blocks = [

+ "0.0.0.0/0",

]

+ description = ""

+ from_port = 80

+ ipv6_cidr_blocks = []

+ prefix_list_ids = []

+ protocol = "tcp"

+ security_groups = []

+ self = false

+ to_port = 80

},

]

117

Дальше: 3.9 Clean-Up