Create a Terraform Module
Traducciones al EspañolEstamos traduciendo nuestros guías y tutoriales al Español. Es posible que usted esté viendo una traducción generada automáticamente. Estamos trabajando con traductores profesionales para verificar las traducciones de nuestro sitio web. Este proyecto es un trabajo en curso.
Terraform is a popular orchestration tool by HashiCorp. It’s used to build, maintain, and version infrastructure safely. Terraform modules allow you to better organize your configuration code and make the code reusable.
In this guide you will create a Linode StackScripts module. This module will deploy a Linode instance from a StackScript you will create. This module will include nested modules that split up the required resources between the root module, a linode_instance module, and a stackscripts module.
Terraform Modules
What is a Terraform Module?
Officially, a Terraform module contains multiple resources that are used with each other, but if you look at one, it’s a directory with a set of Terraform configuration files contained within. Modules can create lightweight abstractions so your infrastructure can be described in terms of its architecture, as opposed to direct terms of physical objects. You can also host your Terraform modules on remote version control services, like Git, for others to use. The Terraform Module Registry hosts community modules that you can reuse for your own Terraform configurations, or you can publish your own modules for consumption by the Terraform community. Reading the “Modules” section of the Terraform Language Documentation is suggested for more details.
How do I create a Terraform Module?
This guide covers the creation of a Terraform module used to deploy a Linode instance. However, if you still have questions, HashiCorp has posted a series of tutorials on Terraform modules on HashiCorp Learn, and the “Build a Module” section is timed to take only about 15 minutes. We recommend taking that lesson to learn more.
Before You Begin
- Install Terraform on your local computer using the steps found in the Install Terraform section of the Use Terraform to Provision Linode Environments guide. Your Terraform project directory should be named - linode_stackscripts.- Note Terraform’s Linode Provider has been updated and now requires Terraform version 0.12+. To learn how to safely upgrade to Terraform version 0.12+, see Terraform’s official documentation. View Terraform v0.12’s changelog for a full list of new features and version incompatibility notes.
- Terraform requires an API access token. Follow the Getting Started with the Linode API guide to obtain a token. 
- Complete the steps in the Configure Git section of the Getting Started with Git guide. 
- Review Deploy a WordPress Site using Terraform and StackScripts to familiarize yourself with the Linode provider’s StackScript resource. 
Standard Terraform Module Structure
Terraform’s standard module structure provides guidance on file and directory layouts for reusable modules. If you would like to make your module public to the Terraform community, the recommended layout allows Terraform to generate documentation and index modules for the Terraform Module Registry.
- The primary module structure requirement is that a root module must exist. The root module is the directory that holds the Terraform configuration files that are applied to build your desired infrastructure. These files provide an entry point into any nested modules you might utilize. 
- Any module should include, at minimum, a - main.tf, a- variables.tf, and an- outputs.tffile. This naming convention is recommended, but not enforced.- If using nested modules to split up your infrastructure’s required resources, the - main.tffile holds all your module blocks and any needed resources not contained within your nested modules. A simple module’s- main.tffile, without any nested modules, declares all resources within this file.
- The - variables.tfand- outputs.tffiles contain input variable and output variable declarations. All variables and outputs should include descriptions.
 
- If using nested modules, they should be located in a root module’s subdirectory named - modules/.
- If your modules will be hosted on Terraform’s Module Registry, root modules and any nested modules should contain a - README.MDfile with a description that explains the module’s intended use.
- You can provide examples in a subdirectory named - examplesof the root module directory.
Create the Linode StackScripts Module
The Linode StackScripts module will include two nested modules that split up the required resources between the root module, a linodes module, and a stackscripts module. When you are done creating all required Terraform files your directory structure will look as follows:
linode_stackscripts/
├── main.tf
├── outputs.tf
├── secrets.tfvars
├── terraform
├── terraform.tfvars
├── variables.tf
└── modules/
    ├── linodes/
    |   ├── main.tf
    │   ├── variables.tf
    │   └── outputs.tf
    └── stackscripts/
        ├── main.tf
        ├── variables.tf
        └── outputs.tflinode_stackscripts directory will likely contain other files related to the Terraform installation you completed prior to beginning the steps in this guide.Create the Linodes Module
