{{announcement.body}}
{{announcement.title}}

Getting Started With Terraform Modules

DZone 's Guide to

Getting Started With Terraform Modules

Build your first terraform module and test it for free in Google Cloud with Google Kubernetes Engine.

· DevOps Zone ·
Free Resource

Introduction

In this article, we will see a subtle introduction to terraform modules, how to pass data into the module, get something from the module and create a resource (GKE cluster), it’s intended to be as simple as possible just to be aware of what a module is composed of, or how can you do your own modules, sometimes it makes sense to have modules to abstract implementations that you use over several projects, or things that are often repeated along the project. So let’s see what it takes to create and use a module. 

The source code for this article can be found here. Note that in this example I’m using GCP since they give you $300 USD for a year to try their services and it looks pretty good so far, after sign-up you will need to go to IAM, then create a service account and after that export the key (this is required for the terraform provider to talk to GCP).

Composition of a Module

A module can be any folder with a main.tf file in it, yes, that is the only required file for a module to be usable, but the recommendation is that you also put a README.md file with a description of the module if it’s intended to be used by people if it’s a sub-module it’s not necessary, also you will need a file called variables.tf and other outputs.tf of course if it’s a big module that cannot be split into sub-modules you can split those files for convenience or readability, variables should have descriptions so the tooling can show you what are they for, you can read more about the basics for a module here.

Before moving on let’s see the folder structure of our project:

Java
 




x
11


 
1
├── account.json
2
├── LICENSE
3
├── main.tf
4
├── module
5
   ├── main.tf
6
   ├── outputs.tf
7
   └── variables.tf
8
├── README.md
9
└── terraform.tfvars
10
 
          
11
1 directory, 8 files



The Project

Let’s start with the main.tf that will call our module, notice that I added a few additional comments but it’s pretty much straight forward, we set the provider, then we define some variables, call our module and print some output (output can also be used to pass data between modules).

Java
 




xxxxxxxxxx
1
39


 
1
# Set the provider to be able to talk to GCP
2
provider "google" {
3
  credentials = "${file("account.json")}"
4
  project     = "${var.project_name}"
5
  region      = "${var.region}"
6
}
7
 
          
8
# Variable definition
9
variable "project_name" {
10
  default = "testinggcp"
11
  type    = "string"
12
}
13
 
          
14
variable "cluster_name" {
15
  default = "demo-terraform-cluster"
16
  type    = "string"
17
}
18
 
          
19
variable "region" {
20
  default = "us-east1"
21
  type    = "string"
22
}
23
 
          
24
variable "zone" {
25
  default = "us-east1-c"
26
  type    = "string"
27
}
28
 
          
29
# Call our module and pass the var zone in, and get cluster_name out
30
module "terraform-gke" {
31
  source = "./module"
32
  zone = "${var.zone}"
33
  cluster_name = "${var.cluster_name}"
34
}
35
 
          
36
# Print the value of k8s_master_version
37
output "kubernetes-version" {
38
  value = module.terraform-gke.k8s_master_version
39
}



Then terraform.tfvars has some values to override the defaults that we defined:

Java
 




xxxxxxxxxx
1


 
1
project_name = "testingcontainerengine"
2
cluster_name = "demo-cluster"
3
region = "us-east1"
4
zone = "us-east1-c"



The Module

Now into the module itself, this module will create a GKE cluster, and while it’s not a good practice to have a module as a wrapper but for this example, we will forget about that rule for a while, this is the main.tf file:

Java
 




xxxxxxxxxx
1


 
1
# Create the cluster
2
resource "google_container_cluster" "gke-cluster" {
3
  name               = "${var.cluster_name}"
4
  network            = "default"
5
  zone               = "${var.zone}"
6
  initial_node_count = 3
7
}



The variables.tf file:

Java
 




xxxxxxxxxx
1
14


 
1
variable "cluster_name" {
2
  default = "terraform-module-demo"
3
  type    = "string"
4
}
5
 
          
6
variable "zone" {
7
  default = "us-east1-b"
8
  type    = "string"
9
}
10
 
          
11
variable "region" {
12
  default = "us-east1"
13
  type = "string"
14
}



And finally the outputs.tf file:

Java
 




xxxxxxxxxx
1
23


 
1
output "k8s_endpoint" {
2
  value = "${google_container_cluster.gke-cluster.endpoint}"
3
}
4
 
          
5
output "k8s_master_version" {
6
  value = "${google_container_cluster.gke-cluster.master_version}"
7
}
8
 
          
9
output "k8s_instance_group_urls" {
10
  value = "${google_container_cluster.gke-cluster.instance_group_urls.0}"
11
}
12
 
          
13
output "k8s_master_auth_client_certificate" {
14
  value = "${google_container_cluster.gke-cluster.master_auth.0.client_certificate}"
15
}
16
 
          
17
output "k8s_master_auth_client_key" {
18
  value = "${google_container_cluster.gke-cluster.master_auth.0.client_key}"
19
}
20
 
          
21
output "k8s_master_auth_cluster_ca_certificate" {
22
  value = "${google_container_cluster.gke-cluster.master_auth.0.cluster_ca_certificate}"
23
}



