TroubleShooting

[AWS] 프리티어 노드 용량 부족 이슈

2-30 2026. 5. 18. 21:40

발단

Secret 적용을 위해 deployment.yamlenvFrom 블록을 추가하고 ArgoCD sync를 진행했다. 롤링 업데이트가 시작되면서 기존 Pod와 새 Pod가 동시에 올라오려다 일부 Pod가 Pending 상태에서 멈췄다.

dev-shoong-delivery-6f94795565-cr5cl      0/1     Pending   0          92m
dev-shoong-kitchen-b9bdd8964-bc727        0/1     Pending   0          92m
dev-shoong-notification-6c78bffc75        0/1     Pending   0          92m
dev-shoong-order-7875ccc9db-x9lts         0/1     Pending   0          4m42s

원인 파악

Pending 상태는 스케줄러가 Pod를 배치할 노드를 찾지 못했다는 신호다. kubectl describe pod로 확인하니 다음 메시지가 나왔다.

Warning  FailedScheduling  default-scheduler
0/2 nodes are available: 2 Too many pods.
no new claims to deallocate, preemption: 0/2 nodes are available:
2 No preemption victims found for incoming pod.

스케줄러가 거부한 직접 원인은 노드의 pod 수 상한 초과(Too many pods)였다. 롤링 업데이트 중 기존 Pod와 새 Pod가 동시에 올라오면서 노드당 최대 pod 수(ENI 제한)를 넘겼기 때문이다. kubectl describe nodes로 실제 리소스 사용량도 확인했다.

노드 1

cpu     650m  (33%)   1500m  (77%)
memory  524Mi (36%)   1876Mi (130%) <- limits 초과

노드 2

cpu     450m  (23%)   1500m  (77%)
memory  384Mi (26%)   1536Mi (107%) <- limits 초과

여기에 더해 메모리 limits도 130%, 107%로 초과 상태였다. 서비스 5개가 각각 memory limit 512Mi로 설정되어 있었고, 롤링 업데이트 중 Pod가 2배로 늘어나면서 pod count 초과와 메모리 초과가 동시에 발생한 상황이었다.


1차 판단 및 결정

dev 환경에서 512Mi limit은 과도하게 설정된 값이었다. 단기적으로는 memory limit을 256Mi로 줄이는 방법을 고려했으나, 근본적으로 노드 스펙 자체가 작고 개수도 부족하다고 판단했다. 따라서 노드 타입을 t3.medium으로 변경하고 2개에서 3개로 늘리기로 결정했다.


2차 문제 발생 — Terraform 오류

노드 타입을 t3.medium으로 변경한 뒤 Terraform을 다시 실행했다. 그런데 30분 넘게 노드 그룹 생성을 시도하다가 결국 다음 오류와 함께 실패했다.

Error: waiting for EKS Node Group (shoong-dev-cluster:shoong-dev-node-group) create:
unexpected state 'CREATE_FAILED', wanted target 'ACTIVE'.
last error: AsgInstanceLaunchFailures: Could not launch On-Demand Instances.
InvalidParameterCombination - The specified instance type is not eligible for Free Tier.
Launching EC2 instance failed.

원인

AWS Free Tier 계정에서는 사용 가능한 인스턴스 타입이 제한된다. t3.medium은 Free Tier eligible 대상이 아니었기 때문에 인스턴스 생성 자체가 차단된 것이었다.


해결 방향 — 용량 최적화

t3.medium을 사용할 수 없는 상황에서, Free Tier에서 사용 가능한 c7i-flex.large 2개로 전환하기로 결정했다. 동시에 파드와 인프라 자체의 리소스 사용량을 줄이는 방향으로 함께 진행했다.

  • 2025년 7월 이후로 프리티어에서 사용 가능한 ec2 인스턴스 타입은 t3.micro, t3.small, t4g.micro, t4g.small, c7i-flex.large, m7i-flex.large 이다.
인스턴스 타입 vCPU RAM 아키텍처 비고
t3.micro 2 1 GiB x86_64 버스터블, 기존 Free Tier 대상
t3.small 2 2 GiB x86_64 버스터블
t4g.micro 2 1 GiB ARM64 (Graviton2) 버스터블
t4g.small 2 2 GiB ARM64 (Graviton2) 버스터블
c7i-flex.large 2 4 GiB x86_64 (Intel Sapphire Rapids) 컴퓨팅 최적화, flex = vCPU 유연 할당
m7i-flex.large 2 8 GiB x86_64 (Intel Sapphire Rapids) 범용, flex = vCPU 유연 할당

인스턴스 구성

c7i-flex.large(vCPU 2, RAM 4GB) 2개로 구성했다.

instance_types = ["c7i-flex.large"]
desired_size   = 2
min_size       = 2
max_size       = 4

