우리가 만든 서버 프로그램은 리눅스 서버의 한 프로세스로 동작 합니다.
고로, 프로세스로 구동하는 모든 프로그램들, 예) 도커, nginx, node.js, Mysql
등의
종료에 관한 메커니즘을 이해 한다면, 장애 대비 개발을 할 수 있겠죠?
그런데 요즘 아임웹은 배포에 컨테이너
를 굉장히 많이 씁니다.
컨테이너 배포는 앞선 앱을 죽이고 새 앱을 띄우는 행위죠.
따라서 종료가 깔끔 해야 오류 가능성도 줄일수 있겠죠?
그래서 이 글을 작성 했습니다.
우리의 개발 결과물은 리눅스 프로세스로 구동 됩니다.
Node.js
는 node 프로세스
이며, PHP
는 nginx
또는 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
자! 이제, 두 신호가 어떻게 동작하는지 실전 상황별 시나리오로 들어오시죠.
리눅스 서버는 서버 종료 명령을 받으면 실행중인 모든 프로세스에 일괄적으로 종료 신호를 보냅니다.
그런데 SIGKILL
을 보낼까요 SIGTERM
을 보낼까요?
대략 다음 절차를 거치게 됩니다.
shutdown
명령을 받으면,SIGKILL
을 보냅니다.참고 링크: 커널의 shutdown
이를 토대로 한다면, 우리 서버 개발자는 SIGTERM
을 받으면 몇초 이내에 돌고 있는 로직들의 안전한 종료 처리를 해야 합니다.
하지만 AWS의 저렴한 Spot Instance
는 약간 다릅니다.
SIGTERM
신호를 보내기에 앞서 2분 전에 AWS에서 먼저 EC2의 상태를 바꾸게 됩니다.
이것은 스팟인스턴스의 특성으로 리눅스 표준은 아닙니다.
이 신호는 시스템 커널이 보내는 신호가 아니며, 실제로 서버에는 어떤 이벤트도 받지 못합니다.
다만 AWS의 다른 서비스를 통해 알아낼 수 있습니다.
참고링크:
우리는, 이 문서에 따른 여러가지 방법 중에 AWS의 인스턴스 메타데이터 서비스를 활용합니다.
즉, 우리 로직이 스스로 인스턴스 메타데이터를 Polling
하며 인스턴스의 상태가 바뀜을 감지하면
2분 이내로 종료 처리를 스스로 해야 합니다.
컨테이너 인스턴스로 띄워진 앱은 리눅스 입장에서는 그냥 하나의 프로세스 입니다.
따라서 도커가 구동중인 호스트 서버가 종료 신호를 받거나,
컨테이너가 docker stop
명령을 받으면
내 앱은 마찬가지로 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초만) 기다려 주세요. (주의! 한국인은 힘듭니다.)
오늘도 좋은 하루 되십시오.
매튜 드림.
좋은 글 감사합니다
죽어 주시면 안될까요? 에 웃기시네 ㅋ 까지 너무 웃겨서 댓글 달고 갑니다 ㅋㅋㅋㅋㅋㅋ