Amazon Web Services ブログ

AWS App Mesh と Fluent Bit でアクセスログを簡単に作成

マイクロサービスという用語は、話し相手に応じて異なる意味と利点を持ちます。しかし、私の意見と一致した利点の 1 つは、マイクロサービスにより、チームが各ジョブに最適なツールを自由に選択できるということです。つまり、マイクロサービスアーキテクチャは「サイズが 1 つでもすべてに適合する」アプローチに従う必要がありません。

このアプローチのおかげでチームは独立して作業できますが、その一方でマイクロサービスアーキテクチャの成長に伴い、いくつかの課題に直面する可能性があります。多言語マイクロサービスアーキテクチャの課題の 1 つは、さまざまなアクセスログを一元化されたログソリューションに送信する際に、一貫した形式に関連付けるということです。ログにデータの一貫性がない状態で相互作用しているさまざまなサービスで特定のエラーまたはステータスコードを見つける場合を想像してください。 さらに、そのデータをログソリューションに取り込むために必要なすべての異なるパーサーを維持しようとしていると仮定します。マイクロサービスアーキテクチャが組織にもたらすイノベーションと、市場投入までの時間を短縮できるので、サイクルを無駄にしたくないと思うでしょう。

前述の課題は、AWS App Mesh がサービスメッシュの背後でサービスを統合するのが最適である理由の 1 つにすぎません。App Mesh を使用すると、環境内のさまざまなサービス間で可視性を確保しながら、それらのサービス間の通信を簡単にモニタリング、制御、デバッグすることができます。App Mesh は Envoy をサービスメッシュにあるコンテナの前のプロキシとして活用し、一貫した形式でアクセスログを生成できます。このブログ記事では、AWS App Mesh と Fluent Bit を使用して、マイクロサービスアプリケーション用に構造化された一貫ログ形式を実装する方法を学びます。

Envoy と Envoy アクセスログについて理解する

始める前に、Envoy と App Mesh の基本を説明しましょう。前述のように、App Mesh はオープンソースの Envoy プロキシを使用しており、幅広い AWS Partner Network (APN) テクノロジーパートナーとオープンソースツールと互換性があります。これにより、複数のタイプのコンピューティングインフラストラクチャにわたって構築されたサービスの一貫した可視性とネットワークトラフィック制御を実現します。

アクセスログとそれらのログ形式に関して、Envoy プロキシはアクセスログを生成するときに形式文字列を使用します。これは HTTP リクエストの詳細を含むプレーンな文字列です。以下は、Envoy がアクセスログに使用する形式です。

[%START_TIME%] "%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%"
%RESPONSE_CODE% %RESPONSE_FLAGS% %BYTES_RECEIVED% %BYTES_SENT% %DURATION%
%RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% "%REQ(X-FORWARDED-FOR)%" "%REQ(USER-AGENT)%"
"%REQ(X-REQUEST-ID)%" "%REQ(:AUTHORITY)%" "%UPSTREAM_HOST%"\n

デフォルトの Envoy アクセスログ形式の例を次に示します。

[2016-04-15T20:17:00.310Z] "POST /api/v1/locations HTTP/2" 204 - 154 0 226 100 "10.0.35.28"
"nsq2http" "cc21d9b0-cf5c-432b-8c7e-98aeb7988cd2" "locations" "tcp://10.0.2.1:80"

ご覧のとおり、こちらに豊富な情報があります。ダウンストリームログ収集システムで簡単に検索できるようにするには、これらのメッセージを JSON に構造化する必要があります。基本を理解したところで、2 つの異なる例を見てみましょう。

FireLens の例: AWS App Mesh からの Envoy アクセスログの解析

このでは、AWS App Mesh、Amazon ECS on AWS FargateFireLens for Amazon ECS にある程度精通していることを前提としています。サービスメッシュで実行されているマイクロサービスアプリケーションを示すために、サンプルアプリケーションとして Color App を活用します。

環境を作成したら、App Mesh で Envoy のアクセスログをオンにする必要がありますが、実際にはとても簡単です。App Mesh の仮想ノードに精通していない場合は、ECS または Kubernetes サービスなどの検出可能なサービスへの論理ポインターとしてお考えください。仮想ノードを作成するときに、Envoy アクセスログのパスを設定するオプションがあります。

/dev/stdout にログを記録するコンソールの例を次に示します。Amazon CloudWatch Logs のような送信先に送信するように FireLens を設定できます。

