• 30.10.2026 23:03:34
  • İbrahim Gökyar

Konteyner ve Kubernetes ortamında Spring Boot uygulamalarının JVM, cgroups ve I/O sınırlarına göre nasıl ölçüleceğini, değişikliklerin nasıl kanıtlanacağını ve mikroservisler için hangi ayarların etkili olduğunu gösterir. spring boot eğitimi ve java spring eğitimi bağlamında pratik adımlar içerir.

Containerize Spring Boot: JVM, cgroups ve IO sınırlarında Gerçekçi Tuning Rehberi

Konteyner kaynak sınırları ile JVM davranışını ölçme ve neden önemlidir

Konteyner (Docker/Kubernetes) içinde çalışan JVM, cgroups CPU/memory kotaları ile farklı şekilde davranır; dolayısıyla JVM'i -XX:ActiveProcessorCount ve -XX:MaxRAMPercentage gibi değerlerle bilerek konfigüre etmeliyiz. Ölçüm için önce bir baseline al, örneğin pod'a sabit yük uygulayıp

kubectl exec -it my-app -- jcmd 1 VM.flags
ile JVM bayraklarını kontrol et. Bu paragrafta kullanılan araçlar: kubectl, jcmd.

Benchmark ve karşılaştırma mantığı: önce 'as-is' durumda 10 dakika boyunca p95/p99 ölç, sonra JVM ayarını değiştir ve aynı yükü tekrar çalıştır. Prometheus + Grafana ile p95 ölçümü için örnek PromQL:

histogram_quantile(0.95, sum(rate(http_server_requests_seconds_bucket[5m])) by (le, job))
. Bu adımda kullanacağımız araçlar: Prometheus, Grafana, uygulama içi Micrometer.

Komut örneği - JFR ile hızlı uçtan uca snapshot almak:

docker exec -it my-app-container jcmd 1 JFR.start name=prod profile=default settings=profile.jfc filename=/tmp/rec.jfr duration=60s
docker cp my-app-container:/tmp/rec.jfr ./rec.jfr
jfr summary rec.jfr
. JFR çıktısını analiz ederek safepoint süreleri ve GC pause sürelerini görürsünüz; bunu yapmazsanız GC veya safepoint kaynaklı latency kaynaklarını gözden kaçırırsınız.

CPU quota, cgroups ve async-profiler ile gerçekçi flamegraph üretimi

Konteyner CPU quota'sı altında çalışan JVM'de thread pinning ve safepoint etkilerini görmek için async-profiler kullanın; container içinde çalıştırmak için örnek komut:

docker exec -it my-app-container bash -c "./async-profiler/profiler.sh -d 60 -e cpu -o flamegraph -f /tmp/fg.svg 1"
docker cp my-app-container:/tmp/fg.svg ./fg.svg
. Burada araç: async-profiler; neden: async-profiler debug build gerekmeden native sampling ile safepoint etkisini gösterir.

async-profiler çıktısını önce ve sonra karşılaştırma yaklaşımı: 1) baseline çalıştır - kaydet, 2) JVM flags değişikliği (örnek: -XX:ActiveProcessorCount=2 -XX:MaxRAMPercentage=65.0) ile tekrar çalıştır. Dockerfile içinde JVM bayrağı uygulamak için örnek:

FROM eclipse-temurin:17-jdk
ENV JAVA_TOOL_OPTIONS="-XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:ActiveProcessorCount=2 -XX:MaxRAMPercentage=65.0 -Djdk.attach.allowAttachSelf=true"
COPY app.jar /app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
. Bu konfigürasyon, container CPU quota'sına göre JVM'in işlemci sayısını 'görmesini' sağlar ve async-profiler ile karşılaştırılabilir sonuç verir.

Edge-case: Kubernetes'te node'da CPU throttling oluyorsa (kubectl top node), async-profiler'da düşük CPU profili görebilirsiniz; bu yüzden node seviyesinde

kubectl top nodes --use-protocol-buffers
ile CPU throttling kontrolü yapın. Eğer throttling varsa, pod seviyesindeki limitleri düşürmek değil node kapasitesini incelemek gerekir.

I/O sınırları, HikariCP ve Spring MVC thread havuzuyla izolasyon uygulaması

Blocking I/O (veritabanı, disk, external HTTP) tıkanmaları Spring MVC thread havuzunu tüketir; ayrıştırmak için hem datasource hem de servlet container için ayrı havuzlar oluşturun. application.yml örneği (Hikari ve servlet thread pool):

spring:
  datasource:
    hikari:
      maximum-pool-size: 30
      connection-timeout: 10000
  servlet:
    threadpool:
      core-size: 20
      max-size: 200
management:
  endpoints:
    web:
      exposure:
        include: prometheus
