Konteyner Performansı: Docker ve Kubernetes Optimizasyonu

Prodüksiyonda konteyner ortamı kurduğunuzda her şey harika görünür. Uygulamalar çalışıyor, servisler ayakta, herkes mutlu. Sonra trafik artmaya başlar ve o an gerçeklerle yüzleşirsiniz: yüksek CPU kullanımı, bellek sızıntıları, ağ gecikmeleri ve Kubernetes pod’larının sürekli yeniden başlaması. Bu yazıda Docker ve Kubernetes ortamlarında performans sorunlarını nasıl tespit edeceğinizi, izleyeceğinizi ve optimize edeceğinizi gerçek dünya senaryolarıyla anlatacağım.

Docker Konteyner Performansını Anlama

Her şeyden önce konteynerlerinizin ne kadar kaynak tükettiğini bilmeniz gerekiyor. docker stats komutu bunun için en hızlı başlangıç noktası.

# Tüm çalışan konteynerlerin anlık istatistikleri
docker stats --no-stream

# Belirli bir konteynerin detaylı izlenmesi
docker stats --format "table {{.Container}}t{{.CPUPerc}}t{{.MemUsage}}t{{.NetIO}}t{{.BlockIO}}" my-app

# JSON formatında çıktı almak (otomasyon için kullanışlı)
docker stats --no-stream --format json my-app

Burada dikkat etmeniz gereken birkaç kritik metrik var. CPUPerc değeri 80% üzerindeyse throttling başlar ve uygulamanız yavaşlar. MemUsage değeri limiti aşmaya başlıyorsa OOM Killer devreye girer ve konteyneriniz çöker. Bu değerleri düzenli olarak kayıt altına almak için basit bir script yazabilirsiniz.

#!/bin/bash
# docker-perf-monitor.sh
LOG_FILE="/var/log/docker-stats.log"
INTERVAL=30

while true; do
    TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
    echo "=== $TIMESTAMP ===" >> $LOG_FILE
    docker stats --no-stream --format 
        "{{.Container}},{{.CPUPerc}},{{.MemUsage}},{{.MemPerc}},{{.NetIO}},{{.BlockIO}}" 
        >> $LOG_FILE
    sleep $INTERVAL
done

Bu script’i systemd servisi olarak çalıştırıp log rotasyonunu da ayarladığınızda, performans sorunlarını retrospektif olarak analiz edebilirsiniz.

Resource Limits: Konteyner Kaynak Kısıtlamaları

Prodüksiyonda kaynak limitleri olmadan çalışmak, makinenizin tüm RAM’ini tek bir uygulamanın yemesine izin vermek anlamına gelir. Bu hem güvensiz hem de tehlikeli.

# CPU ve bellek limiti ile konteyner başlatma
docker run -d 
    --name web-app 
    --cpus="2.0" 
    --memory="512m" 
    --memory-swap="512m" 
    --memory-reservation="256m" 
    --cpu-shares=1024 
    nginx:latest

# Çalışan bir konteynerin limitlerini güncelleme (yeniden başlatma gerekmez)
docker update 
    --cpus="1.5" 
    --memory="1g" 
    --memory-swap="1g" 
    my-existing-container

Buradaki parametreleri açıklayayım:

  • –cpus: Konteyner’in kullanabileceği maksimum CPU miktarı (2.0 = 2 çekirdek eşdeğeri)
  • –memory: Hard limit, bu aşılırsa OOM Killer devreye girer
  • –memory-swap: RAM + swap toplamı, memory ile aynıysa swap kullanılmaz
  • –memory-reservation: Soft limit, sistem baskı altındayken bu kadarı garanti edilir
  • –cpu-shares: Göreli CPU ağırlığı, varsayılan 1024, rekabet durumunda öncelik belirler

Gerçek bir senaryoda bir e-ticaret sitesini yönetiyordum. Gece yarısı yapılan kampanya trafiğinde MySQL konteyner’i tüm sunucu belleğini yiyip diğer servisleri çöküşe geçiriyordu. Kaynak limitleri ekleyince sorun ortadan kalktı ama bu sefer doğru limitleri bulmak için benchmark almak gerekti.