colorteller-black などの仮想ノードのいずれかでこれを実行すると、アクセスログに次のような内容が表示されます。

{
	"log": "[2020-01-23T16:32:40.781Z] \"GET / HTTP/1.1\" 200 - 0 5 0 0 \"-\" \"Go-http-client/1.1\" \"0ed75cb8-a563-9ca3-8ff0-2d8eab307e3e\" \"colorteller.appmesh-demo:9080\" \"127.0.0.1:9080\"\n",
	"stream": "stdout",
	"time": "2020-01-23T16:32:49.400311038Z"
}

ご覧のとおり、ログメッセージのデータは Envoy のデフォルトのアクセスログですが、JSON はエスケープされています。ログメッセージをより有意義に解析するには、Envoy 独自のパーサーを作成する必要があります。このブログを書いた時点では、デフォルトの amazon/aws-for-fluent-bit イメージにも fluent/fluent-bit イメージにも、Envoy アクセスログ用のパーサーは含まれていません。ただし、このパーサーを追加するためのプルリクエストが Fluent Bit 公式プロジェクトと共に送信されています。このような理由から、amazon/aws-for-fluent-bit イメージには /fluent-bit/parsers にいくつかのパーサーファイルが含まれています。これらのパーサーは、Fluent Bit Docker 公式イメージから直接コピーされます。デフォルトで含まれるパーサーを確認するには、Fluent Bit Github リポジトリをご覧ください。

envoy_parser.conf

