2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

OpenTelemetry を活用した、コスト効率を重視したマルチクラスタ Observability 基盤の構築事例

Posted at

By Grace Park, DevOps Engineer, STCLab SRE Team

課題

STCLab では、NetFUNNEL と BotManager という 2 つのプラットフォームを運用しています。
これらは、フラッシュセールやグローバル投票といった高負荷イベントにおいて、
200 か国以上・最大 350 万同時接続を処理する大規模なシステムです。

2023 年、私たちは 20 年以上運用してきたオンプレミス基盤を段階的に終了し、
NetFUNNEL 4.x を Kubernetes ネイティブなグローバル SaaS として、ゼロから再設計する決断をしました。

当時の課題は、単なるコストではありませんでした。

開発・検証環境では APM を完全に無効化せざるを得ず、
本番環境でも 5% 程度のトレースサンプリング に制限されていました。

その結果、パフォーマンス劣化は本番リリース後に初めて発覚するという、
いわゆる「事後対応型」の運用に陥っていました。

すべての環境を妥協なく、かつコスト効率よくモニタリングできる仕組みが必要でした。

移行の成果

OpenTelemetry による計装と、CNCF ネイティブな LGTM スタック(Loki / Grafana / Tempo / Mimir)への移行により、以下を実現しました。

  • 旧ベンダー比で 72% のコスト削減

  • 全環境で 100% の APM トレースカバレッジ

  • 複数 Kubernetes クラスタを横断した統合 Observability

  • CNCF 支援のオープン標準に統一することで vendor lock-in を解消

本記事では、この移行プロジェクトの進め方、直面した技術的課題、
チューニング戦略、実際に有効だった構成を詳しく紹介します。

Observability アーキテクチャ概要

image-20260126-022505.png

主要なアーキテクチャ設計判断

1. Centralized Backend と Distributed Collectors

すべてのクラスタに LGTM を展開するのではなく、
マルチテナンシーを活用して単一の管理クラスタにテレメトリーを集約しました。

実装内容

  • 各クラスタには軽量な OTel Collector のみを配置

  • X-Scope-OrgID ヘッダーで tenant ID を付与(例:scp-dev, scp-prod

  • 中央バックエンド(Mimir Loki Tempo)が tenant 単位でデータを分離

  • tenant ごとのレート制限で noisy neighbor 問題を防止

重要な効果として、開発環境のメトリクス急増が本番環境に影響することはありません。

2. OpenTelemetry をユニバーサルな受信レイヤーとして採用

OTel Collector はマルチテナンシー対応、バッチ処理、バッファリング、リトライ、
tail sampling を一括で担います。

Java と Node.js ワークロードでは auto instrumentation を活用し、
アプリケーションコードを変更せずにフル APM を実現しました。

最大の利点は、バックエンドを完全に疎結合にできる点です。
Tempo から Jaeger へ移行する場合も、設定変更は 1 行のみで、アプリ修正は不要です。

主要な設定パターン

マルチテナンシー注入(クラスタ単位 Collector)

exporters:
  otlphttp/tempo:
    headers:
      X-Scope-OrgID: "scp-dev" # Unique ID for this environment
    retry_on_failure:
      enabled: true
    sending_queue:
      enabled: true

tenant ごとの制限設定(中央 Mimir)

runtimeConfig:
  overrides:
    scp-dev:
      max_global_series_per_user: 1000000 
      ingestion_rate: 10000

Processor の実行順序

  • memory_limiterを最初に実行

  • Enrichment(k8sattributes, resource

  • Filtering transformation(filter, transform

  • batch を最後に実行

 実運用で直面した主な課題

1. メトリクス数が 40 倍に膨張

DaemonSet で Collector を展開したことで、
14 ノード構成のクラスタでは Kubelet メトリクスが 14 回収集される状況になりました。

Target Allocator を per-node 戦略で有効化することで解決しました。

opentelemetryCollector:
  mode: daemonset
  targetAllocator:
    enabled: true
    allocationStrategy: per-node

ターゲット割当戦略

StatefulSet with consistent-hashing
image-20251104-072017.png

DaemonSet with per-node
image-20251104-071945.png

監視すべき指標

  • otelcol_receiver_refused_metric_points_total:Non-zero = データ損失

  • opentelemetry_allocator_targets_per_collector:per-node モードでは、ノードごとのポッド数の違いにより、分布が不均一になることが想定されます

2. バージョン不整合

Operator Collector Target Allocator のバージョンが一致していないと
Prometheus スクレイピングが失敗します。

結論として、すべてのコンポーネントのバージョンを統一しました。

2025-06-27T05:31:27.578Z    error    error creating new scrape pool    {"resource": {"service.instance.id": "bfa11ae0-f6ad-4d5b-97e8-088b8cd0a7f4", "service.name": "otelcol-contrib", "service.version": "0.128.0"}, "otelcol.component.id": "prometheus", "otelcol.component.kind": "receiver", "otelcol.signal": "metrics", "err": "invalid metric name escaping scheme, got empty string instead of escaping scheme", "scrape_pool": "otel-collector"}

根本原因: Prometheus のバージョン間で依存関係に破壊的変更(Breaking Change)が発生していたため。

  • v0.127.0:新しいエスケープ方式の設定を認識していなかったか、正しく実装されていません。

  • v0.128.0:新しい Prometheus 依存関係に基づいて構築され、エスケープ スキームの検証が強化され、古いスタイルの設定を受信したときに Prometheus 受信側が失敗する原因となりました。

ソリューション: すべてのコンポーネントバージョンを明示的に統合します:

opentelemetry-operator:
  manager:
    image:
      repository: "otel/opentelemetry-operator"
      tag: "0.131.0"
    collectorImage:
      repository: "otel/opentelemetry-collector-contrib"
      tag: "0.131.0"
    targetAllocatorImage:
      repository: "otel/target-allocator"
      tag: "0.131.0"

レッスン: Operator、Collector、および Target Allocator を常に一緒にバージョンロックします。

3. 小規模ノードでの OOM

2GB ノードでは、memory_limiter を設定しているにもかかわらず、Collector が OOM (Out Of Memory) を起こし、ノード全体がハングする事象が発生しました。

根本原因: グレースフルシャットダウンに必要なメモリヘッドルームが不足
image-20251029-095604.png

ソリューション:nodeAffinityにより、小規模ノードへの配置を防止

affinity:
  nodeAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      nodeSelectorTerms:
      - matchExpressions:
        - key: karpenter.k8s.aws/instance-size
          operator: NotIn
          values:
          - medium
      - matchExpressions:
        - key: karpenter.k8s.aws/instance-memory
          operator: NotIn
          values:
          - "2048"

レッスン: Collector は 最低でも 4GB のメモリを持つノードにのみ配置すべきです。

結論

当初、この規模で OTel Collector を本番運用している事例は多くありませんでした。
私たちはオープンソースコミュニティに多くを学び、
同じ課題に直面するチームの参考になればと考えています。

今後の記事では、

  • マルチテナンシー実装の詳細

  • Grafana ダッシュボード設計

  • Span Metrics を活用したアラート戦略

  • 本番環境でのトラブルシューティング

について紹介する予定です。

About STCLab

STCLab は、高負荷オンラインイベントにおけるトラフィック制御を専門とするソフトウェア企業です。

NetFUNNEL(Virtual Waiting Room)や BotManager(bot mitigation)などのプロダクトを提供しています。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?