OpenTelemetry Collectorのconfmap providerを実装してみる

この記事はOpenTelemetry Advent Calendar 2023の4日目の記事です。


こんにちは・こんばんは・おはようございます、id:aerealです。最近は所属組織のブログをよく書いています。

2023年は個人的にOpenTelemetry元年を迎えたところ社会でも元年めいているようなので一緒に盛り上がるためにも、この記事では掲題の通りCollectorをカスタマイズするテクニックについて紹介したいと思います。

confmapとconfmap providerとは

confmapとはOpenTelemetry Collectorの設定ファイルを解決して決定的な値を作るためのパッケージです。

confmap providerとは設定ファイル中の ${env:DD_API_KEY} のような記述を解決するコンポーネントで、外部のデータソースから値を取得します。 秘匿情報やデプロイごとに動的に変えたい値を指定したい時にこのコンポーネントを使うことで、設定ファイルはそのままにCollectorの挙動を変えられます。

たとえば設定ファイル中の ${env:DD_API_KEY} のように記述すると環境変数DD_API_KEY を参照できます。

confmap providerを実装する

Collectorのcoreには環境変数の他、ファイルやHTTP/HTTPSエンドポイントと通信するconfmap providerが含まれています。

しかし商用環境でアプリケーションを動かすシーンでは十分とはいえず、たとえば秘匿情報はふつう専用のサービスに保存し、実行時に都度取得するでしょう。 たとえばAWSであればSSM Parameters StoreSecrets Managerが挙げられます。

当然、サービスに認証情報を渡す必要があり、認証情報をキャリーする場所はHTTPヘッダが普通なので、httpsproviderではやはり実用上十分とはいえません。

そこでconfmap providerを自作し、それを組み込んだOpenTelemetry Collectorをビルドし動かす手があります。 なおOpenTelemetry Collectorを自前でビルドする際の知見は筆者が所属組織の開発者ブログで書いた記事も参照してください: refs. Lambda Extensionと自家版OpenTelemetry Collector - Classi開発者ブログ

confmap providerを自作するにはconfmap.Providerインターフェースを実装する型をConfiProviderSetings.ResolverSettings.Providersに渡します。 refs. opentelemetry-lambda/collector/internal/collector/collector.go at own-dist · aereal/opentelemetry-lambda · GitHub

confmap.Providerは Retrieve() で仕事を果たします。引数に渡される uri はたとえば ${awsssm:/path/to/param} という記述であれば /path/to/param という文字列が入ります。 Retrieve() は、この引数を外部サービスなりに渡して得たデータをconfmap.Retrievedという型で包んで返す責務があります。

confmap.Retrieved はすべてのフィールドがプライベートなので confmap.NewRetrieved() 関数のみで作れる構造体です。 ドキュメントにあるようにおおむねJSONYAMLと相互に変換可能な型のみを渡せますが、marshal/unmarshalをカスタマイズする方法は少なくとも公開インターフェースとして現在提供されていないようですので、NewRetrieved()が許容する型をその通りに返さなければいけません。

以上を踏まえてAWS SSM Parameters Storeからパラメータを取得するconfmap providerを実装したコードが以下になります: otel-confmap-provider-awsssm/provider.go at main · aereal/otel-confmap-provider-awsssm · GitHub

AWS SDK Go v2を使ってssm:GetParameterを呼び、その結果を素朴にconfmap.NewRetrieved()に渡すだけです。

SSMとの認証はAWS SDKのデフォルトの認証情報チェーンに頼っています。即ちAWS環境ではメタデータエンドポイントを介して得られたトークンを使います。

ローカルなどその他の環境で動作させるには AWS_ACCESS_KEY_ID などの環境変数を設定するか ~/.aws/credentials に適切な値を置く必要がありますが本題から逸れるのでここでは詳しく触れません。 言い換えれば自作したconfmap providerで外部サービスへ認証するには環境変数などconfmap providerに頼らない方法で認証情報を得る必要があります。

また筆者が実装したParameters Storeから取得するconfmap providerは都度HTTPリクエストを投げればよいので使っていませんが、confmap providerが終了した際に呼ばれるイベントハンドラを設定することもできます。

つまりデーモンのように振る舞うconfmap providerを実装することができます。これはステートフルな通信プロトコルを使うことを示すだけではなく、設定のホットスワップに対応する意欲があればサポートできることも意味します (実際に変更を検知するコールバックにアクセスできます)。

むすび

以上の解説は公開されたインターフェースとドキュメントコメントによるものですが、confmapパッケージのドキュメント自体はWIPであると書かれているので、実装から離れた設計や思想の部分は誤解などがあるかもしれません。

現在のところconfmap providerはcontrib系リポジトリを含めても素朴なものしか見当たらないので、実際にWatcherFuncなどをどのように活用するべきなのか筆者も理解しかねています。

この記事がconfmap providerやOpenTelemetry Collectorの理解の一助になったり、自作してみるきっかけになれば嬉しいです。

良き監視ライフを!