CloudWatch Agent の Classic 設定に OTel パスを追加して並行稼働させてみた

CloudWatch Agent の Classic 設定に OTel パスを追加して並行稼働させてみた

CloudWatch Agent v1.300066.2で、既存のClassic設定を残したままOTel YAMLを `append-config` で追加し、ClassicパスとOTelパスを並行稼働できるか検証しました。既存のホストメトリクス収集を維持しながら、段階的にOTelパスを追加する手順を紹介します。
2026.06.17

はじめに

CloudWatch Agentは、2024年後半のアップデートで内部アーキテクチャがOpenTelemetry Collectorベースに刷新されました。従来のagent.jsonによる設定(以降「Classicパス」と呼びます)はそのまま動作しつつ、OTel Collectorとしての設定をYAMLで追加できるようになっています。

前回の記事ではPython SDKからOTLPエンドポイントに直接メトリクスを送信し、PromQLでクエリする方法を検証しました。

https://dev.classmethod.jp/articles/cloudwatch-otel-metrics-ga-otlp-promql/

今回は「既にCloudWatch Agentを使ってホストメトリクスを収集している環境」を起点に、OTelパスを追加して段階的に移行する手順を検証します。

Classic パスと OTel パスの比較

本記事で扱う2つのパスの違いを最初に整理します。

