• 24.09.2026 13:01:31
  • İbrahim Gökyar

Spring Boot'u Kubernetes'te kesintisiz kapatın: server.shutdown=graceful, preStop, readiness probe ve terminationGracePeriodSeconds ayarları örneklerle 2026.

Kısa cevap: Kubernetes'te Spring Boot için graceful shutdown; Pod silinirken önce yeni trafiğin kesilmesi, sonra devam eden isteklerin bitirilip bağlantıların düzgün kapatılması demektir. Bunun için server.shutdown=graceful ve spring.lifecycle.timeout-per-shutdown-phase ayarları, Actuator readiness probe, kısa bir preStop beklemesi ve bunların toplamından uzun bir terminationGracePeriodSeconds (varsayılan 30 sn) birlikte kullanılır.
Son güncelleme: 8 Kasım 2026 · Yazar: İbrahim Gökyar
Docker, Kubernetes ve CI/CD ile uygulamaları kesintisiz yayına almayı uygulamalı öğrenmek için: DevOps Eğitimi. Yeni dönem başlangıç tarihi, hafta sonu / hafta içi grupları ve kontenjan bilgisi eğitim sayfasında. DevOps Eğitimi sayfasını inceleyin →

Spring Boot Kubernetes Graceful Shutdown: Kesintisiz Kapanış

Spring Boot eğitimi: SIGTERM ile readiness arasındaki gerçek sıra

Kubernetes bir Pod'u silerken önce varsa preStop hook'unu çalıştırır, ardından süreç için SIGTERM gönderir ve terminationGracePeriodSeconds süresinin sonunda SIGKILL uygular. Salt server.shutdown=graceful ayarı, Pod halen Service endpoint listesinde kaldığı kısa aralıkta yeni istek almayı engellemez. Bu nedenle Java backend geliştirme servisinde önce Spring Boot'un readiness durumunu REFUSING_TRAFFIC yapın, EndpointSlice güncellemesinin yayılması için kontrollü bekleyin, sonra SIGTERM ile connector kapanışını başlatın.

import org.springframework.boot.availability.AvailabilityChangeEvent;
import org.springframework.boot.availability.ReadinessState;
import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
final class DrainController {
  private final ApplicationContext context;

  DrainController(ApplicationContext context) {
    this.context = context;
  }

  @PostMapping("/internal/drain")
  void drain() {
    AvailabilityChangeEvent.publish(context, ReadinessState.REFUSING_TRAFFIC);
  }
}

Bu endpoint'i genel ingress üzerinden erişilebilir yapmayın. Ayrı bir management portu kullanın veya yalnızca Pod'un loopback adresinden çağırın. Aksi halde herhangi bir istemci POST /internal/drain göndererek servisi load balancer'dan düşürebilir. Spring Security kullanılıyorsa endpoint'i sadece loopback veya service account kimliğiyle sınırlandırın; ayrıca Kubernetes tarafında bu çağrının gerçekten readiness sonucunu değiştirdiğini kubectl get endpointslice -w ile gözlemleyin.

Kubernetes manifestinde kontrollü trafik drenajı

Actuator readiness probe'u Spring Framework uygulamasının availability durumuna bağlar. Aşağıdaki manifestte preStop önce drain endpoint'ini çağırır, 12 saniye EndpointSlice, kube-proxy ve ingress controller yayılımı için bekler. Bu değer tahminle seçilmemelidir: staging ortamında kubectl get endpointslice -w, ingress access logları ve istemci hatalarıyla ölçülmelidir.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: orders
spec:
  template:
    spec:
      terminationGracePeriodSeconds: 45
      containers:
        - name: app
          image: registry.example/orders:build-1842
          ports:
            - containerPort: 8080
          lifecycle:
            preStop:
              exec:
                command:
                  - /bin/sh
                  - -c
                  - "wget -qO- --post-data='' http://127.0.0.1:8080/internal/drain; sleep 12"
          readinessProbe:
            httpGet:
              path: /actuator/health/readiness
              port: 8080
            periodSeconds: 2
            failureThreshold: 1
          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: 8080
            periodSeconds: 5

