🍙

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に書き込む構成とします。

https://docs.aws.amazon.com/ja_jp/firehose/latest/dev/what-is-this-service.html

FireLensとは

FireLensは一見するとAWSのマネージドサービスのように思えますが、実際はログルーティング機能を持つコンテナのことを指します。
アプリケーションが出力するログを様々なサービスへ転送でき、ログの種類に応じて転送先を柔軟に振り分けることも可能です。
コンテナイメージには、AWSが提供するfluent bitベースの公式イメージを利用できます。また、要件に応じてfluent bitやFluentdをベースに、カスタムイメージも作成可能です。

https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/using_firelens.html

構成図

aws構成図

実装

ディレクトリ構造

最終的なコードについてはこちらのリポジトリをご参照ください。

.
├── 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 出力設定の生成に使用されます。

https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/firelens-taskdef.html

しかし今回は、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

動作確認

構築した環境が正しく動作することを確認します。

  1. AWSコンソールにログイン
  2. ECSタスクへのアクセス
    • ECSコンソールでタスクの詳細を開き、パブリックIPアドレスを確認
    • ブラウザでhttp://<パブリックIP> にアクセスしてnginxのページを表示
    • 数回リロードしてアクセスログを生成
  3. CloudWatchでのログ確認
    • CloudWatchコンソールの「ロググループ」を開く
    • ecs-log-test-cloudwatchロググループにログストリームが作成されたことを確認
    • ログがJSON形式であることを確認
      CloudWatchコンソール
  4. S3でのログ確認
    • S3コンソールでecs-log-test-bucket--* バケットを開く
    • data/year=YYYY/month=MM/day=DD/hour=HH/ の形式でパーティション分割されていることを確認
    • ログファイルをダウンロードして、ログがJSON形式であることを確認
      S3コンソール

まとめ

新卒として入社してから、インシデント対応を経験する中でログ管理の重要性を実感しました。学生時代は短期的な開発が中心でしたが、実際の運用ではログの管理が重要であることを学びました。
今回、ECSでのログ管理の仕組みを一から構築することで、FireLensとFirehoseを組み合わせたログ収集・保存の方法を理解できました。
次のステップとして、S3に蓄積したログをAmazon Athenaで分析する仕組みの構築や、Firehoseの動的パーティショニング機能を使ったログ種別ごとの振り分けに挑戦してみたいと思います。

nextbeat Tech Blog

Discussion