리소스 requests/limits 조정

기존 앱 파드의 memory limit 512Mi를 128Mi로 낮췄다. 롤링 업데이트 중 파드가 일시적으로 2배로 늘어나는 상황을 고려한 값이다.

resources:
  requests:
    cpu: "100m"
    memory: "64Mi"
  limits:
    cpu: "300m"
    memory: "128Mi"

Istio 리소스 축소

istiod 기본값과 dev 환경 축소값 비교다. 기본값은 Istio 공식 Helm 차트 기준이며, 설치 방식이나 버전에 따라 다를 수 있어 실제 환경에서 확인이 필요하다.

v1.29.2 기준

기본값 (default) 조정값 (dev)
cpu request 500m 100m
memory request 2048Mi 256Mi
cpu limit 없음 (Unlimited) 500m
memory limit 없음 (Unlimited) 512Mi
# 적용값
resources:
  requests:
    cpu: "100m"
    memory: "256Mi"
  limits:
    cpu: "500m"
    memory: "512Mi"

Docker 이미지 최적화

ECR에 올라간 이미지가 121MB였다. 분석 결과 두 가지를 개선 할 수 있었다.

개선점

1. prisma CLI가 dependencies에 있음

package.json을 보면 prisma CLI (~25MB)가 dependencies에 있어서 npm ci --omit=dev로 production 설치를 해도 포함된다. prisma generate용이라 빌드 타임에만 필요한데 런타임 이미지에 들어가고 있었다.

2. 런타임 스테이지에서 npm ci + npx prisma generate 재실행

빌더에서 이미 완료한 작업을 런타임에서 다시 실행하고 있어 불필요한 레이어가 추가되고 있었다.

개선된 Dockerfile

Prisma를 사용하는 4개 서비스 공통으로 아래 패턴을 적용했다.

FROM node:20-alpine AS builder
WORKDIR /app

RUN apk add --no-cache openssl

COPY package*.json ./
COPY prisma ./prisma
COPY tsconfig.json ./
COPY src ./src

RUN npm ci
RUN npx prisma generate
RUN npm run build
RUN npm prune --omit=dev        # dev deps 제거 (prisma CLI 포함)

FROM node:20-alpine
WORKDIR /app

RUN apk add --no-cache openssl

COPY --from=builder /app/node_modules ./node_modules   # 빌더에서 복사
COPY --from=builder /app/dist ./dist

EXPOSE 3001
USER node
CMD ["node", "dist/index.js"]

핵심 변경 사항은 세 가지다. 빌더에서 npm prune --omit=dev를 실행해 dev deps와 prisma CLI를 제거한다. 런타임은 node_modules를 빌더에서 복사만 하고 npm ci를 재실행하지 않는다. 런타임에서 package.jsonprisma/ 폴더 복사도 불필요하므로 제거했다.

package.json — prisma devDependencies로 이동

"dependencies": {
  "@prisma/client": "^5.22.0",
  "axios": "..."
},
"devDependencies": {
  "prisma": "5.22.0"
}

.dockerignore 각 서비스에 추가

node_modules
dist
.env
.git
*.md

변경 요약

Dockerfile (5개 서비스)

  • 빌더 스테이지 끝에 npm prune --omit=dev 추가 → dev deps + prisma CLI 제거
  • 런타임 스테이지: npm ci + npx prisma generate 제거, 빌더에서 node_modules와 dist만 복사

package.json (4개 Prisma 서비스)

  • prisma CLI를 dependencies → devDependencies로 이동 (~25MB × 4 = ~100MB 절감)
  • @prisma/client는 런타임에 필요하므로 dependencies에 유지

.dockerignore (5개 서비스 신규 생성)

  • node_modules, dist, .env, .git, *.md 제외

실제 절감 결과

ECR에 실제로 올린 결과, order / kitchen / notification / delivery 4개 서비스 기준으로 이미지 크기가 줄었다.

서비스 기존 최적화 후 절감 절감률
order 121.51MB 88.87MB 32.64MB 26.87%
kitchen 121.33MB 88.75MB 32.58MB 26.85%
notification 121.33MB 88.75MB 32.58MB 26.85%
delivery 121.33MB 88.75MB 32.58MB 26.85%
batch 50.16MB 49.22MB 0.94MB 1.87%
  • batch는 Prisma를 사용하지 않아 prisma CLI 제거 효과가 없었고, Dockerfile 레이어 정리 효과만 반영됐다.

현재는 이미지 최적화와 istiod 리소스 조정을 먼저 적용했다. ArgoCD, Prometheus, Grafana 등 나머지 컴포넌트는 kubectl top pods로 실제 사용량을 확인하면서 필요할때마다 순차적으로 조정할 예정이다.