Spring Actuator 없이 Health Check API를 구현하며

김형준·2025년 1월 22일

Health Check API란

시스템의 상태를 빠르게 모니터링하고 로드 밸런서 등 외부 서비스에서 이를 활용할 수 있도록 하는 정보를 제공하는 API

애플리케이션이 정상적으로 수행되기 위한 목적으로 연동된 외부 서비스들의 상태 또한 API에 포함시킬 수 있다.

구현 과정

Health Check API 구현을 더 편하게 해 주는 Spring Actuator를 사용할 수도 있다.

Spring Actuator는 기본적으로 보안상 문제가 될 수 있는 정보를 포함한 많은 정보들을 제공하며, 퍼블릭 엑세스를 허용해야 하는 인스턴스 상에서 실행될 것이었기에 주의해서 사용할 필요가 있었다.

하지만 단순한 정보들만 확실하게 제공하는 API를 만들 계획이었고, 이를 이슈 없이 구현하기 위해 비교적 높은 러닝커브를 가졌다고 판단되었다.

따라서 아래 사항들을 반환하는 API를 Java와 Spring 정도로만 간단하게 구현해보았다.

  1. 서버 실행 중
  2. 현재 실행중인 서버의 디스크 여유/총 공간
  3. 물리 메모리의 여유/총 공간
  4. JVM이 할당받은 메모리의 여유/총 공간
  5. DB 연결 여부

DB 연결 여부는 연결 설정된 DB에 SELECT 1을 전송해 응답이 정상적으로 받아와지는지 확인하는 식으로 구현하였다.

import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import org.springframework.stereotype.Service;

@Service
public class HealthCheckService {
    @PersistenceContext // @Configuration 없이 EntityManager 주입
    private EntityManager entityManager;

    public boolean isConnected() {
        try {
            return entityManager.createNativeQuery("SELECT 1").getSingleResult() != null;
        } catch (Exception e) {
            return false;
        }
    }
}

서버가 응답을 할 수 있다면 서버 실행 중이라는 사실이 증명되므로 서버 상태로는 항상 OK를 반환하도록 했다.

또한 JVM에 할당되는 메모리는 보통 256MB 정도이므로 알아보기 쉽게 MB 단위로 출력될 수 있게 하였다.

import com.sun.management.OperatingSystemMXBean;
import lombok.Getter;

import java.io.File;

@Getter
public class ServerHealth {

    private final String serverStatus = "OK";

    private final double freeDiskGb;
    private final double totalDiskGb;

    private final double freeMemoryGb;
    private final double totalMemoryGb;

    private final double freeJvmMemoryMb;
    private final double totalJvmMemoryMb;

    private final boolean dbConnected;

    private static final double GB = Math.pow(1024, 3);
    private static final double MB = Math.pow(1024, 2);
    private static final int FLOATING_POINT = (int) Math.pow(10, 2);

    public ServerHealth(boolean dbConnected, File file, Runtime runtime, OperatingSystemMXBean osBean) {
        this.freeDiskGb = getByteFloatingPoint(file.getFreeSpace(), GB);
        this.totalDiskGb = getByteFloatingPoint(file.getTotalSpace(), GB);
        this.freeMemoryGb = getByteFloatingPoint(osBean.getFreeMemorySize(), GB);
        this.totalMemoryGb = getByteFloatingPoint(osBean.getTotalMemorySize(), GB);
        this.freeJvmMemoryMb = getByteFloatingPoint(runtime.freeMemory(), MB);
        this.totalJvmMemoryMb = getByteFloatingPoint(runtime.totalMemory(), MB);
        this.dbConnected = dbConnected;
    }

    // 단위에 맞는 값의 소수점 자리를 FLOATING_POINT 자릿수 만큼 반환
    private double getByteFloatingPoint(long number, double byteUnit) {
        return Math.floor(number / byteUnit * FLOATING_POINT) / FLOATING_POINT;
    }
}
import com.sun.management.OperatingSystemMXBean;
import lombok.RequiredArgsConstructor;
import org.example.expert.domain.common.dto.ServerHealth;
import org.example.expert.domain.common.service.HealthCheckService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.File;
import java.lang.management.ManagementFactory;

@RestController
@RequiredArgsConstructor
public class HealthCheckController {
    private final HealthCheckService service;

    @GetMapping("/health")
    public ResponseEntity<ServerHealth> healthCheck() {
        return ResponseEntity.ok(
            new ServerHealth(
                service.isConnected(),
                new File("/"),
                Runtime.getRuntime(),
                (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean()
            )
        );
    }
}

생각해 볼 점

코드에서 사용된 com.sun.management.OperatingSystemMXBean는 OpenJDK, Oracle JDK에서 제공하는 java.lang.management.OperatingSystemMXBean의 구현체이다.

이는 메모리 및 OS 시스템의 정보에 접근하기 쉽도록 설계된 클래스지만 IBM SDK 등의 다른 JDK에서는 지원되지 않을 수 있는 기능이라는 뜻이다.

현재 실행환경은 모두 OpenJDK를 사용하기에 상관없지만, 추후에 호환성 문제가 발생할 수 있다고 인지할 필요가 있다.

호환성 문제를 해결하려면 이를 해결한 라이브러리들을 사용하거나, JDK 별로 이를 지원하는지 확인하고 지원되지 않는다면 JDK 별 대체 기능을 확인해야 할 것이다.

결론적으로 이러한 호환성 문제를 해결하고 싶으면 Spring Actuator를 도입하고, 보안에 대해 더 신경쓰는 것이 나을 것 같다.

0개의 댓글