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.yaml
と loki-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を設定する。ここでは、hostname
と service.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使用率といったメトリクスについても設定を行なっていけば、手軽にサーバなどの監視を行なうことが実現できる。
面倒臭がってサボっていた監視設定などについてもやっていこうというモチベーションが高まる結果となり大変よかった。