Notice that we have a lot more outputs than the one we decided to print out, but you can play with that and experiment if you want :)

Testing It

First, we need to initialize our project so terraform can put modules, provider files, etc in place, it’s a good practice to version things and to move between versions that way everything can be tested and if something is not working as expected you can always roll back to the previous state.

Java
 




xxxxxxxxxx
1
26


 
1
$ terraform init 
2
Initializing the backend...
3
 
          
4
Initializing provider plugins...
5
- Checking for available provider plugins...
6
- Downloading plugin for provider "google" (terraform-providers/google) 2.9.1...
7
 
          
8
The following providers do not have any version constraints in configuration,
9
so the latest version was installed.
10
 
          
11
To prevent automatic upgrades to new major versions that may contain breaking
12
changes, it is recommended to add version = "..." constraints to the
13
corresponding provider blocks in configuration, with the constraint strings
14
suggested below.
15
 
          
16
* provider.google: version = "~> 2.9"
17
 
          
18
Terraform has been successfully initialized!
19
 
          
20
You may now begin working with Terraform. Try running "terraform plan" to see
21
any changes that are required for your infrastructure. All Terraform commands
22
should now work.
23
 
          
24
If you ever set or change modules or backend configuration for Terraform,
25
rerun this command to reinitialize your working directory. If you forget, other
26
commands will detect it and remind you to do so if necessary.



Then we will just run it.

Java
 




xxxxxxxxxx
1
175


 
1
$ terraform apply
2
 
          
3
An execution plan has been generated and is shown below.
4
Resource actions are indicated with the following symbols:
5
  + create
6
 
          
7
Terraform will perform the following actions:
8
 
          
9
  # module.terraform-gke.google_container_cluster.gke-cluster will be created
10
  + resource "google_container_cluster" "gke-cluster" {
11
      + additional_zones            = (known after apply)
12
      + cluster_autoscaling         = (known after apply)
13
      + cluster_ipv4_cidr           = (known after apply)
14
      + enable_binary_authorization = (known after apply)
15
      + enable_kubernetes_alpha     = false
16
      + enable_legacy_abac          = false
17
      + enable_tpu                  = (known after apply)
18
      + endpoint                    = (known after apply)
19
      + id                          = (known after apply)
20
      + initial_node_count          = 3
21
      + instance_group_urls         = (known after apply)
22
      + ip_allocation_policy        = (known after apply)
23
      + location                    = (known after apply)
24
      + logging_service             = (known after apply)
25
      + master_version              = (known after apply)
26
      + monitoring_service          = (known after apply)
27
      + name                        = "demo-cluster"
28
      + network                     = "default"
29
      + node_locations              = (known after apply)
30
      + node_version                = (known after apply)
31
      + project                     = (known after apply)
32
      + region                      = (known after apply)
33
      + services_ipv4_cidr          = (known after apply)
34
      + subnetwork                  = (known after apply)
35
      + zone                        = "us-east1-c"
36
 
          
37
      + addons_config {
38
          + horizontal_pod_autoscaling {
39
              + disabled = (known after apply)
40
            }
41
 
          
42
          + http_load_balancing {
43
              + disabled = (known after apply)
44
            }
45
 
          
46
          + kubernetes_dashboard {
47
              + disabled = (known after apply)
48
            }
49
 
          
50
          + network_policy_config {
51
              + disabled = (known after apply)
52
            }
53
        }
54
 
          
55
      + master_auth {
56
          + client_certificate     = (known after apply)
57
          + client_key             = (sensitive value)
58
          + cluster_ca_certificate = (known after apply)
59
          + password               = (sensitive value)
60
          + username               = (known after apply)
61
 
          
62
          + client_certificate_config {
63
              + issue_client_certificate = (known after apply)
64
            }
65
        }
66
 
          
67
      + network_policy {
68
          + enabled  = (known after apply)
69
          + provider = (known after apply)
70
        }
71
 
          
72
      + node_config {
73
          + disk_size_gb      = (known after apply)
74
          + disk_type         = (known after apply)
75
          + guest_accelerator = (known after apply)
76
          + image_type        = (known after apply)
77
          + labels            = (known after apply)
78
          + local_ssd_count   = (known after apply)
79
          + machine_type      = (known after apply)
80
          + metadata          = (known after apply)
81
          + min_cpu_platform  = (known after apply)
82
          + oauth_scopes      = (known after apply)
83
          + preemptible       = (known after apply)
84
          + service_account   = (known after apply)
85
          + tags              = (known after apply)
86
 
          
87
          + taint {
88
              + effect = (known after apply)
89
              + key    = (known after apply)
90
              + value  = (known after apply)
91
            }
92
 
          
93
          + workload_metadata_config {
94
              + node_metadata = (known after apply)
95
            }
96
        }
97
 
          
98
      + node_pool {
99
          + initial_node_count  = (known after apply)
100
          + instance_group_urls = (known after apply)
101
          + max_pods_per_node   = (known after apply)
102
          + name                = (known after apply)
103
          + name_prefix         = (known after apply)
104
          + node_count          = (known after apply)
105
          + version             = (known after apply)
106
 
          
107
          + autoscaling {
108
              + max_node_count = (known after apply)
109
              + min_node_count = (known after apply)
110
            }
111
 
          
112
          + management {
113
              + auto_repair  = (known after apply)
114
              + auto_upgrade = (known after apply)
115
            }
116
 
          
117
          + node_config {
118
              + disk_size_gb      = (known after apply)
119
              + disk_type         = (known after apply)
120
              + guest_accelerator = (known after apply)
121
              + image_type        = (known after apply)
122
              + labels            = (known after apply)
123
              + local_ssd_count   = (known after apply)
124
              + machine_type      = (known after apply)
125
              + metadata          = (known after apply)
126
              + min_cpu_platform  = (known after apply)
127
              + oauth_scopes      = (known after apply)
128
              + preemptible       = (known after apply)
129
              + service_account   = (known after apply)
130
              + tags              = (known after apply)
131
 
          
132
              + taint {
133
                  + effect = (known after apply)
134
                  + key    = (known after apply)
135
                  + value  = (known after apply)
136
                }
137
 
          
138
              + workload_metadata_config {
139
                  + node_metadata = (known after apply)
140
                }
141
            }
142
        }
143
    }
