일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- 파라매틱서치
- Kafka
- 알고리즘
- 전역에러처리
- microtask
- 23289
- PS
- hash
- 25186
- Bitwise AND
- 귀납적증명
- node-cron
- nextTick
- eventLoop
- macrotask
- firebase functions
- firebase functions deploy limit
- Docer
- 23560
- 20309
- 1781
- 백준
- node.js
- 코드리뷰를꼼꼼히하자
- promise.race
- ad-hoc
- 25635
- graceful shutdown
- Java
- BOJ
- Today
- Total
웰제오의 개발 블로그
AWS ECS graceful shutdown 설정 및 트러블 슈팅 본문
AWS ECS 는 Elastic Container Service 의 줄임말로,
AWS 에서 제공하는 컨테이너 오케스트레이션 ( Container Orchestration ) 서비스이다.
비슷한 컨테이너 오케스트레이션 서비스로는 도커 스웜 ( Docker Swarm ) 과 그 유명한 쿠버네티스 ( Kubernetes ) 가 있다.
ECS 는 중소규모 프로젝트에서 컨테이너 오케스트레이션을 수행하기에 적합하며, 러닝커브가 매우 낮다는 장점이 있다.
이번 글에서는 AWS ECS 를 활용해 Docker 컨테이너들을 운용하면서 겪었던 graceful shutdown 설정과, 해당 로직이 수행되지 않았던 이슈의 원인과 해결책에 대해 공유하고자 한다.
graceful shutdown 이 필수인가?
일반적으로는, foreground 에서 실행중인 프로세스에 대해서는 ctrl + c
를,
background 로 실행중인 프로세스에 대해서는 해당 프로세스의 pid 로 kill -9 {pid}
커맨드를 실행함으로서 프로세스를 종료하게 되는데, 위 행동은 각각 SIGINT, SIGKILL 시그널을 프로세스에 전송하는 것으로, 이 경우 프로세스가 강제로 종료된다.
다만 위와 같이 프로세스를 강제로 종료하게 되면 문제를 야기할 수도 있고, 이러한 프로세스들은 특정 Signal 에 대한 핸들러를 등록하고, 종료 상황에 해당 Signal 전송함으로서, 자원들을 모두 반납하고 안전하게 종료되게 구성해야 한다.
실제로 필자는 graceful shutdown 구성 없이 카프카 컨슈머들을 운용하다가, 컨슈머들 몇몇이 브로커에 연결을 끊지 않고 종료됨에 따라 필요 이상으로 컨슈머 그룹의 리밸런싱이 자주 일어나는 이슈를 겪었었다.
ECS 환경에서의 Graceuful shutdown 구성
ECS 에서는 실행중인 컨테이너 ( Task ) 의 라이프 사이클을 통해 사용자의 여러 편의성을 제공한다
ECS 에서 도커 이미지를 실행하려면,
- ECR ( Elastic Container Registry ) 에 빌드한 도커 이미지 배포
- Task 라는 이름의 도커 이미지 런타임 설정 ( 실행할 이미지, cpu, memory, log 설정 등등.. )
- Task 의 단독실행, 혹은 Service 라는 이름의 서비스로 ( 기능 이름이 Service 다... ) 실행할 하나 이상의 Task 의 로드밸런싱 혹은 최소 실행 인스턴스 관리를 수행
의 과정을 거친다
실행중인 Task 혹은 Service 를 중지시키면, Task 들은 Stopping
상태에 돌입하게 되고, 이 때 Task 들에게 SIGTERM signal 이 전송된다
AWS 공식 문서에서 설명하는 graceful shutdown 구성 방법이다.
SIGTERM signal 전송 후, 일정 시간이 지나고 ( default 30초 ) SIGKILL signal 을 보내 Task 를 완전히 종료 시킨다고 나와있다. 따라서 Node.js 기준으로 다음과 같이 코드를 작성해 graceful 하게 프로세스를 shutdown 할 수 있게 된다
process.on("SIGTERM", () => {
gracefulShutdown(); // graceful shutdown 로직을 수행하는 함수
process.exit(); // default 핸들러를 덮어 쓰는 것 이므로, 꼭 종료를 해줘야 한다
});
그러나
필자는 위와 같이 코드를 작성했지만, 어찌된 일인지 graceful shutdown 로직이 수행되지 않았다...
stopTimeout 설정도 건드려보고, 로컬에서 테스트를 진행하며 SIGTERM signal 이 잘 핸들링 되는지도 확인해봤지만 여전히 이유를 알 수가 없었다.
황당하게도 이는 Dockerfile 의 CMD 형식이 원인이었다.
Docker CMD 형식에 따른 컨테이너 내부 process hierachy
Dockerfile 을 작성하는데 있어서 해당 컨테이너가 시작될 때 실행할 명령어를 CMD 에 명시하는데, 이는 위의 사진에서 명시하듯 총 3가지 방법이 있다.
필자는 마지막인 shell form 으로 도커파일을 작성했었고, 이게 graceful shutdown 로직이 수행되지 않았던 원인이었다. 이제 그 이유를 알아보자.
앞으로 진행할 실험에서는 아래와 같이 작성한 쉡 스크립트 파일 ps.sh
을 실행할 것 이다.
#!/bin/bash
ps x;
아래와 같이 CMD 를 exec form 으로 작성한 도커파일을 빌드해 실행하면
FROM ubuntu
COPY ./ps.sh ./ps.sh
CMD ["/bin/sh", "./ps.sh"]
위와 같은 결과가 나온다.
헌데 exec form 이 아닌 shell from 으로 작성하게 되면
FROM ubuntu
COPY ./ps.sh ./ps.sh
CMD /bin/sh ./ps.sh
쉘 스크립트가 1 번 프로세스로서 CMD 에 명시한 커맨드를 실행하고,
정작 실행하고자 한 작업은 쉘 스크립트의 자식 프로세스로서 실행되며, pid 7 을 할당받은것을 확인할 수 있다.
AWS 공식문서에서 설명한 글을 다시 보자
graceful shutdown 시 SIGTERM signal 을 PID 1 프로세스에 전송한다고 되어있는것을 확인할 수 있다.
따라서 도커파일의 CMD 를 필자와 같이 shell form 으로 작성할 경우, ECS 는 SIGTERM signal 을 PID 1 프로세스에게 정상적으로 전송했지만, 이는 내가 실행하고자 한 프로세스가 아닌, 쉘 프로세스에게 전송되었던 것이고, 따라서 graceful shutdown 로직이 수행되지 않았던 것이다.
CMD 를 shell form 이 아닌 exec form 으로 수정하면서 해당 이슈를 해결할 수 있었다.
트러블 슈팅을 진행할 때 코드랑 ECS 에서만 원인을 찾으려 하다 보니, 원인파악에 시간이 오래 걸렸었다.
앞으로 트러블 슈팅을 진행함에 있어서 종단 지점이 아닌, 전체 과정 하나하나를 다시 살펴봐야 한다는 교훈을 얻었다.
'개발' 카테고리의 다른 글
[Node.js] Thread Hang 을 야기할 수 있는 작업의 핸들링 (Promise.race, Worker Thread) (1) | 2022.10.27 |
---|---|
배포 프로세스 최적화를 통한 GCP functions deploy limit 해결 (0) | 2022.10.22 |
Node.js 환경에서의 반복작업 수행 (0) | 2022.10.19 |
kafka-consumer-groups.sh NullPointerException 트러블 슈팅 (2) | 2022.10.04 |
Node.js 에서 전역 에러 처리하기 (0) | 2022.09.24 |