1. 배포할 때마다 멈추는 내 서버, 무엇이 문제일까?
개인 프로젝트를 운영하다 보면 사양이 넉넉하지 않은 환경(GCP의 초소형 인스턴스, 라즈베리 파이 등)을 마주하게 됩니다. 저는 여러개의 서버머신을 운영하고 있습니다. AI추론 및 훈련을 위한 고사양 머신부터 정말 네임서버만 간신히 돌아가는 VM까지 다양한 머신들을 돌보고 있지요. 특히 Raspberry Pi 5를 좋아합니다. 24시간 돌려도 전기요금 부담이 없고 성능도 단단하다고 생각합니다. 다른 서버들이 관리 대상인 가축처럼 느껴진다면, Raspberry Pi는 정이 가는 애완동물처럼 느껴진달까요?
하지만 이 애완동물에게 너무 많은 일을 시키다 보니 배포 시 CPU가 100%를 치는 문제가 발생했습니다. 원인은 Celery Worker였습니다. 무중단 배포를 위해 Blue와 Green 그룹을 동시에 띄우면 순간적으로 Worker 수가 2배가 되어 시스템이 감당하지 못하는 것이었죠. Worker 수를 줄이자니 처리 속도가 아쉽고, 그대로 두자니 서버가 터지는 진퇴양난의 상황이었습니다.

이 문제를 해결하기 위해, 리소스 소모를 최소화하면서도 안정성을 확보한 커스텀 Blue-Green 배포 스크립트를 제작하게 되었습니다.
2. 해결 전략: 자원은 아끼고, 안정성은 높이고
단순한 Blue-Green 배포는 두 환경을 동시에 띄우지만, 저는 CPU 부하를 줄이기 위해 다음과 같은 전략을 짰습니다.
- 배경 서비스(Celery) 선제 중지: 새 버전의 웹 서버가 올라갈 CPU 자원을 확보하기 위해, 구 버전의 무거운 백그라운드 작업(Worker, Beat)을 먼저 멈춥니다.
- 단계별 기동: 한꺼번에 모든 서비스를 올리지 않고
Web + Redis를 먼저 올려 헬스체크를 진행합니다. - Human-in-the-loop: 모든 자동화가 끝난 후, 관리자가 직접 눈으로 확인한 뒤 구 버전을 삭제하는 '최종 승인' 단계를 두었습니다.
CPU를 혹사시키다가 터질때마다 분석을 해보면, 서두에서 짧게 설명했지만 주요 원인은 celery worker들이 초기화되고 초기화되자마자 작업들을 부여받고 받자마자 실행해야하는 작업들이 몇개 있다보니 여기서 CPU가 터지는 것이죠. 즉, Celery Worker의 컨커런시(Concurrency) 설정을 낮추면 해결될 문제였지만, 배포 중 비동기 작업 처리 속도가 느려지는 것은 원치 않았습니다.
머신을 좀 가볍게 해야겠다.라고 생각을 하던 와중에 떠오른 아이디어가 바로 재배포시 무중단을 유지하면서도 이전의 celery친구들을 잠시 멈추게 해서 cpu를 확보한 다음 새로운 코드를 배포하는 방식이었습니다.
3. 핵심 코드 살펴보기
전체 코드는 제 GitHub 저장소에서 확인하실 수 있습니다. 스크립트의 핵심 로직 몇 가지를 소개합니다.
① 프로젝트 격리를 위한 Docker Compose 활용
docker compose -p 옵션을 사용하여 동일한 설정 파일로도 blue와 green이라는 별도의 프로젝트(네임스페이스)를 생성합니다.
dc() {
# 프로젝트 이름(-p)을 동적으로 지정하여 환경 격리
docker compose -f "$COMPOSE_FILE" "$@"
}
② 철저한 헬스체크 (Health Check)
새 버전이 완벽히 준비되었는지 확인하기 전까지는 절대 트래픽을 전환하지 않습니다.
health_check() {
# 특정 포트가 200 OK를 응답하는지 10회 재시도
if curl -fsSIL --max-time "$HEALTH_TIMEOUT" "$url"; then
ok "Health check passed"
return 0
fi
}
③ 실패 시 시치미 떼고(?) 복구하기
만약 새 버전에 문제가 있다면, 중지했던 구 버전의 서비스를 즉시 다시 살려 사용자에게 장애가 노출되지 않도록 합니다.
그래서 이전 서비스의 celery worker와 beat를 rm -f 로 완전히 죽이지 않고 혹시나 모를 장애에 긴급히 서비스에 투입을 시켜야 하기 때문에 빠르게 복구가 가능한 stop을 사용하여 cpu를 확보하였습니다.
이 덕분에 장애시 다시 worker , beat가 재투입되는 상황에서 up -d 만으로 빠르게 아무 일 없었다는 듯이 기존 작업들을 이어서 할 수 있었습니다.
4. 운영 노하우: "확인 후 삭제"의 미학
스크립트의 마지막은 구 버전을 자동으로 삭제하지 않고 관리자에게 안내 메시지를 띄우는 것으로 끝납니다.
"배포는 성공했습니다. 하지만 직접 접속해서 확인해 보세요. 이상 없다면 아래 명령어를 복사해서 예전 버전을 정리하세요."
이 한 줄의 안전장치가 자동화 시스템이 놓칠 수 있는 1%의 실수를 방지해 줍니다.
5. 마치며
제한된 리소스 안에서 최선의 효율을 내기 위한 고민들이 이 스크립트에 담겨 있습니다. 변변치 않은 코드지만 제가 실제로 크게 만족하고 사용하고 있기 때문에 분명 이 지구상의 다른 누군가도 비슷한 고민을 하고 있을 지 모른다는 생각에 스크립트를 공유하게 되었습니다. 제 스크립트로 저와 비슷한 고민을 하는 소규모 개발자분들에게 도움이 되길 바랍니다.
관련 링크:
-
문의사항은 댓글이나 깃허브 Issue로 남겨주세요!
댓글이 없습니다.