새소식

Data Engineering/Observability

[Observability] - 5. 자신의 Application Monitoring 해보기!!

  • -

이전에는 node의 모니터링을 했다면
이번에는 자신이 만든 앱의 모니터링을 해보았다.

아래와 같은 구조로 진행해볼 예정이다.

App은 Spring boot로 매우 간단하게 만들었으며,
Prometheus Simple Client java로 Prometheus 형식의 메트릭을 노출한다.

여기서 Prometheus Simple Client

Prometheus는 각 언어별로 client library를 제공한다.
Prometheus 포맷으로 노출할 수 있는 설정, 각 언어별로 기본 모니터링 시스템을 이용해 메트릭 정보를 추출, custom metric을 남기는 기능 등을 할 수 있다.

한마디로 Prometheus가 알아들을 수 있도록 데이터를 남길 수 있도록 해주는 라이브러리이다.

Java App 생성

우선 자신의 app을 만들어야하기에
IntelliJ에서 Gradle로 프로젝트를 만들었다.

build.gradle

plugins {
    id 'java'
    id 'org.springframework.boot' version '2.7.6'
    id 'io.spring.dependency-management' version '1.0.15.RELEASE'
}


group 'org.example'
version '1.0-SNAPSHOT'
sourceCompatibility = '1.8'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'io.prometheus:simpleclient:0.16.0'
    implementation 'io.prometheus:simpleclient_hotspot:0.16.0'
    implementation 'io.prometheus:simpleclient_httpserver:0.16.0'
    implementation 'io.prometheus:simpleclient_pushgateway:0.16.0'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

bootJar {
    mainClass = 'de.monitoring.prometheus.PrometheusMonitoringAppApplication'
}

tasks.named('test') {
    useJUnitPlatform()
}

프로젝트 구조는 아래와 같이 생성하였다.

package: de.monitoring.prometheus
classes
- PrometheusMonitoringAppApplication: main class
- ApiController: API contoller
- Greeting: data object

PrometheusMonitoringAppApplication.java

package de.monitoring.prometheus;

import java.io.IOException;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import io.prometheus.client.exporter.HTTPServer;
import io.prometheus.client.hotspot.DefaultExports;

@SpringBootApplication
public class PrometheusMonitoringAppApplication {

    public static void main(String[] args) throws IOException {
        DefaultExports.initialize();
        HTTPServer server = new HTTPServer.Builder()
                .withPort(1234)
                .build();

        SpringApplication.run(PrometheusMonitoringAppApplication.class, args);
    }
}

ApiController

package de.monitoring.prometheus;

import java.util.concurrent.atomic.AtomicLong;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class ApiController {

    private static final String template = "Hello, %s!";
    private final AtomicLong counter = new AtomicLong();

    @GetMapping("/greeting")
    public Greeting greeting(@RequestParam(value = "name", defaultValue = "World") String name) {
        return new Greeting(counter.incrementAndGet(), String.format(template, name));
    }

    @GetMapping("/cpurun")
    public ResponseEntity<String> runCpu(
            @RequestParam(value = "divider", defaultValue = "65536") long divider) {
        long counter = 0;
        do {
            counter++;
        } while (counter <= (Long.MAX_VALUE / divider));
        return ResponseEntity.ok("finished");
    }

    @GetMapping("/users/{userId}")
    public ResponseEntity<String> getUser(@PathVariable String userId) {
        return ResponseEntity.ok("user_id: " + userId);
    }

    @GetMapping("/website")
    public ResponseEntity<String> getSite(
            @RequestParam(value = "site", defaultValue = "https://www.naver.com") String site) {
        RestTemplate restTemplate = new RestTemplate();
        System.out.println("site: " + site);
        String response = restTemplate.getForObject(site, String.class);
        return ResponseEntity.ok(response);
    }
}

다양한 트래픽에 따른 cpu 변화 등을 알아보기 위해 위와같이 여러 메소드를 작성하였다.

Greeting

package de.monitoring.prometheus;

public class Greeting {

    private final long id;
    private final String content;

    public Greeting(long id, String content) {
        this.id = id;
        this.content = content;
    }

    public long getId() {
        return id;
    }

    public String getContent() {
        return content;
    }
}

그리고 위의 build.gradle에서
bootJar를 실행시켜 jar 파일을 만든다.

