Eğitim Portalı/Spring Boot/Actuator ve Üretim Araçları
Spring Boot03-spring-boot/06-actuator-ve-uretim

Actuator ve Üretim Araçları

Bir uygulamayı yazmak işin yarısıdır; onu üretimde çalışır, izlenebilir ve yönetilebilir tutmak diğer yarısıdır. "Uygulama ayakta mı? Belleği ne durumda? Saniyede kaç sipariş geliyor?" sorularını cevaplamak gerekir. S…

Actuator ve Üretim Araçları

Bir uygulamayı yazmak işin yarısıdır; onu üretimde çalışır, izlenebilir ve yönetilebilir tutmak diğer yarısıdır. "Uygulama ayakta mı? Belleği ne durumda? Saniyede kaç sipariş geliyor?" sorularını cevaplamak gerekir. Spring Boot Actuator, bunları hazır endpoint'lerle sağlar; bu bölümde Actuator'ı ve uygulamayı üretime taşıyan diğer araçları (API dokümantasyonu, Docker, mikroservisler) ele alıyoruz.

Actuator: hazır izleme endpoint'leri

spring-boot-starter-actuator bağımlılığı, uygulamaya bir dizi yönetim endpoint'i ekler. Bunlar güvenlik için varsayılan olarak kısıtlıdır; hangilerinin açılacağını yapılandırırsın:

management:
  endpoints.web.exposure.include: health,info,metrics
  endpoint.health.show-details: always

En önemlileri:

  • /actuator/health — uygulama sağlıklı mı? Load balancer ve Kubernetes liveness/readiness probe'ları bunu kullanır (UP/DOWN).
  • /actuator/metrics — JVM belleği, GC, HTTP istekleri, veritabanı havuzu gibi yüzlerce ölçüm.
  • /actuator/info — uygulama/build bilgisi (sürüm vb.).
  • /actuator/env, /loggers, /threaddump, /heapdump — ortam, log seviyeleri, teşhis.

Örnek 1 (./Ornek1.java) health/info/metrics endpoint'lerini açar ve self-test ile çağırır: /actuator/health{"status":"UP",...}, /actuator/metrics/jvm.memory.used → bellek değeri.

Özel HealthIndicator

Uygulamanın sağlığı, bağlı olduğu dış sistemlere de bağlıdır (ödeme sağlayıcı, mesaj kuyruğu...). Kendi sağlık göstergeni HealthIndicator ile eklersin; bileşen /actuator/health altında görünür:

@Component
class OdemeSaglayiciHealth implements HealthIndicator {
    public Health health() {
        return erisilebilir
            ? Health.up().withDetail("gecikme_ms", 42).build()
            : Health.down().withDetail("hata", "bağlantı yok").build();
    }
}

Herhangi bir bileşen DOWN olursa genel sağlık DOWN olur ve orkestrasyon katmanı (k8s) trafiği kesebilir. Örnek 2 (./Ornek2.java) özel bir ödeme sağlayıcı sağlık göstergesi ekler.

Özel metrikler (Micrometer)

Actuator'ın metrik altyapısı Micrometer'dır — "metrikler için SLF4J" gibi düşün: tek bir API yazarsın, Prometheus/Datadog/Graphite gibi sistemlere aktarılır. İş olaylarını ölçmek için kendi metriklerini tanımlarsın:

Counter siparisSayaci = Counter.builder("siparis.olusturulan").register(registry);
siparisSayaci.increment(); // her siparişte

Ölçer türleri: Counter (artan sayaç), Timer (süre/oran), Gauge (anlık değer), DistributionSummary (dağılım). Örnek 3 (./Ornek3.java) bir sipariş sayacı tanımlar, 3 sipariş oluşturur ve /actuator/metrics/siparis.olusturulan üzerinden değeri okur.

Üretime taşıma: diğer araçlar

