コドモン Product Team Blog

株式会社コドモンの開発チームで運営しているブログです。エンジニアやPdMメンバーが、プロダクトや技術やチームについて発信します!

New Relicアラート設定テンプレートを作って、アラート設定をシンプルにしよう!

こちらの記事は「コドモンAdvent Calendar 2023」と「New Relic Advent Calendar 2023」の 23日目の記事です🎅

こんにちは! コドモンSREチーム期待の星、三口です💫
22日目の西銘さんからキラーパスをいただきました。
↓↓西銘さんのアドカレはこちら↓↓

New Relicをお使いのみなさん、アラート設定の管理に困ったことはありませんか?
今回は、私が直面したNew Relicアラート設定の課題を克服すべく、アラート設定のテンプレート化に取り組みましたので、ご紹介します。 New Relicのアラート設定の管理に困っている方は、ぜひ参考にしてみてください!

前提事項

  • 本記事ではTerraformの詳しい仕組みについては触れていませんので、ご了承ください。
  • New RelicへのTerraform導入は、Getting started Guide for Terraformを参考に、進めてみてください。

アラート設定における課題

弊社では2023年5月からNew Relicの導入・運用を開始しました。 SREチームとしては導入したNew RelicをSREチームだけではなく、開発チームのみんなに活用してほしいという想いがあり、 New Relicを見る会や勉強会などを定期的に開催していました。アラート設定も開発チームのメンバーで管理してもらうため、 よりシンプルで簡単なものにすることがSREとしての役目でした。

しかしながらNew Relicのアラート設定を管理する上で、以下の課題を感じていました。

  1. シンプルな設定の難しさ
  2. UI上での設定における難しさ
  3. Terraformでの管理の難しさ

New Relicのアラートは、条件に応じてとても柔軟に設定をすることができます。 しかしその反面設定項目が多く、シンプルな設定をすることへの難しさを感じていました。

またUI上での設定は手作業のため、アラートの数が多くなるほど設定ミスが発生しやすく、管理も難しくなってしまう印象がありました。

さらにIaCの利便性を享受するために、Terraformでの設定を行っていましたが、 やはりアラートの設定が多いほど行数が嵩み、見づらくなってしまっていました。

そこでアラート設定をよりシンプルになるようテンプレート化し、さらにTerraformの知識がない人でも設定できるよう、JSON形式でアラートを設定できるようにしました✨

アラート設定に必要なNew Relicの機能

アラート設定テンプレートをご紹介する前に、New Relicのアラート設定に必要な機能をご紹介します。 まだNew Relicのアラート設定自体に触れたことがない方は、各機能のリンクを参考にぜひ一度作成してみてください!

Alert Condition

Alert Conditionは、 特定のシステムやアプリケーションのパフォーマンスが一定の閾値を超えた場合にアラートを発生させるための条件を設定する機能です。 ここで、NRQL(New Relic Query Language)でアラートの対象を設定し、さらに設定したアラート対象の閾値を設定します。

Alert Policy

Alert Policyは、 Alert Conditionをまとめるファイルの機能です。Alert Coditionのまとめ方はさまざまですが、弊社では開発チームごとにAlert Conditionをまとめ、Alert Policyとして管理しています。

Workflow

Workflowは、 通知先や通知メッセージを設定する機能です。今回通知先として使用するSlackを例にすると、Alert Conditionで設定したアラート対象が閾値を超えた際、 どのSlackチャンネルで、誰をメンションするかを設定することができます。

テンプレートファイルでのアラート設定方法

ここからはいよいよ、JSONファイルを使用したアラートの設定方法をご紹介します! 今回のテンプレートで作成されるアラート設定の通知は、画像のような流れとなっています。

  1. Alert Conditionで設定したメトリクスの値が閾値を超えると、アラートの送信を引き起こすInsidentやIssueが作成されます。
  2. Workflowにて設定したアラートの種類やレベルの条件に一致した場合、それぞれの条件に合ったSlackチャンネルへとアラートが送信されます。

弊社のアラートの通知先には青付箋の3つの開発チーム共通チャンネルを使用していますが、それぞれの開発チームにメンションが飛ぶ仕組みになっているため、認知負荷を高めることなくアラートに気づけるようにしています。

設定項目と実際の設定ファイル

上記アラート通知の流れを実現するために、以下の表の設定項目を入力します。

設定項目 設定内容
group_name Slackの各開発チームグループメンション名
(@マークなし)
app_alert アプリログのアラート設定
name アラート名
description アラート内容の説明
query 取得するメトリクスのNRQL
runbook_url アラート対応手順書のURL
tag ・app / アプリ系エラー
・system / システム系エラー
system_alert AWSリソースのアラート設定
name ~ tag 上記と同様
aggregation_method event_flow or event_timer
aggregation_duration メトリクスを取得する間隔 (秒)
operator_critical 閾値の越え方
above, above_or_equals,
below, below_or_equals,
equals, not_equals
threshold_critical criticalレベルのアラート閾値
operator_warning 閾値を超え方
above, above_or_equals,
below, below_or_equals,
equals, not_equals
threshold_warning warningレベルのアラート閾値

