본문 바로가기

배포 및 운영 전략

서버와 클라이언트를 어디에 배포할지 고르고, 롤링과 카나리, 블루그린 배포를 용량과 되돌리기의 목표로 비교한 뒤, 사용자에게 보이는 신호에서 시작해 완화와 복구, 확인으로 이어지는 운영의 흐름과 보안, 확장의 기본을 정리합니다.

앞 절의 파이프라인은 검사를 통과한 코드를 서버 한 대에 자동으로 올립니다. 하지만 배포는 시작일 뿐이어서, 실제 서비스는 새 버전을 올리는 동안에도 멈추지 않아야 하고, 문제가 생기면 빨리 알아차려 피해를 줄여야 하며, 사용자가 늘어도 견뎌야 합니다. 이 절은 이 장의 애플리케이션을 운영한다고 생각하며 배포 대상과 배포 전략, 운영 중의 대응, 보안과 확장의 기본을 정리합니다.

예로 드는 서비스 이름은 AWS의 것이지만, 다른 클라우드나 직접 운영하는 서버에도 같은 원리가 적용됩니다.

배포 대상 고르기

애플리케이션을 어디에서 실행할지는 서비스의 규모와 예산, 팀이 감당할 수 있는 운영의 양에 따라 고릅니다.

대상예잘 맞는 경우감당할 것
가상 머신EC2, Compute Engine작은 서비스, 학습, 설정을 직접 정해야 하는 경우운영체제와 런타임의 관리, 확장과 장애 대비를 직접 구성
컨테이너 서비스ECS와 Fargate, Kubernetes컨테이너로 만든 여러 서비스를 함께 운영하는 경우설정의 복잡도와 학습, 늘어나는 비용
서버리스 함수Lambda, Cloud Functions짧게 실행되는 이벤트 처리와 API실행 시간의 제한, 콜드 스타트의 지연, 상태를 갖기 어려움
정적 호스팅과 CDNAmplify, Vercel, Cloudflare Pages빌드한 SPA 같은 정적 파일서버 로직은 따로 배포

이 장의 애플리케이션이라면 클라이언트는 빌드한 정적 파일이므로 정적 호스팅이나 CDN에 올리고, 서버는 앞 절의 Docker 이미지를 ECS와 Fargate 같은 컨테이너 서비스로 실행하는 구성을 생각해 볼 수 있습니다. 앞 절처럼 가상 머신 한 대에 올리는 방식은 시작하기 쉽지만, 그 서버가 멈추면 서비스 전체가 멈춥니다.

배포 전략 고르기

새 버전을 올리는 방식은 한 번에 얼마나 많은 사용자에게 영향을 줄지와, 문제가 생겼을 때 얼마나 빨리 되돌릴 수 있어야 하는지로 고릅니다.

DEPLOYMENT STRATEGY

폭발 반경과 rollback 목표로 배포 전략을 고른다

배포 전략 비교를 위한 질문입니다. 용량·트래픽 제어·관찰 신호·이전 버전 호환성을 함께 검토합니다. 세 경로는 단순 자동 결정 규칙이 아니며, 어떤 방식도 image 교체만으로 데이터 변경을 되돌리지 않습니다.

폭발 반경과 rollback 목표로 배포 전략을 고른다 배포 제약을 확인해 rolling, canary, blue-green 전략으로 분기하고 rollback과 스키마 호환성 조건을 비교하는 결정 흐름. 용량 위험 전환 제약 수집 traffic · capacity 제약을 함께 비교 blast radius · rollback Rolling bounded batches Canary small traffic slice Blue-green 트래픽 경로 전환
flowchart 핵심 계약
  1. 제약 수집 → 전략 비교

    가용 용량·트래픽 분할·준비 완료·관찰 신호와 DB 호환성을 확인합니다. 아래는 선택지 비교이며 실행 순서가 아닙니다.

  2. Rolling · 제한된 교체 묶음

    readiness·여유 용량·연결 drain과 구·신 버전 공존 호환성을 확인합니다. rollback은 역방향 교체 과정이 필요합니다.

  3. Canary · 일부 트래픽 검증

    비교 가능한 사용자·부하와 오류·지연 신호를 확인한 뒤 노출을 늘립니다. 낮은 비율 자체가 안전을 보장하지 않습니다.

  4. Blue-green · 트래픽 경로 전환

    두 환경의 용량과 readiness를 준비합니다. 이전 환경을 유지하고 schema·데이터가 호환돼야 돌아갈 수 있습니다.

  • 핵심 경계
  • 진행·선택 경로