In this section, you will create the linodes module which will be in charge of creating your Linode instance. This module contains a main.tf file and corresponding variables.tf and outputs.tf files.
- If your Terraform project directory is not named - linode_stackscripts, rename it before beginning and move into that directory:- mv terraform linode_stackscripts cd linode_stackscripts- Note - You may need to edit your - ~/.profiledirectory to include the- ~/linode_stackscriptsdirectory in your PATH.- echo ’export PATH="$PATH:$HOME/linode_stackscripts"’ » ~/.profile source ~/.profile 
- Create the - modulesand- linodessubdirectories:- mkdir -p modules/linodes
- Using your preferred text editor, create a - main.tffile in- modules/linodes/with the following resources:- File: ~/linode_stackscripts/modules/linodes/main.tf
- 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24- locals { key = var.key } resource "linode_sshkey" "main_key" { label = var.key_label ssh_key = chomp(file(local.key)) } resource "linode_instance" "linode_id" { image = var.image label = var.label region = var.region type = var.type authorized_keys = [ linode_sshkey.main_key.ssh_key ] root_pass = var.root_pass stackscript_id = var.stackscript_id stackscript_data = { "my_password" = var.stackscript_data["my_password"] "my_userpubkey" = var.stackscript_data["my_userpubkey"] "my_hostname" = var.stackscript_data["my_hostname"] "my_username" = var.stackscript_data["my_username"] } }
 - The - main.tffile declares a- linode_instanceresource that deploys a Linode using a StackScript. Notice that all argument values use interpolation syntax to access variable values. You will declare the variables next and provide the variable values in the root module’s- terraform.tfvarsfile. Using separate files for variable declaration and assignment parameterizes your configurations and allows them to be reused as modules.- Let’s take a closer look at each block in the - main.tfconfiguration file.- 1 2 3 4 5 6 7 8- locals { key = var.key } resource "linode_sshkey" "main_key" { label = var.key_label ssh_key = chomp(file(local.key)) }
 - The - localsstanza declares a local variable- keywhose value will be provided by an input variable.
- The - linode_sshkeyresource will create Linode SSH Keys tied to your Linode account. These keys can be reused for future Linode deployments once the resource has been created.- ssh_key = chomp(file(local.key))uses Terraform’s built-in function- file()to provide a local file path to the public SSH key’s location. The location of the file path is the value of the local variable- key. The- chomp()built-in function removes trailing new lines from the SSH key.- Note If you do not already have SSH keys, follow the steps in the Create an Authentication Key-pair section of the our guide Setting Up and Securing a Compute Instance.
 - 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15- resource "linode_instance" "linode_id" { image = var.image label = var.label region = var.region type = var.type authorized_keys = [ linode_sshkey.main_key.ssh_key ] root_pass = var.root_pass stackscript_id = var.stackscript_id stackscript_data = { "my_password" = var.stackscript_data["my_password"] "my_userpubkey" = var.stackscript_data["my_userpubkey"] "my_hostname" = var.stackscript_data["my_hostname"] "my_username" = var.stackscript_data["my_username"] } }
 - The - linode_instanceresource creates a Linode instance with the listed arguments. Please note the following information:- The - authorized_keysargument uses the SSH public key provided by the- linode_sshkeyresource in the previous stanza. This argument expects a value of type list, so the value must be wrapped in brackets.
- To use an existing Linode StackScript you must use the - stackscript_idargument and provide a valid ID as a value. Every StackScript is assigned a unique ID upon creation. Later on in the guide, you will create your own StackScript and expose its ID as an output variable in order to use its ID to deploy your Linode instance.
- StackScripts support user-defined data. This means a StackScript can use the - UDFtag to create a variable whose value must be provided by the user of the script. This allows users to customize the behavior of a StackScript on a per-deployment basis. Any required- UDFvariable can be defined using the- stackscript_dataargument.
 
