[Spring Boot] 헬스 체크(Health Check) 구현

01_Health Check


📍 Health Check

현재 서버가 살아있는지 확인

→ 사람이 직접 확인 어렵

→ 자동화된 외부 시스템이 주기적으로 서버 상태를 확인할 수 있는 엔드포인트 사용

 

 

 

 

02_Health Check API 구현


📍 직접 엔드포인트 생성

package com.example.global.api;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HealthCheckController {

    @GetMapping("/health-check")
    public String healthCheck() {
        return "OK";
    }
}

 

/health-check에 GET 요청이 오면 단순히 "OK" 문자열을 반환한다.

서버가 살아있고 톰캣이 요청을 처리할 수 있다면 200 응답이 나간다.

그러나 DB 커넥션이 끊겼거나 Redis가 다운됐어도 여전히 "OK"를 반환한다.

 

 

 

📍 API 스펙 분리

API 명세는 인터페이스에, 실제 구현은 컨트롤러에 둠

컨트롤러 코드가 비즈니스 로직에만 집중할 수 있어 가독성이 좋아짐

package com.example.global.api;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;

@Tag(name = "Health-Check", description = "서버 상태 확인 API")
public interface HealthCheckApi {

    @Operation(
        summary = "헬스 체크",
        description = "서버 현재 상태를 확인하기 위한 GET API. 정상 동작 시 'OK'를 반환한다."
    )
    @ApiResponses(value = {
        @ApiResponse(responseCode = "200", description = "서버가 정상 동작 중")
    })
    String healthCheck();
}
@RestController
public class HealthCheckController implements HealthCheckApi {

    @Override
    @GetMapping("/health-check")
    public String healthCheck() {
        return "OK";
    }
}

 

 

 

📍 Spring Boot Actuator 활용

Spring Boot Actuator는 DB나 외부 서비스 의존성 확인하는 것을 표준화해서 제공

클래스패스에 있는 의존성을 자동으로 감지해 헬스 체크에 포함

DataSource가 있으면 자동으로 DB 헬스 체크가 추가되고, Redis나 RabbitMQ가 있으면 해당 컴포넌트도 추가됨

// build.gradle
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
}
# application.yml
management:
  endpoints:
    web:
      exposure:
        include: health, info
  endpoint:
    health:
      show-details: when-authorized  # never, always, when-authorized 중 선택

 

  • /actuator/health로 요청

show-details: always 설정한 경우

{
  "status": "UP",
  "components": {
    "db": {
      "status": "UP",
      "details": {
        "database": "MySQL",
        "validationQuery": "isValid()"
      }
    },
    "diskSpace": {
      "status": "UP",
      "details": {
        "total": 250790436864,
        "free": 87523581952,
        "threshold": 10485760
      }
    },
    "ping": {
      "status": "UP"
    }
  }
}

 

하나라도 DOWN이면 전체 상태가 DOWN

 

 

Liveness와 Readiness 구분

Kubernetes 같은 환경에서는 두 가지 헬스 체크를 구분

종류 의미 실패 시 동작
Liveness 프로세스가 살아있는가 컨테이너 재시작
Readiness 트래픽을 받을 준비가 됐는가 트래픽 라우팅 제외
# application.yml
management:
  endpoint:
    health:
      probes:
        enabled: true
  health:
    livenessstate:
      enabled: true
    readinessstate:
      enabled: true
  • /actuator/health/liveness: 프로세스 생존 여부
  • /actuator/health/readiness: 트래픽 수신 준비 여부

 

 

Kubernetes Manifest

livenessProbe:
  httpGet:
    path: /actuator/health/liveness
    port: 8080
  initialDelaySeconds: 30
  periodSeconds: 10

readinessProbe:
  httpGet:
    path: /actuator/health/readiness
    port: 8080
  initialDelaySeconds: 10
  periodSeconds: 5

 

 

 

📍 커스텀 HealthIndicator 생성

도메인 특화 헬스 체크가 필요할 때 사용

 

ex) 외부 결제 API와의 연결 상태 확인하고 싶을 때

package com.example.global.health;

import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;

@Component
public class PaymentApiHealthIndicator implements HealthIndicator {

    private final PaymentApiClient paymentApiClient;

    public PaymentApiHealthIndicator(PaymentApiClient paymentApiClient) {
        this.paymentApiClient = paymentApiClient;
    }

    @Override
    public Health health() {
        try {
            boolean isHealthy = paymentApiClient.ping();
            if (isHealthy) {
                return Health.up()
                        .withDetail("paymentApi", "정상 연결됨")
                        .build();
            }
            return Health.down()
                    .withDetail("paymentApi", "연결 실패")
                    .build();
        } catch (Exception e) {
            return Health.down(e)
                    .withDetail("paymentApi", "예외 발생")
                    .build();
        }
    }
}

 

  • /actuator/health로 요청
{
  "status": "UP",
  "components": {
    "paymentApi": {
      "status": "UP",
      "details": {
        "paymentApi": "정상 연결됨"
      }
    }
  }
}