• /
  • Log in

Using Terragrunt to Manage Multiple Environments

30 min

Terraform is a popular infrastructure-as-code software tool built by HashiCorp. You use it to provision all kinds of infrastructure and services, including New Relic dashboards and alerts.

Terragrunt is a thin wrapper that provides extra tools for keeping your configurations DRY, working with multiple Terraform modules, and managing remote state.

In this guide, you will use Terragrunt to generate your Terraform configurations, create files with Terragrunt, and reuse the Terragrunt configuration to manage multiple environments.

Before you begin

To use this guide, you should have some basic knowledge of both New Relic and Terraform. If you haven't deployed a New Relic open source agent yet, install New Relic Infrastructure agent for your host. Also, install the Terraform CLI.

Start by initializing a working directory:

You will also need to install Terragrunt to follow this guide:

bash
$
brew install terragrunt

To follow the examples in this guide, the accompanying example code is available on GitHub

bash
$
git clone https://github.com/jsbnr/nr-terraform-intro-example.git
Step 1 of 6

Create a new configuration

Terragrunt is a wrapper that provides extra tooling for your Terraform configurations. In the next steps, you will create a new configuration using Terragrunt, Terraform, and New Relic.

Start by initializing a working directory:

bash
$
mkdir terragrunt-config && cd terragrunt-config

In the new directory, create a terragrunt.hcl file, create a ./src directory, and create a ./environments directory:

bash
$
touch terragrunt.hcl
$
mkdir src
$
mkdir environments

Inside of your environments folder, create a folder named dev. In the ./src directory, create a main.tf and provider.tf files:

bash
$
mkdir environments/dev
$
touch src/main.tf
$
touch src/provider.tf

In the provider.tf file add the appropriate Terraform and New Relic provider configuration information:

terraform {
required_version = "~> 0.14.3"
required_providers {
newrelic = {
source = "newrelic/newrelic"
version = "~> 2.14.0"
}
}
}

In the main.tf add a New Relic alert policy named DemoPolicy:

resource "newrelic_alert_policy" "DemoPolicy" {
name = "My Demo Policy"
}

In the ./environments/dev directory create a new file named terragrunt.hcl

bash
$
cd ./environments/dev/
$
touch terragrunt.hcl

Within the dev folder update the terragrunt.hcl file by adding the following include statement. The include statement tells Terragrunt to use any .hcl confgiuration files it finds in parent folders.

include {
path= find_in_parent_folders()
}

Next, add a Terraform block to provide Terragrunt with a Terraform source reference.

terraform {
source = "../../src"
}

Now you need to configure the provider with your New Relic account details. You will need your New Relic user API key and account ID.

provider "newrelic" {
account_id = 12345 # Your New Relic account ID
api_key = "NRAK-***" # Your New Relic user key
region = "US" # US or EU (defaults to US)
}

Tip

If you have a single account: The account ID is displayed on the API keys page.

If your organization has multiple accounts: From one.newrelic.com, select Entity explorer and use the account selector dropdown near the top of the page.

Tip

Documentation about the New Relic provider is provided on the Terraform doc site.

Update the provider.tf file to define the New Relic account variables that will help keep the Terraform configuration dynamic.

variable "newrelic_personal_apikey" {}
variable "newrelic_account_id" {}
variable "newrelic_region" {}
provider "newrelic" {
account_id = var.newrelic_account_id
api_key = var.newrelic_personal_apikey
region = var.newrelic_region
}

In your terminal, change directories to the environments/dev folder and run the command terragrunt init:

bash
$
terragrunt init

Terraform init sets up a bit of context, sets up environment variables, and then runs terraform init. You will see the following output:

bash
# Example output
------------------------------------------------------------------------
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.
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.
[terragrunt] [/Users/jsius/Sites/deco/terragrunt-config/environments/dev] 2021/02/02 13:30:31 Copying lock file [output] from /Users/jsius/Sites/deco/terragrunt-config/environments/dev/.terragrunt-cache/e-PoBgWhdv3v8QGOtDQxS_WeYu4/
69zjIFUfApJiUt8gFmi-6-dcPe8/.terraform.lock.hcl to /Users/jsius/Sites/deco/terragrunt-config/environments/dev

