클라우드 플랫폼 (AWS, GCP) 배포
지난 절에서는 NestJS 애플리케이션을 위한 CI/CD 파이프라인을 구축하여 자동화된 빌드, 테스트, 배포 프로세스를 만드는 방법을 알아보았습니다. 이제 11장의 세 번째 절로, 컨테이너화된 NestJS 애플리케이션을 클라우드 플랫폼에 배포하는 다양한 전략에 대해 심층적으로 살펴보겠습니다.
클라우드 컴퓨팅은 확장성, 유연성, 고가용성, 그리고 비용 효율성이라는 강력한 이점을 제공하며 현대 소프트웨어 배포의 표준이 되었습니다. AWS, Google Cloud Platform (GCP), Microsoft Azure와 같은 주요 클라우드 서비스 제공업체는 NestJS와 같은 웹 애플리케이션을 배포하고 운영하기 위한 다양한 서비스를 제공합니다.
클라우드 배포 전략 개요
NestJS 애플리케이션을 클라우드에 배포하는 방식은 크게 다음과 같이 나눌 수 있습니다.
IaaS (Infrastructure as a Service) 기반 VM 배포: 가상 머신(VM)을 직접 프로비저닝하고, 그 위에 Docker 환경을 수동으로 설정하여 컨테이너를 실행합니다. 가장 유연하지만 관리 부담이 큽니다.
PaaS (Platform as a Service) 기반 컨테이너 배포: 미리 설정된 플랫폼에 컨테이너 이미지를 배포하면, 플랫폼이 인프라 관리, 스케일링, 로드 밸런싱 등을 자동 처리합니다. 간편하지만 유연성이 제한될 수 있습니다.
컨테이너 오케스트레이션 기반 배포 (Kubernetes): Docker 컨테이너를 대규모로 관리하고 자동화하는 복잡한 시스템입니다. 높은 확장성과 유연성을 제공하지만 학습 곡선이 높습니다.
서버리스 컨테이너 배포: 컨테이너 이미지를 기반으로 하지만, 서버를 직접 관리할 필요 없이 이벤트에 따라 코드가 실행되고 자동으로 스케일링됩니다.
주요 클라우드 플랫폼별 NestJS 배포 전략
각 클라우드 플랫폼은 유사한 목적의 다양한 서비스를 제공합니다. 여기서는 AWS를 중심으로 설명하고, 다른 플랫폼의 대응 서비스도 간략히 언급하겠습니다.
IaaS: 가상 머신 (VM)에 직접 배포
- AWS: EC2 (Elastic Compute Cloud)
- GCP: Compute Engine
- Azure: Azure Virtual Machines
개념: EC2 인스턴스(가상 서버)를 생성하고, 해당 인스턴스에 SSH로 접속하여 Docker를 설치한 다음, 직접 Docker 이미지를 풀(pull)하고 컨테이너를 실행하는 방식입니다.
장점
- 가장 높은 유연성과 제어권을 가집니다.
- 이미 Docker에 익숙하다면 쉽게 시작할 수 있습니다.
단점
- 운영 부담: 서버 패치, 보안 업데이트, 스케일링, 로드 밸런싱, 고가용성 구성 등을 모두 직접 관리해야 합니다.
- 비용 효율성 감소: 유휴 리소스가 발생할 수 있습니다.
NestJS 배포 과정 (EC2 예시)
EC2 인스턴스 생성: Ubuntu, Amazon Linux 2 등의 AMI(Amazon Machine Image)를 선택하고, 인스턴스 타입, 키 페어, 보안 그룹(3000번 포트 및 SSH 22번 포트 허용) 등을 설정합니다.
Docker 설치: 인스턴스에 SSH로 접속하여 Docker Engine을 설치하고 설정합니다.
sudo apt update
sudo apt install docker.io
sudo systemctl start docker
sudo systemctl enable docker
sudo usermod -aG docker ubuntu # 현재 사용자에게 docker 권한 부여
newgrp docker # 그룹 변경 적용
Docker 이미지 풀: Docker Hub 또는 ECR(Elastic Container Registry)에 푸시된 NestJS Docker 이미지를 가져옵니다.
docker pull your-docker-username/nestjs-app:latest
컨테이너 실행: 환경 변수를 주입하여 컨테이너를 실행합니다.
docker run -d -p 80:3000 \
--name nestjs-prod-app \
-e DATABASE_HOST=your_prod_db_host \
-e JWT_SECRET=your_prod_jwt_secret \
your-docker-username/nestjs-app:latest
로드 밸런싱 및 오토 스케일링 (선택 사항): 고가용성과 확장성을 위해 여러 EC2 인스턴스를 만들고, 앞에 ALB (Application Load Balancer) 를 두어 트래픽을 분산하며, Auto Scaling Group (ASG) 을 설정하여 트래픽에 따라 자동으로 인스턴스 수를 조절할 수 있습니다. (이 경우, 컨테이너를 직접 관리하는 방식은 복잡해지므로 ECS/EKS 같은 컨테이너 오케스트레이션으로 넘어가는 것이 일반적입니다.)
PaaS: 컨테이너 이미지 배포
- AWS: AWS App Runner, AWS Elastic Beanstalk
- GCP: Cloud Run, App Engine Flex
- Azure: Azure Container Apps, Azure App Service
개념: 개발자가 컨테이너 이미지만 제공하면, 클라우드 플랫폼이 이미지 빌드, 배포, 스케일링, 로드 밸런싱, SSL/TLS 처리, 로깅 등을 자동으로 처리해주는 서비스입니다. 서버 관리에 대한 부담을 크게 줄여줍니다.
장점
- 관리 용이성: 인프라 관리 부담이 거의 없습니다.
- 빠른 배포: 코드만 푸시하거나 이미지 경로만 지정하면 배포가 자동으로 이루어집니다.
- 자동 스케일링: 트래픽에 따라 자동으로 스케일 아웃/인 됩니다.
- 비용 효율성: 사용한 만큼만 지불합니다.
단점
- 제어권 제한: 인프라에 대한 세부적인 제어가 어렵습니다.
- 벤더 종속성: 특정 클라우드 서비스에 종속될 수 있습니다.
NestJS 배포 과정 (AWS App Runner 예시)
컨테이너 이미지 준비: NestJS 애플리케이션의 Docker 이미지를 Docker Hub 또는 AWS ECR (Elastic Container Registry) 에 푸시합니다. ECR을 사용하면 AWS 서비스 간 연동이 더 편리합니다.
App Runner 서비스 생성
- AWS 콘솔에서 App Runner 서비스 생성으로 이동합니다.
- Source: "Container registry"를 선택하고 ECR 또는 퍼블릭 레지스트리(Docker Hub)의 이미지 URI를 지정합니다.
- Deployment settings: "Automatic" (소스 코드 변경 시 자동 배포) 또는 "Manual"을 선택합니다.
- Service settings
- Service name: 서비스 이름 지정.
- Port: NestJS 앱이 수신 대기하는 포트(예: 3000)를 지정합니다.
- CPU and memory: 필요한 리소스 크기를 선택합니다.
- Environment variables: 데이터베이스 연결 정보, JWT Secret 등 필요한 환경 변수를 설정합니다. 민감 정보는 Secrets Manager와 연동하여 가져올 수 있습니다.
- Auto scaling: 최소/최대 인스턴스 수 및 동시성(Concurrency)을 설정합니다.
- Health checks: 컨테이너의 상태를 확인하기 위한 헬스 체크 경로(예:
/health
)를 설정합니다.
- Networking: VPC 접속이 필요한 경우 설정합니다.
배포: 서비스 생성 후 App Runner가 자동으로 이미지를 풀하고 컨테이너를 실행하며, 공개적으로 접근 가능한 URL을 제공합니다.
커스텀 도메인 설정 (선택 사항): Route 53을 통해 커스텀 도메인을 App Runner 서비스에 연결할 수 있습니다.
컨테이너 오케스트레이션
- AWS: EKS (Elastic Kubernetes Service)
- GCP: GKE (Google Kubernetes Engine)
- Azure: AKS (Azure Kubernetes Service)
개념: 컨테이너화된 애플리케이션의 배포, 확장, 관리, 자동 복구 등을 자동화하는 오픈 소스 플랫폼인 Kubernetes를 클라우드에서 관리형 서비스로 제공합니다. 복잡하지만 가장 강력한 컨테이너 관리 기능을 제공합니다.
장점
- 강력한 확장성 및 고가용성: 대규모, 복잡한 마이크로서비스 아키텍처에 최적화되어 있습니다.
- 유연성: 다양한 워크로드 유형과 배포 전략(롤링 업데이트, 블루/그린, 카나리)을 지원합니다.
- 클라우드 벤더 중립성: Kubernetes 자체가 오픈 소스이므로, 특정 클라우드에 덜 종속적입니다.
단점
- 높은 학습 곡선: Kubernetes 개념과 YAML 설정에 대한 이해가 필요합니다.
- 복잡성: 설정 및 관리가 PaaS 서비스보다 훨씬 복잡합니다.
- 비용: 관리형 서비스라도 클러스터 유지 비용이 발생합니다.
NestJS 배포 과정 (EKS 예시 - 개념적)
EKS 클러스터 생성: AWS 콘솔 또는 eksctl
CLI 도구를 사용하여 EKS 클러스터를 생성합니다.
Kubernetes Manifests 작성: NestJS 애플리케이션을 배포하기 위한 Deployment, Service, Ingress 등의 YAML 파일을 작성합니다.
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nestjs-app
labels: { app: nestjs-app }
spec:
replicas: 3 # NestJS 인스턴스 3개 실행
selector: { matchLabels: { app: nestjs-app } }
template:
metadata: { labels: { app: nestjs-app } }
spec:
containers:
- name: nestjs-container
image: your-ecr-repo/nestjs-app:latest # ECR 이미지 사용
ports: [{ containerPort: 3000 }]
env: # 환경 변수 (Kubernetes Secrets 또는 ConfigMaps 활용)
- name: DATABASE_HOST
valueFrom: { secretKeyRef: { name: 'app-secrets', key: 'DATABASE_HOST' } }
- name: JWT_SECRET
valueFrom: { secretKeyRef: { name: 'app-secrets', key: 'JWT_SECRET' } }
resources: # 리소스 요청 및 제한 설정 (매우 중요)
requests: { cpu: "100m", memory: "128Mi" }
limits: { cpu: "500m", memory: "256Mi" }
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: nestjs-app-service
spec:
selector: { app: nestjs-app }
ports: [{ protocol: TCP, port: 80, targetPort: 3000 }]
type: LoadBalancer # 클라우드 로드 밸런서 프로비저닝 (AWS ELB)
Kubernetes Secrets/ConfigMaps 설정: 민감한 환경 변수는 Kubernetes Secrets에, 민감하지 않은 설정은 ConfigMaps에 저장합니다.
kubectl
로 배포: kubectl apply -f deployment.yaml -f service.yaml
명령어를 사용하여 Kubernetes 클러스터에 배포합니다.
Helm (선택 사항): 복잡한 애플리케이션의 Kubernetes 배포를 패키징하고 관리하기 위한 도구인 Helm을 사용할 수 있습니다.
서버리스 컨테이너 배포
- AWS: AWS Fargate (ECS 또는 EKS와 연동), AWS Lambda (컨테이너 이미지 지원)
- GCP: Cloud Run (컨테이너 이미지를 직접 실행하는 서버리스 플랫폼)
- Azure: Azure Container Apps, Azure Functions (컨테이너 이미지 지원)
개념: 개발자가 컨테이너 이미지를 제공하면, 클라우드 공급자가 서버 프로비저닝, 스케일링, 인프라 관리를 모두 담당하여 '서버리스' 환경을 제공합니다. 사용한 리소스(실행 시간, 메모리 등)에 대해서만 비용을 지불합니다.
장점
- 진정한 서버리스: 서버를 전혀 관리할 필요가 없습니다.
- 뛰어난 비용 효율성: 유휴 상태일 때는 비용이 거의 발생하지 않습니다.
- 자동 스케일링: 트래픽이 0에서부터 수백만 요청까지 자동으로 확장됩니다.
단점
- 콜드 스타트(Cold Start): 유휴 상태에서 첫 요청이 들어올 때 컨테이너가 시작되는 지연 시간이 발생할 수 있습니다.
- 제한 사항: 장시간 실행되거나 특정 네트워크/파일 시스템 접근이 필요한 애플리케이션에는 적합하지 않을 수 있습니다.
- 복잡한 상태 관리: 서버리스 환경에서는 상태 비저장(Stateless) 아키텍처가 권장됩니다.
NestJS 배포 과정 (AWS Fargate on ECS 예시 - 개념적)
ECR에 이미지 푸시: NestJS Docker 이미지를 AWS ECR에 푸시합니다.
ECS 클러스터 및 Fargate 태스크 정의
- ECS 클러스터 생성: Fargate 런치 타입으로 ECS 클러스터를 생성합니다.
- 태스크 정의(Task Definition): NestJS 컨테이너 이미지, CPU/메모리, 환경 변수, 포트 매핑 등을 정의합니다.
networkMode: awsvpc
로 설정하여 ENI(Elastic Network Interface)를 통해 VPC 네트워크에 직접 연결되도록 합니다. - 서비스(Service) 생성: 태스크 정의를 사용하여 ECS 서비스(
Fargate
런치 타입)를 생성하고, desired count, 최소/최대 태스크 수, 로드 밸런서(ALB) 연동 등을 설정합니다.
ALB (Application Load Balancer) 연동: ECS 서비스와 ALB를 연동하여 외부 트래픽을 NestJS 컨테이너로 라우팅합니다.
자동 스케일링: CloudWatch 메트릭(CPU 사용량, 요청 수)에 따라 ECS 서비스의 태스크 수를 자동으로 조절하도록 설정합니다.
NestJS 애플리케이션을 클라우드에 배포하는 전략은 애플리케이션의 규모, 복잡성, 팀의 운영 역량, 그리고 예산에 따라 달라집니다. 소규모 프로젝트나 빠른 시작이 필요하다면 PaaS(App Runner, Cloud Run)가 적합하고, 높은 제어권과 유연성이 필요하지만 운영 부담이 있는 경우 IaaS(EC2)가, 대규모 마이크로서비스와 복잡한 요구사항에는 컨테이너 오케스트레이션(EKS, GKE)이 강력한 선택지입니다. 비용 효율성과 서버 관리의 최소화를 위해서는 서버리스 컨테이너(Fargate, Cloud Run)도 고려해볼 만합니다.
각 클라우드 플랫폼의 장단점을 이해하고, 자신의 프로젝트에 가장 적합한 배포 전략을 선택하는 것이 중요합니다. 이 절에서 다룬 내용들이 여러분의 NestJS 애플리케이션을 성공적으로 클라우드에 배포하고 운영하는 데 도움이 되기를 바랍니다.