지난 포스트에 이어 이번 포스트에서는 K3s 기반 서버 배포 내용을 다뤄보겠다.
Redis 배포
Redis는 K3s 클러스터 내부에서 ClusterIP 서비스로 배포한다. 외부에 노출하지 않고 NestJS 애플리케이션과 동일한 클러스터 내부에서만 접근 가능하도록 구성할 것이다.
<redis-deployment.yaml>
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis-deployment
spec:
replicas: 1
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:7-alpine
ports:
- containerPort: 6379
<redis-service.yaml>
apiVersion: v1
kind: Service
metadata:
name: redis-service
spec:
selector:
app: redis
ports:
- protocol: TCP
port: 6379
targetPort: 6379
type: ClusterIP
ClusterIP 타입으로 설정하여 클러스터 내부의 NestJS 파드에서 redis-service:6379로 접근한다.
이제 Redis 배포를 적용해 보자.
kubectl apply -f redis-deployment.yaml
kubectl apply -f redis-service.yaml
Prisma 마이그레이션
데이터베이스 스키마 마이그레이션은 Kubernetes의 initContainer 패턴을 활용할 것이다. NestJS 메인 컨테이너가 시작되기 전에 initContainer가 먼저 실행되어 마이그레이션을 완료한다.
※ initContainer: Pod 내에서 메인 컨테이너가 시작되기 전에 먼저 실행되고 종료되는 초기화 전용 컨테이너
initContainers:
- name: prisma-migrate
image: <Docker Hub user>/<Image name>:<Tag>
command: ['npx', 'prisma', 'migrate', 'deploy']
envFrom:
- secretRef:
name: project-secret
후에 프로젝트 deployment.yaml 파일을 작성할 때 사용할 것이다.
마이그레이션 흐름
1. Pod 스케줄링 - K3s가 project-deployment Pod를 특정 노드에 배치
2. initContainer 시작 - prisma-migrate 컨테이너 실행
3. 마이그레이션 실행 - npx prisma migrate deploy 명령으로 pending 마이그레이션 적용
4. initContainer 완료 - 마이그레이션 성공 시 Exit 0으로 종료
5. 메인 컨테이너 시작 - project 컨테이너(NestJS) 정상 기동
6. initContainer가 실패(Exit != 0)하면 Pod가 재시작되며 메인 컨테이너는 실행되지 않음
NestJS 배포
NestJS 애플리케이션은 2개의 replica로 배포하여 고가용성을 확보하였다. 각 Pod에는 헬스체크 설정이 포함되어 있어 비정상 Pod를 자동으로 감지한다.
<project-deployment.yaml>
apiVersion: apps/v1
kind: Deployment
metadata:
name: project-deployment
spec:
replicas: 2
selector:
matchLabels:
app: project
template:
metadata:
labels:
app: project
spec:
initContainers:
- name: prisma-migrate
image: <Docker Hub user>/<Image name>:<Tag>
command: ['npx', 'prisma', 'migrate', 'deploy']
envFrom:
- secretRef:
name: project-secret
containers:
- name: cs
image: <Docker Hub user>/<Image name>:<Tag>
ports:
- containerPort: 8080
envFrom:
- secretRef:
name: project-secret
livenessProbe:
httpGet:
path: /api/health
port: 8080
initialDelaySeconds: 15
periodSeconds: 20
readinessProbe:
httpGet:
path: /api/health
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
<project-service.yaml>
apiVersion: v1
kind: Service
metadata:
name: project-service
spec:
selector:
app: project
ports:
- protocol: TCP
port: 8080
targetPort: 8080
nodePort: 30080
type: NodePort
NodePort 타입으로 설정하여 EC2 인스턴스의 퍼블릭 IP와 30080 포트로 외부에서 NestJS API에 접근한다.
배포하기
이제 NestJS를 배포해 보자.
kubectl apply -f project-development.yaml
kubectl apply -f project-service.yaml
# 전체 Pod 상태 확인
kubectl get pods -w
# initContainer 마이그레이션 로그 확인
kubectl logs <pod-name> -c prisma-migrate
# NestJS 컨테이너 로그 확인
kubectl logs <pod-name> -c cs
# 서비스 목록 확인
kubectl get svc
정상적으로 배포되었는지는 다음 명령어를 통해 확인이 가능하다.
# Pod 상태 확인 (모든 Pod가 Running 이어야 함)
kubectl get pods
# 예상 출력:
# NAME READY STATUS RESTARTS
# project-deployment-xxxxxxxxx-xxxxx 1/1 Running 0
# project-deployment-xxxxxxxxx-yyyyy 1/1 Running 0
# redis-deployment-xxxxxxxxx-zzzzz 1/1 Running 0
# Deployment 롤아웃 상태
kubectl rollout status deployment/project-deployment
# 서비스 확인
kubectl get svc
# API 헬스체크
curl http://<EC2_PUBLIC_IP>:30080/api/health
이때 EC2_PUBLIC_IP는 워커 노드의 IP이다. 나는 Swagger 자동화가 설정되어 있던 상태로 배포했기에 Swagger에서도 헬스체크를 시도해 봤다.

새 버전 NestJS 배포
새 버전의 NestJS 애플리케이션을 배포할 때는 Docker 이미지를 새로 빌드하고 태그를 변경한 후 kubectl 명령으로 롤링 업데이트를 수행한다.
# 새 이미지 빌드 및 푸시
docker build -t <image> .
docker push <image>
# Deployment 이미지 업데이트 (롤링 업데이트 자동 트리거)
kubectl set image deployment/project-deployment cs=<image>
# 롤아웃 상태 모니터링
kubectl rollout status deployment/project-deployment
# 이전 버전으로 롤백 (문제 발생 시)
kubectl rollout undo deployment/project-deployment
롤링 업데이트 시 initContainer(prisma-migrate)가 자동으로 재실행되어 새 스키마 마이그레이션이 적용된다. 또한 replicas: 2 설정으로 인해 무중단 배포가 가능하다.(파드가 삭제되면 다른 파드가 살아남)

감사합니다 (´▽`ʃ♡ƪ)
'DevOps > Cloud Native' 카테고리의 다른 글
| [K3s + Docker] K3s 기반 서버 배포 (1) (0) | 2026.04.12 |
|---|