Run terragrunt plan:

bash
$
terragrunt plan

Terragrunt starts to run but pauses to ask for the inputs to the New Relic account variables added to the provider.tf file.

bash
# Example output
------------------------------------------------------------------------
2/02 13:32:08 Running command: terraform plan
var.newrelic_account_id
Enter a value:
Interrupt received.

Exit the terragrunt plan process with CTRL+C . In the environments/dev/terragrunt.hcl file, add an inputs block to provide the values for your New Relic account variables:

inputs = {
newrelic_personal_apikey = "NRAK-***" # Your New Relic account ID
newrelic_account_id = "12345" # Your New Relic account ID
newrelic_region = "US" # US or EU (defaults to US)
}

The updated terragrunt.hcl only has the include, terraform, and inputs block

include {
path = find_in_parent_folders()
}
terraform {
source = "../../src"
}
inputs = {
newrelic_personal_apikey = "NRAK-***" # Your New Relic account ID
newrelic_account_id = "12345" # Your New Relic account ID
newrelic_region = "US" # US or EU (defaults to US)
}

Now, when you run terragrunt plan, Terragrunt will provide the input block's values, no longer prompting the command line for values.

bash
# Example output
------------------------------------------------------------------------
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# newrelic_alert_policy.DemoPolicy will be created
+ resource "newrelic_alert_policy" "DemoPolicy" {
+ account_id = (known after apply)
+ id = (known after apply)
+ incident_preference = "PER_POLICY"
+ name = "My Demo Policy"
}
Plan: 1 to add, 0 to change, 0 to destroy.
------------------------------------------------------------------------
Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.

Run terragrunt apply. After Terraform finishes process, check your New Relic account for the new Demo Policy.

Step 2 of 6

Adding to your configuration

Now that you have a basic New Relic configuration created, add configurations built in the Getting Started with Terraform and Terraform modules guides in this step.

Tip

If you haven't done the previous developer guides, you can copy the configurations for this section from the Terragrunt Intro Github Repo.

Update the main.tf to include the additional terraform configurations. Update the email address in the alert channel to your preferred email address:

resource "newrelic_alert_policy" "DemoPolicy" {
name = "My Demo Policy"
}
resource "newrelic_alert_channel" "DemoChannel" {
name = "My Demo Channel"
type = "email"
config {
recipients = "your@email_address.com"
include_json_attachment = "1"
}
}
resource "newrelic_alert_policy_channel" "ChannelSubs" {
policy_id = newrelic_alert_policy.DemoPolicy.id
channel_ids = [
newrelic_alert_channel.DemoChannel.id
]
}
module "HostConditions" {
source = "git::https://github.com/jsbnr/demo-terraform.git"
policyId = newrelic_alert_policy.DemoPolicy.id
cpu_critical = 88
cpu_warning = 78
diskPercent = 68
}

You have added a New Relic alert channel, subscribed the demo policy to the alert channel, and added a module hosted on Github.

To clone the module from GitHub, run terragrunt init and then run terragrunt apply. After Terraform is done processing, You will see your Demo Policy now has two conditions in your New Relic account and one alert channel attached.

Step 3 of 6

Using your Environment as a Terragrunt Variable

Using Terragrunt, you can add the name of the environment you are running to the name of the data you are creating, making your resource more identifiable in New Relic.

In the root terragrunt.hcl file, create a new input for env_name

inputs = {
env_name = "develop"
}

in the main.tf file add a new variable for 'env_name'

variable "env_name" {}

Using string interpolation, add the new env_name variable to the alert policy and alert channel resource blocks.

