EKSのメトリクスをCloudWatchに取り込む　AWS Distro for OpenTelemetryで

## はじめに
Kubernetesで動作するアプリケーションのメトリクスを管理する方法としては、Prometheus + GrafanaやELKスタックがよく挙げられます。
しかしながら、AWSのElastic Kubernetes Service (EKS)の場合は、AWSのマネージド監視サービスであるCloudWatchを利用したい方も多いのではないでしょうか。

[AWS Distro for OpenTelemetry](https://aws-otel.github.io/) を使えば、簡単にEKS上のアプリのメトリクスをCloudWatchに送信できます！PodのCPU/RAM使用率などはもちろん、Prometheus形式のメトリクスも対応可能です。


## AWS Distro for OpenTelemetry (ADOT) とは
AWSが公式にメンテナンスしている、OpenTelemetry Collectorのディストリビューションです。名前が長いので、以下では頭文字を取って **ADOT** という略称で呼びます。

### そもそもOpenTelemetry Collectorとは？
アプリケーションのログ･メトリクス･トレースをそれぞれ統一的に扱い、多様な入力･出力に対応するための収集エージェントです。[GitHub](https://github.com/open-telemetry/opentelemetry-collector)
特定のクラウドベンダーに依存せずに共通で使うことができるため、昨今かなり活発に開発されています。

Fluent Bitをご存じの方は、(かなり雑に言うと) 似たようなものと思っても良いでしょう。
OpenTelemetry Collectorは Receiver / Processor / Exporter という主に3種類のコンポーネント(下図)を持ちますが、それはFluent Bitが Input / Filter / Output の3要素を持つのと似ています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127392/bf43f5c8-a5de-d272-053d-2e3bae246772.png)



Fluent BitのInputにはstatsdやTail、HTTPなど、OutputにはCloudWatchやStackdriverなどがサポートされており、多様なユースケースで利用可能です。また、Filter/Parserを設定することで、柔軟に収集を構成することもできます。
これと同じことがOpenTelemetry Collectorにも言えます。[Receiverのリスト](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver), [Exporterのリスト](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/exporter)

Fluent Bitとの違いを挙げるなら、Fluent Bitはログ収集がメインのユースケースと捉えている方が多いのではないでしょうか。このため、メトリクス収集においては利用者が少なく、まだサポートも弱いように思います。

一方OpenTelemetry Collectorは、メトリクス収集のユースケースがよくサポートされているようです。例えば、AWS Container Insights Receiverというコンポーネントを使うと、EKSのPod/Nodeのメトリクスがまとめて取得でき、そのままCloudWatchのコンソールで可視化が可能です。


### ADOTは何のためにある？
ADOTはEKS上で使うために便利なコンポーネントがプリインストールされたOpenTelemetry Collectorのディストリビューションです。このため非常に簡単に利用開始でき、またAWSサービスに統合することができます。[プリインストールされたコンポーネント一覧はこちら](https://github.com/aws-observability/aws-otel-collector#adot-collector-built-in-components)。

ここもFluent Bitと類推すると、素のFluent Bitに対して、いくつかのプラグインがプリインストールされた [AWS for Fluent Bit](https://github.com/aws/aws-for-fluent-bit) が提供されているのに似ていますね。
このため、EKSでOpenTelemetry Collectorを使いたい場合は、ADOTを使うのが手っ取り早いでしょう。

その他のディストリビューションとしては、[OpenTelemetry 公式のもの](https://opentelemetry.io/docs/collector/distributions/)がありますが、EKSでは明らかに不要なコンポーネントも含まれているようです。ADOTはEKSで使う場合には必要十分な、ちょうどよい選択肢となりそうですね。

なお、ADOT自体はEKSだけでなくECSやEC2上での動作も想定して開発されていますが、この記事では特にEKS上で使う際の話に特化します。

## インストール方法
EKSにADOTをインストールするには、いくつか方法があります。

### yamlでインストール
[こちらのYAML](https://github.com/aws-observability/aws-otel-collector/blob/main/deployment-template/eks/otel-container-insights-infra.yaml) を `kubectl apply` することでインストールできます。
手順の詳細は[こちらを参照してください](https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/monitoring/Container-Insights-EKS-otel.html)。

yamlでインストールすると、構成をフルにカスタマイズ可能なため、柔軟性を求める場合はおすすめです。

### Helmでインストール
HelmによるインストールもAWS公式にサポートされています。
手順の詳細は[こちらを参照してください](https://github.com/aws-observability/aws-otel-helm-charts/tree/master/charts/adot-exporter-for-eks-on-ec2)。

このHelmチャートにはログ収集のためのFluent Bitも含まれるため、手早くEKSにLog/Metrics収集機能を持たせるには便利でしょう。
ただし、チャートの外から必要な設定をいじれない場合もあるはずなので、完全なカスタマイズ性を求める場合は不向きかもしれません。


また、yaml/helmの手順いずれにしても、ADOTの利用するサービスアカウントのためのIAMロールを別途作成する必要があります。詳細は[こちらを参照してください](https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/monitoring/Container-Insights-prerequisites.html)。
基本的には、ADOTの利用するサービスアカウントがOIDCでAssume可能なIAMロール (ポリシーは`CloudWatchAgentServerPolicy` を付与) を作成すれば良いです。

## 構成方法
上記のインストール方法をそのまま利用した場合、CloudWatch Container Insightsのメトリクスが自動的に送信されはじめます。
このため、インストール後しばらくすると、[CloudWatchのマネジメントコンソール](https://ap-northeast-1.console.aws.amazon.com/cloudwatch/home?region=ap-northeast-1)から下記のようなDashboardを確認できるはずです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127392/68c15446-2862-cc68-42ef-d776871fa545.png)

これでは不足している場合、ADOTの構成を変更して、ユースケースに合うようにカスタマイズすることができます。

### ADOTの設定
ADOTをyamlでインストールした場合、yaml内に `otel-agent-config` という設定があります。
この部分を編集することで、ADOTの構成をカスタマイズすることができます。

Helmでインストールした場合、[`values.yaml`](https://github.com/aws-observability/aws-otel-helm-charts/blob/master/charts/adot-exporter-for-eks-on-ec2/values.yaml) に従っていくつかの項目を設定できるようです。
ただし、Helmチャートの中身を読み解いて良い感じに記述する必要がありそうです。
この点においては、yamlでインストールしたほうが分かりやすいかも知れません。

ADOTの設定の書き方は、[こちらを参照してください](https://opentelemetry.io/docs/collector/configuration/)。

### Prometheusメトリクスの利用
よくありそうなユースケースとして、Prometheus形式のメトリクスをCloudWatchに送信したい場合を考えてみます。

まず、Receiverとして [Prometheus receiver](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver/prometheusreceiver)を利用できます。以下は簡単な構成例です。

```yaml:Prometheus Receiverのコード例
    receivers:
      prometheus:
        config:
          # https://prometheus.io/docs/prometheus/latest/configuration/configuration/
          scrape_configs:
            - job_name: 'otel-collector'
              scrape_interval: 60s
              kubernetes_sd_configs:
                - role: pod
              relabel_configs:
                # PodのAnnotationからPrometheus metricsエンドポイントを検出する
                # https://github.com/prometheus/prometheus/blob/v2.33.4/documentation/examples/prometheus-kubernetes.yml#L157-L178
                - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
                  action: keep
                  regex: true
                - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
                  action: replace
                  target_label: __metrics_path__
                  regex: (.+)
                - source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port]
                  action: replace
                  regex: ([^:]+)(?::\d+)?;(\d+)
                  replacement: $$1:$$2
                  target_label: __address__

                # awscontainerinsightreceiverと同一のLabel (Attribute) を付与する
                - source_labels: [__meta_kubernetes_namespace]
                  action: replace
                  target_label: Namespace
                - source_labels: [__meta_kubernetes_pod_node_name]
                  action: replace
                  target_label: NodeName
                - source_labels: [__meta_kubernetes_pod_name]
                  action: replace
                  target_label: PodName
                # cluster名は既存の __meta labelから取得できないので、外から渡す
                # https://albersdevelopment.net/2019/08/28/prometheus-adding-a-label-to-a-target/
                - source_labels: [__address__]
                  target_label: ClusterName
                  replacement: "sample-cluster"
                # Service名は既存のlabelから取得できない とりあえずコンテナ名を代替にする
                - source_labels: [__meta_kubernetes_pod_container_name]
                  action: replace
                  target_label: Service
```

Prometheus receiverの設定では、`config` に[Prometheus の Configuration](https://prometheus.io/docs/prometheus/latest/configuration/configuration/) (`scrape_configs`) を直接記述できます。この例では `kubernetes_sd_configs` で `pod` のメトリクスを収集するようにしています。

続く `relabel_configs` では、まずPodのannotationを見て、メトリクス取得対象PodのAuto discoveryをしています。これはADOTとは直接関係ない、[Prometheusでよく知られたハック](https://stackoverflow.com/questions/53365191/monitor-custom-kubernetes-pod-metrics-using-prometheus)です。
これにより、これらのAnnotationが設定されたPodは自動的にADOTのメトリクス収集対象となります。

続いて、 `Namespace` / `NodeName` / `PodName` / `Service` / `ClusterName`といったラベルを設定しています。ラベル名は、[AWS Container Insights Receiver](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver/awscontainerinsightreceiver) で設定されるラベルと同一になるようにしています。
これらのラベルは、Exporterの `awsemf` という設定で参照します。以下の `dimensions` の部分に注目してください。

```yaml:ADOT設定のexportersセクション
    exporters:
      awsemf:
        namespace: ContainerInsights
        log_group_name: '/aws/containerinsights/{ClusterName}/performance'
        log_stream_name: '{NodeName}'
        metric_declarations:
          # labelはこのDimensionで指定する！
          - dimensions: [[NodeName, ClusterName]]
            metric_name_selectors:
              - node_cpu_utilization
              - node_memory_utilization
```

`awsemf`は、[AWS CloudWatch EMF Exporter](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/exporter/awsemfexporter#aws-cloudwatch-emf-exporter-for-opentelemetry-collector)の設定です。

このDimesionで設定したラベルにより、CloudWatch上でメトリクスがグルーピングされて集計されます。また、ここで指定されたラベルが存在しないメトリクスは無視され、CloudWatchに送信されません。
CloudWatch上でメトリクスをグルーピングするために、Prometheusメトリクスについても適切にラベルを設定することが必要になります。
ラベルは自由に設定でき、またDimensionの項目も任意ですので、ユースケースに合わせてカスタマイズするのが良いでしょう。
 
ここではPrometheus形式のメトリクス収集例を紹介しましたが、他にも多くのReceiverが対応されています。
[対応されたコンポーネント一覧](https://github.com/aws-observability/aws-otel-collector#adot-collector-built-in-components)
これらは同様にconfigファイルを記述することで利用できるため、適宜試してみてください。


### コストの節約
デフォルトの設定では、一般的に必要とされるメトリクスを一通り送信しています。
ユースケースに合わせてメトリクスを取捨選択することで、コストの節約が可能です。

詳細は[こちらの記事](https://aws.amazon.com/jp/blogs/news/cost-savings-by-customizing-metrics-sent-by-container-insights-in-amazon-eks/)がよくまとまっています。
簡単にまとめると、ADOTのProcessor機能を利用することで、メトリクスのフィルタ･不要なフィールドの削除などを行います。

```yaml
    processors:
      # 特定のフィールドをEMFのログから削除する例
      resource:
        attributes:
        - key: Sources
          action: delete
        - key: kubernetes
          action: delete

      # 特定の名前をもつメトリクスを処理対象から除外する例
      filter/exclude:
        metrics:
          exclude:
            match_type: regexp
            metric_names:
              - container_.*
              - go_.*
              - redis_.*
```

なお、コストの由来はCloudWatch Logsの取り込み料金とCloudWatch Metricsのメトリクスごとの課金が主です。
なぜログ？と思われる方もいるかも知れませんが、ADOTではメトリクスを[EMF形式](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Embedded_Metric_Format_Specification.html)でログとして送信するため、ログの取り込み料金も課金対象になります。
塵も積もればなので、監視に不要なメトリクスは積極的に除外していくのが良さそうです。[参考: CloudWatch 利用料金](https://aws.amazon.com/jp/cloudwatch/pricing/)


## メトリクスは分かった　ログやトレースは？
Observability 3種の神器はログ/メトリクス/トレースですが、ADOTはいずれも対応可能でしょうか？

OpenTelemetry Collector自体には、ログをCloudWatchにエクスポートするプラグインが一応あります。[AWS CloudWatch Logs Exporter](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/exporter/awscloudwatchlogsexporter)
しかしながら、こちらは現状Experimentalであり、またADOT自体にはまだ含まれていません。
このため、ログをCloudWatchに送るのは、Fluent Bitなどを利用するのが現状良いでしょう。

トレースに関しては、ADOTにトレースをAWS X-Rayへエクスポートするプラグインが含まれています。[AWS X-Ray Tracing Exporter for OpenTelemetry Collector](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/exporter/awsxrayexporter)
こちらを利用することで、トレースも扱えるはずです。(ただ私はまだ使ったことないので、利用レポートをお待ちしてます！)

## まとめ
AWS Distro for OpenTelemetry (ADOT)を使うことで、簡単にEKSのメトリクスをCloudWatchに収集することが可能です。
AWS公式にメンテナンスされており現在も活発に開発されているため、将来有望な手段です。
すでにCloudWatchにログを収集されている方、メトリクスの収集先･収集方法に困っている方はぜひ試してみてください！


## 参考文献
* [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/)