11
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ECS Task毎のEphemeral Storage使用量を可視化する

Last updated at Posted at 2024-03-19

背景と概要

troccoではサービスのUIを実行する環境としてECSを採用しています。
ECSのTask Definitionでは各Taskに割り当てるEphemeral Storage容量を指定しますが、その容量以上のファイル書き込みが発生し該当のWebページが表示できなくなるトラブルが発生しました。それ以後、特定のタスクのEphemeral Storage使用率がしきい値を上回った場合はSlackへ通知し、トラブルを未然に防げるような対応を行いました。
しかしCloudWatch Metricsではタスク毎の使用率ではなく全タスクの平均使用率しか確認することができないため、Ephemeral Storageが逼迫しているタスクを特定できないという課題がありました。

弊社の環境のECS ClusterではContainer Insightsを有効化していたので、タスク毎のメトリクスはCloudWatch Logsに保存されていました。
本記事ではContainer InsightがCloudWatch Logsに出力しているログに対しQueryを実行することで、タスク毎のEphemeral Storage使用量を可視化する方法についてまとめました。

前提

  • ECSのTaskはFargateで稼働している
  • Fargate Linuxプラットフォームのバージョンが1.4.0以降
  • ECS ClusterでContainer Insightsが有効化されている

Container Insightsのログ

Container InsightsパフォーマンスログイベントはCloudWatch Logsの/aws/ecs/containerinsights/[クラスタ名]/performanceという名前のロググループに保存されています。このロググループ内には以下のログストリームが作成されています。

  • ClusterTelemetry-[クラスタ名]
  • ServiceTelemetry-[サービス名]
  • FargateTelemetry-xxxx

FargateTelemetry-xxxx内にはTaskとContainerに関するメトリクスのログが1分毎に記録されています。今回はタスク毎のメトリクスを知りたいため、FargateTelemetry-xxxxの内容を調べてみます。

ログの内容については以下の通りです。

FargateTelemetry-xxxx
{
    "Version": "0",
    "Type": "Task",
    "TaskId": "ad1fb6180dfb45a5a8ed3ad7ca7f3ac3",
    "TaskDefinitionFamily": [TaskDefinitionFamily名],
    "TaskDefinitionRevision": "234",
    "ServiceName": [サービス名],
    "ClusterName": [クラスタ名],
    "AccountID": [AWSアカウントID],
    "Region": "ap-northeast-1",
    "AvailabilityZone": "ap-northeast-1a",
    "KnownStatus": "RUNNING",
    "LaunchType": "FARGATE",
    "PullStartedAt": 1703136502731,
    "PullStoppedAt": 1703136532565,
    "CreatedAt": 1703136485850,
    "StartedAt": 1703136569195,
    "Timestamp": 1704442680000,
    "CpuUtilized": 458.5331119791667,
    "CpuReserved": 4096,
    "MemoryUtilized": 7152,
    "MemoryReserved": 12288,
    "StorageReadBytes": 0,
    "StorageWriteBytes": 29413376,
    "NetworkRxBytes": 55973,
    "NetworkRxDropped": 0,
    "NetworkRxErrors": 0,
    "NetworkRxPackets": 28421445,
    "NetworkTxBytes": 18951,
    "NetworkTxDropped": 0,
    "NetworkTxErrors": 0,
    "NetworkTxPackets": 23265409,
    "EphemeralStorageUtilized": 3.18,
    "EphemeralStorageReserved": 42.94,
    "CloudWatchMetrics": [
        {
            "Namespace": "ECS/ContainerInsights",
            "Metrics": [
                {
                    "Name": "CpuUtilized",
                    "Unit": "None"
                },
                {
                    "Name": "CpuReserved",
                    "Unit": "None"
                },
                {
                    "Name": "MemoryUtilized",
                    "Unit": "Megabytes"
                },
                {
                    "Name": "MemoryReserved",
                    "Unit": "Megabytes"
                },
                {
                    "Name": "StorageReadBytes",
                    "Unit": "Bytes/Second"
                },
                {
                    "Name": "StorageWriteBytes",
                    "Unit": "Bytes/Second"
                },
                {
                    "Name": "NetworkRxBytes",
                    "Unit": "Bytes/Second"
                },
                {
                    "Name": "NetworkTxBytes",
                    "Unit": "Bytes/Second"
                },
                {
                    "Name": "EphemeralStorageUtilized",
                    "Unit": "Gigabytes"
                },
                {
                    "Name": "EphemeralStorageReserved",
                    "Unit": "Gigabytes"
                }
            ],
            "Dimensions": [
                [
                    "ClusterName"
                ],
                [
                    "ServiceName",
                    "ClusterName"
                ],
                [
                    "ClusterName",
                    "TaskDefinitionFamily"
                ]
            ]
        }
    ]
}

このログの中でEphemeral Storageに関するメトリクスは以下の2つになります。

  • EphemeralStorageUtilized(Ephemeral Storageの使用バイト数)
  • EphemeralStorageReserved(Ephemeral Storageの予約されたバイト数)

CloudWatch Logs Insightsのクエリ

CloudWatch Logs InsightsではCloudWatch Logsに送信されたログデータを検索することができます。上記のJSONから取得したいデータは最新のリビジョンにおける、task ID毎のEphemeral Storage使用バイト数の最大値 になります。Dashboardに表示する際の視認性も考慮してEphemeral Storage使用率もあわせて計算しておきます。

値を取得するため、Cloudwatch Logs Insightsのコマンドを紹介します。

  • fields: クエリ結果に特定のフィールドを表示し、新しいフィールドを作成したりするときに使用できる関数と演算をサポート
  • filter: クエリをフィルタリング
  • stats: 統計を算出
  • sort: 並び替え

また、複数のコマンドを含むクエリではコマンドをパイプ文字 (|) で区切ります。

上記を踏まえて今回作成したクエリは以下の通りです。

fields @message |
filter Type="Task" | 
filter TaskDefinitionFamily=<対象のTask Deifinition Family名> |
filter @logStream like /FargateTelemetry/ | 
stats latest(TaskDefinitionRevision) as Rev,
  max(EphemeralStorageReserved) as `Reserved(GB)`, 
  max(EphemeralStorageUtilized) as `PeakUtilized(GB)`, 
  max(EphemeralStorageUtilized) * 100 / max(EphemeralStorageReserved) as `PeakUtilized(%)` 
  by TaskId |
sort Rev desc

クエリを基に各コマンドの解説をします。

  • fields
    • @messageはInputLogEventのmessageフィールドに相当します(CloudWatch LogsのコンソールでLog streamを閲覧した際に表示されるmessageというヘッダーに該当)
    • 上記のJSONがそのまま入っています
  • filter
    • Type
      • TaskとContainerとに関するログが含まれています。今回はTaskに間するログを見たいのでTaskを指定します
    • TaskDefinitionFamily
      • Cluster内で複数のTask Definitionが存在する場合は必要なものにしぼります
    • @logStream
      • 上述の通り3種類のログストリームが存在するため、FargateTelemetry-xxxxにしぼります。@logStreamは自動的に生成されるフィールドで、ログストリームが格納されています
  • stats
    • EphemeralStorageReservedEphemeralStorageUtilizedに加え、この2つの値を使用して最大使用率を算出します
    • asを使用して結果にエイリアスを指定します
    • 上記の値をTask ID毎に算出したいので、末尾にby TaskIdを記載します
  • sort
    • 複数のRevisionで取得される可能性があるのでRevisionの降順でソートする

CloudWatch Dashboardにクエリを追加

上記で生成したクエリをCloudWatch Dashboardに追加します。弊社ではAWSのresourceをTerraformで管理しているためDashboardもTerraformで管理します。

CloudWatch Dashboardのwidgetはjsonで定義されます。定義内容は以下の通りです。

{
    "widgets": [
     {
        "type": "log",
        "x": 0,
        "y": 0,
        "width": 24,
        "height": 9,
        "properties": {
          "query": "SOURCE '/aws/ecs/containerinsights/${cluster_name}/performance' | fields @message\n| filter Type=\"Task\"\n| filter TaskDefinitionFamily=\"${task_definition_family}\"\n| filter @logStream like /FargateTelemetry/\n| stats latest(TaskDefinitionRevision) as Rev, max(EphemeralStorageReserved) as `Reserved(GB)`, max(EphemeralStorageUtilized) as `PeakUtilized(GB)`, max(EphemeralStorageUtilized) * 100 / max(EphemeralStorageReserved) as `PeakUtilized(%)` by TaskId\n| sort Rev desc\n",
          "stacked": false,
          "title": "${task_definition_family} Storage Details",
          "view": "table"
        }
     }
    ]
  }

ポイントをいくつか解説します。

  • properties内のqueryフィールドに実行するqueryを定義します。先頭にSOURCEとlog groupの名前を付与します
  • viewで表示形式を指定できます。今回は表形式で見たいためtableを指定します
  • Terraformのtemplatefile関数を使用することで、1つのjsonファイルを複数のTaskや環境で使い回すことができます。今回の例では、ECSのクラスタ名(cluster_name)とタスク定義名(task_definition_family)を変数として扱っています

以下がTerraformのサンプルコードになります。

resource "aws_cloudwatch_dashboard" "ecs_storage_details" {
  dashboard_name = "ecs-storage-details"
  dashboard_body = templatefile("${path.module}/../template/ecs-storage-details-dashboard.json", {
    cluster_name           = data.aws_ecs_cluster.sample_cluster.cluster_name
    task_definition_family = data.aws_ecs_task_definition.sample_task.family
  })
}

実際のDashboard

上記の設定で作成したDashboardの一例です。

image.png

まとめ

Container Insightsが出力するログに対し、CloudWatch Logs Insightsのクエリを使用することでCloudWatch Metricsの標準機能では確認することができない詳細なメトリクスを取得することができます。また、CloudWatch Dashboardにクエリを紐付けることで、クエリで取得した結果を可視化することもできました。

参考

11
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?