- Create the - variables.tffile to define your resource’s required variables:- File: ~/linode_stackscripts/modules/linodes/variables.tf
- 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- variable "key" { description = "Public SSH Key's path." } variable "key_label" { description = "new SSH key label" } variable "image" { description = "Image to use for Linode instance" default = "linode/ubuntu18.04" } variable "label" { description = "The Linode's label is for display purposes only, but must be unique." default = "default-linode" } variable "region" { description = "The region where your Linode will be located." default = "us-east" } variable "type" { description = "Your Linode's plan type." default = "g6-standard-1" } variable "authorized_keys" { description = "SSH Keys to use for the Linode." type = "list" } variable "root_pass" { description = "Your Linode's root user's password." } variable "stackscript_id" { description = "StackScript ID." } variable "stackscript_data" { description = "Map of required StackScript UDF data." type = "map" default = {} }
 - Modules must include a description for each input variable to help document your configuration’s usage. This will make it easier for anyone else to use this module. 
- Every variable can contain a default value. The default value is only used if no other value is provided. For example, if you have a favorite Linux distribution, you may want to provide it as your image variable’s default value. In this case, - linode/ubuntu18.04is set as the default value.
- You can declare a - typefor each variable. If no- typeis provided, the variable will default to- type = "string".
- Notice that the - stackscript_datavariable is of- type = "map". This will allow you to provide values for as many- UDFvariables as your StackScript requires.
 
- Create the - outputs.tffile:- File: ~/linode_stackscripts/modules/linodes/outputs.tf
- 1 2 3- output "sshkey_linode" { value = linode_sshkey.main_key.ssh_key }
 - The - outputs.tffile exposes any values from the resources you declared in the- main.tffile. Any exposed values can be used by any other module within the root module. The- sshkey_linodeoutput variable exposes the- linode_sshkeyresource’s public key.
Now that the linodes module is complete, in the next section, you will create the stackscripts module.
Create the StackScripts Module
In this section, you will create the StackScripts module. This module creates a linode_stackscripts resource which you can use to create and modify your own Linode StackScript.
- Ensure you are in the - linode_stackscriptsdirectory and create the- stackscriptssubdirectory:- mkdir modules/stackscripts
- Using your preferred text editor, create a - main.tffile in- modules/stackscripts/with the following resource:- File: ~/linode_stackscripts/modules/stackscripts/main.tf
- 1 2 3 4 5 6 7- resource "linode_stackscript" "default" { label = var.stackscript_label description = var.description script = var.stackscript images = var.stackscript_image rev_note = var.rev_note }
 - The - main.tffile creates the- linode_stackscriptresource and provides the required configurations. All argument values use interpolation syntax to access input variable values. You will declare the input variables next and provide the variable values in the root module’s- terraform.tfvarsfile. For more information on StackScripts see the StackScripts product page and the Linode APIv4 StackScripts reference.
- Create the - variables.tffile to define your resource’s required variables:- File: ~/linode_stackscripts/modules/stackscripts/variables.tf
- 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17- variable "stackscript_label" { description = "The StackScript's label is for display purposes only." } variable "description" { description = "A description for the StackScript." } variable "stackscript" { description = "The script to execute when provisioning a new Linode with this StackScript." } variable "stackscript_image" { description = " A list of Image IDs representing the Images that this StackScript is compatible for deploying with." } variable "rev_note" { description = "This field allows you to add notes for the set of revisions made to this StackScript." }
 
- Create the - outputs.tffile:- File: ~/linode_stackscripts/modules/stackscripts/output.tf
- 1 2 3- output "stackscript_id" { value = linode_stackscript.default.id }
 - The - outputs.tffile exposes the value of the- linode_stackscriptresource’s ID. Every StackScript is assigned a unique ID upon creation. You will need this ID when creating your root module.
