Amazon Web Services ブログ

Fluent Bit による集中コンテナロギング

本投稿は Wesley Pettit と Michael Hausenblas による寄稿を翻訳したものです

AWS はビルダーのために作られています。ビルダーは常に最適化の方法を模索し、それはアプリケーションのロギングにも当てはまります。全てのログの重要性が同等ということはありません。あるログはリアルタイムの分析を必要とし、他のログは必要となった時に分析が行えるよう単に長期間保管しておくことを必要としたります。それ故に AWS とパートナーが提供する様々なストレージや分析のツールに容易にログをルーティングできることが重要です。

そこで私たちは Fluent Bit をサポートし、コンテナ化されたアプリケーションから AWS やパートナーのログ保存ソリューション、ログ分析ソリューションへのログストリーム向けに、容易に利用可能な拡張ポイントを作成できるようにします。新しくリリースしたAWSコンテナイメージ向けの Fluent Bit プラグインを用いて、ログを Amazon CloudWatchAmazon Kinesis Data Firehose の送信先 (Amazon S3、Amazon Elasticsearch ServiceAmazon Redshift を含みます)へルーティングすることが可能です。 本投稿では、Fluent Bit プラグインのECS、EKS 両クラスタでの動作をご紹介いたします。ツール自体に慣れていない場合は、こちらの記事(basics of Fluentd and the Kinesis Firehose)にあるチュートリアルを確認いただくと参考になるかもしれません。よろしければ AWS containers roadmap の関連するイシュー #10#66 もご参照ください。

ログルーティングの概要

概念的には、Amazon ECS や EKS のようなコンテナ化された構成でのログルーティングは次のように表されます。

Log routing concept

上記の図の左手では、ログの発生源が描かれています。(下から順に見ていきます)

  1. ホストとコントロールプレーンの階層 はコンテナをホストしている EC2 インスタンスから成ります。インスタンスへ直接のアクセスは可能な場合とそうでない場合があります。例えば、Fargate 上で動作しているコンテナであればご自身の EC2 コンソールではインスタンスを確認することができません。この階層では AWS がマネージしている EKS control plane で生成されるログについても期待されるでしょう。
  2. コンテナランタイム階層 は、Docker エンジンにより生成されるログや、ECS コンテナエージェントログのようなものを含みます。これらのログは大抵の場合、インフラストラクチャの管理者ロールにいる人々にとって最も有用ですが、開発者が状況のトラブルシューティンを行う助けにもなります。
  3. アプリケーション階層 はユーザーのコードが動作します。この階層では各アプリケーション特有のログが生成され、例えばご自身のアプリでの操作結果から生じるログエントリや NGINX のような汎用的なアプリケーションコンポーネントからのアプリログが生成されます。

次にルーティングのコンポーネントがあり、これが Fluent Bit です。全ての発生源からログを読み取り、ログシンクとも呼ばれる様々な送信先へログレコードをルーティングする役を担います。ルーティングコンポーネントはどこかで動作する必要があり、例えば、Kubernetes の Pod や ECS タスクでサイドカーとしてや、ホスト単位のデーモンセットとして動かします。

下流のログシンクでは様々な目的や利用者のためにログを利用します。多くのユースケースがあり、コンプライアンスのためのログ分析で決められた期間のログの保管が求められるものから、イベントの通知をユーザーが受け取る必要がある際のアラート、ユーザーがシステムの全体像を一目で把握できるような(リアルタイムの)グラフを集めたログダッシュボードまであります。

以上のような基本をもとに、具体的なユースケースを見ていきましょう。ここでは Fluent Bit を利用して複数クラスタで動作するアプリケーションのログを集める集中ロギングの例を紹介します。全てのコンテナの定義並びに構成は次の GitHub リポジトリで確認可能です。Amazon ECS Fluent Bit Daemon Service GitHub repo

集中ロギングの動作:複数クラスタのログ分析

