근묵자흑
Kubernetes Pattern: 예측 가능한 요구사항(Predictable Demands) 본문
k8s/kubernetes-pattern
Kubernetes Pattern: 예측 가능한 요구사항(Predictable Demands)
Luuuuu 2025. 8. 2. 19:43Kubernetes에서 애플리케이션을 안정적으로 운영하려면 무엇이 필요할까요? 바로 예측 가능성입니다. Predictable Demands 패턴은 컨테이너화된 애플리케이션이 필요로 하는 리소스와 런타임 의존성을 명확히 선언하여, Kubernetes가 효율적으로 워크로드를 스케줄링하고 관리할 수 있도록 하는 기본 패턴입니다.
이 글에서는 실제 테스트 코드와 함께 Predictable Demands 패턴의 핵심 개념들을 살펴보겠습니다.
📚 핵심 개념 이해하기
1. 왜 Predictable Demands가 중요한가?
Kubernetes는 수많은 애플리케이션이 공존하는 멀티테넌트 환경입니다. 각 애플리케이션이 필요한 리소스를 명확히 선언하지 않으면:
- 🚨 리소스 경쟁: 애플리케이션 간 예측 불가능한 리소스 경쟁 발생
- 📉 성능 저하: 리소스 부족으로 인한 애플리케이션 성능 저하
- 💥 시스템 불안정: OOM(Out of Memory) 킬, CPU 스로틀링 등의 문제
- 🔄 비효율적 스케줄링: Kubernetes가 최적의 노드에 Pod을 배치하지 못함
2. 리소스 유형 이해하기
Kubernetes는 리소스를 두 가지로 분류합니다:
# 압축 가능한 리소스 (Compressible Resources)
# - CPU, 네트워크 대역폭
# - 부족해도 throttling으로 대응 가능
resources:
limits:
cpu: "1000m" # 1 vCPU
# 압축 불가능한 리소스 (Incompressible Resources)
# - 메모리, 스토리지
# - 부족하면 프로세스 종료 (OOMKilled)
resources:
limits:
memory: "1Gi" # 1 GiB
🧪 Predictable Demands 테스트 해보기
테스트 코드를 통해 각 개념을 검증해보겠습니다. 전체 테스트 코드는 GitHub 저장소에서 확인할 수 있습니다.
테스트 1: 런타임 의존성 (Runtime Dependencies)
1.1 PVC 의존성 테스트
# test-dependencies.sh 중 일부
test_pvc_dependency() {
# PVC 없이 Pod 생성 시도
kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: test-missing-pvc
spec:
containers:
- name: app
image: busybox
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: non-existent-pvc # 존재하지 않는 PVC
EOF
# Pod이 Pending 상태에 머물러 있는지 확인
pod_status=$(kubectl get pod test-missing-pvc -o jsonpath='{.status.phase}')
if [ "$pod_status" = "Pending" ]; then
# PVC 누락으로 인한 이벤트 확인
kubectl get events --field-selector involvedObject.name=test-missing-pvc
return 0
fi
}
💡 핵심 포인트:
- Pod은 필요한 PVC가 없으면 Pending 상태에서 대기합니다
- 이를 통해 데이터 의존성을 명확히 선언하고 검증할 수 있습니다
1.2 ConfigMap 실시간 업데이트 테스트
# ConfigMap을 볼륨으로 마운트
spec:
containers:
- name: app
volumeMounts:
- name: config
mountPath: /etc/config
volumes:
- name: config
configMap:
name: app-config
테스트 결과:
- ConfigMap 업데이트 후 약 60-90초 내에 Pod에 반영됨
- 환경변수로 주입된 경우는 Pod 재시작 필요
- 볼륨 마운트 방식은 자동 업데이트 지원
1.3 네트워크 의존성과 Service Discovery
# network-dependencies.yaml 예제
apiVersion: v1
kind: Service
metadata:
name: backend-service
spec:
selector:
app: backend
ports:
- port: 8080
targetPort: 8080
---
apiVersion: v1
kind: Pod
metadata:
name: frontend-pod
spec:
containers:
- name: frontend
image: nicolaka/netshoot
command: ["sh", "-c"]
args:
- |
# DNS를 통한 서비스 발견
nslookup backend-service
# 환경변수를 통한 서비스 발견
env | grep BACKEND_SERVICE
# 서비스 접근 테스트
curl -s http://backend-service:8080
Service Discovery 메커니즘:
- DNS 기반:
service-name.namespace.svc.cluster.local
- 환경변수:
SERVICE_NAME_SERVICE_HOST
,SERVICE_NAME_SERVICE_PORT
- Headless Service: StatefulSet을 위한 개별 Pod DNS
테스트 2: QoS 클래스 (Quality of Service)
2.1 QoS 클래스 결정 규칙
# Guaranteed QoS - 최고 우선순위
apiVersion: v1
kind: Pod
metadata:
name: guaranteed-pod
spec:
containers:
- name: app
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "128Mi" # requests와 동일
cpu: "100m" # requests와 동일
# Burstable QoS - 중간 우선순위
apiVersion: v1
kind: Pod
metadata:
name: burstable-pod
spec:
containers:
- name: app
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "128Mi" # requests보다 큼
cpu: "200m"
# BestEffort QoS - 최저 우선순위
apiVersion: v1
kind: Pod
metadata:
name: besteffort-pod
spec:
containers:
- name: app
image: nginx
# resources 섹션 없음
테스트 검증 코드:
# QoS 클래스 확인
kubectl get pod guaranteed-pod -o jsonpath='{.status.qosClass}'
# 출력: Guaranteed
kubectl get pod burstable-pod -o jsonpath='{.status.qosClass}'
# 출력: Burstable
kubectl get pod besteffort-pod -o jsonpath='{.status.qosClass}'
# 출력: BestEffort
2.2 리소스 제한 동작 테스트
CPU Throttling 테스트:
spec:
containers:
- name: cpu-stress
image: polinux/stress
command: ["stress"]
args: ["--cpu", "2", "--timeout", "30s"]
resources:
requests:
cpu: "50m"
limits:
cpu: "100m" # 0.1 CPU로 제한
실행 결과:
- CPU 사용이 100m(0.1 vCPU)로 제한됨
- 초과 사용 시 throttling 발생
- 애플리케이션은 계속 실행되지만 속도가 느려짐
Memory OOM 테스트:
spec:
containers:
- name: memory-stress
image: polinux/stress
command: ["stress"]
args: ["--vm", "1", "--vm-bytes", "150M"]
resources:
limits:
memory: "128Mi" # 128MiB로 제한
실행 결과:
- 메모리 사용이 128MiB를 초과하면 OOMKilled
- Pod은 재시작 정책에 따라 재시작됨
테스트 3: Pod 우선순위와 선점(Preemption)
3.1 우선순위 클래스 정의
# 높은 우선순위 클래스
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority
value: 1000
description: "높은 우선순위 워크로드"
# 낮은 우선순위 클래스
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: low-priority
value: 100
description: "낮은 우선순위 워크로드"
3.2 선점 시나리오 테스트
# 1단계: 낮은 우선순위 Pod으로 클러스터 채우기
for i in {1..8}; do
kubectl run low-priority-$i --image=nginx \
--overrides='{"spec":{"priorityClassName":"low-priority"}}' \
--requests='cpu=50m,memory=64Mi'
done
# 2단계: 높은 우선순위 Pod 추가 (선점 발생)
kubectl run high-priority --image=nginx \
--overrides='{"spec":{"priorityClassName":"high-priority"}}' \
--requests='cpu=300m,memory=256Mi'
# 3단계: 선점 이벤트 확인
kubectl get events --sort-by='.lastTimestamp' | grep Preempted
실행 결과:
- 높은 우선순위 Pod을 위해 낮은 우선순위 Pod들이 evict됨
- Kubernetes는 최소한의 Pod만 제거하여 공간 확보
테스트 4: 네임스페이스 리소스 관리
4.1 ResourceQuota 설정
apiVersion: v1
kind: ResourceQuota
metadata:
name: namespace-quota
namespace: development
spec:
hard:
requests.cpu: "4"
requests.memory: "4Gi"
limits.cpu: "8"
limits.memory: "8Gi"
pods: "20"
persistentvolumeclaims: "10"
테스트 검증:
# 할당량 상태 확인
kubectl describe resourcequota namespace-quota -n development
# 출력 예시:
# Name: namespace-quota
# Resource Used Hard
# -------- ---- ----
# limits.cpu 2 8
# limits.memory 2Gi 8Gi
# persistentvolumeclaims 2 10
# pods 5 20
# requests.cpu 1 4
# requests.memory 1Gi 4Gi
4.2 LimitRange 자동 적용
apiVersion: v1
kind: LimitRange
metadata:
name: default-limits
namespace: development
spec:
limits:
- type: Container
default:
cpu: "500m"
memory: "512Mi"
defaultRequest:
cpu: "100m"
memory: "128Mi"
max:
cpu: "2"
memory: "2Gi"
테스트 결과:
- 리소스를 명시하지 않은 Pod에 자동으로 기본값 적용
- 최대값을 초과하는 요청은 거부됨
📊 테스트 요약
우리의 테스트 스위트 실행 결과:
✅ Runtime Dependencies Tests
- PVC 의존성 검증
- ConfigMap 실시간 업데이트
- Secret 마운트 권한
- Service Discovery
- EmptyDir 볼륨 공유
✅ Resource Limits and QoS Tests
- QoS 클래스 자동 할당
- CPU throttling 동작
- Memory OOM 처리
- 리소스 사용량 모니터링
- Multi-container Pod QoS
- 리소스 경쟁 시뮬레이션
✅ Pod Priority and Preemption Tests
- PriorityClass 생성 및 적용
- 우선순위 기반 스케줄링
- 선점(Preemption) 시나리오
- PodDisruptionBudget 연동
- 우선순위 기반 리소스 할당
- 시스템 Pod 보호
✅ ResourceQuota and LimitRange Tests
- 기본 ResourceQuota 동작
- 할당량 초과 시 거부
- LimitRange 자동 적용
- 제한 범위 위반 검증
- 객체 수 제한
- 스토리지 할당량
- 범위별(Scoped) ResourceQuota
- 복합 제한 테스트
🚀 실습 환경 구성
Minikube 환경 설정
# Minikube 시작 (충분한 리소스 할당)
minikube start --cpus=2 --memory=4096 --driver=docker
# 애드온 활성화
minikube addons enable metrics-server
minikube addons enable dashboard
# 테스트 실행
cd foundational/predictable-demands/05-testing
chmod +x *.sh
./run-all-tests.sh
주요 확인 명령어
# Pod 리소스 사용량 확인
kubectl top pods
# QoS 클래스 확인
kubectl get pods -o custom-columns=NAME:.metadata.name,QOS:.status.qosClass
# ResourceQuota 사용량 확인
kubectl describe resourcequota -A
# 이벤트 모니터링
kubectl get events --sort-by='.lastTimestamp' -w
'k8s > kubernetes-pattern' 카테고리의 다른 글
Kubernetes Pattern: Managed Lifecycle (1) | 2025.08.23 |
---|---|
Kubernetes Pattern: Health Probe Pattern(정상상태 점검 패턴) (2) | 2025.08.16 |
Kubernetes Pattern: 선언적 배포(Declarative Deployment) + FluxCD (3) | 2025.08.09 |