【Terraformコード公開(New Relic編)】ECS高ボリューム環境でAPM・ログ・メトリクスを実装する

2026 年 5 月 14 日 | AWS / Observability

【技術相談】本件の内容に関して 30 分間の無料相談承ります →

【Terraformコード公開(New Relic編)】ECS高ボリューム環境でAPM・ログ・メトリクスを実装する

この記事をシェア

こんにちは、練馬区旭丘で個人事業主として AI コンサル兼 AWS 構築をやっている野口です。

基礎編の続編として、実践編シリーズを 3 本連続で公開します。1 本目は New Relic 編です。

最初に大事なお断りを書いておきます。この記事の Terraform コードは「環境に合わせて調整する雛形」であり、コピー& apply で本番投入できる完成品ではありません。VPC 構成、IAM ポリシー、タグ規約、Terraform のディレクトリ規約は会社ごとに違うので、必ず自社環境に書き直してから使ってください。

とはいえ、「ゼロから書き始めて、どこから手をつけたらいいか分からない」段階の人が、設計判断の足場として読めるレベルまでは書きました。「これを下敷きに自社用に詰める」が想定される使い方です。


この記事で扱うもの

  • New Relic AWS Integration(CloudWatch Metric Streams + Kinesis Firehose 経由の PUSH 方式)
  • ECS/Fargate タスク定義への APM Agent 組み込み
  • FireLens + Fluent Bit による New Relic Logs への直接転送
  • 高ボリューム ECS 環境でのコスト制御の設計
  • NRQL で見るべき代表的な指標
  • 現場でやらかしがちな落とし穴

想定アーキテクチャ

実践編シリーズ共通で、現実的な高トラフィック ECS 構成を想定します。

  • ALB(複数 AZ、HTTPS 終端、WAF 連携あり)
  • ECS on Fargate(複数 AZ、Service Auto Scaling 有効、タスク数 50〜500 の振れ幅)
  • RDS Aurora MySQL Multi-AZ(Writer + Reader 2 本)
  • ElastiCache for Redis(クラスタモード有効)
  • VPC エンドポイント(S3, ECR, CloudWatch Logs, Secrets Manager, Kinesis Firehose 向け)
  • CloudWatch Metric Streams → Kinesis Firehose → New Relic
  • アプリコンテナ(APM Agent 同梱)+ FireLens サイドカー(log_router)

ポイントは、ECS のメトリクスとログは「CloudWatch 経由(Metric Streams)」と「直接送信」の両方の経路を持つこと。AWS サービスのメトリクス(ALB、RDS、ElastiCache、ECS)は Metric Streams 経由で New Relic に流し、アプリの APM データとログは Agent / FireLens から直接 New Relic に送ります。

なぜこの二段構えにするかというと、AWS が勝手に出すサービスメトリクスを取りこぼさないためです。Metric Streams 経由なら、新しい AWS サービスを足してもメトリクスが自動的に上がってきます。アプリ側のデータは直接送ることで、CloudWatch 経由の遅延と二重課金を避けます。


実装方針:何を Terraform で管理するか

Terraform で管理するスコープを最初に切ります。

  • AWS provider と New Relic provider
  • License Key は Secrets Manager に格納し、ARN 参照でのみ渡す
  • New Relic AWS Integration リソース(newrelic_cloud_aws_link_account
  • Kinesis Firehose(メトリクス転送用、HTTP Endpoint = New Relic)
  • CloudWatch Metric Streams
  • ECS Task Definition(アプリ + FireLens サイドカー)
  • New Relic 側の NRQL Alert Condition / Alert Policy

CI/CD(PR ごとに plan、main マージで apply)は別記事で扱います。


variables.tf 相当:環境差分を集約する

variable "aws_region" {
  description = "AWSリージョン"
  type        = string
  default     = "ap-northeast-1"
}

variable "environment" {
  description = "環境名(prod/staging/dev)"
  type        = string
}

variable "service_name" {
  description = "サービス名(タグ・ARNの一部に使用)"
  type        = string
}

variable "newrelic_account_id" {
  description = "New Relic アカウントID"
  type        = string
  sensitive   = true
}

variable "newrelic_region" {
  description = "New Relic データセンター(US または EU)"
  type        = string
  default     = "US"
}

# License Key は Terraform 変数に平文で渡さない
# Secrets Manager に事前格納し、ARN 参照で受け取る
variable "newrelic_license_key_secret_arn" {
  description = "New Relic License Key を格納した Secrets Manager の ARN"
  type        = string
}

variable "common_tags" {
  description = "全リソース共通タグ"
  type        = map(string)
  default = {
    ManagedBy = "Terraform"
  }
}

この設計のポイントは「Secrets Manager に事前に License Key を置いて、ARN 参照だけ Terraform に渡す」こと。Terraform 変数に平文で渡すのはやめてください。terraform.tfstate に License Key が平文で残るのは事故のもとです(リモートステートが暗号化されていても、ログ等で漏れる経路は多い)。

自社環境で変えるポイント

  • common_tags は社内のタグ規約に合わせる(CostCenter、Owner、Compliance など)
  • newrelic_region は契約しているデータセンターに合わせる
  • 環境別の値は terraform.tfvars または workspace で切り替え

Provider 設定

terraform {
  required_version = ">= 1.6.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
    newrelic = {
      source  = "newrelic/newrelic"
      version = "~> 3.0"
    }
  }
}