JSONファイル名はgroup_name同様、グループメンション名で管理しています。
hoge_team.json

{
  "group_name": "hoge_team",
  "app_alert": [
    {
      "name": "[SAMPLE] EMERGENCY",
      "description": "{{tags.tags.Name}} にて、EMERGENCYが発生しました。",
      "query": "SELECT COUNT(*) FROM Log WHERE environment = 'prd' AND level = 'EMERGENCY' FACET environment, tags.Name, level, messageId",
      "runbook_url": "https://docs.google.com/hoge",
      "tag": "app"
    },
    {
      "name": "[SAMPLE] FATAL",
      "description": "{{tags.tags.Name}} にて、FATALが発生しました。",
      "query": "SELECT COUNT(*) FROM Log WHERE environment = 'prd' AND level = 'FATAL' FACET tags.Environment, tags.Name, level, messageId",
      "runbook_url": "https://docs.google.com/piyo",
      "tag": "app"
    }
  ],
  "system_alert": [
    {
      "name": "[EC2] CPU Utilization",
      "description": "{{entity.name}} ({{tags.host.hostname}}) にて、CPU使用率が上昇しています。",
      "query": "SELECT average(host.cpuPercent) FROM Metric WHERE environment = 'prd' FACET entity.name, host.hostname",
      "runbook_url": "https://docs.google.com/huga",
      "tag": "system",
      "aggregation_method": "event_flow",
      "aggregation_duration": 180,
      "operator_critical": "above",
      "threshold_critical": 90,
      "operator_warning": "above",
      "threshold_warning": 80
    },
    {
      "name": "[EC2] Memory usage",
      "description": "{{entity.name}}({{tags.host.hostname}}) にて、メモリ使用率が上昇しています。",
      "query": "SELECT average(host.memoryUsedPercent) FROM Metric WHERE environment = 'prd' FACET entity.name, host.hostname",
      "runbook_url": "",
      "tag": "system",
      "aggregation_method": "event_flow",
      "aggregation_duration": 60,
      "operator_critical": "above",
      "threshold_critical": 90,
      "operator_warning": null,
      "threshold_warning": null
    },
  ]
}

シンプルな設定で見通しがよく、管理もしやすいですね✨

アラート設定テンプレートの仕組みと作成方法

ここからは、アラート設定テンプレートの仕組みと作成方法についてご紹介します! アラート設定テンプレートはTerraformのmodule機能を活用しています。 アラート設定に必要な以下の6つのTerraformリソースを、作りやすい単位にmoduleとして切り分けて構成しています。

  • newrelic_alert_policy
  • newrelic_nrql_alert_condition
  • newrelic_workflow
  • newrelic_notification_channel
  • newrelic_notification_destination
  • newrelic_entity_tags

ディレクトリ構成は以下のようになっています。

.
├── modules
|   ├── app_alert
|   |    ├── main.tf
|   |    ├── provider.tf
|   |    └── variables.tf
|   ├── system_alert
|   |    ├── main.tf
|   |    ├── provider.tf
|   |    └── variables.tf
|   └── workflow
|        ├── main.tf
|        ├── output.tf
|        ├── provider.tf
|        └── variables.tf
└── services
    ├── alert_settings
    |    ├── hoge_team.json ← 先ほどの設定ファイル
    |    ├── piyo_team.json
    |    ├── fuga_team.json
    |    └──  ...
    ├── alert_settings.tf
    ├── notification_destination.tf
     └── その他New Relicリソースのtfファイル

それでは各moduleファイルと、moduleを使うために必要なtfファイルをご紹介していきます。

services/alert_settings.tf

locals {
  json_files = fileset(path.module, "alert_settings/*.json")
  json_data  = [for f in local.json_files : jsondecode(file("${path.module}/${f}"))]
}

module "app_alert" {
  source    = "../../../modules/app_alert"
  for_each  = { for f in local.json_data : f.group_name => f }
  policy_id = module.workflow[each.value.group_name].alert_policy_id
  app_alert = each.value.app_alert
}

module "system_alert" {
  source       = "../../../modules/system_alert"
  for_each     = { for f in local.json_data : f.group_name => f }
  policy_id    = module.workflow[each.value.group_name].alert_policy_id
  system_alert = each.value.system_alert
}

module "workflow" {
  source         = "../../../modules/workflow"
  for_each       = { for f in local.json_data : f.group_name => f }
  group_name     = each.value.group_name
  destination_id = newrelic_notification_destination.slack.id
}

