Elasticsearch ile Log Analizi: Nginx ve Apache Loglarını Yönetme

Production ortamında yüzlerce istek per saniye alan bir web sunucusunu yönetiyorsanız, log dosyalarını tail -f ile takip etmek bir noktadan sonra işe yaramaz hale gelir. Nginx ya da Apache’nin ürettiği gigabaytlarca log verisini anlamlı bir şekilde sorgulamak, görselleştirmek ve alarm üretmek için ciddi bir altyapıya ihtiyaç duyarsınız. Elasticsearch tam da bu noktada devreye girer. Bu yazıda gerçek bir production senaryosunu ele alarak Nginx ve Apache loglarını Elasticsearch’e nasıl aktaracağınızı, anlamlı sorgular yazacağınızı ve performans sorunlarını nasıl tespit edeceğinizi adım adım göstereceğim.

Genel Mimari

Önce büyük resme bakalım. Log analizi için kullanılan klasik ELK Stack şu bileşenlerden oluşur:

  • Elasticsearch: Log verilerini depolayan ve sorgulayan search engine
  • Logstash: Log verilerini parse eden ve dönüştüren pipeline aracı
  • Kibana: Görselleştirme ve dashboard arayüzü
  • Filebeat: Sunuculardaki log dosyalarını okuyan hafif agent

Bu yazıda odak noktamız Elasticsearch tarafı olacak. Logstash ve Filebeat konfigürasyonlarına da değineceğiz çünkü veriyi Elasticsearch’e doğru formatta göndermeden sorgu yazmak mümkün değil.

Elasticsearch Index Tasarımı

Log verileri için index tasarımı kritik önem taşır. Yanlış bir mapping ile başlarsanız, sonradan düzeltmek için reindex işlemi yapmanız gerekir ki bu production ortamında ciddi bir iş yükü demektir.

Nginx ve Apache logları için time-based index stratejisi kullanmak en doğru yaklaşımdır. Her gün yeni bir index açılır, eski indexler arşivlenir ya da silinir. Bu sayede disk yönetimi kolaylaşır ve sorgular yalnızca ilgili zaman dilimini kapsar.

# Index template oluşturma
curl -X PUT "localhost:9200/_index_template/nginx-logs" 
  -H 'Content-Type: application/json' 
  -d '{
    "index_patterns": ["nginx-logs-*"],
    "template": {
      "settings": {
        "number_of_shards": 1,
        "number_of_replicas": 1,
        "index.refresh_interval": "5s"
      },
      "mappings": {
        "properties": {
          "@timestamp": { "type": "date" },
          "remote_addr": { "type": "ip" },
          "request_method": { "type": "keyword" },
          "request_uri": { "type": "keyword" },
          "status": { "type": "integer" },
          "body_bytes_sent": { "type": "long" },
          "http_referer": { "type": "text" },
          "http_user_agent": { "type": "text" },
          "request_time": { "type": "float" },
          "upstream_response_time": { "type": "float" },
          "geoip": {
            "properties": {
              "country_code": { "type": "keyword" },
              "city_name": { "type": "keyword" },
              "location": { "type": "geo_point" }
            }
          }
        }
      }
    }
  }'

Burada dikkat etmeniz gereken birkaç nokta var. ip tipi, IP adresleri üzerinde range sorguları yapmanıza olanak tanır. keyword tipi tam eşleşme sorguları için idealdir, request_uri gibi alanları analiz etmek için kullanırsınız. float tipi ise request_time gibi response süreleri için kullanılır ve percentile hesaplamalarında işinize yarar.

Nginx Log Formatı Ayarları

Elasticsearch’e göndermeden önce Nginx’in JSON formatında log üretmesini sağlamak hayatınızı inanılmaz kolaylaştırır. Custom log format yerine doğrudan yapılandırılmış veri kullanırsınız.

# /etc/nginx/nginx.conf içindeki http bloğuna ekleyin
log_format json_combined escape=json
  '{'
    '"time_local":"$time_local",'
    '"remote_addr":"$remote_addr",'
    '"remote_user":"$remote_user",'
    '"request_method":"$request_method",'
    '"request_uri":"$request_uri",'
    '"server_protocol":"$server_protocol",'
    '"status": $status,'
    '"body_bytes_sent": $body_bytes_sent,'
    '"http_referer":"$http_referer",'
    '"http_user_agent":"$http_user_agent",'
    '"request_time": $request_time,'
    '"upstream_response_time": "$upstream_response_time",'
    '"upstream_addr":"$upstream_addr",'
    '"host":"$host"'
  '}';

