BASEプロダクトチームブログ

ネットショップ作成サービス「BASE ( https://thebase.in )」、ショッピングアプリ「BASE ( https://thebase.in/sp )」のプロダクトチームによるブログです。

Amazon Elasticsearch ServiceによるECSアプリケーションのログ解析基盤の構築

こんにちは、BASE BANK 株式会社 Dev Division でエンジニアとしてインターンをしている前川です。

今回、Amazon Elasticsearch Service(以下、Amazon ES)による、ECS/Fargate で稼働するアプリケーションのログデータの解析基盤を新規で構築することになったので、構築するにあたって調査した内容や関連する内容、実際におこなった構築方法についていくつか紹介します。

今回の構築の簡単な全体構成図は次のようになります。

f:id:ykzeee:20201126141541j:plain

今回は、

  • ECS/Fargate のログを S3 にルーティングする
  • Amazon ES にログをルーティングする
  • VPC アクセスの Amazon ES を構築し、Kibana を外部からアクセスできるようにする

の3つの手順にわけて、構築方法や関連する内容について紹介していきたいと思います。

なお、この記事で取り扱っている各ツール・サービスのバージョンは次のとおりです。

Terraform: v0.12.5
Terraform provider.aws v3.6.0
SAM CLI: version 1.7.0
AWS CLI: aws-cli/2.0.61 Python/3.9.0 Darwin/19.3.0 source/x86_64
Fargate platform version: 1.4.0

FireLens で ECS/Fargate のログを S3 にルーティングする

FireLens について

ECS のコンテナの標準出力/標準エラー出力に出力されたログを S3 にルーティングするために、FireLens をログドライバーとして使用しました。

FireLens を使用することで、ECS で出力されたログを、サイドカーとして実行された Fluentd や Fluent Bit のログルーターコンテナにルーティングすることができます。

別のアプリケーションでは、S3 へのログのルーティングは、CloudWatch Logs 経由で行っていましたが、FilreLens を用いた方法によって、CloudWatch Logs のコストを抑えることや、カスタム設定を用いた柔軟なログの取り扱いができるようになりました。

Kinesis Data Firehose を用いて ECS/Fargate のログを S3 へのルーティングする

今回の構築では Kinesis Data Firehose 経由で S3 にログをルーティングしました。

f:id:ykzeee:20201126142122j:plain

まずはじめに、Kinesis Data Firehose と S3 の構築をします。

Terraform での定義例は次のようになります。

resource "aws_kinesis_firehose_delivery_stream" "sample" {
  destination = "s3"
  name        = "sample"

  s3_configuration {
    bucket_arn = aws_s3_bucket.sample.arn
    role_arn   = aws_iam_role.sample-kinesis-firehose-iam-role.arn
    prefix     = "sample/"
  }
}

resource "aws_iam_role" "sample-kinesis-firehose-iam-role" {
  name = "sample-kinesis-firehose"
  assume_role_policy = jsonencode(
    {
      Version = "2012-10-17",
      Statement = [
        {
          Sid    = "",
          Effect = "Allow",
          Principal = {
            Service = "firehose.amazonaws.com"
          },
          Action = "sts:AssumeRole"
        }
      ]
    }
  )
}

resource "aws_iam_policy" "sample-kinesis-firehose-iam-policy" {
  name = "sample-kinesis-firehose-iam-policy"
  policy = jsonencode(
    {
      Version = "2012-10-17"
      Statement = [
        {
          Sid    = ""
          Effect = "Allow"
          Action = [
            "s3:AbortMultipartUpload",
            "s3:GetBucketLocation",
            "s3:GetObject",
            "s3:ListBucket",
            "s3:ListBucketMultiPartUploads",
            "s3:PutObject",
          ]
          Resource = [
            aws_s3_bucket.sample.arn,
            "${aws_s3_bucket.sample.arn}/*",
          ]
        }
      ]
    }
  )
}

resource "aws_iam_role_policy_attachment" "sample-kinesis-firehose-iam-role-attach" {
  role       = aws_iam_role.sample-kinesis-firehose-iam-role.name
  policy_arn = aws_iam_policy.sample-kinesis-firehose-iam-policy.arn
}

resource "aws_s3_bucket" "sample" {
  bucket = "sample"
  acl    = "private"
}

Kinesis Data Firehose に対しては、データを送信する S3 の設定と、その S3 を操作するために割り当てる IAM ロールを作成しています。

次に、ECS のタスク内へ FireLens コンテナを追加し、サイドカー構成とするために、ECS のタスク定義を修正する必要があります。

ログを出力するアプリケーションコンテナにログドライバーを次のように設定します。

"logConfiguration": {
        "logDriver": "awsfirelens",
        "options": {
          "Name": "firehose",
          "region": "ap-northeast-1",
          "delivery_stream": aws_kinesis_firehose_delivery_stream.sample.name
        }
}

delivery_stream のところには、先程構築した Kinesis Data Firehose の name で設定したものを使用します。

次に FireLens 設定を含むログルーターコンテナを追加します。

定義例は次のようになります。

{
  "name": "log-router",
  "image": "906394416424.dkr.ecr.ap-northeast-1.amazonaws.com/aws-for-fluent-bit:latest",
  "essential": true,
  "firelensConfiguration": {
    "type": "fluentbit"
  }
}

最後に、タスクロールを修正し、FireLens コンテナが Kinesis Data Firehose へとストリームを送信できるようにします。

ポリシーの定義例は次の通りです。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "firehose:PutRecordBatch",
      "Resource": "*"
    }
  ]
}

