👋

OpenTelemetry でPHP の観測に挑戦してみた

2024/02/22に公開

こんにちは。
ご機嫌いかがでしょうか。
"No human labor is no human error" が大好きな吉井 亮です。

PHP で作成されたサイト運営に少しだけ関わることがあったので、OpenTelemetry を用いた Observability を試してみました。
構成は以下になっています。OpenTelemetry Collector、Tempo、Promtail、Loki、Grafana で構成しました。
img

OpenTelemetry と PHP

OpenTelemetry PHP のステータスは Traces、Metrics、Logs ともに Stable となっています。
自動計装用の Extension が用意されています。既存コード修正が必要最低限に抑えられるのは嬉しいことです。

やってみた

今回は minikube を使ってローカル環境で構築しました。こちらで yaml 等を公開しています。

デモアプリ

デモアプリは OpenTelemetry のサンプルをそのままつかいました。

こちらが Dockerfile です。依存関係や SDK をインストールしています。少々過剰な気がしながらも追加追加していたらこのようになりました。

https://github.com/YoshiiRyo1/zenn_blogs/blob/main/sources/php-o11y/docker/php-fpm/Dockerfile

コンテナ起動時に OpenTelemetry のいつもの環境変数を足してあげれば OK です。自動計装は便利ですね。

        env:
        - name: OTEL_PHP_AUTOLOAD_ENABLED
          value: "true"
        - name: OTEL_SERVICE_NAME
          value: "php-demo"
        - name: OTEL_TRACES_EXPORTER
          value: "otlp"
        - name: OTEL_METRICS_EXPORTER
          value: "none"
        - name: OTEL_LOGS_EXPORTER
          value: "console"
        - name: OTEL_EXPORTER_OTLP_PROTOCOL
          value: "grpc"
        - name: OTEL_EXPORTER_OTLP_ENDPOINT
          value: "http://opentelemetry-collector:4317"
        - name: OTEL_PROPAGATORS
          value: "baggage,tracecontext"

OpenTelemetry Collector

Collector の主だった設定は以下のようになっています。なんの変哲も無いです笑

  exporters:
    debug: {}
    otlp:
      endpoint: tempo:4317
      tls:
        insecure: true
  extensions:
    health_check: {}
    memory_ballast: {}
  processors:
    batch: {}
    memory_limiter:
      check_interval: 5s
      limit_percentage: 80
      spike_limit_percentage: 25
  receivers:
    otlp:
      protocols:
        grpc:
          endpoint: ${env:MY_POD_IP}:4317
        http:
          endpoint: ${env:MY_POD_IP}:4318
  service:
    extensions:
      - health_check
      - memory_ballast
    pipelines:
      traces:
        exporters:
          - debug
          - otlp
        processors:
          - memory_limiter
          - batch
        receivers:
          - otlp

Promtail

OpenTelemetry Log を標準エラー(StdErr)に出しそれを Promtail で収集しています。
標準エラーだと JSON ではなく、ただのテキストがつらつら出てしまうので、改行と空白を削除し1行にまとめています。苦肉の策です。

    scrapeConfigs: |
      - job_name: kubernetes-pods
        pipeline_stages:
          - match:
              selector: '{container="php-demo"}'
              stages:
                - docker: {}
                - multiline:
                    firstline: '^\x{007B}'
                    timeout: 3s
                - replace:
                    expression: '(\n)'
                    replace: ""
                - replace:
                    expression: '(\s)'
                    replace: ""

Grafana

データソースの設定から Loki と Tempo を抜き出しました。
こうしておくと Grafana でトレースを表示させた際に該当のログとリンクさせることが可能です。

datasources: 
  datasources.yaml:
    apiVersion: 1
    datasources:
    - name: Loki
      type: loki
      access: proxy
      url: http://loki:3100
      jsonData:
        timeout: 60
        maxLines: 1000
        derivedFields:
          # Field with internal link pointing to data source in Grafana.
          # datasourceUid value can be anything, but it should be unique across all defined data source uids.
          - datasourceUid: tempo
            matcherRegex: "traceID=(\\w+)"
            name: TraceID
            # url will be interpreted as query for the datasource
            url: '$${__value.raw}'
            # optional for URL Label to set a custom display label for the link.
            urlDisplayLabel: 'View Trace'
    - name: tempo
      type: tempo
      url: http://tempo:3100
      isDefault: false
      access: proxy
      basicAuth: false
      jsonData:
        tracesToLogsV2:
          # Field with an internal link pointing to a logs data source in Grafana.
          # datasourceUid value must match the uid value of the logs data source.
          datasourceUid: 'Loki'
          spanStartTimeShift: '1h'
          spanEndTimeShift: '-1h'
          tags: [
            { key: 'app'}
          ]
          filterByTraceID: false
          filterBySpanID: true
          customQuery: false

参考

A language-specific implementation of OpenTelemetry in PHP
Quick start for Tempo
Grafana Loki documentation
Promtail agent

Discussion