들어가기 전
배포는 운영 과정에서 중요한 과정중 하나이다.
왜냐하면 아무리 코드를 잘 짜도, 사용자에게 전달되지 않으면 아무 의미가 없기 때문이다.
그리고 배포가 늦어지거나, 배포 후 장애가 발생하면?
사용자는 바로 화가 난다.
왜냐하면 불편하니까

배포가 불안정하면 서비스 신뢰도도 함께 떨어진다.
겉으로 보면 배포는 “코드 다 짰으니까 서버에 올리면 끝 아닌가?” 처럼 보일 수 있다.
하지만 실제로는 다음과 같은 요소들을 고려해야 한다.
- 서버 환경 구성
- 빌드 및 이미지 관리
- 설정 분리 (local / prod)
- 로그 및 모니터링
- 롤백 전략
- 무중단 배포 전략
배포는 단순한 실행이 아니라,
서비스를 안정적으로 운영하기 위한 엔지니어링 영역이다.
따라서, 이번 글에서는 그중에서도 무중단 배포에 대해 정리해보려 한다.
1. 가장 간단한 배포 방식
가장 단순한 배포 방식은 다음과 같다.
“jar 파일 실행하면 끝!”
예전에 클라우드에 배포해본 경험을 정리한 글도 있다.
https://zoozoozoo.tistory.com/621
그 당시에는 이 방법 말고는 다른 선택지가 없는 줄 알았다.
그 과정은 다음과 같은데
1. 실행 중인 서버 종료
2. 이 순간부터 서비스 다운 (사용자 접속 불가)
3. 새 버전 시작
4. 서비스 복구
하지만 2번과 3번 사이에는 명확한 문제가 존재한다.
그 시간 동안 사용자가 접속하면 502, 503과 같은 오류가 발생한다.
서버가 꺼져 있는 동안 사용자는 아무것도 할 수 없다. 그리고 사용자는 생각보다 이런 상황에 관대하지 않다.
생각보다 긴 애플리케이션 기동 시간
예를 들어 Spring Boot 기반 애플리케이션은 단순한 프로젝트라도 기동에 10~20초 정도가 걸리는 경우가 많다.
- JPA 초기화
- 커넥션 풀 생성
- Bean 등록
- 등등...
서비스 규모가 커질수록 기동 시간은 더 길어진다.
즉, 단순히 “잠깐 껐다 켜는 것”이 아니라 사용자 입장에서는 몇 초에서 수십 초 동안 서비스가 완전히 멈추는 상황이 된다.
왜 이 방식이 위험할까?
- 트래픽이 몰리는 시간에 배포하면 즉시 장애처럼 보일 수 있다.
- 기동 중 오류가 발생하면 복구 시간은 더 길어진다.
- 무엇보다, 배포 자체가 두려워진다.
배포가 두려워진다는 것은 굉장히 위험한 신호다. 배포가 불안정하면 새로운 시도 자체를 꺼리게 된다.
그건 개발자의 문제가 아니라, 시스템 구조의 문제다.
우리는 문제를 해결해야하는 엔지니어기 때문이다.

