0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

EC2のログ・トレースを例に、CloudWatchのOpenTelemetryを分かりやすく記載

0
Posted at

はじめに

前回はメトリクスを CloudWatch の OpenTelemetry(以下 OTel)で送りました。今回は残りの2つのシグナル、ログとトレースを同じ EC2 から送ります。前回の設定ファイルに足す形で、1本のエージェントから3つとも送る構成にします。宛先が3つに分かれることと、トレースだけ事前設定が必要なことがポイントです。なお、前回の記事は ap-northeast-1 でしたが、今回は us-east-1 で記載しています。

スライド1.PNG

環境

役割 値
EC2 Amazon Linux 2023
VPC エンドポイント com.amazonaws.us-east-1.monitoring、com.amazonaws.us-east-1.logs、com.amazonaws.us-east-1.xray、com.amazonaws.us-east-1.ec2(Name タグを取得する場合)
IAM(EC2 ロール) CloudWatchAgentServerPolicy
ログの取得元 /var/log/otel-demo/app.log
トレースの取得元 OTLP(OpenTelemetry Protocol)で localhost:4318 へ送ったスパン

※スパンは処理1区間の記録です。「いつ始まり、いつ終わり、何をしたか」を1件にまとめたもので、これを親子関係でつなげた1本の流れがトレースになります。

仕組み

3つのシグナルと3つの宛先

OTel のシグナルは3種類あり、CloudWatch 側の受け口も3つに分かれています。

シグナル エンドポイント 署名するサービス名
メトリクス https://monitoring.us-east-1.amazonaws.com/v1/metrics monitoring
ログ https://logs.us-east-1.amazonaws.com/v1/logs logs
トレース https://xray.us-east-1.amazonaws.com/v1/traces xray

スライド2.PNG

設定の流れ

設定の流れを並べると次のようになります。水色が EC2 上または CLI で実施する設定、緑が CloudWatch 側で自動的に動く部分です。

スライド3.PNG

事前準備

① VPC エンドポイントを追加する

プライベートサブネットなので、宛先ごとにインターフェース型の VPC エンドポイントが必要です。メトリクスの monitoring に加えて、logs と xray を作ります。

aws ec2 create-vpc-endpoint --region us-east-1 --vpc-endpoint-type Interface --vpc-id vpc-xxxxxxxx --service-name com.amazonaws.us-east-1.logs --subnet-ids subnet-xxxxxxxx --security-group-ids sg-xxxxxxxx --private-dns-enabled
aws ec2 create-vpc-endpoint --region us-east-1 --vpc-endpoint-type Interface --vpc-id vpc-xxxxxxxx --service-name com.amazonaws.us-east-1.xray --subnet-ids subnet-xxxxxxxx --security-group-ids sg-xxxxxxxx --private-dns-enabled

② ロググループを作る

OTLP のログエンドポイントは既存のロググループへ書き込む仕様なので、宛先は先に作っておきます。あとで YAML のヘッダに指定する /otel/demo を作ります。

aws logs create-log-group --log-group-name /otel/demo --region us-east-1

③ Transaction Search を有効にする

必要なコマンドは2つです。やっているのは「受け取る側の許可」と「送る側の宛先の切り替え」の2つだけで、どちらもアカウント単位の設定なので、EC2 ロールではなく作業者の権限で実行します。

スライド4.PNG

1つ目は CloudWatch Logs 側のリソースポリシーです。スパンをロググループへ書き込むのは X-Ray のサービス(xray.amazonaws.com)なので、aws/spans と /aws/application-signals/data の2つに対して logs:PutLogEvents を許可します。

ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) && aws logs put-resource-policy --region us-east-1 --policy-name TransactionSearchXRayAccess --policy-document '{"Version":"2012-10-17","Statement":[{"Sid":"TransactionSearchXRayAccess","Effect":"Allow","Principal":{"Service":"xray.amazonaws.com"},"Action":"logs:PutLogEvents","Resource":["arn:aws:logs:us-east-1:'"$ACCOUNT_ID"':log-group:aws/spans:*","arn:aws:logs:us-east-1:'"$ACCOUNT_ID"':log-group:/aws/application-signals/data:*"],"Condition":{"ArnLike":{"aws:SourceArn":"arn:aws:xray:us-east-1:'"$ACCOUNT_ID"':*"},"StringEquals":{"aws:SourceAccount":"'"$ACCOUNT_ID"'"}}}]}'

2つ目は X-Ray 側の保存先の切り替えです。既定ではスパンは X-Ray が内部に持つトレースストアに入りますが、CloudWatchLogs に切り替えるとロググループへ入るようになります。

aws xray update-trace-segment-destination --destination CloudWatchLogs --region us-east-1

Status が ACTIVE になっていることを確認します。反映まで10分ほどかかります。

aws xray get-trace-segment-destination --region us-east-1

メトリクスとログの設定

① ベースの JSON を用意する

前回と同じ JSON に grpc_endpoint を足したものです。この otlp の受け口が、あとでトレースの入口になります。

