발단
Secret 적용을 위해 deployment.yaml에 envFrom 블록을 추가하고 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.json과 prisma/ 폴더 복사도 불필요하므로 제거했다.
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로 실제 사용량을 확인하면서 필요할때마다 순차적으로 조정할 예정이다.
'TroubleShooting' 카테고리의 다른 글
| ArgoCD Sync 해도 안 풀리는 OutOfSync 트러블슈팅 (0) | 2026.06.01 |
|---|---|
| EBS CSI 드라이버 미설치로 인한 PVC Pending 이슈 (1) | 2026.06.01 |
| [AWS] ESO(External Secrets Operator) 설정 (0) | 2026.05.18 |
| [AWS] EKS kubectl 접근 트러블슈팅 기록 (0) | 2026.05.18 |
| [AWS] Terraform으로 EKS Cluster SG 포트 범위 최소화하기 (0) | 2026.05.18 |