You have now created the StackScripts module and are ready to use both modules within the root module. You will complete this work in the next section.
Create the Root Module
The root module will call the linode and stackscripts modules, satisfy their required variables and then apply those configurations to build your desired infrastructure. These configurations deploy a Linode based on a StackScript you will define in this section. When using nested modules, the modules will be hidden from your root configuration, so you’ll have to re-expose any variables and outputs you require.
- Ensure you are in the - linode_stackscriptsdirectory and create the- main.tffile:- File: ~/linode_stackscripts/main.tf
- 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- terraform { required_providers { linode = { source = "linode/linode" version = "1.16.0" } } } provider "linode" { token = var.token } module "stackscripts" { source = "./modules/stackscripts" stackscript_label = var.stackscript_label description = var.description stackscript = var.stackscript stackscript_image = var.stackscript_image rev_note = var.rev_note } module "linodes" { source = "./modules/linodes" key = var.key key_label = var.key_label image = var.image label = var.label region = var.region type = var.type root_pass = var.root_pass authorized_keys = [ module.linodes.sshkey_linode ] stackscript_id = module.stackscripts.stackscript_id stackscript_data = { "my_password" = var.stackscript_data["my_password"] "my_userpubkey" = var.stackscript_data["my_userpubkey"] "my_hostname" = var.stackscript_data["my_hostname"] "my_username" = var.stackscript_data["my_username"] } }
 - The - main.tffile uses the- linodesand- stackscriptsmodules that were created in the previous sections and provides the required arguments. All argument values use interpolation syntax to access variable values, which you will declare in a- variables.tffile and then provide corresponding values for in a- terraform.tfvarsfile.- Let’s review each block: - 1 2 3- provider "linode" { token = var.token }
 - The first stanza declares Linode as the provider that will manage the lifecycle of any resources declared throughout the configuration file. The Linode provider requires your Linode APIv4 token for authentication. - 1 2 3 4 5 6 7 8- module "stackscripts" { source = "./modules/stackscripts" stackscript_label = var.stackscript_label description = var.description stackscript = var.stackscript stackscript_image = var.stackscript_image rev_note = var.rev_note }
 - The next stanza instructs Terraform to create an instance of the - stackscriptsmodule and instantiate any of the resources defined within the module. The- sourceattribute provides the location of the child module’s source code and is required whenever you create an instance of a module. All other attributes are determined by the module. Notice that all the attributes included in the module block correspond to the- linode_stackscriptresource’s arguments declared in the- main.tffile of the- stackscriptsmodule.- 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19- module "linodes" { source = "./modules/linodes" key = var.key key_label = var.key_label image = var.image label = var.label group = var.group region = var.region type = var.type root_pass = var.root_pass authorized_keys = [ module.linodes.sshkey_linode ] stackscript_id = module.stackscripts.stackscript_id stackscript_data = { "my_password" = var.stackscript_data["my_password"] "my_userpubkey" = var.stackscript_data["my_userpubkey"] "my_hostname" = var.stackscript_data["my_hostname"] "my_username" = var.stackscript_data["my_username"] } }
 - This stanza creates an instance of the - linodesmodule and then instantiates the resources you defined in the module. Notice that- authorized_keys = [ module.linodes.sshkey_id ]and- stackscript_id = "module.stackscripts.stackscript_id"both access values exposed as output variables by the- linodesand- stackscriptsmodules. Any module’s exposed output variables can be referenced in your root module’s- main.tffile.
- Create the - variables.tffile to declare the input variables required by the module instances:- File: ~/linode_stackscripts/variables.tf
- 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- variable "token" { description = " Linode API token" } variable "stackscript_label" { description = "The StackScript's label is for display purposes only." } variable "description" { description = "A description for the StackScript." } variable "stackscript" { description = "The script to execute when provisioning a new Linode with this StackScript." } variable "stackscript_image" { description = "A list of Image IDs representing the Images that this StackScript is compatible for deploying with." } variable "rev_note" { description = "This field allows you to add notes for the set of revisions made to this StackScript." } variable "key" { description = "Public SSH Key's path." } variable "key_label" { description = "New SSH key label." } variable "image" { description = "Image to use for Linode instance." default = "linode/ubuntu18.04" } variable "label" { description = "The Linode's label is for display purposes only, but must be unique." default = "default-linode" } variable "region" { description = "The region where your Linode will be located." default = "us-east" } variable "type" { description = "Your Linode's plan type." default = "g6-standard-1" } variable "root_pass" { description = "Your Linode's root user's password." } variable "stackscript_data" { description = "Map of required StackScript UDF data." type = "map" default = {} } variable "stackscript_id" { description = "Hold the stackscript id output value." }
 
- Create the - outputs.tffile:- File: ~/linode_stackscripts/outputs.tf
- 1 2 3- output "stackscript_id" { value = module.stackscripts.stackscript_id }
 - In the - outputs.tffile you will re-expose the output variables exposed by the- stackscriptsmodule.
- Create the - terraform.tfvarsfile to provide values for all input variables defined in the- variables.tffile. This file will exclude any values that provide sensitive data, like passwords and API tokens. A file containing sensitive values will be created in the next step:- File: ~/linode_stackscripts/terraform.tfvars
- 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- key = "~/.ssh/id_rsa.pub" key_label = "my-ssh-key" label = "my-linode" stackscript_id = "base-ubuntu-deployment" stackscript_label = "base-ubuntu-deployment" description = "A base deployment for Ubuntu 18.04 that creates a limited user account." stackscript = <<EOF #!/bin/bash # <UDF name="my_hostname" Label="Linode's Hostname" /> # <UDF name="my_username" Label="Limited user account" /> # <UDF name="my_password" Label="Limited user account's password" /> # <UDF name="my_userpubkey" Label="Limited user account's public key" /> source <ssinclude StackScriptID="1"> set -x MY_IP=system_primary_ip system_set_hostname "$MY_HOSTNAME" system_add_host_entry "$MY_IP" "$MY_HOSTNAME" user_add_sudo "$MY_USERNAME" "$MY_PASSWORD" user_add_pubkey "$MY_USERNAME" "$MY_USERPUBKEY" ssh_disable_root goodstuff EOF stackscript_image = ["linode/ubuntu18.04"] rev_note = "First revision of my StackScript created with the Linode Terraform provider."
 - The - terraform.tfvarsfile supplies all values required by the- linodesand- stackscriptsmodules. Ensure you replace any values with your own values when using this example file.- The - stackscriptvariable provides the actual contents of the StackScript you create. This example StackScript requires four- UDFvalues:- my_hostname,- my_username,- my_password, and- my_userpubkey. The- my_hostnameand- my_usernamevalues are supplied by the- stackscript_datamap. The- my_passwordand- my_userpubkeyvalues will be provided in the next step.- The StackScript will then use these values to create a limited user account; set a hostname; add a host entry; add the created user to the - sudogroup; disable SSH access for the root user; and install vim, wget, and less. This StackScript uses bash functions defined in the Linode Community StackScript Bash Library.
- Create a file named - secrets.tfvarsto hold any sensitive values:- File: ~/linode_stackscripts/secrets.tfvars
- 1 2 3 4 5 6 7 8- token = "my-linode-api-token" root_pass = "my-secure-root-password" stackscript_data = { "my_password" = "my-limited-users-password" "my_userpubkey" = "my-public-ssh-key" "my_username" = "username" "my_hostname" = "linode-hostname" }
 - This file contains all the sensitive data needed for your Linode deployment. Ensure you replace all values with your own secure passwords and your Linode account’s APIv4 token. This file should never be tracked in version control software and should be listed in your - .gitignorefile if using GitHub.- Note In Terraform 0.12, variables with map and object values will use the last value found and override previous values. This is different from previous versions of Terraform, which would merge map values instead of overriding them. For this reason, the- stackscript_datamap and its values are defined in a single variable definitions file.- Note There are several other options available for secrets management with Terraform. For more information on this subject, see Secrets Management with Terraform.
You are now ready to apply your linode_stackscripts module’s Terraform configuration. These steps will be completed in the next section.
Initialize, Plan and Apply the Terraform Configuration
Whenever a new provider is used in a Terraform configuration, it must first be initialized. The initialization process downloads and installs the provider’s plugin and performs any other steps needed for its use. Before applying your configuration, it is also useful to view your configuration’s execution plan before making any actual changes to your infrastructure. In this section, you will complete all these steps.
- Initialize the Linode provider. Ensure you are in the - linode_stackscriptsdirectory before running this command:- terraform init- You will see a message that confirms that the provider plugins have been successfully initialized. 
- Run the Terraform plan command: - terraform plan -var-file="secrets.tfvars" -var-file="terraform.tfvars"- Terraform plan won’t take any action or make any changes to your Linode account. Instead, an analysis is done to determine which actions (i.e. Linode instance creations, deletions, or modifications) are required to achieve the state described in your configuration. 
- You are now ready to create the infrastructure defined in your root module’s - main.tfconfiguration file:- terraform apply -var-file="secrets.tfvars" -var-file="terraform.tfvars"- Since you are using multiple variable value files, you must call each file individually using the - var-fileargument. You will be prompted to confirm the- applyaction. Enter yes. Terraform will begin to create the resources you’ve defined throughout this guide. This process will take a couple of minutes to complete. Once the infrastructure has been successfully built you will see a similar output:- Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
- To verify the deployment, retrieve your Linode instance’s IP address: - terraform show | grep 'ip_address'- You should see a similar output: - ip_address = 192.0.2.0
- Open a new shell session and SSH into your Linode using the IP address you retrieved in the previous step and the username you defined in the - terraform.tfvarsfile’s- my_usernamevariable:- ssh username@192.0.2.0- You should be able to access your Linode and then verify that what you defined in the StackScript was executed. 
Version Control Your Terraform Module
To make the linode_stackscripts module available to other team members, you can version control it using GitHub. Before completing the steps in this section, ensure you have completed the steps in the Configure Git section of the
Getting Started with Git guide.
- In the - linode_stackscriptsdirectory create a- .gitignorefile:- File: ~/linode_stackscripts/.gitignore
- 1 2 3 4- secrets.tfvars .terraform/ terraform/ terraform.tfstate
 - Note If there are any files related to the Terraform installation steps completed before beginning this guide (such as the zip files and checksum files), you can remove these files from the- linode_stackscriptsdirectory, since you should not track them in version control and they are no longer necessary.
- Initialize the git repository: - git init- Stage all the files you’ve created so far for your first commit: - git add -A
- Commit all the - linode_stackscriptsfiles:- git commit -m "Initial commit"
- Navigate to your GitHub account and create a new repository. Ensure you name the repository the same name as that of your Terraform module. In this example, the GitHub repository will be named - linode_stackscripts.
- At the top of your GitHub repository’s Quick Setup page, copy the remote repository URL. 
- Return to your local computer’s - linode_stackscriptsdirectory and add the URL for the remote repository:- git remote add origin https://github.com/my-github/linode_stackscripts.git
- Push your local - linode_stackscriptsrepository to your remote GitHub repository:- git push -u origin master
Your Terraform module is now tracked via GitHub and can be used, shared, and modified by anyone who has access to your GitHub account.
Invoking Your GitHub-Hosted Module
In the future, you can source this module from GitHub within your Terraform module declarations. You would write your module block like the following:
- 1 2 3 4 5 6- module "linode_stackscripts" { source = "github.com/username/linode_stackscripts" VARIABLES HERE . . . }
More Information
You may wish to consult the following resources for additional information on this topic. While these are provided in the hope that they will be useful, please note that we cannot vouch for the accuracy or timeliness of externally hosted materials.
This page was originally published on