롤백 목표는 image뿐 아니라 실행 설정·라우팅·데이터 상태까지 포함합니다. 측정 결과와 운영 제약에 맞춰 전략을 정합니다.

롤링 배포(rolling update)는 여러 인스턴스를 몇 개씩 묶어 차례로 새 버전으로 바꿉니다. 바꾸는 동안 남은 인스턴스가 요청을 받을 여유 용량이 있어야 하고, 새 인스턴스가 요청을 받을 준비가 되었는지 확인하는 준비 상태(readiness) 검사와, 내려가는 인스턴스가 처리 중인 요청을 마치게 하는 연결 정리가 필요합니다. 옛 버전과 새 버전이 잠시 함께 요청을 받으므로 두 버전이 같은 데이터베이스에서 함께 동작해야 하며, 되돌릴 때도 같은 교체를 반대로 거칩니다.

카나리 배포(canary)는 새 버전에 사용자의 일부만 먼저 보내 오류와 지연을 지켜본 뒤, 문제가 없으면 비율을 늘립니다. 비교할 수 있는 사용자와 부하, 오류율과 지연 같은 신호가 있어야 판단할 수 있으며, 비율이 낮다는 것만으로 안전한 것은 아닙니다. 블루그린 배포(blue-green)는 같은 환경을 두 벌 준비해 새 버전을 쉬고 있는 쪽에 올리고 확인한 뒤 트래픽을 한 번에 옮기며, 옛 환경을 남겨 두면 트래픽을 되돌려 빠르게 돌아갈 수 있지만 두 환경의 용량이 필요합니다.

어느 방식이든 되돌리기의 대상은 이미지만이 아닙니다. 실행 설정과 트래픽의 경로, 데이터의 상태까지 함께 돌아가야 하며, 이미지를 이전 버전으로 바꾼다고 이미 바뀐 데이터베이스의 스키마나 데이터가 돌아오지는 않습니다. 그래서 ORM 사용에서 본 것처럼 스키마는 옛 버전과 새 버전이 함께 쓸 수 있도록 확장, 공존, 백필, 전환, 관찰, 축소의 순서로 바꾸고, 이전 버전의 이미지를 레지스트리에 남겨 두어 다시 배포할 수 있게 합니다.

더 깊이: 컨테이너가 멈출 때 서버가 받는 신호

롤링 배포에서 말한 연결 정리는 컨테이너를 멈추는 순서와 맞물립니다. Docker의 docker stop 문서에 따르면, 컨테이너 안의 주 프로세스는 먼저 SIGTERM을 받고, 유예 시간이 지나도 끝나지 않으면 SIGKILL로 강제로 끝납니다. 유예 시간의 기본값은 Linux 컨테이너에서 10초이며, 컨테이너를 만들 때 --stop-timeout으로 바꿀 수 있습니다. 첫 신호는 Dockerfile의 STOPSIGNAL이나 --stop-signal로 바꿀 수 있습니다.

NestJS는 이 신호를 받아도 기본으로는 정리 훅을 부르지 않습니다. NestJS의 수명 주기 문서는 종료 신호를 듣는 리스너가 자원을 쓰므로 기본으로 꺼져 있고, 앱을 시작할 때 enableShutdownHooks()를 불러야 켜진다고 설명합니다. 켜 두면 종료 신호를 받았을 때 onModuleDestroy(), beforeApplicationShutdown(), onApplicationShutdown()이 차례로 불리고, 이 메서드가 Promise를 돌려주면 끝나기를 기다립니다. 그래서 데이터베이스 연결을 닫는 것 같은 정리는 이 훅에 두고, 그 일이 컨테이너의 유예 시간 안에 끝나도록 맞춥니다.

비밀 값과 설정