Gerçek bir üretim dağıtımında Actuator'ın yanında şunlar da kullanılır:

  • API dokümantasyonu (OpenAPI/Swagger): springdoc-openapi bağımlılığı eklenince, controller'larından otomatik bir OpenAPI şeması ve /swagger-ui.html arayüzü üretilir; istemciler API'ni keşfeder.
  • Yapılandırılmış loglama: Üretimde JSON formatında log + merkezi toplama (ELK/Loki).
  • Docker ile konteynerleştirme: Uygulamayı bir imaja paketlersin. Spring Boot, bootBuildImage (Cloud Native Buildpacks) ile Dockerfile yazmadan imaj üretebilir:
    FROM eclipse-temurin:21-jre
    COPY build/libs/uygulama.jar app.jar
    ENTRYPOINT ["java","-jar","/app.jar"]
  • Profiller ve dışsal yapılandırma: application-prod.yml, ortam değişkenleri ve secret yöneticileriyle ortam-bazlı ayar.
  • Prometheus/Grafana: micrometer-registry-prometheus ile /actuator/prometheus endpoint'i açılır; metrikler Prometheus'a toplanır, Grafana'da görselleştirilir.

Mikroservislere giriş

Tek bir büyük uygulama (monolit) yerine, sistemi küçük, bağımsız servislere bölmek mikroservis mimarisidir. Spring ekosistemi bunu Spring Cloud ile destekler:

  • Eureka / Service Discovery — servisler birbirini isimle bulur.
  • API Gateway (Spring Cloud Gateway) — tek giriş noktası, yönlendirme, kimlik.
  • Config Server — merkezi yapılandırma.
  • Resilience4j — devre kesici (circuit breaker), yeniden deneme.
  • Mesajlaşma (RabbitMQ/Kafka) — servisler arası asenkron iletişim.

Actuator burada da kritiktir: her servisin sağlığı ve metrikleri merkezi olarak izlenir.

Özet

Actuator ile health/metrics/info endpoint'lerini (Örnek 1), özel HealthIndicator ile uygulamaya özel sağlık kontrolünü (Örnek 2) ve Micrometer ile özel iş metriklerini (Örnek 3) gördük; API dokümantasyonu, Docker, Prometheus/Grafana ve mikroservislere giriş kavramlarını ele aldık. Uygulaman artık yalnızca çalışmıyor; gözlemlenebilir ve üretime hazır. Sırada, tüm bu yazdıklarımızın doğru çalıştığını garanti eden konu: test.

Kod Örnekleri(3)

Ornek1

ortam gerekir
Ornek1.java
1// Ornek1: Spring Boot Actuator — uygulamayı izlemek için hazır endpoint'ler (health, metrics, info).
2// Çalıştırma: portal derleyip gömülü Tomcat ile başlatır.
3package com.egitim.springboot.actuator;
4
5import org.springframework.boot.CommandLineRunner;
6import org.springframework.boot.autoconfigure.SpringBootApplication;
7import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
8import org.springframework.boot.builder.SpringApplicationBuilder;
9import org.springframework.context.annotation.Bean;
10import org.springframework.web.bind.annotation.GetMapping;
11import org.springframework.web.bind.annotation.RestController;
12import org.springframework.web.client.RestClient;
13
14@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
15public class Ornek1 {
16
17    public static void main(String[] args) {
18        // Actuator endpoint'leri varsayılan olarak yalnızca /health açıktır; diğerlerini açıyoruz.
19        // (Gerçekte bu ayarlar application.yml'de management.* altında yapılır.)
20        new SpringApplicationBuilder(Ornek1.class)
21                .properties(
22                        "management.endpoints.web.exposure.include=health,info,metrics",
23                        "management.endpoint.health.show-details=always",
24                        "management.info.env.enabled=true",
25                        "info.app.ad=Eğitim Portalı",
26                        "info.app.surum=1.0")
27                .run(args);
28    }
29
30    @RestController
31    static class ApiController {
32        @GetMapping("/selam") String selam() { return "merhaba"; }
33    }
34
35    @Bean
36    CommandLineRunner selfTest() {
37        return args -> {
38            RestClient c = RestClient.create("http://localhost:8080");
39            System.out.println("\n================ ACTUATOR ================");
40
41            // /actuator/health: uygulama sağlıklı mı? (load balancer / k8s probe'ları bunu kullanır)
42            System.out.println("GET /actuator/health -> " + al(c, "/actuator/health"));
43
44            // /actuator/info: uygulama hakkında bilgi (sürüm vb.)
45            System.out.println("GET /actuator/info   -> " + al(c, "/actuator/info"));
46
47            // /actuator/metrics: mevcut metrik adları
48            String metrikler = al(c, "/actuator/metrics");
49            System.out.println("GET /actuator/metrics-> " + metrikler.substring(0, Math.min(160, metrikler.length())) + "...");
50
51            // Belirli bir metrik: JVM bellek kullanımı
52            System.out.println("GET .../jvm.memory.used -> " + al(c, "/actuator/metrics/jvm.memory.used"));
53            System.out.println("==========================================");
54            System.out.println("Actuator: health (sağlık), metrics (ölçüm), info, env, loggers... üretim izlemenin temeli.");
55        };
56    }
57
58    // Güvenli GET: 4xx'te exception fırlatmaz; 200 ise gövdeyi, değilse durum kodunu döndürür.
59    static String al(RestClient c, String uri) {
60        return c.get().uri(uri).exchange((req, res) -> {
61            int s = res.getStatusCode().value();
62            String body = new String(res.getBody().readAllBytes());
63            return s == 200 ? body : ("HTTP " + s);
64        });
65    }
66}
canlı çalıştırma için ortam gerekir

Bu örnek Spring / Spring Boot (Gradle) ortamı gerektirir; tek dosya olarak java Ornek1.java ile çalışmaz. Orijinal portal bunu gömülü Tomcat / Spring context ile koşuyordu. Beklenen davranış yukarıdaki anlatım ve kodda açıklanmıştır.

Ornek2

ortam gerekir
Ornek2.java
1// Ornek2: Özel HealthIndicator — kendi sağlık kontrolünü /actuator/health'e eklemek.
2// Çalıştırma: portal derleyip gömülü Tomcat ile başlatır.
3package com.egitim.springboot.actuator;
4
5import org.springframework.boot.CommandLineRunner;
6import org.springframework.boot.actuate.health.Health;
7import org.springframework.boot.actuate.health.HealthIndicator;
8import org.springframework.boot.autoconfigure.SpringBootApplication;
9import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
10import org.springframework.boot.builder.SpringApplicationBuilder;
11import org.springframework.context.annotation.Bean;
12import org.springframework.stereotype.Component;
13import org.springframework.web.client.RestClient;
14
15@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
16public class Ornek2 {
17
18    public static void main(String[] args) {
19        new SpringApplicationBuilder(Ornek2.class)
20                .properties("management.endpoints.web.exposure.include=health",
21                        "management.endpoint.health.show-details=always")
22                .run(args);
23    }
24
25    // Özel sağlık göstergesi: dış bir bağımlılığın (ör. ödeme sağlayıcı) durumunu raporlar.
26    // Bean adı "odemeSaglayici" -> /actuator/health'te "odemeSaglayici" bileşeni olarak görünür.
27    @Component
28    static class OdemeSaglayiciHealth implements HealthIndicator {
29        @Override
30        public Health health() {
31            boolean erisilebilir = dahiliKontrol(); // gerçekte sağlayıcıya ping atılır
32            if (erisilebilir) {
33                return Health.up().withDetail("saglayici", "AcmePay").withDetail("gecikme_ms", 42).build();
34            }
35            return Health.down().withDetail("saglayici", "AcmePay").withDetail("hata", "bağlantı yok").build();
36        }
37
38        private boolean dahiliKontrol() { return true; } // demo: sağlıklı
39    }
40
41    @Bean
42    CommandLineRunner selfTest() {
43        return args -> {
44            RestClient c = RestClient.create("http://localhost:8080");
45            System.out.println("\n================ ÖZEL HEALTH INDICATOR ================");
46            // /actuator/health artık dahili (diskSpace, ping) + bizim "odemeSaglayici" bileşenini içerir.
47            System.out.println("GET /actuator/health ->");
48            System.out.println("  " + c.get().uri("/actuator/health").retrieve().body(String.class));
49            System.out.println("=======================================================");
50            System.out.println("Bir bileşen DOWN olursa genel health DOWN olur; k8s/load balancer trafiği kesebilir.");
51        };
52    }
53}
canlı çalıştırma için ortam gerekir