access_log /var/log/nginx/access.log json_combined;

Nginx’i yeniden yükledikten sonra log dosyasına bakın:

tail -f /var/log/nginx/access.log | python3 -m json.tool

Her satırın düzgün JSON formatında olduğunu göreceksiniz. Bu format Filebeat ve Logstash tarafında parse etme zahmetini ortadan kaldırır.

Filebeat Konfigürasyonu

Filebeat, Elasticsearch’e hafif bir agent ile log göndermek istediğinizde tercih etmeniz gereken araçtır. Logstash’in tüm parse gücüne ihtiyaç duymadığınız durumlarda doğrudan Elasticsearch’e yazabilir.

# /etc/filebeat/filebeat.yml
filebeat.inputs:
  - type: log
    enabled: true
    paths:
      - /var/log/nginx/access.log
    json.keys_under_root: true
    json.add_error_key: true
    json.message_key: log
    tags: ["nginx", "access"]
    fields:
      server_name: "web01"
      environment: "production"
    fields_under_root: true

  - type: log
    enabled: true
    paths:
      - /var/log/apache2/access.log
    tags: ["apache", "access"]
    fields:
      server_name: "web01"
      environment: "production"
    fields_under_root: true

output.elasticsearch:
  hosts: ["elasticsearch-01:9200", "elasticsearch-02:9200"]
  index: "nginx-logs-%{+yyyy.MM.dd}"
  username: "filebeat_writer"
  password: "${FILEBEAT_ES_PASSWORD}"
  bulk_max_size: 2048

processors:
  - add_host_metadata: ~
  - add_docker_metadata: ~
  - geoip:
      field: remote_addr
      target_field: geoip
      ignore_missing: true

Apache için ise Logstash’in Grok filter’ını kullanmak daha pratik olabilir çünkü Apache’nin Combined Log Format’ını parse etmek gerekir.

Logstash Pipeline: Apache Logları

Apache loglarını JSON’a dönüştürmek için Logstash pipeline kullanabilirsiniz:

# /etc/logstash/conf.d/apache.conf
input {
  beats {
    port => 5044
    type => "apache"
  }
}

filter {
  if [type] == "apache" {
    grok {
      match => {
        "message" => '%{IPORHOST:remote_addr} - %{DATA:remote_user} [%{HTTPDATE:time_local}] "%{WORD:request_method} %{DATA:request_uri} HTTP/%{NUMBER:http_version}" %{NUMBER:status:int} (?:%{NUMBER:body_bytes_sent:int}|-) "%{DATA:http_referer}" "%{DATA:http_user_agent}"'
      }
    }

    date {
      match => ["time_local", "dd/MMM/yyyy:HH:mm:ss Z"]
      target => "@timestamp"
    }

    mutate {
      convert => {
        "status" => "integer"
        "body_bytes_sent" => "integer"
      }
      remove_field => ["message", "time_local"]
    }

    if [remote_addr] {
      geoip {
        source => "remote_addr"
        target => "geoip"
        fields => ["country_code2", "city_name", "location"]
      }
    }

    # Bot trafik tespiti
    if [http_user_agent] =~ /(?i)(bot|crawler|spider|slurp)/ {
      mutate {
        add_tag => ["bot_traffic"]
      }
    }
  }
}

output {
  if [type] == "apache" {
    elasticsearch {
      hosts => ["elasticsearch-01:9200"]
      index => "apache-logs-%{+YYYY.MM.dd}"
      document_type => "_doc"
    }
  }
}

Temel Elasticsearch Sorguları

Veriler Elasticsearch’e aktarıldıktan sonra anlamlı sorgular yazmaya başlayabiliriz. Aşağıdaki senaryolar gerçek production ortamlarında sıkça karşılaştığım durumları yansıtıyor.

5xx Hataları Tespit Etme

Bir sabah uyandınızda alarmlar çalıyor, uygulama hata veriyor. İlk bakmanız gereken yer server-side error’lar:

