アイリッジ開発者ブログ

アイリッジに所属するエンジニアが技術情報を発信していきます。

AWS ELB アクセスログを Datadog Logs に転送するシンプルな方法 w/Datadog Forwarder

f:id:deltarune:20200928104613p:plain

はじめに

プロダクト開発部の井口です。 私たちのプロダクトでは、システムを監視するための仕組みとして Datadog という SaaS を導入しています。 クラウドインフラにおけるログの一元的なモニタリング環境の整備を進めているのですが、今回はタイトルにもあるように AWS ELB アクセスログの転送方法をご紹介します。

Datadog のセットアップ

Integration のインストール

左メニューの Integrations -> Integrations から Amazon Web Services をインストールします。 詳細はDatadog 公式リファレンスにおける Setup を参照してください。

API Key の発行

左メニューの Integrations -> APIs を開いてください。 API KeysNew API key に一意な名称を入力し、Create API Key ボタンを押下します。 API Key は環境毎に発行するのが望ましいです。

Logs におけるパイプラインの構築

Datadog の機能を使って、転送されてきたログを処理するためのパイプラインを構築します。 左メニューの Logs -> Configuration を開いてください。 開いた先にある Pipelines に、パイプラインを追加します。 今回は Datadog 側が用意しているパイプラインをそのまま使用します。 右上の Browse Pipeline Library から AWS ELB Access を検索し、クローンします。 これだけで、ログのパースなどを自動で実行してくれるようになります。

以上で Datadog 側のセットアップは完了です。

AWS ELB アクセスログ作成の有効化

AWS が提供しているロードバランサには、指定した S3 バケットにアクセスログを書き込んでくれる便利なオプションがあります。 AWS コンソール、あるいは AWS CLI からその設定が可能です。 詳細はAWS 公式リファレンスにおける Enable access logging を参照してください。

Datadog Forwarder のインストール

f:id:deltarune:20201011164217p:plain

Datadog Forwarder とは、ログやカスタムメトリクスなどを Datadog に転送するために用意された AWS Lambda 関数です。

フォワーダをインストールする方法として CloudFormation, Terraform, Manual の3つがあります。 CloudFormation でインストールする場合、各種オプションを AWS コンソール上で設定するだけで簡単に完了できます。 今回は、各種オプションや後述する Lambda のトリガー設定を含めた全てをコードベースで管理するために Terraform を用いてインストールします。

先に述べたように、フォワーダの実体は AWS Lambda 関数です。 そして、この関数は CloudFormation スタックのリソースとして管理されています。 この CloudFormation スタックを、Terraform の管理下にて展開していく流れとなります。

ディレクトリ構成は以下の通りです。

├── envs
│   ├── prd
│   │   ├── dd_forwarder_init
│   │   │   ├── backend.tf
│   │   │   ├── main.tf
│   │   │   └── variables.tf
│   │   └── dd_forwarder_trigger
│   │       ├── backend.tf
│   │       ├── main.tf
│   │       └── variables.tf
│   └── stg
│       ├── dd_forwarder_init
│       │   ├── backend.tf
│       │   ├── main.tf
│       │   └── variables.tf
│       └── dd_forwarder_trigger
│           ├── backend.tf
│           ├── main.tf
│           └── variables.tf
└── modules
    ├── dd_forwarder_init
    │   ├── main.tf
    │   └── variables.tf
    └── dd_forwarder_trigger
        ├── main.tf
        └── variables.tf

環境を切り分ける手段として、Terraform v0.10 で導入された Workspaces という機能を使う選択肢もあります。

今回は弊プロダクトにおける既存の Terraform リソースに追加するかたちでフォワーダを導入したので、ディレクトリレベルで環境を切り分けています。

それでは、各モジュールと、その呼び出しについて説明します。

module: dd_forwarder_init

対象ファイル: modules/dd_forwarder_init/main.tf

resource "aws_cloudformation_stack" "datadog_forwarder" {
  name         = "datadog-forwarder-${var.env}"
  capabilities = ["CAPABILITY_IAM", "CAPABILITY_NAMED_IAM", "CAPABILITY_AUTO_EXPAND"]
  parameters = {
    DdApiKey          = "this_value_is_not_used"
    DdApiKeySecretArn = aws_secretsmanager_secret.dd_api_key.arn
    FunctionName      = "datadog-forwarder-${var.env}"
    DdTags            = "env:${var.env}"
    IncludeAtMatch    = var.include_at_match
  }
  template_url = "https://datadog-cloudformation-template.s3.amazonaws.com/aws/forwarder/3.17.0.yaml"
}

resource "aws_secretsmanager_secret" "dd_api_key" {
  name                    = "datadog/api_key-${var.env}"
  description             = "Encrypted Datadog API Key"
  recovery_window_in_days = 0
}

resource "aws_secretsmanager_secret_version" "dd_api_key" {
  secret_id     = aws_secretsmanager_secret.dd_api_key.id
  secret_string = var.dd_api_key
}

ここでは、各環境から共通して使用されるリソースの記述をしています。 公式のインストールガイドにもあるように、API キーのコンフィギュレーションを分離するとフォワーダの更新時に dd_api_key を再度入力する手間が省けます。 本稿では構成の単純化のため、同一ファイルに記述することとします。

たったこれだけの記述で、指定したテンプレートを元に CloudFormation のスタックを構築できます。 また、AWS Secrets Manager を利用することで API キーをセキュアに管理できます。 aws_secretsmanager_secret リソースにおける recovery_window_in_days では、シークレットを削除する際に設けられる復旧期間を設定しています。 0 を指定することで、即時での削除が可能となります。 運用に合わせて数値を設定してください。

aws_cloudformation_stack リソースにおける IncludeAtMatch では、転送対象のログをホワイトリスト方式でフィルタリングするためのオプションを設定しています。 指定した正規表現に一致するログのみ、Datadog 側に転送されることとなります。 以下をご覧ください。


対象ファイル: modules/dd_forwarder_init/variables.tf

variable "env" {}

variable "dd_api_key" {}

variable "include_at_match" {
  default = "(?:\\S+\\s)?\\d{4}\\-\\d{2}\\-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{6}Z\\s(?:\\S+\\s){6}5\\d{2}\\s.+"
}

上記のうちの include_at_match が、ログのフィルタリングに用いられる正規表現です。 AWS が提供しているロードバランサのうち、ALBCLB のログフォーマットに適用可能です。 また、このフィルタによって、転送するログを 5xx エラー のみに絞り込んでいます。 適宜、必要なログのみを転送するよう設定してください。

転送対象のログをブラックリスト方式でフィルタリングするためには ExcludeAtMatch オプションを使用します。


モジュールの呼び出しは、以下の通りです。

対象ファイル: envs/prd/dd_forwarder_init/main.tf

provider "aws" {
  region  = "ap-northeast-1"
  profile = "prd"
}

module "dd_forwarder_init" {
  source = "../../../modules/dd_forwarder_init"

  env        = var.env
  dd_api_key = var.dd_api_key
}

対象ファイル: envs/prd/dd_forwarder_init/variables.tf

variable "env" {
  default = "prd"
}

variable "dd_api_key" {
  type        = string
  description = "Datadog API key"
}

dd_api_key は、terraform apply 時にインタラクティブに入力することになります。


dd_forwarder_init モジュールを apply することで、指定した環境に CloudFormation スタックが展開されました。 AWS コンソールから実際に作成されたリソースを確認してみてください。 ただ、これだけではフォワーダは動作しません。 次に、フォワーダに対してトリガーを設定しましょう。 フォワーダの実体は AWS Lambda 関数ですので、この関数にトリガーを紐付けていくことになります。

module: dd_forwarder_trigger

対象ファイル: modules/dd_forwarder_trigger/main.tf

# CloudFormation テンプレート内で設定した Export の取得
# ${AWS::StackName}-ForwarderArn
data "aws_cloudformation_export" "lambda_arn" {
  name = "datadog-forwarder-${var.env}-ForwarderArn"
}

resource "aws_s3_bucket_notification" "bucket_notification" {
  bucket = "logs-${var.env}"

  dynamic "lambda_function" {
    for_each = var.bucket_prefixes

    content {
      lambda_function_arn = data.aws_cloudformation_export.lambda_arn.value
      events              = ["s3:ObjectCreated:*"]
      filter_prefix       = lambda_function.value
    }
  }
}

data "aws_s3_bucket" "access_log" {
  bucket = "logs-${var.env}"
}

resource "aws_lambda_permission" "allow_bucket" {
  statement_id  = "AllowExecutionFromS3Bucket"
  action        = "lambda:InvokeFunction"
  function_name = data.aws_cloudformation_export.lambda_arn.value
  principal     = "s3.amazonaws.com"
  source_arn    = data.aws_s3_bucket.access_log.arn
}

aws_s3_bucket_notification リソースで、S3 バケットにおけるオブジェクト作成イベントを Lambda に対して通知するよう設定しています。 弊プロダクトでは、ロードバランサのアクセスログを書き出すバケットを一つに集約し、サービス毎にプレフィックスを定めてディレクトリを切っています。 ここでは、プレフィックスのリストの数だけループさせるために dynamic ブロックを使用しています。 ブロック内の要素を動的にイテレートして生成しています。 これは Terraform v0.12 で導入された機能です。 バージョンにご注意ください。

aws_lambda_permission リソースで、ログ収集対象の S3 バケットに対する Lambda の実行権限を付与しています。


対象ファイル: modules/dd_forwarder_trigger/variables.tf

variable "env" {}

variable "bucket_prefixes" {}

モジュールの呼び出しは、以下の通りです。

対象ファイル: envs/prd/dd_forwarder_trigger/main.tf

provider "aws" {
  region  = "ap-northeast-1"
  profile = "prd"
}

module "dd_forwarder_trigger" {
  source = "../../../modules/dd_forwarder_trigger"

  env             = var.env
  bucket_prefixes = var.bucket_prefixes
}

対象ファイル: envs/prd/dd_forwarder_trigger/variables.tf

variable "env" {
  default = "prd"
}

variable "bucket_prefixes" {
  default = [
    "alb-1/",
    "alb-2/",
    "alb-3/",
    "clb-1/",
    "clb-2/",
    "clb-3/",
  ]
}

トリガーしたいディレクトリに合致するプレフィックスを bucket_prefixes リストに記載します。 今後、トリガーを追加・削除する場合もこのリストをメンテナンスするだけで実現できます。

おわりに

Datadog が用意しているフォワーダとパイプラインの強力さが伝わったかと思います。 今回は Infrastructure as Code を実現するために Terraform を用いましたが、用意された CFn テンプレートを AWS コンソール上からデプロイすることで、ノンコードでの構築も可能です。 Datadog Forwarder の機能は S3 バケットからのログ転送だけに留まらないので、日々の運用・監視の仕組みの中に是非とも導入してみてください。