144
 
          
145
Plan: 1 to add, 0 to change, 0 to destroy.
146
 
          
147
Do you want to perform these actions?
148
  Terraform will perform the actions described above.
149
  Only 'yes' will be accepted to approve.
150
 
          
151
  Enter a value: yes
152
 
          
153
module.terraform-gke.google_container_cluster.gke-cluster: Creating...
154
module.terraform-gke.google_container_cluster.gke-cluster: Still creating... [10s elapsed]
155
module.terraform-gke.google_container_cluster.gke-cluster: Still creating... [20s elapsed]
156
module.terraform-gke.google_container_cluster.gke-cluster: Still creating... [30s elapsed]
157
module.terraform-gke.google_container_cluster.gke-cluster: Still creating... [40s elapsed]
158
module.terraform-gke.google_container_cluster.gke-cluster: Still creating... [50s elapsed]
159
module.terraform-gke.google_container_cluster.gke-cluster: Still creating... [1m0s elapsed]
160
module.terraform-gke.google_container_cluster.gke-cluster: Still creating... [1m10s elapsed]
161
module.terraform-gke.google_container_cluster.gke-cluster: Still creating... [1m20s elapsed]
162
module.terraform-gke.google_container_cluster.gke-cluster: Still creating... [1m30s elapsed]
163
module.terraform-gke.google_container_cluster.gke-cluster: Still creating... [1m40s elapsed]
164
module.terraform-gke.google_container_cluster.gke-cluster: Still creating... [1m50s elapsed]
165
module.terraform-gke.google_container_cluster.gke-cluster: Still creating... [2m0s elapsed]
166
module.terraform-gke.google_container_cluster.gke-cluster: Still creating... [2m10s elapsed]
167
module.terraform-gke.google_container_cluster.gke-cluster: Still creating... [2m20s elapsed]
168
module.terraform-gke.google_container_cluster.gke-cluster: Still creating... [2m30s elapsed]
169
module.terraform-gke.google_container_cluster.gke-cluster: Creation complete after 2m35s [id=demo-cluster]
170
 
          
171
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
172
 
          
173
Outputs:
174
 
          
175
kubernetes-version = 1.12.8-gke.10



If we check the output, we will see that the name of the cluster matches the one from our variables and, in the end, we can see the output that the module produced.

Closing Notes

As you can see, creating a module is pretty simple and with good planning and practice, it can save you a lot of effort with big projects or while working on multiple projects, let me know your thoughts about it. Always remember to destroy the resources that you’re not going to use with terraform destroy.

Topics:
devops, gcp cloud, kubernetes for beginners, terraform, terraform modules, terraform tutorial, tutorial

Published at DZone with permission of Gabriel Garrido . See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}