DevOps

Kubernetes 실전 운영 - Helm, Ingress, HPA 오토스케일링

백엔드 개발자 김승원 2026. 4. 7. 10:54

들어가며

이전 글에서 Kubernetes의 핵심 개념인 Pod, Service, Deployment를 살펴봤습니다. 하지만 실제 프로덕션 환경에서 K8s를 운영하려면 더 많은 도구와 개념이 필요합니다.

이 글에서는 패키지 매니저 Helm, HTTP 라우팅을 담당하는 Ingress, 트래픽에 따라 자동으로 스케일링하는 HPA, 그리고 안정적인 운영을 위한 Probe, Resource 관리, PDB 등을 Spring Boot 애플리케이션 배포 예제와 함께 다룹니다.

Helm - Kubernetes 패키지 매니저

Helm은 Kubernetes 애플리케이션의 패키징, 배포, 버전 관리를 위한 도구입니다. 여러 YAML 매니페스트를 하나의 Chart로 묶어 재사용 가능하게 만듭니다.

Helm Chart 디렉토리 구조

my-spring-app/
├── Chart.yaml          # 차트 메타데이터
├── values.yaml         # 기본 설정값
├── values-dev.yaml     # 개발 환경 오버라이드
├── values-prod.yaml    # 운영 환경 오버라이드
├── templates/
│   ├── _helpers.tpl    # 템플릿 헬퍼 함수
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── ingress.yaml
│   ├── configmap.yaml
│   ├── secret.yaml
│   ├── hpa.yaml
│   └── NOTES.txt       # 설치 후 안내 메시지
└── charts/             # 의존성 차트

Chart.yaml

apiVersion: v2
name: my-spring-app
description: A Helm chart for Spring Boot application
type: application
version: 1.0.0       # Chart 버전
appVersion: "2.0.0"  # 애플리케이션 버전

values.yaml - 기본 설정값

# values.yaml
replicaCount: 2

image:
  repository: myregistry/spring-boot-app
  tag: "2.0.0"
  pullPolicy: IfNotPresent

service:
  type: ClusterIP
  port: 80
  targetPort: 8080

ingress:
  enabled: true
  className: nginx
  hosts:
    - host: api.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: api-tls
      hosts:
        - api.example.com

resources:
  requests:
    memory: "512Mi"
    cpu: "500m"
  limits:
    memory: "1Gi"
    cpu: "1000m"

autoscaling:
  enabled: true
  minReplicas: 2
  maxReplicas: 10
  targetCPUUtilizationPercentage: 70

spring:
  profiles: production
  datasource:
    url: jdbc:mysql://db:3306/mydb

templates/deployment.yaml - 템플릿

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "my-spring-app.fullname" . }}
  labels:
    {{- include "my-spring-app.labels" . | nindent 4 }}