2. 무중단 배포를 하기 전에 알아야 할 것
무중단 배포를 하려면 먼저 이해해야 할 전제가 있다.
기존 버전과 새 버전이 동시에 떠 있어야 한다.
그러기 위해서는 동일한 실행 환경을 여러 개 띄울 수 있어야 한다.
기존 방식은 서버 환경에 강하게 의존한다.
기존 방식의 배포
./gradlew build
scp build/libs/app.jar user@server:/app/
ssh user@server "java -jar /app/app.jar"
Gradle 로 JAR를 빌드하고 scp로 서버에 복사한 뒤 SSH로 접속해 실행하는 방식이다.
직관적이고 단순하다.
JAR는 JVM만 있으면 실행 가능하다. 이론적으로는 “어디서나 실행 가능”하다.
하지만 현실은 다르다.
환경 불일치 문제
- JDK 버전이 다르다.
- OS 설정이 다르다.
- 환경변수가 다르다.
- 라이브러리 버전이 다르다.
이 중 하나라도 어긋나면 이런 말이 나온다.
“내 컴퓨터에서는 되는데 서버에서는 안 됩니다.”
문제의 본질은 이것이다. 애플리케이션이 서버 환경에 종속된다.
이 상태에서는 안전하게 두 개를 동시에 띄우는 것이 어렵다.
따라서, 이 문제를 해결하기 위해 도커를 활용하였다.
3. 왜 Docker로 배포하는가
이 문제를 해결해주는 것이 Docker 이다.
Docker는 애플리케이션과 실행 환경 전체를 Image라는 단위로 패키징한다.
이미지가 동일하면 어디에서 실행하든 환경이 동일하다.
서버에 무엇이 설치되어 있는가? 를 걱정하는 대신 이미지를 실행하는 개념으로 전환한다.
핵심 용어
| 용어 | 설명 |
|---|---|
| Image | 실행 환경의 스냅샷 (읽기 전용) |
| Container | Image를 실행한 인스턴스 |
이제 우리는 동일한 환경의 컨테이너를 여러 개 띄울 수 있는 기반을 갖추었다.
그리고 이것이 무중단 배포의 첫 번째 조건이다.
4. nginx 리버스 프록시
무중단 배포의 두 번째 조건은 트래픽을 제어할 수 있어야 한다는 것이다.
이를 위해 우리는 Nginx 를 사용한다.
Nginx는 웹 서버이자 리버스 프록시 서버로, 클라이언트 요청을 내부 애플리케이션 서버로 전달하는 역할을 한다. 무중단 배포에서는 트래픽 전환을 담당하는 핵심 구성 요소다.
리버스 프록시 구조
클라이언트 → nginx(:80) → Spring Boot(:8080)
브라우저는 80번 포트로 접속한다. nginx가 내부적으로 Spring Boot로 요청을 전달한다.
중요한 점은 이것이다.
nginx는 트래픽의 방향을 바꿀 수 있다.
이 기능이 바로 Blue-Green 배포의 핵심이다.
nginx reload vs restart
sudo nginx -s reload
sudo systemctl restart nginx
reload→ 기존 연결 유지, 설정만 재적용 (무중단)restart→ 프로세스 완전 재시작 (짧은 다운타임 발생)
Blue-Green 전환 시 reload를 사용해야 한다.
nginx.conf 구조 (예시)
※ 아래 설정은 이해를 돕기 위한 예시이며, 실제 운영 환경의 IP·도메인·경로 정보는 보안상 일부 수정되었습니다.
백엔드 서버 그룹 정의 (Blue-Green 전환 대상)
# 백엔드 서버 그룹 정의 (Blue-Green 전환 대상)
upstream backend_app {
server 127.0.0.1:8080; # 예시 포트 (배포 시 8080 ↔ 8081 전환)
keepalive 32; # upstream 연결 재사용
}
server {
listen 80;
server_name example.com; # 실제 도메인 대신 예시 사용
# SSE(Server-Sent Events) 전용 설정
location /api/stream/ {
proxy_pass http://backend_app;
proxy_buffering off; # 중요: SSE는 반드시 버퍼링 비활성화
proxy_read_timeout 3600s; # 장시간 연결 유지
proxy_set_header Connection '';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# 일반 API 및 SPA 요청
location / {
proxy_pass http://backend_app;
proxy_read_timeout 60s;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
}
5. Blue-Green 무중단 배포
Blue-Green은 두 개의 동일한 환경을 번갈아 사용하는 방식이다.
[초기 상태]
nginx → Blue(8080) 운영 중
Green 대기
[배포 시]
1. Green(8081)에 새 버전 실행
2. 헬스체크 통과 확인
3. nginx upstream을 Green으로 변경 (reload)
4. Blue graceful 종료
[완료]
nginx → Green(8081)
3번 순간이 바로 배포 완료다. 사용자는 이 전환을 느끼지 못한다.
다른 무중단 배포 방식과 비교
| 방식 | 특징 | 적합 환경 |
|---|---|---|
| Blue-Green | 통째로 전환, 즉시 롤백 가능 | 단일 서버 + nginx |
| Rolling Update | 하나씩 교체 | 다중 인스턴스 |
| Canary | 일부 트래픽만 전환 | 대규모 서비스 |
6. Graceful Shutdown
Graceful Shutdown 을 설정 해주어야 한다.
Graceful Shutdown은 말 그대로 우아한 종료를 의미한다.
즉, 서버를 즉시 강제로 종료하는 것이 아니라 현재 처리 중인 요청을 모두 마친 뒤 종료하는 방식이다.
Blue-Green 배포에서 nginx가 새 버전으로 트래픽을 전환했다고 해서 모든 작업이 끝난 것은 아니다.
기존 컨테이너(Blue)에는 여전히 처리 중인 요청이 남아 있을 수 있다.
이 상태에서 컨테이너를 강제로 종료하면, 사용자는 갑자기 연결이 끊기는 경험을 하게 된다.

이를 방지하기 위해 Spring Boot 에서는 다음과 같이 설정할 수 있다.
server:
shutdown: graceful
spring:
lifecycle:
timeout-per-shutdown-phase: 30s
server.shutdown: graceful→ 즉시 종료하지 않고, 진행 중인 요청을 먼저 처리timeout-per-shutdown-phase: 30s→ 최대 30초까지 종료를 기다림
그리고 컨테이너는 다음과 같이 종료한다.
docker stop --time=30 shareweplan-blue
docker stop은 기본적으로 SIGTERM 신호를 보내고, 지정한 시간 동안 애플리케이션이 정상 종료되기를 기다린다.
30초 안에 요청 처리가 완료되면 정상 종료되고, 초과하면 강제 종료(SIGKILL)된다.
이 설정을 통해 기존 요청을 안전하게 마무리한 뒤 서버를 종료할 수 있다.
마무리
지금까지 다뤄본 Blue-Green 방식은 단일 서버에서 강력한 방식이다.
다중 서버 환경에서는 더 좋은 방식이 존재한다.
그럼 정리해보면, 무중단 배포의 핵심은 세 가지다.
- 동일한 실행 환경을 여러 개 띄울 수 있어야 한다 → Docker
- 트래픽을 제어할 수 있어야 한다 → nginx
- 기존 요청을 안전하게 종료해야 한다 → Graceful Shutdown
이 세 가지가 결합될 때 가장 간단한 무중단 배포의 구성이 이루어질 수 있다.
배포는 단순한 기술이 아니라, 서비스 신뢰를 지키는 방법이라는 것을 최근에 더욱 더욱 알게 되는거 같다.
사실 더 많은 경우의 수 사건이 존재하겠지만, 아직 인프라에 대하여 공부를 하는 입장에서
여기 정도의 지식을 기반으로 파악하고
추후 더 추가될 내용 고려해야할 것이 있다면 나중에 한번 다시 다뤄보겠다.
아무튼 이번 글 끝!
'인프라' 카테고리의 다른 글
| 인프라 구성 로드 밸런서의 이해 (0) | 2026.02.15 |
|---|---|
| [인프라] 서버의 고가용성을 높여보자 (0) | 2026.02.01 |