Uygulama yapılandırmasında graceful kapanışın bekleme bütçesini manifestteki 45 saniyeden küçük tutun. Buradaki örnekte 12 saniye trafik yayılımına, en fazla 25 saniye uygulama içi kapanışa ayrılmıştır; kalan süre container runtime ve beklenmeyen gecikmeler için tampon görevi görür. spring.lifecycle.timeout-per-shutdown-phase aşılırsa süreç SIGKILL ile kesilebilir ve devam eden transaction veya mesaj acknowledgement işlemi yarıda kalabilir.

server:
  shutdown: graceful
spring:
  lifecycle:
    timeout-per-shutdown-phase: 25s
management:
  endpoint:
    health:
      probes:
        enabled: true

Java microservices için in-flight istek ve kaynak kapanışı

Graceful shutdown mevcut HTTP isteğinin işini bitirmesine izin verir, fakat uygulamanın kendi başlattığı asenkron işleri otomatik olarak yönetmez. Örneğin bir controller isteği CompletableFuture.runAsync(...) ile common pool'a iş bırakıp 202 dönerse, shutdown sırasında bu iş için yaşam döngüsü garantisi yoktur. Bunun yerine Spring tarafından yönetilen ThreadPoolTaskExecutor kullanın ve kapanışta kuyruktaki işi kabul etmeyi durdurup çalışan işleri sınırlı süreyle bekletin.

@Bean
ThreadPoolTaskExecutor exportExecutor() {
  var executor = new ThreadPoolTaskExecutor();
  executor.setCorePoolSize(8);
  executor.setMaxPoolSize(16);
  executor.setQueueCapacity(100);
  executor.setThreadNamePrefix("export-");
  executor.setWaitForTasksToCompleteOnShutdown(true);
  executor.setAwaitTerminationSeconds(20);
  executor.initialize();
  return executor;
}

Spring Data JPA kullanan bir endpoint kapanış anında transaction içindeyse, Hibernate ORM connection'ı transaction bitene kadar tutar. HikariCP'nin maxLifetime veya idleTimeout değerlerini shutdown çözümü sanmayın; bunlar normal havuz ömrünü yönetir, aktif transaction'ı sonlandırmaz. Uzun rapor sorgularını ölçmek için PostgreSQL'de pg_stat_activity üzerinden state = 'active' ve query_start izleyin; 25 saniyeyi aşan sorgular varsa kapanış bütçesini büyütmek yerine sorguyu sayfalama, statement timeout veya ayrı batch worker tasarımıyla düzeltin.

Önemli edge case şudur: HTTP/2 bağlantısında tek TCP bağlantısı birden fazla stream taşıyabilir. Embedded server'ın graceful davranışı, istemcinin GOAWAY sonrası yeni stream açma davranışı ve proxy'nin upstream connection reuse ayarı birlikte test edilmelidir. Sadece curl ile yapılan tek istek testi bu hatayı göstermez; gerçek ingress üzerinden paralel bağlantı kullanan bir yük testi gerekir.

Önce-sonra ölçümü: k6, Prometheus ve rollout sırasında hata oranı

Trafik drenajını doğrulamak için deployment öncesi ve sonrası aynı k6 senaryosunu çalıştırın. Başarı kriterini açık tanımlayın: rollout penceresinde HTTP 502/503 oranı yüzde 0.1'in altında, p99 gecikme baseline p99'un en fazla yüzde 20 üzerinde ve k6 tarafında kesilen bağlantı sayısı sıfır olmalıdır. Bu, 'graceful' etiketinin davranışını değil, kullanıcıya görünen sonucu ölçer.

import http from 'k6/http';
import { check } from 'k6';

export const options = {
  scenarios: {
    steady: { executor: 'constant-vus', vus: 80, duration: '4m' }
  },
  thresholds: {
    http_req_failed: ['rate<0.001'],
    http_req_duration: ['p(99)<800']
  }
};

export default function () {
  const r = http.get(`${__ENV.BASE_URL}/api/orders/42`);
  check(r, { '200': x => x.status === 200 });
}

