Amazon ECS on FargateでS3とNew Relicに同時にログを送る方法
はじめに
この記事はNew Relic 使ってみた情報をシェアしよう! by New Relic Advent Calendar 2023
のシリーズ3、7日目のものです😎
クルマ🚖のサブスクの会社でインフラ屋さんをしている@exitjukiです
ECS利用時にS3とNew Relicに同時にアプリケーションログを送りたいケースは多いと思います
色々方法はありますが、今回はNew Relicでのログ/トレース/メトリクスを紐づけることを前提としたNew Relicへのログ送信方法について以下の2つの方法を紹介します
- New Relic Agent + Fluent Bitを利用した方法
- Fluent Bit に集約して送信する方法
以下補足です。
- Java 17を利用
- New Relic Agentを利用
- 今回はJava Agentを利用しています
- Fluent BitはECSのサイドカーとして動作
New Relic Agent + Fluent Bitを利用した方法
New Relic Agentでアプリケーションログ、メトリクス、トレースを送信しつつFluent Bitでアプリケーションの標準出力を拾ってS3とその他に送る方法です
Fluent BitそのもののログはCloudWatch Logsのみに送ってます
注意点としてはNew Relic Agent経由で送信したログとFluent Bitで送信したログの属性が異なることです
New Relic Agent経由のログはNew Relic側で属性をカスタマイズされるためアプリケーション側で付与した属性が削られてしまうことがあります
対策としては後述のFluent Bitに集約する方法もしくはAPM Agentの設定変更になります。Javaの場合は以下のドキュメントを参考にしてください。Agentに手を入れる場合、設定を管理する箇所が一つ多くなってしまうことには注意してください
New Relicではmessage部分のみ確認できればOKということであればこのままの利用でも問題ないです
New Relic Java Agentをインストールする
今回はNew Relicドキュメントを参考にGradleでインストールしました
-
build.gradleの設定
- エージェントダウンロード用のプラグインを追加
plugins { id "de.undercouch.download" version "5.3.0" }
- エージェントのダウンロードと解凍
task downloadNewrelic(type: Download) { mkdir 'newrelic' src 'https://download.newrelic.com/newrelic/java-agent/newrelic-agent/current/newrelic-java.zip' dest file('newrelic') } task unzipNewrelic(type: Copy) { from zipTree(file('newrelic/newrelic-java.zip')) into rootDir }
- jibでコンテナ化、最後にエージェントのダウンロード部分との依存関係の設定もしておく
jib { from { image = "eclipse-temurin:17-alpine" } container { mainClass = 'com.hoge' jvmFlags = [ '-javaagent:/app/newrelic/newrelic.jar' ] } extraDirectories { paths { path { from = 'newrelic/newrelic' into = '/app/newrelic' } } } } // New Relic Agentインストールを依存づける tasks.jib.dependsOn unzipNewrelic
ビルドしたコンテナはECRへアップロードしておきます
ECSのタスク定義の設定
コンテナ部分にピックアップします
- applications部分
{
"name": "application",
"image": "11112222333.dkr.ecr.us-west-2.amazonaws.com/hoge/application:latest",
"cpu": 128,
"memoryReservation": 256,
"portMappings": [
{"containerPort": 8080, "hostPort": 8080, "protocol": "tcp"},
{"containerPort": 8081, "hostPort": 8081, "protocol": "tcp"}
],
"essential": true,
"environment": [
{"name": "NEW_RELIC_APP_NAME", "value": "hoge-app"}
{"name": "NEW_RELIC_AGENT_ENABLED", "value": "true"}
],
"secrets": [
{"name": "NEW_RELIC_LICENSE_KEY", "valueFrom": "arn:aws:ssm:us-west-2:11112222333:parameter/new_relic_license_key"}
],
"mountPoints": [],
"volumesFrom": [],
"logConfiguration": {"logDriver": "awsfirelens"}
},
ライセンスキーはパラメータストアから取得するようにしています。New Relicで使用する環境変数は最小限にしてます。NEW_RELIC_APP_NAME
はNew Relicで表示されるアプリケーション名なので識別しやすい名前がおすすめです
これだけでNew Relicでログ/トレース/メトリクスを取得することができます
Fluent BitでS3にログを送る部分
今回はAWS提供のイメージを使用します
まずはECSのタスク定義でコンテナを追加します- log部分
{
"name": "log",
"image": "public.ecr.aws/aws-observability/aws-for-fluent-bit:init-latest",
"cpu": 64,
"memoryReservation": 128,
"portMappings": [],
"essential": true,
"environment": [
{"name": "LOG_BUCKET_NAME", "value": "hogehoge-application-bucket"},
{"name": "UPLOAD_TIMEOUT", "value": "1m"},
{"name": "S3_KEY_FORMAT", "value": "/app/%Y/%m/%d/%H/%M/%S"},
{"name": "CONTAINER_NAME", "value": "application"},
{"name": "REGION_NAME", "value": "us-west-2"},
{
"name": "aws_fluent_bit_init_s3_1",
"value": "arn:aws:s3:::hogehoge-application-bucket/fluent-bit-custom.conf"
}
],
"mountPoints": [],
"volumesFrom": [],
"user": "0:1337",
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/app/log",
"awslogs-region": "us-west-2",
"awslogs-stream-prefix": "firelens"
}
},
"firelensConfiguration": {
"type": "fluentbit"
}
}
init*の付くイメージを使用することでFluent BitのコンフィグファイルをS3から読み込めるようになるので、それを利用するようにしています
なお、タスクロールでS3へのアクセス権限が必要なのでそこは付与しておく必要があります
{
"actions": [
"s3:GetObject",
"s3:GetBucketLocation"
],
"resources": [
"arn:aws:s3:::*"
]
}
ECSの環境変数もそのまま利用できるため以下のようにS3出力部分を設定します
[OUTPUT]
Name s3
Match *
bucket ${LOG_BUCKET_NAME}
region ${REGION_NAME}
upload_timeout ${UPLOAD_TIMEOUT}
s3_key_format ${S3_KEY_FORMAT}
compression gzip
use_put_object true
これでNew Relicにログ/トレース/メトリクスを送りながら、S3にもログを送ることができました
ただしこれではNew RelicとS3とで送られるログに違いが出てきてしまうので少し気持ち悪さが残ります。そのため、次にFluent Bitでまとめて送る方法について紹介します
Fluent Bit に集約して送信する方法
New RelicはECSからNew Relicにログを送るサイドカーコンテナを用意してくれています
しかしこのコンテナはNew Relic専用であり、S3など他の場所に同時にログを送ることはできませんそのため、
New Relic提供のFluent Bitに設定を追加してS3にもログを送れるようにします。
New RelicとS3にログを送るFluent Bitの用意
色々やる方法はあるかと思いますが、今回はレポジトリをフォークして直接ビルドして利用することにしました
フォークした後にDockerfileのCOPYでコンフィグを一つ追加します
FROM golang:1.20.5-buster AS builder
WORKDIR /go/src/github.com/newrelic/newrelic-fluent-bit-output
COPY Makefile go.* *.go /go/src/github.com/newrelic/newrelic-fluent-bit-output/
COPY config/ /go/src/github.com/newrelic/newrelic-fluent-bit-output/config
COPY nrclient/ /go/src/github.com/newrelic/newrelic-fluent-bit-output/nrclient
COPY record/ /go/src/github.com/newrelic/newrelic-fluent-bit-output/record
COPY utils/ /go/src/github.com/newrelic/newrelic-fluent-bit-output/utils
ENV SOURCE docker
# Not using default value here due to this: https://github.com/docker/buildx/issues/510
ARG TARGETPLATFORM
ENV TARGETPLATFORM=${TARGETPLATFORM:-linux/amd64}
RUN echo "Building for ${TARGETPLATFORM} architecture"
RUN make ${TARGETPLATFORM}
FROM amazon/aws-for-fluent-bit:latest
# RUN yum update -y \
# && yum clean all
COPY /go/src/github.com/newrelic/newrelic-fluent-bit-output/out_newrelic-linux-*.so /fluent-bit/bin/out_newrelic.so
COPY config/fluent-bit-custom.conf /fluent-bit/etc
COPY config/plugins.conf /fluent-bit/etc
コンフィグの中身ではS3とNew RelicのOUTPUTを設定しています
[SERVICE]
HTTP_Server On
HTTP_Listen 0.0.0.0
HTTP_PORT 2020
[OUTPUT]
Name s3
Match *
bucket ${LOG_BUCKET_NAME}
region ${REGION_NAME}
upload_timeout ${UPLOAD_TIMEOUT}
s3_key_format ${S3_KEY_FORMAT}
compression gzip
use_put_object true
[OUTPUT]
Name newrelic
Match *
licenseKey ${NRIA_LICENSE_KEY}
これをビルドしECRにアップロードしておきます
ECSのタスク定義の編集
このまま起動してしまうと、ログがNew Relic AgentとFluent Bitからの二重送信状態になってしまうため、New Relic Agentからのログ出力を止める必要があります
環境変数でコントロールします
- applications部分
{
"name": "application",
"image": "11112222333.dkr.ecr.us-west-2.amazonaws.com/hoge/application:latest",
"cpu": 128,
"memoryReservation": 256,
"portMappings": [
{"containerPort": 8080, "hostPort": 8080, "protocol": "tcp"},
{"containerPort": 8081, "hostPort": 8081, "protocol": "tcp"}
],
"essential": true,
"environment": [
{"name": "NEW_RELIC_APP_NAME", "value": "hoge-app"}
{"name": "NEW_RELIC_AGENT_ENABLED", "value": "true"}
{"name": "NEW_RELIC_APPLICATION_LOGGING_ENABLED", "value": "true"}
{"name": "NEW_RELIC_APPLICATION_LOGGING_LOCAL_DECORATING_ENABLED", "value": "true"}
{"name": "NEW_RELIC_APPLICATION_LOGGING_FORWARDING_ENABLED", "value": "false"}
],
"secrets": [
{"name": "NEW_RELIC_LICENSE_KEY", "valueFrom": "arn:aws:ssm:us-west-2:11112222333:parameter/new_relic_license_key"}
],
"mountPoints": [],
"volumesFrom": [],
"logConfiguration": {"logDriver": "awsfirelens"}
},
NEW_RELIC_APPLICATION_LOGGING_LOCAL_DECORATING_ENABLED
ではNew Relicの情報をFluent Bitに付与しています
NEW_RELIC_APPLICATION_LOGGING_FORWARDING_ENABLED
でログを止めています
ログ部分についてもNew Relicのライセンスキーの情報が必要になるため変更をします
S3のコンフィグもビルド時に直接指定しているため環境変数から外します
- log部分
{
"name": "log",
"image": "11112222333.dkr.ecr.us-west-2.amazonaws.com/hoge/log:latest",
"cpu": 64,
"memoryReservation": 128,
"portMappings": [],
"essential": true,
"environment": [
{"name": "LOG_BUCKET_NAME", "value": "hogehoge-application-bucket"},
{"name": "UPLOAD_TIMEOUT", "value": "1m"},
{"name": "S3_KEY_FORMAT", "value": "/app/%Y/%m/%d/%H/%M/%S"},
{"name": "CONTAINER_NAME", "value": "application"},
{"name": "REGION_NAME", "value": "us-west-2"}
],
"secrets": [
{"name": "NEW_RELIC_LICENSE_KEY", "valueFrom": "arn:aws:ssm:us-west-2:11112222333:parameter/new_relic_license_key"}
],
"mountPoints": [],
"volumesFrom": [],
"user": "0:1337",
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/app/log",
"awslogs-region": "us-west-2",
"awslogs-stream-prefix": "firelens"
}
},
"firelensConfiguration": {
"type": "fluentbit"
}
}
これでFluent Bit経由でS3 + New Relicにログを飛ばせるようになります
トレースも紐づいているはずです
New Relic上でのログの見え方について(小ネタ)
Fluent Bitから送られたログのnewrelic.sourceはapi.logs
New Relic Agentから送られたログのnewrelic.sourceはlogs.APM
可用性を持たせるためにFirehoseの導入
今回はライトな感じの紹介になってしまいましたが、本番ワークロードにおいては以下のような構成がおすすめです
New RelicもS3もKinesis Data Firehoseに対応しているので、ログのバーストなども考慮すると導入したほうがいいとおもいます
参考
Discussion