Docker Image Optimizasyonu

Küçük image’lar hem daha hızlı başlar hem de daha az bellek tüketir. Multi-stage build bu konuda büyük fark yaratır.

# Optimize edilmemiş örnek (YAPMAYIN)
FROM node:18
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build
EXPOSE 3000
CMD ["node", "dist/server.js"]

# Optimize edilmiş multi-stage build
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build

FROM node:18-alpine AS runtime
WORKDIR /app
RUN addgroup -g 1001 -S nodejs && 
    adduser -S nodeuser -u 1001
COPY --from=builder --chown=nodeuser:nodejs /app/dist ./dist
COPY --from=builder --chown=nodeuser:nodejs /app/node_modules ./node_modules
USER nodeuser
EXPOSE 3000
CMD ["node", "dist/server.js"]

Bu yaklaşımla image boyutunuzu bazen 10 kata kadar küçültebilirsiniz. Alpine tabanlı image’lar 5-10MB’dan başlarken, tam Ubuntu tabanlı bir image 200MB+ olabilir. Build zamanı bağımlılıklarını runtime image’a taşımamak kritik önem taşır.

Kubernetes Pod Optimizasyonu

Kubernetes’te performans optimizasyonu Docker’a göre çok daha katmanlı bir konu. Başlangıç noktamız yine kaynak tanımları olacak.

# deployment.yaml - Optimize edilmiş kaynak tanımları
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-api
  namespace: production
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web-api
  template:
    metadata:
      labels:
        app: web-api
    spec:
      containers:
      - name: web-api
        image: my-registry/web-api:v1.2.3
        ports:
        - containerPort: 8080
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10
          failureThreshold: 3
        readinessProbe:
          httpGet:
            path: /ready
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 5
          successThreshold: 1
          failureThreshold: 3
        env:
        - name: GOMAXPROCS
          valueFrom:
            resourceFieldRef:
              resource: limits.cpu

requests ile limits arasındaki fark çok önemli. Requests, pod’un schedule edilmesi için gerekli minimum kaynak, limits ise maksimum kullanabileceği miktar. requests=limits yaparsanız pod’unuz “Guaranteed” QoS class’ına girer ve OOM durumunda en son kill edilir. Bu prodüksiyon için önerilir.

Horizontal Pod Autoscaler Konfigürasyonu

Trafik dalgalanmalarını yönetmenin en etkili yolu HPA. Ama yanlış konfigüre edilmiş HPA bazen daha büyük sorunlara yol açabilir.

# hpa.yaml - Akıllı otomatik ölçeklendirme
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: web-api-hpa
  namespace: production
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web-api
  minReplicas: 3
  maxReplicas: 20
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 80
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
      - type: Percent
        value: 10
        periodSeconds: 60
    scaleUp:
      stabilizationWindowSeconds: 60
      policies:
      - type: Percent
        value: 50
        periodSeconds: 30
      - type: Pods
        value: 4
        periodSeconds: 30
      selectPolicy: Max

scaleDown.stabilizationWindowSeconds: 300 değeri özellikle önemli. Bu olmadan HPA trafiğin düşmesiyle hemen scale down yapar, trafik tekrar artınca yavaşça scale up eder ve bu arada kullanıcılar yüksek latency yaşar. 5 dakikalık bekleme penceresi bu dalgalanmayı önler.

Node Affinity ve Pod Anti-Affinity

Kubernetes cluster’ınızda node’larınız arasında yük dengesini sağlamak için affinity kuralları kritik.

# anti-affinity ile pod dağılımı
spec:
  affinity:
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: app
            operator: In
            values:
            - web-api
        topologyKey: kubernetes.io/hostname
    nodeAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        preference:
          matchExpressions:
          - key: node-type
            operator: In
            values:
            - compute-optimized

Bu konfigürasyonla aynı uygulamanın pod’larının farklı node’lara dağılması zorunlu hale gelir. Bir node çökerse diğer pod’lar etkilenmez. Gerçek bir prodüksiyon felaketini önlemenin en basit yolu bu.

