ELK Stack Docker Compose ile Hızlı Kurulum

Üretim ortamında log yönetimi deyince akla ilk gelen şey hala ELK Stack. Elasticsearch, Logstash ve Kibana üçlüsünü elle, paket paket kurmak bir dönem can sıkıcıydı; Java sürüm uyuşmazlıkları, servis bağımlılıkları, config dosyaları arasında kaybolmak… Docker Compose bu tabloya ciddi anlamda müdahale etti. Artık birkaç dakika içinde çalışan bir ELK ortamı kurmak mümkün. Ama “mümkün” ile “doğru yapılmış” arasındaki fark, bu yazının konusu.

Neden Docker Compose ile ELK?

Klasik kurulumda her bileşen ayrı ayrı yönetilir. Elasticsearch güncellenmesi gerektiğinde Logstash uyumluluğu kontrol edilir, Kibana versiyonu takip edilir. Bunlar küçük şeyler gibi görünse de gece 2’de alarm geldiğinde fark yaratır. Docker Compose ile tüm stack tek bir YAML dosyasında tanımlanır, versiyonlar sabitleniyor ve ortamlar arasında taşınabilirlik ciddi ölçüde artıyor.

Geliştirme, staging ve üretim ortamlarında aynı compose dosyasını (küçük overrides ile) kullanmak gerçekten hayat kurtarıcı. “Bende çalışıyor” problemini minimize ediyor.

Ön Gereksinimler

Sisteminizde şunların kurulu olması gerekiyor:

  • Docker Engine 20.10+
  • Docker Compose v2 (plugin olarak veya standalone)
  • En az 4 GB RAM (Elasticsearch bu konuda cimri değil)
  • vm.max_map_count ayarı (bunu atlayan çok kişi var, sonra Elasticsearch başlamıyor)

vm.max_map_count ayarını şimdi yapalım, sonraya bırakmayın:

# Geçici olarak ayarla (reboot'ta sıfırlanır)
sudo sysctl -w vm.max_map_count=262144

# Kalıcı hale getir
echo "vm.max_map_count=262144" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

Bu ayarı yapmadan Elasticsearch başlar gibi yapıp 137 exit code ile kapanır. Logları incelediğinizde “max virtual memory areas vm.max_map_count [65530] is too low” satırını görürsünüz.

Dizin Yapısını Oluşturalım

Düzenli bir yapıyla başlamak, ilerleyen süreçte büyük kolaylık sağlar:

mkdir -p elk-stack/{elasticsearch/data,logstash/{config,pipeline},kibana/config}
cd elk-stack

Bu yapıda Elasticsearch verisi, Logstash konfigürasyonu ve Kibana ayarları ayrı dizinlerde tutulacak. Volume mount ederken bu izolasyon işinize yarayacak.

Environment Dosyası

Tüm değişkenleri merkezi bir yerde tutmak en temiz yaklaşım. .env dosyası oluşturun:

cat > .env << 'EOF'
ELASTIC_VERSION=8.11.0
ELASTIC_PASSWORD=guclu_bir_sifre_koyun
KIBANA_PASSWORD=kibana_icin_ayri_sifre
STACK_VERSION=8.11.0
CLUSTER_NAME=my-elk-cluster
LICENSE=basic
ES_PORT=9200
KIBANA_PORT=5601
LOGSTASH_PORT=5044
MEM_LIMIT=1073741824
EOF

MEM_LIMIT değeri byte cinsinden. 1073741824 = 1 GB. Sunucunuzun kapasitesine göre bu değeri artırın. Üretimde her bileşene en az 2 GB vermeniz önerilir.

Docker Compose Dosyası

Şimdi asıl işe gelelim. docker-compose.yml dosyasını oluşturun:

version: "3.8"