provider "aws" {
  region = var.aws_region

  default_tags {
    tags = merge(var.common_tags, {
      Environment = var.environment
      Service     = var.service_name
    })
  }
}

# New Relic Provider
# API Key は環境変数 NEW_RELIC_API_KEY 経由で渡す前提
provider "newrelic" {
  account_id = var.newrelic_account_id
  region     = var.newrelic_region
}

NEW_RELIC_API_KEY(User API Key)は CI/CD のシークレットから渡してください。ローカルでは direnv などで読み込むのが定石です。Terraform コード内に書き込まないこと。


New Relic AWS Integration

New Relic 側で「この AWS アカウントを連携する」設定を Terraform で書きます。これがないと、CloudWatch Metric Streams 経由で投げたデータが New Relic のアカウントに紐付きません。

# IAM Role: New Relic がメタデータ取得に使うクロスアカウントロール
data "aws_iam_policy_document" "newrelic_assume_role" {
  statement {
    effect  = "Allow"
    actions = ["sts:AssumeRole"]

    principals {
      type        = "AWS"
      identifiers = ["arn:aws:iam::754728514883:root"] # New Relic 側 AWS アカウント
    }

    condition {
      test     = "StringEquals"
      variable = "sts:ExternalId"
      values   = [var.newrelic_account_id]
    }
  }
}

resource "aws_iam_role" "newrelic_integration" {
  name               = "${var.service_name}-newrelic-integration-${var.environment}"
  assume_role_policy = data.aws_iam_policy_document.newrelic_assume_role.json
}

# 推奨は ReadOnlyAccess。社内ポリシーで広すぎる場合は最小権限版に置換
resource "aws_iam_role_policy_attachment" "newrelic_readonly" {
  role       = aws_iam_role.newrelic_integration.name
  policy_arn = "arn:aws:iam::aws:policy/ReadOnlyAccess"
}

# Billing メトリクスを取得したい場合は別途付与
resource "aws_iam_role_policy_attachment" "newrelic_budgets" {
  role       = aws_iam_role.newrelic_integration.name
  policy_arn = "arn:aws:iam::aws:policy/job-function/Billing"
}

# New Relic 側に AWS アカウントを紐付け(PUSH モード = Metric Streams 経由)
resource "newrelic_cloud_aws_link_account" "this" {
  account_id             = var.newrelic_account_id
  arn                    = aws_iam_role.newrelic_integration.arn
  metric_collection_mode = "PUSH"
  name                   = "${var.service_name}-${var.environment}"
}

「PUSH」モードは CloudWatch Metric Streams 経由でメトリクスを送る方式で、現在の推奨パターンです。これにより メトリクスのほぼリアルタイム到達と、API Polling 廃止によるレートリミット問題の回避ができます。旧方式の Polling だと 5 分以上の遅延と API コール超過に泣くので、新規構築では PUSH 一択です。

External ID として newrelic_account_id を使うのは、他テナント混入を防ぐ定石です。これを忘れるとセキュリティ警告が出るうえ、New Relic のサポートからも指摘されます。