curl -X GET "localhost:9200/nginx-logs-*/_search" 
  -H 'Content-Type: application/json' 
  -d '{
    "query": {
      "bool": {
        "filter": [
          {
            "range": {
              "@timestamp": {
                "gte": "now-1h",
                "lte": "now"
              }
            }
          },
          {
            "range": {
              "status": {
                "gte": 500,
                "lte": 599
              }
            }
          }
        ]
      }
    },
    "aggs": {
      "errors_by_uri": {
        "terms": {
          "field": "request_uri",
          "size": 20
        }
      },
      "errors_over_time": {
        "date_histogram": {
          "field": "@timestamp",
          "fixed_interval": "5m"
        }
      }
    },
    "size": 0
  }'

Bu sorgu son 1 saatteki tüm 5xx hatalarını, hangi endpoint’ten kaynaklandığını ve zaman içindeki dağılımını verir. "size": 0 ayarı sadece aggregation sonuçlarını döndürür, ham dökümanları getirmez, bu sayede sorgu çok daha hızlı çalışır.

Yavaş İstekler Analizi (P95, P99)

Response time sorunlarını tespit etmek için percentile aggregation kullanın:

curl -X GET "localhost:9200/nginx-logs-*/_search" 
  -H 'Content-Type: application/json' 
  -d '{
    "query": {
      "bool": {
        "filter": [
          {
            "range": {
              "@timestamp": {
                "gte": "now-24h"
              }
            }
          }
        ]
      }
    },
    "aggs": {
      "response_time_percentiles": {
        "percentiles": {
          "field": "request_time",
          "percents": [50, 75, 90, 95, 99]
        }
      },
      "slow_endpoints": {
        "filter": {
          "range": {
            "request_time": {
              "gte": 2.0
            }
          }
        },
        "aggs": {
          "by_uri": {
            "terms": {
              "field": "request_uri",
              "size": 10,
              "order": {
                "avg_time": "desc"
              }
            },
            "aggs": {
              "avg_time": {
                "avg": {
                  "field": "request_time"
                }
              }
            }
          }
        }
      }
    },
    "size": 0
  }'

P99 response time’ın 2 saniyenin üzerinde olduğu endpoint’leri bu sorgu ile kolayca tespit edebilirsiniz.

DDoS ve Brute Force Tespiti

Belirli bir IP adresinden gelen anormal yüksek istek sayısı genellikle saldırı veya scraping işaretçisidir:

curl -X GET "localhost:9200/nginx-logs-*/_search" 
  -H 'Content-Type: application/json' 
  -d '{
    "query": {
      "range": {
        "@timestamp": {
          "gte": "now-15m"
        }
      }
    },
    "aggs": {
      "top_ips": {
        "terms": {
          "field": "remote_addr",
          "size": 20,
          "order": {
            "_count": "desc"
          }
        },
        "aggs": {
          "status_breakdown": {
            "terms": {
              "field": "status"
            }
          },
          "unique_uris": {
            "cardinality": {
              "field": "request_uri"
            }
          }
        }
      }
    },
    "size": 0
  }'

Son 15 dakikada en fazla istek gönderen IP’leri ve bu IP’lerden gelen isteklerin status code dağılımını görürsünüz. Eğer bir IP’den 200’den fazla istek geliyorsa ve çoğu 404 ise bu büyük ihtimalle path scanning yapan bir bot’tur.

Index Lifecycle Management (ILM)

Log verileri zamanla büyür ve disk alanını tüketir. ILM ile log indexlerinin yaşam döngüsünü otomatize edebilirsiniz.

# ILM Policy oluşturma
curl -X PUT "localhost:9200/_ilm/policy/nginx-logs-policy" 
  -H 'Content-Type: application/json' 
  -d '{
    "policy": {
      "phases": {
        "hot": {
          "min_age": "0ms",
          "actions": {
            "rollover": {
              "max_primary_shard_size": "10gb",
              "max_age": "1d"
            },
            "set_priority": {
              "priority": 100
            }
          }
        },
        "warm": {
          "min_age": "7d",
          "actions": {
            "shrink": {
              "number_of_shards": 1
            },
            "forcemerge": {
              "max_num_segments": 1
            },
            "set_priority": {
              "priority": 50
            }
          }
        },
        "cold": {
          "min_age": "30d",
          "actions": {
            "freeze": {},
            "set_priority": {
              "priority": 0
            }
          }
        },
        "delete": {
          "min_age": "90d",
          "actions": {
            "delete": {}
          }
        }
      }
    }
  }'

