최근 KBO:NOTE 프로젝트에서 Docker + Nginx + GitHub Actions 기반 Blue-Green 무중단 배포 환경을 구축하면서 몇 가지 예상하지 못한 문제를 겪었다.
이번 글에서는 배포 과정에서 실제로 발생했던 문제와 해결 과정을 정리해보려고 한다.
1. Health Check 실패로 배포 중단
문제 상황
배포 로그에서 Health Check 단계에서 반복적으로 실패했다.
예시 로그
===== Health Check =====
Health check retry...
Health check retry...
Process exited with status 1
하지만 서버에서 직접 확인해보면 서비스는 정상적으로 실행되고 있었다.
curl localhost:8081/api/v1/feeds/health
응답
{"service":"kbo-feed-service","status":"UP"}
즉 서비스는 정상인데 배포 스크립트는 실패로 판단하는 상황이었다.
원인
Health Check 검증을 다음 코드로 구현해 두었다.
STATUS=$(curl -s http://localhost:$TARGET_PORT/api/v1/feeds/health | grep '"status":"UP"')
이 방식은 문자열 매칭에 의존하기 때문에 환경에 따라 매칭이 실패할 수 있다.
특히 CI 환경에서는
- JSON formatting 차이
- 공백 문제
- grep 매칭 실패
등으로 인해 STATUS 변수가 비어있는 경우가 발생했다.
그 결과 스크립트는 다음 로직에 의해 배포 실패로 판단했다.
if [ -z "$STATUS" ]; then
exit 1
fi
해결 방법
Health endpoint 자체가 정상인지 직접 확인하여 문제 원인을 파악했고,
Health Check 로직을 retry 기반으로 유지하여 애플리케이션 기동 시간을 고려하도록 구성했다.
2. Blue / Green 컨테이너가 동시에 실행되는 문제
문제 상황
배포 후 서버 상태를 확인했을 때 다음과 같이 두 개의 컨테이너가 동시에 실행되고 있었다.
docker ps
feed-blue
feed-green
Blue-Green 배포에서는 항상 하나의 컨테이너만 실행되는 것이 정상이다.
원인
배포 스크립트의 흐름은 다음과 같다.
새 컨테이너 실행
→ Health Check
→ Nginx 트래픽 전환
→ 이전 컨테이너 제거
하지만 Health Check 단계에서 배포가 실패하면 스크립트가 중간에서 종료된다.
exit 1
이 경우 마지막 단계인 이전 컨테이너 제거 코드가 실행되지 않는다.
docker stop $CURRENT
docker rm $CURRENT
그래서 서버에는
feed-blue
feed-green
두 컨테이너가 동시에 남게 되었다.
해결 방법
Health Check 문제를 해결한 뒤 배포가 정상적으로 끝나면서 이전 컨테이너 제거 로직이 정상적으로 실행되었다.
이후에는 항상 하나의 컨테이너만 실행되는 상태가 유지되었다.
3. Nginx 설정 파일 포트가 깨지는 문제
문제 상황
배포 과정에서 Nginx 설정 테스트 단계에서 다음과 같은 에러가 발생했다.
invalid port in "9808180818081" of the "listen" directive
또는
nginx: configuration file test failed
Nginx 설정 파일을 확인해보니 listen 포트가 다음과 같이 비정상적인 값으로 변경되어 있었다.
listen 9808180818081;
정상적인 설정은 다음과 같아야 한다.
listen 9000;
원인
배포 스크립트에서 Nginx 설정을 변경하기 위해 다음과 같은 sed 명령어를 사용하고 있었다.
sed -i "s/$CURRENT_PORT/$TARGET_PORT/g" $NGINX_CONFIG
이 명령어는 설정 파일 전체에서 해당 숫자를 모두 치환한다.
예를 들어 설정 파일이 다음과 같을 경우
listen 9000;
proxy_pass http://127.0.0.1:8081;
여러 번 배포가 반복되면서 sed 명령이 계속 실행되고 숫자가 누적되면서 다음과 같은 값이 만들어질 수 있다.
listen 9808180818081;
즉 포트 치환 범위가 너무 넓어서 Nginx 설정이 깨지는 문제가 발생했다.
임시 복구
우선 Nginx 설정 파일을 직접 수정하여 정상 값으로 복구했다.
listen 9000;
proxy_pass http://127.0.0.1:8081;
이후 설정이 정상인지 확인했다.
sudo nginx -t
정상 결과
syntax is ok
test is successful
근본 해결
문제의 원인이었던 sed 명령어를 수정했다.
기존 방식
sed -i "s/$CURRENT_PORT/$TARGET_PORT/g" $NGINX_CONFIG
이 방식은 설정 파일 전체를 치환하기 때문에 위험하다.
대신 proxy_pass 라인만 변경하도록 범위를 제한했다.
sed -i "s|proxy_pass http://127.0.0.1:$CURRENT_PORT|proxy_pass http://127.0.0.1:$TARGET_PORT|" $NGINX_CONFIG
이렇게 수정하면 listen 포트에는 영향을 주지 않고 트래픽을 전달하는 포트만 변경할 수 있다.
4. Health Check 타이밍 문제
문제 상황
새 컨테이너 실행 직후 Health Check가 실패하는 경우가 있었다.
Health check retry...
Health check retry...
서비스 정상
원인
Spring Boot 애플리케이션이 완전히 기동되기 전에
Health Check 요청이 먼저 실행되었기 때문이다.
즉 애플리케이션 startup time 문제였다.
해결 방법
Health Check 로직에 retry 구조를 적용했다.
for i in {1..10}
do
sleep 3
최대 30초 동안 애플리케이션 기동을 기다리도록 구성했다.
마무리
Blue-Green 배포 자체는 구조가 단순해 보이지만 실제로 구현해보면 다양한 문제를 마주하게 된다.
특히 다음 세 가지가 중요하다는 것을 느꼈다.
- Health Check 로직 안정성
- 배포 실패 시 상태 관리
- Nginx 설정 변경 방식
이번 경험을 통해 무중단 배포 환경을 실제로 운영하면서 발생할 수 있는 문제들을 직접 해결해볼 수 있었다.
'💻 프로젝트 > 트러블 슈팅' 카테고리의 다른 글
| [QRoad] 서버는 빠른데 로컬만 느린 API, N+1 병목 줄이기 (0) | 2026.04.04 |
|---|---|
| [QRoad]운영 전 API 부하 테스트를 진행하며 403을 마주하다 (문제 추적 과정 정리) (0) | 2026.03.02 |
| 나는 -i 옵션을 줬는데, 왜 다른 SSH 키가 사용됐을까? (0) | 2026.02.26 |
| [QRoad]PDF 기반 publication 업로드 구조 개선기 - 3편: Spring Boot 최소 구현 (0) | 2026.02.11 |
| [QRoad]PDF 기반 publication 업로드 구조 개선기 – 2편: S3 presigned URL 설계 (0) | 2026.02.11 |