services:
  setup:
    image: docker.elastic.co/elasticsearch/elasticsearch:${STACK_VERSION}
    volumes:
      - ./elasticsearch/data:/usr/share/elasticsearch/data
    user: "0"
    command: >
      bash -c '
        if [ x${ELASTIC_PASSWORD} == x ]; then
          echo "ELASTIC_PASSWORD degiskeni bos, lutfen doldurun";
          exit 1;
        elif [ x${KIBANA_PASSWORD} == x ]; then
          echo "KIBANA_PASSWORD degiskeni bos";
          exit 1;
        fi;
        if [ ! -f config/certs/ca.zip ]; then
          echo "CA sertifikasi olusturuluyor...";
          bin/elasticsearch-certutil ca --silent --pem -out config/certs/ca.zip;
          unzip config/certs/ca.zip -d config/certs;
        fi;
        if [ ! -f config/certs/certs.zip ]; then
          echo "Sertifikalar olusturuluyor...";
          echo -ne 
          "instances:n"
          "  - name: es01n"
          "    dns:n"
          "      - es01n"
          "      - localhostn"
          "    ip:n"
          "      - 127.0.0.1n"
          > config/certs/instances.yml;
          bin/elasticsearch-certutil cert --silent --pem -out config/certs/certs.zip --in config/certs/instances.yml --ca-cert config/certs/ca/ca.crt --ca-key config/certs/ca/ca.key;
          unzip config/certs/certs.zip -d config/certs;
        fi;
        echo "Dosya izinleri ayarlaniyor...";
        chown -R root:root config/certs;
        find . -type d -exec chmod 750 {} ;;
        find . -type f -exec chmod 640 {} ;;
        echo "Hazir.";
      '
    healthcheck:
      test: ["CMD-SHELL", "[ -f config/certs/es01/es01.crt ]"]
      interval: 1s
      timeout: 5s
      retries: 120

  elasticsearch:
    depends_on:
      setup:
        condition: service_healthy
    image: docker.elastic.co/elasticsearch/elasticsearch:${STACK_VERSION}
    container_name: elasticsearch
    volumes:
      - ./elasticsearch/data:/usr/share/elasticsearch/data
      - certs:/usr/share/elasticsearch/config/certs
    ports:
      - ${ES_PORT}:9200
    environment:
      - node.name=es01
      - cluster.name=${CLUSTER_NAME}
      - discovery.type=single-node
      - ELASTIC_PASSWORD=${ELASTIC_PASSWORD}
      - bootstrap.memory_lock=true
      - xpack.security.enabled=true
      - xpack.security.http.ssl.enabled=true
      - xpack.security.http.ssl.key=certs/es01/es01.key
      - xpack.security.http.ssl.certificate=certs/es01/es01.crt
      - xpack.security.http.ssl.certificate_authorities=certs/ca/ca.crt
      - xpack.license.self_generated.type=${LICENSE}
    mem_limit: ${MEM_LIMIT}
    ulimits:
      memlock:
        soft: -1
        hard: -1
    healthcheck:
      test:
        [
          "CMD-SHELL",
          "curl -s --cacert config/certs/ca/ca.crt https://localhost:9200 | grep -q 'missing authentication credentials'",
        ]
      interval: 10s
      timeout: 10s
      retries: 120

  logstash:
    depends_on:
      elasticsearch:
        condition: service_healthy
    image: docker.elastic.co/logstash/logstash:${STACK_VERSION}
    container_name: logstash
    volumes:
      - ./logstash/config/logstash.yml:/usr/share/logstash/config/logstash.yml:ro
      - ./logstash/pipeline:/usr/share/logstash/pipeline:ro
      - certs:/usr/share/logstash/certs
    ports:
      - ${LOGSTASH_PORT}:5044
    environment:
      - ELASTIC_PASSWORD=${ELASTIC_PASSWORD}
    mem_limit: ${MEM_LIMIT}

  kibana:
    depends_on:
      elasticsearch:
        condition: service_healthy
    image: docker.elastic.co/kibana/kibana:${STACK_VERSION}
    container_name: kibana
    volumes:
      - ./kibana/config/kibana.yml:/usr/share/kibana/config/kibana.yml:ro
      - certs:/usr/share/kibana/certs
    ports:
      - ${KIBANA_PORT}:5601
    environment:
      - SERVERNAME=kibana
      - ELASTICSEARCH_HOSTS=https://elasticsearch:9200
      - ELASTICSEARCH_USERNAME=kibana_system
      - ELASTICSEARCH_PASSWORD=${KIBANA_PASSWORD}
      - ELASTICSEARCH_SSL_CERTIFICATEAUTHORITIES=certs/ca/ca.crt
    mem_limit: ${MEM_LIMIT}
    healthcheck:
      test:
        [
          "CMD-SHELL",
          "curl -s -I http://localhost:5601 | grep -q 'HTTP/1.1 302 Found'",
        ]
      interval: 10s
      timeout: 10s
      retries: 120

volumes:
  certs:
    driver: local

Logstash Konfigürasyonu

Logstash’in çalışması için iki dosya gerekiyor. İlk olarak ana konfigürasyon:

cat > logstash/config/logstash.yml << 'EOF'
http.host: "0.0.0.0"
xpack.monitoring.elasticsearch.hosts: ["https://elasticsearch:9200"]
xpack.monitoring.elasticsearch.username: "elastic"
xpack.monitoring.elasticsearch.password: "${ELASTIC_PASSWORD}"
xpack.monitoring.elasticsearch.ssl.certificate_authority: "/usr/share/logstash/certs/ca/ca.crt"
EOF

Şimdi pipeline dosyası. Bu dosya Logstash’e “gelen veriyi nasıl işle, nereye gönder” diyor:

cat > logstash/pipeline/main.conf << 'EOF'
input {
  beats {
    port => 5044
    ssl => false
  }
  tcp {
    port => 5000
    codec => json
  }
}

filter {
  if [type] == "nginx" {
    grok {
      match => {
        "message" => '%{IPORHOST:remote_addr} - %{DATA:remote_user} [%{HTTPDATE:time_local}] "%{WORD:method} %{DATA:request} HTTP/%{NUMBER:http_version}" %{NUMBER:status} %{NUMBER:body_bytes_sent} "%{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"
      }
    }
  }

  if [status] >= 400 {
    mutate {
      add_tag => ["error_request"]
    }
  }
}

output {
  elasticsearch {
    hosts => ["https://elasticsearch:9200"]
    user => "elastic"
    password => "${ELASTIC_PASSWORD}"
    ssl_certificate_authority => "/usr/share/logstash/certs/ca/ca.crt"
    index => "logs-%{[type]}-%{+YYYY.MM.dd}"
  }
}
EOF

Bu pipeline Nginx loglarını parse ediyor, 400 ve üzeri HTTP kodlarını etiketliyor. Gerçek dünyada bu filtreleri ihtiyacınıza göre genişleteceksiniz.

Kibana Konfigürasyonu

cat > kibana/config/kibana.yml << 'EOF'
server.name: kibana
server.host: "0.0.0.0"
elasticsearch.hosts: ["https://elasticsearch:9200"]
monitoring.ui.container.elasticsearch.enabled: true
EOF

Stack’i Ayağa Kaldırın

Her şey hazırsa başlatabilirsiniz:

# Arka planda çalıştır
docker compose up -d

# Logları takip et
docker compose logs -f

# Sadece belirli bir servisi izle
docker compose logs -f elasticsearch

İlk başlatmada setup servisi çalışır, sertifikaları oluşturur ve sağlıklı duruma geçince diğer servisler ayağa kalkar. Bu süreç 2-3 dakika sürebilir, sabırlı olun.

Servislerin durumunu kontrol etmek için:

docker compose ps

Elasticsearch’ün ayakta olup olmadığını test etmek:

curl -s --cacert elasticsearch/data/certs/ca/ca.crt 
  -u elastic:guclu_bir_sifre_koyun 
  https://localhost:9200/_cluster/health | python3 -m json.tool

status: green veya status: yellow görüyorsanız (tek node’da yellow normaldir) her şey yolunda demektir.

Kibana’ya İlk Giriş

Tarayıcınızda http://sunucu-ip:5601 adresini açın. Kullanıcı adı elastic, şifre .env dosyasında belirlediğiniz şifre.

İlk girişte Kibana size bir “Explore on my own” seçeneği sunacak. Oradan devam edin. Sol menüden Stack Management > Index Patterns yolunu izleyerek logs-* pattern’ını tanımlayın. @timestamp alanını zaman alanı olarak seçin.

Filebeat ile Log Göndermek

Log üreten sunucunuzda Filebeat kurulu olmalı. Hızlı kurulum için:

# Debian/Ubuntu
curl -L -O https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-8.11.0-amd64.deb
dpkg -i filebeat-8.11.0-amd64.deb

# RHEL/CentOS
curl -L -O https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-8.11.0-x86_64.rpm
rpm -vi filebeat-8.11.0-x86_64.rpm

Filebeat konfigürasyonu (/etc/filebeat/filebeat.yml):

filebeat.inputs:
  - type: log
    enabled: true
    paths:
      - /var/log/nginx/access.log
    fields:
      type: nginx
    fields_under_root: true

output.logstash:
  hosts: ["elk-sunucu-ip:5044"]

logging.level: info
logging.to_files: true
logging.files:
  path: /var/log/filebeat
  name: filebeat
  keepfiles: 7

Filebeat’i başlatın:

systemctl enable filebeat
systemctl start filebeat
systemctl status filebeat

Yaygın Sorunlar ve Çözümleri

Elasticsearch başlamıyor, exit code 137: Bu genellikle OOM (Out of Memory) sorunu. Docker’a verilen memory limiti yetersiz. docker compose down yapıp .env içindeki MEM_LIMIT değerini artırın.

“flood stage disk watermark exceeded” hatası: Elasticsearch disk dolunca yazma işlemlerini durduruyor. Disk temizleyin veya eski index’leri silin:

