티스토리 뷰
배경
행동대장 프로젝트에 무중단 배포를 도입하기에 앞서서 현재 배포 방식을 돌아봤다. 우리 팀은 도커를 사용해서 SpringBoot 애플리케이션을 띄우고 있다. 배포가 필요할 때는 컨테이너를 강제로 삭제하고 새로운 도커 이미지에서 새로운 컨테이너를 생성한다.
이 방식에는 문제가 있다. 도커 컨테이너를 강제로 삭제하면 컨테이너 내부의 모든 프로세스가 강제로 종료된다. 처리 중인 사용자의 요청도 강제로 종료되고, 사용자는 텅 빈 응답을 받게 된다. 그렇다면 도커 컨테이너를 어떻게 종료해야 SpringBoot 애플리케이션이 사용자의 요청을 모두 처리하고 종료될까?
별도의 애플리케이션을 만들어서 테스트해 보았다. 좌측은 테스트용 API, 우측은 도커 파일이다. API는 테스트를 위해 스레드를 20초 동안 잡고 있다가 사용자에게 응답하도록 구현했다. 그 외에 별도의 설정을 하지 않았다.
시나리오 1: 컨테이너를 강제로 삭제하면?
초기에 아무런 설정이 되어 있지 않은 애플리케이션에 cURL 클라이언트로 요청을 보내고 10초 뒤에 docker rm -f로 컨테이너를 강제로 삭제했다. 그러자 빈 응답을 받았다. 사용자 요청을 처리하는 중에 컨테이너를 강제로 삭제하면 사용자 요청에 제대로 응답하지 못한다.
이유는 docker rm -f는 컨테이너 프로세스에 SIGKILL을 발생하기 때문이다. 프로세스는 즉시 강제로 종료되고 내부 프로세스도 당연히 강제 종료된다.
시나리오 2: Graceful shutdown을 적용하고, 컨테이너를 강제로 삭제하면?
SpringBoot 애플리케이션에 graceful shutdown을 적용하면 어떻게 될까? application.properties에 server.shutdown=graceful을 추가해서 테스트해 보니, 여전히 텅 빈 응답을 받았다.
이것도 시나리오1과 동일하다. 아무리 SpringBoot 애플리케이션에 graceful shutdown이 적용되었어도, 컨테이너 프로세스가 강제 종료되면 별 다른 방법이 없다.
시나리오 3: Graceful shutdown을 적용하고, 컨테이너를 그냥 종료하면?
컨테이너를 삭제하는 게 아니라, 종료하면 어떻게 될까? docker stop 명령어로 종료해 보니, 이제 graceful shutdown이 적용되어 컨테이너가 즉시 종료되지 않고 요청을 모두 처리하고 종료된다.
이유는 docker stop 명령어가 컨테이너에 SIGTERM을 발생하기 때문이다. SIGTERM은 프로세스에게 스스로 종료할 기회를 주어, 컨테이너 프로세스 속 SpringBoot 애플리케이션이 graceful shutdown 할 기회가 주어진다.
시나리오 4: Graceful shutdown을 적용하고 컨테이너를 종료하는데, 요청 대기 시간이 길다면?
시나리오3에서 SpringBoot 애플리케이션에 graceful shudown을 적용하고 도커를 정상 종료하면, SpringBoot는 사용자 요청을 처리하고 정상 응답하는 것을 확인했다.
만약, 응답 시간이 긴 API를 호출한 경우에도 도커 컨테이너가 기다려줄까? 현재 테스트 API는 응답까지 20초를 기다려야 한다. API를 호출하고 즉시 컨테이너를 중단하면, 컨테이너 프로세스는 SIGTERM을 받았지만 SpringBoot 애플리케이션의 graceful shutdown 때문에 20초를 기다려야 프로세스를 종료할 수 있다. 하지만 docker stop을 입력하고 10초가 지나면 도커 컨테이너 프로세스는 종료된다. 이는 docker stop이 SIGTERM을 발생해서 도커 프로세스가 스스로 종료할 기회를 주었지만, 기본 설정이 10초가 지나도 도커 프로세스가 종료되지 않았기에 SIGKILL을 발생하기 때문이다.
마무리
이 글의 내용은 예측할 수 있는 합리적인 결과다. 당연한 내용이라 작성할 필요가 있을지 고민했고, 팀원들에게 공유할 목적으로 작성했다.