Get Started - Google Cloud

Modules

Up to this point, you have been configuring Terraform by editing Terraform configurations directly. As your infrastructure grows, this practice has a few key problems: a lack of organization, a lack of reusability, and difficulties in management for teams.

Modules in Terraform are self-contained packages of Terraform configurations that are managed as a group. Modules are used to create reusable components, improve organization, and to treat pieces of infrastructure as a black box.

This section of the getting started will cover the basics of using modules. Writing modules is covered in more detail in the modules documentation.

Using Modules

The Terraform Registry includes a directory of ready-to-use modules for various common purposes, which can serve as larger building-blocks for your infrastructure.

In this example, we're going to use a network module for GCP, which will set up a more advanced networking configuration for us.

Use a module to define a network by adding the following to your main.tf file.

module "network" {
  source  = "terraform-google-modules/network/google"
  version = "2.0.2"

  network_name = "terraform-vpc-network"
  project_id   = var.project

  subnets = [
    {
      subnet_name   = "subnet-01"
      subnet_ip     = var.cidrs[0]
      subnet_region = var.region
    },
    {
      subnet_name   = "subnet-02"
      subnet_ip     = var.cidrs[1]
      subnet_region = var.region

      subnet_private_access = "true"
    },
  ]

  secondary_ranges = {
    subnet-01 = []
    subnet-02 = []
  }
}

The module block begins with the name of the module. This is similar to a resource block: it defines a name used within this configuration -- in this case, "network" -- and a set of input values that are listed in the module's "Inputs" documentation.

The source attribute is the only mandatory argument for all modules. It tells Terraform where the module can be retrieved. Terraform will install and manage modules for you from the Terraform registry, source control systems like GitHub, a URL, or your local filesystem.

The other attributes shown are inputs to our module. This module supports many additional inputs, which you can read about in its documentation, but all are optional and have reasonable defaults.

This will configure a simple network with two subnets. We can replace that network with the one provisioned by the new module. Comment out or delete the vpc_network resource:

# Remove or #comment out:
#
# resource "google_compute_network" "vpc_network" {
#   name = "terraform-network"
# }

You will also need to refer to the new network differently. Inside of the configuration for the vm_instance, update the network_interface section like so:

# Replace this line:
#   network = google_compute_network.vpc_network.name
# With this these two:
    network    = module.network.network_name
    subnetwork = module.network.subnets_names[0]

Now your configuration is referring to the output of the network module. You can see the output values for modules in the module registry documentation.

Since the instance will now be located in a different network, it will be destroyed and recreated.

Initializing Modules

Terraform modules are essentially just pre-packaged Terraform configuration. They need to be installed on your system before you can use them in your configuration. You can do that with the init command.

Install the network module by running terraform init now.

$ terraform init
Initializing modules...
Downloading terraform-google-modules/network/google 1.1.0 for network...
- network in .terraform/modules/network/terraform-google-modules-terraform-google-network-2ada6f9

Initializing the backend...

Initializing provider plugins...
- Checking for available provider plugins...
- Downloading plugin for provider "null" (terraform-providers/null) 2.1.2...

The following providers do not have any version constraints in configuration,
so the latest version was installed.

To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.

* provider.google: version = "~> 2.12"
* provider.null: version = "~> 2.1"

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.

Now the module is installed on your system and Terraform can use it to provision resources. You need to run terraform init or terraform get every time you add a new module or want to change module versions.

If you like, you can run terraform plan at this point to see what changes will be applied. Notice that your instance will be destroyed and recreated because it will move to the new network.

Apply Changes

Now run terraform apply.

The output is similar to what we saw when using resources directly, but the resources now have the module name prefixed to their names.

$ terraform apply
google_compute_network.vpc_network: Refreshing state... [id=terraform-network]
google_compute_address.vm_static_ip: Refreshing state... [id=capable-stream-249119/us-central1/terraform-static-ip]
module.network.data.google_compute_subnetwork.created_subnets[0]: Refreshing state...
module.network.data.google_compute_subnetwork.created_subnets[1]: Refreshing state...
google_compute_instance.vm_instance: Refreshing state... [id=terraform-instance]

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create
  - destroy
-/+ destroy and then create replacement

Terraform will perform the following actions:

  # google_compute_instance.vm_instance must be replaced