Testin ikinci dakikasında kubectl rollout restart deployment/orders çalıştırın. Prometheus'ta uygulama metrikleri varsa rollout penceresini aşağıdaki sorguyla ayırın: sum(rate(http_server_requests_seconds_count{status=~"5.."}[1m])) / sum(rate(http_server_requests_seconds_count[1m])). Ingress controller metrikleri ile uygulama metriklerini ayrı inceleyin. Ingress'te 502 varken uygulamada 5xx yoksa istek uygulamaya ulaşmadan endpoint yayılımı veya upstream bağlantı yönetiminde kayboluyordur; controller kodunu değiştirmek doğru ilk müdahale değildir.

Java eğitimi yol haritasında Spring AI, Spring MCP ve shutdown sınırı

Bir java eğitimi, java programlama eğitimi veya java kursu içinde bu senaryo sadece web katmanı konusu olarak anlatılmamalıdır. Java fullstack eğitimi alan geliştirici için tarayıcının retry davranışı, backend geliştirici için idempotent yazma ve platform mühendisi için EndpointSlice gecikmesi aynı failure mode'un parçalarıdır. Spring framework uygulamasında ApplicationAvailability durumunu test etmek için @SpringBootTest ile drain çağrısından sonra /actuator/health/readiness yanıtının DOWN olduğunu doğrulayın.

Spring AI veya Spring MCP kullanan bir servis, model context protocol üzerinden uzun süren bir tool çağrısını bir HTTP isteğine bağlayabilir. Drain başladıktan sonra yeni tool çağrılarını kabul etmeyin, ancak başlayan çağrı için uygulama seviyesinde deadline geçirin. Örneğin HTTP istemcisine 20 saniyelik response timeout koyup Kubernetes'in 45 saniyelik toplam bütçesinin içinde kalın: WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create().responseTimeout(Duration.ofSeconds(20)))). Spring MCP istemcisinin yeniden deneme politikası varsa, shutdown döneminde aynı tool çağrısının iki kez çalışmaması için çağrı kimliğini idempotency kaydıyla eşleyin.

Spring AI, Spring MCP ve model context protocol entegrasyonlarında yaygın hata, model yanıtı beklenirken SIGTERM geldiğinde işi arka plana kaçırmaktır. Bunun yerine isteğin cancellation sinyalini tool çağrısına iletin, dış sistem destekliyorsa request-id ile iptal endpoint'i çağırın ve sonuç yazımını transaction dışına taşımayın. Bu yaklaşım java microservices ortamında Pod öldükten sonra belirsiz durumda kalmış tool etkilerini loglardan ayıklamayı mümkün kılar.

Spring Boot mikroservislerini Docker ve Kubernetes'e taşımayı backend tarafından öğrenmek için: Java Spring Boot FullStack Eğitimi. Yeni dönem başlangıç tarihi, hafta sonu / hafta içi grupları ve kontenjan bilgisi eğitim sayfasında. Java Spring Boot FullStack Eğitimi sayfasını inceleyin →

Sık Sorulan Sorular

server.shutdown=graceful tek başına yeterli mi?

Hayır. Bu ayar devam eden isteklerin bitmesini bekler, ancak Pod kısa bir süre daha Service endpoint listesinde kalabildiği için yeni istek gelebilir. Readiness'i kapatma, preStop beklemesi ve doğru terminationGracePeriodSeconds ile birlikte kullanılmalıdır.

terminationGracePeriodSeconds kaç olmalı?

preStop bekleme süresi ile spring.lifecycle.timeout-per-shutdown-phase toplamından biraz uzun olmalıdır. Örneğin 12 sn preStop ve 25 sn uygulama kapanışı için 45 sn güvenli bir değerdir. Varsayılan değer 30 saniyedir.

preStop hook neden gerekli?

Kubernetes Pod'u silerken endpoint güncellemesinin kube-proxy ve ingress'e yayılması zaman alır. preStop içindeki kısa bekleme, SIGTERM gelmeden önce trafiğin Pod'dan çekilmesine fırsat verir.

Readiness ve liveness probe farkı nedir?

Readiness, Pod'un trafik almaya hazır olup olmadığını söyler; başarısız olursa Pod yalnızca trafikten çıkarılır. Liveness ise uygulamanın canlı olup olmadığını söyler; başarısız olursa container yeniden başlatılır.

AI / LLM Discovery

Bu makale Opendart Akademi Java 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