ポリシーは ReadOnlyAccess を基本に Billing 用ポリシーを足す、というのが New Relic の推奨です。社内のセキュリティチームから「ReadOnlyAccess は広すぎる」と言われる場面では、必要サービスのみ列挙したカスタムポリシーに置き換えてください。ただし最小権限化しすぎると新しい AWS サービスを足すたびに権限不足になるので、運用コストとのバランスで決めます。

provider バージョン注意: newrelic_cloud_aws_link_account の属性名・必須属性は provider バージョンで揺れるので、terraform init -upgrade 後の terraform plan で必ず差分確認してください。ここは「代表例」と捉えてください。


CloudWatch Metric Streams → Kinesis Firehose → New Relic

メトリクス転送経路の本体です。一番大事な部分。

# Firehose の配信エラー保存用 S3
resource "aws_s3_bucket" "firehose_backup" {
  bucket = "${var.service_name}-newrelic-firehose-${var.environment}"
}

resource "aws_s3_bucket_lifecycle_configuration" "firehose_backup" {
  bucket = aws_s3_bucket.firehose_backup.id

  rule {
    id     = "expire-old"
    status = "Enabled"
    expiration {
      days = 14
    }
  }
}

# Firehose 用 IAM Role
data "aws_iam_policy_document" "firehose_assume_role" {
  statement {
    effect  = "Allow"
    actions = ["sts:AssumeRole"]
    principals {
      type        = "Service"
      identifiers = ["firehose.amazonaws.com"]
    }
  }
}

resource "aws_iam_role" "firehose_to_newrelic" {
  name               = "${var.service_name}-firehose-newrelic-${var.environment}"
  assume_role_policy = data.aws_iam_policy_document.firehose_assume_role.json
}

resource "aws_iam_role_policy" "firehose_to_newrelic" {
  role = aws_iam_role.firehose_to_newrelic.id
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Action = [
          "s3:AbortMultipartUpload",
          "s3:GetBucketLocation",
          "s3:GetObject",
          "s3:ListBucket",
          "s3:ListBucketMultipartUploads",
          "s3:PutObject"
        ]
        Resource = [
          aws_s3_bucket.firehose_backup.arn,
          "${aws_s3_bucket.firehose_backup.arn}/*"
        ]
      },
      {
        Effect   = "Allow"
        Action   = ["secretsmanager:GetSecretValue"]
        Resource = [var.newrelic_license_key_secret_arn]
      }
    ]
  })
}

locals {
  newrelic_metrics_endpoint = var.newrelic_region == "US" ? "https://aws-api.newrelic.com/cloudwatch-metrics/v1" : "https://aws-api.eu.newrelic.com/cloudwatch-metrics/v1"
}

# Kinesis Firehose: HTTP Endpoint = New Relic
resource "aws_kinesis_firehose_delivery_stream" "newrelic_metrics" {
  name        = "${var.service_name}-newrelic-metrics-${var.environment}"
  destination = "http_endpoint"

  http_endpoint_configuration {
    name               = "NewRelic"
    url                = local.newrelic_metrics_endpoint
    role_arn           = aws_iam_role.firehose_to_newrelic.arn
    s3_backup_mode     = "FailedDataOnly"
    buffering_size     = 1
    buffering_interval = 60

    request_configuration {
      content_encoding = "GZIP"
    }

    # License Key を Secrets Manager から参照
    secrets_manager_configuration {
      enabled    = true
      secret_arn = var.newrelic_license_key_secret_arn
    }
  }

  s3_configuration {
    role_arn   = aws_iam_role.firehose_to_newrelic.arn
    bucket_arn = aws_s3_bucket.firehose_backup.arn
  }
}

# CloudWatch Metric Streams 用 IAM Role
data "aws_iam_policy_document" "metric_stream_assume_role" {
  statement {
    effect  = "Allow"
    actions = ["sts:AssumeRole"]
    principals {
      type        = "Service"
      identifiers = ["streams.metrics.cloudwatch.amazonaws.com"]
    }
  }
}

resource "aws_iam_role" "metric_stream" {
  name               = "${var.service_name}-metric-stream-${var.environment}"
  assume_role_policy = data.aws_iam_policy_document.metric_stream_assume_role.json
}

