📊

COS上のアプリのPrometheus MetricsをCloud Monitoringに送る

2024/09/22に公開

Google Cloudを利用してコンテナを動かす選択肢としてGoogle Computing Engine + Container-Optimized OS (COS)の構成があると思います。

しかし、COSで動かしているアプリケーションから出力されるPrometheusメトリクスをCloud Monitoringに送信したい場合、Ops Agent(Google Cloudで推奨される、Cloud Logging, Monitoring向けのエージェント)が利用できない問題があります。

issueでも議論がされていますが、執筆時点ではまだ対応される見込みはなさそうです。

https://github.com/GoogleCloudPlatform/ops-agent/issues/325

そこで、OpenTelemetryのCustom Collectorを使って代用できないかという検証を行ってみました。

ソースコードはこちらです。

https://github.com/ymtdzzz/otel-as-prom-agent

注意事項

この記事ではOpenTelemetryのstability: betaのコンポーネントをふんだんに利用しています。

また、Google Cloudの推奨構成からも離れることになるので、まずはOps Agentが利用できる別のOSに乗り換えることができないか検討することをおすすめします!

構成

やりたいこととしては、下記の要件を満たすコンポーネントを作ることです。

  • prometheus metrics endpointにアクセスしてメトリクスを取得(pull)する
  • バッファリングしつつCloud Monitoring APIに送信(push)する

OpenTelemetry Collectorは、Custom Collector Builder(ocb)という仕組みがあり、Receiver(メトリクスの受信)、Processor(メトリクスの加工)、Exporter(メトリクスの送信)をpluggableに構成することが可能です。

ocbの利用方法については逆井さん(@k6s4i53rx)の記事がわかりやすいです。

https://zenn.dev/k6s4i53rx/articles/df59cb65b34ef8

今回については、ありがたいことにそれぞれやりたいことが実現できるコンポーネントがありそうなので、これらを使ってみることにしました。

OpenTelemetry Collectorの作成

※すでにDockerhubにビルド済みのコンテナイメージがありますので、自前ビルドしたい方(arm使ってる方など)以外はスキップ可能です。

今回は下記のビルド設定でCollectorを作成します。OTLPさえない最小構成です。

# builder-config.yaml
dist:
  name: prom-cloud-monitoring-collector
  description: Prometheus collector for Cloud Monitoring
  output_path: ./prom-col
  otelcol_version: 0.109.0

exporters:
  - gomod:
      github.com/open-telemetry/opentelemetry-collector-contrib/exporter/googlecloudexporter v0.109.0

processors:
  - gomod:
      go.opentelemetry.io/collector/processor/batchprocessor v0.109.0

receivers:
  - gomod:
      github.com/open-telemetry/opentelemetry-collector-contrib/receiver/prometheusreceiver v0.109.0

ocbでCollectorのコードを生成します。コンテナ化したいのでこの時点ではビルドはしません。

$ ocb --config builder-config.yaml --skip-strict-versioning --skip-compilation

コンテナ化のため、Dockerfileを作成します。

#
# Build
#
FROM golang:1.22 AS builder

ENV CGO_ENABLED=0
ENV GOOS=linux
WORKDIR /build

COPY ./prom-col .

RUN go build -o prom-col ./...

#
# Deploy
#
FROM gcr.io/distroless/static-debian12:latest

WORKDIR /

COPY --from=builder /build/prom-col /prom-col

USER nonroot

CMD [ "/prom-col" ]

コンテナイメージを作成します。push先はお好みで。

docker build . -t yourname/otel-as-prom-agent:test

ローカルで動作確認

上記までの作業も含めてローカルで動作確認できるdocker compose yamlを用意したので、下記のリポジトリをクローンしておきます。

https://github.com/ymtdzzz/otel-as-prom-agent

テスト用credentialの発行

IAMで、「モニタリング指標の書き込み」ロールを付与したService Accountを作成し、credentialをkey.jsonというファイル名でクローンしたディレクトリ直下に配置します。

※実際にGCEにデプロイするときはworkload identityを利用するのでこの作業は不要です

また、Cloud Monitoring APIも有効化しておいてください。

テスト用コンテナの起動

docker compose upでコンテナが起動します。テストアプリはprometheus-example-appを利用させていただきました。

https://github.com/brancz/prometheus-example-app

localhost:8080にアクセスするとサンプルレスポンスが返ります。

$ curl localhost:8080                                                        
Hello from example application.

localhost:8080/metricsにアクセスすると、メトリクス情報が取得できます。