resource "newrelic_alert_policy" "DemoPolicy" {
name = "${var.env_name}: My Demo Policy"
}
resource "newrelic_alert_channel" "DemoChannel" {
name = "${env_name}: My Demo Channel"
type = "email"
config {
recipients = "your@email_address.com"
include_json_attachment = "1"
}
}

Run terragrunt plan to see the environment variable added to the configuration name.

bash
# Example output
------------------------------------------------------------------------
# newrelic_alert_policy.DemoPolicy will be updated in-place
~ resource "newrelic_alert_policy" "DemoPolicy" {
id = "1216533"
~ name = "My Demo Policy" -> "develop: My Demo Policy"
# (2 unchanged attributes hidden)
}
# newrelic_alert_policy_channel.ChannelSubs must be replaced
-/+ resource "newrelic_alert_policy_channel" "ChannelSubs" {
~ channel_ids = [
- 4737437,
] -> (known after apply) # forces replacement
~ id = "1216533:4737437" -> (known after apply)
# (1 unchanged attribute hidden)
}

Currently, the environment variable name is hardcoded into the root terragrunt.hcl file. You want to avoid having your variables hardcoded, using a terragrunt built-in function will pass the value.

In the root terragrunt.hcl file, update the input to use the path_relative_to_include() built-in function and pass the value has the env_name variable:

inputs = {
env_name = path_relative_to_include()
}

Run terragrunt plan. Looking at the output, the env_name variable will have the entire ./environments/dev/ path.

bash
# Example output
------------------------------------------------------------------------
# newrelic_alert_policy.DemoPolicy will be updated in-place
~ resource "newrelic_alert_policy" "DemoPolicy" {
id = "1216533"
~ name = "My Demo Policy" -> "environments/dev: My Demo Policy"
# (2 unchanged attributes hidden)
}
# newrelic_alert_policy_channel.ChannelSubs must be replaced
-/+ resource "newrelic_alert_policy_channel" "ChannelSubs" {
~ channel_ids = [
- 4737437,
] -> (known after apply) # forces replacement
~ id = "1216533:4737437" -> (known after apply)
# (1 unchanged attribute hidden)
}
Plan: 2 to add, 1 to change, 2 to destroy.

Next, update the terragrunt.hcl to only include 'dev' as the env_name variable. Adding a locals block creates a local variable, using the replace built-in function will remove the unwanted parts of the relative path, and update the inputs block to use the local variable.

locals {
env_name = replace(path_relative_to_include(), "environments/", "")
}
inputs = {
env_name = local.env_name
}

Run terragrunt plan. Looking at the output the env_name variable will now have 'dev:' added. Run terragrunt apply to update your configurations.

bash
# Example output
------------------------------------------------------------------------
# newrelic_alert_policy.DemoPolicy will be updated in-place
~ resource "newrelic_alert_policy" "DemoPolicy" {
id = "1216533"
~ name = "My Demo Policy" -> "dev: My Demo Policy"
# (2 unchanged attributes hidden)
}
# newrelic_alert_policy_channel.ChannelSubs must be replaced
-/+ resource "newrelic_alert_policy_channel" "ChannelSubs" {
~ channel_ids = [
- 4737437,
] -> (known after apply) # forces replacement
~ id = "1216533:4737437" -> (known after apply)
# (1 unchanged attribute hidden)
}
Plan: 2 to add, 1 to change, 2 to destroy.
Step 4 of 6

Storing the State Remotely

At the moment, the state file is local. The next steps will update your Terraform configurations to remove the local state file and use a file stored remotely in Amazon S3.

Tip

Since Terragrunt allows you to configure multiple environments, various state files will need to be store in separate S3 buckets, not to override each other.

Terragrunt allows you to generate files on the fly. Adding the remote_state block into the root level terragrunt.hcl file, you will generate a new file named backend.tf.

remote_state {
backend = "s3"
generate = {
path = "backend.tf"
if_exists = "overwrite_terragrunt"
}
config = {
bucket = "my-terraform-state"
key = "${path_relative_to_include()}/terraform.tfstate"
region = "us-east-1"
encrypt = true
}
}