sudo tee /tmp/agent.json > /dev/null <<'EOF'
{
  "agent": {
    "metrics_collection_interval": 60
  },
  "opentelemetry": {
    "collect": {
      "otlp": {
        "grpc_endpoint": "0.0.0.0:4317",
        "http_endpoint": "0.0.0.0:4318"
      }
    }
  }
}
EOF

② YAML を書く

メトリクスとログの2本のパイプラインを1つのファイルにまとめます。前回の YAML に、ログの受け口(filelog)・署名(sigv4auth/cwlogs)・送信先(otlphttp/cwlogs)を足した形です。

sudo tee /tmp/otel.yaml > /dev/null <<'EOF'
receivers:
  hostmetrics/cwagent:
    collection_interval: 60s
    scrapers:
      cpu:
        metrics:
          system.cpu.utilization:
            enabled: true
      memory:
        metrics:
          system.memory.utilization:
            enabled: true
      filesystem:
        include_fs_types:
          fs_types: [xfs, ext4]
          match_type: strict
        metrics:
          system.filesystem.utilization:
            enabled: true

  filelog/cwagent:
    include: [/var/log/otel-demo/app.log]
    start_at: beginning

processors:
  resourcedetection/cwagent:
    detectors: [env, ec2]
    ec2:
      tags:
        - ^Name$
  resource/cwagent:
    attributes:
      - key: service.name
        value: otel-demo
        action: upsert
      - key: deployment.environment
        value: test
        action: upsert
  batch/cwagent:
    send_batch_size: 200
    timeout: 10s

extensions:
  sigv4auth/cwmetrics:
    region: us-east-1
    service: monitoring
  sigv4auth/cwlogs:
    region: us-east-1
    service: logs

exporters:
  otlphttp/cwmetrics:
    metrics_endpoint: https://monitoring.us-east-1.amazonaws.com/v1/metrics
    auth:
      authenticator: sigv4auth/cwmetrics
  otlphttp/cwlogs:
    logs_endpoint: https://logs.us-east-1.amazonaws.com/v1/logs
    headers:
      x-aws-log-group: /otel/demo
      x-aws-log-stream: default
    auth:
      authenticator: sigv4auth/cwlogs
  debug/cwagent:
    verbosity: detailed

service:
  extensions: [sigv4auth/cwmetrics, sigv4auth/cwlogs]
  pipelines:
    metrics/cwagent:
      receivers: [hostmetrics/cwagent]
      processors: [resourcedetection/cwagent, resource/cwagent, batch/cwagent]
      exporters: [otlphttp/cwmetrics, debug/cwagent]
    logs/cwagent:
      receivers: [filelog/cwagent]
      processors: [resourcedetection/cwagent, resource/cwagent, batch/cwagent]
      exporters: [otlphttp/cwlogs, debug/cwagent]
EOF

ログでメトリクスと違うのは3点です。sigv4auth の service が logs になること、otlphttp が metrics_endpoint ではなく logs_endpoint を持つこと、そして headers で宛先のロググループとログストリームを指定することです。

③ 設定を反映する

ベースの JSON で起動してから YAML を append します。前回と同じコマンドです。

sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -c file:/tmp/agent.json -s && sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a append-config -c file:/tmp/otel.yaml -s

④ ログを書いて届くか確認する

読み取り元のファイルを作り、行を1つ足します。エージェントが読めるようにパーミッションを設定します。

sudo mkdir -p /var/log/otel-demo && sudo touch /var/log/otel-demo/app.log && sudo chmod 755 /var/log/otel-demo && sudo chmod 644 /var/log/otel-demo/app.log
sudo sh -c 'echo "$(date -Is) INFO order accepted id=1001" >> /var/log/otel-demo/app.log'

ロググループに届いたかは CLI で確認できます。

sleep 30 && aws logs tail /otel/demo --since 5m --region us-east-1

CloudWatch コンソールからも確認します。上記で書いた行が body に追加されています。

ot12.png

トレースの設定

① OTLP の受け口を確認する

トレースは YAML を書きません。ベースの JSON で OTLP の受け口(4318)を開けていれば、エージェントが X-Ray のエンドポイントへ送信します。受け口が起動しているかはエージェントのログで確認します。

sudo grep "Starting HTTP server" /opt/aws/amazon-cloudwatch-agent/logs/amazon-cloudwatch-agent.log | tail -n 5

② スパンを送る

アプリを計装する場合は OTel SDK のエクスポーターを http://localhost:4318 に向けますが、ここでは中身が見えるように OTLP の JSON を直接投げます。親子2つのスパンを作ります。