resource "aws_iam_role_policy" "metric_stream" {
  role = aws_iam_role.metric_stream.id
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect   = "Allow"
      Action   = ["firehose:PutRecord", "firehose:PutRecordBatch"]
      Resource = aws_kinesis_firehose_delivery_stream.newrelic_metrics.arn
    }]
  })
}

# Metric Streams:必要な namespace のみ列挙(最重要)
resource "aws_cloudwatch_metric_stream" "newrelic" {
  name          = "${var.service_name}-newrelic-${var.environment}"
  role_arn      = aws_iam_role.metric_stream.arn
  firehose_arn  = aws_kinesis_firehose_delivery_stream.newrelic_metrics.arn
  output_format = "opentelemetry1.0"

  include_filter { namespace = "AWS/ECS" }
  include_filter { namespace = "AWS/ApplicationELB" }
  include_filter { namespace = "AWS/RDS" }
  include_filter { namespace = "AWS/ElastiCache" }
  include_filter { namespace = "AWS/Lambda" }
  # ※必要な namespace だけ列挙する。「全部送る」は絶対にやらない
}

ここが一番の落とし穴ポイントです。include_filter で namespace を絞らないと、すべての AWS namespace のメトリクスが New Relic に流れます。そうすると取り込みデータ量(Ingest GB)が爆発して、月末請求書を見て卒倒します。

自社環境で変えるポイント

  • include_filter の namespace は、自社で本当に必要なものだけ列挙する
  • 不要になった namespace は減らす運用フローを最初から決めておく
  • output_formatopentelemetry1.0 が現状の推奨。json は古い構築でしか使わない

高ボリューム環境での注意点

  • Firehose の buffering_size / buffering_interval を小さくしすぎると Firehose 課金が増える
  • 大きくしすぎると New Relic 上の表示遅延が増える
  • 60 秒・1MB あたりが現実的な折衷点
  • バックアップ用 S3 のライフサイクルを 必ず設定。エラーログが S3 に積み上がって地味な課金になる事故が多い

provider バージョン注意: secrets_manager_configuration ブロックは比較的新しい属性です。古い AWS provider(4 系前半など)では使えないので、その場合は access_key 属性 + KMS 暗号化など別経路を取ってください。


ECS Task Definition への APM Agent 組み込み

ここからアプリ側です。APM Agent の入れ方には大きく 2 パターン。

  1. アプリイメージに APM Agent を同梱する(推奨)
  2. APM Agent を Init Container や別レイヤで注入する

Java、.NET、Node.js、Ruby、Python なら同梱方式が一番素直です。Go の場合はライブラリ組み込み(OpenTelemetry 経由が主流)になります。

ここでは Node.js アプリ前提の同梱方式 + FireLens でログ転送の構成を示します。

# CloudWatch Logs Group(FireLens 自身のログ用)
resource "aws_cloudwatch_log_group" "firelens" {
  name              = "/ecs/${var.service_name}/${var.environment}/firelens"
  retention_in_days = 7
}

resource "aws_ecs_task_definition" "app" {
  family                   = "${var.service_name}-${var.environment}"
  network_mode             = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  cpu                      = "1024"
  memory                   = "2048"
  execution_role_arn       = aws_iam_role.ecs_task_execution.arn
  task_role_arn            = aws_iam_role.ecs_task.arn

  container_definitions = jsonencode([
    {
      name      = "app"
      image     = "${var.ecr_repo_url}:${var.app_image_tag}"
      essential = true
      portMappings = [{ containerPort = 3000, protocol = "tcp" }]

      environment = [
        { name = "NODE_ENV",                              value = var.environment },
        { name = "NEW_RELIC_APP_NAME",                    value = "${var.service_name}-${var.environment}" },
        { name = "NEW_RELIC_DISTRIBUTED_TRACING_ENABLED", value = "true" },
        { name = "NEW_RELIC_LOG",                         value = "stdout" },
        { name = "NEW_RELIC_NO_CONFIG_FILE",              value = "true" },
        { name = "NEW_RELIC_LABELS",                      value = "env:${var.environment};service:${var.service_name}" }
      ]

      secrets = [
        {
          name      = "NEW_RELIC_LICENSE_KEY"
          valueFrom = var.newrelic_license_key_secret_arn
        }
      ]

      # FireLens にログを流す(直接 New Relic Logs へ転送)
      logConfiguration = {
        logDriver = "awsfirelens"
        options = {
          Name     = "newrelic"
          endpoint = "https://log-api.newrelic.com/log/v1"
        }
        secretOptions = [
          {
            name      = "apiKey"
            valueFrom = var.newrelic_license_key_secret_arn
          }
        ]
      }
    },
    {
      name      = "log_router"
      image     = "public.ecr.aws/aws-observability/aws-for-fluent-bit:stable"
      essential = true

      firelensConfiguration = {
        type = "fluentbit"
        options = {
          enable-ecs-log-metadata = "true"
        }
      }

      logConfiguration = {
        logDriver = "awslogs"
        options = {
          awslogs-group         = aws_cloudwatch_log_group.firelens.name
          awslogs-region        = var.aws_region
          awslogs-stream-prefix = "firelens"
        }
      }

      memoryReservation = 50
    }
  ])
}

