クロスアカウント連携でFireLensを使いたい

thumbnail

 

サントリーウエルネス DX推進部 エンジニアリングGの青木です。
Comadoというアプリを開発しており、主にサーバーサイド領域でエンジニアをしています。

現在Comadoではデータ活用を見据えてデータ整備を行っています。
アプリの動作をイベントログとして出力し、データ基盤に連携しようと考えていますが、Comadoとデータ基盤のAWSアカウントが別れているためクロスアカウントでの連携が必要な環境になっています。 このイベントログをリアルタイムに送信するようなアーキテクチャを考えており、FireLensを使ったシステム構成の検討を行いました。 クロスアカウントにおける設定の中でいくつかの点で苦労しましたので、その知見を共有できればと思います。

目的

Comadoでは様々なコンテンツを提供しており、それに伴うログを取得できる状態です。それらの行動データをイベントログとして収集し、データを分析することでより良いサービスに繋げたい狙いがあり収集することとなりました。

サントリーウエルネスではAWSにシステムごとのデータを集めて分析する環境としてデータ基盤を構築しており、そこに連携することになります。
Comadoで取得できるイベントログはデータ基盤に連携が必要ですが、Comadoとデータ基盤でAWSアカウントが別れているためアカウント間での連携が必要になります。 そのため、クロスアカウント連携ができるようにシステム構成を検討・設計しなくてはなりません。

今回検討した連携方式

ログの出し分けの必要性

アプリケーションログとイベントログはそれぞれ利用目的が異なるため、目的にあった連携先に出し分ける必要があります。
アプリケーションログは、エラーメッセージ、スタックトレース、デバッグ情報などを記録するため、フィルタリングやアラート機能を持つログ管理システム(AWSでいうとCloudWatch)に連携する必要があります。
一方でイベントログはユーザーの行動単位で記録し、大量のデータを長期間保存できるストレージに保存したいです。AWSでいうとS3、Redshiftなどになります。
この出し分けの機能を提供しているのがECSではFireLensです。
今回はアプリケーションログはCloudWatchへ、イベントログはリアルタイムに送信したいためデータ基盤側のKinesisFirehose+S3を使用する構成で検討することにします。

想定の構成図は下記です。

FireLensを使った連携方式

FireLens は、ECS ログドライバの1つである awsfirelens として提供されており、ECSタスク上のコンテナのログをFluentdまたはFluent Bitを通じてログルーティングができる仕組みのことを指します。

  • 簡潔にいうと、awslogsでは単一の送信先(CloudWatch)しか指定できないが、FireLensでは複数の送信先を指定でき、ログをFilterやParseして加工した上で送信することができる。
  • Fluentd or Fluent Bit どちらかを指定可
  • これらをECSに組み込みやすいようにカスタムしてくれているサービスであると理解して良い。

ここでは、よりコンテナと相性が良いFluent Bitを採用して構成検討することにします。

Fluent Bitを利用した場合のFireLens設定例

FireLens導入にあたって、ECSタスク定義追加定義Dockerfile、の3つの定義が必要になります。

ECSタスク定義

FireLensはメインコンテナのサイドカーとして動作しますので、タスク定義にコンテナを1つ追加する必要があります。
その定義例は下記です。 これはFluent Bitを指定して後述するextra.conf(ファイル名は何でも良い)を自動生成されるfluent-bit.confに含める設定方法です。
また、FireLensコンテナそのもののヘルスチェックもしたい場合はフィールドを追加して定義します。
記事執筆現在時点でTCPでのヘルスチェックは非推奨となっているので注意

{
    "containerDefinitions": [
        {
            "name": "firelens",
            "image": "${account}.dkr.ecr.${region}.amazonaws.com/firelens:${tag}",
            "firelensConfiguration": {
                "type": "fluentbit",
                "options": {
                    "config-file-type": "file",
                    "config-file-value": "/extra.conf"
                }
            },
            "healthCheck": {
                "retries": 2,
                "command": [
                    "CMD-SHELL",
                    "curl -f http://127.0.0.1:2020/api/v1/health || exit 1"
                ],
                "timeout": 5,
                "interval": 10,
                "startPeriod": 30
            }
        },
        {
            "name": "application",
            "logConfiguration": {
                "logDriver": "awsfirelens"
            }
        }
    ]
}   

この設定を入れることでメインコンテナからFireLensコンテナへUnixソケットを経由してログが送信されるようになります。

追加定義(extra.conf)

ECSタスク定義のoptionにoutput先を指定する方法もありますが、個人的には後々追加の定義が必要になったときに別にファイルを用意しておけばそこのファイルを書き換えれば良く、拡張性が高いと思うこちらを記載します。

[SERVICE]
    Flush        1
    Daemon       Off
    Log_Level    info
    Grace        30
    Parsers_File /fluent-bit/parsers/parsers.conf

[SERVICE]
    HTTP_Server  On
    HTTP_Listen  0.0.0.0
    HTTP_PORT    2020
    Health_Check On 
    HC_Errors_Count 5 
    HC_Retry_Failure_Count 5 
    HC_Period 5

[FILTER]
    Name         parser
    Match        *
    Key_Name     log
    Parser       json
    Reserve_Data True

[FILTER]
    Name         rewrite_tag
    Match        *-firelens-*
    Rule         $logClass (EVENT_LOG) event_log false