項目 Classic パス OTel パス
設定ファイル agent.json (JSON) otel.yaml (YAML)
収集方式 Telegraf プラグイン OTel Receiver (hostmetrics)
メトリクス名 Agent独自 (cpu_usage_idle) OTel Semantic Conventions (system.cpu.time)
値の形式 百分率(%)など加工済み 累積秒・バイトなど生値
識別体系 CloudWatch namespace(CWAgent OTelメトリクス名・ラベル・resource attribute(PromQLでクエリ)
クエリ方法 GetMetricData / Metric Math PromQL
課金モデル メトリクス数ベース 取り込みGBベース

この2つのパスは同一のCloudWatch Agentプロセス内で同時に動作できます。以降のステップではClassicパスを維持したままOTelパスを追加し、両方のメトリクスが出揃うことを確認していきます。

検証環境

項目
リージョン ap-northeast-1
EC2 Amazon Linux 2023 (t3.micro)
CloudWatch Agent v1.300066.2
IAM EC2 インスタンスプロファイル(CloudWatch + SSM 権限)

Agent 最新化

まずはCloudWatch AgentをOTel Collectorベースの最新版に更新します。

sudo yum install -y amazon-cloudwatch-agent

バージョンを確認します。

$ amazon-cloudwatch-agent-ctl -a status
{
  "status": "stopped",
  "starttime": "",
  "configstatus": "not configured",
  "version": "1.300066.2b1039"
}

v1.300066.2がインストールされました。このバージョンは内部的にOTel Collectorとして動作しており、従来のagent.jsonに加えてOTelのYAML設定を受け付けます。

Classic 設定の動作確認

Agentを更新した後、従来のagent.jsonを適用してClassicパスのメトリクスが正常に収集されることを確認します。これが移行前のbaselineです。

Classic 設定ファイル(agent.json)

{
  "agent": {
    "metrics_collection_interval": 60
  },
  "metrics": {
    "namespace": "CWAgent",
    "metrics_collected": {
      "cpu": {
        "resources": ["*"],
        "measurement": ["usage_idle", "usage_user", "usage_system"],
        "totalcpu": true
      },
      "mem": {
        "measurement": ["used_percent", "available_percent"]
      },
      "disk": {
        "resources": ["/"],
        "measurement": ["used_percent"],
        "ignore_file_system_types": ["sysfs", "devtmpfs"]
      }
    },
    "append_dimensions": {
      "InstanceId": "${aws:InstanceId}"
    }
  }
}

CPU(idle/user/system)、メモリ(used_percent)、ディスク(used_percent、ルートFSのみ)を60秒間隔で収集します。CWAgent namespaceに送信する設定です。append_dimensionsでInstanceIdをdimensionに付与しています。

設定の適用と起動

amazon-cloudwatch-agent-ctl -a fetch-config \
  -m ec2 -c file:/opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.d/file_agent.json -s

Classic メトリクスの確認

数分待ってからCloudWatchのGetMetricDataで値を確認しました。

メトリクス 値(直近の代表値)
cpu_usage_idle 99.33%
cpu_usage_user 0.15%
cpu_usage_system 0.17%
mem_used_percent 26.98%
disk_used_percent 24.51%

アイドル状態のt3.microなのでCPUはほぼidleです。このbaselineを後のステップでOTel側と比較します。

OTel 設定の追加

Classicパスが動作している状態に、OTelのYAML設定を append-config で追加します。Classic設定を削除したり事前に停止したりする必要はありません。

OTel 設定ファイル(otel.yaml)

receivers:
  hostmetrics/cwagent:
    collection_interval: 60s
    scrapers:
      cpu: {}
      memory: {}
      disk: {}
      filesystem: {}

processors:
  batch/cwagent: {}
  resource/cwagent:
    attributes:
      - key: service.name
        value: "hostmetrics-otel-demo"
        action: upsert

exporters:
  otlphttp/cwagent:
    metrics_endpoint: https://monitoring.ap-northeast-1.amazonaws.com/v1/metrics
    auth:
      authenticator: sigv4auth/cwagent

extensions:
  sigv4auth/cwagent:
    region: "ap-northeast-1"
    service: "monitoring"

service:
  extensions: [sigv4auth/cwagent]
  pipelines:
    metrics/cwagent:
      receivers: [hostmetrics/cwagent]
      processors: [resource/cwagent, batch/cwagent]
      exporters: [otlphttp/cwagent]

設定のポイントを解説します。

コンポーネント名に/cwagentサフィックスを付ける理由: CloudWatch Agentでは複数の設定が最終的に1つのCollector設定としてマージされます。既存または自動生成されたコンポーネント名と衝突しないよう、各コンポーネント名に/cwagentサフィックスを付けています。

hostmetrics receiver の scrapers:

  • cpu: CPU時間(cumulative、秒単位)
  • memory: メモリ使用量(バイト単位、state別)
  • disk: ディスクI/O(読み書きバイト数・オペレーション数など。本記事の容量比較では使用しません)
  • filesystem: ファイルシステム容量(使用量・空き容量、バイト単位)

Classicの disk_used_percent に対応するのは filesystem scraperです(disk scraperはディスクI/O用)。

resource processor: service.name を付与しています。Classicパスで append_dimensions / ec2tagger により付与されるInstanceId dimensionは、OTelパスでは自動付与されません。アカウントIDやリージョンは付与されますが、インスタンス識別子は明示設定が必要です(後述の注意点を参照)。

SigV4 認証: sigv4auth extensionでインスタンスプロファイルの認証情報を使って署名します。アクセスキーの記載は不要です。

設定の適用

既存のClassicパスを維持したまま append-config で追加適用します。

amazon-cloudwatch-agent-ctl -a append-config -m ec2 -c file:/tmp/otel.yaml -s

Agent ログでパイプライン構成を確認

Agentログを確認すると、ClassicパスとOTelパスが同時に構成されていることが分かります。

pipelines:
  metrics/cwagent:
    receivers: [hostmetrics/cwagent]
    processors: [resource/cwagent, batch/cwagent]
    exporters: [otlphttp/cwagent]
  metrics/host:
    receivers: [telegraf_cpu, telegraf_mem, telegraf_disk]
    processors: [ec2tagger, awsentity/resource]
    exporters: [awscloudwatch]

metrics/cwagent が今回追加したOTelパス、metrics/host が従来のClassicパスです。1つのAgentプロセス内で両方のパイプラインが動作しています。

PromQL でメトリクスを確認

{"__name__"="system.cpu.time", state="user"}

このクエリでCPUのuser状態の累積時間(秒)が返ります。レスポンスの構造:

{
  "@aws.account": "123456789012",
  "@aws.region": "ap-northeast-1",
  "@instrumentation.@name": "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/hostmetricsreceiver/internal/scraper/cpuscraper",
  "@resource.service.name": "hostmetrics-otel-demo",
  "__monotonicity__": "true",
  "__temporality__": "cumulative",
  "__type__": "Sum",
  "__unit__": "s",
  "cpu": "cpu0",
  "state": "user"
}

注目すべき点:

  • cpu ラベルに cpu0, cpu1 のようにコアごとの値が入る(Classicの totalcpu: true とは異なり、per-coreで報告される)
  • __unit__s(秒)— Classicの百分率(%)ではなく累積秒
  • __temporality__cumulative__monotonicity__true — 累積の単調増加カウンタであるため、使用率を出すには rate() で変化率を計算する

並行稼働の確認

Classic 側の値(GetMetricData)

CloudWatchのGetMetricData APIで CWAgent namespaceからメトリクスを取得しました。以下は cpu_usage_idle の取得例です。同様のクエリで他のメトリクスも取得しています。

aws cloudwatch get-metric-data \
  --metric-data-queries '[
    {"Id":"cpu_idle","MetricStat":{"Metric":{"Namespace":"CWAgent","MetricName":"cpu_usage_idle","Dimensions":[{"Name":"InstanceId","Value":"i-09704c70c1ff88906"},{"Name":"cpu","Value":"cpu-total"}]},"Period":60,"Stat":"Average"}}
  ]' \
  --start-time 2026-06-17T08:50:00Z \
  --end-time 2026-06-17T09:01:00Z

