반응형
1. K8S 기반 소스 배포 시 기존 POD 내 실행중인 프로세스가 있는 상화에도 불구하고 종료할 것인가?
: No, 처리중인 내역이 모두 정상처리 후에 기존 Pod 를 정리해야 한다.
2. Graceful Shotdown 설정(spring boot 2.3이상 지원)
server:
port: 8080
shutdown: graceful # SIGTERM 수신 시 graceful shutdown 모드 활성화
#생략
spring:
profiles:
active: "local"
lifecycle:
timeout-per-shutdown-phase: 20s #종료대기 시간 설정(최대 허용 시간)
3. 처리 순서
1) K8S SIGTERM 을 보냄
2) spring boot 서버에서 신규 요청을 차단, 기존 요청이 처리 완료까지 대기
3) timeout-per-shutdown-phase 초과 시: 아직 처리중인 요청이 있더라도 강제 종료
4. 특징
1) Netty, Tomcat 모두 지원
2) 기존 in-flignt http 요청만 처리(Kafka 메시지에 대한 처리는 별도 관리 필요 - transactional outbox pattern 및 DLQ 처리 등)
3) Websocket 요청은 별도 처리 필요
4) 애플리케이션 단에서 in-flight 요청/작업 추적과 draining 모드를 함께 구현하는 것이 안전
종료 시 아래와 같은 로그를 확인할 수 있다.

5. in-flight, draining 모드 예시
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.event.ContextClosedEvent
import org.springframework.context.event.EventListener
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Component
import org.springframework.web.server.ServerWebExchange
import org.springframework.web.server.WebFilter
import org.springframework.web.server.WebFilterChain
import reactor.core.publisher.Mono
import java.util.concurrent.Executors
import java.util.concurrent.atomic.AtomicInteger
//전역 애플리케이션 상태 관리
object AppState {
@Volatile
var draining: Boolean = false // SIGTERM 수신 후 draining 모드 여부
val inFlight: AtomicInteger = AtomicInteger(0) // 진행 중 요청/작업 수
}
//HTTP 요청 추적 및 신규 요청 차단 WebFilter
@Component
class InFlightTrackingFilter : WebFilter {
override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
if (AppState.draining) {
exchange.response.statusCode = HttpStatus.SERVICE_UNAVAILABLE
return exchange.response.setComplete()
}
AppState.inFlight.incrementAndGet()
return chain.filter(exchange)
.doFinally { AppState.inFlight.decrementAndGet() }
}
}
//비동기 작업 관리용 예제 Executor
@Component
class AsyncTaskExecutor {
private val executor = Executors.newFixedThreadPool(4)
fun submit(task: Runnable) {
if (AppState.draining) {
println("Draining 중 신규 작업은 거부")
return
}
AppState.inFlight.incrementAndGet()
executor.submit {
try {
task.run()
} finally {
AppState.inFlight.decrementAndGet()
}
}
}
fun shutdown() {
executor.shutdown()
}
}
//SIGTERM 수신 시 draining + in-flight 대기 후 종료
@Component
class ShutdownHandler(private val asyncTaskExecutor: AsyncTaskExecutor) {
@EventListener
fun onContextClosed(event: ContextClosedEvent) {
println("SIGTERM 수신: draining 모드 전환")
AppState.draining = true
// 종료 전 in-flight 작업 완료 대기
while (AppState.inFlight.get() > 0) {
println("in-flight 작업 남음: ${AppState.inFlight.get()}, 대기중")
Thread.sleep(100)
}
println("모든 in-flight 작업완료, Executor 종료")
asyncTaskExecutor.shutdown()
}
}
반응형
'Kotlin Spring > Kotlin Spring' 카테고리의 다른 글
| WebClient vs OpenFeign vs RestTemplate 비교 (0) | 2026.01.01 |
|---|---|
| 왜 많은 팀은 결국 Webflux와 같은 "Reactive Programming"를 포기하는가? (0) | 2025.12.22 |
| Spring Boot warm up 순서 (1) | 2025.08.31 |
| CloudWatchClient로 CPUUtilization 메트릭 Data 가져오기 (feat. CPU 상태 확인) for Kafka Throttling (0) | 2025.04.26 |
| 단방향 암호화 sha512 (0) | 2024.12.18 |