Getting Started

Modules

Up to this point, we've been configuring Terraform by editing Terraform configurations directly. As our 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

If you have any instances running from prior steps in the getting started guide, use terraform destroy to destroy them, and remove all configuration files.

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 the Consul Terraform module for AWS, which will set up a complete Consul cluster. This and other modules can be found via the search feature on the Terraform Registry site.

Create a configuration file with the following contents:

provider "aws" {
  access_key = "AWS ACCESS KEY"
  secret_key = "AWS SECRET KEY"
  region     = "us-east-1"
}

module "consul" {
  source = "hashicorp/consul/aws"

  aws_region  = "us-east-1" # should match provider region
  num_servers = "3"
}

The module block begins with the example given on the Terraform Registry page for this module, telling Terraform to create and manage this module. This is similar to a resource block: it has a name used within this configuration -- in this case, "consul" -- and a set of input values that are listed in the module's "Inputs" documentation.

(Note that the provider block can be omitted in favor of environment variables. See the AWS Provider docs for details. This module requires that your AWS account has a default VPC.)

The source attribute is the only mandatory argument for modules. It tells Terraform where the module can be retrieved. Terraform automatically downloads and manages modules for you.

In this case, the module is retrieved from the official Terraform Registry. Terraform can also retrieve modules from a variety of sources, including private module registries or directly from Git, Mercurial, HTTP, and local files.

The other attributes shown are inputs to our module. This module supports many additional inputs, but all are optional and have reasonable values for experimentation.

After adding a new module to configuration, it is necessary to run (or re-run) terraform init to obtain and install the new module's source code:

$ terraform init
# ...

By default, this command does not check for new module versions that may be available, so it is safe to run multiple times. The -upgrade option will additionally check for any newer versions of existing modules and providers that may be available.

» Apply Changes

With the Consul module (and its dependencies) installed, we can now apply these changes to create the resources described within.

If you run terraform apply, you will see a large list of all of the resources encapsulated in the module. The output is similar to what we saw when using resources directly, but the resource names now have module paths prefixed to their names, like in the following example:

  + module.consul.module.consul_clients.aws_autoscaling_group.autoscaling_group
      id:                                        <computed>
      arn:                                       <computed>
      default_cooldown:                          <computed>
      desired_capacity:                          "6"
      force_delete:                              "false"
      health_check_grace_period:                 "300"
      health_check_type:                         "EC2"
      launch_configuration:                      "${aws_launch_configuration.launch_configuration.name}"
      max_size:                                  "6"
      metrics_granularity:                       "1Minute"
      min_size:                                  "6"
      name:                                      <computed>
      protect_from_scale_in:                     "false"
      tag.#:                                     "2"
      tag.2151078592.key:                        "consul-clients"
      tag.2151078592.propagate_at_launch:        "true"
      tag.2151078592.value:                      "consul-example"
      tag.462896764.key:                         "Name"
      tag.462896764.propagate_at_launch:         "true"
      tag.462896764.value:                       "consul-example-client"
      termination_policies.#:                    "1"
      termination_policies.0:                    "Default"
      vpc_zone_identifier.#:                     "6"
      vpc_zone_identifier.1880739334:            "subnet-5ce4282a"
      vpc_zone_identifier.3458061785:            "subnet-16600f73"
      vpc_zone_identifier.4176925006:            "subnet-485abd10"
      vpc_zone_identifier.4226228233:            "subnet-40a9b86b"
      vpc_zone_identifier.595613151:             "subnet-5131b95d"
      vpc_zone_identifier.765942872:             "subnet-595ae164"
      wait_for_capacity_timeout:                 "10m"

The module.consul.module.consul_clients prefix shown above indicates not only that the resource is from the module "consul" block we wrote, but in fact that this module has its own module "consul_clients" block within it. Modules can be nested to decompose complex systems into manageable components.

The full set of resources created by this module includes an autoscaling group, security groups, IAM roles and other individual resources that all support the Consul cluster that will be created.

Note that as we warned above, the resources created by this module are not eligible for the AWS free tier and so proceeding further will have some cost associated. To proceed with the creation of the Consul cluster, type yes at the confirmation prompt.

# ...

module.consul.module.consul_clients.aws_security_group.lc_security_group: Creating...
  description:            "" => "Security group for the consul-example-client launch configuration"
  egress.#:               "" => "<computed>"
  ingress.#:              "" => "<computed>"
  name:                   "" => "<computed>"
  name_prefix:            "" => "consul-example-client"
  owner_id:               "" => "<computed>"
  revoke_rules_on_delete: "" => "false"
  vpc_id:                 "" => "vpc-22099946"

# ...

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

After several minutes and many log messages about all of the resources being created, you'll have a three-server Consul cluster up and running. Without needing any knowledge of how Consul works, how to install Consul, or how to form a Consul cluster, you've created a working cluster in just a few minutes.

» Module Outputs

Just as the module instance had input arguments such as num_servers above, module can also produce output values, similar to resource attributes.

The module's outputs reference describes all of the different values it produces. Overall, it exposes the id of each of the resources it creates, as well as echoing back some of the input values.

One of the supported outputs is called asg_name_servers, and its value is the name of the auto-scaling group that was created to manage the Consul servers.

To reference this, we'll just put it into our own output value. This value could actually be used anywhere: in another resource, to configure another provider, etc.

Add the following to the end of the existing configuration file created above:

output "consul_server_asg_name" {
  value = "${module.consul.asg_name_servers}"
}

The syntax for referencing module outputs is ${module.NAME.OUTPUT}, where NAME is the module name given in the header of the module configuration block and OUTPUT is the name of the output to reference.

If you run terraform apply again, Terraform will make no changes to infrastructure, but you'll now see the "consul_server_asg_name" output with the name of the created auto-scaling group:

# ...

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

Outputs:

consul_server_asg_name = tf-asg-2017103123350991200000000a

If you look in the Auto-scaling Groups section of the EC2 console you should find an autoscaling group of this name, and from there find the three Consul servers it is running. (If you can't find it, make sure you're looking in the right region!)

» Destroy

Just as with top-level resources, we can destroy the resources created by the Consul module to avoid ongoing costs:

$ terraform destroy
# ...

Terraform will perform the following actions:

  - module.consul.module.consul_clients.aws_autoscaling_group.autoscaling_group

  - module.consul.module.consul_clients.aws_iam_instance_profile.instance_profile

  - module.consul.module.consul_clients.aws_iam_role.instance_role

# ...

As usual, Terraform describes all of the actions it will take. In this case, it plans to destroy all of the resources that were created by the module. Type yes to confirm and, after a few minutes and even more log output, all of the resources should be destroyed:

Destroy complete! Resources: 34 destroyed.

With all of the resources destroyed, you can delete the configuration file we created above. We will not make any further use of it, and so this avoids the risk of accidentally re-creating the Consul cluster.