[New Relic] Terraform で AWS Integration を実装する
🌱 はじめに
NewRelicとAWSを連携させることで、AWSリソースのメトリクスをNewRelicで監視できるようになります。
本記事では、PUSHモード(CloudWatch Metric Streams) を使ったリアルタイム監視の実装に焦点を当てます。
😓 既存の方法の不便な点
NewRelicは公式にCloudFormationテンプレートやTerraformモジュールを提供していますが、実際の運用では以下のような不便な点がありました。
CloudFormation (NewRelic公式推奨)
NewRelicのUI上で提供されるCloudFormationテンプレートは、複数の子スタックを呼び出すネスト構造になっています。
親スタック (newrelic-integration.yml)
├── newrelic-integration-role.yml # IAMロール作成
├── newrelic-link-account.yml # GraphQL APIでアカウント連携
├── newrelic-configure-integrations.yml # ポーリング設定
└── newrelic-metric-streams.yml # メトリックストリーム設定
この構造により、以下の不便さがありました。
- 保守性の低さ: テンプレート更新時に親・子スタックの両方を修正する必要があり、パラメータ変更時は複数スタック間の整合性調整が必要
- 可視性の低さ: S3上の外部テンプレート参照やGraphQL APIによる自動設定により、AWS・NewRelic側で何が作られるか把握しづらい
- マルチリージョン対応: CloudFront・WAF(us-east-1)など、リージョンをまたぐ場合は手動で複数回デプロイが必要
参考🔗: CloudFormationとCloudWatchメトリックストリームを介して統合
Terraform公式モジュール
Terraformで管理する場合でも、公式モジュールには以下の制約がありました。
- PUSH/PULLモードの制御不足: 両方のモードが有効化され、コスト最適化が難しい
- License Keyの柔軟性: 既存のキーを再利用できず、新規発行が必要
参考🔗: NewRelic - Cloud integrations guide
Terraformで管理するメリット
これらの不便さを解消し、複数環境への展開を効率化するため、必要なリソースだけを定義したシンプルなTerraformモジュールを作成しました。
- ✅ フラットな構造 : ネストなしで、全リソースがわかりやすい
- ✅ 1回のデプロイで完結 : 複数リージョンを
terraform apply1回で設定 - ✅ 環境間の設定共有 :
dev/prodなどの複数の環境で同じモジュールを再利用 - ✅ リアルタイム監視 : PUSHモードでメトリクスをリアルタイム配信
- ✅ メトリクスの選択的収集 : 必要なメトリクスだけを指定して、データ転送量を最適化
🧩 全体構成
今回実装するシステムの全体像です。各リージョンでMetric StreamとFirehoseを構築し、NewRelicにデータを送信します。
AWS Account
├── ap-northeast-1 (Tokyo)
│ ├── CloudWatch Metric Stream → Kinesis Firehose → NewRelic
│ ├── S3 (Backup)
│ └── IAM Roles/Policies
│
├── us-east-1 (Virginia)
│ ├── CloudWatch Metric Stream → Kinesis Firehose → NewRelic
│ ├── S3 (Backup)
│ └── IAM Roles/Policies
│
└── NewRelic Link Account (AWSアカウント全体とNewRelicの紐付け)
⚙️ 事前準備:ディレクトリ構成
モジュール化により、複数環境で同じコードを再利用できる構成にします。
terraform/
└── newrelic/
├── modules/
│ └── integrations/ # 再利用可能なモジュール
│ ├── main.tf
│ ├── variables.tf
│ └── terraform.tf
└── environments/
└── dev/
└── integration/ # 環境固有の設定
├── main.tf
├── variables.tf
├── locals.tf
└── terraform.tf
📝 実装
ここからは、実際のTerraformコードを順番に見ていきます。
1️⃣ モジュール: variables.tf
まず、モジュールの入力変数を定義します。
variable "service_name" {
description = "サービス名"
type = string
}
variable "env" {
description = "環境名 (dev, prod)"
type = string
}
variable "aws_region" {
description = "監視対象のAWSリージョン"
type = string
}
variable "name_suffix" {
description = "リソース名に追加するサフィックス (例: use1, apne1)。空文字列の場合はサフィックスなし"
type = string
default = ""
}
variable "create_link_account" {
description = "NewRelic Link Accountを作成するかどうか。同じAWSアカウント内の複数リージョンを監視する場合、1つだけtrueにする"
type = bool
default = true
}
variable "newrelic_account_id" {
description = "NewRelic Account ID"
type = number
}
variable "newrelic_license_key" {
description = "NewRelic Ingest License Key"
type = string
sensitive = true
}
variable "newrelic_account_principal_arn" {
description = "NewRelicのAWS Principal ARN"
type = string
default = "754728514883"
}
variable "newrelic_endpoint_url" {
description = "NewRelic Metric Stream Endpoint URL"
type = string
default = "https://aws-api.newrelic.com/cloudwatch-metrics/v1"
}
variable "output_format" {
description = "Metric Streamの出力フォーマット"
type = string
default = "opentelemetry1.0"
}
variable "metric_namespaces" {
description = "監視するCloudWatch名前空間とメトリクスのリスト"
type = list(object({
namespace = string
metric_names = optional(list(string), [])
}))
default = []
}
💡ポイント1: OpenTelemetry 1.0 (Protobuf) 形式を採用
output_format のデフォルトを opentelemetry1.0 に設定しています。
NewRelicは2025年10月に、AWS CloudWatch Metric StreamsからのOpenTelemetry 1.0 (Protobuf)形式に正式対応しました。従来の opentelemetry0.7 (JSON形式) と比較して、以下のメリットがあります。
- データサイズの削減: Protobufバイナリ形式により、JSON形式より大幅に圧縮効率が向上
- 転送コストの削減: データ転送量が減少することで、AWS Data Transfer料金を削減
- 処理効率の向上: バイナリ形式のため、パース・処理が高速化
NewRelicは公式ブログで、opentelemetry1.0 形式の利用を推奨しています。
参考: AWS監視の効率とコストを最適化:OTel 1.0形式(Protobuf)サポート開始 - NewRelic Blog
💡ポイント2: メトリクスの柔軟な絞り込み
metric_namespaces は optional() を使って柔軟な指定を可能にしています。
# 全メトリクスを収集
{ namespace = "AWS/RDS" }
# 特定メトリクスのみ収集
{
namespace = "AWS/RDS",
metric_names = ["CPUUtilization", "DatabaseConnections"]
}
💡ポイント3: リージョン別の命名制御
name_suffix を使うことで、リージョンごとに識別しやすいリソース名を付けられます。
- 🇯🇵 ap-northeast-1:
myapp-prod-metric-stream - 🇺🇸 us-east-1:
myapp-prod-use1-metric-stream
2️⃣ モジュール: main.tf (Part 1 - IAM & S3)
data "aws_caller_identity" "current" {}
locals {
prefix = var.name_suffix != "" ? "${var.service_name}-${var.env}-${var.name_suffix}" : "${var.service_name}-${var.env}"
account_id = data.aws_caller_identity.current.account_id
}
# 1. IAM Role - CloudWatch Metric Stream → Kinesis Firehose
resource "aws_iam_role" "metric_stream_to_firehose" {
name = "${local.prefix}-metric-stream-firehose-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "streams.metrics.cloudwatch.amazonaws.com"
}
}]
})
}
resource "aws_iam_role_policy" "metric_stream_to_firehose" {
name = "${local.prefix}-metric-stream-to-firehose-policy"
role = aws_iam_role.metric_stream_to_firehose.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = [
"firehose:PutRecord",
"firehose:PutRecordBatch"
]
Effect = "Allow"
Resource = aws_kinesis_firehose_delivery_stream.newrelic_stream.arn
}]
})
}
# 2. IAM Role - Kinesis Firehose → NewRelic
resource "aws_iam_role" "firehose_to_newrelic" {
name = "${local.prefix}-firehose-delivery-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "firehose.amazonaws.com"
}
}]
})
}
# 3. S3 Bucket(Firehose失敗時のバックアップ)
resource "aws_s3_bucket" "firehose_backup" {
bucket = "${local.prefix}-firehose-backup-${local.account_id}"
force_destroy = true
}
resource "aws_s3_bucket_ownership_controls" "firehose_backup" {
bucket = aws_s3_bucket.firehose_backup.id
rule {
object_ownership = "BucketOwnerEnforced"
}
}
resource "aws_iam_role_policy" "firehose_s3_backup" {
name = "${local.prefix}-firehose-s3-backup-policy"
role = aws_iam_role.firehose_to_newrelic.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = [
"s3:PutObject",
"s3:GetObject",
"s3:ListBucket"
]
Effect = "Allow"
Resource = [
aws_s3_bucket.firehose_backup.arn,
"${aws_s3_bucket.firehose_backup.arn}/*"
]
}]
})
}
3️⃣ モジュール: main.tf (Part 2 - Firehose & Metric Stream)
# 4. Kinesis Firehose Delivery Stream
resource "aws_kinesis_firehose_delivery_stream" "newrelic_stream" {
name = "${local.prefix}-metric-stream"
destination = "http_endpoint"
http_endpoint_configuration {
url = var.newrelic_endpoint_url
name = "NewRelic ${var.env}"
access_key = var.newrelic_license_key
buffering_size = 1
buffering_interval = 60
s3_backup_mode = "FailedDataOnly"
role_arn = aws_iam_role.firehose_to_newrelic.arn
s3_configuration {
role_arn = aws_iam_role.firehose_to_newrelic.arn
bucket_arn = aws_s3_bucket.firehose_backup.arn
buffering_size = 10
buffering_interval = 400
compression_format = "GZIP"
}
request_configuration {
content_encoding = "GZIP"
}
}
}
# 5. CloudWatch Metric Stream
resource "aws_cloudwatch_metric_stream" "newrelic" {
name = "${local.prefix}-metric-stream"
role_arn = aws_iam_role.metric_stream_to_firehose.arn
firehose_arn = aws_kinesis_firehose_delivery_stream.newrelic_stream.arn
output_format = var.output_format
dynamic "include_filter" {
for_each = var.metric_namespaces
content {
namespace = include_filter.value.namespace
metric_names = include_filter.value.metric_names
}
}
}
💡ポイント4: Firehose の設定値
s3_backup_mode = "FailedDataOnly" にすることで、 正常配信されたデータはS3に保存せず、コストを削減します。
💡ポイント5: dynamic ブロックでメトリクスフィルター
dynamic "include_filter" を使うことで、変数で渡された名前空間分だけフィルターを動的に生成します。
4️⃣ モジュール: main.tf (Part 3 - NewRelic Integration)
# 6. IAM Role - NewRelicがAWSリソース情報を読み取る
resource "aws_iam_role" "newrelic_aws_role" {
name = "${local.prefix}-newrelic-integrations-role"
description = "NewRelic Cloud integration role (PUSH mode only)"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
AWS = var.newrelic_account_principal_arn
}
Condition = {
StringEquals = {
"sts:ExternalId" = tostring(var.newrelic_account_id)
}
}
}]
})
}
resource "aws_iam_policy" "newrelic_aws_permissions" {
name = "${local.prefix}-newrelic-cloud-permissions"
description = "Permissions for NewRelic to read AWS resource metadata"
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = [
"ec2:DescribeVpcs",
"ec2:DescribeSubnets",
"ec2:DescribeSecurityGroups",
"ec2:DescribeNetworkInterfaces",
"ec2:DescribeInternetGateways",
"ec2:DescribeNatGateways",
"ec2:DescribeVpcEndpoints",
"ec2:DescribeNetworkAcls",
"ec2:DescribeVpcAttribute",
"ec2:DescribeRouteTables",
"ec2:DescribeVpcPeeringConnections",
"ec2:DescribeVpnConnections",
"tag:GetResources",
"cloudtrail:LookupEvents",
"config:BatchGetResourceConfig",
"config:ListDiscoveredResources"
]
Effect = "Allow"
Resource = "*"
}]
})
}
resource "aws_iam_role_policy_attachment" "newrelic_aws_policy_attach" {
role = aws_iam_role.newrelic_aws_role.name
policy_arn = aws_iam_policy.newrelic_aws_permissions.arn
}
# 7. NewRelic Cloud AWS Link Account(PUSHモードのみ)
# 同じAWSアカウント内の複数リージョンを監視する場合、1つのモジュールでのみ作成する
resource "newrelic_cloud_aws_link_account" "push" {
count = var.create_link_account ? 1 : 0
account_id = var.newrelic_account_id
arn = aws_iam_role.newrelic_aws_role.arn
metric_collection_mode = "PUSH"
name = "${local.prefix}-push"
}
💡ポイント6: NewRelic Link Account は1つだけ
count を使って、Link Accountの作成を制御します。複数リージョンを監視する場合でも、AWSアカウント全体に対して1つだけ作成します。
- メインリージョン (ap-northeast-1):
create_link_account = true - 追加リージョン (us-east-1):
create_link_account = false
5️⃣ モジュール: terraform.tf
モジュールで必要なプロバイダーのバージョン制約を定義します。
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
newrelic = {
source = "newrelic/newrelic"
version = "~> 3.76.0"
}
}
}
6️⃣ 環境設定: terraform.tf
環境ごとのTerraformバージョン、プロバイダー設定、S3バックエンド設定を定義します。
terraform {
required_version = "~> 1.10.0"
required_providers {
newrelic = {
source = "newrelic/newrelic"
version = "~> 3.76.0"
}
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
backend "s3" {
bucket = "myapp-dev-tfstate"
key = "newrelic/environments/dev/integration/terraform.tfstate"
region = "ap-northeast-1"
}
}
provider "newrelic" {
account_id = local.newrelic_account_id
region = "US"
api_key = var.newrelic_user_key
}
provider "aws" {
region = "ap-northeast-1"
}
# us-east-1用のプロバイダー (CloudFront, WAF監視用)
provider "aws" {
alias = "us_east_1"
region = "us-east-1"
}
7️⃣ 環境設定: locals.tf & variables.tf
locals {
service_name = "myapp"
env = "dev"
newrelic_account_id = 1234567 # あなたのNewRelic Account ID
}
variable "newrelic_user_key" {
description = "NewRelic User Key"
type = string
sensitive = true
}
variable "newrelic_license_key" {
description = "NewRelic Ingest License Key"
type = string
sensitive = true
}
💡ポイント7: 2種類のキーが必要
NewRelicとの連携には、以下の2種類のAPIキーが必要です。
- User Key 🔑: Terraform Providerの認証用。NerdGraphやREST API経由での操作・管理に使用
- Ingest License Key 📥: AWSからNewRelicへのデータ送信用。監視対象からのデータ取り込み(インジェスト)に使用
8️⃣ 環境設定: main.tf
# ap-northeast-1 リージョンの監視
module "newrelic_aws_integration" {
source = "../../../modules/integrations"
service_name = local.service_name
env = local.env
aws_region = "ap-northeast-1"
name_suffix = ""
create_link_account = true
newrelic_account_id = local.newrelic_account_id
newrelic_license_key = var.newrelic_license_key
output_format = "opentelemetry1.0"
metric_namespaces = [
# 全メトリクスを収集
{ namespace = "AWS/ApplicationELB" },
{ namespace = "AWS/RDS" },
{ namespace = "AWS/ECS" },
{ namespace = "ECS/ContainerInsights" },
{ namespace = "AWS/ElastiCache" }
]
providers = {
aws = aws
newrelic = newrelic
}
}
# us-east-1 リージョンの監視(CloudFront, WAF)
module "newrelic_aws_integration_use1" {
source = "../../../modules/integrations"
service_name = local.service_name
env = local.env
aws_region = "us-east-1"
name_suffix = "use1"
create_link_account = false
newrelic_account_id = local.newrelic_account_id
newrelic_license_key = var.newrelic_license_key
output_format = "opentelemetry1.0"
metric_namespaces = [
{ namespace = "AWS/CloudFront" },
{ namespace = "AWS/WAFV2" }
]
providers = {
aws = aws.us_east_1
newrelic = newrelic
}
}
💡ポイント8: マルチリージョン監視の設定
-
メインリージョン 🇯🇵 (ap-northeast-1)
-
create_link_account = true: AWSアカウントとNewRelicを紐付け -
name_suffix = "": 既存リソース名との互換性維持 - 主要なAWSサービスを監視(ECS, RDS, ALB等)
-
-
追加リージョン 🇺🇸 (us-east-1)
-
create_link_account = false: 既存のLink Accountを使用 -
name_suffix = "use1": リソース識別用のサフィックス - グローバルサービスを監視(CloudFront, WAF)
-
aliasを使って複数のAWSプロバイダーを定義
-
🎉 まとめ
NewRelicとAWSの連携をTerraformで実装することで、CloudFormationのネスト構造や公式モジュールの制約から解放され、透明性の高い運用が可能になります。
この実装が、マルチリージョン環境でのNewRelic監視構築の参考になれば幸いです🚀
えみり〜でした|ωΦ)ฅ
Discussion