Infrastructure as Code#
Challenge of Provisioning Cloud Resources#
Manual Provisioning of infrastructure at scale is slow and cumbersome. Provisioning infrastructure through
point-and-click GUIs is error-prone, inefficient, and doesn’t scale. The cloud was built for
automation, but writing custom scripts for every service and every use case is time-consuming
and costly for development teams.
Solution#
Infrastructure as Code(IaC) to automate infrastructure provisioning on any cloud. Multi-cloud provisioning
should be automated with declarative infrastructure as code. There should be a simple, easy to learn
configuration language to allow people to define infrastructure resources such that infrastructure can be codified,
shared, versioned, and executed with a consistent workflow across all environments.
There are dozens of different tools for infrastructure as code, and
Terraform, developed by HashiCorp, is one of the most popular IaC tool in the industry now.
Alternatively, OpenTofu is an open-source tool forked from terraform 1.5.6 and offers similar
features and interfaces.
Terraform relies on different service providers to actual manage infrastructure resources on different platforms.
Service provider is actually a module that use upstream platform APIs to provision resources.
There are 5 steps to deploy infrastructure with Terraform:
- Scope - Identify the infrastructure for the project
- Author - Write the configuration for the infrastructure
- Initialize - Install the plugins Terraform needs to manage the infrastructure
- Plan - Preview the changes Terraform will make to match your configuration
- Apply - Make the planned changes
Use Homebrew to install Terraform on Mac
1
2
| brew tap hashicorp/tap
brew install hashicorp/tap/terraform
|
Verify the Installation
1
2
3
4
5
6
7
8
9
| terraform -help
Usage: terraform [-version] [-help] <command> [args]
The available commands for execution are listed below.
The most common, useful commands are shown first, followed by
less common or more advanced commands. If you're just getting
started with Terraform, stick with the common commands. For the
other commands, please read the help and docs before usage.
|
Prerequisite#
- Docker Desktop Installed and Running
- Terraform Installed
Create a director for the project#
Create and navigate to a director for the project
1
2
| mkdir learn-terraform-docker-container
cd learn-terraform-docker-container
|
In the working directory, create a fill named main.tf with the following configuration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| terraform {
required_providers {
docker = {
source = "kreuzwerker/docker"
version = "~> 3.0.1"
}
}
}
provider "docker" {}
resource "docker_image" "nginx" {
name = "nginx"
keep_locally = false
}
resource "docker_container" "nginx" {
image = docker_image.nginx.image_id
name = "tutorial"
ports {
internal = 80
external = 8000
}
}
|
Initialize the Project#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| terraform init
Initializing the backend...
Initializing provider plugins...
- Reusing previous version of kreuzwerker/docker from the dependency lock file
- Using previously-installed kreuzwerker/docker v3.0.2
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.
|
This will install the required plugins for the providers in the configuration.
Plan and Apply the config#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
| terraform plan
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# docker_container.nginx will be created
+ resource "docker_container" "nginx" {
+ attach = false
+ bridge = (known after apply)
+ command = (known after apply)
+ container_logs = (known after apply)
+ container_read_refresh_timeout_milliseconds = 15000
+ entrypoint = (known after apply)
+ env = (known after apply)
+ exit_code = (known after apply)
+ hostname = (known after apply)
+ id = (known after apply)
+ image = (known after apply)
+ init = (known after apply)
+ ipc_mode = (known after apply)
+ log_driver = (known after apply)
+ logs = false
+ must_run = true
+ name = "tutorial"
+ network_data = (known after apply)
+ read_only = false
+ remove_volumes = true
+ restart = "no"
+ rm = false
+ runtime = (known after apply)
+ security_opts = (known after apply)
+ shm_size = (known after apply)
+ start = true
+ stdin_open = false
+ stop_signal = (known after apply)
+ stop_timeout = (known after apply)
+ tty = false
+ wait = false
+ wait_timeout = 60
+ healthcheck (known after apply)
+ labels (known after apply)
+ ports {
+ external = 8000
+ internal = 80
+ ip = "0.0.0.0"
+ protocol = "tcp"
}
}
# docker_image.nginx will be created
+ resource "docker_image" "nginx" {
+ id = (known after apply)
+ image_id = (known after apply)
+ keep_locally = false
+ name = "nginx"
+ repo_digest = (known after apply)
}
Plan: 2 to add, 0 to change, 0 to destroy.
|
This will preview what kind of changes terraform will apply.
If everything looks good, we can proceed to apply it. It will require an input of ‘Yes’ to confirm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
| terraform apply
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# docker_container.nginx will be created
+ resource "docker_container" "nginx" {
+ attach = false
+ bridge = (known after apply)
+ command = (known after apply)
+ container_logs = (known after apply)
+ container_read_refresh_timeout_milliseconds = 15000
+ entrypoint = (known after apply)
+ env = (known after apply)
+ exit_code = (known after apply)
+ hostname = (known after apply)
+ id = (known after apply)
+ image = (known after apply)
+ init = (known after apply)
+ ipc_mode = (known after apply)
+ log_driver = (known after apply)
+ logs = false
+ must_run = true
+ name = "tutorial"
+ network_data = (known after apply)
+ read_only = false
+ remove_volumes = true
+ restart = "no"
+ rm = false
+ runtime = (known after apply)
+ security_opts = (known after apply)
+ shm_size = (known after apply)
+ start = true
+ stdin_open = false
+ stop_signal = (known after apply)
+ stop_timeout = (known after apply)
+ tty = false
+ wait = false
+ wait_timeout = 60
+ healthcheck (known after apply)
+ labels (known after apply)
+ ports {
+ external = 8000
+ internal = 80
+ ip = "0.0.0.0"
+ protocol = "tcp"
}
}
# docker_image.nginx will be created
+ resource "docker_image" "nginx" {
+ id = (known after apply)
+ image_id = (known after apply)
+ keep_locally = false
+ name = "nginx"
+ repo_digest = (known after apply)
}
Plan: 2 to add, 0 to change, 0 to destroy.
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
docker_image.nginx: Creating...
docker_image.nginx: Still creating... [10s elapsed]
docker_image.nginx: Still creating... [20s elapsed]
docker_image.nginx: Creation complete after 26s [id=sha256:a72860cb95fd59e9c696c66441c64f18e66915fa26b249911e83c3854477ed9anginx]
docker_container.nginx: Creating...
docker_container.nginx: Creation complete after 3s [id=89fb0fe9d56082357e1059cf4229dee92979154d73402b2758c872ffda0cf104]
|
Verify Nginx Container Being Created#
1
2
3
4
| docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
89fb0fe9d560 a72860cb95fd "/docker-entrypoint.…" About a minute ago Up About a minute 0.0.0.0:8000->80/tcp tutorial
|
visit localhost:8000 in the browser:
Prerequisite#
- Terraform installed
- An active Azure subscription (a free subscription would work)
- Azure CLI installed (brew install for MacOS)
1
| brew update && brew install azure-cli
|
Authenticate using Azure CLI#
Login#
or using this command with non default browser
1
| az login --use-device-code
|
Set the Subscription#
1
| az account set --subscription "your-subscription-id"
|
Create a Service Principal#
A Service Principal is an application within Azure Active Directory with the authentication tokens Terraform needs to perform actions on your behalf.
1
2
3
4
5
6
7
8
9
10
| az ad sp create-for-rbac --role="Contributor" --scopes="/subscriptions/<SUBSCRIPTION_ID>"
Creating 'Contributor' role assignment under scope '/subscriptions/your-subscription-id'
The output includes credentials that you must protect. Be sure that you do not include these credentials in your code or check the credentials into your source control. For more information, see https://aka.ms/azadsp-cli
{
"appId": "xxxxxx-xxx-xxxx-xxxx-xxxxxxxxxx",
"displayName": "azure-cli-2022-xxxx",
"password": "xxxxxx~xxxxxx~xxxxx",
"tenant": "xxxxx-xxxx-xxxxx-xxxx-xxxxx"
}
|
Set the Environment Variables#
1
2
3
4
| export ARM_CLIENT_ID="<APPID_VALUE>"
export ARM_CLIENT_SECRET="<PASSWORD_VALUE>"
export ARM_SUBSCRIPTION_ID="<SUBSCRIPTION_ID>"
export ARM_TENANT_ID="<TENANT_VALUE>"
|
Create a Project Folder#
1
2
| mkdir learn-terraform-azure
cd learn-terraform-azure
|
Create a file named main.tf with the following content
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| # Configure the Azure provider
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.0.2"
}
}
required_version = ">= 1.1.0"
}
provider "azurerm" {
features {}
}
resource "azurerm_resource_group" "rg" {
name = "myTFResourceGroup"
location = "westus2"
}
|
Initialize
Format the config
Validate the config
Apply the config
Inspect your state
1
2
3
4
5
6
7
8
| terraform show
azurerm_resource_group.rg:
resource "azurerm_resource_group" "rg" {
id = "/subscriptions/c9ed8610-47a3-4107-a2b2-a322114dfb29/resourceGroups/myTFResourceGroup"
location = "westus2"
name = "myTFResourceGroup"
}
|
1
2
3
| terraform state list
azurerm_resource_group.rg
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| terraform state
Usage: terraform state <subcommand> [options] [args]
This command has subcommands for advanced state management.
These subcommands can be used to slice and dice the Terraform state.
This is sometimes necessary in advanced cases. For your safety, all
state management commands that modify the state create a timestamped
backup of the state prior to making modifications.
The structure and output of the commands is specifically tailored to work
well with the common Unix utilities such as grep, awk, etc. We recommend
using those tools to perform more advanced state tasks.
Subcommands:
list List resources in the state
mv Move an item in the state
pull Pull current state and output to stdout
push Update remote state from a local state file
replace-provider Replace provider in the state
rm Remove instances from the state
show Show a resource in the state
|
Terraform language is an easy to learn and simple to use language to declare infrastructure resources.
Can read about it in details at: https://developer.hashicorp.com/terraform/language