19장. 365일 24시간 무중단 서비스 만들기
18장에서 "죽지 않는 구조"를 봤습니다. 그런데 서비스가 가장 죽기 쉬운 순간이 언제인지 아세요? 역설적이게도 배포하는 순간입니다. 낡은 것을 새것으로 바꾸는 그 틈에 서비스가 끊기기 쉽습니다. 이번 장은 그 틈을 없애는 기술, 무중단 배포입니다.
[지금 여기]
우리는 22단계를 거쳐 "라이브 투표 서비스"를 만들고, 컨테이너에 담아, AWS 클라우드에 배포하는 여정을 지나고 있습니다. 아래가 그 전체 여정 지도입니다. 지금은 19단계, 무중단 배포 차례입니다.
- 1장개발 환경 준비 (터미널 / Node.js / Git / GitHub / VS Code / Claude Code)
- 2장Claude Code 사용법 익히기
- 3장무엇을 만들지 기획 (라이브 투표 서비스)
- 4장로컬에서 서비스 만들기 (React + Express + PostgreSQL)
- 5장Docker 개념
- 6장Dockerfile 작성
- 7장컨테이너 빌드·실행
- 8장AWS 시작 (계정 / IAM / 리전 / 비용)
- 9장이미지 태깅
- 10장ECR에 이미지 올리기
- 11장VPC 네트워크 구성
- 12장RDS 데이터베이스 생성
- 13장SSM 터널로 RDS 초기화
- 14장ECS Fargate + ALB로 배포 (기본 완성)
- 15장CI/CD (GitHub Actions)
- 16장IaC (Terraform)
- 17장배포 자동화 연결
- 18장현업 운영 구조 이해
- 19장무중단 배포
- 20장신규 버전 배포 & 롤백
- 21장(심화) 프론트를 Vercel로 분리 배포
- 22장마무리 (회고 + 리소스 삭제)
- 방금 끝낸 것: 18장에서 우리 서비스의 "현업 운영 구조"를 이해했습니다. 로드밸런서·여러 태스크·헬스체크가 어떻게 서로를 받쳐 주며 서비스를 죽지 않게 하는지를 봤습니다.
- 이번 장에서 하는 것: 그 구조 위에서, 배포하는 동안에도 서비스가 안 끊기는 원리(롤링 배포 + 헬스체크)를 이해하고, 우리 서비스의 무중단 설정을 확인한 뒤, 실제 배포 중에 서비스가 정말 안 끊기는지 감시 실험으로 직접 증명합니다.
이번 장에서 완성되는 것
한 문장으로: 배포가 진행되는 내내 서비스에 1초 간격으로 요청을 보내며,
단 한 번도 실패 없이 200(성공)이 이어지는 것을 눈으로 확인하게 됩니다.
이렇게 되면 성공입니다: 감시 화면에 200이 끊김 없이 이어지는 동안,
ECS 콘솔에서는 구버전 태스크가 새 태스크로 하나씩 갈아 끼워지고,
배포가 끝난 뒤 브라우저에는 바뀐 새 버전이 떠 있습니다. 즉 사용자는
아무것도 눈치채지 못한 채 서비스가 새 버전으로 교체된 것입니다.
사전 조건
이 장은 1~18장을 마친 상태에서 시작합니다. 아래가 갖춰져 있어야 합니다.
- 14장까지 마쳐 ALB HTTP 주소로 접속되는 투표 서비스가 떠 있어야
합니다. 브라우저에서
vote-alb-...ap-northeast-2.elb.amazonaws.com주소로 들어가면 투표 화면이 보여야 합니다. - 15·17장에서 만든 CI/CD 파이프라인이 살아 있어야 합니다. 즉
main브랜치에 코드를 푸시하면 GitHub Actions가 자동으로 빌드하고 ECS에 배포하는 상태여야 합니다. (이번 장의 실험은 이 자동 배포를 일부러 한 번 일으켜서 관찰하는 것입니다.) - VS Code로
vote-app폴더가 열려 있어야 합니다. (VS Code → 파일 → 폴더 열기 → 문서(Documents) 폴더의vote-app선택.) 이번 장의 모든 명령과 프롬프트는 이 VS Code 안의 터미널에서 실행합니다.
이 장에서는 우리가 직접 명령을 외워 칠 일이 거의 없습니다. 감시(1초 간격 요청)와 배포(커밋·푸시)는 모두 Claude Code에게 프롬프트로 시킵니다. 우리는 그 결과를 "읽고 관찰"하는 것이 핵심입니다.
19.1 무중단 배포란 — 다운타임 0
먼저 문제 상황부터 봅시다. "배포"는 낡은 버전을 새 버전으로 바꾸는 일입니다. 그런데 이 교체를 어떻게 하느냐에 따라 서비스가 끊길 수도, 안 끊길 수도 있습니다.
다운타임(Downtime) 서비스가 응답하지 못하는 시간입니다. 사용자가 접속했는데 오류 화면이 나오거나 무한 로딩이 걸리는 그 시간을 말합니다.
가장 순진한 배포를 생각해 봅시다. 구버전 컨테이너를 끄고 → 새 버전 컨테이너를 켠다. 이 사이의 몇십 초, 서비스는 완전히 죽어 있습니다. 사용자 입장에서는 "사이트가 안 들어가지는" 시간이죠. 하루 한 번 배포하면 하루 한 번 끊기는 서비스가 됩니다.
무중단 배포(Zero-downtime Deployment) 배포가 진행되는 동안에도 다운타임이 0인 배포입니다. 사용자는 배포가 일어났다는 사실 자체를 모릅니다. "365일 24시간 서비스"의 필수 조건입니다.
[활용 사례] 무중단 배포가 실제로 어디에 쓰이나
무중단 배포는 특별한 대형 서비스만의 이야기가 아닙니다. 우리가 매일 쓰는 서비스 대부분이 이 방식으로 굴러갑니다.
- 365일 멈추면 안 되는 서비스: 은행 앱, 쇼핑몰, 메신저처럼 언제 들어가도 켜져 있어야 하는 서비스. 새벽 3시에 접속한 사용자에게도 "점검 중"을 보여 줄 수 없으니, 배포는 반드시 무중단이어야 합니다.
- 낮에도, 하루에도 여러 번 배포: 요즘 회사들은 기능 하나 고칠 때마다 배포합니다. 하루에 수십 번씩 배포하는 곳도 흔합니다. 배포할 때마다 서비스가 끊긴다면 이렇게 자주 배포할 수 없습니다. 무중단이라야 "고치면 바로 내보내기"가 가능합니다.
- 사용자 몰래 갈아 끼우기: 사용자가 장바구니에 물건을 담는 그 순간에 배포가 일어나도, 사용자는 아무것도 못 느껴야 합니다. 방금 담은 물건이 사라지거나 결제가 튕기면 안 되니까요.
무중단의 핵심 아이디어는 의외로 간단합니다. "끄고 나서 켜는" 게 아니라, "켜고 나서 끈다." 새것을 먼저 준비시켜 놓고, 새것이 정상임을 확인한 뒤에, 낡은 것을 내리는 순서로 바꾸는 것입니다. 이 아이디어를 구현하는 대표적인 두 방식이 있습니다.
19.2 롤링 업데이트 vs 블루/그린 — 두 가지 방식
방식 1. 롤링 업데이트(Rolling Update) — 한 대씩 갈아 끼우기
롤링 업데이트 여러 대 중 한 대씩(또는 일부씩) 순서대로 새 버전으로 교체하는 방식입니다. 교체되는 동안 나머지가 사용자를 받칩니다. "롤링(rolling)"은 도미노가 한 칸씩 넘어가듯 순서대로 굴러간다는 뜻입니다.
우리처럼 태스크가 2개일 때의 진행을 그림으로 보면:
어느 순간에도 정상 동작하는 태스크가 최소 2대 유지됩니다. 다운타임이 생길 틈이 없습니다.
- 장점: 추가 비용이 거의 없다(잠깐 1대분만 더 씀). 설정이 단순하다.
- 단점: 교체가 진행되는 동안 구버전과 신버전이 잠시 섞여서 서비스된다.
방식 2. 블루/그린 배포(Blue/Green) — 통째로 갈아타기
블루/그린 배포 구버전 전체(블루)를 그대로 둔 채, 새 버전 전체(그린)를 한 벌 더 띄워 놓고, 준비가 끝나면 트래픽을 블루에서 그린으로 한 번에 전환하는 방식입니다. 문제가 생기면 다시 블루로 전환하면 되므로 롤백도 즉시입니다.
- 장점: 구·신버전이 섞이지 않는다. 전환·롤백이 순간이다.
- 단점: 잠시나마 인프라가 두 벌이라 비용이 든다. 구성이 복잡하다.
[활용 사례] 두 방식은 실무에서 이렇게 나눠 씁니다
- 롤링: 대부분의 웹 서비스가 기본으로 쓰는 방식입니다. 추가 비용이 거의 없고 설정이 간단해서, 작은 서비스부터 큰 서비스까지 두루 씁니다. 우리 서비스도 이 방식입니다.
- 블루/그린: 결제·정산처럼 "구·신버전이 잠깐이라도 섞이면 안 되는" 민감한 서비스나, 문제가 나면 1초라도 빨리 되돌려야 하는 서비스에서 씁니다. 대신 잠깐 서버를 두 벌 돌리는 비용을 감수합니다.
우리의 선택: ECS 서비스의 기본 배포 방식이 롤링 업데이트이고, 우리 규모에서는 그것으로 충분합니다. 이 장에서는 롤링을 실습하고, 블루/그린은 "더 큰 서비스에서 쓰는 상위 옵션"으로 기억해 두면 됩니다.
19.3 무중단의 진짜 주역 — ALB 헬스체크
그런데 롤링 그림에서 얼버무린 대목이 하나 있습니다. "신1이 정상 확인되면"이라는 부분입니다. 누가, 어떻게 확인할까요? 바로 2부에 만들고 18장에서 감시망 1겹이라 불렀던 ALB 헬스체크입니다.
배포 중에 벌어지는 일을 자세히 들여다보면:
- 새 태스크가 뜨면, ECS가 그 태스크를 ALB의 타깃 그룹에 등록합니다.
- ALB는 바로 트래픽을 보내지 않습니다. 먼저 헬스체크 요청을 몇 차례 보내서, 연속으로 정상 응답이 와야 "합격(healthy)" 판정을 내립니다.
- 합격한 태스크에만 사용자 트래픽을 보내기 시작합니다.
- 내려갈 구버전 태스크는 그 반대입니다. 먼저 분배 목록에서 빼서 새 요청이 가지 않게 하고, 처리 중이던 요청을 마칠 시간을 준 뒤 종료 합니다.
드레이닝(Draining, 등록 취소 지연) 태스크를 내리기 전에 "하던 일을 마무리할 시간"을 주는 것입니다. 요청을 처리하던 도중에 뚝 끊기는 일을 막습니다. 식당이 문 닫을 때 새 손님은 안 받되, 이미 앉아 식사 중인 손님은 다 먹고 나갈 때까지 기다려 주는 것과 같습니다.
즉 무중단 배포는 롤링(순서)과 헬스체크(검증)의 합작입니다. "검증되지 않은 새것에는 트래픽을 주지 않고, 일하던 낡은 것은 마무리를 기다려 준다" — 이 두 원칙이 다운타임 0을 만듭니다.
여기에는 중요한 함의가 하나 있습니다. 만약 새 버전이 아예 뜨자마자 죽는 수준으로 고장 났다면, 헬스체크 불합격 → 트래픽이 가지 않음 → 구버전이 계속 서비스, 즉 불량 배포가 자동으로 차단됩니다. 하지만 "떠 있고 헬스체크는 통과하는데 특정 기능만 고장"인 경우는 걸러내지 못합니다. (20장의 버그가 정확히 이 유형입니다. 그래서 롤백이 필요합니다.)
19.4 우리 서비스의 무중단 설정 확인
좋은 소식: ECS 서비스는 기본이 롤링 배포라서, 우리는 이미 무중단의 틀 위에 있습니다. 확인하고 의미를 이해합시다.
[콘솔] 배포 설정 확인
- 웹브라우저에서 AWS 콘솔(
https://console.aws.amazon.com)에 로그인하고, 오른쪽 위 리전이 서울(ap-northeast-2)로 되어 있는지 확인합니다. - 위쪽 검색창에
ECS를 입력해 Elastic Container Service로 들어갑니다. - 왼쪽 메뉴에서 클러스터(Clusters) →
vote-cluster를 클릭합니다. - 서비스(Services) 탭에서
vote-service를 클릭합니다. - 오른쪽 위 업데이트(Update) 버튼을 눌러 설정 화면으로 들어갑니다.
- 배포 옵션(Deployment options) 을 펼치면 두 숫자가 보입니다.
- 최소 실행 태스크 비율(Minimum running tasks / Minimum healthy percent):
기본
100 - 최대 실행 태스크 비율(Maximum running tasks / Maximum percent):
기본
200
- 최소 실행 태스크 비율(Minimum running tasks / Minimum healthy percent):
기본
여기서는 값을 바꾸지 말고, 확인만 하고 나옵니다. 기본값 (100 / 200)이 무중단에 가장 안전합니다. 화면을 확인했으면 저장하지 말고 브라우저 뒤로 가기나 취소로 빠져나오세요. 실수로 숫자를 바꿔 저장하면 실험 결과가 달라질 수 있습니다.
이 두 숫자가 롤링의 규칙입니다. 우리(원하는 태스크 2개) 기준으로 읽으면:
- 최소 100% = "배포 중에도 정상 태스크를 2개(100%) 밑으로 떨어뜨리지 마라." → 구버전을 먼저 끄는 것이 금지됩니다.
- 최대 200% = "배포 중에는 최대 4개(200%)까지 떠 있어도 된다." → 새 버전 2개를 추가로 띄울 여유를 허락합니다.
그래서 배포는 "2개 → (새것 추가) 3~4개 → (검증 후 구버전 제거) → 2개"로 진행되고, 정상 태스크가 2개 미만이 되는 순간은 없습니다. 19.2의 그림이 이 두 숫자로 구현되는 것입니다.
숫자를 바꾸면 배포 성격이 바뀝니다 최소 50% / 최대 100%로 두면 "구버전 1개를 먼저 내리고 그 자리에 새것" 방식이 됩니다. 추가 태스크 비용이 없는 대신 배포 중 수용량이 절반이 됩니다. 기본값(100/200)이 무중단에 가장 안전하므로 그대로 둡니다.
19.5 실험 준비 — HTTP 상태 코드부터 손에 익히기
이론은 끝났습니다. 이제 배포 중에 서비스가 정말 안 끊기는지 증명할 차례입니다. 그 전에, 실험의 눈이 될 도구 하나를 먼저 익혀 둡시다. 바로 HTTP 상태 코드입니다.
HTTP 상태 코드(HTTP Status Code)
웹 요청의 결과를 나타내는 세 자리 숫자입니다. 서버가 요청을 잘
처리했으면 200(성공)을, 서버가 응답을 못 하는 상태면 502·503
같은 숫자를 돌려줍니다. 우리 실험에서는 200이 성공, 그 외 숫자가
실패입니다. 감시 화면에 200이 끊김 없이 이어지는지만 보면 됩니다.
[맛보기] 상태 코드를 눈으로 보기
감시 실험에 들어가기 전에, 상태 코드가 어떻게 생겼는지 우리 프로젝트와
분리해서 한 번 만져 봅시다. VS Code 위쪽 메뉴에서 터미널(Terminal) →
새 터미널(New Terminal)을 열고, 아래를 그대로 붙여넣어 Enter를
누르세요. (아무것도 바꾸지 않는, 그냥 남의 웹사이트에 요청 한 번 보내
상태 코드만 찍어 보는 안전한 명령입니다.)
curl -o /dev/null -s -w "%{http_code}\n" https://example.com # 응답 본문은 버리고, HTTP 상태 코드만 한 줄로 출력
-o /dev/null: 받은 웹페이지 내용은 버립니다(우리는 코드만 볼 겁니다).-s: 진행 표시줄 같은 잡음을 숨깁니다.-w "%{http_code}\n": 상태 코드 숫자만 찍고 줄바꿈합니다.
200 이라는 한 줄이 나오면 성공입니다. 방금 여러분은 "요청을 보내고
그 결과 코드를 읽는" 감시의 최소 단위를 직접 해 본 겁니다. 다음 절의
감시 실험은 이 한 줄을 1초마다 반복하면서, 우리 서비스 주소를 향해
쏘는 것일 뿐입니다.
19.6 실험 — 배포 중에도 정말 안 끊기는가
실험 설계는 이렇습니다. 배포가 진행되는 내내 서비스에 쉬지 않고 요청을 보내면서, 단 한 번이라도 실패가 나오는지 관찰한다.
0단계. 내 ALB 주소 확인
감시를 걸려면 우리 서비스 주소가 필요합니다. 14장에서 확인한 그 주소입니다.
잊었다면 19.4에서 들어간 것과 같은 경로로 콘솔에서 찾습니다: ECS 콘솔 →
vote-cluster → vote-service → 아래쪽 네트워킹(Networking) 또는
연결된 로드밸런서에서 DNS 이름을 확인합니다. vote-alb-로 시작해
.ap-northeast-2.elb.amazonaws.com으로 끝나는 주소입니다. 이 주소를
복사해 둡니다. 아래 프롬프트의 예시 주소 자리에 여러분 주소를 넣습니다.
1단계. 감시 시작
VS Code에서 터미널을 하나 더 엽니다. (터미널 → 새 터미널.) 이 터미널에서
claude를 실행해 Claude Code를 켜고, 아래 프롬프트로 1초 간격 감시를
맡깁니다. 예시 주소를 여러분의 ALB 주소로 바꿔서 넣으세요.
몇 초만 지켜보면 ... 200, ... 200, ... 200 처럼 한 줄씩 계속
쌓입니다. 지금은 배포를 하지 않았으니 당연히 전부 200입니다. 이 감시
터미널은 끄지 말고 그대로 둡니다.
2단계. 배포 일으키기
감시가 도는 상태에서, 아무 변경이나 만들어 배포를 시작합니다. 15·17장
파이프라인 덕분에 main에 푸시만 하면 자동으로 배포됩니다. 감시가
도는 터미널은 그대로 두고, 앞서 쓰던 다른 터미널(또는 새 터미널)에서
Claude Code에게 아래를 시킵니다.
감시 터미널과 배포 터미널은 서로 다른 터미널이어야 합니다. 감시가 도는 터미널에 배포 프롬프트를 넣으면 감시가 멈춥니다. VS Code 터미널 영역에서 새 터미널을 열면 위쪽(또는 옆쪽)에 탭이 하나 더 생깁니다. 감시는 감시 탭, 배포는 배포 탭에서 각각 돌립니다.
3단계. 세 화면을 동시에 관찰
- GitHub Actions 탭(브라우저에서 내 GitHub 저장소 → Actions): 방금 푸시로 시작된 워크플로가 빌드 → 이미지 푸시 → ECS 배포 순으로 진행됩니다.
- ECS 콘솔(
vote-service→ 태스크(Tasks) 탭): 태스크가 2개 → 3~4개로 늘었다가, 구버전이 하나씩 사라지고 새 태스크 2개만 남는 과정이 보입니다. 19.2의 롤링 그림이 실시간으로 재생되는 것입니다. - 감시 터미널: 그 내내
200이 끊기지 않고 이어집니다. 이게 이 실험의 핵심 장면입니다.
4단계. 판정
배포가 끝나면(Actions에 초록색 체크 표시) 감시 터미널로 가서, Claude Code
입력창에 아래처럼 멈추라고 지시하거나 Ctrl + C로 감시를 멈춥니다.
[확인]
이번 장이 제대로 끝났는지 눈으로 확인합니다.
- 감시 로그에 실패가 0건입니다. 배포 전 → 중 → 후에 걸쳐 찍힌 상태
코드가 전부
200이면, 무중단 배포가 증명된 것입니다. (아주 짧은 순간200이 아닌 코드가 한두 개 섞일 수 있으나, 배포 내내 지속적으로 실패가 이어지지 않았다면 서비스는 끊기지 않은 것입니다.) - 브라우저에서 새 버전 확인: ALB 주소로 접속해 새로고침하면, 2단계에서
바꾼 문구(예: 제목의
v1.1)가 보입니다. 서비스는 한 순간도 끊기지 않은 채 새 버전이 되었습니다. - ECS 콘솔에서 롤링 확인: 태스크 탭에서 새 태스크 2개만 남아 있고, 구버전 태스크는 모두 사라져 있습니다.
이 세 가지가 확인되면, 여러분은 실무의 기본형인 무중단 롤링 배포를 눈으로 검증한 것입니다. 사용자가 투표하는 도중에 배포해도 아무도 모릅니다. 배포는 이제 한밤중에 몰래 하는 일이 아니라, 낮에 커피 마시며 하는 일상이 됩니다.
막히면
이 장에서 자주 나는 오류와 대처법입니다.
-
감시 로그에 처음부터 계속
502·503만 나온다. 배포 때문이 아니라, 서비스 주소가 틀렸거나 서비스가 애초에 안 떠 있는 경우입니다. 프롬프트에 넣은 ALB 주소가http://로 시작하고 오타가 없는지, 19.4처럼 ECS 콘솔에서vote-service의 태스크가 정상(RUNNING) 2개인지 먼저 확인하세요. -
감시가 안 돌고
curl: command not found가 나온다. 거의 없지만,curl이 없는 환경이라면 Claude Code에게 "curl없이 다른 방법으로 같은 감시를 해 줘"라고 다시 요청하면 됩니다. (macOS와 Git Bash에는 기본으로curl이 들어 있습니다.) -
푸시했는데 GitHub Actions가 안 돌거나 배포가 안 된다. 15·17장의 파이프라인 문제입니다. GitHub 저장소 → Actions 탭에서 최근 실행이 빨간 X인지 확인하고, 실패한 단계의 로그를 열어 원인을 봅니다. 파이프라인이 정상이어야 이 장의 실험이 성립합니다.
-
배포 중에
200이 아닌 코드가 잠깐 여러 개 찍혔다. 아주 짧은 순간이라면 정상 범위일 수 있습니다. 하지만 여러 초에 걸쳐 지속됐다면, 19.4로 돌아가 배포 옵션이 최소100/ 최대200인지 확인하세요. 최소값이 100보다 낮으면 배포 중 태스크 수가 줄어 잠깐 끊길 수 있습니다. -
태스크가 3~4개로 안 늘고 그대로 2개다. 화면 갱신이 늦은 것일 수 있으니 ECS 태스크 탭에서 새로고침을 눌러 보세요. 롤링은 순식간에 지나갈 수 있어, 늘어난 순간을 놓쳤을 수도 있습니다. 그래도 감시 로그가 계속
200이면 무중단은 성립한 것입니다.
[체크리스트]
아래가 모두 됐는지 확인해 보세요. 하나라도 안 됐다면 해당 절로 돌아가 다시 진행합니다.
- □ 다운타임이 무엇이고, 순진한 배포가 왜 끊기는지 설명할 수 있다
- □ "켜고 나서 끈다"는 무중단의 핵심 순서를 안다
- □ 롤링과 블루/그린의 차이·장단점을 그림으로 설명할 수 있다
- □ 헬스체크·드레이닝이 무중단에서 하는 역할을 안다
- □ ECS 콘솔에서 배포 옵션이 최소 100 / 최대 200인 것을 확인했다
- □
curl로 HTTP 상태 코드200을 한 번 눈으로 봤다(맛보기) - □ 감시(1초 간격 요청)를 걸어 둔 채 배포를 일으켜, 로그에 실패 0건인 것을 확인했다
- □ 브라우저에서 새 버전(바뀐 문구)이 뜬 것을 확인했다
모두 체크됐다면, 여러분의 서비스는 이제 "배포해도 안 끊기는" 서비스입니다.