[OUTPUT]
    Name         kinesis_firehose
    Match        event_log
    region       ${region}
    delivery_stream ${stream_name}
    role_arn arn:aws:iam::${account}:role/${role}

[OUTPUT]
    Name         cloudwatch
    Match        *-firelens-*
    region       ${region}
    log_group_name app
    log_stream_prefix app-

Fluent Bitでは内部的にタグを発行し、タグによって処理を分けることができます。
ここではlogClassというkeyに対してEVENT_LOGというvalueが入ってくるとevent_logタグをつけ、target_logはkinesis_firehoseに、それ以外のログはcloudwatchに連携するという設定にしています。
rewrite_tagのMatch条件を*ではなく*-firelens-*にしている理由ですが、rewrite_tagされてtarget_logになったあと、再びrewrite_tagの条件に入り無限ループとなってしまうため明示的にタグを指定する必要があります。*-firelens-*はFireLensがすべてのログにつける初期タグ命名規則なのでそのタグを指定するようにしています。

Dockerfile

データプレーンにFargateを選択している場合、configをS3から取得できないため、extra.confはdocker image内に含めて事前にdocker buildしてimageを作成する必要があります。
※今回採用はしていませんが、aws-for-fluent-bitでinitタグがついたイメージを使い、initプロセスを用いればconfigをS3等から取得できるようです。

buildするディレクトリ直下にDockerfileとextra.confを置いている場合は下記でOKです。
この定義ではdocker imageをECRから取得していますが、Docker Hubから取ってきても問題ありません。

FROM public.ecr.aws/aws-observability/aws-for-fluent-bit:${tag}

COPY extra.conf /extra.conf

タスク定義で下記のように設定しているので、COPYしたextra.confをincludeするようになります。

{
    "config-file-type": "file",
    "config-file-value": "/extra.conf"
}

FireLensにおけるクロスアカウントの設定例・注意点

実際に連携テストをしてみて、いくつかの点で苦労したので共有します。

①送信元、送信先でIAMロール・ポリシーの調整が必要

考えれば当然ですが、送信側の設定だけ気にしていたら詰みます。

  • Comado側の設定(送信元)
    • データ基盤側のIAMロールを使ってKinesisFirehoseにレコードをputするための権限
    • データ基盤側のIAMロールを使うための権限
[
    {
        "Effect": "Allow",
        "Action": [
            "firehose:PutRecordBatch",
            "firehose:PutRecord"
        ],
        "Resource": "arn:aws:firehose:${region}:${データ基盤のアカウント}:deliverystream/${stream_name}"
    },
    {
        "Effect": "Allow",
        "Action": "sts:AssumeRole",
        "Resource": "arn:aws:iam::${データ基盤のアカウント}:role/${データ基盤側のIAMロール}"
    }
]

次にデータ基盤側にも設定が必要です。

  • データ基盤側の設定(送信先)
    • Comado側(ECSタスクロール)を受け入れるための権限
{
    "Effect": "Allow",
    "Action": "sts:AssumeRole",
    "Resource": "arn:aws:iam::${account}:role/${Comado側のIAMロール}"
}

②extra.confに含めるrore_arnはKinesisFirehoseが使っているroleである

公式ドキュメントにはARN of an IAM role to assume (for cross account access).と記載がありますが、ん?この構成におけるroleって色々あるな…と思いました。
結論からいうとKinesisFirehoseが使っているroleを設定すれば良いです。
今回の構成でいうと送信先がKinesisFirehoseがデータ基盤側にあるため、データ基盤側で設定しているroleを指定する必要があります。

[OUTPUT]
    Name         kinesis_firehose
    Match        target_log
    region       ${region}
    delivery_stream ${stream_name}
    role_arn arn:aws:iam::${データ基盤のアカウント}:role/${KinesisFirehoseで利用しているrole}

③送信先にも作業依頼をする必要があることを認識しておく

最後はコミュニケーションの部分です。
これらの構成を送信先であるデータ基盤の担当者に伝えなくてはいけません。システム構成が曖昧な部分があるとコミュニケーションロスが発生し、適切にデータが連携できずにFireLens側でエラーを吐いてタスクが立ち上がらずに再起動を繰り返す…のようなことになりかねません(戒め)
例えば、私の場合はテストの関係でロールの切り替えを行っている際にextra.conf側だけの切り替えをしてしまい切り替え先のロールでComado側のロールを受け入れる設定を入れていなかったりなどで疎通できない状況が発生したりしました。
この場合、Comado側のロール、データ基盤側のロール、そしてFireLensが参照するKinesisFirehoseのロールの3箇所を変更することをしっかりと関係者が理解して、明確に変更箇所を伝えることができれば疎通エラーを回避できたと思っています。
基本的なことではありますが、連携するときは変更に必要な箇所を抑えながら密にコミュニケーションを取ってロールやポリシーの確認をすると良いです。

おわりに

FireLensを導入し、ECSから取得したログを出し分け、同一アカウントのCloudwatchとクロスアカウントのKinesisFirehoseを通じてS3へ連携するところまで動作確認できました。
動作はこれで問題ないと思いますが、非機能観点(FireLensを導入して運用する観点でみたときの考慮やログの永続性の観点で最適な構成かどうかなど)を考慮した設計に関しては残検討課題です。

以上、クロスアカウント環境におけるFireLens導入の際の参考になれば幸いです。

参考