Kubernetes Performans Analizi Araçları

Sorun yaşandığında hızlıca analiz yapabilmek için araç setinizin hazır olması gerekiyor.

# Pod kaynak kullanımını node bazında görüntüleme
kubectl top nodes
kubectl top pods -n production --sort-by=cpu
kubectl top pods -n production --sort-by=memory

# Belirli bir pod'un detaylı event'lerini inceleme
kubectl describe pod web-api-7d9f6b8c4-xk2p9 -n production

# Pod loglarını canlı takip etme (son 100 satır)
kubectl logs -f web-api-7d9f6b8c4-xk2p9 -n production --tail=100

# Crash loop'taki pod'un önceki container loglarını görme
kubectl logs web-api-7d9f6b8c4-xk2p9 -n production --previous

# Namespace bazında tüm kaynakların durumu
kubectl get all -n production -o wide

# Pod'un hangi node'da çalıştığını ve kaynak kullanımını kontrol etme
kubectl get pod web-api-7d9f6b8c4-xk2p9 -n production -o jsonpath=
'{.spec.nodeName}{"n"}{.status.containerStatuses[0].restartCount}{"n"}'

Bir prodüksiyon ortamında sabah 3’te alarm aldığınızda bu komutları ezberden çalıştırabilmeniz gerekiyor. Her saniye önemli.

cgroups ile Sistem Düzeyinde Analiz

Konteyner performansı sorunları bazen Docker ve Kubernetes katmanının altındaki cgroups seviyesinde analiz gerektirir.

# Docker konteyner'ının cgroup bilgilerini bulma
CONTAINER_ID=$(docker inspect --format='{{.Id}}' my-container)
CGROUP_PATH="/sys/fs/cgroup/cpu/docker/${CONTAINER_ID}"

# CPU throttling istatistikleri
cat /sys/fs/cgroup/cpu/docker/${CONTAINER_ID}/cpu.stat
# nr_throttled ve throttled_time değerlerine bakın

# Bellek kullanım detayları
cat /sys/fs/cgroup/memory/docker/${CONTAINER_ID}/memory.stat

# Kubernetes pod için cgroup path
# /sys/fs/cgroup/cpu/kubepods/besteffort/pod<POD_UID>/
ls /sys/fs/cgroup/cpu/kubepods/guaranteed/

# CPU throttling yüzdesi hesaplama scripti
#!/bin/bash
CONTAINER_ID=$1
CGROUP="/sys/fs/cgroup/cpu/docker/${CONTAINER_ID}"

read NR_PERIODS NR_THROTTLED THROTTLED_TIME <<< $(awk 
    '/nr_periods|nr_throttled|throttled_time/ {print $2}' 
    ${CGROUP}/cpu.stat | tr 'n' ' ')

if [ "$NR_PERIODS" -gt 0 ]; then
    THROTTLE_PCT=$(echo "scale=2; $NR_THROTTLED * 100 / $NR_PERIODS" | bc)
    echo "Throttling: ${THROTTLE_PCT}%"
    echo "Toplam throttled süre: $((THROTTLED_TIME / 1000000)) ms"
fi

CPU throttling oranı %5’in üzerindeyse ciddi performans sorununuz var demektir. Bu analizi yapmak Kubernetes metrics-server’ın görmediği sorunları ortaya çıkarır.

Network Performansı ve Service Mesh

Konteyner ortamlarında network gecikmeleri çok kolay gözden kaçar. Servisler arası iletişim yavaşladığında ilk bakılacak yer network katmanı.

# Kubernetes servisler arası network latency testi
kubectl run nettest --image=nicolaka/netshoot -it --rm -- 
    bash -c "for i in {1..10}; do curl -s -o /dev/null -w '%{time_total}n' 
    http://web-api-service.production.svc.cluster.local/health; done"

# DNS çözümleme süresini ölçme
kubectl run dnstest --image=nicolaka/netshoot -it --rm -- 
    bash -c "for i in {1..5}; do time nslookup 
    web-api-service.production.svc.cluster.local; done"

