티스토리 뷰

반응형

쿠버네티스 실습: Helm Chart로 NestJS + NextJS + PostgreSQL + Redis 통합 배포하기

앞선 글에서는 Helm Chart에 ConfigMap, Secret, PVC, Ingress, HPA를 통합해 실제 서비스 수준의 배포 패키지를 구성했습니다.
이번 글에서는 우리가 목표로 하는 프로젝트, 즉 NestJS + NextJS + PostgreSQL + Redis 기반의 위치 기반 주유소 정보 시스템을 Helm Chart 하나로 배포하는 과정을 실습합니다.


1) 전체 아키텍처

구성 요소:

  • NestJS: 백엔드 API 서버
  • NextJS: 프론트엔드 웹 애플리케이션
  • PostgreSQL: 주유소 데이터 저장소
  • Redis: 캐싱/세션 관리
  • Ingress: 외부 트래픽 라우팅
  • PVC: PostgreSQL 데이터 영속성
  • ConfigMap/Secret: 환경 변수 및 민감 정보 관리
  • HPA: 트래픽에 따른 Pod 오토스케일링

구조 다이어그램:

[사용자] → [Ingress] → [NextJS Service] 
                      → [NestJS Service] → [PostgreSQL PVC]
                                         → [Redis Service]

2) Chart 구조

fuelstation-app/
  Chart.yaml
  values.yaml
  templates/
    deployment-backend.yaml
    deployment-frontend.yaml
    service-backend.yaml
    service-frontend.yaml
    postgres-statefulset.yaml
    postgres-service.yaml
    redis-deployment.yaml
    redis-service.yaml
    configmap.yaml
    secret.yaml
    pvc.yaml
    ingress.yaml
    hpa.yaml

3) values.yaml

반응형
image:
  backend: <DOCKER_USERNAME>/nestjs-app:latest
  frontend: <DOCKER_USERNAME>/nextjs-app:latest
  postgres: postgres:15
  redis: redis:7

service:
  backendPort: 3000
  frontendPort: 3000
  dbPort: 5432
  redisPort: 6379

ingress:
  hostBackend: api.fuel.local
  hostFrontend: www.fuel.local

persistence:
  dbSize: 1Gi

secrets:
  dbPassword: supersecret

hpa:
  backendTargetCPU: 60
  frontendTargetCPU: 50

4) Deployment 예시 (NestJS 백엔드)

templates/deployment-backend.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Release.Name }}-backend
spec:
  replicas: 2
  selector:
    matchLabels:
      app: {{ .Release.Name }}-backend
  template:
    metadata:
      labels:
        app: {{ .Release.Name }}-backend
    spec:
      containers:
      - name: nestjs
        image: {{ .Values.image.backend }}
        ports:
        - containerPort: {{ .Values.service.backendPort }}
        env:
        - name: DATABASE_URL
          value: "postgres://postgres:{{ .Values.secrets.dbPassword }}@{{ .Release.Name }}-postgres:{{ .Values.service.dbPort }}/fuel"
        - name: REDIS_URL
          value: "redis://{{ .Release.Name }}-redis:{{ .Values.service.redisPort }}"

5) PostgreSQL StatefulSet

templates/postgres-statefulset.yaml

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: {{ .Release.Name }}-postgres
spec:
  serviceName: {{ .Release.Name }}-postgres
  replicas: 1
  selector:
    matchLabels:
      app: {{ .Release.Name }}-postgres
  template:
    metadata:
      labels:
        app: {{ .Release.Name }}-postgres
    spec:
      containers:
      - name: postgres
        image: {{ .Values.image.postgres }}
        ports:
        - containerPort: {{ .Values.service.dbPort }}
        env:
        - name: POSTGRES_PASSWORD
          value: {{ .Values.secrets.dbPassword | quote }}
        volumeMounts:
        - name: postgres-storage
          mountPath: /var/lib/postgresql/data
  volumeClaimTemplates:
  - metadata:
      name: postgres-storage
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: {{ .Values.persistence.dbSize }}

6) Redis Deployment

templates/redis-deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Release.Name }}-redis
spec:
  replicas: 1
  selector:
    matchLabels:
      app: {{ .Release.Name }}-redis
  template:
    metadata:
      labels:
        app: {{ .Release.Name }}-redis
    spec:
      containers:
      - name: redis
        image: {{ .Values.image.redis }}
        ports:
        - containerPort: {{ .Values.service.redisPort }}

7) Ingress 예시

templates/ingress.yaml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: {{ .Release.Name }}-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - host: {{ .Values.ingress.hostBackend }}
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: {{ .Release.Name }}-backend
            port:
              number: {{ .Values.service.backendPort }}
  - host: {{ .Values.ingress.hostFrontend }}
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: {{ .Release.Name }}-frontend
            port:
              number: {{ .Values.service.frontendPort }}

8) 배포 및 검증

8-1. Chart 배포

helm install fuelstation ./fuelstation-app

8-2. 리소스 확인

kubectl get all
kubectl get ingress

8-3. 도메인 매핑

/etc/hosts에 추가:

192.168.49.2   api.fuel.local www.fuel.local

브라우저 확인:


9) 정리

  • Helm Chart를 활용해 **백엔드(NestJS) + 프론트엔드(NextJS) + DB(PostgreSQL) + 캐시(Redis)**까지 통합 배포 완료.
  • ConfigMap/Secret/PVC/Ingress/HPA 등 운영 필수 리소스를 포함해 실제 서비스 운영 가능한 구조 확보.
  • 이제부터는 클라우드(Kubernetes on GCP, AWS, Azure) 환경으로 옮겨도 큰 변경 없이 배포 가능.

