Container Insightsでタスクレベルのメトリクスを確認する上で注意したいこと

メトリクスの単位が「タスク定義ファミリー」であることについて少し掘り下げてみました
2021.04.05

ちゃだいん(@chazuke4649)です。

CloudWatch Container Insights を使用する上で気をつけたい点が見つかったので共有します。

結論

  • ダッシュボード上のタスクレベルのメトリクスのグラフは「同じタスク定義ファミリーの平均」である
  • それにより、「一部のタスクID」だけ高負荷状態になっていても気づかない可能性がある
  • 状況によっては、タスクID単位やコンテナ単位での監視が必要

どういうこと?

Container Insightsのダッシュボードはボタン一つでまるっと自動生成され、とても見やすいのですが、タスクレベルのメトリクスを見る上では注意が必要と感じました。

というのも、[ECS Tasks]を選びタスクレベルでメトリクスを参照する場合、「同じタスク定義ファミリーの平均」が表示されます。タスク定義ファミリーとは「同じタスク定義から起動したタスク群」です。よって同じタスク定義で10タスク起動している場合、[ECS Tasks]画面のグラフは基本的に10タスクの平均が表示されます。

これで問題ないケースも多いかと思いますが、一部のユースケースではこれだと見誤ってしまう場合があります。例えば以下のような場合です。

  • 同じタスク定義を使用して、本番環境と開発環境のサービスを起動している場合
  • 同じタスク定義を使用して、異なるワークロードのタスクを実行している場合(1つは常駐サービス、1つはバッチ処理など) など

具体例

下図の例では、クラスター:example-stg-clusterのタスク定義ファミリー:example-stg-taskdef-appのメトリクスです。グラフのCPU使用率は43.7%であり、問題なさそうに見えます。

しかし、画面を下にスクロールすると[Container performance]にて、タスクID57b...に内包される2つのコンテナのみ極端にCPU使用率が上がっており、もう片方のタスクID953...の方はほとんど使用されていない状態であることがわかります。

ちなみに、画面を[ECS Services]に切り替え、画面をスクロールすると[Task performance]では、タスクID単位で表示されるので、各タスクごとのメトリクスを確認することができます。

超カンタンにカスタムダッシュボードを作成する

様々な視点から見るためにContainer Insights内で画面を切り替えて見比べる方法がありますが、それ以外に独自のダッシュボードを作成する方法もあります。

超カンタンにタスクID単位のメトリクスをずらっと比較できるテンプレートを見つけたのでご紹介します。

ちなみに、以下ワークショップの中で紹介されていました。

Fargate Rightsizing :: Observability Workshop

このカスタムダッシュボードは、「Fargateのサイジング最適化」を目的としたものであり、主に「タスクID単位で確保しているCPUとメモリのキャパシティを持て余してないかどうか」を確認することができます。

実際にやってみる

実際にやってみました。

詳しい手順や最新のテンプレートは上記GitHubを参照いただきたいのですが、基本的に以下2つをやればOKです。(前提としてContainer Insightsが有効であり、AWS CLIが使用できる必要があります)

  1. Jsonファイルのクラスター名を書き換える
  2. aws cloudwatch put-dashboardコマンド一発で設定する

1. Jsonファイルのクラスター名を書き換える

以下Json のCLUSTERNAMEを対象のクラスター名(今回の例だとexample-stg-clusterに置換します。

fargate-right-sizing.json

{
    "widgets": [
      {
        "type": "log",
        "x": 0,
        "y": 15,
        "width": 24,
        "height": 9,
        "properties": {
          "query": "SOURCE '/aws/ecs/containerinsights/CLUSTERNAME/performance' | fields @message\n| filter Type=\"Task\"\n| filter @logStream like /FargateTelemetry/\n| stats latest(TaskDefinitionFamily) as TaskDefFamily, latest(TaskDefinitionRevision) as Rev, latest(ServiceName) as Service, latest(ClusterName) as Cluster, max(CpuReserved) as TaskCpuReserved, avg(CpuUtilized) as AvgCpuUtilized, concat(ceil(avg(CpuUtilized) * 100 / TaskCpuReserved),\" %\") as AvgCpuUtilizedPerc, max(CpuUtilized) as PeakCpuUtilized, concat(ceil(max(CpuUtilized) * 100 / TaskCpuReserved),\" %\") as PeakCpuUtilizedPerc, max(MemoryReserved) as TaskMemReserved, ceil(avg(MemoryUtilized)) as AvgMemUtilized, concat(ceil(avg(MemoryUtilized) * 100 / TaskMemReserved),\" %\") as AvgMemUtilizedPerc, max(MemoryUtilized) as PeakMemUtilized, concat(ceil(max(MemoryUtilized) * 100 / TaskMemReserved),\" %\") as PeakMemUtilizedPerc by TaskId\n| sort TaskDefFamily asc\n",
          "stacked": false,
          "title": "All Fargate Tasks Configuration and Consumption Details (CPU and Memory)",
          "view": "table"
        }
      },
      {
        "type": "log",
        "x": 0,
        "y": 0,
        "width": 24,
        "height": 3,
        "properties": {
          "query": "SOURCE '/aws/ecs/containerinsights/CLUSTERNAME/performance' | fields @message\n| filter Type=\"Task\"\n| filter @logStream like /FargateTelemetry/\n| stats count_distinct(TaskId) as TotalCountFargateTasks by bin(30m)",
          "stacked": true,
          "title": "Total count of Fargate tasks",
          "view": "timeSeries"
        }
      },
      {
        "type": "log",
        "x": 0,
        "y": 3,
        "width": 15,
        "height": 6,
        "properties": {
          "query": "SOURCE '/aws/ecs/containerinsights/CLUSTERNAME/performance' | fields @message\n| filter Type=\"Task\"\n| filter @logStream like /FargateTelemetry/\n| stats latest(TaskDefinitionFamily) as TaskDefFamily, latest(ServiceName) as SvcName, concat(floor((max(CpuReserved) - avg(CpuUtilized)) * 100 / max(CpuReserved)), \" %\") as AvgCpuWastePercentage by TaskId\n| sort AvgCpuWastePercentage desc\n| limit 10",
          "stacked": false,
          "title": "Top 10 Fargate Tasks with Optimization Opportunities (CPU)",
          "view": "table"
        }
      },
      {
        "type": "log",
        "x": 0,
        "y": 9,
        "width": 15,
        "height": 6,
        "properties": {
          "query": "SOURCE '/aws/ecs/containerinsights/CLUSTERNAME/performance' | fields @message\n| filter Type=\"Task\"\n| filter @logStream like /FargateTelemetry/\n| stats latest(TaskDefinitionFamily) as TaskDefFamily, latest(ServiceName) as SvcName, concat(floor((max(MemoryReserved) - avg(MemoryUtilized)) * 100 / max(MemoryReserved)), \" %\") as AvgMemWastePercentage by TaskId\n| sort AvgMemWastePercentage desc\n| limit 10",
          "stacked": false,
          "title": "Top 10 Fargate Tasks with Optimization Opportunities (Memory)",
          "view": "table"
        }
      },
      {
        "type": "log",
        "x": 15,
        "y": 3,
        "width": 9,
        "height": 6,
        "properties": {
          "query": "SOURCE '/aws/ecs/containerinsights/CLUSTERNAME/performance' | fields @message\n| filter Type = \"Task\"\n| filter @logStream like /FargateTelemetry/\n| stats count_distinct(TaskId) as TotalTasks, avg(CpuReserved) * TotalTasks as TotalCPUReserved, avg(CpuUtilized) * TotalTasks as AvgCPUConsumed by bin(30m) \n",
          "stacked": false,
          "title": "CPU Reserved Vs Avg Usage (All Fargate Tasks)",
          "view": "timeSeries"
        }
      },
      {
        "type": "log",
        "x": 15,
        "y": 9,
        "width": 9,
        "height": 6,
        "properties": {
          "query": "SOURCE '/aws/ecs/containerinsights/CLUSTERNAME/performance' | fields @message\n| filter Type = \"Task\"\n| filter @logStream like /FargateTelemetry/\n| stats count_distinct(TaskId) as TotalTasks, avg(MemoryReserved) * TotalTasks as TotalMemReserved, avg(MemoryUtilized) * TotalTasks as AvgMemConsumed by bin(30m) \n",
          "stacked": false,
          "title": "Memory Reserved Vs Avg Usage (All Fargate Tasks)",
          "view": "timeSeries"
        }
      },
      {
        "type": "log",
        "x": 0,
        "y": 24,
        "width": 24,
        "height": 9,
        "properties": {
          "query": "SOURCE '/aws/ecs/containerinsights/CLUSTERNAME/performance' | fields @message\n| filter Type=\"Task\"\n| filter ispresent(ServiceName)\n| filter @logStream like /FargateTelemetry/\n| stats latest(TaskDefinitionFamily) as TaskDefFamily, latest(TaskDefinitionRevision) as Rev, latest(ClusterName) as Cluster, max(CpuReserved) as TaskCpuReserved, avg(CpuUtilized) as AvgCpuUtilized, concat(ceil(avg(CpuUtilized) * 100 / TaskCpuReserved),\" %\") as AvgCpuUtilizedPerc, max(CpuUtilized) as PeakCpuUtilized, concat(ceil(max(CpuUtilized) * 100 / TaskCpuReserved),\" %\") as PeakCpuUtilizedPerc, (max(MemoryReserved)) as TaskMemReserved, ceil(avg(MemoryUtilized)) as AvgMemUtilized, concat(ceil(avg(MemoryUtilized) * 100 / TaskMemReserved),\" %\") as AvgMemUtilizedPerc, max(MemoryUtilized) as PeakMemUtilized, concat(ceil(max(MemoryUtilized) * 100 / TaskMemReserved),\" %\") as PeakMemUtilizedPerc by ServiceName as Service\n| sort ServiceName asc\n",
          "stacked": false,
          "title": "All Fargate Services Configuration and Consumption Details (CPU and Memory)",
          "view": "table"
        }
      }
    ]
  }

2. aws cloudwatch put-dashboardコマンド一発で設定する

あとは、上記ファイルをパラメータに指定して、以下コマンドを実行するだけです。

aws cloudwatch put-dashboard --dashboard-name fargate-right-sizing --dashboard-body file://./fargate-right-sizing.json

結果

CloudWatchコンソールからダッシュボード画面を開いて[fargate-right-sizing]を開くと下図のようなダッシュボードが作成されていることが確認できました。

文字小さいので一部拡大すると、

このダッシュボードの[Top 10 Fargate with Optimization Opportunities:CPU(Fargate最適化のチャンスTop10 CPU編)]では、タスクID単位でCPUを持て余している順番で表示してくれます。

タスクID単位で、無駄なリソース配分がないかどうかや、一部高負荷がかかっているタスクがないかどうかなど確認するのに役立ちそうです。

終わりに

デフォルトの便利なContainer Insightsの個人的に気をつけたいところと、カンタンにカスタムダッシュボードを作成するテンプレートの紹介をしました。どなたかのお役に立てば幸いです。

それではこの辺で。ちゃだいん(@chazuke4649)でした。