# iptables kurallarının performans etkisini görme
iptables -t nat -L -n --line-numbers | grep -c "KUBE"
# Bu sayı çok yüksekse (1000+) kube-proxy IPVS moduna geçmeyi düşünün

# kube-proxy IPVS moduna geçiş kontrolü
kubectl get configmap kube-proxy -n kube-system -o yaml | grep mode

Büyük cluster’larda iptables tabanlı kube-proxy ciddi performans sorunu yaratır. IPVS moduna geçmek network latency’yi önemli ölçüde düşürür.

Prometheus ve Grafana ile Konteyner Monitoring

Gerçek anlamda performans yönetimi için metrik toplama ve görselleştirme şart. Prometheus + Grafana stack’i bu iş için endüstri standardı haline geldi.

# prometheus-rules.yaml - Kritik uyarı kuralları
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: container-performance-alerts
  namespace: monitoring
spec:
  groups:
  - name: container.rules
    rules:
    - alert: ContainerHighCPUThrottling
      expr: |
        rate(container_cpu_cfs_throttled_seconds_total[5m]) /
        rate(container_cpu_cfs_periods_total[5m]) > 0.25
      for: 5m
      labels:
        severity: warning
      annotations:
        summary: "Container CPU throttling yüksek"
        description: "{{ $labels.container }} konteyneri %{{ $value | humanizePercentage }} throttling yaşıyor"

    - alert: ContainerMemoryNearLimit
      expr: |
        container_memory_working_set_bytes /
        container_spec_memory_limit_bytes > 0.90
      for: 5m
      labels:
        severity: critical
      annotations:
        summary: "Konteyner bellek limiti yaklaşıyor"
        description: "{{ $labels.container }} belleğinin %90'ını kullanıyor, OOM riski var"

    - alert: PodRestartingFrequently
      expr: |
        rate(kube_pod_container_status_restarts_total[15m]) > 0
      for: 15m
      labels:
        severity: warning
      annotations:
        summary: "Pod sürekli yeniden başlıyor"
        description: "{{ $labels.pod }} pod'u son 15 dakikada yeniden başladı"

Bu alertler prodüksiyon ortamında sorunları oluşmadan önce yakalamanızı sağlar. CPU throttling uyarısı özellikle değerli çünkü metrics-server CPU kullanımını olduğundan düşük gösterebilir.

Pratik Optimizasyon Senaryosu: API Servisi

Gerçek bir senaryo üzerinden gidelim. Bir microservice API’si var, response time’ları artmış, intermittent timeout’lar yaşanıyor.

Adım adım analiz:

  • 1. Adım: kubectl top pods ile CPU ve memory baseline alın
  • 2. Adım: kubectl describe pod ile event’leri kontrol edin, OOMKilled veya throttling var mı?
  • 3. Adım: cgroup stats ile CPU throttling oranını hesaplayın
  • 4. Adım: Application metrics’e bakın, hangi endpoint yavaş?
  • 5. Adım: Network latency testi yapın, DNS çözümleme sorunlu mu?
  • 6. Adım: Node kaynak kullanımını kontrol edin, noisy neighbor problemi var mı?

Bu analiz sırasında en sık karşılaştığım sorun yanlış ayarlanmış CPU limitleri. CPU limit’i requests’e çok yakın ayarladığınızda normal trafik dalgalanmalarında bile throttling başlıyor. Genel kural olarak CPU limits’i requests’in 2-3 katı yapın. Memory için ise OOM riski nedeniyle limits’i requests’e daha yakın tutun, ama requests’i gerçekçi ölçümlerle belirleyin.

Init Container ve Startup Probe Optimizasyonu

Konteyner başlangıç süresi prodüksiyonda çok önemli. Yavaş başlayan pod’lar rolling update’leri uzatır, ölçeklendirme geç kalır.