$ curl localhost:8080/metrics
# HELP http_request_duration_seconds Duration of all HTTP requests
# TYPE http_request_duration_seconds histogram
http_request_duration_seconds_bucket{code="200",handler="found",method="get",le="0.005"} 3
http_request_duration_seconds_bucket{code="200",handler="found",method="get",le="0.01"} 3
http_request_duration_seconds_bucket{code="200",handler="found",method="get",le="0.025"} 3
http_request_duration_seconds_bucket{code="200",handler="found",method="get",le="0.05"} 3
http_request_duration_seconds_bucket{code="200",handler="found",method="get",le="0.1"} 3
http_request_duration_seconds_bucket{code="200",handler="found",method="get",le="0.25"} 3
http_request_duration_seconds_bucket{code="200",handler="found",method="get",le="0.5"} 3
http_request_duration_seconds_bucket{code="200",handler="found",method="get",le="1"} 3
http_request_duration_seconds_bucket{code="200",handler="found",method="get",le="2.5"} 3
http_request_duration_seconds_bucket{code="200",handler="found",method="get",le="5"} 3
http_request_duration_seconds_bucket{code="200",handler="found",method="get",le="10"} 3
http_request_duration_seconds_bucket{code="200",handler="found",method="get",le="+Inf"} 3
http_request_duration_seconds_sum{code="200",handler="found",method="get"} 0.000103787
http_request_duration_seconds_count{code="200",handler="found",method="get"} 3
# HELP http_requests_total Count of all HTTP requests
# TYPE http_requests_total counter
http_requests_total{code="200",method="get"} 3
# HELP version Version information about this binary
# TYPE version gauge
version{version="v0.3.0"} 1

credentialがきちんと取得できていれば、Cloud MonitoringのMetrics Explorerにメトリクス情報が飛んでいるはずです。

GCE+COSでデプロイしてみる

いよいよ本番です。構成通りインスタンス内に2つのコンテナ(app, prom-col)を立てたいのですが、そのためにはcloud-initを用いた設定を行う必要があります。

cloud-init用設定ファイル(cloud-config)の作成

各コンテナの設定をsystemdのユニットファイルとして記述します。今回の例だと下記のようになります(リポジトリにもあります)。

#cloud-config

write_files:
  # コンテナ間通信のためのbridgeネットワークを作成します
  - path: /etc/systemd/system/create-network.service
    permissions: 0644
    owner: root
    content: |
      [Unit]
      Description=Create network for containers
      Before=app.service promcol.service

      [Service]
      Type=oneshot
      ExecStart=/usr/bin/docker network create my-bridge
      RemainAfterExit=true

  # サンプルアプリのコンテナ起動設定
  - path: /etc/systemd/system/app.service
    permissions: 0644
    owner: root
    content: |
      [Unit]
      Description=Start app container
      After=create-network.service

      [Service]
      Environment="HOME=/home/cloudservice"
      ExecStart=/usr/bin/docker run --rm --network my-bridge --name app quay.io/brancz/prometheus-example-app:v0.3.0
      ExecStop=/usr/bin/docker stop app

  # 今回作成したcollectorのコンテナ起動設定
  - path: /etc/systemd/system/promcol.service
    permissions: 0644
    owner: root
    content: |
      [Unit]
      Description=Start promcol container
      After=create-network.service

      [Service]
      Environment="HOME=/home/cloudservice"
      ExecStart=/usr/bin/docker run --rm --network my-bridge -v /etc/config.yml:/etc/config.yml --name promcol ymtdzzz/otel-as-prom-agent:test ./prom-col --config=/etc/config.yml
      ExecStop=/usr/bin/docker stop promcol
      
  # collector用の設定ファイル
  - path: /etc/config.yml
    permissions: 0644
    owner: root
    content: |
      receivers:
          prometheus:
            config:
              scrape_configs:
                - job_name: 'prom-col'
                  scrape_interval: 5s
                  static_configs:
                    - targets: ['app:8080']
      exporters:
        googlecloud:
          log:
            default_log_name: opentelemetry.io/collector-exported-log
      processors:
        batch:
      service:
        pipelines:
          metrics:
            receivers: [prometheus]
            processors: [batch] # NOTICE: use memory_limiter in production
            exporters: [googlecloud]

runcmd:
  - systemctl daemon-reload
  - systemctl start create-network.service
  - systemctl start app.service
  - systemctl start promcol.service

インスタンスの起動

上記の設定ファイルは、メタデータuser-dataの値として設定してあげることで、GCE起動時に実行してくれます。

スクリーンショットのように設定した上で起動してみましょう(コピペミス注意)。

起動できたら、適当にSSHしてコンテナが上がっていることを確認します。

docker run --rm -it --network my-bridge alpine /bin/shとかで適当なコンテナを立てて、リクエストを何度か送ってみます(curl app:8080)。

Cloud Monitoringで見てみましょう。

取れてそうですね。dimensionもちゃんと反映されてそうです。

まとめ

まだstableなコンポーネントではありませんが、OpenTelemetryのocbの仕組みを使えば比較的容易にPrometheusのscraping、及び監視バックエンドへの送信が行えました。

まだotelとpromのメトリクス変換は完全では無く、かつアップデート程度ではあるもののCustom Collector自体のコードのメンテナンスも必要になりますが、そこまで複雑なメトリクスを取得しない場合はこういった方法も使えるよ という一案として、誰かの参考になれば幸いです。

参考資料

Discussion