다음 글에서는 **운영 환경에서 발생할 수 있는 트러블슈팅 사례 (CrashLoopBackOff, PVC 바인딩 실패, Ingress 문제 등)**를 실제 시나리오로 정리해보겠습니다.


 

쿠버네티스,Helm,통합배포,NestJS,NextJS,PostgreSQL,Redis,DevOps,Minikube,K8s실습

 

 


✅ 좋았던 부분

먼저 장점부터요:

  • ConfigMap, Secret, PVC, Ingress, HPA가 한 차트에 포함되어 있어 “풀스택 배포” 느낌이 강해짐
  • NestJS + NextJS + PostgreSQL + Redis 조합은 실제 프로젝트 환경에서 자주 쓰이는 조합임
  • values.yaml 통해 이미지, 포트, 크기, 호스트, 비밀값 등이 명확히 분리되어 있어 환경 간 설정 차별 가능

⚠️ 보완/주의할 점 & 웹 자료 참고

아래 항목들은 실습 중 흔히 발생하는 오류나 혼란을 줄이기 위해 보강되면 좋습니다.

항목 설명 & 개선 제안 관련 웹 자료

Ingress rewrite-target & pathType 사용 주의 Ingress에서 nginx.ingress.kubernetes.io/rewrite-target을 사용할 땐 path 패턴을 적절히 설정해야 하고, use-regex: "true" 등의 설정이 필요할 수 있음. 기본 / path 사용 시 rewrite가 불필요하거나 오히려 잘못된 라우팅을 유발할 수 있음. 예: pathPrefix 사용하는 방식이나 regex 캡처 그룹을 쓰는 방식. 또한, Ingress 클래스(ingressClassName 또는 annotation) 명시하는 것이 안정적임. NGINX Ingress 공식 문서: rewrite-target 예시들 (kubernetes.github.io) “Nginx Ingress & rewrite-target” 스택오버플로우 Q&A에서 capture group 사용 예 (Stack Overflow)
Helm 차트 구조와 Best Practices 차트 버전 관리, Chart.yaml 내 version vs appVersion 구분, dependency 사용 여부 등의 기본 원칙을 지키면 좋음. 예: PostgreSQL, Redis 같은 부품을 umbrella chart 또는 subcharts로 분리하면 유지보수가 쉬워짐. 또한, values.yaml 안에 기본값과 optional 설정(예: Ingress enabled 여부, HPA enabled 여부)을 넣고, templates 안에 if 분기 처리 추가하여 유연성 확보. Helm 공식 “Chart Best Practices Guide” 문서 (helm.sh) Plural.sh의 “Kubernetes Helm Charts: A Practical Guide” 에서도 구조화/버전 관리 중요성 강조됨 (plural.sh)
Secret / DB 접속 정보 보안 Secret 값이 values.yaml에 노출되는 형태이면 실습엔 괜찮지만, 운영 환경에선 암호화 또는 외부 Secret Manager 연동 방식 고려해야 함. Helm 차트에서는 secrets 블록을 optional로, 또는 stringData vs data 방식 명확히 구분되도록 하면 좋음.  
PVC + StatefulSet 주의 PostgreSQL StatefulSet 사용 시, VolumeClaimTemplates 이름/스토리지 클래스 지정 등이 필요함. 또한, 리소스 제한(Requests/Limits)도 설정하면 안정성 증가. 또한, StorageClass 기본 설정 여부(동적 프로비저닝) 확인 필요.  
HPA 설정 시 조건 명확화 HPA를 enabled 옵션으로 감출 수 있게 하고, resources.requests 및 limits 설정이 반드시 포함된 Deployment 템플릿이어야 HPA가 정상 작동. 또한, Backend/Frontend 각각 따로 HPA 붙이는 경우 트래픽 패턴 차이를 고려해서 타겟 CPU %, 최소/최대 replica 수 조정 필요.  
Helm values override 및 여러 환경 지원 예: values-staging.yaml, values-prod.yaml 같이 환경별 값 파일 두고, helm install/upgrade --values 또는 --set 활용하는 패턴을 문서로 포함하면 좋음. 운영 환경에서는 롤백/upgrade 테스트 포함하는 것이 베스트 프랙티스.  

🔄 수정/보강 예시 제안

아래는 제안하신 배포 예시에 위 사항을 반영한 수정안 일부입니다.

# templates/ingress.yaml 일부 예
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: {{ .Release.Name }}-ingress
  annotations:
    kubernetes.io/ingress.class: "nginx"
    {{- if .Values.ingress.forceSSLRedirect }} nginx.ingress.kubernetes.io/force-ssl-redirect: "true" {{- end }}
    {{- if .Values.ingress.rewriteTarget }} nginx.ingress.kubernetes.io/rewrite-target: {{ .Values.ingress.rewriteTarget }} {{- end }}
spec:
  ingressClassName: nginx
  rules:
  - host: {{ .Values.ingress.hostBackend }}
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: {{ .Release.Name }}-backend
            port:
              number: {{ .Values.service.backendPort }}
  - host: {{ .Values.ingress.hostFrontend }}
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: {{ .Release.Name }}-frontend
            port:
              number: {{ .Values.service.frontendPort }}
# values.yaml 일부 예시 보강
ingress:
  enabled: true
  hostBackend: api.fuel.local
  hostFrontend: www.fuel.local
  forceSSLRedirect: false
  rewriteTarget: "/$1"  # 필요 시만 사용하는 옵션

hpa:
  enabled: true
  backendTargetCPU: 60
  backendMinReplicas: 1
  backendMaxReplicas: 5
  frontendTargetCPU: 50
  frontendMinReplicas: 1
  frontendMaxReplicas: 3

 

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/04   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30
글 보관함
반응형