• Cum. Ara 3rd, 2021

Merhabalar.

Bu yazıda bir Spring Boot projesinden metric akışlarını sağlayıp Prometheus üzerinde izleme konusundan bahsedeceğiz. Bu yazı için hazırladığım Spring Boot projesini buraya tıklayarak ulaşabilirsiniz.

Prometheus Nedir?

Günümüz yazılım dünyasında artık yazılımın monitör edilmesi, izlenmesi artık bir zorunluluk. İşte Prometheus da bu ihtiyacı karşılamak üzere ortaya çıkarılmış bir monitoring yazılımı. Açık kaynak kodlu olan Prometheus üzerinde metricleri toplayıp belirli caselerde alert ürettirebiliriz.

Adım 1: Spring Boot Projesi

Bu yazı için oldukça basit bir iş yapan bir Spring Boot projesi ayağa kaldıracağız. Biz bir karakter ismini query param olarak verip bir REST API çağıracağız ve REST API de bize karakterin ait olduğu diziyi verecek. Aynı zamanda her karakterin ait olduğu dizinin kaç defa çağırıldığını da Prometheus’a söyleyecek ve biz de Prometheus üzerinden kontrol edeceğiz. Şimdi API call’lara cevap verecek olan RestAPI sınıfımıza bakalım.

package com.ilkaygunel.prometheus.api;

import com.ilkaygunel.prometheus.service.SeriesService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api")
public class RestAPI {

    @Autowired
    private SeriesService seriesService;

    @RequestMapping(method = RequestMethod.GET, value = "/seriesName")
    public ResponseEntity getSeriesNameOfCharacter(@RequestParam(value = "character", defaultValue = "") String character) {
        String seriesName = seriesService.getSeriesNameOfCharacter(character);
        if ("NOT_FOUND".equalsIgnoreCase(seriesName)) {
            return new ResponseEntity(HttpStatus.NOT_FOUND);
        } else {
            return new ResponseEntity(seriesName, HttpStatus.OK);
        }
    }
}

RestAPI sınıfımızda HTTP GET istemiğize cevap verecek olan ve /seriesName path’inde çalışacak olan getSeriesNameOfCharacter metodumuz yer alıyor. Bu metot query param olarak karakter ismini SeriesService sınıfındaki getSeriesNameOfCharacter metoduna verip karakterin ait olduğu dizinin ismini buluyor. Eğer karakterin ait olduğu dizi tanımlı ise ilgili dizi kullanılarak 200 HTTP kodu dönülüyor. Eğer tanımlı değilse de HTTP 404 dönülüyor. Oldukça basit bir metot. Şimdi bir de SeriesService sınıfımıza bakalım.

package com.ilkaygunel.prometheus.service;

import io.prometheus.client.CollectorRegistry;
import io.prometheus.client.Counter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;

@Service
public class SeriesService {

    @Autowired
    CollectorRegistry registry;

    Counter characterNameCounter;

    Map<String, String> seriesAndCharactersMap;

    @PostConstruct
    public void init() {
        seriesAndCharactersMap = new HashMap<>();
        seriesAndCharactersMap.put("Leslie_Knope", "Parks And Recreation");
        seriesAndCharactersMap.put("Michael_Scott", "The Office");
        seriesAndCharactersMap.put("Joey_Tribbiani", "Friends");
        seriesAndCharactersMap.put("Sheldon_Cooper", "The Big Bang Theory");

        characterNameCounter = Counter.build()
                .name("character_name_count")
                .help("Number of character name request")
                .labelNames("character_name")
                .register(registry);
    }

    public String getSeriesNameOfCharacter(String characterName) {
        if (seriesAndCharactersMap.entrySet().stream().anyMatch(entry -> entry.getKey().equals(characterName))) {
            characterNameCounter.labels(characterName).inc();
            return seriesAndCharactersMap.entrySet().stream().filter(entry -> entry.getKey().equals(characterName)).findAny().get().getValue();
        } else {
            characterNameCounter.labels("NOT_FOUND").inc();
            return "NOT_FOUND";
        }
    }

}

SeriesService sınıfımızda CollectorRegistry tipinde bir nesneyi @Autowired metodu ile inject ediyoruz. Ayrıca bir de Counter tipinde characterNameCounter isminde nesne tanımlıyoruz.