この設計の肝

  • アプリ側のログは awsfirelens ドライバ経由で Fluent Bit に渡し、Fluent Bit が直接 New Relic Logs に投げる
  • CloudWatch Logs は FireLens 自身のログ用にだけ使う(保持期間も 7 日と短く)
  • License Key は Secrets Manager から ECS Secrets 経由で渡す(環境変数に平文で書かない)
  • NEW_RELIC_LABELS でタグを最初から付けておくと NRQL で絞り込みやすい

自社環境で変えるポイント

  • CPU / Memory は実測ベースで調整。Node.js なら 1vCPU / 2GB を起点に
  • NEW_RELIC_APP_NAME は監視粒度に合わせて命名。環境別を強く推奨(prod と staging を混ぜると地獄)
  • VPC エンドポイント経由でない場合、NAT Gateway 通信料が乗るので確認

高ボリューム環境での注意点

  • FireLens の Fluent Bit は メモリ制限が低いとログを落とすmemoryReservation は最低 50MB は確保
  • ステージング環境で ログ取り込みレートを実測 してから本番に持っていく
  • アプリの DEBUG ログを本番で出すと一瞬で破綻するので、Fluent Bit の Filter で除外推奨(次節)

Fluent Bit のカスタム設定(コスト制御の本丸)

タスク定義に Name = newrelic を書くだけで最低限動きますが、現場では Fluent Bit の Filter を独自定義したくなる場面が多いです。S3 から設定ファイルを読ませる例:

resource "aws_s3_bucket" "fluentbit_config" {
  bucket = "${var.service_name}-fluentbit-config-${var.environment}"
}

resource "aws_s3_object" "fluentbit_config" {
  bucket  = aws_s3_bucket.fluentbit_config.id
  key     = "fluentbit.conf"
  content = <<-EOT
    [SERVICE]
        Flush               1
        Log_Level           info
        Parsers_File        parsers.conf

    [FILTER]
        Name                grep
        Match               *
        Exclude             level debug

    [FILTER]
        Name                modify
        Match               *
        Add                 environment ${var.environment}
        Add                 service     ${var.service_name}

    [OUTPUT]
        Name                newrelic
        Match               *
        licenseKey          $${NEW_RELIC_LICENSE_KEY}
        endpoint            https://log-api.newrelic.com/log/v1
  EOT
}

ECS タスク定義の firelensConfiguration から config-file-type = s3 + config-file-value = S3 ARN を指定すれば、この設定ファイルが読み込まれます。

この設定の意味

  • DEBUG レベルのログを Fluent Bit 段階で落とす(New Relic 取り込み課金を最大要因の手前で削る)
  • 全ログに environment / service タグを付与(NRQL で絞り込めるように)
  • licenseKey環境変数経由で受け取る(ECS Secrets で注入済み)

現場の落とし穴

  • Log_Level debug のまま本番投入する事故が定番。これだけで取り込み GB が数倍になる
  • Match * で全部投げるので、送りたくないログは Filter で明示的に Exclude する
  • 設定ファイルを S3 で管理する運用は、設定変更ごとに ECS Service の再デプロイ(タスク入れ替え)が必要。Terraform 側で terraform apply 即反映ではない点に注意

NRQL Alert Condition の Terraform 化

「アラート設定は UI でポチポチ作成」だと、レビューできずインフラ as Code が成立しません。NRQL ベースの Alert Condition を Terraform 管理します。

resource "newrelic_alert_policy" "ecs_app" {
  name                = "${var.service_name}-${var.environment}"
  incident_preference = "PER_CONDITION_AND_TARGET"
}

# Apdex 劣化アラート
resource "newrelic_nrql_alert_condition" "apdex_degraded" {
  policy_id = newrelic_alert_policy.ecs_app.id
  name      = "Apdex Degraded"
  type      = "static"
  enabled   = true

  nrql {
    query = <<-NRQL
      SELECT apdex(duration, t: 0.5)
      FROM Transaction
      WHERE appName = '${var.service_name}-${var.environment}'
    NRQL
  }

  critical {
    operator              = "below"
    threshold             = 0.85
    threshold_duration    = 300
    threshold_occurrences = "ALL"
  }

  aggregation_window = 60
  aggregation_method = "event_flow"
  aggregation_delay  = 30
}

# 5xx 率アラート
resource "newrelic_nrql_alert_condition" "error_rate" {
  policy_id = newrelic_alert_policy.ecs_app.id
  name      = "5xx Error Rate High"
  type      = "static"
  enabled   = true

  nrql {
    query = <<-NRQL
      SELECT percentage(count(*), WHERE httpResponseCode LIKE '5%')
      FROM Transaction
      WHERE appName = '${var.service_name}-${var.environment}'
    NRQL
  }

  critical {
    operator              = "above"
    threshold             = 5
    threshold_duration    = 300
    threshold_occurrences = "ALL"
  }
}

NRQL は 最初に書くときが一番つらいですが、慣れると SQL ライクなので強力です。

Alert Condition は必ず aggregation_delay を設定すること(テレメトリ到達のばらつきを吸収するため)。これを忘れると遅延データでアラートが誤発火・未発火します。30〜60 秒くらいが妥当な開始値。