# 30 günden eski logları sil
curl -X DELETE 
  --cacert elasticsearch/data/certs/ca/ca.crt 
  -u elastic:sifreniz 
  "https://localhost:9200/logs-nginx-$(date -d '30 days ago' +%Y.%m.%d)"

Kibana “Kibana server is not ready yet” diyor: Elasticsearch’ün tam olarak ayağa kalkmasını bekleyin. docker compose logs elasticsearch ile durumu takip edin.

Logstash pipeline hataları: Pipeline syntax hatalarını şu şekilde test edebilirsiniz:

docker compose exec logstash 
  bin/logstash -f /usr/share/logstash/pipeline/main.conf --config.test_and_exit

Index Lifecycle Management Ayarı

Üretim ortamında index’lerin otomatik olarak yönetilmesi gerekiyor. Loglar büyür, disk dolar. ILM (Index Lifecycle Management) politikası oluşturun:

curl -X PUT 
  --cacert elasticsearch/data/certs/ca/ca.crt 
  -u elastic:sifreniz 
  -H "Content-Type: application/json" 
  https://localhost:9200/_ilm/policy/logs-policy 
  -d '{
    "policy": {
      "phases": {
        "hot": {
          "min_age": "0ms",
          "actions": {
            "rollover": {
              "max_size": "10gb",
              "max_age": "7d"
            }
          }
        },
        "warm": {
          "min_age": "3d",
          "actions": {
            "shrink": {
              "number_of_shards": 1
            },
            "forcemerge": {
              "max_num_segments": 1
            }
          }
        },
        "delete": {
          "min_age": "30d",
          "actions": {
            "delete": {}
          }
        }
      }
    }
  }'

Bu politika şunu yapıyor: index 10 GB’a ulaştığında veya 7 gün geçtiğinde yeni index’e geçiyor (rollover). 3 gün sonra “warm” fazına alınıyor, shard sayısı düşürülüyor. 30 gün sonra siliniyor. Üretimde bu değerleri kendi saklama politikanıza göre ayarlayın.

Performans İpuçları

Günlük operasyonda dikkat etmeniz gereken birkaç nokta var:

  • Heap Size: Elasticsearch için JVM heap’ini sistem RAM’inin yarısına kadar çıkarabilirsiniz, ama 32 GB’ı geçmeyin. ES_JAVA_OPTS="-Xms2g -Xmx2g" şeklinde environment değişkeni ekleyin.
  • Refresh Interval: Yüksek yazma yükünde index refresh interval’ını artırın. "refresh_interval": "30s" yaparsanız hem yazma performansı artar hem de kaynak kullanımı düşer.
  • Shard Sayısı: Tek node kurulumda index başına 1 shard yeterli. Gereksiz shard memory tüketir.
  • Docker Log Rotation: Containerların kendi logları da büyüyebilir. docker compose dosyanıza logging sınırı ekleyin: logging: options: max-size: "100m" max-file: "3"

Stack’i Güncellemek

Versiyon güncellemesi yaparken önce .env dosyasındaki STACK_VERSION değerini güncelleyin:

# Önce mevcut stack'i durdur
docker compose down

# .env dosyasında versiyonu güncelle
sed -i 's/8.11.0/8.12.0/g' .env

# Yeni imajları çek
docker compose pull

# Stack'i yeniden başlat
docker compose up -d

Major version güncellemelerinde (7.x’ten 8.x’e) bu kadar basit değil, Elasticsearch’ün rolling upgrade prosedürünü takip etmeniz gerekiyor.

Sonuç

Docker Compose ile ELK Stack kurmak artık gerçekten hızlı ve tekrarlanabilir bir süreç. Burada anlattığım yapı single-node kurulum; production için multi-node cluster kurmanız ve Logstash yerine doğrudan Elasticsearch’e veri gönderen daha hafif Beats ajanlarını değerlendirmeniz mantıklı olabilir.

Dikkat edilmesi gereken en kritik noktalar şunlar: vm.max_map_count ayarını atlamamak, sertifika yönetimini ihmal etmemek, ILM politikası tanımlamadan stack’i üretime almamak. Disk dolduğunda Elasticsearch yazmayı durdurur ve bu durumdan çıkmak bazen can sıkıcı oluyor.

Log yönetimi reactive değil proactive yapılmalı. Kibana’da anlamlı dashboard’lar oluşturun, alert mekanizmalarını kurun (Kibana Alerting veya ElastAlert2 değerlendirilebilir) ve logları sadece sorun çıktığında değil, düzenli olarak inceleyin. Asıl güç burada.

Bir yanıt yazın

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