3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

New RelicによるAWSの監視をTerraformで自動化する(第1回: メトリクスのモニタリング)

Last updated at Posted at 2024-10-06

はじめに

AWSの監視は、Amazon CloudWatchシリーズで機能が一通り揃っているのでそちらを使うのがセオリーではあるものの、New RelicやDatadogといったオブザーバビリティによる監視に特化したSaaSを使うことも多くなってきている(AWSの標準機能でもオブザーバビリティに対応しているものはある)。

本記事では、New Relicの監視をTerraformを使ってAWSと一貫して構築を自動化する方法を構築している。

それぞれのリソース等のより詳細な情報は、以下の公式ドキュメント類を確認するのが良いが、本記事が、New Relic+AWSを構築する初めの一歩の一助になればと思う。

なお、本記事で設定できる内容は、New Relicの無料利用でできる範囲に閉じている。
実際にプロダクション環境での実運用レベルで扱うには課金は必須になるが、使用したことなくて、どんなものかを試すときに参考にしていただきたい。

Terraform Providerの設定

さて、まずはTerraformでNew Relicを動作させる環境から構築する。

Providerを以下のように設定してterraform initで取得できるようにしよう。

account_idには、New Relicのユーザ登録後、ログイン後のURLの以下の部分で参照できる7桁の値を設定する。

キャプチャ1.png

api_keiには、ログイン後の左下のメニューで「API Keys」を選択し、

キャプチャ2.png

次の画面で表示されるAPI KeyのうちType: INGEST-LISENCEのValueを使おう。

image.png

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

provider "newrelic" {
  account_id = var.newrelic_account_id
  api_key    = var.newrelic_api_key
  region     = "US"
}

AWSからメトリクスをNew Relicに送る

さて、まずは監視の第一歩は、AWSからNew Relicに情報を送る部分からだ。
New Relicでは、PUSH型とPULL型の2種類があるが、今回はよりディレイの少ないPUSH型の方法で自動化する。

PUSH型とPULL型のより詳細な差異については、以下のドキュメントを参照していただきたい。
※PUSH型の方がどの比較軸も優れてるよ、と言っているようだ。

New Relic側の設定

以下のようにNew Relic側でのメトリクスを受ける設定を行う。

resource "newrelic_cloud_aws_link_account" "example" {
  depends_on = [aws_iam_role_policy.metric_stream]

  arn                    = aws_iam_role.newrelic.arn
  metric_collection_mode = "PUSH"
  name                   = "example"
}

また、AWS側にもNew Relic用のIAMロールを作成してリソースにアクセス可能な状態にしておく。
えっ、アカウントID書いちゃってよいの?と思われるかもしれないが、これはNew Relicが運用されているAWSアカウントであるため問題ない。以下の公式のサイトにも書かれている設定値だ。

resource "aws_iam_role" "newrelic" {
  name               = local.iam_newrelic_role_name
  assume_role_policy = data.aws_iam_policy_document.newrelic_assume.json
}

