- Published on
ELKスタック基盤ログ収集·分析パイプライン: Elasticsearch·Fluentd·Kibanaプロダクション構築と最適化
- Authors
- Name
- はじめに
- ELK vs EFKスタック比較
- Elasticsearchクラスタアーキテクチャ
- Index Lifecycle Management (ILM) 設定
- Fluentd設定とパイプライン構成
- Kibanaダッシュボード構成
- パフォーマンスチューニングと最適化
- 運用時の注意事項
- 障害事例と復旧手順
- モニタリング構成
- まとめ
- 参考資料

はじめに
本番環境においてログは、障害検知、デバッグ、セキュリティ監査、パフォーマンス分析の核心データです。システム規模が大きくなるほど、分散した数十〜数百台のサーバーで発生するログを中央集中的に収集・分析する体制が不可欠となります。ELKスタック(Elasticsearch + Logstash + Kibana)はこうしたログパイプラインの事実上の標準として定着しており、LogstashをFluentdに置き換えたEFKスタックもKubernetes環境で広く採用されています。
この記事では、ELK/EFKスタックの各コンポーネントアーキテクチャ、Elasticsearchクラスタ設計とシャード戦略、ILM(Index Lifecycle Management)設定、FluentdとFluent Bitの比較と設定、Kibanaダッシュボード構成、パフォーマンスチューニング、そしてプロダクション環境で頻繁に発生する障害事例と復旧手順まで全過程を解説します。
ELK vs EFKスタック比較
ELKスタックとEFKスタックの核心的な違いはログコレクタにあります。
| 項目 | ELK (Logstash) | EFK (Fluentd) |
|---|---|---|
| 開発言語 | Java (JRuby) | Ruby + C |
| メモリ使用量 | 約500MB〜1GB | 約40〜100MB |
| プラグイン数 | 200+ | 1,000+ |
| 設定形式 | 独自DSL | タグベースルーティング |
| Kubernetes親和性 | 普通 | 非常に高い(CNCF卒業) |
| バッファリング | メモリ/ディスク | メモリ/ファイル |
| データパーシング | Grokパターン | 正規表現 + パーサープラグイン |
| 適した環境 | 複雑な変換ロジック | クラウドネイティブ、K8s |
Fluentd vs Fluent Bit比較
FluentdとFluent Bitは同じエコシステムに属していますが、用途が異なります。
| 項目 | Fluentd | Fluent Bit |
|---|---|---|
| 開発言語 | Ruby + C | C |
| メモリ使用量 | 約40MB+ | 約450KB |
| プラグインエコシステム | 1,000+ | コアプラグインのみ |
| 役割 | 中央集約/変換 | エッジ収集/転送 |
| 適した環境 | サーバー、アグリゲーター | IoT、サイドカー、DaemonSet |
| 処理性能 | 普通 | 非常に高い(10〜40倍) |
プロダクション推奨構成は Fluent Bit(DaemonSet)+ Fluentd(Aggregator)+ Elasticsearch の組み合わせです。Fluent Bitが各ノードでログを軽量に収集し、Fluentdが中央で変換とルーティングを担当します。
Elasticsearchクラスタアーキテクチャ
ノード役割の分離
プロダクションElasticsearchクラスタではノード役割の分離が核心です。
# elasticsearch-master.yml
cluster.name: prod-logs
node.name: master-01
node.roles: [ master ]
network.host: 0.0.0.0
discovery.seed_hosts:
- master-01
- master-02
- master-03
cluster.initial_master_nodes:
- master-01
- master-02
- master-03
# JVM heap settings (jvm.options)
-Xms4g
-Xmx4g
# elasticsearch-data-hot.yml
cluster.name: prod-logs
node.name: data-hot-01
node.roles: [ data_hot, data_content ]
node.attr.data: hot
network.host: 0.0.0.0
discovery.seed_hosts:
- master-01
- master-02
- master-03
# JVM heap settings
-Xms16g
-Xmx16g
# elasticsearch-data-warm.yml
cluster.name: prod-logs
node.name: data-warm-01
node.roles: [ data_warm ]
node.attr.data: warm
network.host: 0.0.0.0
discovery.seed_hosts:
- master-01
- master-02
- master-03
# JVM heap settings
-Xms8g
-Xmx8g
各ノード役割の推奨スペックは以下の通りです。
| ノード役割 | CPU | メモリ | ストレージ | 台数 |
|---|---|---|---|---|
| Master | 4 vCPU | 8GB | 50GB SSD | 3台(奇数) |
| Data Hot | 8〜16 vCPU | 32〜64GB | NVMe SSD | 3台以上 |
| Data Warm | 4〜8 vCPU | 16〜32GB | HDD/SSD | 2台以上 |
| Data Cold | 2〜4 vCPU | 8〜16GB | HDD | 1台以上 |
| Coordinating | 4〜8 vCPU | 16GB | 50GB SSD | 2台 |
| Ingest | 4〜8 vCPU | 16GB | 50GB SSD | 2台 |
シャードとレプリカ戦略
# Set shard configuration via index template
curl -X PUT "localhost:9200/_index_template/logs-template" \
-H 'Content-Type: application/json' \
-d '{
"index_patterns": ["logs-*"],
"template": {
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1,
"refresh_interval": "30s",
"codec": "best_compression",
"routing.allocation.require.data": "hot"
},
"mappings": {
"dynamic": "strict",
"properties": {
"@timestamp": { "type": "date" },
"level": { "type": "keyword" },
"service": { "type": "keyword" },
"message": { "type": "text" },
"trace_id": { "type": "keyword" },
"host": { "type": "keyword" }
}
}
}
}'
シャード設計の核心原則は以下の通りです。
- シャードサイズ: 30〜50GBを目標に設定します。10GB未満の小さなシャードはオーバーヘッドが大きく、50GBを超えるとリカバリ時間が長くなります
- シャード数: ノードあたりのシャード数をヒープ1GBあたり20個以下に維持します
- レプリカ: プロダクションでは最低1つのレプリカを設定して可用性を保証します
- refresh_interval: ログデータはリアルタイム性が低いため30秒〜60秒に増やします
Index Lifecycle Management (ILM) 設定
ILMはインデックスの作成から削除まで自動化する核心機能です。
{
"policy": {
"phases": {
"hot": {
"min_age": "0ms",
"actions": {
"rollover": {
"max_primary_shard_size": "50gb",
"max_age": "1d"
},
"set_priority": {
"priority": 100
}
}
},
"warm": {
"min_age": "3d",
"actions": {
"shrink": {
"number_of_shards": 1
},
"forcemerge": {
"max_num_segments": 1
},
"allocate": {
"require": {
"data": "warm"
}
},
"set_priority": {
"priority": 50
}
}
},
"cold": {
"min_age": "30d",
"actions": {
"allocate": {
"require": {
"data": "cold"
}
},
"set_priority": {
"priority": 0
}
}
},
"delete": {
"min_age": "90d",
"actions": {
"delete": {}
}
}
}
}
}
# Create ILM policy
curl -X PUT "localhost:9200/_ilm/policy/logs-lifecycle" \
-H 'Content-Type: application/json' \
-d @ilm-policy.json
# Attach ILM policy to index template
curl -X PUT "localhost:9200/_index_template/logs-template" \
-H 'Content-Type: application/json' \
-d '{
"index_patterns": ["logs-*"],
"template": {
"settings": {
"index.lifecycle.name": "logs-lifecycle",
"index.lifecycle.rollover_alias": "logs"
}
}
}'
# Create bootstrap index
curl -X PUT "localhost:9200/logs-000001" \
-H 'Content-Type: application/json' \
-d '{
"aliases": {
"logs": {
"is_write_index": true
}
}
}'
ILM各Phaseの主要アクション
| Phase | トリガー条件 | 主要アクション | 目的 |
|---|---|---|---|
| Hot | インデックス作成時 | rollover, set_priority | アクティブな書き込み/読み取り |
| Warm | 3日経過 | shrink, forcemerge, allocate | 読み取り中心、ストレージ節約 |
| Cold | 30日経過 | allocate, freeze | まれな読み取り、コスト最小化 |
| Delete | 90日経過 | delete | ストレージ確保 |
Fluentd設定とパイプライン構成
Fluentd基本設定
<!-- /etc/fluentd/fluent.conf -->
<system>
log_level info
workers 4
</system>
<!-- Input: Application log collection -->
<source>
@type tail
path /var/log/app/*.log
pos_file /var/log/fluentd/app.log.pos
tag app.logs
read_from_head true
<parse>
@type json
time_key timestamp
time_format %Y-%m-%dT%H:%M:%S.%NZ
</parse>
</source>
<!-- Input: Kubernetes log collection -->
<source>
@type tail
path /var/log/containers/*.log
pos_file /var/log/fluentd/containers.log.pos
tag kubernetes.*
<parse>
@type cri
</parse>
</source>
<!-- Filter: Add Kubernetes metadata -->
<filter kubernetes.**>
@type kubernetes_metadata
@id filter_kube_metadata
skip_labels false
skip_container_metadata false
</filter>
<!-- Filter: Remove unnecessary logs -->
<filter **>
@type grep
<exclude>
key log
pattern /healthcheck|readiness|liveness/
</exclude>
</filter>
<!-- Filter: Tag based on log level -->
<filter app.logs>
@type record_transformer
enable_ruby true
<record>
hostname "#{Socket.gethostname}"
environment "production"
</record>
</filter>
<!-- Output: Send to Elasticsearch -->
<match **>
@type elasticsearch
host elasticsearch-coordinating
port 9200
logstash_format true
logstash_prefix fluentd-logs
logstash_dateformat %Y.%m.%d
include_tag_key true
tag_key @fluentd_tag
<buffer tag, time>
@type file
path /var/log/fluentd/buffer
timekey 1h
timekey_wait 10m
chunk_limit_size 64MB
total_limit_size 8GB
flush_mode interval
flush_interval 30s
flush_thread_count 4
retry_max_interval 30
retry_forever true
overflow_action block
</buffer>
</match>
Fluent Bit DaemonSet設定(Kubernetes)
# fluent-bit-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: fluent-bit-config
namespace: logging
data:
fluent-bit.conf: |
[SERVICE]
Flush 5
Log_Level info
Daemon off
Parsers_File parsers.conf
HTTP_Server On
HTTP_Listen 0.0.0.0
HTTP_Port 2020
storage.path /var/log/flb-storage/
storage.sync normal
storage.checksum off
storage.backlog.mem_limit 5M
[INPUT]
Name tail
Tag kube.*
Path /var/log/containers/*.log
Parser cri
DB /var/log/flb_kube.db
Mem_Buf_Limit 5MB
Skip_Long_Lines On
Refresh_Interval 10
storage.type filesystem
[FILTER]
Name kubernetes
Match kube.*
Kube_URL https://kubernetes.default.svc:443
Kube_CA_File /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
Kube_Token_File /var/run/secrets/kubernetes.io/serviceaccount/token
Kube_Tag_Prefix kube.var.log.containers.
Merge_Log On
Keep_Log Off
K8S-Logging.Parser On
K8S-Logging.Exclude On
[FILTER]
Name grep
Match *
Exclude log healthcheck
[OUTPUT]
Name forward
Match *
Host fluentd-aggregator.logging.svc.cluster.local
Port 24224
Retry_Limit False
parsers.conf: |
[PARSER]
Name cri
Format regex
Regex ^(?<time>[^ ]+) (?<stream>stdout|stderr) (?<logtag>[^ ]*) (?<log>.*)$
Time_Key time
Time_Format %Y-%m-%dT%H:%M:%S.%L%z
# fluent-bit-daemonset.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluent-bit
namespace: logging
spec:
selector:
matchLabels:
app: fluent-bit
template:
metadata:
labels:
app: fluent-bit
spec:
serviceAccountName: fluent-bit
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
containers:
- name: fluent-bit
image: fluent/fluent-bit:3.2
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
volumeMounts:
- name: varlog
mountPath: /var/log
- name: config
mountPath: /fluent-bit/etc/
- name: storage
mountPath: /var/log/flb-storage/
volumes:
- name: varlog
hostPath:
path: /var/log
- name: config
configMap:
name: fluent-bit-config
- name: storage
emptyDir: {}
Kibanaダッシュボード構成
インデックスパターン設定
Kibanaでまずインデックスパターンを作成する必要があります。
# Create index pattern via Kibana API
curl -X POST "localhost:5601/api/saved_objects/index-pattern" \
-H 'kbn-xsrf: true' \
-H 'Content-Type: application/json' \
-d '{
"attributes": {
"title": "fluentd-logs-*",
"timeFieldName": "@timestamp"
}
}'
効果的なダッシュボード設計原則
Kibanaダッシュボードは目的に応じて設計する必要があります。
| ダッシュボードタイプ | 含まれる可視化 | 対象ユーザー |
|---|---|---|
| 運用概要 | ログ量推移、エラー率グラフ、サービス別分布 | SRE/DevOps |
| エラー分析 | エラータイプ別分類、Topエラーメッセージ、スタックトレース | 開発者 |
| セキュリティ監査 | 認証失敗イベント、異常アクセスパターン | セキュリティチーム |
| インフラモニタリング | ノード別ログ量、インデキシング速度、レイテンシ | プラットフォームチーム |
主要な可視化コンポーネントは以下の通りです。
- Lensチャート: 時系列ログ量、サービス別エラー率
- TSVB (Time Series Visual Builder): 詳細な時系列分析
- Data Table: Top Nエラーメッセージ、サービス別統計
- Markdownウィジェット: ダッシュボード説明、ランブックリンク
パフォーマンスチューニングと最適化
Elasticsearchインデキシングパフォーマンス最適化
# Bulk indexing optimization settings
curl -X PUT "localhost:9200/logs-000001/_settings" \
-H 'Content-Type: application/json' \
-d '{
"index": {
"refresh_interval": "30s",
"translog.durability": "async",
"translog.sync_interval": "30s",
"translog.flush_threshold_size": "1gb"
}
}'
主要パフォーマンスチューニングパラメータ
| パラメータ | デフォルト値 | 推奨値 | 説明 |
|---|---|---|---|
| refresh_interval | 1s | 30s〜60s | ログデータはリアルタイム性が低いため増やす |
| translog.durability | request | async | 非同期translogで書き込み性能向上 |
| number_of_replicas | 1 | 0(初期ロード時) | 大量ロード時はレプリカを無効化 |
| bulkサイズ | - | 5〜15MB | 大きすぎるとメモリ圧迫、小さすぎるとオーバーヘッド |
| flush_thread_count | 1 | 4〜8 | Fluentd出力スレッド数 |
JVMヒープメモリ設定
# jvm.options settings
# Max 50% of total RAM, never more than 31GB (Compressed OOPs limit)
-Xms16g
-Xmx16g
# G1GC settings (default in Elasticsearch 8.x)
-XX:+UseG1GC
-XX:G1HeapRegionSize=16m
-XX:InitiatingHeapOccupancyPercent=30
-XX:+ParallelRefProcEnabled
# Enable GC logging
-Xlog:gc*,gc+age=trace,safepoint:file=logs/gc.log:utctime,pid,tags:filecount=32,filesize=64m
ストレージ最適化
# Force merge segments (on read-only indices)
curl -X POST "localhost:9200/logs-2026.03.01/_forcemerge?max_num_segments=1"
# Check index compression
curl -X GET "localhost:9200/_cat/indices/logs-*?v&h=index,store.size,pri.store.size,docs.count&s=index"
# Disk watermark settings
curl -X PUT "localhost:9200/_cluster/settings" \
-H 'Content-Type: application/json' \
-d '{
"persistent": {
"cluster.routing.allocation.disk.watermark.low": "85%",
"cluster.routing.allocation.disk.watermark.high": "90%",
"cluster.routing.allocation.disk.watermark.flood_stage": "95%"
}
}'
運用時の注意事項
1. マッピング爆発の防止
ダイナミックマッピングが有効な状態でキー名が自由なJSONログをインデキシングすると、フィールド数が急増しクラスタが不安定になります。
# Set mapping field count limit
curl -X PUT "localhost:9200/_index_template/logs-template" \
-H 'Content-Type: application/json' \
-d '{
"index_patterns": ["logs-*"],
"template": {
"settings": {
"index.mapping.total_fields.limit": 1000
},
"mappings": {
"dynamic": "strict"
}
}
}'
2. シャード過剰割り当ての注意
- 1,000個以上の小規模シャードはマスターノードに深刻な負荷を与えます
- インデックスあたりのシャード数を最小化し、ILMロールオーバーでサイズベースの分割を使用します
_cat/shardsAPIで定期的にモニタリングします
3. GCプレッシャーの管理
- ヒープを31GB以下に維持してCompressed OOPsを活用します
- Old GCが頻繁な場合、fielddataキャッシュサイズを制限します
- circuit breaker設定でOOMを防止します
# Circuit breaker settings
curl -X PUT "localhost:9200/_cluster/settings" \
-H 'Content-Type: application/json' \
-d '{
"persistent": {
"indices.breaker.total.limit": "70%",
"indices.breaker.fielddata.limit": "40%",
"indices.breaker.request.limit": "40%"
}
}'
4. セキュリティ設定
# elasticsearch.yml - Security settings
xpack.security.enabled: true
xpack.security.transport.ssl.enabled: true
xpack.security.transport.ssl.verification_mode: certificate
xpack.security.transport.ssl.keystore.path: /etc/elasticsearch/certs/elastic-certificates.p12
xpack.security.transport.ssl.truststore.path: /etc/elasticsearch/certs/elastic-certificates.p12
xpack.security.http.ssl.enabled: true
xpack.security.http.ssl.keystore.path: /etc/elasticsearch/certs/http.p12
障害事例と復旧手順
障害事例1: クラスタステータスRED
症状: プライマリシャードが割り当てられず、データ損失の可能性
# Check unassigned shards
curl -X GET "localhost:9200/_cat/shards?v&h=index,shard,prirep,state,unassigned.reason&s=state"
# Diagnose unassignment cause
curl -X GET "localhost:9200/_cluster/allocation/explain?pretty"
# Manual shard allocation (when disk space is insufficient)
curl -X POST "localhost:9200/_cluster/reroute" \
-H 'Content-Type: application/json' \
-d '{
"commands": [
{
"allocate_stale_primary": {
"index": "logs-2026.03.10",
"shard": 0,
"node": "data-hot-02",
"accept_data_loss": true
}
}
]
}'
障害事例2: インデキシング遅延(Bulk Rejection)
症状: Fluentdログに 429 Too Many Requests エラーが大量発生
# Check thread pool status
curl -X GET "localhost:9200/_cat/thread_pool/write?v&h=node_name,active,rejected,queue,completed"
# Adjust bulk queue size
curl -X PUT "localhost:9200/_cluster/settings" \
-H 'Content-Type: application/json' \
-d '{
"persistent": {
"thread_pool.write.queue_size": 1000
}
}'
復旧手順:
- Fluentdバッファ状態確認(ディスクバッファ残量)
- Elasticsearchノード追加またはバルクキューサイズ増加
- refresh_intervalを一時的に60秒に増加
- 必要に応じてレプリカを0に減らしインデキシング負荷を軽減
- 安定化後、レプリカを元の値に復元
障害事例3: ディスクウォーターマーク超過
症状: インデックスがread-onlyモードに切り替わる
# Release read-only (after securing disk space)
curl -X PUT "localhost:9200/_all/_settings" \
-H 'Content-Type: application/json' \
-d '{
"index.blocks.read_only_allow_delete": null
}'
# Manually delete old indices
curl -X DELETE "localhost:9200/logs-2026.01.*"
# Check disk usage
curl -X GET "localhost:9200/_cat/allocation?v"
モニタリング構成
ELKスタック自体をモニタリングするための別途モニタリングクラスタの構成が推奨されます。
# metricbeat.yml - Elasticsearch monitoring
metricbeat.modules:
- module: elasticsearch
xpack.enabled: true
period: 10s
hosts:
- 'https://es-node-01:9200'
- 'https://es-node-02:9200'
username: 'monitoring_user'
password: 'secure_password'
ssl.certificate_authorities:
- /etc/metricbeat/certs/ca.crt
- module: kibana
xpack.enabled: true
period: 10s
hosts:
- 'https://kibana:5601'
output.elasticsearch:
hosts:
- 'https://monitoring-es:9200'
username: 'metricbeat_writer'
password: 'secure_password'
核心モニタリング指標は以下の通りです。
| 指標 | しきい値 | 対処 |
|---|---|---|
| クラスタステータス | RED | 即座の対応が必要 |
| JVMヒープ使用率 | 85%以上 | ノード追加またはヒープ調整 |
| インデキシング速度 | 急激な変動 | ソース確認 |
| 検索レイテンシ | 5秒以上 | シャード/クエリ最適化 |
| ディスク使用率 | 85%以上 | ILMポリシー点検 |
| 未割当シャード数 | 0超過 | 割り当て原因の診断 |
| Fluentdバッファサイズ | しきい値超過 | 出力ボトルネック確認 |
まとめ
ELK/EFKスタックは成熟したエコシステムと豊富な機能を備えたログパイプラインソリューションです。しかしプロダクションで安定的に運用するには、Elasticsearchクラスタアーキテクチャ設計、シャード戦略、ILMポリシー、Fluentdバッファリング戦略、そしてモニタリング体制まで総合的に考慮する必要があります。
核心ポイントは以下の通りです。
- ノード役割の分離: Master、Data Hot/Warm/Cold、Coordinatingノードを分離して障害分離とパフォーマンス最適化を実現する
- ILM活用: Hot-Warm-Cold-Delete段階の自動遷移でストレージコストを最適化する
- Fluent Bit + Fluentdの組み合わせ: エッジでの軽量収集と中央での変換・ルーティングを分離する
- マッピング管理: strictマッピングとフィールド数制限でマッピング爆発を防止する
- モニタリング: 別途モニタリングクラスタでELKスタック自体を監視する
規模が小さい環境では単一ノードや小規模クラスタから始め、データ量の増加に応じてHot-Warm-ColdアーキテクチャとILMを段階的に導入することを推奨します。
参考資料
- Elasticsearch Architecture Best Practices - Elastic
- Index Lifecycle Management (ILM) - Elastic Docs
- Fluentd vs Fluent Bit: How to Choose in 2026 - Better Stack
- Production Guidance - Elastic Docs
- 7 Ways to Optimize Your Elastic (ELK) Stack in Production - Better Stack
- The Complete Guide to the ELK Stack - Logz.io
- Best Practices for Building Kibana Dashboards - Elastic