OpenTeremetry CollectorとLokiを使ってログ保存環境を構築した

投稿者: | 2025年3月19日

OpenTelemetryとは?

近年、OpenTelemetryという技術が使われるようになってきている。

これはサーバやアプリケーションのテレメトリデータ(メトリクス、ログ、トレースなど)を扱うプロトコルなどを統一し、ソフトウェアの動作などに対する分析を容易にするものである。

DatadogやSplunkといった監視やオブザーバビリティ関連のソリューションを提供しているサービスでも採用されてるようになってきている。

OpenTelemetryではテレメトリデータとしてログについても扱うことが出来る。

ログの管理にはsyslogが広く採用されているが、syslogの形式は近代的なアプリケーションやログ管理の仕組みとしては機能不足な面が否めない。

例えばログにラベルをつけるといったことを実現しようとするとsyslog単体の機能では不足である。OpenTelemetryではログにラベルをつけることが可能で、保存する基盤側でもラベルによる検索機能などが提供されている。

OpenTelemetryを使ってログを取り扱うことでログのやりとりを標準化することができ、ログ記録を行う仕組みの近代化も実現が容易となる。

なお、fluentd、fluent-bitといったログ転送に特化したエージェント導入してOpenTelemetryを使わずに多機能なログ基盤にログを保存する、ということも可能である。

しかし、標準化を進めている団体がいるOpenTelemetryの方が長期的に見るとメリットが大きいと考えられる。

さて、今回はこのOpenTelemetryを使って自宅にあるインフラたちのログを集約するようなことを実施してみた記録である。

環境について

自宅で稼働しているサーバは以下のようなものがある。

  • OpenStack
  • OpenStackで動作しているVM * 4台
  • Kubernetes
    • Pod数は80-100程度

これらのサーバのログを集約するような形で構築を行なっていく。

ログ保存の構成

今回は以下のような構成でログ保存を行なっていくこととする。

  • ログの転送
    • otel-collector
  • ログの保存
    • Loki
    • QuObjects
  • ログの検索、確認
    • Grafana

ログの保存に今回はLokiを利用することにした。OpenTelemetryに対応しておりログ保存の基盤としての事例も多いものである。

また、Lokiがデータを保存するバックエンドとしては、QNAPのQuObjectsというS3互換のストレージを利用することにした。なお、QuObjectsは中を覗いてみるとOpenStackのSwiftとGlusterFSを組み合わせたもののようである。

ログの転送にはotel-collector、ログ自体の確認については、Grafanaを利用するという構成を選んだ。

以下のような図の構成となる。

またLokiはコンテナで運用することとした。otel-collectorについてはホスト側で直接動作するような設定とする。

Lokiの構築

Lokiはdockerを使って構築した。主に以下のような設定を行なった。

  • ログの保存先をQuObjectsに設定
  • ログの保存期間を365日に設定
  • ログの保存先については、hostnameとservice_nameをindex labelとして設定

最終的に、以下のような compose.yamlloki-config.yaml を作成した。

compose.yaml の内容

services:
  loki:
    image: grafana/loki:latest
    restart: always
    ports:
      - "3100:3100"
    volumes:
      - ./loki-config.yaml:/etc/loki/local-config.yaml
    command: -config.file=/etc/loki/local-config.yaml      

loki-config.yaml の内容

auth_enabled: false

server:
  http_listen_port: 3100
  log_level: error

common:
  instance_addr: 127.0.0.1
  path_prefix: /loki
  replication_factor: 1
  ring:
    kvstore:
      store: memberlist

memberlist:
  join_members: [loki]


limits_config:
  metric_aggregation_enabled: true
  # ログの保持期間を365日に設定
  max_query_lookback: 365d
  retention_period: 365d
  allow_structured_metadata: true
  # hostnameとservice_nameをindex labelとして設定
  # 各ホストに対してhostnameというラベルを付与して、検索できるようにしたいので設定する。 
  # 同様にservice_nameにも設定を行っている。
  # なお、lokiはデフォルトで service.name(otlpでは `.` が `_` に変換されてservice_nameになる) というラベルにindexを貼るようになっている。
  # よって、こちらの設定はなくても良いが、明示的に設定しておく。
  otlp_config:
    resource_attributes:
      attributes_config:
        - action: index_label
          attributes:
            - hostname
        - action: index_label
          attributes:
            - service_name

# S3互換のストレージ(QNAPのQuObjects)に保存するように設定
storage_config:
  tsdb_shipper:
    active_index_directory: /loki/index
    cache_location: /loki/index_cache
    resync_interval: 5s
  aws:
    endpoint: http://<QNAPのQuObjectsのアドレス>
    insecure: true
    bucketnames: <バケット名>
    access_key_id: <QuObjectsのアクセスキー>
    secret_access_key: <QuObjectsのシークレットキー>
    s3forcepathstyle: true

