本記事は
【Advent Calendar 2023】
18日目の記事②です。
🎄
17日目
▶▶
18日目記事① ▶▶
本記事 ▶▶
19日目
🎅
こんにちは。梅原です。
ECSコンテナのログは通常CloudWatch logsに出力されます。長期保管用としてS3にも出力したいといったこともあると思います。複数サービスへ出力したいときに使うのがログドライバーの1つであるAWS FireLensです。
今回はnginxのログをAWS FireLensを使ってS3とCloudWatch logsにログを出力してみます。
fluent bitのロゴはhttps://github.com/fluent/fluent-bit/blob/master/fluentbit_logo.pngより引用
- ECSコンテナのログについて
- AWS FireLensとは
- AWS FireLensを使うときの方法
- fluentbitの設定ファイルの書き方
- FireLensを使うときの注意点
- 実際にやってみる
- 最後に
ECSコンテナのログについて
コンテナでは、アプリケーションログの出力先は標準出力と標準エラー出力に設定しておきます。
そのログをログドライバーが処理し、様々なサービスに出力します。
AWSのコンテナサービスであるAmazon ECSでは、タスク定義でログドライバーを指定します。
CloudWatch logsのみへ出力するときは、タスク定義でCloudWatch logsへの出力を選択します。 ロググループやリージョン、ストリームのプレフィックスは出力先情報をオプションで渡します。
以下がタスク定義のコンテナ部分の例です。
{ "containerDefinitions": [ { "logConfiguration": { "logDriver": "awslogs", # ログドライバーをawslogsに設定 "options": { "awslogs-group": "firelens-container", "awslogs-region": "us-west-2", "awslogs-create-group": "true", "awslogs-stream-prefix": "firelens" } } ] }
awslogs ログドライバーを使用する - Amazon Elastic Container Serviceより引用
CloudWatch logsへの出力は上記の通り簡単に設定できますが、標準出力と標準エラー出力が同じロググループとストリームに出力されてしまいます。 また、CloudWatch logsのログ取り込みや保存料金はS3と比較して高めです。
そこでアプリケーションのすべてのログを保管用途でS3に、ログ監視用で標準エラー出力をCloudWatch logsに出力したいといった要件が出ると思います。 この要件に対応できるのがawsfirelensログドライバーです。
AWS FireLensとは
AWS FireLensはログドライバーの1つで、OSSであるfluentbitもしくはfluentdと連携してAWS内外のサービスへログを出力することができます。
AWS FireLensを使うときは、fluentbitもしくはfluentdをログルーターコンテナとしてデプロイします。
アプリケーションログを標準出力と標準エラー出力に出しておくだけで、AWS FireLensがログルーターコンテナへ転送してくれます。アプリケーション側で対応する必要はありません。
ログを受け取ったログルーターコンテナがCloudWatch logsやS3、SplunkといったAWS内外のサービスへログを出力してくれます。
fluentbitやfluentdを使うことで
- CloudWatch logsとS3といった複数サービスへの出力
- 特定のログ形式にパース
- 標準出力と標準エラー出力の出力先の分離
- 不要なログを削ぎ落とす
- ログにECSのメタデータ追加
といった多くのことができます。
FireLensではログルーターのOSSであるfluentbitとfluentdを選択できますが、AWSではより軽量であるfluentbitが推奨されています。 今回のブログでもfluentbitを例に話します。 docs.aws.amazon.com
AWS FireLensを使うときの方法
FireLensログドライバーを使うときの方法は3パターンあります。
1. タスク定義のみで完結
タスク定義でアプリケーションコンテナのログドライバーにawsfirelensを指定して、出力先情報をオプションに記載します。
オプションのNameで出力したいサービスを設定します。以下はFIreLensからCloudWatch logsに出力する例です。
環境変数に入れるだけの簡単設定ですが、複数出力先といった複雑な設定はできません。
{ "containerDefinitions": [ { "logConfiguration": { "logDriver": "awsfirelens", # ログドライバーにawsfirelensを指定 "options": { "Name": "cloudwatch_logs", # 出力先はCloudWatch logs "region": "ap-northeast-1", "log_group_name": "/ecs/demo/nginx-from-fluent-bit", "auto_create_group": "true", "log_stream_prefix": "ecs/" } } ] }
2. カスタムイメージを作成する
自前で設定ファイルを埋め込んだdockerイメージをビルドし、タスク定義でそのイメージを指定する方法です。
AWSはfluentbit用のdockerイメージを公開しています。
このdockerイメージをベースイメージとして、fluentbitの設定ファイルをCOPYしてイメージをビルドします。
ビルドしたイメージをECRへプッシュし、タスク定義の中でイメージのURIを指定することで、作成した設定ファイルから起動するfluentbitを使用する事ができます。
この方法ではイメージの管理の手間がかかってしまい、デプロイの方法も考えなければいけません。
3. S3から設定ファイルを取得する
AWSが提供しているfluentbitのdockerイメージのタグにはinit-から始まるものがあります。
このイメージを使うことで、ログルーターコンテナが起動時にS3に置かれた設定ファイルを取得し、自動的に設定を読み込みます。設定ファイルを埋め込むためにイメージからビルドをする必要がなくなります。
起動時間が遅くなったり、getobjectの料金もかかる点には注意が必要です。
設定ファイルのオブジェクト情報は、タスク定義のログルーターコンテナの環境変数で、nameにaws_fluent_bit_init_file_{連番}を、valueにS3オブジェクトのARNを指定します。そうするだけで起動時に設定ファイルのS3オブジェクトを取得し、fluentbitが設定ファイルを読み込みます。
どのパターンを採用すべきか
fluentbitで複数出力先といった設定が不要な場合は、環境変数だけで完結するパターン1をおすすめします。
パターン2は先述の通りイメージの管理やデプロイ方式の検討が必要になります。そのため、S3から設定ファイルを読み込むパターン3をおすすめします。
パターン3で設定を変更したい場合は、S3に新しい設定ファイルをアップロードし、タスクの更新だけでよくなります。
fluentbitの設定ファイルの書き方
fluentbitの設定ファイルは4つのセクションに分かれます。
- Service
Fluent Bitの全体の動作に関わる設定 - Input
入力プラグインの指定 - Filter
ログのパースなどの加工 - Output
マッチするログの出力設定
設定できるパラメータはドキュメントの参照お願いします。
Configuration File - Fluent Bit: Official Manual
S3へ出力するときのOutput設定
Amazon S3 - Fluent Bit: Official Manual
CloudWatch logsへ出力するときのOutput設定
Amazon CloudWatch - Fluent Bit: Official Manual
また設定ファイルが長くなるのを回避するために、@INCLUDEで外部の設定ファイルを読み込むことができます。
少し話が逸れますが、
initタグがついているfluentbitイメージでは、S3から取得してきた設定ファイルを@INCLUDEで読み込んでいます。
sh-4.2# cat /init/fluent-bit-init.conf @INCLUDE /fluent-bit/etc/fluent-bit.conf @INCLUDE /init/fluent-bit-init-s3-files/extra.conf
fluentbitの実行コマンドで上記の設定ファイルが渡され、@INCLUDEで基本設定がされたファイルとS3に置かれたファイルが読み込まれています。 基本設定のファイルは以下です。INPUTセクションがあるので自分で設定ファイルには書く必要はありません。
sh-4.2# cat /fluent-bit/etc/fluent-bit.conf [INPUT] Name tcp Listen 127.0.0.1 Port 8877 Tag firelens-healthcheck [INPUT] Name forward unix_path /var/run/fluent.sock [INPUT] Name forward Listen 127.0.0.1 Port 24224 [FILTER] Name record_modifier Match * Record ecs_cluster <クラスター名> Record ecs_task_arn <taskのARN> Record ecs_task_definition <タスク定義名:リビジョン> [OUTPUT] Name null Match firelens-healthcheck
コンテナを起動したときに、クラスター名などが取得され設定ファイルに埋め込まれています。
FireLensを使うときの注意点
ECSタスクの権限
ログルーターとしてデプロイされたECSタスクからCloudWatch logsやS3に出力するので、タスクロールに権限を付与する必要があります。ECSのコンテナリソース
先述の通りECSタスクがデプロイされるので、コンテナのリソースはそれを見越して大きくしてあげる必要があります。サイズが大きいログの取り扱い
16KB以上のログは分割されて送られてしまいます。そういったログはfluentbit内でMultiline Filter Pluginを使用して再結合してあげる必要あります。
スタックトレースといった複数行のログもMultiline Filter Pluginで結合が可能です。
実際にやってみる
今回はnginxのすべてのログをcloudWatch logsとS3に出力したいと思います。
fluentbitの設定
fluentbitの設定ファイルを作成し、S3へアップロードしておきます。
# グローバルな設定 [SERVICE] # nginx用でパースするためパーサーを読み込む Parsers_File fluent-bit/parsers/parsers.conf Flush 1 Grace 30 # nginx用にパースする [FILTER] Name parser Match * Key_Name log Parser nginx # CloudWatch logsへの出力 [OUTPUT] Name cloudwatch_logs Match * region ap-northeast-1 log_group_name /ecs/k-umehara-nginx-from-fluent-bit log_stream_prefix nginx- auto_create_group true # S3への出力 [OUTPUT] Name s3 Match * bucket k-umehara-log-output region ap-northeast-1 total_file_size 1M use_put_object On compression gzip s3_key_format /$TAG/%Y/%m/%d/%H/%M/%S/$UUID.gz
タスク定義の設定
タスク定義のコンテナ部分は以下です。
"containerDefinitions": [ { "name": "web", "image": "nginx", # アプリケーションはnginx "cpu": 0, "portMappings": [ { "name": "nginx-80-tcp", "containerPort": 80, "hostPort": 80, "protocol": "tcp", "appProtocol": "http" } ], "essential": true, "environment": [], "environmentFiles": [], "mountPoints": [], "volumesFrom": [], "ulimits": [], "logConfiguration": { "logDriver": "awsfirelens", # ログドライバーにawsfirelens "options": {}, "secretOptions": [] } }, { "name": "log_router", # initタグがついたもの指定 "image": "public.ecr.aws/aws-observability/aws-for-fluent-bit:init-latest", "cpu": 0, "memoryReservation": 50, "portMappings": [], "essential": true, "environment": [ { # nameにaws_fluent_bit_init_s3_{連番} "name": "aws_fluent_bit_init_s3_1", # valueに設定ファイルのS3オブジェクトのARN "value": "arn:aws:s3:::k-umehara-log-conf/extra.conf" } ], "mountPoints": [], "volumesFrom": [], "user": "0", "logConfiguration": { "logDriver": "awslogs", # fluentbit自体のログはCloudWatch logsに出力 "options": { "awslogs-create-group": "true", "awslogs-group": "/ecs/k-umehara-firelens-log", "awslogs-region": "ap-northeast-1", "awslogs-stream-prefix": "ecs" }, "secretOptions": [] }, "firelensConfiguration": { "type": "fluentbit" } } ],
デプロイして確認
このタスク定義を元にECSサービスをデプロイします。nginxにアクセスし出力されたログを確認します。
CloudWatch logsの確認
CloudWatch logsにログが出力されていますね。 指定したロググループに出力され、プレフィックスもついてる事がわかります。
ログの中身を見てみると、nginxのログにparseされています。
S3バケットの確認
指定したS3バケットにもコンテナ毎で出力されていることがわかります。
アップロードされたオブジェクトも指定した形式で出力されています。
最後に
今回はAWS FireLensの説明から実際にやってみるところまで紹介しました。
AWS FireLensについての基礎的な話は理解できるようになったのではないでしょうか。
今回は基本的なことしかしてないですが、Filterを用いることでここでお伝えしたようなこともできるようになります。
ぜひ試してみてください。
お役に立てる部分があったら嬉しいです。