Files
joungmin 2d41f22b83 fix(infra): #316 backend resource request 재산정 + RollingUpdate 25%/25% 복귀
노드 다운사이징(2×1OCPU/6GB) 이후 backend CPU request 500m이 노드 한도
의 절반을 차지해 rollingUpdate 데드락 발생. 임시 패치(maxSurge=0/
maxUnavailable=1) 상태를 합리화하여 25%/25% 기본 정책으로 복귀.

변경:
- cpu 500m/1 → 300m/800m
- mem 768Mi/1536Mi → 512Mi/1024Mi
- strategy 25%/25% 명시 (기본값 복귀)

근거: 실측 idle CPU 0.7%, RSS ~305 MB. peak 30-40% 추정 안에서 안전.
검증: 적용 후 노드 잔여 330m → 다음 배포 시 두 Pod 공존 가능 (무중단).
다운타임: 이번 1회 ~25초 (구 500m Pod 점유 해제), 다음 배포부터 0초.

설계서: docs/design/316-backend-resource-rightsize/README.md (Approved).

Refs: #316
2026-06-15 12:07:47 +09:00

7.1 KiB
Raw Permalink Blame History

설계서: backend resource request 재산정 (#316)

상태: Approved 작성: [AI] Architect · 최종수정: 2026-06-15 추적성 — Redmine: #316 · 관련 ADR: 없음 · 부모 이슈: #267 (현행화/배포 컨텍스트) · 구현 파일: k8s/backend-deployment.yaml · 테스트: kubectl rollout 무중단 (수동 검증) · 자동 테스트 없음

1. 목적 (Why)

노드 다운사이징(2 × 2 OCPU/8 GB → 2 × 1 OCPU/6 GB) 이후 backend Deployment의 CPU request 500m이 노드 가용 자원의 절반을 차지하여, RollingUpdate 시 신/구 Pod 공존이 불가능. 임시로 maxSurge=0, maxUnavailable=1 패치(매 배포 ~30초 다운타임) 상태를 합리화하여 25%/25% 정책으로 복귀하고 무중단 배포를 회복한다.

2. 범위 (Scope)

  • 포함
    • k8s/backend-deployment.yamlresources.requests/limits 재산정.
    • 같은 파일의 spec.strategy 또는 라이브 deploy의 strategy를 25%/25%로 복귀(이미 패치되어 있다면 디폴트로 환원).
  • 제외 (out of scope)
    • frontend/redis 등 다른 Deployment.
    • JVM heap/-XX 옵션(별도 튜닝 이슈).
    • HPA, VPA 도입.
    • 노드 수/형상 추가 변경.

3. 인수조건 (Acceptance Criteria)

  • backend CPU request ≤ 300m, memory request ≤ 512Mi (실측 ~305 MB 사용 기준 여유 포함).
  • limit은 노드 한도(1 OCPU / 6 GB) 안에서 cpu ≤ 800m, mem ≤ 1Gi.
  • Deployment strategy: maxSurge: 25%, maxUnavailable: 25%(또는 default).
  • kubectl apply 직후 새 Pod이 Pending 없이 Running 진입.
  • 적용 동안 https://www.tasteby.net/api/health 가 끊김 없이 200 응답.
  • 변경 사항을 git 커밋·push.

4. 컨텍스트 & 제약

  • 의존성: OKE 1.34.2, ARM64 노드 1 OCPU/6 GB × 2.
  • 노드 가용 CPU(allocatable, 시스템 데몬 차감 후): 약 940-960m per node.
  • 노드 가용 메모리(allocatable): 약 5.0-5.3 GiB per node.
  • 같은 노드에 frontend(200m/256Mi), kube-system DaemonSet들(약 200m/300Mi 합계), 가끔 redis/cert-manager Pod.
  • 두 backend Pod이 한 노드에 공존하지 않아도 됨(replicas=1이지만 RollingUpdate 동안 일시적으로 2개).
  • 제약: 비용 X(코드 변경 없음), 운영 영향(작지만 rollout 한 번 발생).
  • 가정: 운영 실측 idle CPU 0.7%, peak 추정 30-40% (영상 추출·벡터 검색 시).

5. 아키텍처 개요

git: k8s/backend-deployment.yaml
   │
   ▼ (수정)
spec.replicas: 1                              (변경 없음)
spec.strategy:
   type: RollingUpdate
   rollingUpdate:
     maxSurge: 25%                            (=0 → 25%, 임시 패치 복귀)
     maxUnavailable: 25%                      (=1 → 25%, 임시 패치 복귀)
spec.template.spec.containers[0].resources:
   requests: { cpu: 300m, memory: 512Mi }    (500m/768Mi → 다운)
   limits:   { cpu: 800m, memory: 1024Mi }   (1/1536 → 다운)
   │
   ▼ (kubectl apply)
OKE rolling update → 새 Pod 1개 surge → Ready → 구 Pod 종료
   │
   ▼
www.tasteby.net 트래픽 무중단 (Ingress → Service → Pod, 100ms 응답 유지)

I/O 경계: 매니페스트(선언)와 클러스터 상태(런타임)는 kubectl apply로 단방향 동기화. 검증은 rollout status + 외부 curl.

6. 데이터 모델

필드 변경 전 변경 후 근거
resources.requests.cpu 500m 300m idle 0.7%, peak 추정 30%, 1 OCPU 노드에서 frontend(200m) + 잔여 시스템(~200m) 후 여유
resources.requests.memory 768Mi 512Mi 실측 ~305 MB, JVM heap·코드캐시·메타스페이스 여유
resources.limits.cpu 1 800m 1 OCPU 노드에서 throttle 방지 + 다른 Pod 여유
resources.limits.memory 1536Mi 1024Mi OOM 위험 줄이고 노드당 5 GiB allocatable에서 안정
strategy.rollingUpdate.maxSurge 0 (임시) 25% 무중단 RollingUpdate 복귀
strategy.rollingUpdate.maxUnavailable 1 (임시) 25% 동일

7. 함수 명세

매니페스트 변경이라 코드 함수 없음. 변경 단위 표:

변경 단위 위치 책임 검증
requests.cpu 다운 k8s/backend-deployment.yaml:37 새 Pod 스케줄링 가능 kubectl describe pod Events에 FailedScheduling 없음
requests.memory 다운 k8s/backend-deployment.yaml:38 같음 + OOM 안전 Pod RSS / requests = 70% 이하
limits.cpu 다운 k8s/backend-deployment.yaml:40 throttle 제어 cpu.stat throttled_usec 안 늘어남
limits.memory 다운 k8s/backend-deployment.yaml:41 OOM 보호 OOMKilled 없음
strategy 추가 k8s/backend-deployment.yaml spec 25%/25% 명시 live patch와 GitOps 일치

모두 단순 선언적 변경. 복잡 함수 별도 fn-*.md 불필요.

8. 흐름 / 알고리즘

  1. k8s/backend-deployment.yaml 편집(resources + strategy).
  2. kubectl apply -f k8s/backend-deployment.yaml.
  3. 신 Pod 1개 surge → Ready 대기 (~30-60초, JVM startup).
  4. Ready 되면 구 Pod 종료(graceful, terminationGracePeriodSeconds 기본 30초).
  5. kubectl rollout status deploy/backend -n tasteby PASS.
  6. 외부 curl https://www.tasteby.net/api/health 연속 200 확인.

9. 엣지케이스 & 에러 처리

  • JVM startup이 readinessProbe initialDelaySeconds(30s)보다 길면: 새 Pod이 Ready 못 받음 → 구 Pod 유지 → rollout 진행 안 됨. 현재 backend는 보통 20-25초에 Ready.
  • 노드 가용 메모리 부족: 두 backend Pod이 한 노드에 갈 경우 ~1 GiB 차지. frontend + DaemonSet 합치면 압박 가능. scheduler가 적절히 분산 기대 (replicas=1 surge 시).
  • OCIR 풀 실패: imagePullBackOff 시 rollout 중단. 이미지 이미 풀돼 있으므로 영향 적음.
  • rollback: 문제 시 kubectl rollout undo deploy/backend 또는 git revert + apply.

10. 테스트 계획

  • 수동: 적용 직후 새 Pod Running 확인 + 외부 health 200 연속(약 2분간 5초 간격 polling).
  • 부하 측정 후속: 운영 부하 24시간 관찰 → CPU throttle/OOM 없음 확인 (별도 follow-up).
  • 자동 테스트: 해당 없음 (인프라 매니페스트).

11. 리스크 & 대안 검토

  • 선택: cpu 300m / mem 512Mi.
  • 대안 A: cpu 250m / mem 384Mi — 더 여유롭지만 peak 시 throttle 가능성.
  • 대안 B: cpu 400m / mem 640Mi — 안전 마진 크지만 25%/25% 복귀해도 두 Pod 공존 불가 가능(노드 잔여 CPU 부족).
  • 대안 C: replicas=2 + topologySpreadConstraints — 가용성↑이지만 비용·리소스↑, 현재 노드 한도에서 부적합.
  • 트레이드오프: 선택안은 peak에서 약간 빠듯하나 limits 800m로 burst 허용. 운영 24시간 관찰 후 재조정.

12. 미해결 질문

  • peak 시(영상 추출 동시 다수) CPU 실제 사용량 — 현재 metrics-server 미설치라 정확 측정 불가. 추후 설치 후 재산정.
  • HPA 도입 여부 — 노드 1 OCPU에선 의미 적음. 노드 추가 후 검토.
  • replicas=2 가용성 강화 — 새 노드 형상에서 메모리 압박 우려, 별도 결정 필요.