[Spring Boot] Graceful Shutdown(feat. 우아한 종료)

이동엽·2023년 10월 24일
2

spring

목록 보기
13/21

개요

구버전 애플리케이션을 종료시킬 때 고려해야 할 점 중에 하나는 프로세스를 죽이는 방식이다.

프로세스를 바로 죽이게 현재 요청을 받아 처리중인 작업들이 모두 취소가 되는데, 해당 요청이 결제와 같이 중요한 요청이라면?
→ 복구 프로세스를 확립하는 데에 많은 고생을 할 수 있다.


물론 블루그린 방식을 이용해 배포(deploy)를 한다면, 신버전 실행을 위해 구버전 프로세스를 죽일 필요가 없다.

배포 전략에 대해서는 정리가 잘 된 블로그를 첨부한다.


Kill

리눅스 환경에서는 프로세스를 죽일 때 kill 명령어를 사용한다.

아래 예시에서 옵션으로 주어진 SIGKILL(9)과 SIGTERM(15)에 대한 설명은 따로 정리한 링크를 첨부한다.

$ kill -9 12345 # PID가 12345인 프로세스를 강제 종료
$ kill -15 12345 # PID가 12345인 프로세스를 (하던 작업을 종료한 뒤) 정상 종료

Example Code

이제부터 설명에 사용될 쉘 스크립트를 포함한 예제 코드는 깃헙에서 볼 수 있다.

아래 예제 코드는 요청이 들어오면 30초간 잠든 뒤, 종료 로그를 출력하도록 작성했다.

@RestController
public class TestEndpoint {

    private final Logger logger =  LoggerFactory.getLogger(this.getClass());


    /**
     * shutdown life cycle이 2초인 설정에서
     * 30초간 Sleep하는 동안 정상 종료(kill -15) 요청이 발생할 경우, 작업 실패 로그가 어떻게 나오는지 확인
     */
    @GetMapping("/test")
    public void test() throws InterruptedException {

        logger.warn("start");

        // 30초간 sleep
        Thread.sleep(5000);

        logger.warn("finish");
    }
}

위 API로 요청을 보낸 후, 30초간 잠든 동안에 kill -9로 프로세스를 종료했을 경우

예상했던 대로 종료 로그는 출력하지 않고, 강제종료 되었다.


동일한 상황에서, kill -15 로 프로세스를 종료했을 경우

로그를 출력하고 프로세스를 종료하길 기대했는데, kill -9 와 마찬가지로 로그 출력을 수행하지 못하고 종료가 되었다.

이와 같이 아무리 kill -15 로 프로세스를 종료한다고 해도, 애플리케이션은 어떻게 종료해야 할 지 몰라 강제종료가 된다.

→ 그래서 Spring Boot는 2.3 버전부터 Graceful Shutdown 을 지원한다.



Graceful Shutdown

이는 스프링 부트 내장 WAS인 Tomcat, Jetty, Undertow, Netty 모두 적용 가능하다.

아래와 같이 옵션을 작성하면 적용할 수 있고, 기본 값은 즉시(immediate) 종료되는 설정이다.

server:
  shutdown: graceful # default immediate

이제 다시 API로 요청을 보낸 후, 30초간 잠든 동안에 kill -15로 프로세스를 종료해보자.

API에 요청이 들어오고 30초동안 잠든 사이에 종료 요청이 도착해도, 해당 작업을 마무리를 한 뒤 애플리케이션이 종료된다.


추가로 고려해야 할 사항들

하필 실행중이던 요청에서 데드락이 발생하는 등의 이유로 프로세스가 종료되지 않는다면?

: 종료되지 않으므로, Shutdown에 대한 타임아웃도 설정해야 한다.

Spring Boot에서는 아래와 같이 만료 시간을 설정할 수 있다. 디폴트 값은 30초다.

spring:
  lifecycle:
    timeout-per-shutdown-phase:10s # default 30s

위에서 설정한 Shutdown Lifecycle보다 실행중인 요청의 작업 시간이 더 길 경우
에는 어떻게 처리될까?

: 아래 첨부한 내용과 같이 Graceful Shutdown이 중단됨 (= 강제 종료됨)을 알린다.

Failed to shut down 1 bean with phase value 2147482623 within timeout of 2000ms

Graceful shutdown with one or more requests still active

그럼 중지 스크립트를 실행해서 프로세스를 중지시킬 때, kill -15 다음에 실행될 스크립트가 있다고 가정하자.

다음에 실행될 스크립트는 애플리케이션이 종료가 된 후 실행될까?

: 애플리케이션 종료와 별개로 바로 실행된다.


이 경우의 문제점

Shutdown Lifecycle 동안에는 프로세스가 죽지 않는데, 정지 스크립트를 사용하는 이유는 바로 다음에 기동 스크립트를 실행하기 위함이다.

위 사진과 같이 프로세스가 종료되지 않았는데, 별개로 아래에 작성한 스크립트(= 실행 스크립트)가 바로 실행된다면?
: 실행 스크립트 실행 시에 (종료중인 & 실행할) 애플리케이션끼리 충돌이 발생한다.
: 물론 서버가 다중화되어 있다면 종료한 서버 외에 다른 서버가 트래픽을 받으면 되니 괜찮겠지만, 항상 조심해야 한다.


정리

  • Graceful Shutdown을 활성화하면 스프링부트 애플리케이션이 SIGTERM이나 SIGINT와 같은 정상 종료 시그널을 받았을 때 곧바로 애플리케이션을 종료하지 않는다.
  • 추가적으로 들어온 요청은 거부하고, 기존에 처리중인 요청은 모두 완료를 한 뒤 프로세스를 종료한다.
    • 추가로적으로 들어온 요청에 대한 처리는 WAS 마다 약간 다르다.
    • Tomcat, Jetty, Netty → 네트워크 계정에서 커넥션이 생기는 것을 거부
    • Undertow → 요청을 받되, 503(Service Unavailable) 응답


참고자료

profile
백엔드 개발자로 등 따숩고 배 부르게 되는 그 날까지

1개의 댓글

comment-user-thumbnail
2023년 10월 24일

잘봤어요

답글 달기