以上の設定で、ECS のログを S3 に出力することができます。

ECS/Fargate のログを S3 に直接ルーティングする方法について

また今回の構築では、S3 へのログの転送に Kinesis Data Firehose を使用しましたが、Fluent Bit が v1.6 より、コンテナログをルーティングする送信先として S3 をサポートし、Kinesis Data Firehose を経由せずに直接 S3 にログを転送することができるようになったようです。

aws.amazon.com

ただし、デフォルトの設定である S3 のマルチパートアップロード API を使用し、この方法を使う場合は、Fluent Bit のインスタンスが、データのバッファリングのために永続的なディスクを必要とするので注意が必要です。

このディスクは、マルチパートアップロード API で送信するデータのチャンクをバッファリングするために使用され、もし Fluent Bit が不意に停止した場合に、同じディスクで再起動し、未完了のアップロード処理を完了するために必要だということです。

今回のような ECS/Fargate の環境で使用する場合は、Amazon EFS ファイルシステムを ECS タスクで使用することが推奨されているようでした。

また、永続的なディスクを使用しない場合に、S3 の PutObject API を使用し、データをバッファリングせずに頻繁に送信する方法によって、データの損失を抑える設定もあるようでした。

信頼性を重視する場合には、Kinesis Data Firehose を Fluent Bit と S3 間のバッファとして使用する方法が推奨されているようでしたので、S3 にログを転送する場合には、特別な理由がない限り Kinesis Data Firehose を経由する方法でおこなうのが良いのかなと思いました。

github.com

Amazon Elasticsearch Service にログをルーティングする

Kibana を用いたログの調査環境を構築するために、S3 にルーティングされたログを Amazon ES に転送する Lambda を作成しました。

f:id:ykzeee:20201126142438j:plain

Lambda は S3 の PUT をトリガーにして起動し、受け取った event の情報を用いて取得した S3 からの対象のログデータを加工し、Amazon ES に転送します。

BASE BANK チームでは、AWS SAM CLI を使用した Lambda の運用をおこなっており、今回の Lambda の環境構築にあたっても AWS SAM CLI を使用しました。

AWS SAM CLI を用いた Lambda の環境構築方法に関しては、同僚の永野が書いた下記エントリをご参照ください。

devblog.thebase.in

AWS SAM CLI で Lambda を管理する場合は、AWS SAM のテンプレートファイルでトリガーとなるイベントの設定やロール、環境変数、VPC 周りの設定をおこないます。

BASE BANK チームでは、AWS リソースの管理を Terraform で行っているので、Lambda リソースの管理のみを SAM を用いて Cloud Formation でおこない、arn などを通してトリガーの対象となるリソースを参照し、テンプレートファイルを記述していました。

今回 Lambda のトリガーの対象となる ECS からのログが保存されている S3 についても、当初は Terraform で管理していたのですが、AWS SAM CLI で Lambda を構築し、トリガーの対象を S3 とする場合は、S3 を Lambda と同じテンプレートファイルで定義しなければならないという制約がありました。

github.com

よって、Terraform で管理されていた S3 を、AWS SAM のテンプレートファイルで定義することで CloudFormation で管理するようにし、Terraform 側では、Data Resource として S3 を参照するように修正しました。

SAM テンプレートファイルのリソースセクションの作成例は次のようになります。