schema_config:
  configs:
    - from: 2025-03-07
      store: tsdb
      object_store: aws
      schema: v13
      index:
        prefix: loki_index_
        period: 24h

ruler:
  alertmanager_url: http://localhost:9093

# ログを定期的に削除するように設定
compactor:
  working_directory: /loki/retention
  delete_request_store: aws
  retention_enabled: true

あとは、docker compose up -d で起動すればLokiが起動する。

otel-collectorのインストール

こちらは、ホストで直接動作させるようにした。動作させるホストの環境は `Ubuntu 24.04` である。

インストールは以下のように公式のdebパッケージを使って行った。

ただし、公式の手順でインストールすると拡張機能が何も追加されていないotel-collectorがインストールされる。公式の手順、dockerなどの手順では拡張機能が組み込まれている `opentelemetry-collector-contrib` を動かす手順になっているので罠である。

ここでは、GitHubのopentelemetry-collector-releasesリポジトリからotel-contribのdebファイルをダウンロードしてインストールすることとした。

以下のような手順でインストールした。バージョンなどは環境に合わせて変更する必要がある。

wget https://github.com/open-telemetry/opentelemetry-collector-releases/releases/download/v0.121.0/otelcol-contrib_0.121.0_linux_amd64.deb
sudo dpkg -i otelcol-contrib_0.121.0_linux_amd64.deb

これで、otel-collectorがインストールされて自動的に動作するようになった。あとは、ログを転送する設定を行なっていくこととなる。

ここでは、以下のログファイルをLokiに転送するように設定を行なう。

  • /var/log/以下にあるログファイル
  • journald が管理しているログ

また、ログには追加で以下の情報を付与することとする。

  • hostname
  • service_name
  • severity_text

ログを読めるように権限付与

インストールした直後の状態だと、otel-collector-contribというユーザでotel-collectorが動作するようになっている。このユーザにはログを読む権限が付与されていないため、後々困ることになる。そのため、ログを読む権限を付与しておく。

sudo usermod -aG adm otel-collector-contrib

otel-collectorの設定

/etc/otelcol-contrib/config.yaml に設定を追加する。このファイルを編集後、systemdctlでサービスを再起動する。

filelogの設定

ログを転送するために、ログを受け取るreceiverを設定する。ここではfilelog receiverを使って`/var/log/` 以下にあるログファイルを読み取るよう設定した。

  filelog:
    include: [ "/var/log/**/*.log", "/var/log/syslog" ]
    include_file_path: true
    include_file_name: true
    preserve_leading_whitespaces: true
    operators:
      - type: recombine
        combine_field: body
        is_first_entry: body matches "^[^\\s]"
        source_identifier: attributes["log.file.path"]
    storage: file_storage/filelogreceiver

storageとして file_storage/filelogreceiverを指定している。

これは、不意にotel-collectorが停止した場合などにログが失われてしまう可能性があるためファイルに保存するための拡張である。

以下のようにextensionの設定を行なっておく。

extensions:
  file_storage/filelogreceiver:
    create_directory: true
    directory: /var/lib/otelcol/file_storage/receiver
  file_storage/otlpoutput:
    create_directory: true
    directory: /var/lib/otelcol/file_storage/output

processorの設定

filelogから受け取ったログに対して、追加の情報(resource)を付与するためにprocessorを設定する。ここでは、hostnameservice.name を設定している。

processors:
  resource/filelog:
    attributes:
      - key: hostname
        value: dev-server
        action: upsert
      - key: service.name
        value: system-log
        action: upsert

exporterの設定

あとはLokiに転送するためのexporterと今まで設定してきたreciever、processorをつなげたpipelineを設定すれば設定は完了である。

exporters:
  debug:
    verbosity: detailed
  otlphttp/loki:
    endpoint: http://192.168.6.74:3100/otlp
    sending_queue:
      storage: file_storage/otlpoutput

service:
  pipelines:
    logs/filelog:
      receivers: [filelog]
      processors: [resource/filelog,transform/severity_parse,batch]
      exporters: [otlphttp/loki]

最終的に完成したotel-collectorの設定

journaldにも同様の設定を行って完成させたものが以下の設定となる。この設定ではprocessorsをfilelog、journaldにそれぞれ別になるように名前付けしてpipelineを設定している。routingなどを使えば共通にできると思うが、今回はこのように設定した。

# To limit exposure to denial of service attacks, change the host in endpoints below from 0.0.0.0 to a specific network interface.
# See https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/security-best-practices.md#safeguards-against-denial-of-service-attacks