alert_settings.tfでは、jsonファイルの集計と各moduleファイルの呼び出しを行っています。 localsブロックでは、alert_settingsディレクトリ配下のJSONファイルの情報を読み込んで、各moduleで使いやすい形式に変換しています。 そして各moduleがそれぞれで必要な情報をeach.valueで取り出しています。

services/notification_destination.tf

resource "newrelic_notification_destination" "slack" {
  name = "Hoge Inc."
  type = "SLACK"

  property {
    key   = "scope"
    label = "Permissions"
    value = "app_mentions:read,channels:join,channels:read,chat:write,chat:write.public,commands,groups:read,links:read,links:write,team:read,users:read"
  }
  property {
    key   = "teamName"
    label = "Team Name"
    value = "Hoge Inc."
  }

  lifecycle {
    ignore_changes = [auth_token]
  }
}

notification_destination.tfファイルでは、workflow moduleで使用するアラートの通知先リソースを作成します。 通知先のSlackの情報を入力します。

modules/app_alert/main.tf

# アプリログのアラート設定

resource "newrelic_nrql_alert_condition" "this" {
  for_each = { for f in var.app_alert : f.name => f }

  name                           = each.value.name
  policy_id                      = var.policy_id
  aggregation_method             = "event_timer"
  aggregation_timer              = 60
  expiration_duration            = 90
  close_violations_on_expiration = true
  description                    = each.value.description
  runbook_url                    = each.value.runbook_url != "" ? each.value.runbook_url : null
  nrql {
    query = each.value.query
  }

  critical {
    operator              = "above"
    threshold             = 0
    threshold_duration    = 60
    threshold_occurrences = "ALL"
  }
}

resource "newrelic_entity_tags" "this" {
  for_each = { for f in var.app_alert : f.name => f }

  guid = newrelic_nrql_alert_condition.this[each.value.name].entity_guid

  tag {
    key    = "kind"
    values = [each.value.tag]
  }
}

app_alert moduleでは、JSONファイルのapp_alert項目で設定した値をもとに、アラートを作成しています。 各アラートで値が変わらない引数については、定数を設定しています。また、アプリログのアラートについては、1つでも検知したらアラート通知をさせたいので、閾値もabove 0で定数化しています。

modules/system_alert/main.tf

# AWSリソースのアラート設定

resource "newrelic_nrql_alert_condition" "this" {
  for_each = { for f in var.system_alert : f.name => f }

  name                           = each.value.name
  policy_id                      = var.policy_id
  aggregation_method             = each.value.aggregation_method
  aggregation_window             = each.value.aggregation_method == "event_flow" ? 60 : null
  aggregation_delay              = each.value.aggregation_method == "event_flow" ? 0 : null
  aggregation_timer              = each.value.aggregation_method == "event_timer" ? 60 : null
  expiration_duration            = each.value.aggregation_method == "event_timer" ? 90 : null
  close_violations_on_expiration = each.value.aggregation_method == "event_timer" ? true : null
  description                    = each.value.description
  runbook_url                    = each.value.runbook_url != "" ? each.value.runbook_url : null
  nrql {
    query = each.value.query
  }

  dynamic "critical" {
    for_each = each.value.operator_critical != null ? [1] : []
    content {
      operator              = each.value.operator_critical
      threshold             = each.value.threshold_critical
      threshold_duration    = each.value.aggregation_duration
      threshold_occurrences = "ALL"
    }
  }

  dynamic "warning" {
    for_each = each.value.operator_warning != null ? [1] : []
    content {
      operator              = each.value.operator_warning
      threshold             = each.value.threshold_warning
      threshold_duration    = each.value.aggregation_duration
      threshold_occurrences = "ALL"
    }
  }
}

resource "newrelic_entity_tags" "this" {
  for_each = { for f in var.system_alert : f.name => f }

  guid = newrelic_nrql_alert_condition.this[each.value.name].entity_guid

  tag {
    key    = "kind"
    values = [each.value.tag]
  }
}

system_alert moduleでは、JSONファイルのsystem_alert項目で設定した値をもとに、アラートを作成します。 システムアラートについては、閾値は定数化せず、warningcriticalの2段階でアラートを設定できるよう、変数化しています。

modules/workflow/main.tf

resource "newrelic_alert_policy" "this" {
  name                = var.group_name
  incident_preference = "PER_CONDITION_AND_TARGET"
}

resource "newrelic_notification_channel" "app" {
  name           = var.group_name
  type           = "SLACK"
  destination_id = var.destination_id
  product        = "IINT"

  property {
    key   = "channelId"
    value = "AAAAAAAAAAA" #app_errorチャンネルのID
  }

  property {
    key   = "customDetailsSlack"
    value = "@${var.group_name}"
  }
}

