New Relic FlexをECS Fargateで実行しカスタム イベントを送信する

こんにちは。香田です。

今回はNew Relic FlexをECS Fargateで実行しカスタム イベントを送信する方法を紹介していきます。

New Relicのアカウントについては、必要に応じて下記を参考にサインアップしてみてください。

New Relic Flexについて

New Relci Flexについて簡単に紹介します。

New Relic Flexを利用すると、特定のアプリケーション向けではなく、HTTP、ファイル、シェルコマンド等を利用し任意のデータをNew Relicへ送信することが可能となります。

New Relic Infrastructure Agentのプラグインとして組み込まれている為、Flex用の設定ファイルを追加することで利用可能となります。

ECS Fargate環境について

今回構築するNew Relic Flexは下記のようにECS Fargate上でコンテナとして実行していきます。

ECS FargateなどAWS環境の構築は、Terraformを利用し各リソースを作成していきます。

New Relic Flex コンテナイメージの作成

はじめにNew Relic Flexコンテナ用のDockerfileを作成します。

  • Dockerfile
FROM newrelic/nri-ecs:1.8.0

ADD ./flex.yml /etc/newrelic-infra/integrations.d/

New Relic Flexの設定ファイルを作成します。

New Relic Flexの設定ファイル例は、GitHub リポジトリのほうも参考にしてみてください。

Flex 設定ファイルの例

  • flex.yml
integrations:
  - name: nri-flex
    interval: 60s
    timeout: 5s
    config:
      name: sample
      apis:
        - event_type: JsonApiCallSample
          url: https://jsonplaceholder.typicode.com/todos/1
          method: GET

New Relic Flex 検証用のDocker Composeを作成

New Relic Flexの検証用にDocker Compose環境を用意します。

  • docker-compose.yml
version: "3"
services:
  newrelic-flex:
    container_name: newrelic-flex
    build:
      context: .
      dockerfile: ./Dockerfile
    image: nri-ecs-custom:1.8.0
    entrypoint: [ "/bin/bash" ]
    volumes:
      - ".:/workspace"
    working_dir: /workspace
    tty: true

コンテナ起動しログイン

$ docker-compose up -d
$ docker-compose exec newrelic-flex bash

下記コマンドを実行することで、New Relic Flexで送信されるカスタムイベントが確認可能です。

コマンド実行してもNew Relicへデータは転送されない為、開発時に有効かと思います。

$ /var/db/newrelic-infra/newrelic-integrations/bin/nri-flex \
--config_file flex.yml \
--verbose --pretty

コンテナからログアウト

$ exit

Terraform AWSプロバイダの設定

Terraformを利用し環境構築を進めてていきます。AWSリソース作成用にAWSプロバイダを設定します。

provider "aws" {
  region = "ap-northeast-1"
}

IAM ロールの作成

ECSで利用するIAM ロールとして、「ECS タスク実行 IAM ロール」と「タスク用のIAM ロール」を作成します。

ECS タスク実行 IAM ロールを作成します。

resource "aws_iam_role" "ecs_task_execution_role" {
  name = "newrelic-cluster-ecs-task-execution-role"
  path = "/"
  assume_role_policy = jsonencode({
    "Version" : "2012-10-17",
    "Statement" : [
      {
        "Action" : "sts:AssumeRole",
        "Principal" : {
          "Service" : "ecs-tasks.amazonaws.com"
        },
        "Effect" : "Allow"
      }
    ]
  })
}

resource "aws_iam_role_policy" "ecs_task_execution_role" {
  name = "newrelic-cluster-ecs-task-execution-role"
  role = aws_iam_role.ecs_task_execution_role.name

  policy = jsonencode({
    "Version" : "2012-10-17",
    "Statement" : [
      {
        "Effect" : "Allow",
        "Action" : [
          "ecr:GetAuthorizationToken",
          "ecr:BatchCheckLayerAvailability",
          "ecr:GetDownloadUrlForLayer",
          "ecr:BatchGetImage",
          "logs:CreateLogStream",
          "logs:PutLogEvents"
        ],
        "Resource" : "*"
      },
      {
        "Effect" : "Allow",
        "Action" : [
          "ssm:GetParameters"
        ],
        "Resource" : "*"
      }
    ]
  })
}

タスク用のIAM ロールを作成します。