TRACE_ID=$(openssl rand -hex 16) && ROOT_ID=$(openssl rand -hex 8) && CHILD_ID=$(openssl rand -hex 8) && END=$(date +%s%N) && START=$((END - 500000000)) && CSTART=$((START + 100000000)) && CEND=$((END - 100000000)) && echo "$TRACE_ID"
curl -s -o /dev/null -w '%{http_code}\n' -X POST http://localhost:4318/v1/traces -H 'Content-Type: application/json' -d @- <<EOF
{
  "resourceSpans": [{
    "resource": {
      "attributes": [
        {"key": "service.name", "value": {"stringValue": "otel-demo"}}
      ]
    },
    "scopeSpans": [{
      "scope": {"name": "manual"},
      "spans": [
        {
          "traceId": "$TRACE_ID",
          "spanId": "$ROOT_ID",
          "name": "GET /orders",
          "kind": 2,
          "startTimeUnixNano": "$START",
          "endTimeUnixNano": "$END",
          "attributes": [
            {"key": "http.request.method", "value": {"stringValue": "GET"}},
            {"key": "url.path", "value": {"stringValue": "/orders"}},
            {"key": "http.response.status_code", "value": {"intValue": 200}}
          ],
          "status": {"code": 1}
        },
        {
          "traceId": "$TRACE_ID",
          "spanId": "$CHILD_ID",
          "parentSpanId": "$ROOT_ID",
          "name": "SELECT orders",
          "kind": 3,
          "startTimeUnixNano": "$CSTART",
          "endTimeUnixNano": "$CEND",
          "attributes": [
            {"key": "db.system", "value": {"stringValue": "postgresql"}}
          ],
          "status": {"code": 1}
        }
      ]
    }]
  }]
}
EOF

200 が返れば、エージェントが受け取って X-Ray のエンドポイントへ転送しています。

③ aws/spans に入ったスパンを見る

スパンは aws/spans に入るので、ここも CLI で確認できます。

sleep 60 && aws logs tail aws/spans --since 10m --region us-east-1

ルートスパンは次のようになっていました(見やすいように改行しています)。

2026-09-25T05:09:05.131000+00:00 default {
  "resource": {
    "attributes": {
      "deployment.environment.name": "aws_ec2:default",
      "service.name": "otel-demo",
      "cloud.region": "us-east-1",
      "host.image.id": "ami-xxxxxxxxxxxx",
      "host.type": "t3.small",
      "cloud.availability_zone": "us-east-1a",
      "cloud.provider": "aws",
      "cloud.account.id": "xxxxxxxxxxxx",
      "cloud.resource_id": "arn:aws:ec2:us-east-1:xxxxxxxxxxxx:instance/i-xxxxxxxxxxxx",
      "host.name": "ip-10-0-xxx-xxx.ec2.internal",
      "cloud.platform": "aws_ec2",
      "host.id": "i-xxxxxxxxxxxx"
    }
  },
  "scope": {
    "name": "manual",
    "version": "",
    "attributes": {
      "cloudwatch.solution": "otel-otlp",
      "cloudwatch.source": "cloudwatch-agent"
    }
  },
  "traceId": "c71c228c997fd8357ee53220079399b5",
  "spanId": "b1eb4adbdd960d09",
  "flags": 0,
  "name": "GET /orders",
  "kind": "SERVER",
  "startTimeUnixNano": 1790312944631642214,
  "endTimeUnixNano": 1790312945131642214,
  "durationNano": 500000000,
  "attributes": {
    "aws.local.service": "otel-demo",
    "aws.local.operation": "GET /orders",
    "http.status_code": 200,
    "aws.span.kind": "LOCAL_ROOT",
    "url.path": "/orders",
    "http.request.method": "GET",
    "telemetry.extended": "true",
    "PlatformType": "AWS::EC2",
    "http.response.status_code": 200,
    "aws.local.environment": "aws_ec2:default"
  },
  "status": {"code": "OK"}
}

子スパンは resource と scope が同じなので、違う部分だけ抜き出します。

  "traceId": "c71c228c997fd8357ee53220079399b5",
  "spanId": "15a78a482fe11828",
  "parentSpanId": "b1eb4adbdd960d09",
  "name": "SELECT orders",
  "kind": "CLIENT",
  "durationNano": 300000000,
  "attributes": {
    "aws.local.service": "otel-demo",
    "aws.local.operation": "UnmappedOperation",
    "aws.span.kind": "CLIENT",
    "telemetry.extended": "true",
    "db.system": "postgresql",
    "PlatformType": "AWS::EC2",
    "aws.remote.service": "postgresql",
    "aws.local.environment": "aws_ec2:default",
    "aws.remote.operation": "UnknownRemoteOperation"
  },
  "status": {"code": "OK"}

子の parentSpanId がルートの spanId と一致していて、2つが1本のトレースとして繋がっています。

④ トランザクション検索で確認する

コンソールからは CloudWatch → Application Signals(APM)→ トランザクション検索で見ます。既定で入っているクエリに traceId の絞り込みを足すと、送ったスパンだけが出ます。

ot13.png

結果は2行になります。

ot14.png

まとめ

メトリクス・ログ・トレースの3つを同じ CloudWatch Agent 1本で送れることが確認できました。宛先とサービス名がシグナルごとに違うだけで、書き方の構造は同じです。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?