본문 바로가기

인턴

인턴 1주차 - Helm을 활용한 NestJS 애플리케이션 Kubernetes 배포

Helm을 활용한 NestJS 애플리케이션 Kubernetes 배포 실습

저번 실습에 이어서 이번에는 Helm을 활용하여 NestJS 애플리케이션을 Kubernetes 클러스터에 배포하는 실습을 진행했습니다. Helm Chart를 생성하고 ClusterIP, NodePort, LoadBalancer 세 가지 Service 유형으로 로컬 환경에 배포해본 경험을 정리합니다.


🔹 Helm이란?

Helm은 Kubernetes의 패키지 관리자입니다. Helm을 사용하면 여러 Kubernetes 리소스를 하나의 패키지로 묶어 관리할 수 있으며 이를 통해 재사용성과 배포 편의성을 크게 높일 수 있습니다.

🔹 Helm의 핵심 구성 요소

  • Chart: Kubernetes 리소스가 정의된 패키지 단위 (Chart.yaml, values.yaml, templates 등 포함)
  • Release: 클러스터에 설치된 차트의 인스턴스
  • Repository: Chart 저장하고 공유하는 저장

🔹 Helm의 주요 명령 흐름

  • helm install: 차트 기반 리소스를 설치하며 릴리스 생성
  • helm upgrade: 기존 릴리스를 새 설정으로 갱신
  • helm rollback: 문제 발생 시 이전 릴리스로 복구
  • helm uninstall: 설치된 릴리스 및 관련 리소스 제거

🔹 Helm의 장점

  • 패키지화로 관리 효율성 향상
  • 버전 관리로 배포 이력 추적 가능
  • 재사용성이 뛰어나 여러 환경에 쉽게 적용 가능
  • 자동화된 배포 및 롤백 가능

🔹 설치 및 버전 확인

  • Windows 환경에서 Chocolatey로 Helm 설치

  • Helm 버전 확인


🔹Helm Chart 생성 및 구조

  • Helm Chart를 생성하여 time-service 애플리케이션 패키징

  • Chart 구조 


실습 1: ClusterIP 타입으로 배포

📄values.yaml 설정

이 파일은 Helm 차트의 기본 설정을 정의합니다. 여기에는 Docker 이미지 정보, 레플리카 수, ClusterIP 타입의 서비스 설정이 포함됩니다. 또한, 자동 스케일링과 리소스 요청 및 제한 설정 그리고 Ingress와 노드 선택 조건 등 네트워킹 및 배포 관련 설정도 포함되어 있습니다.

replicaCount: 1

image:
  repository: sj2012/time-service
  pullPolicy: IfNotPresent
  tag: latest

service:
  type: ClusterIP
  port: 80

serviceAccount:
  create: true
  name: ''

ingress:
  enabled: false
  annotations: {}
  hosts:
    - host: chart-example.local
      paths: []
  tls: []

autoscaling:
  enabled: false
  minReplicas: 1
  maxReplicas: 100
  targetCPUUtilizationPercentage: 80

resources: {}
nodeSelector: {}
tolerations: []
affinity: {}

📄 deployment.yaml  설정