Resources:
  LogToEsFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: log_to_es/
      Handler: app.lambda_handler
      Runtime: python3.8
      Events:
        BucketEvent:
          Type: S3
          Properties:
            Bucket: !Ref LogBucket
            Events: s3:ObjectCreated:Put
            Filter:
              S3Key:
                Rules:
                  - Name: prefix
                    Value: log_prefix/
      Role: !Ref ExecutionRole
      Environment:
        Variables:
          ES_DOMAIN_URL: !Ref EsDomainUrl
      VpcConfig:
        SecurityGroupIds: !Ref SecurityGroupIds
        SubnetIds: !Ref SubnetIds

  LogBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Ref S3Bucket

S3 に保存された ECS のログを、Lambda を使用して Amazon ES に転送する方法を今回は使いましたが、ECS のログを Amazon ES にルーティングする方法として、FireLens を使用することで Fluent Bit が直接ログデータを Amazon ES にルーティングすることもできるようになったようです。

aws.amazon.com

こちらの方法を使う場合は、Amazon ES へ送信するデータの加工についてや、ログデータが欠損した場合の考慮などを考える必要がありそうでしたが、手軽に Amazon ES にログデータをルーティングできるのは良さそうだと思いました。

Amazon Elasticsearch Service の構築

Amazon ES の VPC アクセスでの構築と、外部から Kibana にアクセスするための環境構築をおこないました。

Amazon ES の使い分けとして、

  • Lambda による、Elasticsearch API を用いたログデータの投入
  • Kibana によるログデータの解析のためのアクセス

があります。

今回は、VPC アクセスによる構築により、Amazon ES に対するセキュリティの強化、Amazon ES と Lambda 間の安全な通信を実現することができましたが、一方で、Kibana に対しては適切なアクセス制限をした上で、データ解析のための外部からのアクセスをする必要があったので、ALB と ECS のリバースプロキシサーバーを使用した環境構築をおこないました。

Amazon Elasticsearch Service の Terraform による構築

今回 Amazon ES は、Terraform で構築しました。

Amazon ES を Terraform で構築する場合の、基本的な設定をした定義例は次のようになります。

resource "aws_elasticsearch_domain" "es" {
  domain_name           = "sample"
  elasticsearch_version = "6.3"

  cluster_config {
    instance_type  = "t2.small.elasticsearch"
    instance_count = 1
  }

  vpc_options {
    subnet_ids = var.es_subnet_ids

    security_group_ids = var.es_security_group_ids
  }

  ebs_options {
    ebs_enabled = true
    volume_size = 10
  }
}

この例では、Elastic Search の version や、インスタンスタイプの設定、サブネットやセキュリティグループの VPC 周りの設定をしています。

Amazon ES のセキュリティに関しては、

  • VPC アクセス設定によるネットワークに関するセキュリティレイヤー
  • ドメインアクセスポリシーによる、リソースベースのセキュリティレイヤー
  • Fine Grained Access Control による、ロールベースの細かいアクセスコントロールによるセキュリティレイヤー

の 3 つの主要なセキュリティレイヤーがあり、このうち Terraform によるドメインアクセスポリシーの定義例は次のようになります。

resource "aws_elasticsearch_domain_policy" "es" {
  domain_name = aws_elasticsearch_domain.es.domain_name

  access_policies = jsonencode(
    {
      Version : "2012-10-17",
      Statement : [
        {
          Effect : "Allow",
          Principal : {
            AWS : "*"
          },
          Action : "es:*",
          Resource : "${aws_elasticsearch_domain.es.arn}/*"
        }
      ]
    }
  )
}

ドメインアクセスポリシーの設定については、aws_elasticsearch_domain 内の access_policies でも設定することができますが、Terraform では、リソースの定義内で自らを参照することができないので、今回の場合は上記のように別リソースで定義しました。

BASE BANK チームでは、Terraform のセキュリティ静的解析ツールである tfsec を導入していて、今回の構築にあたって、tfsec の指摘により設定の検討をした項目がいくつかありました。

tfsec についてや、検討した項目に関しては、同僚の東口が書いた下記エントリをご参照ください。

devblog.thebase.in

Amazon ES の構築にあたって注意すべき点として、既存のドメインには設定できず、新規で構築する際にしか設定できない項目や、インスタンスタイプや Elasticsearch のバージョンによっては設定できない項目が存在するというのがありました。

例えば、Audit Logs の有効化があります。