自社環境で変えるポイント

  • 5xx 率の閾値はサービスの SLI/SLO に合わせて調整(5% は緩めの例)
  • Apdex の t: 閾値(0.5 秒)はサービスの応答性能目標に合わせる
  • incident_preference は組織のオンコール運用に合わせて選択(PER_POLICY / PER_CONDITION / PER_CONDITION_AND_TARGET

高ボリューム環境での落とし穴 — 現場で本当に見たやつ

ここからが本題。Terraform を書いた後に痛い目に遭うポイントです。

落とし穴 1: Metric Streams で全 namespace を流す

include_filter を書かずデフォルトのまま運用すると、すべての AWS サービスメトリクスが流れて月末で経営呼び出しコース。必ず明示列挙してください。

落とし穴 2: アプリログを DEBUG レベルのまま本番投入

最頻出の事故。リリース日に取り込み GB が 10 倍になって翌日請求書通知。
対策: Fluent Bit の Filter で Exclude level debug、アプリ側で LOG_LEVEL を環境変数化、CI で本番設定の log_level を機械的に検証。

落とし穴 3: カスタム属性に user_id / request_id をそのまま入れる

NRDB のカーディナリティが爆発します。「ユニーク値が 10 万を超える属性」は属性ではなくログ本文に入れるのが鉄則。検索が遅くなるだけでなく、コストにも効いてきます。

落とし穴 4: VPC エンドポイント未設定で NAT Gateway 課金爆発

ECS から Fluent Bit が New Relic の HTTPS エンドポイントに送る通信が NAT Gateway 経由になっている、というのは現場でよく見る。Firehose や Logs の外向き通信は NAT Gateway 料金で蝕まれるので、可能な限り VPC エンドポイント / PrivateLink を経由してください。

落とし穴 5: License Key を平文で Terraform に書く

これは技術的失敗というより事故。絶対に Secrets Manager 経由で渡す。Git にコミットされた瞬間にローテーション作業が発生し、各サービスへの伝達コストが跳ね返ります。

落とし穴 6: Alert を NRQL で書き、テスト通知をしないまま本番化

本当に発火するか確認しないままリリースして、いざ障害時に飛ばない。Notification Channel をテスト送信し、incident_preference で疎通確認まで含めて Terraform plan / apply してください。


コストを爆発させない設計

New Relic の請求は基本的に 取り込みデータ量(GB)× ユーザー数です。コスト制御のレバーは以下に集約されます。

  1. Metric Streams で送る namespace を絞る(最大のレバー)
  2. ログを Fluent Bit でフィルタ/サンプリング(DEBUG 除外、5xx 以外サンプリングなど)
  3. APM の Distributed Tracing サンプリング率を下げる(高トラフィック時)
  4. NRDB の保持期間設定を見直す(取り込み GB は減らないが、検索速度に効く)
  5. 不要な Custom Event 送信をやめる

特に 「取り込み前にフィルタする」が一番効きます。データを New Relic 側に入れた後で消すのは、コスト的にも運用的にも筋が悪いです。

「いつから絞り始めるか?」は、最初の 1 ヶ月は緩めに送って実態を見て、2 ヶ月目から絞り込むのがおすすめ。最初から絞りすぎると見えない世界が広がっていて、それも別の事故のもとです。


NRQL で見るべき代表的な指標(参考)

ダッシュボードに置いておく定番クエリです。

-- レスポンスタイムの p95(5分窓)
SELECT percentile(duration, 95) FROM Transaction
WHERE appName = 'myservice-prod' TIMESERIES 5 minutes

-- 5xx 率の時系列
SELECT percentage(count(*), WHERE httpResponseCode LIKE '5%')
FROM Transaction WHERE appName = 'myservice-prod' TIMESERIES

-- 遅い SQL トップ10
SELECT max(duration) FROM Span
WHERE appName = 'myservice-prod' AND span.kind = 'client'
AND db.system IS NOT NULL FACET name LIMIT 10

-- ECS タスクの CPU 使用率(Metric Streams 経由)
SELECT average(`aws.ecs.CPUUtilization`) FROM Metric
WHERE aws.ecs.ServiceName = 'myservice-prod' TIMESERIES

appNameServiceName は環境ごとに変えてください。TIMESERIES を付けるかどうかでダッシュボードの見え方が変わるので、棒グラフ系か線グラフ系かで使い分けてください。


まとめ:New Relic 編が向いている現場

最後に総括です。

New Relic + Terraform でこの構成を組むのが向いているのは、

  • AWS 中心、ECS/Fargate 主体の本番環境
  • アプリのコード深部まで APM で追いかけたい
  • NRQL を覚えてダッシュボード / アラートを組める運用者がいる
  • 取り込みデータ量で費用が見える方が予算組みやすい組織

逆に 「インフラだけ見たい、アプリには触らない」 ケースだと、DatadogCloudWatch の方が合います。次回の Datadog 編 / CloudWatch 編で扱います。

繰り返しになりますが、このコードは雛形です。VPC の構成、IAM ポリシー、Terraform のリポジトリ規約は会社ごとに違うので、必ず自社環境に書き直してから使ってください。とはいえ、設計判断のたたき台としては十分なところまでは書いたつもりです。これを起点に、本気の本番監視を組んでください。

次回は Datadog 編。ECS の Datadog Agent サイドカー、FireLens 経由のログ転送、Monitors と SLO の Terraform 化を扱います。


関連キーワード

  • New Relic / New Relic APM / New Relic Logs / NRQL
  • Terraform / newrelic provider / AWS provider
  • ECS / Fargate / awsvpc / Service Auto Scaling
  • CloudWatch Metric Streams / Kinesis Firehose / OpenTelemetry
  • FireLens / Fluent Bit / New Relic Logs Intake
  • NRQL Alert Condition / Alert Policy / Apdex
  • Secrets Manager / ECS Secrets
  • VPC Endpoint / PrivateLink / NAT Gateway
  • オブザーバビリティ / SRE

この記事が役に立ったらシェアしてください

New Relic で ECS 本番監視を組もうとしている仲間に共有してみてください。

カテゴリ

AWS / Observability

公開日

2026 年 5 月 14 日

💬 無料技術相談のご案内

この記事でご紹介した技術について、導入や活用のご相談を30 分間無料承っております。

  • 「自社でも導入できる?」といった技術的な疑問
  • 既存システムとの連携・移行に関するご相談
  • コスト感や導入スケジュールの目安

30 年以上の IT 経験をもとに、率直にお答えします。強引なセールスや勧誘は一切ありません。

野口真一 野口真一

お気軽にご相談ください

記事に関するご質問や、AI・IT 技術導入のご相談など、お気軽にお問い合わせください。