After adding the remote_state block, you will need to give the configuration for the S3 bucket that will store the Terraform state.

Tip

To give access to the S3 bucket in your Amazon account, create an IAM user. Give the IAM user access to the S3 bucket storing the terraform state.

Now, update the remote_state block to provide your S3 bucket and update the key to use the local environment variable created previously.

remote_state {
backend = "s3"
generate = {
path = "backend.tf"
if_exists = "overwrite_terragrunt"
}
config = {
bucket = "YOUR_S3_BUCKET_NAME" # Amazon S3 bucket required
key = "envs/${local.env_name}/terraform.tfstate"
region = "us-east-1"
encrypt = true
profile = "YOUR_PROFILE_NAME" # Profile name required
}
}

Next, run terragrunt plan.

If you look in the Amazon S3 bucket, you will see a folder named envs. Within the envs folder is a directory called devs containing terraform.tfstate file.

Tip

To see the files created by Terragrunt, look inside of the ./environments/dev directory. There is a hidden directory named terragrunt-cache. Within the terragrunt-cache directory, you will see the backend.tf file that Terragrunt generated.

Step 5 of 6

Creating a New Environment

Now that you have a working configuration for your dev environment, you will create a new environment reusing the dev configurations in the next steps.

In the environments directory, create a new folder names nonprod. In the nonprod folder, create a new terragrunt.hcl file:

bash
$
mkdir nonprod && cd nonprod
$
touch terragrunt.hcl

In the nonprod terragrunt.hcl file copy over the configuration from the environment terragrunt.hcl:

include {
path= find_in_parent_folders()
}
terraform {
source = "../../src"
}
inputs = {
newrelic_personal_apikey = "NRAK-***" # Your New Relic account ID
newrelic_account_id = "12345" # Your New Relic account ID
newrelic_region = "US" # US or EU (defaults to US)
}

Tip

If you are using a different account for your nonprod environment, you can update the inputs with a new New Relic account ID and API key.

Inside of your nonprod directory, run terragrunt init then run terragrunt apply.

In the output, you will see Terraform has created a new set of resources. The new resources should have the nonprod name prefixed.

Step 6 of 6
Terragrunt has created two environments, but there is no difference between them other than the name.

Adding variables in the configurations allows your configurations to be more dynamic. In the next steps, you will add variables to your configurations.

In the main.tf add new variables for the Host Conditions modules cpu_critical, cpu_warning, and diskPercentage values:

variable "cpu_critical" {default = 89}
variable "cpu_warningl" {default = 79}
variable "diskPercentage" {default = 69}

In the main.tf update the HostConditions module values to use the cpu_critical, cpu_warning, and diskPercentage variables:

module "HostConditions" {
source = "git::https://github.com/jsbnr/demo-terraform.git"
policyId = newrelic_alert_policy.DemoPolicy.id
cpu_critical = var.cpu_critical
cpu_warning = var.cpu_warninig
diskPercent = var.dskPercentage
}

Run terragrunt plan. In the output, the HostConditions values have changed to use the variable defaults.

Next, you want to pass the values into your environment configurations by adding the inputs block variables. In the nonprod/terragrunt.hcl file, add the cpu_critical, cpu_warning, and diskPercentage variables:

inputs = {
newrelic_personal_apikey = "NRAK-***" # Your New Relic account ID
newrelic_account_id = "12345" # Your New Relic account ID
newrelic_region = "US" # US or EU (defaults to US)
cpu_critical = 50
cpu_warninig = 40
diskPercentage = 98
}

Run terragrunt apply. In your New Relic account, you will see a new policy with configurations changed based on the environment.

Conclusion

Congratulations! You've used Terragrunt to generate your New Relic configurations and manage multiple environments. Review the Terragrunt Intro Example, New Relic Terraform provider documentation, and Terragrunt quick start to learn how you can take your configuration to the next level.

Create issueEdit page
Copyright © 2021 New Relic Inc.