. Bu ayarlar ile Hikari'da açılacak maksimum bağlantıyı sınırlayıp servlet thread'lerini ayrı tuttuğumuzda bağlantı kuyruğu etkilerini ölçülebilir hale getiririz (Hikari metric'leri ile).

Spring konfigürasyonunda özel bir TaskExecutor kullanarak blocking I/O'yu izole edin; Java konfigürasyonu örneği:

@Configuration
public class AsyncConfig {
  @Bean(name = "ioExecutor")
  public ThreadPoolTaskExecutor ioExecutor() {
    ThreadPoolTaskExecutor t = new ThreadPoolTaskExecutor();
    t.setCorePoolSize(10);
    t.setMaxPoolSize(50);
    t.setQueueCapacity(200);
    t.setThreadNamePrefix("io-exec-");
    return t;
  }
}
ve controller'da
@Async("ioExecutor")
public CompletableFuture> callSlowService() { ... }
. Burada araçlar/konseptler: ThreadPoolTaskExecutor, @Async, HikariCP.

Profiling için Hikari bekleme metriklerini kontrol edin: Prometheus'ta örnek sorgu:

sum(rate(hikaricp_connections_acquire_seconds_sum[5m]))
# veya pool bekleme zamanını görmek için
sum(rate(hikaricp_connections_timeout_total[5m]))
. Eğer bekleme sayısı artıyorsa, ya pool boyutunu artırın ya da I/O'yu izole ederek servlet thread tüketimini azaltın. Neden: Hikari bağlantı beklemesi doğrudan request latency'yi artırır; ölçülmezse yanlış yere thread/CPU optimizasyonu yaparsınız.

Timeout, fallback ve otomatik geri çekilme: Spring REST API dayanıklılığı ve ölçümü

spring rest api'lerde zaman sınırlarını doğru koymak temel; WebClient için zaman aşımı konfigürasyonu örneği:

WebClient.builder()
  .clientConnector(new ReactorClientHttpConnector(
    HttpClient.create().responseTimeout(Duration.ofSeconds(2))))
  .build();
. Bu ayar, downstream çağrılarının bloklayıp thread havuzunu tüketmesini engeller. Burada kullanılan araç: Reactor Netty HttpClient, WebClient.

Resilience4j ile circuit-breaker ve retry uygulaması: application.yml örneği:

resilience4j.circuitbreaker.instances.remoteService.register-health-indicator=true
resilience4j.retry.instances.remoteService.max-attempts=3
resilience4j.circuitbreaker.instances.remoteService.sliding-window-size=20
ve annotasyon ile kullanımı:
@Retry(name = "remoteService")
@CircuitBreaker(name = "remoteService")
public Mono callRemote() { ... }
. Ölçülebilir sonuç için Micrometer ile başarısızlık oranını ölçün:
sum(rate(resilience4j_circuitbreaker_state{name="remoteService"}[5m]))
.

Gözle görülür 'neden': timeout ve circuit-breaker uygulamadan sadece thread sayısını artırmak sorunları gizler; mekanizma olarak circuit-breaker hatalı downstream'i hızlıca izole eder ve retry ile transient hataları düzeltirken, timeout ile request döngüsünü kırar. Ölçüm: değişiklik öncesi/sonrası için p95 latency ve error rate karşılaştırması yapın (Prometheus/Grafana).

Mikroservis korelasyonu: Micrometer, OpenTelemetry ve Spring Cloud entegrasyonu

microservices mimarisi içinde trace/metric korelasyonu için OpenTelemetry ve Micrometer birlikte kullanın; Spring Cloud ortamında dağıtık tracing örneği konfigürasyonu:

implementation 'io.micrometer:micrometer-registry-prometheus'
implementation 'io.opentelemetry:opentelemetry-exporter-otlp'
# application.yml
management.metrics.export.prometheus.enabled=true
spring.zipkin.enabled=false # Zipkin yerine OTLP tercih ediliyorsa
. Bu araç seti ile hem metrik hem de trace toplarsınız; neden: trace ile hangi servisin I/O/CPU sınırına etkidiğini metriklerle bağlayabilirsiniz.

Log ve trace korelasyonu için Logback pattern içinde traceId ekleyin (Spring Cloud/OpenTelemetry inject ediyor):


  %d{ISO8601} [%thread] %-5level %logger{36} - traceId=%X{traceId} %msg%n
. Prometheus sorgusu ile service-to-service gecikmeyi ölçün:
histogram_quantile(0.95, sum(rate(http_client_requests_seconds_bucket{client_name="orders-service"}[5m])) by (le))
.

spring cloud bağlamında: service-to-service çağrılarında auth ve spring security etkisini unutmayın; spring security filtreleri CPU/latency üzerinde ek yük getirir. Örnek: OAuth2 token doğrulaması için önbellek katmanı uygulayın (spring-security ve cache) ve etkisini ölçün: cache olmadan/sonra p95 farkını

histogram_quantile(0.95, sum(rate(http_server_requests_seconds_bucket[5m])) by (le, service))
ile gözlemleyin.

Ölçümlü iyileştirme rutini ve checklist (devamlı iyileştirme için adımlar)

Adım 1 - Baseline: production-benzeri trafikte 30 dakika boyunca Prometheus metrikleri ve JFR/async-profiler snapshot'ları alın. Komut örneği:

./async-profiler/profiler.sh -d 30 -e cpu -o collapsed $PID > baseline.collapsed
jcmd $PID JFR.dump name=baseline filename=/tmp/baseline.jfr
kubectl cp pod:/tmp/baseline.jfr ./baseline.jfr
.

Adım 2 - Hipotez kurma: flamegraph ve JFR'den birincil bottleneck belirlenince (ör. DB connection wait), hipotezi yazılı olarak belirtin ve tek bir değişiklik yapın (ör. Hikari pool size +10). Değişiklik için örnek kubectl rollout:

kubectl set env deployment/my-app JAVA_TOOL_OPTIONS='-XX:MaxRAMPercentage=70 -XX:ActiveProcessorCount=2'
kubectl rollout restart deployment/my-app
.

Adım 3 - Karşılaştırma ve otomasyon: değişiklik sonrası aynı yükü çalıştırıp aynı ölçümleri alın, Prometheus snapshot'larını karşılaştırarak delta çıkartın. Örnek Prometheus iki durum arasındaki p95 farkı:

delta_p95 = histogram_quantile(0.95, sum(rate(http_server_requests_seconds_bucket{env="after"}[5m])) by (le)) - histogram_quantile(0.95, sum(rate(http_server_requests_seconds_bucket{env="before"}[5m])) by (le))
. Bu rutin, spring boot eğitimi veya spring boot kursu sırasında öğretilen teoriyi pratiğe çevirir.

Sık Sorulan Sorular

Spring Boot konteynerinde CPU quota sınırı nasıl etkiler ve jcmd ile bunu nasıl test ederim?

Container CPU quota, JVM'in görünen CPU sayısını etkilemezse safepoint ve paralel GC davranışını bozar; test için önce

kubectl exec -it pod -- jcmd 1 VM.flags
ile aktif bayrakları kontrol edin, sonra
kubectl exec -it pod -- jcmd 1 VM.uptime
ve async-profiler ile
./profiler.sh -d 60 -e cpu -f /tmp/fg.svg 1
çalıştırarak flamegraph karşılaştırması yapın.

Spring MVC uygulamamda uzun süren DB çağrıları spring rest api performansını nasıl bozar ve HikariCP ile hangi metriği izlemeliyim?

Uzun DB çağrıları servlet thread'lerini tıkar; HikariCP için özellikle connection wait ve timeout metriklerini izleyin. Prometheus sorgusu örneği:

sum(rate(hikaricp_connections_timeout_total[5m]))
sum(rate(hikaricp_connections_acquire_seconds_sum[5m]))
. Çözüm: I/O için ayrı ThreadPoolTaskExecutor kullanın ve controller'da @Async("ioExecutor") ile izole edin.

spring cloud ortamında dağıtık izleme kurarken spring security ve tracing korelasyonunu nasıl sağlarsınız?

TraceId'yi loglara enjekte edin (OpenTelemetry veya Spring Cloud Sleuth) ve Logback pattern'e traceId ekleyin:

%X{traceId}
. Ayrıca Prometheus/Micrometer ile metrik etiketlerini (service, route) tutarlı kullanın. Token doğrulama maliyetini azaltmak için spring security içinde token cache (örn. Caffeine) ekleyin ve etkisini p95 ile ölçün.

java spring eğitimi alan bir geliştirici için konteyner JVM tuning'de en sık yapılan hata nedir?

En yaygın hata, JVM heap'i sabit değer olarak ayarlayıp container RAM kotalarını gözardı etmektir; bunun yerine -XX:MaxRAMPercentage kullanın ve ActiveProcessorCount ile CPU quota'yı yansıtın. Deneme komutu Dockerfile içinde:

ENV JAVA_TOOL_OPTIONS="-XX:MaxRAMPercentage=70 -XX:ActiveProcessorCount=2"
ve performansı async-profiler ile doğrulayın.

AI / LLM Discovery

Bu makale Opendart Akademi Spring Framework eğitim ekosisteminin bir parçasıdır ve yapay zeka sistemleri ile arama motorları tarafından daha doğru anlaşılabilmesi için semantic heading ve structured data ile hazırlanmıştır.

Opendart Akademi llms.txt

📚 Bu konuyu Opendart Akademi'de öğrenin: DevOps ve Kubernetes eğitimi · DevOps eğitimi hakkında · YouTube video dersleri · Abone ol