관리 메뉴

피터의 개발이야기

[Kubernetes] Spring Boot + Kubernetes 기반에서 웜업 적용하기 본문

Kubernetes

[Kubernetes] Spring Boot + Kubernetes 기반에서 웜업 적용하기

기록하는 백앤드개발자 2024. 10. 8. 22:37
반응형

ㅁ 들어가며

ㅇ 이 글은 Spring Boot와 Kubernetes 환경에서 애플리케이션의 성능을 개선하기 위해 웜업을 적용한 경험을 소개하는  Line Engineering - Spring Boot + Kubernetes 기반에서 웜업 적용하기를 읽고 그 내용을 정리하였다. 

 

ㅁ 콜드 스타트와 웜업이란?

  소프트웨어에서 콜드 스타트는 애플리케이션이 처음 실행되거나 오랜 시간 후 재실행될 때 발생하는 현상을 말한다. 이 때 시스템은 애플리케이션을 위한 프로세스와 리소스를 새로 생성해야 하므로 실행 시간이 길어진다. 

웜업은 이러한 콜드 스타트 문제를 해결하기 위한 전략으로, 애플리케이션이 실제 트래픽을 처리하기 전에 필요한 리소스와 데이터를 미리 로드하고 초기화하는 과정이다. 이를 통해 초기 성능 저하를 방지하고 사용자에게 더 빠른 응답 시간을 제공할 수 있다.

 

ㅁ Kubernetes 프로브 활용

Kubernetes의 준비성 프로브(readinessProbe)를 활용하여 웜업 완료 전까지 트래픽을 차단한다. Spring Boot의 애플리케이션 가용성 진단 기능과 Kubernetes 환경 감지 기능을 활용하여 구현한다.

프로브 설명
활성 프로브(livenessProbe) 활성 프로브를 통과하지 못하면 kubelet은 컨테이너를 종료하고, 해당 컨테이너는 재시작 정책의 대상이 된다.
준비성 프로브(readinessProbe) 준비성 프로브를 통과하지 못하면 엔드포인트 컨트롤러는 파드와 연관된 모든 서비스의 엔드포인트에서 파드의 IP 주소를 제거한다.
스타트업 프로브(startupProbe) 스타트업 프로브가 시작되면 스타트업 프로브를 통과하기 전까지 다른 프로브는 활성화되지 않는다. 만약 스타트업 프로브를 통과하지 못하면 kubelet은 컨테이너를 종료하고 해당 컨테이너는 재시작 정책의 대상이
된다.

 

관련 글: [Kubernetes]  Pod의 건강 상태 체크 방법, Readiness Liveness Startup probe설정, Lifecycle Hook

 

ㅁ 웜업 구현 단계

워머(Warmer) 구현: 웜업 기능을 한 번만 실행하고 새로운 웜업을 쉽게 추가할 수 있도록 인터페이스와 구현체를 작성할 수 있다.

// 웜업 인터페이스 선언
interface Warmer {
    suspend fun run()
    val isDone: Boolean
}

// 웜업을 한 번만 실행하도록 강제하는 클래스
abstract class ExactlyOnceRunWarmer : Warmer {
    override var isDone = false
    private val mutex = Mutex()

    override suspend fun run() {
        if (!isDone && mutex.tryLock()) {
            try {
                doRun()
                setDone()
            } finally {
                mutex.unlock()
            }
        }
    }

    protected fun setDone() {
        this.isDone = true
    }

    abstract suspend fun doRun()
}

// 구현 예시
@Component
class Example1 : ExactlyOnceRunWarmer() {
    override suspend fun doRun() {
        delay(1_000) // 실제로 웜업을 위한 코드를 작성하면 된다.
    }
}
 
// 구현 예시
@Component
class Example2 : ExactlyOnceRunWarmer() {
    override suspend fun doRun() {
        delay(5_000)
    }
}
 
// 구현 예시
@Primary
@Component
class CompositeWarmer(
    private val warmers: Collection<Warmer>,
) : ExactlyOnceRunWarmer() {
    override suspend fun doRun() {
        withContext(NonCancellable) { // 이미 실행된 워머는 취소하지 않는다.
            warmers.map {
                async { it.run() }
            }.awaitAll()
 
            if (warmers.all { it.isDone }) {
                setDone()
            }
        }
    }
}

 

Spring Actuator 커스텀 HealthIndicator 추가: AbstractReactiveHealthIndicator를 상속받아 웜업 상태 정보를 제공하는 WarmupHealthIndicator를 구현한다.

// AbstractReactiveHealthIndicator를 상속해 웜업 상태 정보를 제공합니다.
@Component
class WarmupHealthIndicator(
    private val warmer: Warmer,
) : AbstractReactiveHealthIndicator() {
 
    override fun doHealthCheck(builder: Health.Builder): Mono<Health> {
        return mono {
            warmer.run() // 편의상 실행과 검사를 인디케이터에서 처리합니다.
            val health = builder.also {
                if (warmer.isDone()) { // 웜업이 완료되면 상태를 UP으로 노출합니다.
                    it.up()
                } else { // 웜업이 아직 완료되지 않았으면 상태를 DOWN으로 노출합니다.
                    it.down()
                }
            }.build()
 
            return@mono health
        }
    }
}

Spring Actuator 설정 변경: 준비성 상태 진단 기능에 웜업 상태 진단 기능을 통합한다.이러한 웜업 구현을 통해 Spring Boot 애플리케이션의 초기 성능을 개선하고 Kubernetes 환경에서 안정적인 서비스 운영을 할 수 있다.

management:
  endpoint:
    health:
      group:
        readiness:
          show-components: always
        liveness:
          show-components: always
          exclude:
            - warmup # 활성 그룹에서 warmup HealthIndicator를 제외한다.

 

Kubernetes 프로브 설정: 웜업 상태를 포함한 애플리케이션 준비성 상태 진단을 위한 기능을 준비한 뒤 파드 설정을 변경한다. 

apiVersion: apps/v1
kind: Deployment
# 생략
spec:
  template:
    spec:
      containers:
        - name: example
          image: example
          ports: # 컨테이너가 사용할 포트를 지정
            - name: http
              containerPort: 8080
              protocol: TCP
            - name: actuator
              containerPort: 8081 # 외부에 Actuator 엔드포인트를 노출하지 않기 위해 별도 포트로 관리
              protocol: TCP
          livenessProbe:
            httpGet:
              path: "/actuator/health/liveness"
              port: actuator
          readinessProbe:
            httpGet:
              path: "/actuator/health/readiness"
              port: actuator

 

ㅁ 마무리

 JVM 안에서도 Thread를 생성하고 메모리를 할당할 때에 많은 초기 리소스를 차지하게 된다. 이러한 초기 세팅으로 인한 콜드 스타트를 개선하기 위한 웜업 방식을 처음 알게 되었다.  Line Engineering - Spring Boot + Kubernetes 기반에서 웜업 적용하기를 통해 웜업 기술을 이해하고 다음에 적용해 볼 수 있을 것이다.

 

ㅁ 함께 보면 좋은 사이트

 Line Engineering - Spring Boot + Kubernetes 기반에서 웜업 적용하기

반응형
Comments