Bu policy ile loglarınız şu şekilde yönetilir:

  • Hot aşama: Aktif yazma yapılan indexler, 1 gün ya da 10GB dolduğunda rollover
  • Warm aşama: 7 gün sonra shard sayısı düşürülür, segment merge edilir, diskten tasarruf sağlanır
  • Cold aşama: 30 gün sonra index dondurulur, query yapılabilir ama yazma yapılamaz
  • Delete aşama: 90 gün sonra otomatik silme

Gerçek Dünya Senaryosu: Site Yavaşlama Sorununu Tespit Etme

Bir e-ticaret sitesinde çalıştığınızı varsayalım. Öğleden sonra 14:00-16:00 saatleri arasında site belirgin şekilde yavaşlıyor ama monitoring araçlarında CPU ve RAM normal görünüyor. Elasticsearch log analizi ile sorunu bulalım.

İlk adım: Bu saatlerde response time dağılımına bakın.

curl -X GET "localhost:9200/nginx-logs-*/_search" 
  -H 'Content-Type: application/json' 
  -d '{
    "query": {
      "bool": {
        "filter": [
          {
            "range": {
              "@timestamp": {
                "gte": "2024-01-15T14:00:00",
                "lte": "2024-01-15T16:00:00"
              }
            }
          }
        ]
      }
    },
    "aggs": {
      "per_minute": {
        "date_histogram": {
          "field": "@timestamp",
          "fixed_interval": "1m"
        },
        "aggs": {
          "p95_response_time": {
            "percentiles": {
              "field": "request_time",
              "percents": [95]
            }
          },
          "request_count": {
            "value_count": {
              "field": "request_time"
            }
          },
          "slow_requests": {
            "filter": {
              "range": {
                "request_time": {
                  "gte": 3.0
                }
              }
            }
          }
        }
      }
    },
    "size": 0
  }'

Diyelim ki sonuçta belirli dakikalarda P95 response time’ın 8-10 saniyeye çıktığını gördünüz. Bir sonraki adım hangi endpoint’lerin yavaş olduğunu bulmak. Upstream response time’ı da sorguya ekleyerek sorunun Nginx’de mi yoksa backend’de mi olduğunu anlayabilirsiniz. Eğer request_time yüksek ama upstream_response_time normalse sorun Nginx konfigürasyonunda, tam tersi ise sorun uygulama sunucusundadır.

Bu analiz sonucunda genellikle belirli bir database-heavy endpoint’in yük altında timeout’a düştüğünü ve bu durumun Nginx worker’larını meşgul ettiğini görürsünüz.

Curator ile Index Yönetimi

Elasticsearch Curator, ILM’ye alternatif olarak ya da ek yönetim görevleri için kullanılabilecek bir araçtır:

# Curator kurulumu
pip install elasticsearch-curator

# Curator konfigürasyon dosyası
cat > /etc/curator/curator.yml << 'EOF'
client:
  hosts:
    - elasticsearch-01
    - elasticsearch-02
  port: 9200
  use_ssl: false
  certificate:
  client_cert:
  client_key:
  ssl_no_validate: false
  username: curator_user
  password: ${CURATOR_PASSWORD}
  timeout: 30
  master_only: false

logging:
  loglevel: INFO
  logfile: /var/log/curator/curator.log
  logformat: default
EOF

# Eski indexleri silmek için action dosyası
cat > /etc/curator/actions/delete_old_logs.yml << 'EOF'
actions:
  1:
    action: delete_indices
    description: "Delete nginx log indices older than 90 days"
    options:
      ignore_empty_list: true
      timeout_override:
      continue_if_exception: false
      disable_action: false
    filters:
      - filtertype: pattern
        kind: prefix
        value: nginx-logs-
      - filtertype: age
        source: name
        direction: older
        timestring: '%Y.%m.%d'
        unit: days
        unit_count: 90
EOF

# Cron ile günlük çalıştırma
echo "0 2 * * * curator --config /etc/curator/curator.yml /etc/curator/actions/delete_old_logs.yml" | crontab -

Performans Optimizasyon İpuçları

