/ #CloudFront 

僕がほしいのはCloudFrontのエラー数なのだよ。エラー率ではない

もくじ

CloudFront のエラー数が見たい。エラー率ではない。エラー数が見たい。そういう人は僕だけではないはず。。。

今回は Datadog を使って CloudFront のエラー数を取得する方法を紹介します。

はじめに

あけましておめでとうございます。今年もよろしくお願いします。

たとえば CloudFront → API Gateway とかを使って API をホストしているとします。このサービスの可用性(ここではエラー率)を監視したいときにどうすればいいでしょうか?

可用性なのでエラー数と総リクエスト数がほしいですね。API Gateway なら CloudWatch のメトリクスからどちらも取れるので簡単です。では CloudFront ならどうでしょうか?

CloudFront はリクエストの総数はあるのですがエラー数がありません。かわりに 1 分間でスライスしたエラー率があります。この値をそのまま可用性と考えても悪くはないと思います。しかしこれは SLO とかを決めようとなったときに問題が発生します。なぜならこれではメトリクスのスコープが 1 分間で固定されてしまうからです。これは  Datadog の SLO 機能を使う場合で考えると、Datadog は統計量に対する SLO の評価が 5 分のスライスでしか設定できません。なので1分のエラー率を5分のスライスで表現する必要があります。しかしそれではどうやっても正確なものにはなりません。ここで必要なのは 5 分間のリクエスト数と、エラー数なのです。

また CloudFront では取得できないなら、諦めて API Gateway のメトリクス使えば良くない?という話もあります。しかしより Client の近くで監視した方が現実に近い値をえることができるので、可能であれば CloudFront のメトリクスを使った方が良いと考えます。例えば API Gateway のメトリクスだけ見てたら CloudFront と WAF で思いっきりリクエストを弾きまくっていた、、、みたいなときに検知することができません。

前提

  1. Datadog の AWS のインテグレーションは完了している
  2. DatadogForwarder がデプロイされている

では 作っていきましょう 👻

1. サンプルのインフラをデプロイする

早速ですが、今回サンプルで使うインフラをデプロイします。

import aws_cdk as cdk
from aws_cdk import aws_cloudfront as cloudfront
from aws_cdk import aws_cloudfront_origins as origins
from aws_cdk import aws_s3 as s3

app = cdk.App()
stack = cdk.Stack(app, "cloud-front-error-metrics-stack")

# バケットを作成する
bucket = s3.Bucket(
    stack,
    "DummyBucket",
    block_public_access=s3.BlockPublicAccess.BLOCK_ALL,
    removal_policy=cdk.RemovalPolicy.DESTROY,
    auto_delete_objects=True,
)
log_bucket = s3.Bucket(
    stack,
    "LogBucket",
    access_control=s3.BucketAccessControl.LOG_DELIVERY_WRITE,
    block_public_access=s3.BlockPublicAccess.BLOCK_ALL,
    removal_policy=cdk.RemovalPolicy.DESTROY,
    auto_delete_objects=True,
)

cf_function = cloudfront.Function(stack, "Function", code=cloudfront.FunctionCode.from_file(file_path="function.js"))

# CloudFrontのディストリビューションを作成する
distribution = cloudfront.Distribution(
    stack,
    "Distribution",
    default_behavior=cloudfront.BehaviorOptions(
        origin=origins.S3Origin(bucket),
        function_associations=[
            cloudfront.FunctionAssociation(
                function=cf_function, event_type=cloudfront.FunctionEventType.VIEWER_REQUEST
            ),
        ],
    ),
    default_root_object="index.html",
    log_bucket=log_bucket,
    log_file_prefix="cloudfront",  # DatadogのログをCloudFrontのログとして扱うために、ログのプレフィックスを指定する
)

app.synth()

こんな感じです。CloudFrontFunction の中身は ↓ な感じ。

function handler(event) {
  var statusCode =
    (event.request.querystring.code && event.request.querystring.code.value) ||
    400;
  return {
    statusCode: Number(statusCode),
  };
}

下記なコマンドでデプロイしていただけれな OK です。

cdk -a "python app.py" deploy

すると ↓ のようなインフラがデプロイされます。

この構成ですが以前の記事で紹介したものに、アクセスログ用のバケットが追加されただけでそれ以外ほぼ一緒ですので説明を省かせていただきます 🙇‍♂

function.jsの中身に関しては、動作確認の目的でレスポンスのステータスコードをクエリーパラメーターで制御できるようにしてます。

2. DatadogForwarder にアクセスログのファイルを送る

事前にデプロイ済みである DatadogForwarder の Lambda のトリガーに、さっきデプロイしたアクセスロブの S3 バケットを設定します。

↓ な感じの設定で OK です。

3. 動作確認する

Datadog にログが送られることを確認するためにリクエストしてみます。

今回は先程デプロイした CloudFront のエンドポイントがhttps://d379kflkri7vkx.cloudfront.net/だったので ↓ のようなコマンドを投げました。

$ curl -w "%{http_code}" "https://d379kflkri7vkx.cloudfront.net?code=200"
> 200

$ curl -w "%{http_code}" "https://d379kflkri7vkx.cloudfront.net?code=500"
> 500

$ curl -w "%{http_code}" "https://d379kflkri7vkx.cloudfront.net?code=400"
> 400

リクエスト数を稼ぐために何回か叩いてみます。

次に Datadog へ行ってログレコードを確認します。 Datadog の Logs のコンソールで@service:cloudfrontとかで検索してみます。

こんな感じでログが表示されました。

ステータスコードもいい感じにパースされています。(もしパースされてなかったら pipeline を見直し見てるといいかもです。CloudFront のログは W2C 形式ですが、Datadog 側がいい感じの Pipeline を用意してくれているので自分で何かしら用意する必要はありません。)

あとはこのアクセスログをステータスコードごとに集計すれば、ほしい情報がとれます。

またステータスコードの他にhttp.status_categoryというフィールドもあり、Pipeline を眺めてると定義が下記のとおりになっていることがわかります。

NAME MATCHING QUERY
OK @http.status_code:[200 TO 299]
notice @http.status_code:[300 TO 399]
warning @http.status_code:[400 TO 499]
error @http.status_code:[500 TO 599]

ということでhttp.status_categoryerrorでフィルターすれば目的のエラー数が集計できそうです。

またエラー数に 4xx 系を含む場合等にも柔軟に対応できます。

まとめ

CloudFront のエラー数を Datadog で取得する方法を紹介しました。

この場合は CloudFront のアクセスログを Datadog に送る必要があり、アクセスログのhttp.status_categoryを集計することでエラー数を取得できます。

今回のリポジトリはこちら

https://github.com/sisi100/cloud-front-error-metrics

感謝!

表紙の写真は Unsplashfatty corgiが撮影した写真です。 ありがとうございます!

後日談

Datadog をあとから手動で紐付けるのもあれだなーとおもったので、その部分の CDK にしちゃいました。ということでサンプルコードをペタペタしておきます。

from aws_cdk import aws_lambda as lambda_
from aws_cdk import aws_s3 as s3
from aws_cdk.aws_lambda_event_sources import S3EventSource

# ... 省略

# DatadogForwarderをバケットへ紐付ける
DD_FORWARDER_ARN = "arn:aws:lambda:ap-northeast-1:000000000000:function:xxxxxxxxxxxx"  # DatadogForwarderのARNを指定する
dd_forwarder = lambda_.Function.from_function_arn(stack, "DDForwarder", function_arn=DD_FORWARDER_ARN)
dd_forwarder.add_event_source(
    S3EventSource(
        bucket=log_bucket,
        events=[s3.EventType.OBJECT_CREATED],
        filters=[s3.NotificationKeyFilter(prefix="cloudfront")],  # CloudFrontのログのみをDatadogに転送する
    )
)

# ... 省略

Author

Sisii

インフラが好きなエンジニアぶってるなにか