SIGKILL vs SIGTERM 리눅스 종료 신호

IMWEB tech
imweb tech
Published in
5 min readJan 16, 2024

안녕하세요.
아임웹 CTO 매튜입니다.

우리가 만든 서버 프로그램은 리눅스 서버의 한 프로세스로 동작 합니다. 고로, 프로세스로 구동하는 모든 프로그램들도커 nginx node.js Mysql 등의 종료에 관한 메커니즘을 이해한다면, 장애 대비 개발을 할 수 있겠죠?

그런데 요즘 아임웹은 배포에 컨테이너를 굉장히 많이 씁니다. 컨테이너 배포는 앞선 앱을 죽이고 새 앱을 띄우는 행위죠. 따라서 종료가 깔끔해야 오류 가능성도 줄일 수 있겠죠?

그래서 이 글을 작성했습니다.

리눅스 커널의 종료 신호 SIGKILL vs SIGTERM

우리의 개발 결과물은 리눅스 프로세스로 구동 됩니다. Node.jsnode 프로세스 이며, PHPnginx 또는 php-fpm 프로세스 입니다.

우리가 예기치 않은 예외에 잘 대응했다면 OOM이나 Stack Overflow같은 강제 종료 사유 외에는 스스로 종료되지 않죠. 그러나 외부에서 프로세스를 종료할 방법이 있습니다.

커널을 통해 프로세스에 신호를 보낼 수 있는데요. kill -9 같은 명령어를 써보셨을 텐데, 이것은 SIGKILL 이라는 신호를 프로세스에 보내는 것입니다.

이 신호는 KILL에서 느낀 것처럼,

너 당장 죽어!

입니다. 이 신호를 받은 프로세스는 거부 하지도, 반항 하지도, 어떤 말도 하지 못하고 즉시 죽습니다 ㅠㅠ

하지만 프로세스는 뭔가 일을 하고 있었을 텐데요. 데이터가 그렇게 중요한데, 망가지지 않으려면 끝낼 기회는 주어야 하지 않을까요?

그래서 또 다른 SIGTERM이라는 신호가 있습니다. 이 신호는 프로세스에 이렇게 요청합니다.

혹시, 미안한데 지금 죽어 주시면 안 될까요?

그러면, 프로세스는 두 가지 선택을 할 수 있어요.

웃기시네

또는

앗, 알겠습니다. 지금 뭐 하고 있는데요. 요것만 끝내고 알아서 죽을게요~

말 그대로입니다. 무시하든지, 듣든지 프로세스가 결정합니다.

그런데요.

커널이 죽어 달라고 부탁을 하긴 했는데, 프로세스들이 말을 계속 안 듣는 상황이다? 그러면 정해진 시간까지는 기다려 주긴 합니다. 그런데 그 시간이 지나도 안 죽잖아요? 그럼 이제 죄다 SIGKILL을 다시 보냅니다.

이런 식입니다. 리눅스가 제공하는 종료 신호는 이 외에도 여러 개 있는데요. 터미널 명령으로도 얻을 수 있습니다.

$ kill -l
HUP INT QUIT ILL TRAP ABRT EMT FPE KILL BUS SEGV SYS PIPE ALRM TERM URG STOP TSTP CONT CHLD TTIN TTOU IO XCPU XFSZ VTALRM PROF WINCH INFO USR1 USR2

자! 이제 두 신호가 어떻게 동작하는지 실전 상황별 시나리오로 들어오시죠.

상황 1) 리눅스 서버 종료

리눅스 서버는 서버 종료 명령을 받으면 실행 중인 모든 프로세스에 일괄적으로 종료 신호를 보냅니다. 그런데 SIGKILL 을 보낼까요 SIGTERM을 보낼까요?

대략 다음 절차를 거치게 됩니다.

  • 시스템이 shutdown 명령을 받으면,
  • 모든 프로세스에 SIGTERM 신호를 보낸 후
  • 약 5초간 (시스템 설정에 따라 시간은 다를 수 있습니다) 1초마다 프로세스 종료 여부를 체크하여
  • 시간이 지난 후에도 남아있는 프로세스는 SIGKILL을 보냅니다.

참고 링크: 커널의 shutdown

이를 토대로 한다면, 우리 서버 개발자는 SIGTERM을 받으면 몇 초 이내에 돌고 있는 로직들의 안전한 종료 처리를 해야 합니다.

상황 2) AWS의 EC2 Spot 인스턴스 종료

하지만 AWS의 저렴한 Spot Instance는 약간 다릅니다.

SIGTERM 신호를 보내기에 앞서 2분 전에 AWS에서 먼저 EC2의 상태를 바꾸게 됩니다. 이것은 스팟인스턴스의 특성으로 리눅스 표준은 아닙니다.

이 신호는 시스템 커널이 보내는 신호가 아니며, 실제로 서버에는 어떤 이벤트도 받지 못합니다. 다만 AWS의 다른 서비스를 통해 알아낼 수 있습니다.

참고링크:

우리는 이 문서에 따른 여러 가지 방법 중에 AWS의 인스턴스 메타데이터 서비스를 활용합니다.

즉, 우리 로직이 스스로 인스턴스 메타데이터를 Polling 하며 인스턴스의 상태가 바뀜을 감지하면 2분 이내로 종료 처리를 스스로 해야 합니다.

상황 3) 도커 컨테이너 종료 예정 신호

컨테이너 인스턴스로 띄워진 앱은 리눅스 입장에서는 그냥 하나의 프로세스입니다.

따라서, 도커가 구동 중인 호스트 서버가 종료 신호를 받거나, 컨테이너가 docker stop 명령을 받으면 내 앱은 마찬가지로 SIGTERM 신호를 받습니다.
이에 마찬가지로 대응 하면 됩니다.

참고 링크: 도커 컨테이너 정상적 종료

상황 4) Node.js 에서 SIGTERM 신호 핸들링

node에서 SIGTERM 핸들링은 간단합니다. 내장 모듈인 process에서 지원합니다.

const process = require('process');
process.on('SIGTERM', () => {
console.log('SIGTERM');
// Todo: 구독 취소등의 작업을 여기서 실행
// ...
process.exit(0); // 0으로 정상 종료 해주는 것이 좋음
});
  • SIGTERM 외에도 SIGINT 신호 등도 정의하여 잡아낼 수 있습니다.
  • SIGINT는 유저가 ctrl + c등으로 인터럽트를 보낼 때 발생합니다.

다만, SIGKILL은 정의해도 잡을 수 없는데 이 신호는 강제 종료이므로 받자마자 프로그램이 메모리에서 사라지므로 로직을 실행할 기회가 없기 때문입니다. 다른 언어도 비슷한 형태로 잡아낼 수 있어요.

마무리

그동안 단순히 kill -9 로 살아온 당신, 아무 기회도 주지 않는 잔인한 유저 였습니다. 프로세스가 행복하게 일을 마무리 할 기회를 줍시다.

즉, kill -15 를 보내야 SIGTERM 입니다. 그런데 외우기 귀찮죠? 그냥 kill 하시면 기본값이 SIGTERM 입니다.

자, kill 하면 바로 죽지 않아서 kill -9 하신 적 많았다면, 이제는 조금만 (5초만) 기다려 주세요. (주의! 한국인은 힘듭니다.)

오늘도 좋은 하루 되십시오.

매튜 드림.

--

--