Kubernetes Deployment 리소스를 정의하는 Helm 템플릿입니다. 이 템플릿은 애플리케이션의 복제본을 관리하고 배포하는 데 사용됩니다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "time-service.fullname" . }}
  labels:
    {{- include "time-service.labels" . | nindent 4 }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      {{- include "time-service.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      labels:
        {{- include "time-service.selectorLabels" . | nindent 8 }}
    spec:
      serviceAccountName: {{ include "time-service.serviceAccountName" . }}
      containers:
        - name: {{ .Chart.Name }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          ports:
            - containerPort: 3000
          resources:
            {{- toYaml .Values.resources | nindent 12 }}

📄 service.yaml 설정

Helm 차트와 values.yaml 파일을 사용하여 Kubernetes 서비스 리소스를 동적으로 생성합니다.

apiVersion: v1
kind: Service
metadata:
  name: {{ include "time-service.fullname" . }}
  labels:
    {{- include "time-service.labels" . | nindent 4 }}
spec:
  type: {{ .Values.service.type }}
  ports:
    - port: {{ .Values.service.port }}
      targetPort: 3000
  selector:
    {{- include "time-service.selectorLabels" . | nindent 4 }}

📄  chart.yaml 설정

Helm 차트의 메타데이터를 정의하는 파일입니다. 차트의 이름, 버전, 설명, 애플리케이션 버전 등을 포함하고 있으며 Helm CLI와 다른 도구들이 차트를 이해하고 사용할 수 있도록 합니다.

apiVersion: v2
name: time-service
description: A Helm chart for Kubernetes

type: application

version: 0.1.0

appVersion: '1.0'

📄  serviceaccount.yaml 설정

Helm 템플릿을 사용하여 Kubernetes 서비스 계정을 정의합니다.

{{- if .Values.serviceAccount.create }}
apiVersion: v1
kind: ServiceAccount
metadata:
  name: {{ include "time-service.serviceAccountName" . }}
  labels:
    {{- include "time-service.labels" . | nindent 4 }}
{{- end }}

🔹 Helm 차트 배포

helm install 명령어를 사용하여 Helm 차트를 배포했습니다.

🔹  클러스터에 설치된 helm 릴리스

time-service 이름으로 Helm 릴리스가 default 네임스페이스에 설치되었습니다.

🔹 애플리케이션 상태 확인

kubectl get pods, kubectl get services 명령어를 사용하여 Kubernetes 클러스터에서 애플리케이션의 상태를 확인했습니다.

🔹 애플리케이션 로그 확인

kubectl logs <pod-name> 명령어를 사용하여 로그를 확인했습니다.

🔹  포트 포워딩

  • ClusterIP 서비스 타입은 Kubernetes 클러스터 내부에서만 접근할 수 있도록 설정되어 포트 포워딩을 진행했습니다.
  • Kubernetes 클러스터 내의 Pod에 접근하기 위해 로컬 포트 8080을 해당 Pod의 포트 3000으로 포워딩하도록 했습니다.

🔹HTTP 요청 확인

  • 8080포트로 요청하여 200 성공 응답이 오고 content에 시간이 오는 것을 확인했습니다.


실습 2: NodePort 타입으로 배포

📄 values.yaml 설정

service:
  type: NodePort
  port: 80

🔹릴리스 업그레이드

Helm을 사용하여 기존 time-service 릴리스를 업그레이드합니다.

🔹 애플리케이션 상태 확인

  • kubectl get pods, kubectl get services 명령어를 사용하여 Kubernetes 클러스터에서 애플리케이션의 상태를 확인했습니다.
  • NodePort 서비스 타입은 포트포워딩 없이 Kubernetes 클러스터 외부에서 특정 포트를 통해 서비스에 접근할 수 있습니다.
  • 외부에서 접근할 수 있는 NodePort는 31289로 나왔습니다.

🔹HTTP 요청 확인

  • 위에서 확인한 31289 포트로 요청을 보냈습니다.
  • 200 성공 응답이 오고 content에 시간이 오는 것을 확인했습니다.


🔹실습 3: LoadBalancer 타입으로 배포

📄 values.yaml 설정

service:
  type: LoadBalancer
  port: 80

🔹릴리스 업그레이드

Helm을 사용하여 기존 time-service 릴리스를 업그레이드합니다.

🔹 애플리케이션 상태 확인

  • kubectl get pods, kubectl get services 명령어를 사용하여 Kubernetes 클러스터에서 애플리케이션의 상태를 확인했습니다.

🔹  포트 포워딩

  • Kubernetes 클러스터 내의 Pod에 접근하기 위해 로컬 포트 8080을 해당 Pod의 포트 3000으로 포워딩하도록 했습니다.

🔹HTTP 요청 확인

  • 8080포트로 요청하여 200 응답이 오고 content에 시간이 오는 것을 확인했습니다.


초기 서비스 배포 시 문제 및 해결

🔹 첫 시도: LoadBalancer 사용

  • values.yaml에서 service.type: LoadBalancer 설정
  • kubectl get svc로 확인 시 무한로딩

🔹 문제 원인 및 해결

  • 문제 원인: LoadBalancer 서비스 타입은 일반적으로 클라우드 환경(AWS, GCP, Azure 등)에서 사용되며 외부에서 접근할 수 있도록 공인 IP를 자동으로 할당합니다. 그러나 로컬 Kubernetes 환경(Docker Desktop, Minikube 등)에서는 외부 IP가 자동으로 할당되지 않기 때문에 문제가 발생합니다.
  • 해결 방법: 로컬 환경에서는 LoadBalancer로 외부 접근이 불가능하기 때문에 ClusterIP 서비스에서 사용하는 방식처럼 kubectl port-forward 명령어를 사용해 로컬 포트를 서비스 포트로 연결했습니다.
    이를 통해 애플리케이션이 정상적으로 동작하는지 확인할 수 있었습니다.
kubectl port-forward svc/time-service 8080:80

Kubernetes 서비스 타입 비교

서비스 타입 설명 외부 접근 가능 여부 로컬 테스트 방식
ClusterIP 기본값. 클러스터 내부에서만 접근 가능 ❌ 불가능 kubectl port-forward 사용
NodePort 외부에서 노드 IP와 포트를 통해 접근 가능 ✅ 가능 노드 IP + NodePort로 접근
LoadBalancer 클라우드에서 외부 IP 자동 할당. 로컬에서는 동작하지 않음 ❌ 불가능 (로컬 기준) kubectl port-forward 사용

틀린 내용이 있다면 언제든지 지적 부탁드립니다!