ساعت ۱۰ شب، یک تراکنش پرداخت شکست خورده و مشتری شکایت کرده است. درخواست از API Gateway گذشته، به سرویس سفارش رفته، سرویس پرداخت را صدا زده و جایی خطا داده. این سرویسها روی ۴۰ Pod پخش شدهاند و بعضی از آن Podها هم از آن موقع Restart شدهاند و لاگشان دیگر وجود ندارد.
بدون لاگ مرکزی، پیدا کردن این خطا ساعتها طول میکشد. با لاگ مرکزی، یک جستوجو در Kibana.
معماری
Node 1 Node 2 Node N
┌────────┐ ┌────────┐ ┌────────┐
│ Pods │ │ Pods │ │ Pods │
│ ↓ │ │ ↓ │ │ ↓ │
│FluentBit│ │FluentBit│ │FluentBit│ (DaemonSet)
└───┬────┘ └───┬────┘ └───┬────┘
└────────────────┼────────────────┘
▼
Elasticsearch ──▶ Kibana
| جزء | نقش | جایگزین |
|---|---|---|
| Fluent Bit | خواندن لاگ کانتینرها روی هر Node، افزودن متادیتای Kubernetes | Filebeat، Fluentd |
| Elasticsearch | ذخیره و Index کردن برای جستوجوی سریع | OpenSearch، Loki |
| Kibana | جستوجو، فیلتر و داشبورد | OpenSearch Dashboards، Grafana |
| Logstash (اختیاری) | پردازش و تبدیل سنگین قبل از ذخیره | Fluent Bit Filters |
نصب Elasticsearch و Kibana
برای Production، ECK (Elastic Cloud on Kubernetes) سادهترین روش است: یک Operator که Elasticsearch را با گواهی TLS، Upgrade و Scale مدیریت میکند.
apiVersion: elasticsearch.k8s.elastic.co/v1
kind: Elasticsearch
metadata: { name: logs, namespace: logging }
spec:
version: 8.15.3
nodeSets:
- name: default
count: 3
config:
node.store.allow_mmap: false
volumeClaimTemplates:
- metadata: { name: elasticsearch-data }
spec:
accessModes: [ReadWriteOnce]
resources: { requests: { storage: 200Gi } }
---
apiVersion: kibana.k8s.elastic.co/v1
kind: Kibana
metadata: { name: logs, namespace: logging }
spec:
version: 8.15.3
count: 1
elasticsearchRef: { name: logs }
پیکربندی Fluent Bit
Fluent Bit را با Helm Chart رسمی (fluent/fluent-bit) بهصورت DaemonSet نصب کنید و بخش پیکربندی را اینطور تنظیم کنید:
[INPUT]
Name tail
Path /var/log/containers/*.log
Exclude_Path /var/log/containers/*fluent-bit*.log
multiline.parser docker, cri
Tag kube.*
Mem_Buf_Limit 50MB
Skip_Long_Lines On
[FILTER]
Name kubernetes
Match kube.*
Merge_Log On
Keep_Log Off
K8S-Logging.Parser On
K8S-Logging.Exclude On
[OUTPUT]
Name es
Match kube.*
Host logs-es-http.logging.svc
Port 9200
HTTP_User ${ES_USER}
HTTP_Passwd ${ES_PASSWORD}
tls On
tls.verify Off
Logstash_Format On
Logstash_Prefix k8s
Suppress_Type_Name On
Replace_Dots On
Retry_Limit 5
Merge_Log On: اگر برنامه لاگ JSON بنویسد، فیلدهایش جداگانه Index میشوند؛ مثلاًlevel،user_idیاorder_id.- فیلتر
kubernetesبه هر خط لاگ نام Namespace، Pod، Container و Labelها را اضافه میکند. K8S-Logging.Excludeاجازه میدهد یک Pod با Annotationfluentbit.io/exclude: "true"از جمعآوری خارج شود؛ مثلاً سرویسهای بسیار پرحجم و کماهمیت.
لاگ خوب از خود برنامه شروع میشود
بهترین Pipeline لاگ هم از یک خط متنی بیساختار چیز زیادی استخراج نمیکند. دو قانون ساده:
۱. لاگ را JSON و روی stdout بنویسید:
{"time":"2026-08-26T21:14:03Z","level":"Error","service":"payment","trace_id":"9f1c2e7a","order_id":"A-55120","msg":"gateway timeout","elapsed_ms":30012}
برای .NET از Serilog با CompactJsonFormatter، برای Node.js از pino و برای Java از Logback با encoder مخصوص JSON استفاده کنید.
۲. Correlation ID در همهی سرویسها: API Gateway برای هر درخواست یک trace_id بسازد و هر سرویس آن را در Header به سرویس بعدی بدهد و در همهی لاگهایش بنویسد. حالا کل مسیر یک تراکنش با یک جستوجو پیدا میشود.
سیاست نگهداری (ILM)
بدون سیاست نگهداری، دیسک Elasticsearch در چند هفته پر میشود و کلاستر به حالت Read-Only میرود:
PUT _ilm/policy/k8s-logs
{
"policy": {
"phases": {
"hot": { "actions": { "set_priority": { "priority": 100 } } },
"warm": { "min_age": "3d", "actions": { "forcemerge": { "max_num_segments": 1 } } },
"delete": { "min_age": "14d", "actions": { "delete": {} } }
}
}
}
PUT _index_template/k8s-logs
{
"index_patterns": ["k8s-*"],
"template": {
"settings": {
"index.lifecycle.name": "k8s-logs",
"number_of_shards": 1,
"number_of_replicas": 1
}
}
}
کوئریهای KQL که هر روز استفاده میکنم
# all errors of one service in production
kubernetes.namespace_name : "production" and kubernetes.labels.app : "payment" and level : "Error"
# the full journey of a single request across services
trace_id : "9f1c2e7a"
# slow requests
service : "api-gateway" and elapsed_ms > 2000
# pods that logged OOM or panic
log : *OutOfMemory* or log : *panic*
یک Saved Search برای هر سرویس و یک داشبورد با «تعداد خطا به تفکیک سرویس در ۲۴ ساعت گذشته»، معمولاً اولین جایی است که تیم در هر Incident نگاه میکند.
ELK یا Loki؟
| ELK | Grafana Loki | |
|---|---|---|
| جستوجوی متن کامل | بسیار قوی | محدودتر، بر اساس Label |
| مصرف منابع و دیسک | زیاد | کم |
| یکپارچگی با Prometheus و Grafana | جدا | کامل |
| مناسب برای | جستوجوی پیچیده، Audit و تحلیل | تیمهایی که Grafana محور کار میکنند |
اگر منابع محدود دارید و لاگ را بیشتر کنار متریکها میبینید، Loki انتخاب سبکتری است. اگر جستوجوی دقیق و Audit (مثل الزامات بانکی) مهم است، ELK همچنان قویتر است.
جمعبندی
لاگ مرکزی زمانی ارزش واقعی پیدا میکند که سه چیز کنار هم باشند: جمعآوری مطمئن با Fluent Bit، لاگ ساختیافته با Correlation ID در خود برنامه و سیاست نگهداری که کلاستر را سالم نگه دارد. با این سه، جواب «چه اتفاقی برای این تراکنش افتاد؟» چند ثانیه طول میکشد.