운영 환경의 데이터베이스 비밀번호와 JWT_SECRET 같은 값은 소스 코드와 이미지에 넣지 않고, 실행할 때 환경 변수로 넣습니다. 앞 절은 서버에 둔 파일을 --env-file로 읽었지만, 서버가 여러 대가 되면 AWS Secrets Manager나 HashiCorp Vault 같은 비밀 관리 서비스에 두고 컨테이너가 시작할 때 읽게 합니다.

비밀 관리 서비스를 쓰면 누가 언제 어떤 값을 읽었는지가 기록되고, 값을 주기적으로 바꾸는 일도 한곳에서 할 수 있습니다. 비밀 값이 유출되었다고 의심되면 먼저 그 값을 새로 바꾸어 이전 값을 쓸 수 없게 하는데, JWT_SECRET을 바꾸면 이미 발급한 모든 토큰이 무효가 되어 사용자가 다시 로그인해야 한다는 점도 함께 고려합니다.

운영 중의 신호와 대응

운영 중에는 서비스의 상태를 세 가지 신호로 봅니다. 로그(log)는 요청이나 오류 같은 사건의 기록이고, 지표(metric)는 오류율이나 응답 시간처럼 시간에 따라 모은 숫자이며, 추적(trace)은 요청 하나가 여러 서비스와 데이터베이스를 거친 경로와 시간입니다.

로그는 타입스크립트 디버깅 기법에서 본 것처럼 구조화된 한 줄로 남기고, CloudWatch Logs나 Grafana Loki 같은 곳에 모아 검색할 수 있게 합니다. 토큰과 비밀번호, 개인정보의 원문은 남기지 않으며, 오류는 스택 트레이스와 요청을 구별할 ID를 함께 남깁니다. 지표는 Prometheus와 Grafana나 CloudWatch로 모아 대시보드로 보고, 추적은 OpenTelemetry 같은 표준으로 남기며, Sentry 같은 오류 추적 서비스는 같은 오류를 묶어 처음 일어난 때와 영향을 받은 사용자 수를 보여 줍니다.

알림은 CPU 사용률 같은 내부 숫자보다, 오류율과 응답 시간처럼 사용자가 겪는 문제를 기준으로 겁니다. 서비스가 지켜야 할 목표를 서비스 수준 목표(SLO)로 정해 두고, 그 목표를 벗어나는 정도에 따라 알림의 심각도와 받을 사람을 나눕니다.

SLO RESPONSE LOOP

사용자 신호에서 완화와 학습까지 한 운영 루프로 닫는다

사용자 영향 신호로 대응을 시작하고 로그·metric·trace와 최근 변경을 대조합니다. 원인을 단정하기 전에 피해를 줄이고, 필요한 rollback·복구 후 사용자 지표로 회복을 검증해 runbook에 남기는 운영 목표입니다.

사용자 신호에서 완화와 학습까지 한 운영 루프로 닫는다사용자 SLO 신호, telemetry 상관, 변경 증거, 완화, 검증, rollback 또는 복구, 회고 학습으로 이어지는 운영 대응 루프.
SLO signaluser impact
연결 관계SLO signal · user impact → Correlate · logs · metrics · traces
Correlatelogs · metricstraces
연결 관계Correlate · logs · metrics · traces → Change evidence · deploy · config
Change evidencedeploy · config
연결 관계Change evidence · deploy · config → Mitigate · bound blast radius
Mitigatebound blast radius
연결 관계Mitigate · bound blast radius → Recover · rollback · 복구
Recoverrollback · 복구
연결 관계Recover · rollback · 복구 → Verify · recovery · 사용자 지표 확인
Verifyrecovery사용자 지표 확인
연결 관계Verify · recovery · 사용자 지표 확인 → Retain learning · runbook · guard
Retain learningrunbook · guard
process 핵심 계약
  1. SLO signal → 증거 상관

    오류·지연 등 사용자 영향과 logs·metrics·traces를 확인합니다. 상관관계만으로 원인을 확정하지 않습니다.

  2. 최근 변경 확인 → 먼저 완화

    deploy·config·부하를 대조하고 피해를 줄일 조치를 선택합니다. 근거 없는 자동 임계치·rollback을 가정하지 않습니다.

  3. 필요한 Rollback·Recover

    정상 상태로 복구합니다. 데이터·schema 호환성과 실제 가역성을 확인합니다.

  4. 복구 후 Verify

    사용자 지표와 핵심 동작으로 회복을 확인합니다. 회복되지 않으면 증거 분석·완화를 반복합니다.

  5. 학습 보존

    원인·조치·한계를 runbook과 회귀 방지에 반영합니다.

  • 핵심 경계
  • 진행·선택 경로

