ELK Stack ile Docker ve Kubernetes Log Toplama
Konteyner dünyasında log yönetimi, klasik VM tabanlı altyapıdan çok farklı bir zihin yapısı gerektiriyor. Podlar ölüp yeniden doğuyor, konteynerler farklı node’lara taşınıyor, deployment’lar saniyeler içinde scale oluyor. Bu ortamda “sunucuya SSH aç, log dosyasına bak” yaklaşımı artık işe yaramıyor. ELK Stack’i Docker ve Kubernetes ile entegre etmek, bu kaosa düzen getirmenin en yaygın ve kanıtlanmış yollarından biri. Ben de bu yazıda hem Docker Compose üzerinde hem de Kubernetes cluster’ında log toplama mimarisini, gerçek konfigürasyonlarla birlikte ele alacağım.
Neden ELK, Neden Bu Kadar Yaygın?
Açıkçası, Grafana Loki veya Splunk gibi alternatifleri denemeden ELK’yi seçmek körlük olur. Ama ELK’nin bu kadar yaygınlaşmasının somut sebepleri var: Elasticsearch’ün güçlü full-text arama yetenekleri, Kibana’nın kutudan çıkan hazır dashboard’ları ve özellikle Logstash’ın inanılmaz esnek pipeline yapısı. Kubernetes ortamında ise Filebeat, DaemonSet olarak konuşlandırılabiliyor ve her node’daki logları otomatik olarak toplayabiliyor. Bu mimari, özellikle yüzlerce pod çalıştıran ortamlarda ciddi bir operasyonel kolaylık sağlıyor.
Ama şunu da söylemem lazım: ELK kaynak açısından pahalı bir çözüm. Elasticsearch’ün memory ihtiyacı, özellikle JVM heap ayarlarıyla boğuşmak, production’da baş ağrısına dönüşebiliyor. Bu yüzden cluster boyutlandırmasını en baştan doğru yapmak kritik.
Docker Ortamında ELK Kurulumu
Docker Compose ile Temel Stack
Önce development veya staging ortamı için Docker Compose ile hızlı bir ELK kurulumu yapalım. Bu yapı, ilerleyen bölümlerdeki Kubernetes entegrasyonunu anlamak için iyi bir temel oluşturuyor.
# docker-compose.yml
version: '3.8'
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.11.0
container_name: elasticsearch
environment:
- node.name=elasticsearch
- cluster.name=docker-cluster
- discovery.type=single-node
- ELASTIC_PASSWORD=changeme123
- xpack.security.enabled=true
- xpack.security.http.ssl.enabled=false
- "ES_JAVA_OPTS=-Xms1g -Xmx1g"
volumes:
- esdata:/usr/share/elasticsearch/data
ports:
- "9200:9200"
networks:
- elk
ulimits:
memlock:
soft: -1
hard: -1
logstash:
image: docker.elastic.co/logstash/logstash:8.11.0
container_name: logstash
volumes:
- ./logstash/pipeline:/usr/share/logstash/pipeline:ro
- ./logstash/config/logstash.yml:/usr/share/logstash/config/logstash.yml:ro
ports:
- "5044:5044"
- "5000:5000/tcp"
- "5000:5000/udp"
- "9600:9600"
environment:
- "LS_JAVA_OPTS=-Xms512m -Xmx512m"
networks:
- elk
depends_on:
- elasticsearch
kibana:
image: docker.elastic.co/kibana/kibana:8.11.0
container_name: kibana
ports:
- "5601:5601"
environment:
- ELASTICSEARCH_HOSTS=http://elasticsearch:9200
- ELASTICSEARCH_USERNAME=kibana_system
- ELASTICSEARCH_PASSWORD=changeme123
networks:
- elk
depends_on:
- elasticsearch
filebeat:
image: docker.elastic.co/beats/filebeat:8.11.0
container_name: filebeat
user: root
volumes:
- ./filebeat/filebeat.yml:/usr/share/filebeat/filebeat.yml:ro
- /var/lib/docker/containers:/var/lib/docker/containers:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- elk
depends_on:
- logstash
volumes:
esdata:
networks:
elk:
driver: bridge
Bu compose dosyasında dikkat edilmesi gereken birkaç nokta var. ES_JAVA_OPTS ile heap boyutunu sınırlamak kritik, yoksa Elasticsearch container içinde mevcut RAM’in tamamını yemeye çalışır. /var/lib/docker/containers mount’u ise Filebeat’in tüm container log’larına erişmesi için zorunlu.
Filebeat Konfigürasyonu
Filebeat’in Docker container log’larını otomatik discover etmesi için aşağıdaki konfigürasyonu kullanıyorum:
# filebeat/filebeat.yml
filebeat.inputs: []
filebeat.autodiscover:
providers:
- type: docker
hints.enabled: true
hints.default_config:
type: container
paths:
- /var/lib/docker/containers/${data.container.id}/*.log
processors:
- add_cloud_metadata: ~
- add_docker_metadata:
host: "unix:///var/run/docker.sock"
- decode_json_fields:
fields: ["message"]
target: "json"
overwrite_keys: true
output.logstash:
hosts: ["logstash:5044"]
loadbalance: true
logging.level: info
logging.to_files: true
logging.files:
path: /var/log/filebeat
name: filebeat
keepfiles: 7
hints.enabled: true ayarı çok işe yarıyor. Bunu aktif ettiğinizde, container’larınıza label ekleyerek Filebeat davranışını per-container bazında kontrol edebiliyorsunuz. Örneğin bir container’a co.elastic.logs/enabled: "false" label’ı eklerseniz, o container’ın log’ları hiç toplanmıyor.
Logstash Pipeline
Logstash pipeline’ı, log’ların Elasticsearch’e gitmeden önce dönüştürüldüğü ve zenginleştirildiği yer. Temel bir pipeline şöyle görünüyor:
# logstash/pipeline/docker-logs.conf
input {
beats {
port => 5044
}
}
filter {
if [container][image][name] =~ /nginx/ {
grok {
match => {
"message" => '%{IPORHOST:client_ip} - %{DATA:user} [%{HTTPDATE:timestamp}] "%{WORD:method} %{DATA:request} HTTP/%{NUMBER:http_version}" %{NUMBER:response_code} %{NUMBER:bytes}'
}
}
date {
match => ["timestamp", "dd/MMM/yyyy:HH:mm:ss Z"]
target => "@timestamp"
}
mutate {
convert => {
"response_code" => "integer"
"bytes" => "integer"
}
}
}
if [container][image][name] =~ /app/ {
json {
source => "message"
target => "app_log"
}
mutate {
add_field => {
"log_level" => "%{[app_log][level]}"
"service_name" => "%{[app_log][service]}"
}
}
}
mutate {
remove_field => ["agent", "ecs", "input", "tags"]
}
}
output {
elasticsearch {
hosts => ["elasticsearch:9200"]
user => "elastic"
password => "changeme123"
index => "docker-logs-%{[container][image][name]}-%{+YYYY.MM.dd}"
ssl_enabled => false
}
}
Index naming convention’a dikkat edin. Container image adını index adına dahil etmek, Kibana’da filtreleme yaparken hayat kurtarıyor. Nginx logları ayrı bir index’e, uygulama logları ayrı bir index’e gidiyor.
Kubernetes’te ELK Entegrasyonu
Namespace ve Temel Hazırlık
Kubernetes’te ELK stack’i production-grade çalıştırmak için önce namespace yapısını düzenlemek gerekiyor:
kubectl create namespace monitoring
# Elasticsearch için secret oluştur
kubectl create secret generic elasticsearch-credentials
--from-literal=username=elastic
--from-literal=password=StrongPassword123!
-n monitoring
Filebeat DaemonSet
Kubernetes’te Filebeat’in asıl gücü DaemonSet olarak çalışmasından geliyor. Her node’a bir pod konuşlandırılıyor ve o node üzerindeki tüm pod log’ları toplanıyor:
# filebeat-daemonset.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: filebeat
namespace: monitoring
labels:
app: filebeat
spec:
selector:
matchLabels:
app: filebeat
template:
metadata:
labels:
app: filebeat
spec:
serviceAccountName: filebeat
terminationGracePeriodSeconds: 30
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
containers:
- name: filebeat
image: docker.elastic.co/beats/filebeat:8.11.0
args: ["-c", "/etc/filebeat.yml", "-e"]
env:
- name: ELASTICSEARCH_HOST
value: "elasticsearch-service.monitoring.svc.cluster.local"
- name: ELASTICSEARCH_PORT
value: "9200"
- name: ELASTIC_USERNAME
valueFrom:
secretKeyRef:
name: elasticsearch-credentials
key: username
- name: ELASTIC_PASSWORD
valueFrom:
secretKeyRef:
name: elasticsearch-credentials
key: password
- name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
securityContext:
runAsUser: 0
resources:
limits:
memory: 200Mi
cpu: 100m
requests:
memory: 100Mi
cpu: 50m
volumeMounts:
- name: config
mountPath: /etc/filebeat.yml
readOnly: true
subPath: filebeat.yml
- name: data
mountPath: /usr/share/filebeat/data
- name: varlibdockercontainers
mountPath: /var/lib/docker/containers
readOnly: true
- name: varlog
mountPath: /var/log
readOnly: true
volumes:
- name: config
configMap:
defaultMode: 0640
name: filebeat-config
- name: varlibdockercontainers
hostPath:
path: /var/lib/docker/containers
- name: varlog
hostPath:
path: /var/log
- name: data
hostPath:
path: /var/lib/filebeat-data
type: DirectoryOrCreate
hostNetwork: true ve dnsPolicy: ClusterFirstWithHostNet kombinasyonu bazı ortamlarda network sorunlarına yol açabilir, dikkatli olun. Özellikle özel CNI plugin’leri kullanan cluster’larda bu ayarları test etmek lazım.
Filebeat ConfigMap for Kubernetes
# filebeat-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: filebeat-config
namespace: monitoring
data:
filebeat.yml: |-
filebeat.autodiscover:
providers:
- type: kubernetes
node: ${NODE_NAME}
hints.enabled: true
hints.default_config:
type: container
paths:
- /var/log/containers/*${data.kubernetes.container.id}.log
templates:
- condition:
contains:
kubernetes.namespace: "production"
config:
- type: container
paths:
- /var/log/containers/*${data.kubernetes.container.id}.log
multiline.pattern: '^d{4}-d{2}-d{2}'
multiline.negate: true
multiline.match: after
processors:
- add_kubernetes_metadata:
host: ${NODE_NAME}
matchers:
- logs_path:
logs_path: "/var/log/containers/"
- drop_event:
when:
contains:
kubernetes.namespace: "kube-system"
- decode_json_fields:
fields: ["message"]
target: ""
overwrite_keys: true
add_error_key: true
output.elasticsearch:
hosts: ['${ELASTICSEARCH_HOST}:${ELASTICSEARCH_PORT}']
username: '${ELASTIC_USERNAME}'
password: '${ELASTIC_PASSWORD}'
index: "k8s-logs-%{[kubernetes.namespace]}-%{+YYYY.MM.dd}"
ssl.enabled: false
setup.ilm.enabled: true
setup.ilm.rollover_alias: "k8s-logs"
setup.ilm.pattern: "{now/d}-000001"
logging.level: warning
Burada drop_event processor’una dikkat çekmek istiyorum. kube-system namespace’inden gelen log’ları doğrudan düşürüyoruz. Bu namespace’den akan log hacmi ciddi boyutlara ulaşabiliyor ve Elasticsearch’e gereksiz yük bindiriyor. Production’da hangi namespace’lerin log’lanacağını ve hangilerinin dışarıda tutulacağını net bir şekilde tanımlamak gerekiyor.
RBAC Ayarları
Filebeat’in Kubernetes API’sine erişmesi için gerekli RBAC tanımlamaları:
# filebeat-rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: filebeat
namespace: monitoring
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: filebeat
rules:
- apiGroups: [""]
resources:
- namespaces
- pods
- nodes
verbs: ["get", "watch", "list"]
- apiGroups: ["apps"]
resources:
- replicasets
verbs: ["get", "watch", "list"]
- apiGroups: ["batch"]
resources:
- jobs
verbs: ["get", "watch", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: filebeat
subjects:
- kind: ServiceAccount
name: filebeat
namespace: monitoring
roleRef:
kind: ClusterRole
name: filebeat
apiGroup: rbac.authorization.k8s.io
Index Lifecycle Management
Production’da en sık atlanan konu ILM politikaları. Elasticsearch’e akan log’lar için mutlaka bir lifecycle policy tanımlamanız lazım, yoksa disk dolması an meselesi:
curl -X PUT "http://elasticsearch:9200/_ilm/policy/k8s-logs-policy"
-H 'Content-Type: application/json'
-u elastic:changeme123
-d '{
"policy": {
"phases": {
"hot": {
"min_age": "0ms",
"actions": {
"rollover": {
"max_primary_shard_size": "10gb",
"max_age": "1d"
},
"set_priority": {
"priority": 100
}
}
},
"warm": {
"min_age": "3d",
"actions": {
"forcemerge": {
"max_num_segments": 1
},
"shrink": {
"number_of_shards": 1
},
"set_priority": {
"priority": 50
}
}
},
"cold": {
"min_age": "7d",
"actions": {
"set_priority": {
"priority": 0
}
}
},
"delete": {
"min_age": "30d",
"actions": {
"delete": {}
}
}
}
}
}'
Bu policy ile log’lar hot phase’de 1 gün veya 10GB boyutuna ulaşana kadar tutulacak, 3. günden itibaren warm phase’e geçerek shard sayısı küçültülecek, 7. günden itibaren cold phase’e düşecek ve 30. günde silinecek. Retention sürelerini iş gereksinimlerinize göre ayarlamanız gerekiyor, yasal zorunluluklar varsa özellikle dikkatli olun.
Sorun Giderme: Sık Karşılaşılan Durumlar
Filebeat Pod’dan Log Gelmiyor
# Filebeat loglarını kontrol et
kubectl logs -n monitoring -l app=filebeat --tail=100
# Filebeat'in doğru node'da çalışıp çalışmadığını gör
kubectl get pods -n monitoring -o wide
# Elasticsearch bağlantısını test et
kubectl exec -it -n monitoring <filebeat-pod-name> --
curl -u elastic:changeme123 http://elasticsearch-service:9200/_cluster/health
Elasticsearch Heap Pressure
Kubernetes ortamında Elasticsearch pod’u sürekli OOMKilled oluyorsa:
# Mevcut heap kullanımını kontrol et
curl -u elastic:password
"http://elasticsearch:9200/_nodes/stats/jvm?pretty" |
grep -A5 "heap_used_percent"
# Hot thread'leri incele
curl -u elastic:password
"http://elasticsearch:9200/_nodes/hot_threads"
Production’da Elasticsearch pod’u için resources.limits memory değerini, JVM heap’inin iki katı olarak ayarlamak iyi bir başlangıç noktası. Yani 4GB heap için 8GB memory limit.
Kibana’da Log Analizi için Temel Adımlar
Loglar Elasticsearch’e düştükten sonra Kibana’da birkaç şeyi mutlaka yapmanızı öneririm. Önce Stack Management > Index Patterns altında index pattern tanımlayın, k8s-logs-* gibi wildcard kullanın. Ardından Discover ekranında en çok kullandığım field’ları sol tarafa sabitleyin: kubernetes.namespace, kubernetes.pod.name, kubernetes.container.name ve log.level.
Özellikle multi-line log’larda dikkatli olun. Java stack trace’leri veya Python traceback’leri birden fazla satıra yayılıyor ve her satır ayrı bir event olarak gelirse anlamlı bir analiz yapamıyorsunuz. Bu yüzden ConfigMap’teki multiline ayarları kritik önem taşıyor.
Kaynak Optimizasyonu ve Dikkat Edilmesi Gerekenler
Pratik olarak gördüğüm bazı anti-pattern’ler:
- Tüm field’ları index etmek: Elasticsearch her yeni field’ı otomatik index ediyor. Yüzlerce farklı label’a sahip Kubernetes ortamında bu mapping explosion’a yol açabiliyor.
dynamic: "strict"veyadynamic: falseayarını düşünün. - Shard sayısını ayarlamamak: Her index için varsayılan shard sayısı 1 olsa da, yoğun log akışı olan ortamlarda shard sayısını artırmak performansı ciddi iyileştiriyor.
- Logstash’ı gereksiz yere kullanmak: Basit log toplama senaryolarında Filebeat’ten direkt Elasticsearch’e yazmak hem daha az kaynak tüketiyor hem de tek bir hata noktası ortadan kalkıyor. Logstash’ı gerçekten karmaşık dönüşüm ihtiyacınız olduğunda devreye alın.
- Monitoring’i monitoring etmemek: ELK stack’in kendisini izlemek için Elasticsearch’ün kendi monitoring özelliklerini açık tutun. Stack monitoring Kibana’da
Stack Management > Stack Monitoringaltında aktif edilebiliyor.
Sonuç
Docker ve Kubernetes ortamlarında ELK Stack kurgulamak, ilk bakışta çok sayıda moving part içerdiğinden korkutucu gelebiliyor. Ama mimariyi parçalara ayırıp adım adım ilerlendiğinde, her bileşenin neden orada olduğu netleşiyor. Filebeat log’ları topluyor ve ilk işlemeyi yapıyor, Logstash karmaşık dönüşüm ihtiyaçlarını karşılıyor, Elasticsearch depolama ve arama motorunu üstleniyor, Kibana da bu veriden anlam çıkarmamızı sağlıyor.
Production’a almadan önce ILM politikalarınızı, Elasticsearch heap ayarlarınızı ve Filebeat kaynak limitlerini mutlaka gözden geçirin. Log hacmi tahminleriniz tutmazsa, Elasticsearch’ün disk dolması veya OOM sorunu yaşaması kaçınılmaz. Sistemin kendisini de monitör etmek, bu tip sürprizlerin önüne geçmenin en güvenilir yolu.
Son olarak: bu altyapıyı bir kez kurup bırakmayın. Index boyutlarını, query latency’sini ve Filebeat backpressure metriklerini düzenli olarak gözden geçirmek, uzun vadede sizi ciddi baş ağrısından kurtarıyor.