resource "newrelic_notification_channel" "system_error" {
  name           = var.group_name
  type           = "SLACK"
  destination_id = var.destination_id
  product        = "IINT"

  property {
    key   = "channelId"
    value = "BBBBBBBBBBB" #system_errorチャンネルのID
  }

  property {
    key   = "customDetailsSlack"
    value = "@${var.group_name} @sre"
  }
}

resource "newrelic_notification_channel" "system_warning" {
  name           = var.group_name
  type           = "SLACK"
  destination_id = var.destination_id
  product        = "IINT"

  property {
    key   = "channelId"
    value = "CCCCCCCCCCC" #system_warningチャンネルのID
  }
}

resource "newrelic_workflow" "app" {
  name                  = "# app_error_${var.group_name}"
  muting_rules_handling = "NOTIFY_ALL_ISSUES"

  issues_filter {
    name = "Filter-name"
    type = "FILTER"

    predicate {
      attribute = "accumulations.tag.kind"
      operator  = "CONTAINS"
      values    = ["app"]
    }

    predicate {
      attribute = "labels.policyIds"
      operator  = "EXACTLY_MATCHES"
      values    = [newrelic_alert_policy.this.id]
    }

    predicate {
      attribute = "priority"
      operator  = "EQUAL"
      values    = ["CRITICAL"]
    }
  }

  enrichments {
    nrql {
      name = "Log Message"
      configuration {
        query = "SELECT message FROM Log WHERE messageId = {{accumulations.tag.messageId}}"
      }
    }
  }

  destination {
    channel_id            = newrelic_notification_channel.app.id
    notification_triggers = ["ACTIVATED", "ACKNOWLEDGED"]
  }
}

resource "newrelic_workflow" "system_error" {
  name                  = "# system_error_${var.group_name}"
  muting_rules_handling = "NOTIFY_ALL_ISSUES"

  issues_filter {
    name = "Filter-name"
    type = "FILTER"

    predicate {
      attribute = "accumulations.tag.kind"
      operator  = "CONTAINS"
      values    = ["system"]
    }

    predicate {
      attribute = "labels.policyIds"
      operator  = "EXACTLY_MATCHES"
      values    = [newrelic_alert_policy.this.id]
    }

    predicate {
      attribute = "priority"
      operator  = "EQUAL"
      values    = ["CRITICAL"]
    }
  }

  destination {
    channel_id = newrelic_notification_channel.system_error.id
  }
}

resource "newrelic_workflow" "system_warning" {
  name                  = "# system_warning_${var.group_name}"
  muting_rules_handling = "NOTIFY_ALL_ISSUES"

  issues_filter {
    name = "Filter-name"
    type = "FILTER"

    predicate {
      attribute = "accumulations.tag.kind"
      operator  = "CONTAINS"
      values    = ["system"]
    }

    predicate {
      attribute = "labels.policyIds"
      operator  = "EXACTLY_MATCHES"
      values    = [newrelic_alert_policy.this.id]
    }

    predicate {
      attribute = "priority"
      operator  = "EQUAL"
      values    = ["HIGH"]
    }
  }

  destination {
    channel_id = newrelic_notification_channel.system_warning.id
  }
}

workflow moduleでは、上の画像にあったアラート通知の流れを実現できるようにリソースを作成しています。

modules/workflow/output.tf

output "alert_policy_id" {
  description = "The ID of the New Relic alert policy."
  value       = newrelic_alert_policy.this.id
}

workflow moduleにて作成したnewrelic_alert_policyリソースは、app_alert moduleとsystem_alert moduleにて参照する必要があるので、アウトプットしています。

provider.tf

terraform {
  required_providers {
    newrelic = {
      source = "newrelic/newrelic"
    }
  }
}

providerは各moduleで共通の設定です。

アラート設定テンプレート導入の効果

アラート設定テンプレート導入当初は、開発全体会で共有したのち、各開発チームの担当者とペアプロをしながら一緒に設定を行っていきました。設定が直感的で分かりやすいと感じてもらうことが多く、今ではほとんどの開発チームで自主的にアラート設定を管理してもらっています!

また、最近では他の機能も使ってもらう機会が増え、開発チーム全体としてNew Relicを活用したサービスの運用・改善の意識が高まっていると実感しています✨

今後も開発チームとともにNew Relicを活用し、サービス改善に取り組んでいきたいです!

まとめ

今回は、New Relicのアラート設定テンプレート化の取り組みについてご紹介しました!
アラート設定テンプレートの作成方法は、さまざまあると思いますので一例として参考にしていただけますと幸いです。

それでは、2023年のコドモンアドベントカレンダーもあと2日🎄
24日目はお子さん溺愛早起きマッチョ、関口さんです! お楽しみに〜!!