data "aws_iam_policy_document" "newrelic_assume" {
  statement {
    actions = ["sts:AssumeRole"]
    effect  = "Allow"

    principals {
      type        = "AWS"
      identifiers = ["754728514883"]
    }

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

resource "aws_iam_role_policy_attachment" "newrelic_readonly" {
  policy_arn = "arn:aws:iam::aws:policy/ReadOnlyAccess"
  role       = aws_iam_role.newrelic.id
}

AWS側の設定

AWS
AWS側では、Amazon ClooudWatchのメトリクスストリームの機能を使う。
output_formatは、"opentelemetry0.7"固定で問題ない。

最終的にストリームの値はAmazon Data Firehoseを通してNew Relicに送信するので、今回作るAmazon Data Firehoseのリソースに対するアクセス権をAmazon ClooudWatchのメトリクスストリームに渡しておく。

resource "aws_cloudwatch_metric_stream" "newrelic" {
  name          = local.cloudwatch_metric_stream_name
  role_arn      = aws_iam_role.metric_stream.arn
  firehose_arn  = aws_kinesis_firehose_delivery_stream.newrelic.arn
  output_format = "opentelemetry0.7"
}

resource "aws_iam_role" "metric_stream" {
  name               = local.iam_metric_stream_role_name
  assume_role_policy = data.aws_iam_policy_document.metric_stream_assume.json
}

data "aws_iam_policy_document" "metric_stream_assume" {
  statement {
    effect = "Allow"

    actions = [
      "sts:AssumeRole",
    ]

    principals {
      type = "Service"
      identifiers = [
        "streams.metrics.cloudwatch.amazonaws.com",
      ]
    }
  }
}

resource "aws_iam_role_policy" "metric_stream" {
  name   = local.iam_metric_stream_policy_name
  role   = aws_iam_role.metric_stream.id
  policy = data.aws_iam_policy_document.metric_stream_custom.json
}

data "aws_iam_policy_document" "metric_stream_custom" {
  statement {
    effect = "Allow"

    actions = [
      "firehose:PutRecord",
      "firehose:PutRecordBatch",
    ]

    resources = [
      "${aws_kinesis_firehose_delivery_stream.newrelic.arn}",
    ]
  }
}

前述の通り、メトリクスストリームは、Amazon Data Firehoseを使ってNew Relic指定のエンドポイントに送信する。
以下のように設定しておこう。

resource "aws_kinesis_firehose_delivery_stream" "newrelic" {
  name        = local.firehose_stream_name
  destination = "http_endpoint"

  http_endpoint_configuration {
    url                = "https://aws-api.newrelic.com/cloudwatch-metrics/v1"
    name               = "New Relic"
    access_key         = newrelic_api_access_key.example.key
    buffering_size     = 1
    buffering_interval = 60
    role_arn           = aws_iam_role.firehose.arn
    s3_backup_mode     = "FailedDataOnly"

    request_configuration {
      content_encoding = "GZIP"
    }

    s3_configuration {
      role_arn           = aws_iam_role.firehose.arn
      bucket_arn         = aws_s3_bucket.firehose_failed.arn
      buffering_size     = 1
      buffering_interval = 60
      compression_format = "GZIP"
    }

    cloudwatch_logging_options {
      enabled         = true
      log_group_name  = aws_cloudwatch_log_group.firehose.name
      log_stream_name = aws_cloudwatch_log_stream.firehose.name
    }
  }
}

ログ用のAmazon S3とAmazon CloudWatch Logsは以下のように設定し、Amazon Data Firehoseにアクセス権限を渡す。

resource "aws_s3_bucket" "firehose_failed" {
  bucket = local.s3_bucket_name
}

resource "aws_s3_bucket_ownership_controls" "firehose_failed" {
  bucket = aws_s3_bucket.firehose_failed.id

  rule {
    object_ownership = "BucketOwnerEnforced"
  }
}

resource "aws_s3_bucket_public_access_block" "firehose_failed" {
  bucket = aws_s3_bucket.firehose_failed.id

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

resource "aws_cloudwatch_log_group" "firehose" {
  name = "/aws/kinesisfirehose/${local.firehose_stream_name}"
}

resource "aws_cloudwatch_log_stream" "firehose" {
  log_group_name = aws_cloudwatch_log_group.firehose.name
  name           = "DestinationDelivery"
}

Amazon Data Firehoseが作ったリソースそれぞれに書き込めるように以下のようにIAMロールを設定する。

resource "aws_iam_role" "firehose" {
  name               = local.iam_firehose_role_name
  assume_role_policy = data.aws_iam_policy_document.firehose_assume.json
}

data "aws_iam_policy_document" "firehose_assume" {
  statement {
    effect = "Allow"

    actions = [
      "sts:AssumeRole",
    ]

    principals {
      type = "Service"
      identifiers = [
        "firehose.amazonaws.com",
      ]
    }
  }
}

resource "aws_iam_role_policy" "firehose" {
  name   = local.iam_firehose_policy_name
  role   = aws_iam_role.firehose.id
  policy = data.aws_iam_policy_document.firehose_custom.json
}

data "aws_iam_policy_document" "firehose_custom" {
  statement {
    effect = "Allow"

    actions = [
      "logs:PutLogEvents",
    ]

    resources = [
      "${aws_cloudwatch_log_group.firehose.arn}:log-stream:*",
    ]
  }

  statement {
    effect = "Allow"

    actions = [
      "s3:GetObject",
      "s3:GetObjectVersion",
      "s3:GetBucketVersioning",
      "s3:PutObject",
    ]

    resources = [
      "${aws_s3_bucket.firehose_failed.arn}",
      "${aws_s3_bucket.firehose_failed.arn}/*"
    ]
  }
}

これで準備は完了だ。terraform applyしてから、実際にメトリクスをモニタリングしてみよう。

いざ、動かす!

New Relicのコンソールの左のメニュー画面からAll Capabilities⇒次の画面でAWSを検索して、表示されたAmazon Web Servicesのパネルをクリックしてみよう。

キャプチャ5.png

以下のように、自身のAWSのリソースの情報が表示されれば成功だ。

キャプチャ6.png

実際に動いているメトリクスを見る

ここまでの確認では面白くないので、次回のトピック(監視アラートを設定する)に繋げるために実際に動いているメトリクスを確認しよう。

今回、サンプルとして以下のように一定割合でエラーを返すLambdaを作成した。
インラインでスクリプトを書くのはやや反則だが、おためしなのでご容赦を。

data "archive_file" "lambda_example" {
  type        = "zip"
  output_path = "../output/lambda_example.zip"

  source {
    filename = "lambda_example.py"
    content  = <<EOF
import json
import random

def lambda_handler(event, context):
  random_value = random.random()

  if random_value < 0.5:
    return {
      'statusCode': '200',
      'body': json.dumps({'message': 'OK.'}),
    }
  else:
    return {
      'statusCode': '500',
      'body': json.dumps({'message': 'NG.'}),
    }
EOF
  }
}

resource "aws_lambda_function" "example" {
  depends_on = [
    aws_cloudwatch_log_group.lambda,
  ]

  function_name    = local.lambda_function_name
  filename         = data.archive_file.lambda_example.output_path
  role             = aws_iam_role.lambda.arn
  handler          = "lambda_example.lambda_handler"
  source_code_hash = data.archive_file.lambda_example.output_base64sha256
  runtime          = "python3.10"

  memory_size = 128
  timeout     = 60

  tags = {
    newrelic_integration = true
  }
}

resource "aws_lambda_function_url" "example" {
  function_name      = aws_lambda_function.example1.function_name
  authorization_type = "NONE"
}

resource "aws_cloudwatch_log_group" "lambda" {
  name              = "/aws/lambda/${local.lambda_function_name}"
  retention_in_days = 3
}

resource "aws_iam_role" "lambda" {
  name               = local.iam_lambda_role_name
  assume_role_policy = data.aws_iam_policy_document.lambda_assume.json
}

data "aws_iam_policy_document" "lambda_assume" {
  statement {
    effect = "Allow"

    actions = [
      "sts:AssumeRole",
    ]

    principals {
      type = "Service"
      identifiers = [
        "lambda.amazonaws.com",
      ]
    }
  }
}

resource "aws_iam_role_policy" "lambda" {
  name   = local.iam_lambda_policy_name
  role   = aws_iam_role.lambda.id
  policy = data.aws_iam_policy_document.lambda_custom.json
}

data "aws_iam_policy_document" "lambda_custom" {
  statement {
    effect = "Allow"

    actions = [
      "logs:CreateLogGroup",
      "logs:CreateLogStream",
      "logs:PutLogEvents",
    ]

    resources = [
      aws_cloudwatch_log_group.lambda.arn,
      "${aws_cloudwatch_log_group.lambda.arn}:log-stream:*",
    ]
  }
}

output "lambda_function_url" {
  value = aws_lambda_function_url.example.function_url
}

これをterraform applyして、以下のコマンドを数分流して、実際にメトリクスがどう変化するかを確認してみよう。

$ while true; do curl (TerraformのOutputのURL); echo; sleep 1; done

数分流した後に、Lambda functionsで作ったLambda関数の情報を表示してみよう。

キャプチャ7.png

すると、以下のようにサマリを確認することができる。

image.png

これで、New RelicでAWSの情報が参照できるようになった!

次回

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?