resource "aws_iam_role" "ecs_task_role" {
  name = "newrelic-cluster-ecs-task-role"
  path = "/"
  assume_role_policy = jsonencode({
    "Version" : "2012-10-17",
    "Statement" : [
      {
        "Action" : "sts:AssumeRole",
        "Principal" : {
          "Service" : "ecs-tasks.amazonaws.com"
        },
        "Effect" : "Allow"
      }
    ]
  })
}

resource "aws_iam_role_policy" "ecs_task_role" {
  name = "newrelic-cluster-ecs-task-role"
  role = aws_iam_role.ecs_task_role.name

  policy = jsonencode({
    "Version" : "2012-10-17",
    "Statement" : [
      {
        "Effect" : "Allow",
        "Action" : [
          "ssmmessages:CreateControlChannel",
          "ssmmessages:CreateDataChannel",
          "ssmmessages:OpenControlChannel",
          "ssmmessages:OpenDataChannel",
        ],
        "Resource" : "*"
      },
      {
        "Effect" : "Allow",
        "Action" : [
          "logs:DescribeLogGroups",
          "logs:CreateLogStream",
          "logs:DescribeLogStreams",
          "logs:PutLogEvents"
        ],
        "Resource" : "*"
      }
    ]
  })
}

CloudWatch Logsの作成

New Relic FlexコンテナとECS Exec実行時のログ出力先としてCloudWatch Logsを作成します。

resource "aws_cloudwatch_log_group" "ecs_exec" {
  name              = "/aws/ecs/newrelic-cluster/ecs-exec"
  retention_in_days = 14
}

resource "aws_cloudwatch_log_group" "newrelic_flex" {
  name              = "/aws/ecs/newrelic-cluster/newrelic-flex"
  retention_in_days = 14
}

SSM Parameter Storeの作成

New Relic Flexコンテナで利用するライセンスキーを設定します。

resource "aws_ssm_parameter" "newrelic_license_key" {
  name  = "/newrelic/license_key"
  type  = "SecureString"
  value = "New Relic ライセンスキー"
}

ECR リポジトリの作成

コンテナイメージの保存先としてECRを作成します。

resource "aws_ecr_repository" "newrelic_flex" {
  name                 = "newrelic-flex"
  image_tag_mutability = "MUTABLE"

  image_scanning_configuration {
    scan_on_push = true
  }
}

ECS クラスターの作成

ECS クラスターを作成します。

resource "aws_ecs_cluster" "newrelic" {
  name = "newrelic-cluster"

  setting {
    name  = "containerInsights"
    value = "enabled"
  }

  configuration {
    execute_command_configuration {
      logging = "OVERRIDE"
      log_configuration {
        cloud_watch_log_group_name = aws_cloudwatch_log_group.ecs_exec.name
      }
    }
  }
}

resource "aws_ecs_cluster_capacity_providers" "newrelic" {
  cluster_name       = aws_ecs_cluster.newrelic.name
  capacity_providers = ["FARGATE", "FARGATE_SPOT"]
}

ECS タスク定義の作成

New Relic Flexコンテナ用のタスク定義を作成します。

data "aws_region" "current" {}

data "aws_caller_identity" "current" {}

resource "aws_ecs_task_definition" "newrelic_flex" {
  family                   = "newrelic-flex"
  requires_compatibilities = ["FARGATE"]
  network_mode             = "awsvpc"
  cpu                      = "256"
  memory                   = "512"
  task_role_arn            = aws_iam_role.ecs_task_role.arn
  execution_role_arn       = aws_iam_role.ecs_task_execution_role.arn
  container_definitions = jsonencode([
    {
      "name" : "newrelic-flex",
      "image" : "${aws_ecr_repository.newrelic_flex.repository_url}:latest",
      "cpu" : 256,
      "memoryReservation" : 512,
      "secrets" : [
        {
          "name" : "NRIA_LICENSE_KEY",
          "valueFrom" : aws_ssm_parameter.newrelic_license_key.arn
        }
      ],
      "environment" : [
        {
          "name" : "NRIA_OVERRIDE_HOST_ROOT",
          "value" : ""
        },
        {
          "name" : "NRIA_IS_SECURE_FORWARD_ONLY",
          "value" : "true"
        },
        {
          "name" : "FARGATE",
          "value" : "true"
        },
        {
          "name" : "ENABLE_NRI_ECS",
          "value" : "true"
        },
        {
          "name" : "NRIA_PASSTHROUGH_ENVIRONMENT",
          "value" : "ECS_CONTAINER_METADATA_URI,ENABLE_NRI_ECS,FARGATE"
        }
      ],
      "essential" : true,
      "linuxParameters" : {
        "initProcessEnabled" : true
      }
      "logConfiguration" : {
        "logDriver" : "awslogs",
        "options" : {
          "awslogs-group" : aws_cloudwatch_log_group.newrelic_flex.name,
          "awslogs-stream-prefix" : "newrelic-flex",
          "awslogs-region" : data.aws_region.current.name
        }
      }
    }
  ])
}

