• EnglishEspañol日本語한국어Português
  • Log inStart now

Using Terragrunt to Manage Multiple Environments

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 and alerts.

Terragrunt is a thin wrapper around Terraform that provides extra tools for:

  • Reducing repetition
  • Working with multiple Terraform modules
  • Managing remote state

In this guide, you use Terragrunt to:

  • Generate your Terraform configurations
  • Create files
  • 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 already:

To follow the examples in this guide, you can find example code on GitHub.

Create a configuration

Terragrunt provides extra tooling for your Terraform configurations. Here, you create a configuration using Terragrunt, Terraform, and New Relic.

Initialize a workspace:

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

In your new folder, create a terragrunt.hcl file:

bash
$
touch terragrunt.hcl

Next, create an environments folder with a subfolder called dev:

bash
$
mkdir -p environments/dev

Then, create a src folder with main.tf and provider.tf files:

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

You configure Terraform resources in main.tf and providers in provider.tf.

In src/provider.tf, configure a New Relic provider.

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

In src/main.tf, add a New Relic alert policy named DemoPolicy:

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

In environments/dev, create a file named terragrunt.hcl:

bash
$
touch environments/dev/terragrunt.hcl

In it, add the following include statement:

include {
path = find_in_parent_folders()
}

This instructs Terragrunt to use any .hcl configuration files it finds in parent folders.

Add a terraform block to give Terragrunt a source reference:

include {
path = find_in_parent_folders()
}
terraform {
source = "../../src"
}

In src/provider.tf, configure the New Relic provider with an API key, account ID, and region:

terraform {
required_version = "~> 0.14.3"
required_providers {
newrelic = {
source = "newrelic/newrelic"
version = "~> 2.14.0"
}
}
}
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
}

You use variables to keep your configuration dynamic.

In environments/dev, initialize terragrunt:

bash
$
terragrunt init

This sets up a bit of context, including environment variables, then runs terraform init:

bash
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] [/workspace/terragrunt-config/environments/dev] 2021/02/02 13:30:31 Copying lock file [output] from /workspace/terragrunt-config/environments/dev/.terragrunt-cache/e-PoBgWhdv3v8QGOtDQxS_WeYu4/
69zjIFUfApJiUt8gFmi-6-dcPe8/.terraform.lock.hcl to /workspace/terragrunt-config/environments/dev

In environments/dev/terragrunt.hcl, add an inputs block to provide values for your New Relic account variables:

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

Now, run terragrunt plan:

bash
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.

Terragrunt provides the inputs block's values.

Run terragrunt apply:

bash
$
terragrunt apply

Now your Demo Policy is in your New Relic account.

Add to your configuration

Now that you've created a basic New Relic configuration, add the configurations from our Getting Started with New Relic and Terraform and Terraform modules guides.

Tip

If you haven't done these guides yet, you can copy their configurations from the Terragrunt intro Github repo.

In src/main.tf, 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
}

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

Run terragrunt init and then run terragrunt apply:

bash
$
terragrunt init
$
terragrunt apply

After Terraform finishes processing, your alert policy has two conditions and one alert channel.

Use your environment as a Terragrunt variable

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

In the root terragrunt.hcl file, create an input for env_name:

inputs = {
env_name = "develop"
}

In the src/main.tf, file add a new variable called env_name:

variable "env_name" {}

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 your policy name:

bash
# 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)
}

Here, you hardcoded the environment in terragrunt.hcl. To make this more dynamic, use a terragrunt built-in function to get the environment for you.

In the root terragrunt.hcl file, update the input to use path_relative_to_include(), and pass the value as the env_name variable:

inputs = {
env_name = path_relative_to_include()
}

Run terragrunt plan:

bash
# 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.

Note that the env_name variable has the entire ./environments/dev/ path. Instead, you want to include only the "dev" part.

Update the terragrunt.hcl to strip "environements/" from env_name:

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

Here, you added a locals block to create a local variable and used the built-in replace function to remove the unwanted parts of the relative path. Then, you updated the inputs block to use the local variable.

Run terragrunt plan:

bash
# 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.

Your new policy name is "dev: My Demo Policy".

Run terragrunt apply to update your configurations:

bash
$
terragrunt apply

Move your state to remote storage

At the moment, your state file is local. Now, you update your Terraform configurations to store it in Amazon S3.

Tip

Since Terragrunt allows you to configure multiple environments, you should store state files in their own S3 buckets so they don't overwrite each other.

Create an S3 bucket for you development state file.

In your root terragrunt.hcl, add a remote_state block that tells Terragrunt where to place your file in S3:

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
}
}

Here, you defined a remote state configuration that specifies a bucket name, region, encryption, and profile. Make sure you replace the placeholder values with real ones. For key, you used the local env_name you created earlier to dynamically set the environment for the state file. Finally, you told Terragrunt to generate a new file called backend.tf in your bucket.

Run terragrunt plan:

bash
$
terragrunt plan

In your bucket, you see a folder named envs. Inside it is a folder called devs containing a terraform.tfstate file.

Tip

Inside envs/dev, there is a hidden folder named terragrunt-cache. In it, is the backend.tf file that Terragrunt generated.

Create a new environment

Now that you've configured your development environment, create another that reuses most of your work.

Under environments, create a folder named nonprod. In it, create a file called terragrunt.hcl:

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

In environments/nonprod/terragrunt.hcl, copy the configuration from the environments/dev/terragrunt.hcl:

include {
path= find_in_parent_folders()
}
terraform {
source = "../../src"
}
inputs = {
newrelic_personal_apikey = "NRAK-**</DNT>*" # 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're using a different account for your nonprod environment, update inputs with a new account ID, API key, and region.

Inside nonprod, run terragrunt init and terragrunt apply:

bash
$
terragrunt init
$
terragrunt apply

Terraform creates a new set of resources prefixed with "nonprod:".

Now, you've created two environments, dev and nonprod, but they're the same, other than their name.

In src/main.tf, add new variables for the Host Conditions module:

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

Using variables like these makes your configurations more dynamic.

Update HostConditions 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:

bash
$
terragrunt plan

The HostConditions values now include the variable defaults.

In nonprod/terragrunt.hcl, add values for your 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
}

This passes the values into your environment configurations.

Run terragrunt apply:

bash
$
terragrunt apply

In your New Relic account, you have a new policy with nonprod-specific configurations.

Conclusion

Congratulations! You've used Terragrunt to generate 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.

Copyright © 2024 New Relic Inc.

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.