@PostConstruct notasyonu ile işaretli olan init metodumuz seriesAndCharactersMap map’ini bizim için dolduracak. init metodunun devamında da characterNameCounter nesnesini oluşturuyoruz. Prometheus’dan gelen Counter sınıfı üzerinden nesneyi oluşturuyoruz. name ile Prometheus üzerindeki sorgulamarda kullanacağımız keyi tanımlıyoruz. help ile oluşturduğumuz counter’a bir description veriyoruz. labelNames ile neyin count’unu tuttuğumuzu sözlüyoruz. Biz karakter isimlerinin count’unu tutacağımız için character_name verdik. register ile de oluşturduğumuz counter’ı kayda geçiriyoruz.

getSeriesNameOfCharacter metodu query param’dan gelen karakter ismini parametre olarak alıyor ve Map’ten bu karakter ismine karşılık gelen dizi ismini bize dönüyor. Dizinin ismini dönmeden hemen önce de characterNameCounter üzerinde ilgili karakter ismini label olarak geçip değerini 1 arttırıyor. Yani biz diyelim ki Michael_Scott’ın ait olduğu diziyi getir dedik, bu durumda labelNames’de verdiğimiz character_name’i Michael_Scott ile eşleştirip değerini 1 arttıracak.

Eğer bu karakter ismine karşılık gelen dizi yoksa da NOT_FOUND şeklinde bir text dönüyor ve dönmeden önce de değerini yine 1 arttıracak.

Spring Boot projesinde son olarak bir de application.properties dosyasına bakalım.

server.servlet.context-path=/SpringBootPrometheusDemoProject

server.port=8080

management.endpoints.web.exposure.include=prometheus

application.properties dosyasında projenin çalışacağı contextPath’i veriyoruz, çalışacağı portu veriyoruz ve son olarak da endpoints.web.exposure.include property’sine prometheus değerini atayarak Spring Boot projesinin Prometheus’a veri aktarmasını enable etmiş oluyoruz.

Şimdi Prometheus’a ayarlama ve ayağa kaldırma işimize bakalım. Öncelikle Prometheus’un config ayarlamalarını yaptığımız prometheus.yml dosyasına bakalım.

global:
scrape_interval: 15s 
evaluation_interval: 15s
scrape_configs:
- job_name: 'prometheus'
  static_configs:
  - targets: ['127.0.0.1:9090']
- job_name: 'SpringBootPrometheusDemoProject'
  metrics_path: '/SpringBootPrometheusDemoProject/actuator/prometheus'
  scrape_interval: 5s
  static_configs:
  - targets: ['192.168.56.1:8080']

prometheus.yml dosyası içerisinde iki adet scrape configs var. Birisi Prometheus’un kendisi için. Diğeri ise asıl bizi ilgilendiren. job_name ile Prometheus’un yapacağı tarama işlemine bir isim vermiş oluyoruz. metrics_path ile Prometheus’u besleyecek projenin hangi path ile bu verileri sunacağını söylüyoruz. application.properties dosyasından hatırlayacağınız gibi bizim projemiz SpringBootPrometheusDemoProject path’i altında çalışacak. /actuator/prometheus path’i ise Spring tarafından otomatik eklenen ve çalıştırılan bir path. Spring Boot projesi bu path ile metric’leri açıyor. scrape_interval ile tarama süresini söylüyoruz. 5 saniyede bir taratacağız. static_configs altındaki targets ile de taramanın yapılacağı ve metric’lerin nereden alınacağını bildiriyoruz. Bilgisayarınızda Windows kullanıyorsanız ipconfig komutunu CMD’de çalıştırıp çıkan sonuçtaki IPv4 adresini kullanabilirsiniz burada.

Şimdi de Prometheus’u ayağa kaldıracağımız docker compose dosyamıza bakalım.

version: '3'
services:
  prometheus:
    image: prom/prometheus:v2.21.0
    ports:
      - 9000:9090
    volumes:
    - "/C/Users/ilkay.gunel/Documents/LocalPrometheus/prometheus.yml:/etc/prometheus/prometheus.yml"
  srvgrafana:
    image: grafana/grafana:latest
    ports:
      - 3000:3000
    depends_on:
    - prometheus
    volumes:
    - "/C/Users/ilkay.gunel/Documents/LocalPrometheus/grafana.ini:/etc/grafana/grafana.ini"
    - "/C/Users/ilkay.gunel/Documents/LocalPrometheus/lib:/var/lib/grafana"