Bu örnek Spring / Spring Boot (Gradle) ortamı gerektirir; tek dosya olarak java Ornek2.java ile çalışmaz. Orijinal portal bunu gömülü Tomcat / Spring context ile koşuyordu. Beklenen davranış yukarıdaki anlatım ve kodda açıklanmıştır.

Ornek3

ortam gerekir
Ornek3.java
1// Ornek3: Özel metrik (Micrometer) — iş olaylarını ölçmek ve /actuator/metrics'ten okumak.
2// Çalıştırma: portal derleyip gömülü Tomcat ile başlatır.
3package com.egitim.springboot.actuator;
4
5import io.micrometer.core.instrument.Counter;
6import io.micrometer.core.instrument.MeterRegistry;
7import org.springframework.boot.CommandLineRunner;
8import org.springframework.boot.autoconfigure.SpringBootApplication;
9import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
10import org.springframework.boot.builder.SpringApplicationBuilder;
11import org.springframework.context.annotation.Bean;
12import org.springframework.web.bind.annotation.PostMapping;
13import org.springframework.web.bind.annotation.RestController;
14import org.springframework.web.client.RestClient;
15
16@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
17public class Ornek3 {
18
19    public static void main(String[] args) {
20        new SpringApplicationBuilder(Ornek3.class)
21                .properties("management.endpoints.web.exposure.include=metrics")
22                .run(args);
23    }
24
25    @RestController
26    static class SiparisController {
27        private final Counter siparisSayaci;
28
29        // MeterRegistry enjekte edilir; üzerinde özel bir Counter (sayaç) tanımlarız.
30        SiparisController(MeterRegistry registry) {
31            this.siparisSayaci = Counter.builder("siparis.olusturulan")
32                    .description("Oluşturulan toplam sipariş sayısı")
33                    .register(registry);
34        }
35
36        @PostMapping("/api/siparis")
37        public String olustur() {
38            siparisSayaci.increment(); // her siparişte iş metriğini artır
39            return "sipariş oluşturuldu";
40        }
41    }
42
43    @Bean
44    CommandLineRunner selfTest() {
45        return args -> {
46            RestClient c = RestClient.create("http://localhost:8080");
47            System.out.println("\n================ ÖZEL METRİK (Micrometer) ================");
48
49            // 3 sipariş oluştur -> sayaç 3 olmalı.
50            for (int i = 0; i < 3; i++) {
51                c.post().uri("/api/siparis").retrieve().toBodilessEntity();
52            }
53
54            // Özel metriği Actuator'dan oku.
55            System.out.println("3 sipariş oluşturuldu.");
56            String metrik = c.get().uri("/actuator/metrics/siparis.olusturulan").exchange((req, res) -> {
57                int s = res.getStatusCode().value();
58                String body = new String(res.getBody().readAllBytes());
59                return s == 200 ? body : ("HTTP " + s);
60            });
61            System.out.println("GET /actuator/metrics/siparis.olusturulan ->");
62            System.out.println("  " + metrik);
63            System.out.println("==========================================================");
64            System.out.println("Özel metrikler (Counter/Timer/Gauge) iş olaylarını ölçer; Prometheus/Grafana ile izlenir.");
65        };
66    }
67}
canlı çalıştırma için ortam gerekir

Bu örnek Spring / Spring Boot (Gradle) ortamı gerektirir; tek dosya olarak java Ornek3.java ile çalışmaz. Orijinal portal bunu gömülü Tomcat / Spring context ile koşuyordu. Beklenen davranış yukarıdaki anlatım ve kodda açıklanmıştır.