ECSのログをFireLens&FirehoseでS3に転送する
はじめに
Amazon Elastic Container Service(ECS)でアプリケーションを運用する上で、ログ管理は不可欠です。
ECSのログ管理の仕組みを知らないと、いざという時に必要な情報が見つからず、困ってしまいます。
本記事では、ECSコンテナのログをFireLens for Amazon ECS(以下、FireLens)で収集し、CloudWatchとAmazon Data Firehose(以下、Firehose)経由でのS3に送信する環境をTerraformで構築する方法をご紹介します。
ログはJSON形式で構造化して保存することで、将来的にAmazon AthenaやKibana等での分析にも対応できる設計です。
Firehoseとは
ストリーミングデータをリアルタイムで他のサービス(S3、Amazon Redshift、Amazon OpenSearch Serviceなど)に配信するフルマネージドサービスです。
今回は、FireLensから送信されたログをS3に配信する用途で使用します。
FireLens単体でもS3への直接書き込みは可能ですが、役割分担の観点とFireLensコンテナの負荷軽減を考慮し、FirehoseからS3に書き込む構成とします。
FireLensとは
FireLensは一見するとAWSのマネージドサービスのように思えますが、実際はログルーティング機能を持つコンテナのことを指します。
アプリケーションが出力するログを様々なサービスへ転送でき、ログの種類に応じて転送先を柔軟に振り分けることも可能です。
コンテナイメージには、AWSが提供するfluent bitベースの公式イメージを利用できます。また、要件に応じてfluent bitやFluentdをベースに、カスタムイメージも作成可能です。
構成図
実装
ディレクトリ構造
最終的なコードについてはこちらのリポジトリをご参照ください。
.
├── ecs-log-test
│ ├── ecs.tf
│ ├── firehose.tf
│ ├── iam.tf
│ ├── main.tf
│ ├── s3.tf
│ ├── vpc.tf
│ └── cloudwatch.tf
├── fluent-bit
├── Dockerfile
└── extra.conf
Firelensイメージの作成
FireLensでは、送信先が1つだけの場合はタスク定義のlogConfiguration
で簡単に設定できます。
logConfiguration オブジェクトのオプションとして指定されたキーバリューペアは、Fluentd または fluent bit 出力設定の生成に使用されます。
しかし今回は、CloudWatchとS3の両方にログを送信したいため、カスタムイメージを作成します。
AWS公式のfluent bitイメージをベースに、Dockerfileを用意します。
FROM public.ecr.aws/aws-observability/aws-for-fluent-bit:stable
COPY extra.conf /fluent-bit/etc/extra.conf
fluent bitの設定ファイルを作成します。
この設定では、まずFILTERセクションでnginxのログをJSON形式にパースしています。
その後、2つのOUTPUTセクションでログを異なる宛先に送信します。1つ目はCloudWatchへの送信、2つ目はFirehose経由でS3への送信を行っています。
# extra.conf
[SERVICE]
Parsers_File parsers.conf
[FILTER]
Name parser
Match *
Key_Name log
Parser nginx
Reserve_Data On
Preserve_Key Off
[OUTPUT]
Name cloudwatch_logs
Match *
region ap-northeast-1
log_group_name ecs-log-test-cloudwatch
log_stream_prefix from-fluent-bit-
auto_create_group true
log_format json
[OUTPUT]
Name kinesis_firehose
Match *
region ap-northeast-1
delivery_stream ecs-log-test-s3-stream
設定ファイルが完成したら、Dockerイメージをビルドします。
docker build --platform linux/amd64 -t log-router:v1 .
ECRへのイメージ登録
作成したFireLensイメージをECSから利用するため、Amazon Elastic Container Registry(以下、ECR)へプッシュします。
まず、イメージを保存するためのECRリポジトリを作成します。
aws ecr create-repository --repository-name log-router --region ap-northeast-1
DockerがECRにアクセスできるよう認証を行います。
aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin <your-account-id>.dkr.ecr.ap-northeast-1.amazonaws.com
ECRのリポジトリURLに合わせてイメージにタグを付与し、プッシュします
# タグ付け
docker tag log-router:v1 <your-account-id>.dkr.ecr.ap-northeast-1.amazonaws.com/log-router:v1
# ECRにプッシュ
docker push <your-account-id>.dkr.ecr.ap-northeast-1.amazonaws.com/log-router:v1
Terraform
ECSタスク定義では、アプリケーションコンテナとFireLensコンテナを組み合わせて設定します。
IAMロールやVPCなどの基本的な設定については本筋から外れるため省略しますが、主要なリソースの設定方法を解説します。詳細な設定についてはこちらのリポジトリをご参照ください。
ECSタスク定義
ECSタスク定義では、アプリケーションコンテナとFireLensコンテナを組み合わせて設定します。
主なポイントは以下の通りです。
- アプリケーションコンテナのlogConfigurationで
awsfirelens
を指定 - FireLensコンテナには作成した設定ファイルの場所を指定
- dependsOnで、アプリケーションコンテナより先にFireLensコンテナが起動するように制御
resource "aws_ecs_task_definition" "main" {
...
container_definitions = jsonencode([
{
name = "app-container"
image = "nginx:latest"
portMappings = [
{
containerPort = 80
hostPort = 80
}
],
logConfiguration = {
logDriver = "awsfirelens"
},
depends_on = [
{
containerName = "log_router"
condition = "START"
}
]
},
{
name = "log_router"
image = "xxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/log-router:v1"
essential = true
cpu = 512
memory = 1024
firelensConfiguration = {
type = "fluentbit"
options = {
config-file-type = "file"
config-file-value = "/fluent-bit/etc/extra.conf"
}
}
}
])
}
Firehoseの設定
ログをS3に保存するため、Firehoseを設定します。
主なポイントは以下の通りです。
- extended_s3を指定してS3へ配信
- prefixでタイムスタンプ形式を指定してパーティション分割
resource "aws_kinesis_firehose_delivery_stream" "main" {
name = "ecs-log-test-s3-stream"
destination = "extended_s3"
extended_s3_configuration {
role_arn = aws_iam_role.firehose_role.arn
bucket_arn = aws_s3_bucket.main.arn
buffering_size = 64
prefix = "data/year=!{timestamp:yyyy}/month=!{timestamp:MM}/day=!{timestamp:dd}/hour=!{timestamp:HH}/"
error_output_prefix = "errors/year=!{timestamp:yyyy}/month=!{timestamp:MM}/day=!{timestamp:dd}/hour=!{timestamp:HH}/!{firehose:error-output-type}/"
}
}
Terraform実行
設定が完了したら、Terraformで環境を構築します。
# 初回実行時
terraform init
# 変更内容の確認
terraform plan
# 実際にリソースを作成
terraform apply
動作確認
構築した環境が正しく動作することを確認します。
- AWSコンソールにログイン
- ECSタスクへのアクセス
- ECSコンソールでタスクの詳細を開き、パブリックIPアドレスを確認
- ブラウザでhttp://<パブリックIP> にアクセスしてnginxのページを表示
- 数回リロードしてアクセスログを生成
- CloudWatchでのログ確認
- CloudWatchコンソールの「ロググループ」を開く
- ecs-log-test-cloudwatchロググループにログストリームが作成されたことを確認
- ログがJSON形式であることを確認
- S3でのログ確認
- S3コンソールでecs-log-test-bucket--* バケットを開く
- data/year=YYYY/month=MM/day=DD/hour=HH/ の形式でパーティション分割されていることを確認
- ログファイルをダウンロードして、ログがJSON形式であることを確認
まとめ
新卒として入社してから、インシデント対応を経験する中でログ管理の重要性を実感しました。学生時代は短期的な開発が中心でしたが、実際の運用ではログの管理が重要であることを学びました。
今回、ECSでのログ管理の仕組みを一から構築することで、FireLensとFirehoseを組み合わせたログ収集・保存の方法を理解できました。
次のステップとして、S3に蓄積したログをAmazon Athenaで分析する仕組みの構築や、Firehoseの動的パーティショニング機能を使ったログ種別ごとの振り分けに挑戦してみたいと思います。
Discussion