[PARSER]
    Name envoy
    Format regex
    Regex ^\[(?<start_time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^\"]*?)(?: +\S*)?)? (?<protocol>\S+)" (?[^ ]*) (?<response_flags>[^ ]*) (?<bytes_received>[^ ]*) (?<bytes_sent>[^ ]*) (?<duration>[^ ]*) (?<x_envoy_upstream_service_time>[^ ]*) "(?<x_forwarded_for>[^ ]*)" "(?<user_agent>[^\"]*)" "(?<request_id>[^\"]*)" "(?<authority>[^ ]*)" "(?<upstream_host>[^ ]*)"

ご覧のとおり、先ほど強調したデフォルトの Envoy 形式に一致する正規表現を作成しました。次に、この Dockerfile を使用してカスタム Docker イメージを構築する必要があります。

FROM amazon/aws-for-fluent-bit:latest

ADD conf/* /fluent-bit/conf/
ADD parsers/* /fluent-bit/parsers/

Docker イメージを作成したら、Amazon Elastic Container Registry (ECR) にプッシュする必要があります。ECR にアップロードしたら、タスク定義でカスタム Fluent Bit イメージを参照し、FireLens 固有の値を追加します。Color App の例では、メッシュの各色に対して複数のタスク定義を作成します。以下は、カスタム Fluent Bit イメージを Envoy に結び付けるタスク定義の例です。

appmesh-firelens-colorteller-black-ecs-task-def.json

次に新しい設定の概要を示します。

  • log_router と呼ばれる新しいコンテナを追加しました。ECR でカスタム Fluent Bit イメージを参照し、Envoy のアクセスログを解析するために /fluent-bit/conf/parse_envoy.conf 設定ファイルを使用するように指示しました。
              "image": "012345678910.dkr.ecr.us-east-1.amazonaws.com/aws-for-fluent-bit-custom-envoy:latest",
              "name": "log_router",
              "firelensConfiguration": {
                  "type": "fluentbit",
                  "options": {
                      "enable-ecs-log-metadata": "true",
                      "config-file-type": "file",
                      "config-file-value": "/fluent-bit/conf/parse_envoy.conf"
                  }
              }
  • Envoy コンテナに新しい環境変数 {“name”:”ENVOY_LOG_LEVEL”,”value”:”info”} を追加しました。これは、Envoy コンテナのログレベルを指定できる任意の値です。詳細については、こちらをお読みください。必要に応じて、ENVOY_LOG_LEVELオフに設定し、Envoy アクセスログのみをエクスポートする (他の Envoy コンテナログを無視する) ことを選択できます。そういう訳で、Envoy 自体の問題を診断するために必要であるため、実稼働環境でこれらのログをオフにすることはお勧めしません。
        "environment": [{
            "name": "APPMESH_VIRTUAL_NODE_NAME",
            "value": "mesh/color-mesh/virtualNode/colorteller-black-appmesh-demo"
          },
          {
            "name": "ENVOY_LOG_LEVEL",
            "value": "info"
          }
        ],
  • Envoy コンテナの logDriver を変更して、awsfirelens を使用して Fluent Bit コンテナを接続し、ログを CloudWatch にプッシュします。
        "logConfiguration": {
          "logDriver": "awsfirelens",
          "options": {
            "Name": "cloudwatch",
            "region": "us-east-1",
            "log_group_name": "appmesh-firelens",
            "auto_create_group": "true",
            "log_stream_prefix": "envoy-black-"
          }
        }
  • この時点からは、更新したタスク定義を登録し、サービスを更新して、最新バージョンのタスク定義を使用するのと同じくらい簡単です。その後、CloudWatch ロググループに移動して、解析された Envoy ログを JSON で表示できます。
{
    "authority": "colorteller.appmesh-demo:9080",
    "bytes_received": "0",
    "bytes_sent": "6",
    "code": "200",
    "container_id": "32561e17b9b943cc6a07d8db68d2d0c921fe0e9daafa9e4c7d402fc36eaf3196",
    "container_name": "/ecs-appmesh-firelens-6-envoy-d4b2bcf39bd698b9a101",
    "duration": "0",
    "ecs_cluster": "arn:aws:ecs:us-east-1:012345678910:cluster/appmesh-firelens",
    "ecs_task_arn": "arn:aws:ecs:us-east-1:012345678910:task/b69367b1-d558-4116-9b9f-18dfcae657d1",
    "ecs_task_definition": "appmesh-firelens-colorteller-black:6",
    "method": "GET",
    "path": "/",
    "protocol": "HTTP/1.1",
    "request_id": "3a1957c3-3d47-9259-bdc6-f88ebc4b3da7",
    "response_flags": "-",
    "source": "stdout",
    "start_time": "2020-02-03T19:03:22.305Z",
    "upstream_host": "127.0.0.1:9080",
    "user_agent": "Go-http-client/1.1",
    "x_envoy_upstream_service_time": "0",
    "x_forwarded_for": "-"
}

Fluent Bit の例: AWS App Mesh on Amazon EKS からの Envoy アクセスログの解析

このでは、AWS App Mesh、Amazon EKS、Fluent Bit にある程度精通していることを前提としています。サービスメッシュで実行されているマイクロサービスアプリケーションの説明を行うために、もう一度 Color App を利用します。EKS クラスターを使ってご自分でテストする場合は、必ずドキュメント通りに進めてください。

EKS で実行中のアプリケーションから Envoy をログアウトさせる作業は、上記の FireLens の例と本質的に同じプロセスです。ただし、Envoy ログに入る前に、Kubernetes はログデータのネイティブストレージソリューションを提供しないのでご注意ください。ただし、多くの既存のログソリューションを Kubernetes クラスターに統合できます。したがって、一般的に EKS でログを取り込み、解析、転送する方法として、以下に示すように EKS ワーカーノードに Fluent Bit DaemonSet を実装しています。

以下の例でわかるように、Fluent Bit は入力を介してアプリケーションから出力されたさまざまなログを読み取ります。これらはすべて ConfigMap によって制御されます。コンテナ化されたアプリケーションの移植性を維持するために、イメージコンテンツから設定アーティファクトを疎結合化する強力な方法が提供されます。この例で使用される全体 ConfigMap は、こちらにあります。

  input-kubernetes.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

ConfigMap でパーサーとして Envoy 正規表現を実装するだけで、Fluent Bit が App Mesh からの Envoy ログを理解できます。

    [PARSER]
        Name    envoy
        Format  regex
        Regex ^\[(?<start_time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^\"]*?)(?: +\S*)?)? (?<protocol>\S+)" (?[^ ]*) (?<response_flags>[^ ]*) (?<bytes_received>[^ ]*) (?<bytes_sent>[^ ]*) (?<duration>[^ ]*) (?<x_envoy_upstream_service_time>[^ ]*) "(?<x_forwarded_for>[^ ]*)" "(?<user_agent>[^\"]*)" "(?<request_id>[^\"]*)" "(?<authority>[^ ]*)" "(?<upstream_host>[^ ]*)" Time_Format %Y-%m-%dT%H:%M:%S.%L%z Time_Keep On Time_Key start_time

サンプルの color.yaml を見ると、AWS App Mesh ドキュメントの Color App で使用されているのと同じマニフェストであることがわかります。ただし、次のようにわずかに変更されています。

  • Envoy アクセスログを /dev/stdout に出力するように、App Mesh で colorteller-black VirtualNode を構成します
apiVersion: appmesh.k8s.aws/v1beta1
kind: VirtualNode
metadata:
  name: colorteller-black
  namespace: appmesh-demo
spec:
  meshName: color-mesh
  listeners:
    - portMapping:
        port: 9080
        protocol: http
  serviceDiscovery:
    dns:
      hostName: colorteller-black.appmesh-demo.svc.cluster.local
  logging:
    accessLog:
      file:
        path: /dev/stdout
  • また、fluentbit.io/parser: envoy のアノテーションを colorteller-black デプロイに追加します。
  template:
    metadata:
      labels:
        app: colorteller
        version: black
      annotations:
        fluentbit.io/parser: envoy

このアノテーションは、fluent-bit-configmap.yaml で定義した envoy と呼ばれる事前定義されたパーサーを使用してデータを処理することを示しています。Fluent Bit パーサーの最も強力な機能の 1 つだと思います。設定マップで集中化する代わりに、アノテーションを使用してポッドごとにパーサー設定を提案できるからです。Fluent Bit を結び付けて、App Mesh の Envoy アクセスログを解析する方法です。Fluent Bit で事前定義されたパーサーの詳細については、このリンクを参照してください。

この時点から、colorteller-black Envoy のアクセスログはすべて、CloudWatch の次の例のようになります。これで、ログが log_processed と呼ばれるキーで構造化され、設定マップでも定義されていることがわかります。Fluent Bit ドキュメントの Kubernetes フィルターで詳細をお読みください。

{
    "kubernetes": {
        "annotations": {
            "fluentbit.io/parser": "envoy",
            "kubernetes.io/psp": "eks.privileged"
        },
        "container_hash": "b10687cb4b94ef7aecc0c6e815efb56c8d8889db5316bafc42477acd908a0e91",
        "container_name": "envoy",
        "docker_id": "3be97d47d717ae3ba9937d1bd58b683cdb8b24bd5c66cf7fefeb2ee47c808b08",
        "host": "ip-192-168-10-112.ec2.internal",
        "labels": {
            "app": "colorteller",
            "pod-template-hash": "d868b5bc9",
            "version": "black"
        },
        "namespace_name": "appmesh-demo",
        "pod_id": "42336070-4796-11ea-ac15-0278ee4c2031",
        "pod_name": "colorteller-black-d868b5bc9-zwz28"
    },
    "log": "[2020-02-05T16:37:27.958Z] \"GET / HTTP/1.1\" 200 - 0 5 0 0 \"-\" \"Go-http-client/1.1\" \"6c6ae4b8-60ac-98b6-ba32-a3a4a5c938bf\" \"colorteller.appmesh-demo:9080\" \"127.0.0.1:9080\"\n",
    "log_processed": {
        "authority": "colorteller.appmesh-demo:9080",
        "bytes_received": "0",
        "bytes_sent": "5",
        "code": "200",
        "duration": "0",
        "method": "GET",
        "path": "/",
        "protocol": "HTTP/1.1",
        "request_id": "6c6ae4b8-60ac-98b6-ba32-a3a4a5c938bf",
        "response_flags": "-",
        "start_time": "2020-02-05T16:37:27.958Z",
        "upstream_host": "127.0.0.1:9080",
        "user_agent": "Go-http-client/1.1",
        "x_envoy_upstream_service_time": "0",
        "x_forwarded_for": "-"
    },
    "stream": "stdout",
    "time": "2020-02-05T16:37:33.976469719Z"
}

まとめ

この記事では、AWS App Mesh と Fluent Bit を使用して、マイクロサービスアプリケーションのアクセスログに構造化された一貫ログ形式を実装する方法がいかに簡単かをお見せしました。ご覧のとおり、どのコンテナオーケストレーターを選んでも、チームが選択した言語を使ってもかまいません。App Mesh と Envoy のアクセスログですぐに使用できる機能を活用すると、ご使用の環境に一貫したログ構造を実装するための基盤が得られます。

FireLens の詳細については、Wesley Pettit による素晴らしいオンラインセミナーと、アプリケーションのログ出力を複数のストリームに分割する方法を詳しく説明している Wesley のブログをご覧ください。

ユースケースについてのお問い合わせをお待ちしております。確認したい点がございましたら、AWS コンテナロードマップまたは GitHub の AWS App Mesh ロードマップで問題を公開してください。