복구 검증에 실패하면 증거 분석·완화를 반복합니다. 로그에 password·JWT·개인정보를 기록하지 않고 필요한 상관 식별자를 남깁니다. 이 그림은 대응 방법이며 실제 장애 대응이나 복구 실행의 증거가 아닙니다.

그림은 알림을 받은 뒤의 대응 흐름입니다. 사용자에게 보이는 신호에서 시작해 로그와 지표, 추적을 함께 보고, 최근의 배포나 설정 변경과 시점을 맞추어 봅니다. 시점이 겹친다고 원인이 확정되는 것은 아니므로, 원인을 단정하기 전에 피해를 줄이는 조치를 먼저 합니다. 최근 배포를 되돌리거나, 문제가 된 기능을 끄거나, 트래픽을 줄이는 것이 그런 조치입니다.

그다음 필요하면 이전 버전으로 되돌리거나 데이터를 복구하고, 사용자의 지표와 핵심 기능으로 정말 회복되었는지를 확인합니다. 회복되지 않았다면 신호를 다시 보며 완화를 반복하고, 회복되었다면 무엇이 일어났고 어떻게 대응했는지를 런북(runbook)과 회귀를 막는 검사로 남겨 다음 대응을 빠르게 합니다. 어떤 숫자를 넘으면 자동으로 되돌리거나 확장한다는 규칙은, 그 숫자가 실제 문제를 가리킨다는 확인이 있을 때만 둡니다.

보안의 기본

운영 환경에서는 모든 통신을 HTTPS로 암호화하며, AWS Certificate Manager나 Let's Encrypt로 인증서를 발급받습니다. 서버와 데이터베이스는 방화벽과 보안 그룹으로 필요한 포트와 출처만 열고, 특히 데이터베이스는 인터넷에서 바로 접속할 수 없게 서버에서만 접근하게 합니다.

클라우드의 권한은 IAM으로 최소 권한의 원칙에 따라 주어, 배포에 쓰는 계정은 배포에 필요한 일만 할 수 있게 합니다. 운영체제와 Node.js, 의존성 패키지의 보안 업데이트는 주기적으로 적용하고, npm audit이나 GitHub의 Dependabot으로 알려진 취약점이 있는 패키지를 찾습니다. 이 장의 서버가 하지 않은 일, 즉 로그인 시도 횟수의 제한, 이메일 중복의 처리, 토큰의 폐기도 운영 전에 갖춰야 할 목록에 넣습니다.

확장의 기본

사용자가 늘면 서버를 더 좋은 기계로 바꾸는 수직 확장보다, 같은 서버를 여러 대로 늘리는 수평 확장이 한계가 적습니다. 로드 밸런서가 요청을 여러 인스턴스에 나누고, 오토 스케일링(auto scaling)이 요청 수나 CPU 사용률에 따라 인스턴스를 늘리고 줄입니다. 이 장의 서버는 로그인 상태를 서버의 메모리가 아니라 JWT에 담으므로, 어느 인스턴스가 요청을 받아도 같은 결과를 내어 수평 확장에 잘 맞습니다.

데이터베이스는 읽기가 많다면 읽기 전용 복제본(read replica)으로 읽기를 나누고, 그래도 부족하면 데이터를 여러 데이터베이스로 나누는 샤딩(sharding)을 검토합니다. 자주 읽고 잘 바뀌지 않는 데이터는 Redis 같은 캐시에 두고, 클라이언트의 정적 파일은 CDN으로 사용자에게 가까운 곳에서 제공합니다. 어느 방법이든 성능 최적화와 프로파일링의 순서대로, 측정해서 찾은 병목에 맞추어 적용합니다.

