EKS 고가용성 아키텍처 가이드
📌 기준 환경: EKS 1.33+, Karpenter v1.x, Istio 1.22+
1. 개요
레질리언시(Resiliency)는 시스템이 장애에 직면했을 때 정상 상태로 복구하거나, 장애 영향을 최소화하면서 서비스를 유지하는 능력입니다. 클라우드 네이티브 환경에서 레질리언시의 핵심 원칙은 단순합니다: 장애는 반드시 발생한다 — 설계로 대비한다.
단일 Pod 장애부터 리전 전체 장애까지, 각 계층에서 발생할 수 있는 Failure Domain을 이해하고 그에 맞는 방어 전략을 수립하는 것이 EKS 운영의 핵심입니다.
Failure Domain 계층 구조
레질리언시 성숙도 모델
조직의 레질리언시 수준을 4단계로 분류하고, 현재 위치에서 점진적으로 발전시켜 나갈 수 있습니다.
| Level | 단계 | 핵 심 역량 | 구현 항목 | 복잡성 | 비용 영향 |
|---|---|---|---|---|---|
| 1 | 기본 (Basic) | Pod 수준 복원력 | Probe 설정, PDB, Graceful Shutdown, 리소스 Limits | 낮음 | 최소 |
| 2 | Multi-AZ | AZ 장애 내성 | Topology Spread, Multi-AZ NodePool, ARC Zonal Shift | 중간 | Cross-AZ 트래픽 비용 |
| 3 | Cell-Based | Blast Radius 격리 | Cell Architecture, Shuffle Sharding, 독립 배포 | 높음 | Cell 별 오버헤드 |
| 4 | Multi-Region | 리전 장애 내성 | Active-Active 아키텍처, Global Accelerator, 데이터 복제 | 매우 높음 | 리전 별 인프라 비용 |
운영 중 장애 진단 및 해결은 EKS 장애 진단 및 대응 가이드를 참조하세요. 본 문서는 장애 예방과 설계에 초점을 맞추고 있으며, 실시간 트러블슈팅은 장애 진단 및 대응 가이드에서 다룹니다.
2. Multi-AZ 전략
Multi-AZ 배포는 EKS 레질리언시의 가장 기본적이면서도 강력한 전략입니다. 단일 AZ 장애가 서비스 전체를 중단시키지 않도록 워크로드를 여러 가용 영역에 분산합니다.
Pod Topology Spread Constraints
Topology Spread Constraints는 Pod를 AZ, 노드, 커스텀 토폴로지 도메인에 걸쳐 균등하게 분산시킵니다. minDomains 파라미터(K8s 1.24 alpha → 1.30 GA)를 통해 최소 분산 도메인 수를 지정할 수 있습니다.
| 파라미터 | 설명 | 권장값 |
|---|---|---|
maxSkew | 도메인 간 Pod 수 최대 차이 | AZ: 1, 노드: 2 |
topologyKey | 분산 기준 레이블 | topology.kubernetes.io/zone |
whenUnsatisfiable | 조건 불충족 시 동작 | DoNotSchedule (hard) 또는 ScheduleAnyway (soft) |
minDomains | 최소 분산 도메인 수 | AZ 수와 동일 (예: 3) |
labelSelector | 대상 Pod 선택 | Deployment의 matchLabels와 동일 |
Hard + Soft 조합 전략 (권장):
apiVersion: apps/v1
kind: Deployment
metadata:
name: critical-app
spec:
replicas: 6
selector:
matchLabels:
app: critical-app
template:
metadata:
labels:
app: critical-app
spec:
topologySpreadConstraints:
# Hard: AZ 간 균등 분산 (반드시 보장)
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: critical-app
minDomains: 3
# Soft: 노드 간 분산 (가능한 한 보장)
- maxSkew: 2
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: critical-app
maxSkew: 1은 가장 엄격한 균등 분산을 보장합니다. 6개 replica를 3 AZ에 배포하면 각 AZ에 정확히 2개씩 배치됩니다. 스케일링 속도가 중요한 경우 maxSkew: 2로 느슨하게 설정하여 스케줄링 유연성을 확보할 수 있습니다.
AZ-aware Karpenter 설정
Karpenter v1 GA에서는 NodePool 단위로 Multi-AZ 분산, Disruption budget, Spot + On-Demand 혼합 전략을 선언적으로 구성합니다.
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: multi-az-pool
spec:
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 5m
# Disruption budget: 동시에 20% 이상의 노드가 중단되지 않도록 제한
budgets:
- nodes: "20%"
# 업무 시간에는 더 보수적으로 운영 (선택 사항)
# - nodes: "10%"
# schedule: "0 9 * * MON-FRI" # 평일 09:00-17:00
# duration: 8h
template:
spec:
requirements:
# 3개 AZ에 걸쳐 노드 프로비저닝
- key: topology.kubernetes.io/zone
operator: In
values: ["us-east-1a", "us-east-1b", "us-east-1c"]
# Spot + On-Demand 혼합으로 비용 최적화 + 안정성 확보
- key: karpenter.sh/capacity-type
operator: In
values: ["on-demand", "spot"]
- key: node.kubernetes.io/instance-type
operator: In
values:
- c6i.xlarge
- c6i.2xlarge
- c6i.4xlarge
- c7i.xlarge
- c7i.2xlarge
- c7i.4xlarge
- m6i.xlarge
- m6i.2xlarge
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: multi-az
limits:
cpu: "1000"
memory: 2000Gi
Spot 인스턴스는 AZ별로 가용 풀이 다릅니다. 15개 이상의 다양한 인스턴스 유형을 지정하면 Spot 용량 부족으로 인한 프로비저닝 실패를 최소화할 수 있습니다. 미션 크리티컬 워크로드의 base capacity는 반드시 On-Demand로 운영하세요.
Node Readiness 기반 안전한 워크로드 배치
Multi-AZ 환경에서 새 노드가 프로비저닝될 때, 노드가 Ready 상태가 되더라도 실제로 워크로드를 수용할 준비가 완료되지 않았을 수 있습니다. 이를 방지하기 위한 Kubernetes readiness 메커니즘들을 활용합니다.
Node Readiness Controller (2026년 2월 발표)
Node Readiness Controller는 노드 부트스트랩 과정에서 커스텀 taint를 선언적으로 관리하여, GPU 드라이버, CNI 플러그인, CSI 드라이버, 보안 에이전트 등 모든 인프라 요구사항이 충족될 때까지 워크로드 스케줄링을 지연시킵니다.
레질리언시 관점의 이점:
- AZ 장애 복구 시: Karpenter가 새 AZ에 노드 를 프로비저닝할 때, 노드가 완전히 준비된 후에만 트래픽을 수용
- Scale-out 이벤트: 급격한 확장 시에도 미완성 노드에 워크로드가 배치되지 않음
- GPU/ML 워크로드: 드라이버 로딩 완료 전 스케줄링을 방지하여
CrashLoopBackOff방지
Pod Scheduling Readiness (K8s 1.30 GA)
schedulingGates를 사용하면 Pod 측에서 스케줄링 타이밍을 제어할 수 있습니다. 외부 시스템이 준비 상태를 확인한 후 gate를 제거하여 스케줄링을 허용합니다:
apiVersion: v1
kind: Pod
metadata:
name: validated-pod
spec:
schedulingGates:
- name: "example.com/capacity-validation"
- name: "example.com/security-clearance"
containers:
- name: app
image: app:latest
resources:
requests:
cpu: "4"
memory: "8Gi"
활용 사례:
- 리소스 쿼터 사전 검증 후 스케줄링 허용
- 보안 승인 완료 후 스케줄링 허용
- 커스텀 어드미션 체크 통과 후 스케줄링 허용
Pod Readiness Gates (AWS LB Controller)
AWS Load Balancer Controller의 Pod Readiness Gates는 롤링 업데이트 시 무중단 배포를 보장합니다:
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
elbv2.k8s.aws/pod-readiness-gate-inject: enabled # 자동 주입 활성화
새 Pod가 ALB/NLB 타겟으로 등록되고 헬스 체크를 통과할 때까지 이전 Pod가 종료되지 않으므로, 트래픽 유실 없는 배포가 가능합니다.
| 요구사항 | 추천 기능 | 적용 레벨 |
|---|---|---|
| 노드 부트스트랩 완료 보장 | Node Readiness Controller | Node |
| Pod 스케줄링 전 외부 검증 | Pod Scheduling Readiness | Pod |
| LB 등록 완료 후 트래픽 수신 | Pod Readiness Gates | Pod |
| GPU/특수 하드웨어 준비 보장 | Node Readiness Controller | Node |
| 무중단 롤링 배포 | Pod Readiness Gates | Pod |
AZ 회피 배포 전략 (ARC Zonal Shift)
AWS Application Recovery Controller(ARC) Zonal Shift는 특정 AZ에 문제가 감지되었을 때 해당 AZ로의 트래픽을 자동 또는 수동으로 전환하는 기능입니다. EKS는 2024년 11월부터 ARC Zonal Shift를 지원합니다.
ARC Zonal Shift 활성화 및 사용:
# EKS 클러스터에 Zonal Shift 활성화
aws eks update-cluster-config \
--name my-cluster \
--zonal-shift-config enabled=true
# 수동 Zonal Shift 시작 (특정 AZ에서 트래픽 우회)
aws arc-zonal-shift start-zonal-shift \
--resource-identifier arn:aws:eks:us-east-1:123456789012:cluster/my-cluster \
--away-from us-east-1b \
--expires-in 3h \
--comment "AZ-b impairment detected via Health Dashboard"
# Zonal Shift 상태 확인
aws arc-zonal-shift list-zonal-shifts \
--resource-identifier arn:aws:eks:us-east-1:123456789012:cluster/my-cluster
Zonal Shift의 최대 지속 시간은 3일이며, 필요 시 연장할 수 있습니다. Zonal Autoshift를 활성화하면 AWS가 AZ 수준의 장애를 감지하여 자동으로 트래픽을 전환합니다.
긴급 AZ Evacuation 스크립트:
#!/bin/bash
# az-evacuation.sh - 장애 AZ의 모든 워 크로드를 안전하게 대피
IMPAIRED_AZ=$1
if [ -z "$IMPAIRED_AZ" ]; then
echo "Usage: $0 <az-name>"
echo "Example: $0 us-east-1b"
exit 1
fi
echo "=== AZ Evacuation: ${IMPAIRED_AZ} ==="
# 1. 해당 AZ의 노드 Cordon (새 Pod 스케줄링 차단)
echo "[Step 1] Cordoning nodes in ${IMPAIRED_AZ}..."
kubectl get nodes -l topology.kubernetes.io/zone=${IMPAIRED_AZ} -o name | \
xargs -I {} kubectl cordon {}
# 2. 해당 AZ의 노드 Drain (기존 Pod 안전하게 이동)
echo "[Step 2] Draining nodes in ${IMPAIRED_AZ}..."
kubectl get nodes -l topology.kubernetes.io/zone=${IMPAIRED_AZ} -o name | \
xargs -I {} kubectl drain {} \
--ignore-daemonsets \
--delete-emptydir-data \
--grace-period=30 \
--timeout=120s
# 3. 대피 결과 확인
echo "[Step 3] Verifying evacuation..."
echo "Remaining pods in ${IMPAIRED_AZ}:"
kubectl get pods --all-namespaces -o wide | grep ${IMPAIRED_AZ} | grep -v DaemonSet
echo "=== Evacuation complete ==="
EBS AZ-Pinning 대응
EBS 볼륨은 특정 AZ에 고정(pinned)됩니다. 해당 AZ에 장애가 발생하면 볼륨을 사용하는 Pod가 다른 AZ로 이동할 수 없습니다.
WaitForFirstConsumer StorageClass (권장):
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: topology-aware-ebs
provisioner: ebs.csi.aws.com
parameters:
type: gp3
encrypted: "true"
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
WaitForFirstConsumer는 Pod가 스케줄링될 때까지 볼륨 생성을 지연시켜, Pod와 같은 AZ에 볼륨이 생성되도록 보장합니다.
EFS Cross-AZ 대안: AZ 장애 시에도 스토리지 접근이 필요한 워크로드에는 Amazon EFS를 사용합니다. EFS는 모든 AZ에서 동시 접근이 가능하므로 AZ-Pinning 문제가 없습니다.
| 스토리지 | AZ 종속성 | 장애 시 동작 | 적합한 워크로드 |
|---|---|---|---|
| EBS (gp3) | 단일 AZ 고정 | AZ 장애 시 접근 불가 | 데이터베이스, 상태 저장 앱 |
| EFS | Cross-AZ | AZ 장애에도 접근 가능 | 공유 파일, CMS, 로그 |
| Instance Store | 노드 종속 | 노드 종료 시 데이터 소실 | 임시 캐시, 스크래치 |
Cross-AZ 비용 최적화
Multi-AZ 배포의 주요 비용 요인은 Cross-AZ 네트워크 트래픽입니다. AWS에서 같은 리전 내 AZ 간 데이터 전송은 양방향 각 $0.01/GB가 부과됩니다.
Istio Locality-Aware 라우팅으로 Cross-AZ 트래픽을 최소화할 수 있습니다:
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: locality-aware-routing
spec:
host: backend-service
trafficPolicy:
connectionPool:
http:
http2MaxRequests: 1000
outlierDetection:
consecutive5xxErrors: 5
interval: 10s
baseEjectionTime: 30s
loadBalancer:
localityLbSetting:
enabled: true
# 같은 AZ 우선, 장애 시 다른 AZ로 failover
distribute:
- from: "us-east-1/us-east-1a/*"
to:
"us-east-1/us-east-1a/*": 80
"us-east-1/us-east-1b/*": 10
"us-east-1/us-east-1c/*": 10
- from: "us-east-1/us-east-1b/*"
to:
"us-east-1/us-east-1b/*": 80
"us-east-1/us-east-1a/*": 10
"us-east-1/us-east-1c/*": 10
Locality-Aware 라우팅을 적용하면 같은 AZ 내 트래픽을 80% 이상 유지하여 Cross-AZ 데이터 전송 비용을 크게 절감할 수 있습니다. 대용량 트래픽 서비스에서는 월 수천 달러의 비용 절감이 가능합니다.
3. Cell-Based Architecture
Cell-Based Architecture는 AWS Well-Architected Framework에서 권장하는 고급 레질리언시 패턴으로, 시스템을 독립적인 Cell로 분할하여 장애 영향 범위(Blast Radius)를 격리합니다.
Cell 개념과 설계 원칙
Cell은 독립적으로 동작할 수 있는 자기 완결적(self-contained) 서비스 단위입니다. 하나의 Cell이 장애를 겪어도 다른 Cell은 영향을 받지 않습니다.
Cell 설계 핵심 원칙:
- 독립성(Independence): 각 Cell은 자체 데이터 스토어, 캐시, 큐를 보유
- 격리(Isolation): Cell 간 직접 통신 없음 — Control Plane을 통해서만 조율
- 균일성(Homogeneity): 모든 Cell은 동일한 코드와 구성 을 실행
- 확장성(Scalability): 수요 증가 시 기존 Cell 확장이 아닌 새 Cell 추가
EKS에서의 Cell 구현
| 구현 방식 | Namespace 기반 Cell | Cluster 기반 Cell |
|---|---|---|
| 격리 수준 | 논리적 격리 (soft) | 물리적 격리 (hard) |
| 리소스 격리 | ResourceQuota, LimitRange | 완전한 클러스터 격리 |
| 네트워크 격리 | NetworkPolicy | VPC/Subnet 수준 |
| Blast Radius | 같은 클러스터 내 잠재적 영향 | Cell 간 완전한 격리 |
| 운영 복잡성 | 낮음 (단일 클러스터) | 높음 (멀티 클러스터) |
| 비용 | 낮음 | 높음 (Control Plane 비용 × Cell 수) |
| 적합한 환경 | 소~중규모, 내부 서비스 | 대규모, 규제 준수 필요 |
Namespace 기반 Cell 구현 예시:
# Cell-1 Namespace 및 ResourceQuota
apiVersion: v1
kind: Namespace
metadata:
name: cell-1
labels:
cell-id: "cell-1"
partition: "customers-a-h"
---
apiVersion: v1
kind: ResourceQuota
metadata:
name: cell-1-quota
namespace: cell-1
spec:
hard:
requests.cpu: "20"
requests.memory: 40Gi
limits.cpu: "40"
limits.memory: 80Gi
pods: "100"
---
# Cell-aware Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server
namespace: cell-1
labels:
cell-id: "cell-1"
spec:
replicas: 4
selector:
matchLabels:
app: api-server
cell-id: "cell-1"
template:
metadata:
labels:
app: api-server
cell-id: "cell-1"
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: api-server
cell-id: "cell-1"
containers:
- name: api-server
image: myapp/api-server:v2.1
env:
- name: CELL_ID
value: "cell-1"
- name: PARTITION_RANGE
value: "A-H"
resources:
requests:
cpu: "500m"
memory: 1Gi
limits:
cpu: "1"
memory: 2Gi
Cell Router 구현
Cell Router는 들어오는 요청을 적절한 Cell로 라우팅하는 핵심 컴포넌트입니다. 세 가지 구현 방식이 있습니다.
1. Route 53 ARC Routing Control 기반:
DNS 수준에서 Cell 라우팅을 제어합니다. 각 Cell에 대한 Health Check와 Routing Control을 설정하여, Cell 장애 시 DNS 레벨에서 트래픽을 차단합니다.
2. ALB Target Group 기반:
ALB의 Weighted Target Group을 활용하여 Cell별 트래픽을 분배합니다. 헤더 기반 라우팅 규칙으로 고객별 Cell 매핑을 구현합니다.
3. Service Mesh 기반 (Istio):
Istio VirtualService의 header-based 라우팅을 사용하여 Cell 라우팅을 구현합니다. 가장 유연하지만 Istio 운영 복잡성이 추가됩니다.
Blast Radius 격리 전략
| 전략 | 설명 | 격리 기준 | 사용 사례 |
|---|---|---|---|
| Customer Partitioning | 고객 ID 해시 기반 Cell 배정 | 고객 그룹 | SaaS 플랫폼 |
| Geographic | 지리적 위치 기반 Cell 배정 | 리전/국가 | 글로벌 서비스 |
| Capacity-Based | Cell 용량 기반 동적 배정 | 가용 리소스 | 트래픽 변동 큰 서비스 |
| Tier-Based | 고객 등급 기반 Cell 배정 | 서비스 레벨 | 프리미엄/스탠다드 분리 |
Shuffle Sharding 패턴
Shuffle Sharding은 각 고객(또는 테넌트)을 전체 Cell 풀에서 랜덤하게 선택한 소수의 Cell에 할당하는 패턴입니다. 이를 통해 하나의 Cell 장애가 소수의 고객에게만 영향을 미치도록 합니다.
원리: 8개의 Cell이 있고, 각 고객에게 2개의 Cell을 할당하면, 가능한 조합은 C(8,2) = 28개입니다. 특정 Cell 하나가 장애를 겪어도 해당 Cell을 사용하는 고객만 영향을 받으며, 나머지 Cell로 자동 failover됩니다.
# Shuffle Sharding ConfigMap 예시
apiVersion: v1
kind: ConfigMap
metadata:
name: shuffle-sharding-config
data:
sharding-config.yaml: |
totalCells: 8
shardsPerTenant: 2
tenantAssignments:
tenant-acme:
cells: ["cell-1", "cell-5"]
primary: "cell-1"
tenant-globex:
cells: ["cell-3", "cell-7"]
primary: "cell-3"
tenant-initech:
cells: ["cell-2", "cell-6"]
primary: "cell-2"
Cell Architecture는 강력한 격리를 제공하지만, 운영 복잡성과 비용이 증가합니다. 각 Cell이 독립적인 데이터 스토어를 가지므로 데이터 마이그레이션, Cross-Cell 쿼리, Cell 간 일관성 유지에 추가적인 설계가 필요합니다. SLA 99.99% 이상이 요구되는 서비스부터 도입을 검토하세요.
4. Multi-Cluster / Multi-Region
리전 수준의 장애에 대비하기 위한 Multi-Cluster 및 Multi-Region 전략입니다.
아키텍처 패턴 비교
| 패턴 | 설명 | RTO | RPO | 비용 | 복잡성 | 적합한 환경 |
|---|---|---|---|---|---|---|
| Active-Active | 모든 리전에서 동시에 트래픽 처리 | ~0 | ~0 | 매우 높음 | 매우 높음 | 글로벌 서비스, 극한 SLA |
| Active-Passive | 하나의 리전만 활성, 나머지 대기 | 분~시간 | 분 | 높음 | 높음 | 대부분의 비즈니스 앱 |
| Regional Isolation | 리전별 독립 운영, 데이터 격리 | 리전별 독립 | N/A | 중간 | 중간 | 규제 준수, 데이터 주권 |
| Hub-Spoke | 중앙 Hub에서 관리, Spoke에서 서빙 | 분 | 초~분 | 중간~높음 | 중간 | 관리 효율 중시 |
Global Accelerator + EKS
AWS Global Accelerator는 AWS 글로벌 네트워크를 활용하여 사용자에게 가장 가까운 리전의 EKS 클러스터로 트래픽을 라우팅합니다.
ArgoCD Multi-Cluster GitOps
ArgoCD ApplicationSet Generator를 사용하 여 여러 클러스터에 일관된 배포를 자동화합니다.
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: multi-cluster-app
namespace: argocd
spec:
generators:
# 클러스터 레이블 기반 동적 배포
- clusters:
selector:
matchLabels:
environment: production
resiliency-tier: "high"
template:
metadata:
name: 'myapp-{{name}}'
spec:
project: default
source:
repoURL: https://github.com/myorg/k8s-manifests.git
targetRevision: main
path: 'overlays/{{metadata.labels.region}}'
destination:
server: '{{server}}'
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
Istio Multi-Cluster Federation
Istio Multi-Primary 설정은 각 클러스터에 독립적인 Istio Control Plane을 운영하면서, 클러스터 간 서비스 디스커버리와 로드 밸런싱을 제공합니다.
# Istio Locality-Aware 라우팅 (Multi-Region)
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: multi-region-routing
spec:
host: backend-service
trafficPolicy:
loadBalancer:
localityLbSetting:
enabled: true
# 같은 리전 우선, 장애 시 다른 리전으로 failover
failover:
- from: us-east-1
to: eu-west-1
- from: eu-west-1
to: us-east-1
- from: ap-northeast-2
to: ap-southeast-1
outlierDetection:
consecutive5xxErrors: 3
interval: 10s
baseEjectionTime: 30s
maxEjectionPercent: 50
Istio 1.22+에서는 networking.istio.io/v1과 networking.istio.io/v1beta1 모두 사용 가능합니다. 신규 배포에서는 v1을 권장하며, 기존 v1beta1 설정도 여전히 유효합니다.
5. 애플리케이션 레질리언시 패턴
인프라 수준의 레질리언시와 함께, 애플리케이션 레벨의 장애 내성 패턴을 구현해야 합니다.