Amazon ES では、Audit Logs を使用することで、Elasticsearch へのすべてのリクエストのロギング、インデックスの変更、受信検索クエリの記録などのあらゆるユーザーアクティビティのログが記録できるようになりました。

aws.amazon.com

しかし、この項目を設定するには、既存、または新規の Amazon ES で、Elasticsearch のバージョンが 6.7 以降であり、Fine Grained Access Control が有効になっている必要があります。

Fine Grained Access Control とは、ロールベースのアクセスコントロールによる、クラスターレベル、インデックスレベル、ドキュメントレベル、フィールドレベルの細かいアクセス許可や、Kibana マルチテナンシーの使用などができるようになるものです。

この設定を有効化するには、保管時のデータの暗号化ノード間の暗号化が有効になっており、ドメインへのすべてのトラフィックに HTTPS を要求する設定が有効になっていなければなりません。

このうち、保管時のデータの暗号化と、ノード間の暗号化、Elasticsearch のバージョンに関しては、既存のドメインに対して変更することができないので、変更したい場合はドメインを新規に作成する必要があります。

また、保管時のデータの暗号化に関しては、インスタンスタイプによっては設定できないので注意が必要です。

docs.aws.amazon.com

ECS のリバースプロキシサーバーを使用した Kibana によるデータ解析環境構築

VPC アクセスによって構築された Amazon ES において、Kibana への外部からのアクセスをする方法はいくつかありますが、今回は ALB と ECS のリバースプロキシサーバーを使用した方法により環境構築しました。

ECS のリバースプロキシサーバーを経由せずに、ALB から直接アクセスすることもできますが、Amazon ES の Private IP の変更を定期的にメンテナンスする必要があり不便です。

ECS のリバースプロキシサーバーを経由することで、Amazon ES のホスト名を使って設定ができるので、定期的なメンテナンスが不要になり、より柔軟な設定をすることもできるかと思います。

今回の構築では、ECS のリバースプロキシサーバーは、nginx のイメージを使用し、設定ファイルを置き換えるだけの簡素な方法でおこないました。

f:id:ykzeee:20201126142747j:plain

ECS デプロイツールについて

ECS のデプロイ方法についてはいくつか検討した上で、ECS CLI によるデプロイ環境を構築しました。

docs.aws.amazon.com

ECS CLI は、ECS クラスターおよびタスクの作成、更新を、Docker Compose ファイルを利用しておこなえるツールです。

ECS CLI では、ALB やセキュリティグループなどのリソースの作成、管理ができませんが、今回は Terraform で ECS サービス、タスク以外の必要な AWS リソースが管理されていたので容易に導入することができました。

また、Docker Compose と Amazon ECS の統合により、Docker コマンドラインを使用し ECS へのアプリケーションのデプロイ ができるようになりました。

aws.amazon.com

Docker Compose で構築された既存のアプリケーションの拡張や、Docker Compose を利用する ECS 開発者の開発体験の向上が図れるようになるようなので、機会があれば ECS CLI との違いについても含めて調査してみたいと思いました。

デプロイ方法を検討する中で、AWS Copilot CLI についても調査しました。

aws.github.io

AWS Copilot CLI は、最低限 Dockerfile さえあれば、ECS クラスターやサービス、タスクに加えて、VPC やサブネット、ALB などのその他必要な AWS リソースを作成と、複数の AWS アカウントや複数の環境に対するデプロイのサポートまでおこなってくれる、かなり抽象度の高いツールです。

一方で、v0.3 から既存の VPC やサブネットの設定ができるようになりましたが、細かい設定についてはまだまだできない状況です。

aws.amazon.com

既存の ALB を設定するなど、その他の細かい設定ができない状況から今回は導入を見送りましたが、ECS でプロトタイプを動かしたり、技術検証をしたりすることが簡単にできる強力なツールだと思いますので、今後の進化に期待しつつ、使っていきたいと思いました。

おわりに

Amazon ES を用いた、ECS/Fargate アプリケーションのログ解析基盤の構築例と、それに関連する内容について紹介させていただきました。

これから ECS/Fargate アプリケーションのログ解析基盤の環境構築をされる方々の助けになれば幸いです。

また今回の構築にあたっては、AWS のアップデートのサイクルの早さをとても感じました。

次々と新しい機能がリリースされるので、定期的にキャッチアップしたり、環境構築するたびに新しい技術の検証をしていかないとなと思いました。