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-openapibağımlılığı eklenince, controller'larından otomatik bir OpenAPI şeması ve/swagger-ui.htmlarayü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-prometheusile/actuator/prometheusendpoint'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 gerekir1// 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}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 gerekir1// 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}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 gerekir1// 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}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.