Application Server

다시 ec2 서버로 돌아오는데
이번에는 prometheus가 돌아가는 서버가 아니라 다른 서버로 들어왔다.

그리고 jdk와 node exporter를 설치한다.

jdk는 아래와 같이 설치하면 되고 node exporter는 이전 블로그 글을 참고한다.

sudo apt install openjdk-8-jre-headless

2024.01.01 - [Data Engineering/Observability] - [Observability] - 4. Node Monitoring 해보기!!

 

[Observability] - 4. Node Monitoring 해보기!!

우선 여기서 말하는 노드란, 컴퓨터 네트워크를 구성하는 하나의 기기를 의미한다. 흔히 컴퓨터 1대가 노드가 될 수 있다. Node 정보를 가져오기 위해서는 Node Exporter가 필요하다. Node Exporter 설치

cstory-bo.tistory.com

 

이제 jar 파일을 이 서버로 보낸다.

scp -i {ec2 서버 접속을 위한 키 파일} {jar 파일 경로} ubuntu@{java 설치한 ec2서버 ip}:~/monitoring-app/.

이때 해당 위치의 폴더는 미리 생성해놓았다.

nohup을 이용해서 간단하게 app을 실행한다.

nohup java -jar {jar 파일} > server.log 2>&1 & echo $! > run.pid

웹 브라우저로 {IP}:8080/greeting 으로 잘 동작하는 지 확인해보고
{IP}:1234/metrics로 app의 메트릭이 잘 쌓이고 있는지도 확인해본다.
여기서 1234 포트는 Prometheus simple client 라이브러리로 설정한 포트이다.
8080포트는 spring boot의 디폴트 포트번호이다.

Prometheus에서 Application Metric Scrap

다시 Prometheus가 설치되어있는 서버에 접속한다.

거기서 prometheus.yml 파일을 수정해
App Metric도 scrap한다.

추가로 App이 동작하고 있는 node의 메트릭도 scrap 하기 위해
job_name이 node에 targets를 app 서버 ip를 추가한다.

  - job_name: "node"
    static_configs:
      - targets: ["localhost:9100", "{app 서버의 ip}:9100"]

추가로 아래 내용도 붙인다.

  - job_name: "spring"
    static_configs:
            - targets: ["{App 서버 ip}:1234"]

다시 systemctl로 prometheus를 재가동 시킨다.

Grafana에서 Application Metric 대시보드 구성하기

Memory

  • JVM heal메모리 사용량을 구하려면
    • jvm_memory_bytes_used{area="heap"} / jvm_memory_bytes_max
  • App 메모리 사용량(heap + offheap)을 구하려면
    • sum by (instance) (jvm_memory_bytes_used{job="spring"})
  • App 메모리 사용률 %를 구하고 싶으면
    • 쿼리 A에 App 메모리 사용량을 넣고
    • 쿼리 B에 전체 메모리를 구한다.
      • node_memory_MemTotal_bytes{job="node", instance="$your_instance"
    • 그리고 expression을 추가하고 Math로 표현식을 아래처럼 넣는다.
      • $A/$B
  • GC time을 구하려면
    • sum by (job, instance) (rate(jvm_gc_collection_seconds_sum[$__rate_interval]))

CPU

* prometheus simple client java에서는 cpu에 대한 정보가 process_cpu_seconds_total 만 있다.
그래서 node exporter와 함께 cpu 사용량을 구한다.

  • cpu seconds total 구하려면
    • sum by (job, instance) (rate(node_cpu_seconds_total{job="node", instance="$your_instance"}[$__rate_interval])))
  • rate로 단위 구간당 cpu seconds total 구하려면
    • rate(process_cpu_seconds_total{job="spring", instance="$your_instance"}[$__rate_interval]) >= 0
  • 1번 쿼리를 A, 2번 쿼리를 B로 하고 Math 수식을 이용하면 비율%를 구할 수 있다.
    • $B / $A

이때 /cpurun API를 호출해 cpu 사용량을 급격하게 올리고
prometheus에 반영되는 지 확인할 수 있다.

멈추려면 프로세스 자체를 강제종료시키고 다시 띄워야한다.

Filesystem

FD count는

process_open_fds / process_max_fds 로 그 비율을 구할 수 있다.

 

 

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.