直近11分間(11データポイント)の平均:

メトリクス 平均値
cpu_usage_idle 99.30%
cpu_usage_user 0.23%
cpu_usage_system 0.16%
mem_used_percent 26.94%
disk_used_percent 24.52%

比較結果

同じ時間帯にOTelパスで送信されたメトリクスをPromQLでクエリし、Classicと比較しました。

指標 Classic (GetMetricData) OTel (PromQL) 差異
CPU idle 99.30% 99.09% 0.21pt
CPU user 0.23% 0.40% 0.17pt
CPU system 0.16% 0.23% 0.07pt
Memory used 26.94% 27.02% 0.08pt
Disk used (/) 24.52% 24.53% 0.01pt

CPUの差(0.1〜0.2pt)は、集計の窓やタイミングの違いによるものと考えられます。Classic側は60秒間隔で算出した使用率、OTel側は累積カウンタに rate(...[5m]) を適用した5分レンジの値です。いずれもアイドル状態で絶対値が小さく、実用上は同等とみなせる範囲です。メモリとディスクはゲージ値(瞬時の絶対量)であり、計算方法の差が小さいため本検証ではほぼ一致しました。

OTel 側で Classic 相当の百分率を出す PromQL

OTel側は生値(累積秒・バイト)で返るため、Classicと同等の百分率にするにはクエリ側で計算が必要です。対応関係を先に示します。

Classic メトリクス OTel メトリクス 計算方法
cpu_usage_idle (%) system.cpu.time{state="idle"} (秒) rate(...[5m])avg()*100
cpu_usage_user (%) system.cpu.time{state="user"} (秒) 同上
cpu_usage_system (%) system.cpu.time{state="system"} (秒) 同上
mem_used_percent (%) system.memory.usage{state="used"} (バイト) sum(used) / sum(all) * 100
disk_used_percent (%) system.filesystem.usage{state="used"} (バイト) sum(used) / sum(used+free) * 100

Classicの cpu dimensionで cpu-total を指定して得ていた集約値は、OTel側ではPromQLの avg() でper-coreを集約して再現します。Classicの path dimension(/)はOTelでは mountpoint ラベルに対応します。

以下、代表的なクエリ例です。

CPU user 使用率: rate() で5分間の変化率(秒/秒、コアあたり0〜1)を求め、avg() で全コアを集約します。

avg(rate({"__name__"="system.cpu.time", state="user"}[5m])) * 100

idle率を全stateの合計に対する割合で出す場合は sum(rate(...state="idle"...)) / sum(rate(...)) * 100 です。対応表の avg(rate()) * 100 とは別のアプローチで、丸め等の影響を受けにくくなります。

メモリ使用率: state="used" のバイト数を全stateの合計で割ります。Telegrafの mem_used_percent とOTelの state="used" ではbuffer/cacheの扱いなど定義が完全に同一とは限りません。本検証ではほぼ一致する値が得られました。

sum({"__name__"="system.memory.usage", state="used"})
/ sum({"__name__"="system.memory.usage"})
* 100

ファイルシステム使用率: state="used"used + free で割ります。

sum by (mountpoint) (
  {"__name__"="system.filesystem.usage", mountpoint="/", state="used"}
)
/ sum by (mountpoint) (
  {"__name__"="system.filesystem.usage", mountpoint="/", state=~"used|free"}
)
* 100

system.filesystem.usage には reserved stateも存在します。Classicの disk_used_percent と近い値を得るには used / (used + free) で計算します。

移行完了への道筋

ここからはClassicパスの撤去に向けた手順を整理します(本記事では実行しません)。

ダッシュボード・アラームの切り替え

CloudWatchダッシュボードやアラームでClassicメトリクス(GetMetricData / Metric Math)を参照している箇所を、PromQLベースのクエリに書き換えます。

前のセクションで確認したとおり、メトリクス名と計算方法が異なるため単純な文字列置換では対応できません。PromQLでのCPU使用率計算(rate() + avg() + *100)やメモリ使用率計算(sum(used) / sum(all))をダッシュボードウィジェットに設定します。しばらくClassicウィジェットと並べて表示しながら問題がないことを確認するのがよいでしょう。

Classic パスの撤去

ダッシュボード・アラームの切り替えが完了したら、Classicパスを撤去します。append-config で積み上げた設定を場当たり的に編集するよりも、最終的に必要な設定(OTel YAMLのみ)を整理して fetch-config で再適用する方が管理しやすくなります。

AWSは公式にClassicからOTelへの移行ガイドを提供しています。

https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/metrics-otel-migrate.html

注意点

並行稼働中の二重課金

同じ観測対象(CPU・メモリ・ディスク)をClassicパスとOTelパスの両方で収集するため、並行稼働期間中は二重に課金が発生します。Classicはメトリクス数ベース、OTelは取り込みGBベースと課金モデルが異なります。移行期間は必要最小限にとどめることをお勧めします。

resource attribute の設計

Classicパスでは append_dimensionsec2tagger によりInstanceIdが自動的にdimensionに付与されます。OTelパスではこのような自動付与は行われません。本記事の設定では resource processorで service.name を固定値として付与していますが、インスタンスごとに異なるInstanceId相当の値はこれだけでは入りません。複数のEC2インスタンスに展開する場合は、インスタンスを識別できるresource attributeを別途付与する設計が必要です。

PromQL のクエリ制約

OTelメトリクスの保持期間はClassicと同じ15か月です。ただしPromQLでは1回のクエリで参照できるレンジが最大7日間、返却シリーズが500までという制約があります(PromQL limits and restrictions)。データ自体は存在するため、クエリの時間窓をずらせば過去の値も参照できますが、1クエリで数週間〜数か月のトレンドを俯瞰することはできません。

長期トレンドを1画面で確認したい場合はClassicメトリクス(GetMetricData / Metric Math)の方が扱いやすいです。短期のアラートやリアルタイム分析にはPromQLが向いています。用途に応じて併用する設計も検討してください。

まとめ

検証したCloudWatch Agent v1.300066.2では、既存のClassic設定を残したままOTel YAMLを append-config で追加できました。ClassicパスとOTelパスの並行稼働を確認し、CPU・メモリ・ディスクについて両者の値を比較したところ、PromQLでの計算を含めてほぼ同等の値が得られました。

移行は「Classicを止めてOTelに切り替える」だけでなく、OTelパスを追加して比較 → ダッシュボード・アラームを準備 → Classicパスを撤去、という段階的なアプローチで進められます。

参考リンク

この記事をシェアする

AWSのお困り事はクラスメソッドへ

関連記事