-/+ resource "google_compute_instance" "vm_instance" {
        can_ip_forward       = false
      ~ cpu_platform         = "Intel Haswell" -> (known after apply)
        deletion_protection  = false
      ~ guest_accelerator    = [] -> (known after apply)
      ~ id                   = "terraform-instance" -> (known after apply)
      ~ instance_id          = "830530979429882449" -> (known after apply)
      ~ label_fingerprint    = "42WmSpB8rSM=" -> (known after apply)
      - labels               = {} -> null
        machine_type         = "f1-micro"
      - metadata             = {} -> null
      ~ metadata_fingerprint = "-lidUweE2gg=" -> (known after apply)
        name                 = "terraform-instance"
      ~ project              = "capable-stream-249119" -> (known after apply)
      ~ self_link            = "https://www.googleapis.com/compute/v1/projects/capable-stream-249119/zones/us-central1-c/instances/terraform-instance" -> (known after apply)
        tags                 = [
            "dev",
            "web",
        ]
      ~ tags_fingerprint     = "XaeQnaHMn9Y=" -> (known after apply)
      ~ zone                 = "us-central1-c" -> (known after apply)

      ~ boot_disk {
            auto_delete                = true
          ~ device_name                = "persistent-disk-0" -> (known after apply)
          + disk_encryption_key_sha256 = (known after apply)
          + kms_key_self_link          = (known after apply)
          ~ source                     = "https://www.googleapis.com/compute/v1/projects/capable-stream-249119/zones/us-central1-c/disks/terraform-instance" -> (known after apply)

          ~ initialize_params {
              ~ image  = "https://www.googleapis.com/compute/v1/projects/cos-cloud/global/images/cos-stable-75-12105-97-0" -> "cos-cloud/cos-stable"
              ~ labels = {} -> (known after apply)
              ~ size   = 10 -> (known after apply)
              ~ type   = "pd-standard" -> (known after apply)
            }
        }

      ~ network_interface {
          + address            = (known after apply)
          ~ name               = "nic0" -> (known after apply)
          ~ network            = "https://www.googleapis.com/compute/v1/projects/capable-stream-249119/global/networks/terraform-network" -> "terraform-vpc-network" # forces replacement
          ~ network_ip         = "10.128.0.2" -> (known after apply)
          ~ subnetwork         = "https://www.googleapis.com/compute/v1/projects/capable-stream-249119/regions/us-central1/subnetworks/terraform-network" -> "subnet-01" # forces replacement
          ~ subnetwork_project = "capable-stream-249119" -> (known after apply)

          ~ access_config {
              + assigned_nat_ip = (known after apply)
                nat_ip          = "35.194.46.141"
              ~ network_tier    = "PREMIUM" -> (known after apply)
            }
        }

      ~ scheduling {
          ~ automatic_restart   = true -> (known after apply)
          ~ on_host_maintenance = "MIGRATE" -> (known after apply)
          ~ preemptible         = false -> (known after apply)

          + node_affinities {
              + key      = (known after apply)
              + operator = (known after apply)
              + values   = (known after apply)
            }
        }

      - shielded_instance_config {
          - enable_integrity_monitoring = true -> null
          - enable_secure_boot          = false -> null
          - enable_vtpm                 = true -> null
        }
    }

  # google_compute_network.vpc_network will be destroyed
  - resource "google_compute_network" "vpc_network" {
      - auto_create_subnetworks         = true -> null
      - delete_default_routes_on_create = false -> null
      - id                              = "terraform-network" -> null
      - name                            = "terraform-network" -> null
      - project                         = "capable-stream-249119" -> null
      - routing_mode                    = "REGIONAL" -> null
      - self_link                       = "https://www.googleapis.com/compute/v1/projects/capable-stream-249119/global/networks/terraform-network" -> null
    }

  # module.network.google_compute_network.network will be created
  + resource "google_compute_network" "network" {
      + auto_create_subnetworks         = false
      + delete_default_routes_on_create = false
      + gateway_ipv4                    = (known after apply)
      + id                              = (known after apply)
      + name                            = "terraform-vpc-network"
      + project                         = "capable-stream-249119"
      + routing_mode                    = "GLOBAL"
      + self_link                       = (known after apply)
    }

  # module.network.google_compute_subnetwork.subnetwork[0] will be created
  + resource "google_compute_subnetwork" "subnetwork" {
      + creation_timestamp       = (known after apply)
      + enable_flow_logs         = false
      + fingerprint              = (known after apply)
      + gateway_address          = (known after apply)
      + id                       = (known after apply)
      + ip_cidr_range            = "10.0.0.0/16"
      + name                     = "subnet-01"
      + network                  = "terraform-vpc-network"
      + private_ip_google_access = false
      + project                  = "capable-stream-249119"
      + region                   = "us-central1"
      + secondary_ip_range       = []
      + self_link                = (known after apply)
    }

  # module.network.google_compute_subnetwork.subnetwork[1] will be created
  + resource "google_compute_subnetwork" "subnetwork" {
      + creation_timestamp       = (known after apply)
      + enable_flow_logs         = false
      + fingerprint              = (known after apply)
      + gateway_address          = (known after apply)
      + id                       = (known after apply)
      + ip_cidr_range            = "10.1.0.0/16"
      + name                     = "subnet-02"
      + network                  = "terraform-vpc-network"
      + private_ip_google_access = true
      + project                  = "capable-stream-249119"
      + region                   = "us-central1"
      + secondary_ip_range       = []
      + self_link                = (known after apply)
    }

