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 composedosyanı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.
