클라우드 플랫폼(AWS, Google Cloud, Azure) 배포
VM, 관리형 장기 실행 컨테이너, Kubernetes, 함수 또는 잡 컨테이너를 운영 책임으로 비교하고 재현 가능한 NestJS 배포 기준을 세웁니다.
지난 절에서는 NestJS 애플리케이션의 빌드, 테스트, 배포를 자동화하는 CI/CD 파이프라인을 구성했습니다.
이 절에서는 그 파이프라인이 만든 컨테이너 이미지를 클라우드에 배포합니다. 상품 이름보다 먼저 확인할 것은 배포 뒤에도 팀에 남는 운영 책임입니다. 호스트 OS, 오케스트레이터, 애플리케이션 설정 중 누가 무엇을 맡는지에 따라 필요한 기술과 비용 구조가 달라집니다.
OPERATING MODEL · RESPONSIBILITY
상품 이름이 아니라 팀에 남는 운영 책임으로 고른다
관리형일수록 책임이 사라지는 것이 아니라 경계가 이동합니다. 공급자가 맡는 기반과 팀이 계속 운영할 애플리케이션 계약을 함께 비교하세요.
| 실행 모델 | 대표 서비스 | 공급자 운영 | 팀 운영 | 확장과 비용 |
|---|---|---|---|---|
| VM 직접 운영 | EC2, Compute Engine, Azure VM | 시설, 물리 호스트와 가상화 계층 | 게스트 OS와 런타임 패치, 이미지, 용량, LB와 TLS, 배포와 롤백 | 팀이 ASG 등을 설계하며 프로비저닝한 인스턴스와 부속 자원에 과금 |
| 관리형 장기 실행 컨테이너 | ECS 서비스 on Fargate, Cloud Run 서비스, Container Apps | 호스트, 실행 제어면, 기본 스케줄링 | 이미지 digest, 서비스 설정, ID와 비밀, 네트워크, 상태 확인, 종료와 drain, 관측성, 롤아웃 | 최소, 최대 수, 동시성, 확장 신호를 설정하며 과금 모드와 최소 인스턴스를 확인 |
| 관리형 Kubernetes | EKS, GKE, AKS | 관리형 제어면과 선택한 자동 관리 기능 | 워크로드, RBAC와 네트워크 정책, 노드 모드, 업그레이드, 보안과 관측성 | HPA와 KEDA에도 메트릭이 필요하며 클러스터, 노드와 네트워크 비용이 남음 |
| 함수 또는 잡 컨테이너 | Lambda 이미지, Cloud Run jobs, Container Apps jobs | 함수 호출 또는 잡 실행 인프라. trigger와 scheduler 제공 범위는 제품별로 다름 | Lambda 이벤트 연결, Cloud Run용 별도 Cloud Scheduler, Container Apps 잡 trigger, IAM, timeout, 재시도와 멱등성 | 실행과 동시성 기준을 확인하며 장기 실행 웹 서비스와 별도로 평가 |
VM 직접 운영
예: EC2, Compute Engine, Azure VM
공급자: 시설, 물리 호스트와 가상화 계층
팀: 게스트 OS와 런타임 패치, 이미지, 용량, LB와 TLS, 배포와 롤백
확장과 비용: ASG 등을 직접 설계하고 프로비저닝한 인스턴스와 부속 자원에 과금
관리형 장기 실행 컨테이너
예: ECS 서비스 on Fargate, Cloud Run 서비스, Container Apps
공급자: 호스트, 실행 제어면, 기본 스케줄링
팀: 이미지 digest, 서비스 설정, ID와 비밀, 네트워크, 상태 확인, 종료와 drain, 관측성, 롤아웃
확장과 비용: 최소, 최대 수, 동시성, 확장 신호를 설정하고 과금 모드와 최소 인스턴스를 확인
관리형 Kubernetes
예: EKS, GKE, AKS
공급자: 관리형 제어면과 선택한 자동 관리 기능
팀: 워크로드, RBAC와 네트워크 정책, 노드 모드, 업그레이드, 보안과 관측성
확장과 비용: HPA와 KEDA에도 메트릭이 필요하며 클러스터, 노드와 네트워크 비용이 남음
함수 또는 잡 컨테이너
예: Lambda 이미지, Cloud Run jobs, Container Apps jobs
공급자: 함수 호출 또는 잡 실행 인프라. trigger와 scheduler 제공 범위는 제품별로 다름
팀: Lambda 이벤트 연결, Cloud Run용 별도 Cloud Scheduler, Container Apps 잡 trigger, IAM, timeout, 재시도와 멱등성
확장과 비용: 실행과 동시성 기준을 확인하고 장기 실행 웹 서비스와 별도로 평가
“서버를 보지 않는다”는 “운영할 것이 없다”는 뜻이 아닙니다. 배포 이미지부터 트래픽, 자격 증명, 관측성과 복구까지 남는 책임을 팀 역량과 맞추세요.
실행 모델을 운영 책임으로 구분하기
VM에 직접 배포
AWS EC2, Google Compute Engine, Azure Virtual Machines에서는 팀이 가상 머신의 게스트 OS부터 관리합니다. Docker 설치, OS와 런타임 패치, 용량 계획, 로드 밸런서와 TLS, 배포와 롤백 자동화를 직접 설계하는 대신 네트워크와 런타임을 세밀하게 제어할 수 있습니다.
VM 한 대로 시작할 수 있지만, 운영에서는 장애 도메인을 나눈 여러 인스턴스와 로드 밸런서, 감사 가능한 관리 경로를 함께 설계해야 합니다. 유휴 인스턴스에도 비용이 발생하므로 “가장 단순한 운영 모델”과 같지는 않습니다.
EC2에서 컨테이너를 실행하는 최소 예시
아래 명령은 Ubuntu 배포판 패키지를 이용한 예시입니다. 실제 운영 이미지는 설치 절차와 버전을 팀 기준으로 고정하세요.
sudo apt-get update
sudo apt-get install -y docker.io
sudo systemctl enable --now docker
sudo usermod -aG docker deployer
# 그룹 변경은 deployer의 다음 로그인 세션부터 적용한다.docker 그룹은 Docker 데몬을 통해 호스트의 root 수준 권한을 얻을 수 있습니다. 전용 배포 사용자, 제한된 SSH/Session Manager 경로와 감사 로그를 함께 적용합니다. 자세한 위험은 Docker의 Linux 설치 후 단계를 참고하세요.
운영 이미지는 변경 가능한 태그가 아니라 레지스트리가 반환한 digest로 고정합니다. digest는 동일한 명령이 동일한 이미지 내용을 가리키게 합니다.
IMAGE='123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/nestjs-app@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'
docker pull "$IMAGE"비밀값은 명령줄에 직접 쓰지 않고 접근 권한이 제한된 env 파일이나 비밀 저장소에서 주입합니다. 아래 예시는 로컬 프록시나 로드 밸런서만 접근하도록 호스트의 loopback 주소에 바인딩합니다.
IMAGE='123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/nestjs-app@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'
docker run -d \
--name nestjs-prod-app \
--restart unless-stopped \
--read-only \
--tmpfs /tmp:rw,noexec,nosuid,size=64m \
--env-file /etc/nestjs-app/production.env \
-p 127.0.0.1:3000:3000 \
"$IMAGE"이미지 digest로 실행하는 방법은 Docker pull의 digest 설명에서 확인할 수 있습니다.
관리형 장기 실행 컨테이너
AWS ECS 서비스 on Fargate, Google Cloud Run 서비스, Azure Container Apps는 호스트와 실행 제어면의 상당 부분을 공급자가 운영합니다. 하지만 팀은 여전히 다음 항목을 결정해야 합니다.
- 배포할 이미지 digest와 이전 정상 릴리스
- 워크로드 ID 및 비밀 저장소 참조
- 공개 또는 비공개 ingress, egress, TLS와 데이터베이스 연결 경로
- CPU, 메모리, 최소, 최대 인스턴스 수, 동시성과 확장 신호
- 플랫폼별 시작, 트래픽 수신, 생존 확인과 로그, 메트릭, 알림, 롤아웃, 롤백 정책
- 종료 신호 전달, 새 요청 차단과 진행 중 요청 drain, 데이터베이스와 큐 정리, 강제 종료 전 grace 또는 stop timeout
이미지를 제공하는 배포 경로에서 플랫폼은 이미지를 가져와 실행합니다. 이미지 빌드는 별도의 CI/빌드 서비스가 담당하며, 소스 기반 배포를 명시적으로 선택했을 때만 해당 플랫폼 흐름에 포함됩니다.
컨테이너가 scale-in이나 롤아웃으로 종료될 때도 애플리케이션 계약은 남습니다. NestJS bootstrap에서 app.enableShutdownHooks()를 호출하고, beforeApplicationShutdown이나 onApplicationShutdown에서 새 작업 수락을 멈춘 뒤 진행 중 요청을 drain하고 데이터베이스 연결과 큐 consumer를 정리합니다. 컨테이너 명령은 Node 프로세스를 exec form으로 직접 실행하거나 init 프로세스가 SIGTERM을 전달하도록 구성하여, shell wrapper가 신호를 가로막지 않게 합니다. 정리에 필요한 시간을 측정한 뒤 Cloud Run 종료 계약, ECS stopTimeout, Azure Container Apps 종료 계약, Kubernetes의 terminationGracePeriodSeconds와 맞춥니다. probe는 이 정상 종료 처리를 대신하지 않습니다.
자동 확장도 무조건 켜지는 성질이 아닙니다. 예를 들어 ECS 서비스는 Application Auto Scaling 정책과 최소, 최대 태스크 수를 설정해야 하며, AWS는 수요와 용량의 관계를 나타내는 사용률 또는 포화도 메트릭을 선택하도록 안내합니다. ECS 서비스는 원하는 태스크 수를 유지하고 Fargate는 그 태스크의 실행 용량을 제공하므로, Fargate 자체를 별도의 웹 서비스 상품처럼 다루지 않습니다.
Cloud Run은 구성에 따라 요청 기반 또는 인스턴스 기반 과금을 사용하고, 최소 인스턴스를 두지 않은 서비스는 기본적으로 0까지 축소될 수 있습니다. 따라서 “항상 사용한 요청만큼만 과금”된다고 일반화하지 말고 Cloud Run 과금 설정과 서비스 동작을 함께 확인합니다. Azure Container Apps도 HTTP, TCP, 사용자 지정 확장 규칙과 최소, 최대 replica를 팀이 설정합니다.
상태 확인 이름과 동작도 플랫폼마다 다릅니다. Kubernetes와 Azure Container Apps는 startup, readiness, liveness probe를 구분합니다. Cloud Run도 세 종류를 제공하지만 readiness probe는 현재 Preview이고, startup 성공 직후 첫 readiness 결과보다 먼저 트래픽을 받을 수 있으므로 startup 자체가 안전한 요청 수신 상태를 보장해야 합니다. ECS는 컨테이너 health check의 startPeriod와 로드 밸런서 target health를 조합합니다. 따라서 Kubernetes 필드 이름을 모든 상품에 그대로 복사하지 말고 “초기화 보호, 새 트래픽 수신 여부, 복구 불가능한 프로세스 재시작”이라는 의도를 각 플랫폼 기능에 매핑합니다.
AWS App Runner는 신규 프로젝트의 기본 후보로 제시하면 안 됩니다. AWS 공식 안내에 따르면 2025년 11월 7일부터 신규 고객에게 닫혔고, 기존 고객은 계속 사용할 수 있지만 새 기능은 계획되어 있지 않습니다. 신규 AWS 워크로드는 요구사항에 따라 ECS Express Mode 또는 ECS 서비스 on Fargate 등을 검토합니다.
관리형 Kubernetes
Amazon EKS, Google Kubernetes Engine, Azure Kubernetes Service는 Kubernetes 제어면 관리의 일부를 공급자에게 맡깁니다. 팀은 Deployment와 Service 같은 워크로드 객체뿐 아니라 namespace/RBAC, 네트워크 정책, 노드 또는 자동 관리 모드, 클러스터 업그레이드, 관측성과 비용 경계까지 운영해야 합니다. Kubernetes가 오픈 소스라는 사실만으로 클라우드 간 애플리케이션, IAM, 네트워크 구성이 자동 이식되지는 않습니다.
다음 Deployment를 적용하기 전에 같은 namespace에 nestjs-app ServiceAccount와 app-secrets Secret을 먼저 만들어야 합니다. 존재하지 않는 ServiceAccount는 Pod admission 단계에서 거부되고, 필수 Secret을 찾지 못하면 컨테이너가 시작되지 않습니다. 또한 Kubernetes ServiceAccount 이름만으로 클라우드 API 권한이 생기지는 않으므로 EKS Pod Identity/IRSA, GKE Workload Identity Federation, AKS Workload Identity의 provider-side association과 최소 권한 IAM binding을 별도로 구성합니다. /health/startup, /health/ready, /health/live도 Nest가 자동으로 만드는 경로가 아니며, @nestjs/terminus나 전용 controller로 각 계약을 구현해야 합니다.
이 선행조건을 충족했다는 전제에서, 다음 Deployment는 이미지 digest, 워크로드용 ServiceAccount, 비밀 참조, 서로 다른 세 probe와 정상 종료 grace period를 명시합니다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: nestjs-app
labels:
app: nestjs-app
spec:
replicas: 3
selector:
matchLabels:
app: nestjs-app
template:
metadata:
labels:
app: nestjs-app
spec:
serviceAccountName: nestjs-app
terminationGracePeriodSeconds: 30
containers:
- name: nestjs-container
image: 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/nestjs-app@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
ports:
- name: http
containerPort: 3000
env:
- name: DATABASE_HOST
valueFrom:
secretKeyRef:
name: app-secrets
key: DATABASE_HOST
- name: JWT_SECRET
valueFrom:
secretKeyRef:
name: app-secrets
key: JWT_SECRET
startupProbe:
httpGet:
path: /health/startup
port: http
periodSeconds: 2
failureThreshold: 30
readinessProbe:
httpGet:
path: /health/ready
port: http
periodSeconds: 5
failureThreshold: 3
livenessProbe:
httpGet:
path: /health/live
port: http
periodSeconds: 10
failureThreshold: 3
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
securityContext:
runAsNonRoot: true
runAsUser: 10001
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
seccompProfile:
type: RuntimeDefaultstartupProbe가 성공하기 전에는 readiness와 liveness 검사가 시작되지 않습니다. 이후 readiness 실패는 Pod를 Service 엔드포인트에서 빼고, liveness 실패는 컨테이너 재시작을 유도합니다. 이 역할을 같은 /health 응답으로 뭉개지 말고 Kubernetes probe 계약에 맞게 의존성을 나눕니다. Pod가 종료될 때는 endpoint가 트래픽 대상에서 제외되고 컨테이너에 SIGTERM이 전달됩니다. Nest는 grace period 안에 새 요청을 차단하고 진행 중 요청, 데이터베이스 연결, 큐 작업을 정리해야 하며, 예시의 30초는 측정 결과에 맞춰 조정합니다.
Service는 클러스터 내부의 안정적인 목적지를 제공합니다. HTTP 라우팅과 TLS는 Ingress/Gateway 또는 클라우드의 L7 로드 밸런서에서 별도로 구성합니다.
apiVersion: v1
kind: Service
metadata:
name: nestjs-app
spec:
selector:
app: nestjs-app
ports:
- name: http
protocol: TCP
port: 80
targetPort: http
type: ClusterIPKubernetes Secret은 비밀값 전달 객체이지 완전한 비밀 관리 체계가 아닙니다. Kubernetes 문서가 설명하듯 Secret은 기본 설정에서 etcd에 암호화되지 않은 채 저장될 수 있으므로, 저장 시 암호화, 최소 권한 RBAC, Pod 접근 제한과 외부 비밀 저장소 연동을 함께 구성합니다. 클라우드 API 접근에는 정적 키보다 EKS Pod Identity/IRSA, GKE Workload Identity Federation, AKS Workload Identity 같은 단기 워크로드 자격 증명을 우선합니다.
함수 또는 잡 컨테이너
함수와 잡은 모두 실행 후 끝날 수 있지만 트리거 소유권은 같지 않습니다. Lambda 컨테이너 이미지는 요청이나 이벤트로 호출되는 함수 패키지입니다. Cloud Run job은 API, CLI, workflow 등으로 실행하는 task이며 정기 실행에는 별도 Cloud Scheduler가 필요합니다. Azure Container Apps job은 manual, schedule, event trigger를 제공합니다. 플랫폼은 함수 호출 또는 잡 실행 인프라를 운영하지만, 팀은 어떤 이벤트나 scheduler가 실행을 시작하는지, 호출 IAM, timeout, 재시도, 멱등성, 동시 실행 수와 작업별 비용을 제품별로 설계해야 합니다.
특히 Lambda의 컨테이너 이미지는 일반 Docker 서비스를 그대로 호스팅하는 기능이 아니라 Lambda Runtime API에 맞춘 함수 패키징 방식입니다. Lambda 컨테이너 이미지 요구사항처럼 읽기 전용 파일 시스템과 /tmp, 런타임 인터페이스 같은 Lambda 실행 계약을 따라야 합니다.
배포 준비 상태를 한 장으로 검증하기
클라우드 상품을 고른 뒤에는 “이미지가 실행되었다”를 완료 조건으로 삼지 않습니다. 현재 digest와 이전 정상 릴리스를 포함한 versioned release record를 남기고, 배포 명세가 ID, 비밀, 네트워크 정책을 참조하며, 플랫폼별 시작, 트래픽, 생존 gate와 capacity 규칙이 런타임을 제어하는지 확인합니다. 종료 시에는 SIGTERM이 Node 프로세스까지 전달되고, 새 요청 차단과 진행 중 요청 drain, 데이터베이스와 큐 정리가 플랫폼 grace 또는 stop timeout 안에 끝나야 합니다. 로그, 메트릭, 알림에는 scale 변경이나 rollback을 결정할 owner와 검증 결과를 연결합니다.
Cloud Run revision, Azure Container Apps revision, ECS service revision은 지원 범위 안에서 immutable한 release record를 제공합니다. 반면 Kubernetes Deployment revision은 ReplicaSet에 보존되는 Pod template 이력이며 revisionHistoryLimit에 따라 삭제될 수 있고, VM은 팀이 이미지 digest와 배포 명세 이력을 직접 보존해야 합니다. ECS의 실패 감지 후 자동 rollback도 deployment circuit breaker에서 명시적으로 활성화해야 합니다. 어떤 플랫폼이든 자동 복구를 가정하지 말고 이전 정상 release record, 실행 권한과 승인 절차를 실제 rollback 경로로 검증하세요.
DEPLOYMENT READINESS · CONTROL RELATIONSHIPS
실행 성공보다 증거, 제어, 복구 경로를 함께 준비한다
세 구역은 시간 순서가 아니라 운영 책임 경계입니다. versioned release record와 제어면 선언, 런타임 신호를 연결하고 지원 서비스의 immutable revision과 이전 정상 릴리스로 되돌릴 경로를 구분해 기록합니다.
현재와 이전 정상 릴리스 기록을 함께 보존
현재 배포는 승인, 빌드, 배포 기록과 같은 immutable image digest를 가리킵니다. 이전 정상 digest와 versioned record도 실행 가능하게 보존하고 승인된 명시적 rollback 대상으로 연결합니다. immutable revision은 이를 지원하는 서비스에서만 사용합니다.
ID, 비밀, 네트워크와 배포 정책 선언
배포 명세는 workload ID, secret 참조, ingress와 egress, TLS와 DB 경로, 현재 digest, traffic 대상, desired capacity, CPU와 memory를 선언합니다.
시작은 초기화 보호, 트래픽은 요청 수신 가능 여부, 생존은 복구 불가 프로세스 재시작을 뜻합니다. Kubernetes와 Container Apps는 세 probe를 구분하고, Cloud Run readiness는 Preview이며, ECS는 container health와 LB target health를 조합합니다. 각 플랫폼에 capacity와 사용률 신호를 별도로 설정합니다.
런타임 신호를 확장과 복구 판단으로 환류
task, replica, revision instance는 선언한 digest와 workload ID, 허용된 network 경로로 실행됩니다. 종료 시 SIGTERM을 전달하고 grace 또는 stop timeout 안에 요청을 drain하며 DB와 queue resource를 정리합니다.
구조화 log, metric, alert에서 오류율, 지연, 포화도 신호를 만들고 scale 변경, remediation 또는 이전 릴리스 rollback의 owner, 결정, 검증 결과를 기록합니다.
- 선언, binding, 제어 관계
- 런타임 evidence의 feedback
Cloud Run, Azure Container Apps, ECS가 제공하는 immutable revision은 지원 서비스의 release record입니다. Kubernetes Deployment revision은 보존 한도가 있는 ReplicaSet 이력이고 VM 이력은 팀이 관리합니다. 화살표는 시간 순서가 아니라 선언, 제어, evidence의 방향이며 강조선은 owner가 이전 정상 릴리스를 선택하는 명시적 rollback입니다.
운영 모델 선택과 배포 준비는 별개의 판단입니다. 관리형 서비스도 애플리케이션 이미지, 자격 증명, 네트워크, 상태 확인, 관측성과 롤백 책임까지 대신해 주지는 않습니다.