Plan: 4 to add, 0 to change, 2 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

google_compute_network.vpc_network: Destroying... [id=terraform-network]
module.network.google_compute_network.network: Creating...
google_compute_instance.vm_instance: Destroying... [id=terraform-instance]
module.network.google_compute_network.network: Still creating... [10s elapsed]
google_compute_instance.vm_instance: Still destroying... [id=terraform-instance, 10s elapsed]
module.network.google_compute_network.network: Creation complete after 17s [id=terraform-vpc-network]
module.network.google_compute_subnetwork.subnetwork[1]: Creating...
module.network.google_compute_subnetwork.subnetwork[0]: Creating...
google_compute_instance.vm_instance: Still destroying... [id=terraform-instance, 20s elapsed]
module.network.google_compute_subnetwork.subnetwork[0]: Still creating... [10s elapsed]
module.network.google_compute_subnetwork.subnetwork[1]: Still creating... [10s elapsed]
google_compute_instance.vm_instance: Still destroying... [id=terraform-instance, 30s elapsed]
module.network.google_compute_subnetwork.subnetwork[0]: Still creating... [20s elapsed]
module.network.google_compute_subnetwork.subnetwork[1]: Still creating... [20s elapsed]
google_compute_instance.vm_instance: Still destroying... [id=terraform-instance, 40s elapsed]
module.network.google_compute_subnetwork.subnetwork[0]: Creation complete after 26s [id=us-central1/subnet-01]
module.network.google_compute_subnetwork.subnetwork[1]: Creation complete after 27s [id=us-central1/subnet-02]
google_compute_instance.vm_instance: Still destroying... [id=terraform-instance, 50s elapsed]
google_compute_instance.vm_instance: Still destroying... [id=terraform-instance, 1m0s elapsed]
google_compute_instance.vm_instance: Still destroying... [id=terraform-instance, 1m10s elapsed]
google_compute_instance.vm_instance: Still destroying... [id=terraform-instance, 1m20s elapsed]

Module Outputs

Just as the module instance has input arguments, a module can also produce output values, similar to resource attributes. We used two of them when configuring our instance - the name of the network and one of our subnets.

The module's output reference describes all of the different values it produces.

One of the supported outputs is called subnets_ips, and its value describes the IPs and CIDR blocks created for our network.

You can reference a module's output with the expression module.<MODULE NAME>.<OUTPUT NAME>. Like most expressions, this value can be used almost anywhere: in another resource, to configure another module, etc. To demonstrate, try referencing it in a root-level output, so Terraform displays it after an apply.

Add an output for the VPC's subnet IP addresses to outputs.tf.

output "vpc_network_subnets_ips" {
  value = module.network.subnets_ips
}

If you run terraform apply again, Terraform will make no changes to infrastructure, but you'll now see the "vpc_network_subnets_ips" output.

$ terraform apply

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

Outputs:

ip = 35.194.46.141
vpc_network_subnets_ips = [
  "10.0.0.0/16",
  "10.1.0.0/16",
]

Infrastructure configuration can be complex and often repetitive. Modules provide a way to re-use and share standard configuration patterns.

Destroy

As a final step, you will probably want to destroy the infrastructure you created for this track, to avoid being charged for it in the future. Do so by running terraform destroy.

terraform destroy

module.network.google_compute_network.network: Refreshing state... [id=terraform-vpc-network]
google_compute_address.vm_static_ip: Refreshing state... [id=capable-stream-249119/us-central1/terraform-static-ip]
module.network.google_compute_subnetwork.subnetwork[1]: Refreshing state... [id=us-central1/subnet-02]
module.network.google_compute_subnetwork.subnetwork[0]: Refreshing state... [id=us-central1/subnet-01]
module.network.data.google_compute_subnetwork.created_subnets[0]: Refreshing state...
module.network.data.google_compute_subnetwork.created_subnets[1]: Refreshing state...
google_compute_instance.vm_instance: Refreshing state... [id=terraform-instance]

# ...

Plan: 0 to add, 0 to change, 5 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:

After you respond to the prompt with yes, Terraform will remove all of the infrastructure managed by your configuration.

  Enter a value: yes

google_compute_instance.vm_instance: Destroying... [id=terraform-instance]
google_compute_instance.vm_instance: Still destroying... [id=terraform-instance, 10s elapsed]
google_compute_instance.vm_instance: Still destroying... [id=terraform-instance, 20s elapsed]

# ...

module.network.google_compute_network.network: Still destroying... [id=terraform-vpc-network, 30s elapsed]
module.network.google_compute_network.network: Destruction complete after 36s

Destroy complete! Resources: 5 destroyed.