Fluent Bit の動作をご確認いただくために、Amazon ECS クラスタ及び Amazon EKS クラスタを対象に、Fluent Bit をデーモンセットとして構成してデプロイし複数クラスタのログ分析を行います。各クラスタ内で動作する NGINX アプリにより生成されるアプリケーションレベルのログが Fluent Bit によりキャプチャされ、Amazon Kinesis Data Firehose を通じて Amazon S3 にストリームされます。Amazon S3 のログは Amazon Athena を用いてクエリすることができます。

Setup of the centralized logging demo app

Amazon ECS 向けのセットアップ

以下のユーザデータを使って EC2 起動タイプの ECS クラスタを作成し、ECSエージェントの Fluentd log driver を有効化します。同内容は enable-fluent-log-driver.sh ファイル(ソース)にあります。

#!/bin/bash
echo "ECS_AVAILABLE_LOGGING_DRIVERS=[\"awslogs\",\"fluentd\"]" >> /etc/ecs/ecs.config

例えば、EC2 起動タイプの ECS クラスタを以下のように作成します。この手順では、ECS CLI がインストールされていることを前提としています。

$ ecs-cli up \
          --size 2 \
          --instance-type t2.medium \
          --extra-user-data enable-fluent-log-driver.sh \
          --keypair fluent-bit-demo-key \
          --capability-iam \
          --cluster-config fluent-bit-demo

続いて、Fluent Bit の設定ファイルを含むコンテナイメージをビルドする必要があります。以下の内容の Dockerfile (ソース) を作成して行います。

FROM amazon/aws-for-fluent-bit:1.2.0
ADD fluent-bit.conf /fluent-bit/etc/
ADD parsers.conf /fluent-bit/etc/

注意 セキュリティベストプラクティスに反して、USER は定義されておらず、root として実行されます。これはFluent Bit が現状 root での実行を必要としているため、意図的にそうしています。

上記の Dockerfile は順に2つの設定ファイルに依存しています。

  • fluent-bit.conf ファイル (ソース)は Kinesis Data Firehose 配信ストリームへのルーティングを定義しています。
  • parsers.conf ファイル(ソース)は NGINX ログのパースを定義しています。

いよいよ私たちのカスタムのコンテナイメージをビルドし、fluent-bit-demo という名前のECR リポジトリへプッシュします。

$ docker build --tag fluent-bit-demo:0.1 .
$ ecs-cli push fluent-bit-demo:0.1

ECR コンソールを開き、カスタムのログルーティングイメージのビルドとプッシュが成功していることを確認します。コンソール上は以下のように表示されるはずです。

Amazon ECR repo with custom Fluent Bit container image

カスタムで構成した Fulent Bit をクラスタへデプロイするために、デーモンスケジュール戦略の ECS サービス を作成します。上記のコンテナイメージを用いて以下のようにデプロイします。

$ aws cloudformation deploy \
      --template-file ecs-fluent-bit-daemonset.yml \
      --stack-name ecs-fluent-bit-daemon-service \
      --parameter-overrides \
      EnvironmentName=fluentbit-daemon-service \
      DockerImage=XXXXXXXXXXXX.dkr.ecr.us-west-2.amazonaws.com/fluent-bit-demo:0.1 \
      Cluster=fluent-bit-demo \
      --region $(aws configure get region) \
      --capabilities CAPABILITY_NAMED_IAM

ECS コンソールでは、以下のように表示されます。

ようやく NGINX を動かす ECS サービスを作成することができます。以下のタスク定義 を用います。

{
    "taskDefinition": {
        "taskDefinitionArn": "arn:aws:ecs:us-west-2:XXXXXXXXXXXX:task-definition/nginx:1",
        "containerDefinitions": [
			{
				"name": "nginx",
				"image": "nginx:1.17",
				"memory": 100,
				"essential": true,
				"portMappings": [
					{
                        "hostPort": 80,
                        "protocol": "tcp",
                        "containerPort": 80
                    }
                ],
                "logConfiguration": {
                    "logDriver": "fluentd",
                    "options": {
                        "fluentd-address": "unix:///var/run/fluent.sock",
                        "tag": "logs-from-nginx"
                    }
                }
            }
        ],
        "family": "nginx"
    }
}