Elasticsearch’i log analizi için kullanırken dikkat etmeniz gereken bazı kritik noktalar var.

Shard boyutları: Log indexleri için shard başına 10-50GB arası ideal kabul edilir. Çok küçük shardlar overhead yaratır, çok büyük shardlar recovery süresini uzatır.

Refresh interval: Log analizi için real-time değil near-real-time veri yeterlidir. Refresh interval’ı artırmak yazma performansını önemli ölçüde artırır:

curl -X PUT "localhost:9200/nginx-logs-*/_settings" 
  -H 'Content-Type: application/json' 
  -d '{
    "index": {
      "refresh_interval": "30s",
      "number_of_replicas": 0
    }
  }'

Mapping explosion: Nginx loglarında request_uri gibi alanları text yerine keyword olarak tanımlamak önemlidir. Aksi halde Elasticsearch her benzersiz değeri analiz eder ve mapping boyutu patlayabilir.

Query cache: Kibana dashboard’larında sık kullanılan filter’lar için query cache devreye girer. filter context kullanmanız bu cache’ten yararlanmanızı sağlar, query context kullanırsanız cache çalışmaz.

Alerting: Watcher ile Otomatik Uyarılar

Elasticsearch’in Watcher özelliği ile log verilerine dayalı otomatik uyarılar oluşturabilirsiniz:

curl -X PUT "localhost:9200/_watcher/watch/high-error-rate" 
  -H 'Content-Type: application/json' 
  -d '{
    "trigger": {
      "schedule": {
        "interval": "5m"
      }
    },
    "input": {
      "search": {
        "request": {
          "indices": ["nginx-logs-*"],
          "body": {
            "query": {
              "bool": {
                "filter": [
                  {
                    "range": {
                      "@timestamp": {
                        "gte": "now-5m"
                      }
                    }
                  },
                  {
                    "range": {
                      "status": {
                        "gte": 500
                      }
                    }
                  }
                ]
              }
            },
            "size": 0
          }
        }
      }
    },
    "condition": {
      "compare": {
        "ctx.payload.hits.total.value": {
          "gte": 50
        }
      }
    },
    "actions": {
      "send_slack": {
        "webhook": {
          "scheme": "https",
          "host": "hooks.slack.com",
          "port": 443,
          "method": "post",
          "path": "/services/YOUR/SLACK/WEBHOOK",
          "body": "{"text": "UYARI: Son 5 dakikada {{ctx.payload.hits.total.value}} adet 5xx hatasi tespit edildi!"}"
        }
      }
    }
  }'

Bu watcher her 5 dakikada bir çalışır ve son 5 dakikada 50’den fazla 5xx hatası varsa Slack’e mesaj gönderir. Threshold değerini sitenizin normal error rate’ine göre ayarlamanız gerekir.

Sonuç

Elasticsearch ile Nginx ve Apache log analizi, production ortamında karşılaşabileceğiniz sorunları hızla tespit etmenin en etkili yöntemlerinden biridir. grep ve awk komutlarıyla saatler harcayarak bulabileceğiniz bir sorunu, doğru kurgulanmış bir Elasticsearch altyapısıyla dakikalar içinde tespit edebilirsiniz.

Başlangıç için şu adımları öneririm: Önce Nginx’i JSON log format’ına geçirin, ardından Filebeat ile Elasticsearch’e veri gönderin. Index template’i doğru tanımlayın ve ILM policy’yi aktive edin. Temel sorguları yazıp Kibana’da görselleştirin. Son olarak kritik metrikler için Watcher alert’leri kurun.

Büyük ölçekli ortamlarda Logstash pipeline’larını devreye alarak log zenginleştirme (GeoIP, user agent parsing, bot detection) işlemlerini otomatize edebilirsiniz. Veri hacmi arttıkça shard stratejinizi gözden geçirin ve hot-warm-cold mimarisini optimize edin.

Log analizi bir kez kurulduktan sonra “neden yavaşladı”, “kim saldırıyordu”, “hangi deploy bu hatayı çıkardı” gibi soruların cevabını bulmak inanılmaz derecede kolaylaşıyor. Zamanında kurmak, gece 2’de bir kriz sırasında log dosyalarını grep‘lemekten çok daha az stres yaratıyor, bunu deneyimden söylüyorum.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir