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:
$mkdir terragrunt-config && cd terragrunt-config
In your new folder, create a terragrunt.hcl file:
$touch terragrunt.hcl
Next, create an environments folder with a subfolder called dev:
$mkdir -p environments/dev
Then, create a src folder with main.tf and provider.tf files:
$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:
$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:
$terragrunt init
This sets up a bit of context, including environment variables, then runs terraform init
:
Terraform has created a lock file .terraform.lock.hcl to record the providerselections it made above. Include this file in your version control repositoryso that Terraform can guarantee to make the same selections by default whenyou run "terraform init" in the future.Terraform has been successfully initialized!You may now begin working with Terraform. Try running "terraform plan" to seeany changes that are required for your infrastructure. All Terraform commandsshould 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, othercommands 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
:
An execution plan has been generated and is shown below.Resource actions are indicated with the following symbols: + createTerraform 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 Terraformcan'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
:
$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.
ヒント
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
:
$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:
# 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
:
# 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
:
# 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:
$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.
ヒント
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
:
$terragrunt plan
In your bucket, you see a folder named envs. Inside it is a folder called devs containing a terraform.tfstate file.
ヒント
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:
$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)}
ヒント
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
:
$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
:
$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
:
$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.