現実の問題:ELK Stack がチームの予算を食い尽くしている
昨年、チームの監視スタックで厄介な問題にぶつかりました。Elasticsearch + Logstash + Kibana からなる ELK Stack を使って約20台のサーバーからログを収集していたのですが、毎月のストレージコストが止まることなく上昇し続けていました。Elasticsearch は Lucene インデックス形式でデータを保存するため、ディスク使用量が実際のログデータの3〜5倍になることがよくあります。
RAM については言うまでもありません。本番環境で Elasticsearch を安定稼働させるには、最低でも4GB のヒープが必要です。3ノードクラスターでは、Elasticsearch だけで12GB の RAM を消費します。Logstash と Kibana を加えれば、さらに増えます。VPS のコストは毎月膨らむ一方なのに、チームの予算はびくともしません。
なぜ ELK は Observability に高コストなのか?
Elasticsearch はもともと検索エンジンとして設計されました。Observability の用途に転用すると、不要なオーバーヘッドが大量に発生します:
- Lucene の転置インデックスは全文検索に最適化されていますが、時系列ログに対しては余計な重荷でしかありません
- Schema-on-write のため事前にマッピングを定義する必要があり、ログフォーマットが一貫していないと dynamic mapping でフィールド爆発が起きやすくなります
- デフォルトのレプリケーションファクターでは HA を実現するために最低2ノードが必要で、ディスク上のデータが二重になります
- JVM オーバーヘッド——ヒープのウォームアップが必要で、重いクエリのたびに GC ポーズが悪夢を呼びます
ELK Stack は強力ですが、検索エンジンとして生まれたものです。Observability に使うのは、段ボール箱一つを運ぶために10トントラックを借りるようなもの——できないことはないですが、運用コストがまったく見合いません。
検討した代替ソリューション
最終的な判断を下す前に、いくつかのソリューションをベンチマークしました:
- Grafana Loki:フルコンテンツではなくメタデータのみをインデックスするため、はるかに軽量です。ただし LogQL の習得に時間がかかり、インデックスが不完全なままデータが数十GB を超えると検索パフォーマンスが落ちてきます。
- Graylog:バックエンドに Elasticsearch を使い続けているため、UI が改善されるだけでストレージ問題は解決されません。
- VictoriaMetrics:メトリクスには非常に優れていますが、当時はネイティブのログ機能が本番利用に十分成熟していませんでした。
- OpenObserve:ログ・メトリクス・トレースをすべて単一のバイナリで処理でき、公式ベンチマークでは ELK より140分の1のストレージコストを実現しています。
OpenObserve——実際に使っているソリューション
OpenObserve(旧称 ZincObserve)は Rust で書かれており、高圧縮の Parquet 形式でデータを保存します。ELK との主な違いは以下のとおりです:
- 10MB 未満の単一バイナリで動作し、JVM も Elasticsearch も不要
- S3 互換ストレージをサポート——Cloudflare R2 や MinIO にログを保存すれば、従来のブロックストレージよりはるかに安価
- SQL でのログクエリ、PromQL でのメトリクスクエリ、OpenTelemetry 準拠のトレース表示に対応した統合 UI を内蔵
- アイドル時の実際の RAM 消費はおよそ 100〜150MB——本番サーバーで直接計測した値です
Docker で OpenObserve をインストールする
新しいサーバーで最短でテストするなら:
# データ保存用ディレクトリを作成
mkdir -p /opt/openobserve/data
# OpenObserve を起動
docker run -d \
--name openobserve \
--restart unless-stopped \
-p 5080:5080 \
-e [email protected] \
-e ZO_ROOT_USER_PASSWORD=StrongPass123! \
-e ZO_DATA_DIR=/data \
-v /opt/openobserve/data:/data \
public.ecr.aws/zinclabs/openobserve:latest
完了したらブラウザで http://your-server-ip:5080 にアクセスし、設定した認証情報でログインすればすぐに使えます。
本番環境向け Docker Compose
version: '3.8'
services:
openobserve:
image: public.ecr.aws/zinclabs/openobserve:latest
container_name: openobserve
restart: unless-stopped
ports:
- "5080:5080"
environment:
ZO_ROOT_USER_EMAIL: "[email protected]"
ZO_ROOT_USER_PASSWORD: "ChangeThisToSomethingStrong!"
ZO_DATA_DIR: /data
ZO_TELEMETRY: "false"
volumes:
- openobserve_data:/data
volumes:
openobserve_data:
以下のコマンドで起動します:
docker compose up -d
# 起動ログを確認
docker compose logs -f openobserve
Fluent Bit でサーバーのログを OpenObserve に送信する
Fluent Bit は私が使ってきた中で最も軽量なログ収集エージェントです。RAM フットプリントはわずか約5MB で、Logstash の500MB 以上とは比較になりません。Ubuntu/Debian へのインストール:
curl https://raw.githubusercontent.com/fluent/fluent-bit/master/install.sh | sh
systemctl enable fluent-bit --now
OpenObserve にログを送信するための設定ファイルを作成します:
# /etc/fluent-bit/fluent-bit.conf
[SERVICE]
Flush 5
Log_Level info
[INPUT]
Name tail
Path /var/log/syslog
Tag server.syslog
Read_from_Head False
[INPUT]
Name tail
Path /var/log/nginx/access.log
Tag nginx.access
Read_from_Head False
[OUTPUT]
Name http
Match *
Host your-openobserve-server
Port 5080
URI /api/default/server_logs/_json
Format json
Http_User [email protected]
Http_Passwd ChangeThisToSomethingStrong!
compress gzip
tls Off
systemctl restart fluent-bit
# ログが送信されているか確認
journalctl -u fluent-bit -f
UI で SQL を使ってログをクエリする
LogQL や KQL を覚える必要はありません。OpenObserve は SQL 構文を採用しており、リレーショナルデータベースに慣れていれば即座に使いこなせます。メニューの Logs を開き、ストリームを選択して実行します:
-- 過去1時間のすべての ERROR を検索
SELECT * FROM "server_logs"
WHERE log LIKE '%ERROR%'
ORDER BY _timestamp DESC
LIMIT 100
-- 時間帯ごとのエラー数を集計してトレンドを把握
SELECT
date_trunc('hour', _timestamp) AS hour,
count(*) AS error_count
FROM "server_logs"
WHERE log LIKE '%ERROR%'
GROUP BY 1
ORDER BY 1 DESC
アラート設定——アラート疲れを防ぐために
ここが初期セットアップで最も時間を費やした部分です。最初にしきい値を低く設定しすぎてしまい、2〜3分ごとに通知が届くようになり、チーム全員が DevOps チャットグループをミュートする事態に陥りました。痛い教訓:しきい値は感覚的な数値ではなく、最低1〜2週間分のログ履歴から得た実際のベースラインに基づいて設定しなければなりません。
OpenObserve で Alerts → Create Alert を開きます。設定サンプル:
{
"name": "High Error Rate",
"stream_name": "server_logs",
"query": "SELECT count(*) as error_count FROM server_logs WHERE log LIKE '%ERROR%'",
"condition": {
"column": "error_count",
"operator": ">",
"value": 50
},
"duration": 5,
"frequency": 1,
"time_between_alerts": 30
}
最も重要なパラメーターは2つです。time_between_alerts はスパムを防ぐために最低30分に設定してください。duration: 5 は条件が5分間継続して満たされた場合にのみトリガーされることを意味し、一時的なスパイクによる誤検知を効果的に排除できます。
アラートは Slack、Webhook、またはメールで送信できます。送信先の設定は Alerts → Destinations で行います。
OpenObserve に移行して3ヶ月後の実際の結果
20台のサーバーから同量のログを扱った場合の計測値です:
- ディスク使用量:月180GB(ELK)から月13GB(OpenObserve)へ——約14分の1に削減
- RAM 消費:12GB(ELK 3ノードクラスター)から256MB(OpenObserve シングルノード)へ
- VPS コスト:同じワークロードで月$80から月$12へ
- セットアップ時間:Docker なら10分——ELK を正しくセットアップする半日と比べると歴然です
OpenObserve があらゆる用途で Elasticsearch の代わりになるわけではありません。アプリケーションデータに対して複雑な全文検索が必要な場合は、Elasticsearch が依然として正しい選択です。しかし、インフラのログ・メトリクス・トレースの収集・保存・分析という Observability の本来の用途においては、2年早く知っておけばよかったと心から思っています。
