はじめに
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桁の値を設定する。
api_keiには、ログイン後の左下のメニューで「API Keys」を選択し、
次の画面で表示されるAPI KeyのうちType: INGEST-LISENCE
のValueを使おう。
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
のパネルをクリックしてみよう。
以下のように、自身のAWSのリソースの情報が表示されれば成功だ。
実際に動いているメトリクスを見る
ここまでの確認では面白くないので、次回のトピック(監視アラートを設定する)に繋げるために実際に動いているメトリクスを確認しよう。
今回、サンプルとして以下のように一定割合でエラーを返す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関数の情報を表示してみよう。
すると、以下のようにサマリを確認することができる。
これで、New RelicでAWSの情報が参照できるようになった!
次回