spec:
  {{- if not .Values.autoscaling.enabled }}
  replicas: {{ .Values.replicaCount }}
  {{- end }}
  selector:
    matchLabels:
      {{- include "my-spring-app.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      labels:
        {{- include "my-spring-app.selectorLabels" . | nindent 8 }}
    spec:
      containers:
      - name: {{ .Chart.Name }}
        image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
        imagePullPolicy: {{ .Values.image.pullPolicy }}
        ports:
        - containerPort: {{ .Values.service.targetPort }}
        env:
        - name: SPRING_PROFILES_ACTIVE
          value: {{ .Values.spring.profiles | quote }}
        resources:
          {{- toYaml .Values.resources | nindent 12 }}
        livenessProbe:
          httpGet:
            path: /actuator/health/liveness
            port: {{ .Values.service.targetPort }}
          initialDelaySeconds: 60
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /actuator/health/readiness
            port: {{ .Values.service.targetPort }}
          initialDelaySeconds: 20
          periodSeconds: 5

Helm 핵심 명령어

# 차트 설치
helm install my-app ./my-spring-app

# 환경별 values 파일 지정
helm install my-app ./my-spring-app -f values-prod.yaml

# 업그레이드
helm upgrade my-app ./my-spring-app -f values-prod.yaml

# 설치 또는 업그레이드 (멱등성)
helm upgrade --install my-app ./my-spring-app -f values-prod.yaml

# 릴리스 목록 조회
helm list

# 롤백
helm rollback my-app 1   # revision 1로 롤백

# 릴리스 삭제
helm uninstall my-app

# 템플릿 렌더링 결과 미리 확인
helm template my-app ./my-spring-app -f values-prod.yaml

Ingress - HTTP 라우팅과 TLS

Ingress는 클러스터 외부에서 내부 서비스로의 HTTP/HTTPS 라우팅 규칙을 정의합니다. 하나의 로드밸런서로 여러 서비스에 도메인/경로 기반 라우팅이 가능합니다.

Nginx Ingress Controller 설치

# Helm으로 Nginx Ingress Controller 설치
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update

helm install ingress-nginx ingress-nginx/ingress-nginx \
  --namespace ingress-nginx \
  --create-namespace \
  --set controller.replicaCount=2

Ingress 리소스 정의

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: backend-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/proxy-body-size: "10m"
    nginx.ingress.kubernetes.io/rate-limit-connections: "5"
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - api.example.com
    secretName: api-tls-secret
  rules:
  - host: api.example.com
    http:
      paths:
      - path: /api/v1/users
        pathType: Prefix
        backend:
          service:
            name: user-service
            port:
              number: 80
      - path: /api/v1/orders
        pathType: Prefix
        backend:
          service:
            name: order-service
            port:
              number: 80
      - path: /api/v1/products
        pathType: Prefix
        backend:
          service:
            name: product-service
            port:
              number: 80

TLS 인증서 자동 발급 (cert-manager)

# cert-manager 설치
helm repo add jetstack https://charts.jetstack.io
helm install cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --create-namespace \
  --set installCRDs=true

# ClusterIssuer 정의 (Let's Encrypt)
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: admin@example.com
    privateKeySecretRef:
      name: letsencrypt-prod-key
    solvers:
    - http01:
        ingress:
          class: nginx

HPA - Horizontal Pod Autoscaler

HPA는 CPU, 메모리 사용량 또는 커스텀 메트릭을 기반으로 Pod 수를 자동 조절합니다.

HPA 설정

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: spring-app-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: spring-app
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 80
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 30
      policies:
      - type: Pods
        value: 2
        periodSeconds: 60    # 60초마다 최대 2개씩 증가
    scaleDown:
      stabilizationWindowSeconds: 300  # 5분 안정화 대기
      policies:
      - type: Percent
        value: 10
        periodSeconds: 60   # 60초마다 최대 10%씩 감소

HPA 동작 확인

# HPA 상태 확인
kubectl get hpa
kubectl describe hpa spring-app-hpa

# 부하 테스트로 HPA 동작 확인
kubectl run load-test --image=busybox --rm -it -- \
  /bin/sh -c "while true; do wget -q -O- http://spring-app-service; done"

VPA - Vertical Pod Autoscaler

HPA가 Pod 수를 조절한다면, VPA는 개별 Pod의 리소스(CPU/메모리) 요청량을 자동 조절합니다.

apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: spring-app-vpa
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: spring-app
  updatePolicy:
    updateMode: "Auto"  # Off, Initial, Recreate, Auto
  resourcePolicy:
    containerPolicies:
    - containerName: app
      minAllowed:
        cpu: 250m
        memory: 256Mi
      maxAllowed:
        cpu: 2000m
        memory: 2Gi

Resource Requests와 Limits

적절한 리소스 설정은 안정적인 K8s 운영의 핵심입니다.

Spring Boot 애플리케이션 권장 설정

resources:
  requests:
    memory: "512Mi"   # JVM 힙 + 메타스페이스 + 여유
    cpu: "500m"       # 0.5 vCPU
  limits:
    memory: "1Gi"     # OOMKilled 방지를 위한 여유
    cpu: "1000m"      # 1 vCPU - CPU throttling 주의

주의사항:

  • Memory Limit: JVM의 -Xmx보다 충분히 크게 설정 (메타스페이스, 스레드 스택 고려)
  • CPU Limit: 너무 낮으면 CPU throttling으로 응답 지연 발생
  • Request = Limit으로 설정하면 QoS Class가 Guaranteed가 되어 OOM 시 마지막에 종료됨

QoS Class

QoS Class 조건 OOM 우선순위
Guaranteed 모든 컨테이너의 requests = limits 가장 마지막에 종료
Burstable requests < limits 중간
BestEffort requests/limits 미설정 가장 먼저 종료

Liveness / Readiness / Startup Probe

Probe는 컨테이너의 상태를 주기적으로 확인하여 장애 상황에 자동 대응합니다.

Probe 종류 비교

Probe 목적 실패 시 동작
Liveness 컨테이너가 살아있는지 확인 컨테이너 재시작
Readiness 트래픽 수신 준비가 되었는지 확인 Service 엔드포인트에서 제외
Startup 애플리케이션 초기 시작 완료 확인 시작 실패 시 컨테이너 재시작

Spring Boot Actuator 연동

# application.yml
management:
  endpoints:
    web:
      exposure:
        include: health,info,prometheus
  endpoint:
    health:
      probes:
        enabled: true
      group:
        liveness:
          include: livenessState
        readiness:
          include: readinessState, db, redis
  health:
    livenessState:
      enabled: true
    readinessState:
      enabled: true
# Kubernetes Probe 설정
startupProbe:
  httpGet:
    path: /actuator/health/liveness
    port: 8080
  failureThreshold: 30
  periodSeconds: 10       # 최대 300초(5분) 대기
livenessProbe:
  httpGet:
    path: /actuator/health/liveness
    port: 8080
  periodSeconds: 10
  failureThreshold: 3
readinessProbe:
  httpGet:
    path: /actuator/health/readiness
    port: 8080
  periodSeconds: 5
  failureThreshold: 3

PDB - Pod Disruption Budget

PDB는 노드 드레인이나 클러스터 업그레이드 시 최소한의 Pod 가용성을 보장합니다.

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: spring-app-pdb
spec:
  minAvailable: 2          # 최소 2개 Pod 유지
  # 또는 maxUnavailable: 1 # 최대 1개까지만 중단 허용
  selector:
    matchLabels:
      app: spring-app

실전 종합 예제 - Spring Boot 앱 프로덕션 배포

지금까지 배운 모든 개념을 적용한 Helm values 파일입니다.

# values-prod.yaml
replicaCount: 3

image:
  repository: myregistry/spring-boot-app
  tag: "2.0.0"

service:
  type: ClusterIP
  port: 80
  targetPort: 8080

ingress:
  enabled: true
  className: nginx
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "60"
  hosts:
    - host: api.myservice.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: api-tls
      hosts:
        - api.myservice.com

resources:
  requests:
    memory: "512Mi"
    cpu: "500m"
  limits:
    memory: "1Gi"
    cpu: "1000m"

autoscaling:
  enabled: true
  minReplicas: 3
  maxReplicas: 20
  targetCPUUtilization: 70
  targetMemoryUtilization: 80
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 30
    scaleDown:
      stabilizationWindowSeconds: 300

pdb:
  enabled: true
  minAvailable: 2

spring:
  profiles: production
  jvmOpts: "-Xms384m -Xmx384m -XX:+UseG1GC"

probes:
  startup:
    enabled: true
    failureThreshold: 30
    periodSeconds: 10
  liveness:
    enabled: true
    periodSeconds: 10
    failureThreshold: 3
  readiness:
    enabled: true
    periodSeconds: 5
    failureThreshold: 3
# 프로덕션 배포
helm upgrade --install my-app ./my-spring-app \
  -f values-prod.yaml \
  -n production \
  --create-namespace \
  --wait \
  --timeout 5m

마치며

이번 글에서는 Kubernetes 실전 운영에 필요한 고급 도구와 개념을 다뤘습니다.

핵심 정리:

  • Helm으로 K8s 매니페스트를 패키징하고 환경별 설정을 분리합니다
  • Ingress로 도메인 기반 HTTP 라우팅과 TLS 종료를 처리합니다
  • HPA/VPA로 트래픽 변화에 자동 대응하는 오토스케일링을 구성합니다
  • Probe 설정으로 애플리케이션 장애를 자동 감지하고 복구합니다
  • Resource 관리PDB로 안정적인 서비스 가용성을 보장합니다

Kubernetes는 학습 곡선이 가파르지만, 이러한 도구들을 하나씩 적용하다 보면 자연스럽게 안정적인 프로덕션 환경을 구축할 수 있습니다. Spring Boot Actuator와의 연동은 특히 백엔드 개발자에게 실무적으로 가장 중요한 부분이니 꼭 직접 구성해보시기 바랍니다.