ECS サービスの作成

New Relic Flexコンテナ用のECS サービスを作成します。

ECS サービスで利用するサブネット、セキュリティグループは、デフォルトVPCのものを利用しています。

data "aws_vpc" "default" {
  default = true
}

data "aws_security_group" "default" {
  name   = "default"
  vpc_id = data.aws_vpc.default.id
}

data "aws_subnets" "default" {
  filter {
    name   = "vpc-id"
    values = [data.aws_vpc.default.id]
  }
}

resource "aws_ecs_service" "newrelic_flex" {
  name                   = "newrelic-flex"
  cluster                = "newrelic-cluster"
  desired_count          = 1
  enable_execute_command = true
  task_definition        = aws_ecs_task_definition.newrelic_flex.arn

  capacity_provider_strategy {
    capacity_provider = "FARGATE"
    weight            = 1
  }

  network_configuration {
    subnets = data.aws_subnets.default.ids
    security_groups = [
      data.aws_security_group.default.id
    ]
    assign_public_ip = true
  }

  lifecycle {
    ignore_changes = [
      desired_count,
    ]
  }
}

AWSリソースの作成

terraform applyを実行しAWSリソースを作成します。

$ terraform init
$ terraform apply

ECRへイメージ登録

イメージをビルドしECRへ登録していきます。

ECRへログインします。

$ export ECR_URI_BASE=$(aws sts get-caller-identity --query Account --output text).dkr.ecr.ap-northeast-1.amazonaws.com
$ aws ecr get-login-password | docker login --username AWS --password-stdin $ECR_URI_BASE

ビルドしECR リポジトリへ登録します。

$ docker build -t newrelic-flex:latest .
$ docker tag newrelic-flex:latest $ECR_URI_BASE/newrelic-flex:latest
$ docker push $ECR_URI_BASE/newrelic-flex:latest

New Relic Query builderで確認

しばらくするとNew Relicへデータ送信され確認できるはずです。

New RelicのQuery builderへアクセスします。

下記のクエリを実行し、New Relic Flexの設定ファイルで定義したカスタムイベントが取得されているか確認します。

SELECT event_type,api.StatusCode,title,userId FROM JsonApiCallSample SINCE 10 minutes ago

下記のようにデータが取得できていれば成功です。

New Relic Flexで環境変数の利用について

New Relic Flexで環境変数を元にカスタムイベントを送信したい場合があるかと思います。

環境変数を利用する場合、Flexの設定ファイルへ利用したい環境変数のprefixに$$を設定します。

  • 例) Postgres Databaseよりデータ取得
integrations:
  - name: nri-flex
    interval: 60s
    timeout: 5s
    config:
      name: sample
      apis:
        - event_type: PgStatActivity
          database: postgres
          db_conn: dbname=$$DB_NAME user=$$DB_USER host=$$DB_HOST sslmode=disable port=5432
          logging:
            open: true
          db_queries:
            - name: PgStatActivity
              run: select * FROM pg_stat_activity

ECS タスク定義に追加したい環境変数を設定し、追加した環境変数をNRIA_PASSTHROUGH_ENVIRONMENTに追加することで、環境変数が利用可能となります。

      "environment" : [
        {
          "name" : "DB_HOST",
          "value" : "xxxx"
        },

        ...

        {
          "name" : "NRIA_PASSTHROUGH_ENVIRONMENT",
          "value" : "ECS_CONTAINER_METADATA_URI,ENABLE_NRI_ECS,FARGATE,DB_HOST"
        },

さいごに

New Relic FlexをECS Fargateで実行しカスタム イベントを送信する方法如何でしたでしょうか?

データベースに登録されているデータや内部APIのHTTPステータスなど、様々なデータを取得したい場合メリットは大きいのではないでしょうか。

最後までご覧いただきありがとうございます。

SNSでもご購読できます。