extensions:
  health_check:
  pprof:
    endpoint: 0.0.0.0:1777
  zpages:
    endpoint: 0.0.0.0:55679
  file_storage/filelogreceiver:
    create_directory: true
    directory: /var/lib/otelcol/file_storage/receiver
  file_storage/otlpoutput:
    create_directory: true
    directory: /var/lib/otelcol/file_storage/output

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

  # Collect own metrics
  prometheus:
    config:
      scrape_configs:
      - job_name: 'otel-collector'
        scrape_interval: 10s
        static_configs:
        - targets: ['0.0.0.0:8888']
  journald:
    directory: /var/log/journal

  filelog:
    include: [ "/var/log/**/*.log", "/var/log/syslog" ]
    include_file_path: true
    include_file_name: true
    preserve_leading_whitespaces: true
    operators:
      - type: recombine
        combine_field: body
        is_first_entry: body matches "^[^\\s]"
        source_identifier: attributes["log.file.path"]
    storage: file_storage/filelogreceiver


processors:
  resource/filelog:
    attributes:
      - key: hostname
        value: dev-server
        action: upsert
      - key: service.name
        value: system-log
        action: upsert
  resource/journald:
    attributes:
      - key: hostname
        value: dev-server
        action: upsert
      - key: service.name
        value: journal-log
        action: upsert
  transform/severity_parse:
    log_statements:
    - context: log
      statements:
      - set(severity_text, "Info") where IsMatch(body.string, ".*INFO.*")
      - set(severity_text, "Warn") where IsMatch(body.string, ".*WARN.*")
      - set(severity_text, "Error") where IsMatch(body.string, ".*ERROR.*")
      - set(severity_text, "Fatal") where IsMatch(body.string, ".*FATAL.*")
  batch:

exporters:
  debug:
    verbosity: detailed
  otlphttp/loki:
    endpoint: http://192.168.6.74:3100/otlp
    sending_queue:
      storage: file_storage/otlpoutput

service:
  pipelines:
    logs/filelog:
      receivers: [filelog]
      processors: [resource/filelog,transform/severity_parse,batch]
      exporters: [otlphttp/loki]
    logs/journald:
      receivers: [journald]
      processors: [resource/journald,transform/severity_parse,batch]
      exporters: [otlphttp/loki]
  extensions: [health_check, pprof, zpages, file_storage/filelogreceiver, file_storage/otlpoutput]

Grafanaの構築

Grafanaもdockerを使って構築した。以下のような compose.yaml を作成して起動するだけである

services:
  grafana:
    environment:
      - GF_PATHS_PROVISIONING=/etc/grafana/provisioning
      - GF_AUTH_ANONYMOUS_ENABLED=true
      - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
      - GF_FEATURE_TOGGLES_ENABLE=alertingSimplifiedRouting,alertingQueryAndExpressionsStepMode
    entrypoint:
      - sh
      - -euc
      - |
        mkdir -p /etc/grafana/provisioning/datasources
        cat <<EOF > /etc/grafana/provisioning/datasources/ds.yaml
        apiVersion: 1
        datasources:
        - name: Loki
          type: loki
          access: proxy
          orgId: 1
          url: <lokiのurl>
          basicAuth: false
          isDefault: true
          version: 1
          editable: false
        EOF
        /run.sh
    image: grafana/grafana:latest
    ports:
      - "3000:3000"

起動したら、http://<grafanaが動いているマシンのIP>:3000 にアクセスしてGrafanaにログインする。

試しに、Lokiのデータソースを追加して、ログを確認して動作を確認する。試しに、dev-serverのログを確認してみた。

これでログの保存環境が構築された。

ハマったところや気になったところ

最初、filelogのattributeが上手く設定されなかった。試行錯誤してみたがどうもresource.attributesに設定した値をLoki側がIndex Labelに設定できるときとできないときがあるようで上手く設定できなかった。結局filelogで設定するよりもprocessor側で設定する方が都合が良かったので、そちらで設定するようにしたら全て問題なく動作するようになった。

またLokiの問題というか仕様であるが、Index Labelに出来るものが設定ファイルに記述しなければならないのはそのうち困るだろう。新しくラベルが増えてそれをIndex Labelにしたいという場合はLokiの再起動なりが必要となってしまう。あらかじめIndex Labelにしそうなものについては登録しておくしかなさそうだ。

まとめ

若干設定でハマったりしたが、ログが標準化されたもので記録されていくというのはとても気楽で良い。otel-collectorの設定とバイナリを置いておくだけでログの転送が実現される。Lokiに不満が出てきたら違うものへの置き換えも簡単にできる。

また、今回色々調査していて気が付いたが設定自体も他の基盤と共通化できる部分が多いので、参考にできる情報が多い。例えば、IBMのinstanaの設定などを見ても、今回の設定にそのまま流用できている部分がある。同様に他の基盤が公開している設定も参考にできるだろう。

今回は設定していないが、CPU使用率といったメトリクスについても設定を行なっていけば、手軽にサーバなどの監視を行なうことが実現できる。

面倒臭がってサボっていた監視設定などについてもやっていこうというモチベーションが高まる結果となり大変よかった。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です