🐕

ログ基盤の構築

に公開

みなさん、はじめまして。
ジンジャープロダクト開発部の福与です。

2024年2月に入社し、生涯現場でエンジニアとしてコードを書き続けていたい、アーキテクチャを考えたいと思いながら、「日本一のエンジニア集団」を目指して、仲間たちと日々奮闘しております。

ログ基盤構築の背景

障害発生時や不具合などのトラブルシューティングにおいて、ログの調査は必要不可欠です。
システムを安定して運用するためにも、迅速に調査を行い、原因を特定し、素早く対応することが求められます。
ジンジャーは多くのプロダクトで構成されており、ログの運用方法や出力方法などをプロダクト毎で決めているため、ログが分散し、調査や分析がプロダクト毎で秘伝のタレとなってしまい、属人化の原因にもなっています。
今回は、ジンジャーとして統一したログの運用をおこなうために、ログ基盤を構築した際のお話をさせていただければと思います。

ログ基盤の目的

  • 統一したログ基盤を構築し、プロダクト毎で分散されたアプリケーションログを集約する
  • ログの出力方法や出力内容をルール化し、プロダクト間を跨いだ調査をできるようにする
  • 集約したログを低工数で検索できるようにする
  • ログ基盤の導入を低コストでできるようにする

ログの収集

各プロダクトからログを収集する手段として、API経由でのログ収集も候補にあがりましたが、APIの場合、ログ出力時にAPIで送信するように実装してもらう必要があります。

また、ピーク時などAPIがボトルネックになってしまう可能性があります。
各プロダクトが低コストでかつ速やかにログ基盤を導入できるようにするにはどうすればいいのかを主軸にログ基盤のベストプラクティスなどを調査し議論しました。

FireLens + FluentBit

AWS ECS on FargateではFireLensログドライバを利用することで、コンテナのログをFluentdまたはFluentBitに転送することができます。
このFireLensを使ってアプリケーションログをFluentBitのコンテナに転送し、FluentBitから
S3へ転送するようにしました。

また、今回はアプリケーションログだけをS3に転送したかったので、ログ出力ルールを統一化し、FluentBitの設定でログ出力ルールに合致したものだけをS3に転送し、ミドルウェアで出力されるログはCloudWatch Logsに転送するようにしました。
各プロダクトがログ基盤を導入する際にもアプリケーション側は意識せずに、ECSタスク定義にサイドカーコンテナの設定とFireLensログドライバの設定をすれば、低コストで導入することができます。
参考: Amazon ECS ログを AWS サービスまたは AWS Partner に送信する

アーキテクチャ

ログの転送

FluentBitの設定でログ出力ルールに合致したものだけをS3に転送し、ミドルウェアで出力されるログはCloudWatch Logsに転送するようにしました。

S3とCloudWatchLogsへの転送はFulentBitのextra.confで定義しています。
ログ内にlevelがあるものはアプリケーションログ、それ以外はアプリケーションログとして扱わないといったログ転送ルールに従い、extra.confでラベリングして設定を行いました。
※以下はサンプルです。

# ログレベルがあってかつERRORのログをerror-{コンテナ名}としてラベリング
[FILTER]
    Name rewrite_tag
    Match *-firelens-*
    Rule $level ^(error|ERROR)$ error-$container_id false

# ログ内にlevelの文字列がなければother-{コンテナ名}としてラベリング
[FILTER]
    Name rewrite_tag
    Match *-firelens-*
    Rule $log (?!.*(level|LEVEL)) other-$container_id false

# ラベルがerrorのものをS3へ転送
[OUTPUT]
    Name s3
    Match_Regex ^error-*
    region us-west-2
    bucket xxxx
    compression gzip

# ラベルがotherのものをCloudwatchLogsへ転送
[OUTPUT]
    Name cloudwatch_logs
    Match_Regex ^other-*
    region xxxx
    log_group_name test-log-group
    log_stream_name test-log-group

ログの検索

ログの検索はFluentBitで転送したアプリケーションログのS3バケットを直接参照し、クエリベースで検索できるAmazon Athenaを採用しました。
Athenaのコスト削減やパフォーマンス向上の観点からパーティションをおこなうため、S3の構成はHive形式として、FluentBitからS3の転送設定をおこない、Athenaでは広範囲条件のクエリ、スキャンデータ量の制限したりと、高コストとならないようにしました。
参考: データのパーティション化

アーキテクチャ

S3への転送をHive形式

Hive形式として、FluentBitからS3の転送設定をおこない、

Hive形式の設定もFulentBitのextra.confで定義しています。
s3_key_formatを使って、/year=%Y/month=%m/day=%d/hour=%H/%Y%m%d%H%M%S-$UUID.gzの形式で転送するようにしています。

# S3への転送はHive形式
[OUTPUT]
    Name s3
    Match_Regex ^error-*
    region us-west-2
    bucket xxxx
    s3_key_format /year=%Y/month=%m/day=%d/hour=%H/%Y%m%d%H%M%S-$UUID.gz
    compression gzip

アラート通知

FluentBitがS3にログを転送すると、S3PUTイベントをトリガーにログレベルが特定のもの(ERROR、WARNなど)をSlackへアラート通知するようにしました。
エンジニアがSlackのアラートを確認し、Athenaで検索することで、障害や問題が発生していることを認識し、ピンポイントで素早くアプローチでき、対応策やバグの修正などをおこなうことができます。

さらに、アラート通知から問題が発生しているログへ素早く到達できるように、トレースIDを導入しました。
トレースIDはプロダクト毎でかつアプリケーションログ毎にユニークなIDを生成したものです。
ジンジャーは多くのプロダクトで構成されているため、プロダクト間での依存部分の調査も
AthenaでトレースIDを検索することで、プロダクトを横断したログ調査ができるため、初動調査を行い、他のプロダクトの影響によるものであれば、本格調査を他のプロダクトに依頼するといった問題の切り分けもできます。

技術選定の裏ばなし

プロダクト開発部では「ティール組織」として、従来のヒエラルキー型とは異なり、自律的でフラットな組織を目指し、一人ひとりの主体性を重視しています。
技術選定においても、トップダウンではなく、どういうことを重視するのかをメンバーで議論し、それに沿ってどういうアーキテクチャや技術を使うのかを話し合って決めていきます。
それぞれのメンバーの想いが強すぎて、議論が白熱し、他のプロダクトから喧嘩しているのではないか?なんて言われることもあります。
その場では決まったことでも、お風呂に入ってゆっくり考えたらこっちの技術の方がいいのでは?と翌日にひっくり返ることも。(おいおい。。。)
一人ひとりが考え、主張し、議論を重ねながら、自分が想定していなかったことも認識し、決めていけるのはすごくいいなって思います。

最後に

今回は触れませんでしたが、ECS以外にLambdaのアプリケーションログもCloudWatch LogsからKinesis Firehoseを使ってS3連携するなどして、アプリケーションログの集約を行っています。

今回はログ基盤の構築についてお話しいたしました。この記事が何かのお役に少しでも立てたなら幸いです。

私たちは一緒に「日本一」の開発組織を目指す仲間を募集しています。
プロダクト開発部は色々な経歴を持った仲間も多く、チャレンジングな環境です。
興味を持たれた方は是非一緒に「日本一」を目指しましょう!

jinjerテックブログ

Discussion