spec:
  initContainers:
  - name: wait-for-db
    image: busybox:1.35
    command: ['sh', '-c', 
      'until nc -z postgres-service 5432; do echo waiting; sleep 2; done']
    resources:
      requests:
        cpu: "10m"
        memory: "16Mi"
      limits:
        cpu: "50m"
        memory: "32Mi"
  containers:
  - name: web-api
    startupProbe:
      httpGet:
        path: /health
        port: 8080
      failureThreshold: 30
      periodSeconds: 10
    livenessProbe:
      httpGet:
        path: /health
        port: 8080
      initialDelaySeconds: 0
      periodSeconds: 10
      timeoutSeconds: 5
      failureThreshold: 3

StartupProbe kullanmak kritik bir optimizasyon. Bu probe başarılı olana kadar liveness probe devreye girmiyor. Yavaş başlayan uygulamalar liveness probe başarısızlığı nedeniyle sürekli yeniden başlamak yerine, başlangıç sürecini tamamlayabiliyor. failureThreshold: 30 ve periodSeconds: 10 ile uygulamaya 300 saniye başlangıç süresi tanıyorsunuz.

Garbage Collection ve JVM Konteynerleri

Java uygulamaları konteyner ortamında özel dikkat gerektirir. Eski JVM sürümleri container CPU limitlerini göremez ve tüm host CPU’larını sayar.

# JVM 17+ için container-aware ayarlar
docker run -d 
    --name java-app 
    --cpus="2.0" 
    --memory="2g" 
    -e JAVA_OPTS="-XX:+UseContainerSupport 
                  -XX:MaxRAMPercentage=75.0 
                  -XX:InitialRAMPercentage=50.0 
                  -XX:+UseG1GC 
                  -XX:MaxGCPauseMillis=200 
                  -XX:+PrintGCDetails 
                  -Xlog:gc*:file=/tmp/gc.log:time,uptime:filecount=5,filesize=20m" 
    my-java-app:latest

# Kubernetes için JVM environment
env:
- name: JAVA_OPTS
  value: >-
    -XX:+UseContainerSupport
    -XX:MaxRAMPercentage=75.0
    -XX:+UseG1GC
    -XX:MaxGCPauseMillis=200
- name: JVM_MAX_HEAP
  valueFrom:
    resourceFieldRef:
      resource: limits.memory
      divisor: 1Mi

-XX:+UseContainerSupport ve -XX:MaxRAMPercentage=75.0 kombinasyonu JVM’in container memory limit’inin %75’ini kullanmasını sağlar. Kalan %25 JVM overhead, metaspace ve native memory için. Bu olmadan ya OOM yaşarsınız ya da JVM’e çok az heap verip GC thrashing’e düşersiniz.

Sonuç

Konteyner performans optimizasyonu tek seferlik bir görev değil, sürekli bir süreç. Prodüksiyona aldığınız her uygulama için kaynak limitlerini gerçekçi yük testleriyle belirleyin, CPU throttling’i düzenli kontrol edin ve Prometheus alertlerini erkenden kurun.

En önemli takeaway’ler şunlar:

  • Kaynak limitleri olmadan prodüksiyona çıkmayın, ama yanlış ayarlanmış limitler bazen limitsiz çalışmaktan daha kötü sonuç verir
  • CPU throttling en sessiz performans katili, cgroup stats ile düzenli kontrol edin
  • Multi-stage Docker build ile başlayın, küçük image her şeyi iyileştirir
  • HPA scale-down davranışını stabilizasyon penceresiyle yavaşlatın
  • requests ve limits arasındaki farkı anlayın, QoS class’ları önemli
  • JVM uygulamaları için container-aware JVM flaglerini mutlaka kullanın
  • StartupProbe kullanarak yavaş başlayan uygulamaları false positive OOM’dan koruyun

Performans optimizasyonu veri odaklı bir iş. Ölçmeden optimize etmeye çalışmak karanlıkta hedef aramak gibi. Önce ölçün, sonra optimize edin, ardından tekrar ölçün. Bu döngüyü iyi kurarsanız konteyner ortamınız hem güvenilir hem de verimli çalışır.

Similar Posts

Bir yanıt yazın

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