정리

  • 배포 대상은 서비스의 규모와 운영할 수 있는 양에 따라 가상 머신, 컨테이너 서비스, 서버리스, 정적 호스팅 가운데에서 고르며, 클라이언트와 서버를 다른 곳에 둘 수 있습니다.
  • 롤링은 여유 용량과 준비 상태 검사, 카나리는 비교할 수 있는 신호, 블루그린은 두 환경의 용량이 필요하며, 되돌리기는 이미지뿐 아니라 설정과 데이터까지 포함합니다.
  • 스키마는 옛 버전과 새 버전이 함께 쓸 수 있게 단계적으로 바꾸고, 이전 이미지를 남겨 두어 다시 배포할 수 있게 합니다.
  • 비밀 값은 이미지가 아니라 실행할 때 넣고, 서버가 늘면 비밀 관리 서비스로 옮기며, 유출이 의심되면 먼저 값을 바꿉니다.
  • 운영 중에는 로그와 지표, 추적을 함께 보고, 사용자가 겪는 문제로 알림을 걸며, 원인을 단정하기 전에 피해를 줄이고 회복을 확인한 뒤 배운 것을 남깁니다.
  • 보안은 HTTPS와 최소한의 개방, 최소 권한, 주기적인 업데이트에서 시작하고, 확장은 상태 없는 서버의 수평 확장과 측정에 맞춘 캐시와 복제로 합니다.

연습 문제

1. 이미지를 되돌렸는데도

새 버전을 배포하면서 사용자 테이블의 name 컬럼을 지우고 displayName만 남기는 마이그레이션을 함께 적용했습니다. 오류가 늘어 이전 이미지로 되돌렸는데도 오류가 계속됩니다. 왜 그런가요?

해설 보기

이전 이미지의 코드는 여전히 name 컬럼을 읽는데, 그 컬럼은 이미 지워졌기 때문입니다. 이미지를 되돌려도 데이터베이스의 스키마는 돌아오지 않으므로, 컬럼을 바꾸는 일은 새 컬럼을 더하고 두 버전이 함께 동작하는 기간을 둔 뒤, 옛 코드가 더는 없다고 확인된 다음 배포에서 옛 컬럼을 지우는 순서로 나누어야 합니다.

2. 알림의 기준

CPU 사용률이 80퍼센트를 넘으면 알림을 보내도록 했더니, 밤마다 배치 작업 때문에 알림이 오지만 사용자는 아무 문제를 겪지 않았습니다. 알림의 기준을 어떻게 바꾸는 것이 좋을까요?

해설 보기

CPU 사용률은 사용자의 경험과 직접 이어지지 않으므로, 오류율과 응답 시간처럼 사용자가 겪는 신호를 서비스 수준 목표로 정하고 그 목표를 벗어날 때 알림을 겁니다. CPU 같은 내부 지표는 대시보드에서 원인을 찾을 때 함께 보는 자료로 두면, 아무 일도 없는데 사람을 깨우는 알림이 줄어듭니다.

3. 여러 대로 늘린 서버

서버를 세 대로 늘렸더니, 로그인한 사용자의 요청이 어느 서버로 가든 /auth/profile이 정상으로 동작했습니다. 로그인 상태를 서버의 메모리에 세션으로 저장했다면 어떤 문제가 생겼을까요?

해설 보기

로그인을 처리한 서버의 메모리에만 세션이 있으므로, 다음 요청이 다른 서버로 가면 그 서버는 세션을 모르고 401을 돌려줍니다. 같은 사용자를 늘 같은 서버로 보내거나 세션을 Redis 같은 공용 저장소에 두어야 합니다. 이 장의 서버는 서명한 JWT로 사용자를 확인하므로 어느 서버든 같은 결과를 냅니다.

이것으로 16장의 실전 프로젝트를 마치며, 타입스크립트 교재도 여기서 끝납니다. 처음의 타입 기초부터 이 장의 운영까지 다시 훑어보려면 타입스크립트 교재의 목차로 돌아가 필요한 장을 골라 읽어 보세요.