🔭

Cloud RunでOpenTelemetry Collectorをサイドカーとして動かす

2023/05/16に公開

こんにちは!Google Cloudでオブザーバビリティを担当しているものです!Cloud Runでマルチコンテナーサポートがパブリックプレビューになりましたね!これはCloud Runでサイドカーを走らせられるということです!というわけで今日は1ユースケースとしてOpenTelemetry CollectorをCloud Runのサイドカーとして走らせてみようと思います。

TL;DR

Cloud Runのマルチコンテナーサポートを使うと、アプリケーション側はOTLP送信の実装だけして、OpenTelemetry Collectorをサイドカーとして走らせて、テレメトリーをCloud Opsや外部のオブザーバビリティツールに送ることが可能になります。

構成

Kubernetesで使っているようなポッド内のサイドカーの構成をCloud Runでもできますよ、というだけなので、それをわかってる人にはそれだけの話です。

次の図でCloud Runの中で ApplicationCollector の2つのコンテナが動いていると思ってください。

コンテナの準備

まずアプリケーションとコレクターのコンテナをそれぞれ準備しましょう。

アプリケーション

今回のデモアプリケーションは、再帰的に分散トレース用のスパンを生成して、それをOTLP over gRPCで localhost:4317 に送信するアプリです。

OpenTelemetryの初期化部分はこうなっています。(少しだけ簡単のために改変)

main.go
    // OTLP exporter config for Collector (using default config)
    exporter, err := otlptracegrpc.New(
        context.Background(),
        otlptracegrpc.WithEndpoint("localhost:4317"),
        otlptracegrpc.WithInsecure(),
    )
    ...
    tp := sdktrace.NewTracerProvider(
        sdktrace.WithSampler(sdktrace.AlwaysSample()),
        sdktrace.WithBatcher(exporter),
        sdktrace.WithResource(res),
    )
    otel.SetTracerProvider(tp)
    otel.SetTextMapPropagator(propagation.TraceContext{})

注目してほしいのは otlptracegrpc.WithEndpoint() で、これが localhost:4317 に向いています。これでサイドカーのOpenTelemetry Collector(以下、コレクター)に送る設定になっています。

OpenTelemetry Collector

そして、今回の肝はコレクターをサイドカーとして走らせるところです。アプリケーションの側は localhost:4317 に対してスパンをOTLP over gRPCで送信してくるので、コレクター側はそれを受け取って、Cloud Traceに送信してあげる必要があります。まずそのための設定ファイルを書きます。

config.yaml
receivers:
  otlp:
    protocols:
      grpc:

processors:
  batch:

exporters:
  googlecloud:
    retry_on_failure:
      enabled: false

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [googlecloud]

4317 ポートは otlp レシーバーでのデフォルトポートなので、そのまま設定しておきます。また念の為 batch プロセッサーを用意し、最後にCloud Traceにトレース情報を送信するため googlecloud エクスポーターを設定しておきます。このエクスポーターは tracemetriclog のそれぞれの設定があり、それぞれCloud Trace、Cloud Monitoring、Cloud Loggingへのテレメトリーの送信に関連する設定を行います。

今回はトレース情報しか送信しないので、パイプラインの設定はトレースのみ( traces )だけ行いました。

この設定ファイルを使うわけですが、各プラグイン( otlpbatchgooglecloud )が同梱されているコレクターを使わなければいけません。本番環境などであれば、必要なプラグインだけが入ったCollectorを自前ビルドして作成するところですが、今回はデモ環境ということで opentelemetry-collector-contrib のプラグイン全部入りごった煮コレクターを使います。

Dockerfile
FROM ghcr.io/open-telemetry/opentelemetry-collector-releases/opentelemetry-collector-contrib:0.77.0-amd64
COPY config.yaml /etc/otel/config.yaml
EXPOSE 4317
CMD ["--config", "/etc/otel/config.yaml"]

行儀がいい方法ではSecret Managerから読み込んだりしますが、今回は簡単のために同梱してしまいます。

Cloud Run YAMLの設定

アプリケーションとコレクターのコンテナをビルドして、Cloud Runがアクセスできるコンテナレジストリーに入れたら、いよいよこの2つのコンテナをサイドカーパターンでCloud RunにデプロイするためのYAMLを用意します。これは公式ドキュメントにサンプルがあるのでそれを参考にしていきます。

Cloud Run YAML自体のリファレンスはこちらです。

今回はCloud Run用のインスタンスを起動しっぱなしにするためのオプションを追加で指定しています。(これをオンにしないとコレクターがすぐにシャットダウンされてしまうので指定しています。CPU always allocatedについての記事も参照してください。)

multicontainers.yaml
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  annotations:
    run.googleapis.com/launch-stage: BETA
  name: otel-sidecar-challenge
spec:
  template:
    metadata:
      annotations:
        autoscaling.knative.dev/minScale: "1"
        run.googleapis.com/cpu-throttling: "false"
        run.googleapis.com/execution-environment: gen2
    spec:
      containers:
        - image: ${REGISTRY}/otelsidecar:latest
          resources:
            limits:
              cpu: 1000m
              memory: 512Mi
        - image: ${REGISTRY}/recursive:latest
          ports:
            - containerPort: 8080

ここで ${REGISTRY} は適当にArtifact RegistryやContainer Registryのレジストリ名に読み替えてください。(例: asia-east1-docker.pkg.dev/sample-project/test-registry

Cloud Runでのコンテナの起動順は特に無いわけですが、もしコレクター起動前にリクエストが来たときのエラーが気になる場合は、コンテナの依存関係をYAMLファイルに設定してあげたほうが良いでしょう。

デプロイしてトレースを確認

もうあとはデプロイするだけです。(サンプルではここまでの手順をすべてCloud Buildですべて行うように設定しています。)

gcloud run services replace multicontainers.yaml --region asia-east1

これでエンドポイントにアクセスしてみると、無事にトレースが取れているのが確認できます。

トレースを確認してみると、Cloud Runが生成したルートスパンから取得できていることが分かります。

他のオブザーバビリティツールに流してみる

コレクターを使うところの良いところは、コレクター側の設定を変えるだけで、テレメトリーデータの処理や送信先が変更できることです。

せっかくなので、今回はSentryにもトレースデータを送ってみようと思います。先ほどのコレクター用設定ファイルを次のように変更して、Sentryをトレースの送信先としてパイプラインに追加します。

config.yaml
...
exporters:
  googlecloud:
    retry_on_failure:
      enabled: false
  sentry:
    dsn: https://key@host/path/123456...
    environment: prod
    insecure_skip_verify: true

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [googlecloud, sentry]

再度サービスをデプロイすると、次のようにSentry側でもスパン一覧に表示されるようになりました!

これをよく見ると、Cloud Run自体が生成した /root のスパンが取得されていません。

トレースIDで絞り込んでも、やはり /root のスパンは取得できていないようでした。このあたりはどの分散トレースバックエンドでは処理できるのか、他には確認できていないので、他に試した方がいればぜひ結果を共有してもらいたいです。

おわりに

OpenTelemery CollectorをサイドカーとしてCloud Run内で走らせることができました。コレクターをCloud Runで走らせるのは構成として少し大きいように感じるかもしれませんが、この構成が選択肢にあることでOpenTelemetryを使う幅が広がってくると思います。

今回確認できなかったCloud Runが生成するルートスパンの扱いについて、もし他のオブザーバビリティツールで確認できた例があればぜひ教えて下さい。

Google Cloud Japan

Discussion