مانیتورینگ سطح: متوسط

لاگ مرکزی در Kubernetes با Fluent Bit، Elasticsearch و Kibana

جمع‌آوری لاگ همه‌ی Podها با Fluent Bit، ارسال به Elasticsearch، لاگ ساخت‌یافته‌ی JSON، Correlation ID، سیاست نگهداری با ILM و کوئری‌های کاربردی Kibana برای عیب‌یابی سریع.

۵ دقیقه مطالعه

ساعت ۱۰ شب، یک تراکنش پرداخت شکست خورده و مشتری شکایت کرده است. درخواست از 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 با Annotation fluentbit.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 در خود برنامه و سیاست نگهداری که کلاستر را سالم نگه دارد. با این سه، جواب «چه اتفاقی برای این تراکنش افتاد؟» چند ثانیه طول می‌کشد.