구버전 애플리케이션을 종료시킬 때 고려해야 할 점 중에 하나는 프로세스를 죽이는 방식이다.
프로세스를 바로 죽이게 현재 요청을 받아 처리중인 작업들이 모두 취소가 되는데, 해당 요청이 결제와 같이 중요한 요청이라면?
→ 복구 프로세스를 확립하는 데에 많은 고생을 할 수 있다.
물론 블루그린 방식을 이용해 배포(deploy)를 한다면, 신버전 실행을 위해 구버전 프로세스를 죽일 필요가 없다.
배포 전략에 대해서는 정리가 잘 된 블로그를 첨부한다.
리눅스 환경에서는 프로세스를 죽일 때 kill
명령어를 사용한다.
아래 예시에서 옵션으로 주어진 SIGKILL(9)과 SIGTERM(15)에 대한 설명은 따로 정리한 링크를 첨부한다.
$ kill -9 12345 # PID가 12345인 프로세스를 강제 종료
$ kill -15 12345 # PID가 12345인 프로세스를 (하던 작업을 종료한 뒤) 정상 종료
이제부터 설명에 사용될 쉘 스크립트를 포함한 예제 코드는 깃헙에서 볼 수 있다.
아래 예제 코드는 요청이 들어오면 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
을 지원한다.
이는 스프링 부트 내장 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 동안에는 프로세스가 죽지 않는데, 정지 스크립트를 사용하는 이유는 바로 다음에 기동 스크립트를 실행하기 위함이다.
위 사진과 같이 프로세스가 종료되지 않았는데, 별개로 아래에 작성한 스크립트(= 실행 스크립트)가 바로 실행된다면?
: 실행 스크립트 실행 시에 (종료중인 & 실행할) 애플리케이션끼리 충돌이 발생한다.
: 물론 서버가 다중화되어 있다면 종료한 서버 외에 다른 서버가 트래픽을 받으면 되니 괜찮겠지만, 항상 조심해야 한다.
SIGTERM
이나 SIGINT
와 같은 정상 종료 시그널을 받았을 때 곧바로 애플리케이션을 종료하지 않는다.
잘봤어요