(ARC310) EMFログをOpenTelemetryを使って送信してみた

re:Invent 2023のセッションARC310 Detecting and mitigating gray failuresで紹介されていたEMFによるメトリクスの作成、複合アラームによる部分的な障害検出をサンプルアプリで試してみました。
2023.12.27

はじめに

前回までの記事ではアプリケーションで整形したEMFのログをCloud Watch Agentで送信していました。 この記事にはアプリでEMFにログを整形するのではなく、オープン規格であるOpenTelemetryライブラリを使って独自のメトリクスを送信してみます。

構成

OTEL CollectorとしてAWS Distro for OpenTelemetryを使います。今回はEMFでメトリクスを送信したいのでAWS CloudWatch EMF Exporterを構成します。

アプリ側は以下のpipパッケージを使用します。

opentelemetry-sdk
opentelemetry-exporter-otlp

otel-agent-config.yaml

エージェントの設定は下記の通りです。 metric_declarationsで送信されるメトリクスに付与するDimensionを設定します。 これを設定しないとコレクタ側で付与される余計なメタ情報も追加されてしまいます。

receivers:
  otlp:
    protocols:
      http:
        endpoint: 0.0.0.0:4318
      grpc:
        endpoint: 0.0.0.0:4317
processors:
  batch:
    timeout: 60s
exporters:
  awsemf:
      log_group_name: 'gray-failure-emf'
      log_stream_name: '{ContainerInstanceId}'
      namespace: 'app'
      dimension_rollup_option: 1
      log_retention: 60
      metric_declarations:
        dimensions: # 付与するDimensionの組み合わせ
          - ["AvailabilityZone"]
          - ["AvailabilityZone", "DockerId"]
        metric_name_selectors: #この設定がマッチするメトリクス名
          - "^backend.*" #NOTE: メトリクス名は小文字に正規化される
service:
  pipelines:
    metrics:
      receivers: [otlp]
      processors: [batch]
      exporters: [awsemf]

アプリのソースコード

アプリ側のコードは以下の通りです。

メトリクス送信

メトリクスが観測されるタイミングと送信されるタイミングは一致しない(非同期処理)なので、観測値と共にタイムスタンプを記録しておいて、リーダーによってメトリクスが読み取られる度に送信します。タイムスタンプはtime_unix_nano という属性として指定します。

# オブザーバーを作成
meter.create_observable_gauge(
    "backendlatency", callbacks=[backend_latency_metric_callback],unit='MilliSecond'
)


# アプリで発生したメトリクスの観測値を保存する
backend_latency_metrics.append((time.time_ns(), latency))

# リーダーから呼び出されるコールバック
def backend_latency_metric_callback(options):
    observes = []
    # 保存していた観測値を変換して送信する
    for ts, latency in backend_latency_metrics:
        observes.append(metrics.Observation(latency, dict(**{'time_unix_nano': ts}, **metadata())))
    backend_latency_metrics.clear()
    return observes

docker-compse.yml

今回はメトリクス送信の確認だけなのでdocker composeを使ってローカルで確認します。 以下のように設定ファイルを指定してコレクタを起動します。

version: '3'
services:
  otel:
    image: amazon/aws-otel-collector:latest
    command: ["--config=/etc/otel-agent/otel-agent-config.yaml"]
    env_file:
      - .env
    volumes:
      - ./otel-agent:/etc/otel-agent
    ports:
      - "1777:1777"   # pprof extension
      - "55679:55679" # zpages extension
      - "4317:4317" # OTLP receiver
      - "4318:4318" # OTLP receiver
      - "13133"       # health_check

メトリクスの確認

15分ほど動作させると下記の通り送信したメトリクスを確認できました。

まとめ

EMF Exporterを使うことでアプリ側はOpenTelemetryに従ってメトリクスを送信しながらEMFでCloudWatchにログとメトリクスを送信できました。 非同期の送信処理は少し癖がありますがEMF固有のコードをなくすことができました。