上記のタスク定義を作成した後には、ECS コンソールで以下のように表示されているはずです。

ECS サービスを上記タスク定義を元に作成します。

$ aws ecs create-service \
      --cluster fluent-bit-demo \
      --service-name nginx-svc \
      --task-definition nginx:1 \
      --desired-count 1

全てがうまく進んでいれば、ECSコンソールでは以下のような表示を確認できるでしょう。

Amazon ECS services

これをもって ECS 部分のセットアップは完了しました。Amaozn EKS で動作する Kubernetes クラスタについても同様に構成します。

Amazon EKS 向けのセットアップ

eksctl を用いて fluent-bit-demo という名前の Amzon EKS クラスタを作成します(eksctl の詳細は EKS ドキュメント を参照ください)。以下の内容で、eks-fluent-bit-daemonset-policy.json (ソース) という名前のポリシーファイルを作成します。

{
    "Version": "2012-10-17",
    "Statement": [
		{
			"Effect": "Allow",
			"Action": [
				"firehose:PutRecordBatch"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": "logs:PutLogEvents",
            "Resource": "arn:aws:logs:*:*:log-group:*:*:*"
        },
        {
            "Effect": "Allow",
            "Action": [
				"logs:CreateLogStream",
				"logs:DescribeLogStreams",
				"logs:PutLogEvents"
			],
            "Resource": "arn:aws:logs:*:*:log-group:*"
        },
        {
            "Effect": "Allow",
            "Action": "logs:CreateLogGroup",
            "Resource": "*"
        }
    ]
}

本ポリシーを EC2 上で動作している EKS ワーカーノードへアタッチするには、以下の手順を実行します。

$ STACK_NAME=$(eksctl get nodegroup --cluster fluent-bit-demo -o json | jq -r '.[].StackName')

$ INSTANCE_PROFILE_ARN=$(aws cloudformation describe-stacks --stack-name $STACK_NAME | jq -r '.Stacks[].Outputs[] | select(.OutputKey=="InstanceProfileARN") | .OutputValue')

$ ROLE_NAME=$(aws cloudformation describe-stacks --stack-name $STACK_NAME | jq -r '.Stacks[].Outputs[] | select(.OutputKey=="InstanceRoleARN") | .OutputValue' | cut -f2 -d/)

$ aws iam put-role-policy \
    --role-name $ROLE_NAME \
    --policy-name FluentBit-DS \
    --policy-document file://eks-fluent-bit-ds-policy.json

続いて Kubernetes RBAC 設定、すなわち Fluent Bit ポッドがロール及びロールバインディングとともに利用するサービスアカウントの定義に移ります。
はじめに、kubectl create sa fluent-bit を実行し、fluent-bit というサービスアカウント(後ほどデーモンセット内で利用します)を作成します。
次に、eks-fluent-bit-daemonset-rbac.yaml (ソース) というファイル内にロールとロールバインディングを定義します。

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  name: pod-log-reader
rules:
- apiGroups: [""]
resources:
  - namespaces
  - pods
  verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: pod-log-crb
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: pod-log-reader
subjects:
- kind: ServiceAccount
  name: fluent-bit
  namespace: default

Fluent Bit プラグインへのアクセス許可を有効にするために kubectl apply -f eks-fluent-bit-ds-rbac.yaml コマンドを実行して上記で定義したロールとロールバインディングを作成します。
カスタムイメージの中に構成情報を入れていた ECS の場合と異なり、Kubernetes のセットアップでは config map でログのパースとルーティングを Fluent Bit プラグインに定義します。これには、eks-fluent-bit-configmap.yaml (ソース) ファイルにある以下内容を使います。

apiVersion: v1
kind: ConfigMap
metadata:
  name: fluent-bit-config
  labels:
    app.kubernetes.io/name: fluentbit
data:
  fluent-bit.conf: |
    [SERVICE]
		Parsers_File  parsers.conf
    [INPUT]
		Name              tail
        Tag               kube.*
        Path              /var/log/containers/*.log
        Parser            docker
        DB                /var/log/flb_kube.db
        Mem_Buf_Limit     5MB
        Skip_Long_Lines   On
        Refresh_Interval  10
    [FILTER]
		Name parser
        Match **
        Parser nginx
        Key_Name log
    [OUTPUT]
		Name firehose
        Match **
        delivery_stream eks-stream
        region us-west-2 
  parsers.conf: |
    [PARSER]
		Name   nginx
        Format regex
        Regex ^(?<remote>[^ ]*) (?<host>[^ ]*) (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^\"]*?)(?: +\S*)?)?" (?<code>[^ ]*) (?<size>[^ ]*)(?: "(?<referer>[^\"]*)" "(?<agent>[^\"]*)")? \"-\"$
        Time_Key time
        Time_Format %d/%b/%Y:%H:%M:%S %z

kubectl apply -f eks-fluent-bit-confimap.yaml コマンドでこの ConfigMap を作成します。続いて、Kubernetes デーモンセットを(この ConfigMap を利用して)eks-fluent-bit-daemonset.yaml (ソース) ファイル内に以下ように定義します。

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentbit
  labels:
    app.kubernetes.io/name: fluentbit
spec:
  selector:
    matchLabels:
      name: fluentbit
  template:
    metadata:
      labels:
        name: fluentbit
    spec:
      serviceAccountName: fluent-bit
      containers:
      - name: aws-for-fluent-bit
        image: amazon/aws-for-fluent-bit:1.2.0
        volumeMounts:
        - name: varlog
          mountPath: /var/log
        - name: varlibdockercontainers
          mountPath: /var/lib/docker/containers
          readOnly: true
        - name: fluent-bit-config
          mountPath: /fluent-bit/etc/
        - name: mnt
          mountPath: /mnt
          readOnly: true
        resources:
          limits:
            memory: 500Mi
          requests:
            cpu: 500m
            memory: 100Mi
      volumes:
      - name: varlog
        hostPath:
          path: /var/log
      - name: varlibdockercontainers
        hostPath:
          path: /var/lib/docker/containers
      - name: fluent-bit-config
        configMap:
          name: fluent-bit-config
      - name: mnt
        hostPath:
          path: /mnt

最後に、Fluent Bit デーモンセットを kubectl apply -f eks-fluent-bit-daemonset.yaml を実行して起動します。以下のようにログを覗いてFluent Bit デーモンセットを確認します。

$ kubectl logs ds/fluentbit
Found 3 pods, using pod/fluentbit-9zszm
Fluent Bit v1.1.3
Copyright (C) Treasure Data

[2019/07/08 13:44:54] [ info] [storage] initializing...
[2019/07/08 13:44:54] [ info] [storage] in-memory
[2019/07/08 13:44:54] [ info] [storage] normal synchronization mode, checksum disabled
[2019/07/08 13:44:54] [ info] [engine] started (pid=1)
[2019/07/08 13:44:54] [ info] [in_fw] listening on unix:///var/run/fluent.sock
...
[2019/07/08 13:44:55] [ info] [sp] stream processor started

次に、以下の NGINX アプリkubectl apply -f eks-nginx-app.yaml でデプロイします。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  labels:
    app.kubernetes.io/name: nginx
spec:
  replicas: 4 
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.17
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    targetPort: 80
  selector:
    app: nginx

これにてログ生成とルーティングのセットアップが完了しました。ECS と EKS で動作している NGINX コンテナから収集したログデータ全体に対して実際に何かをするパートへ移りましょう。ログの集中分析を行います。

複数クラスタを対象としたログ分析

目的は、ECS クラスタと EKS クラスタで動作している NGINX コンテナのログ分析を行うことです。Amazon S3 にあるログデータに対し、SQL でインタラクティブにクエリすることができる Amazon Athena を用います。とはいえ、S3のデータにクエリする前に、ログデータを S3 に準備する必要があります。
これまでの ECS と EKS での Fluent Bit の構成で、出力先を delivery_stream xxx-stream としていたことを思い出してください。これは Kinesis Firehose 配信ストリームで、まず ECS と EKS 用に作成する必要があります。
はじめに、Firehose に S3 への書き込みを許可する効力を持つポリシーを定義することで、アクセスコントロール部分のセットアップを行います。2つのポリシーファイルを伴う IAM ロールを作成します。一つは firehose-policy.json (ソース) です。

{
  "Version": "2012-10-17",
  "Statement": {
      "Effect": "Allow",
      "Principal": {
        "Service": "firehose.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
  }
}

2つ目は、firehose-delivery-policy.json (ソース) ポリシーファイル内の XXXXXXXXXXXX を自身のアカウント ID に置き換えます(不明な場合は、aws sts get-caller-identity --output text --query 'Account' を実行することでアカウント ID を取得可能です)。また、S3 の部分で、mh9-firelens-demo を自身の S3 バケット名に置き換えます。
ECS と EKS の配信ストリーム向けに mh9-firelens-demo を作成可能となっています。

$ aws iam create-role \
        --role-name firehose_delivery_role \
        --assume-role-policy-document file://firehose-policy.json

上記のコマンドの結果として出力される JSON から、arn:aws:iam::XXXXXXXXXXXXX:role/firehose_delivery_role のような形式で表されるロール ARN を書き留めます。すぐに配信ストリームの作成に利用しますが、その前に firehose-delivery-policy.json で定義されたポリシーを設定する必要があります。

$ aws iam put-role-policy \
        --role-name firehose_delivery_role \
        --policy-name firehose-fluentbit-s3-streaming \
        --policy-document file://firehose-delivery-policy.json

ECS 向けに配信ストリームを作成します。

$ aws firehose create-delivery-stream \
            --delivery-stream-name ecs-stream \
            --delivery-stream-type DirectPut \
            --s3-destination-configuration \
RoleARN=arn:aws:iam::XXXXXXXXXXXX:role/example_firehose_delivery_role,\
BucketARN="arn:aws:s3:::mh9-firelens-demo",\
Prefix=ecs

注意 上記のコマンドではスペースの有無も重要です:RoleARN 等はスペースなしで一行になければなりません。

初めのステップで作成したロールは再利用して、EKS 向けの配信ストリームのために上記の手順を繰り返します(言い換えると、aws firehose create-delivery-stream コマンドを ecs-streameks-stream に、Prefix=ecsPrefix=eks に置き換えて手順を実行します)。
配信ストリームが作成されてアクティブとなるまでに数分の時間を要します。以下のような状態となったら次の手順に進む準備ができています。

Amazon Kinesis Firehose delivery streams

ECS 及び EKS 上で動いている NGINX コンテナにかける負荷を生成します。ECS 及び EKS 向けの負荷生成ファイルを取得し、下記のコマンドを実行することが可能です。スクリプトを停止するまでの間、それぞれの NGINX サービスを2秒おきに curl します(実行はバックグラウンドで行われます)。

$ ./load-gen-ecs.sh &
$ ./load-gen-eks.sh &

NGINX ウェブサーバーのログデータを得ましたので、S3 のログエントリに対して Athena からクエリすることが可能となりました。まず ECS 及び EKS 向けにテーブルを作成し、Athena に私たちが利用しているスキーマを伝達します。(ECS ログデータ向けを以下に記載します。EKS に関しても同様です。)

CREATE EXTERNAL TABLE fluentbit_ecs (
    agent string,
    code string,
    host string,
    method string,
    path string,
    referer string,
    remote string,
    size string,
    user string
)
ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe'
LOCATION 's3://mh9-firelens-demo/ecs2019/'

注意 Amazon Athena はデータのインポートやインジェストは行わず、S3 のデータへ直接クエリします。したがって、ログデータが Fluent Bit と Firehose 配信ストリームを介して NGINX から S3 バケットへ到着さえすれば、Athena によるクエリの対象とすることができるようになります。

続いて、ECS 及び EKS のログエントリの統合ビュー を以下のSQL文で作成します。

CREATE OR REPLACE VIEW "fluentbit_consolidated" AS
SELECT * , 'ECS' as source
FROM fluentbit_ecs
UNION
SELECT * , 'EKS' as source
FROM fluentbit_eks

これにより2つのテーブル(スキーマは同一)がマージされ、ソースが ECS か EKS かのフラグが追加の列として追加されます。2つのクラスタを跨って NGINX サービスの トップ10のユーザー を確認する SQL クエリを実行することができるようになりました。

SELECT source,
         remote AS IP,
         count(remote) AS num_requests
FROM fluentbit_consolidated
GROUP BY  remote, source
ORDER BY  num_requests DESC LIMIT 10

ここまでの成果として得られたことは以下のようになります:

これで完了です!Fluent Bit プラグインを正しくセットアップし、異なる2つの AWS のマネージドコンテナ環境(ECS 及び EKS)でログ分析を実行しました。
終了後にはそれぞれのワークロードの削除を忘れないようにご注意ください。Kubernetes の NGINX サービス(これによりロードバランサも削除されます)、 EKS 及び ECS クラスタをコンテナとともに削除ください。Kinesis 配信ストリームとログデータの入った S3 バケットも削除すると良いでしょう。
将来に向けては、AWS Fargate、Amazon ECS、Amazon EKS での利用において、さらに Fluent Bit プラグインのインストールと構成をシンプルにする機能の提供に向けて取り組んでいます。本機能については、AWS コンテナロードマップの Issue 10 で状況をフォローすることができます。

パフォーマンスに関するノートと次のステップ

パフォーマンスに関してより良い知見を得るために、私たちはベンチマークテストを実施して、Fluent Bit プラグインと Fluentd を CloudWatch プラグインと Kinesis Firehose プラグインとで比較しました。全てのテストは c5.9xlarge EC2 インスタンス 上で実施しました。以下が結果です。

CloudWatch プラグイン: Fluentd vs Fluent Bit

Log Lines Per second Data Out Fluentd CPU Fluent Bit CPU Fluentd Memory Fluent Bit Memory
100 25 KB/s 0.013 vCPU 0.003 vCPU 146 MB 27 MB
1000 250 KB/s 0.103 vCPU 0.03 vCPU 303 MB 44 MB
10000 2.5 MB/s 1.03 vCPU 0.19 vCPU 376 MB 65 MB

私たちのテストでは、Fluent Bit プラグインは Fluentd よりリソース効率が良いことが示されました。平均で、Fluentd は CPU で4倍以上、メモリで6倍以上を Fluent Bit プラグインと比較して使っています。

Kinesis Firehose プラグイン: Fluentd vs Fluent Bit

Log Lines Per second Data Out Fluentd CPU Fluent Bit CPU Fluentd Memory Fluent Bit Memory
100 25 KB/s 0.006 vCPU 0.003 vCPU 84 MB 27 MB
1000 250 KB/s 0.073 vCPU 0.033 vCPU 102 MB 37 MB
10000 2.5 MB/s 0.86 vCPU 0.13 vCPU 438 MB 55 MB

こちらのベンチマークでは、Fluentd は Fluent Bit プラグインの消費と比較して、CPU で3倍以上、メモリで4倍以上を使いました。皆様のフットプリントは異なっているかもしれず、これらのデータが性能を保証するものでないことはくれぐれもご留意ください。しかしながら、上記のデータは Fluent Bit プラグインが Fluentd より大幅に効率的に動作することを示唆しています。

次のステップ

ぜひご自身のクラスタでお試しください。想定通りの動作とならない場合がありましたら私たちにお知らせいただき、パフォーマンスやフットプリント、ユースケースについての洞察がありましたらそちらもぜひ共有ください。GitHub の issue にコメントを残していただくか、GitHubの AWS containers roadmap で issue をオープンしてもらえればと思います。

翻訳はソリューションアーキテクト川崎が担当しました。原文はこちらです。