Docker Compose dosyası içerisinde services altında iki servis görüyoruz. Birisi prometheus diğeri de grafana. Grafana ile ilgili kısım için bu yazıya güncelleme geçeceğim. Bu nedenle Grafana kısmına şimdi girmiyorum. Prometheus servisinde image özelliği Docker Hub’dan pull edip çalıştırlacak image’ı belirtiyor. ports özelliği Prometheus’un hangi port üzerinde koşacağını söylüyor. Prometheus default’ta 9090 portunda koşuyor, biz bunu docker-compose dosyasında 9000 portuna map ediyoruz, yani Prometheus’a 9000 portu üzerinden erişeceğiz. volumes özelliğini Prometheus’un kullanacağı config dosyası bildirmekte kullanıyoruz. Bu config dosyasından az önce bahsetmiştik zaten.

DEMO

Şimdi uygulamamızın demosunu yapalım.

İlk olarak komus satırında docker-compose dosyasının olduğu yere cd komutu ile geçelim ve docker-compose -f docker-compose-prometheus.yml up komutunu çalıştıralım.

Starting Prometheus ve Starting Grafana için done mesajlarını gördü isek ve Prometheus için “Server is ready to receive web requests” textini gördü isek Prometheus artık metric toplamak için hazır demektir.

Şimdi de Application sınıfı üzerinden Spring Boot projemizi çalıştırıyoruz. Ardından isterseniz browser üzerinden isterseniz Postman üzerinden http://localhost:8080/SpringBootPrometheusDemoProject/api/seriesName?character=Leslie_Knope adresine bir GET isteğinde bulunuyorum. Ben Firefox browser üzerinden çağırıyorum. Aşağıdaki resimde görebileceğiniz gibi bana karakterin ait olduğu diziyi döndü.

Bu işlemin ardından http://localhost:8080/SpringBootPrometheusDemoProject/actuator/prometheus adresine gidip character_name_count’u kontrol ediyorum.

Ekran çıktısında görebileceğimiz gibi şu satır eklenmiş durumda: character_name_count_total{character_name=”Leslie_Knope”,} 1.0

Bu satırdan anladığımız şey Leslie_Knope karakteri için 1 defa sorgulama yapılmış. Şimdi bir de http://localhost:8080/SpringBootPrometheusDemoProject/api/seriesName?character=Joey_Tribbiani adresini çağıralım ve ardından hemen http://localhost:9000 adresine gidelim. Expression kısmına character_name_count_total yazalım ve enter’a tıklayalım. Bu işlem ile aslında tüm karakterlerin kaç kere arandığını listelemiş oluyoruz.

Element kısmından görebileceğiniz gibi Joey_Tribbiani ve Leslie_Knope birer kez çağırılmışlar.

Şimdi Joey_Tribbiani ismini çağırdığımız linke 3 kez daha tıklayalım ve Prometheus’a dönüp Expression’ı şu şekilde değiştirelim: character_name_count_total{character_name=”Joey_Tribbiani”}

Ekran çıktısından göreceğimiz gibi bu şekilde sadece Joey_Tribbiani’nin çağırılması sayısını getirmiş oluyoruz. Daha önceden 1 kez çağırılmıştı, 3 kez daha çağırdığım için sayı 4 oldu.

Bu yazıyı ilerde Grafana entegrasyonunu da anlatacağım şekilde güncelleyeceğim. O yüzden haberdar olmak için beni Twitter Hesabım‘dan takip edip haberdar olabilirsiniz.

Şimdilik bu yazıda anlatacaklarım bu kadar. Gelecek yazıda görüşmek üzere sağlıcakla kalın.

Merhaba! Ben İlkay Günel :) İstanbul Üniversitesi Bilgisayar Mühendisliği 2016 mezunuyum. Güncel olarak Paycore'da SoftPOS projesinde Java Developer olarak çalışmaktayım. Büyük oranda Java teknolojileri ile uğraşıp kendimi geliştirmeye çalışıyorum ve bu mecrada öğrendiklerimi sizle paylaşmaya da çabalıyorum :) Hakkımda biraz daha detaylı bilgi için: http://www.ilkaygunel.com/about/

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir