✍️ 필사 모드: Kubernetes 내부 완전 가이드 2025: Scheduler, Controller, etcd, API Machinery, CRD/Operator 심층 분석
한국어들어가며: kubectl apply의 진짜 의미
한 줄 명령 뒤
kubectl apply -f deployment.yaml
이 명령 하나로 Kubernetes는 다음을 수행한다:
- YAML을 API Server로 전송.
- API Server가 인증, 인가, admission control 수행.
- etcd에 선언된 상태(desired state) 저장.
- Deployment Controller가 이를 감지.
- ReplicaSet을 생성하라고 API Server에 요청.
- ReplicaSet Controller가 Pod를 생성하라고 요청.
- Scheduler가 각 Pod를 적절한 Node에 배치.
- Node의 kubelet이 컨테이너 런타임(containerd)에게 컨테이너 실행 요청.
- CNI 플러그인이 네트워크 설정.
- kubelet이 Pod 상태를 API Server에 보고.
- API Server가 etcd에 실제 상태 저장.
- Control loop가 계속 desired ↔ actual 조정.
12단계가 수 초 내에 일어난다. 각 단계가 독립적이고, 분산되어 있으며, 실패를 견딘다. 이것이 Kubernetes의 마법이다.
이 글에서 다룰 것
- Architecture 개요: Control plane과 Data plane.
- API Server: 모든 것의 관문.
- etcd: 유일한 진실의 원천.
- Scheduler: Pod → Node 배치.
- Controller Manager: 조정 루프.
- kubelet: Node의 일꾼.
- Informer와 Reconciliation: Kubernetes 프로그래밍 모델.
- CRD와 Operator: Kubernetes 확장.
왜 내부를 알아야 하는가?
- 디버깅: "왜 Pod가 Pending이지?"의 정확한 답.
- 최적화: 스케줄링, 리소스, 네트워크 튜닝.
- 확장: 커스텀 controller 작성.
- 보안: 공격 표면 이해.
- 아키텍처 결정: 무엇이 가능하고 무엇이 불가능한가.
1. Architecture 개요
Control Plane vs Data Plane
Kubernetes는 두 개의 주요 층으로 구성된다:
Control Plane (두뇌):
- API Server: 모든 요청의 진입점.
- etcd: 모든 상태 저장.
- Scheduler: Pod 배치 결정.
- Controller Manager: 조정 로직.
- Cloud Controller Manager: 클라우드 통합.
Data Plane (근육):
- Nodes: 워커 머신.
- kubelet: Node agent.
- kube-proxy: 네트워크.
- Container Runtime: containerd, CRI-O.
- CNI: 네트워크 플러그인.
- CSI: 스토리지 플러그인.
통신 다이어그램
┌─────────────┐
│ kubectl │
└──────┬──────┘
│ (REST/gRPC)
▼
┌───────────────────────────────────────────┐
│ Control Plane │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ API │←→ etcd │ │Scheduler │ │
│ │ Server │←→│ │ │ │ │
│ └─────┬────┘ └──────────┘ └─────┬────┘ │
│ ↑ │ │
│ ┌─────┴────┐ ↓ │
│ │Controller│ ←──────────────────┘ │
│ │ Manager │ │
│ └──────────┘ │
└─────────┬──────────────────────────────────┘
│ (watch)
▼
┌──────────────────────────────────┐
│ Worker Node │
│ ┌──────────┐ │
│ │ kubelet │ ← 모든 명령 수행 │
│ └────┬─────┘ │
│ │ │
│ ┌────▼──────┐ ┌──────────┐ │
│ │containerd │ │kube-proxy│ │
│ └───────────┘ └──────────┘ │
│ │ │
│ ┌────▼──────┐ │
│ │ Container │ │
│ └───────────┘ │
└────────────────────────────────────┘
핵심 원칙: Declarative + Reconciliation
Kubernetes의 근본 철학:
Imperative (명령형): "이 작업을 해라". Declarative (선언형): "이 상태가 되게 해라".
Kubernetes는 후자다. 당신은 "원하는 상태 (desired state)"를 선언하고, Kubernetes가 끊임없이 현재 상태 (actual state) 를 desired state에 맞추려 한다. 이를 reconciliation loop라고 한다.
while True:
actual = get_current_state()
desired = get_desired_state()
if actual != desired:
take_action_to_move_toward(desired)
sleep(interval)
이 단순한 루프가 Kubernetes 전체를 정의한다.
2. API Server: 모든 것의 관문
역할
kube-apiserver 는 Kubernetes control plane의 유일한 진입점:
- 모든 kubectl 명령.
- 모든 controller의 watch.
- 모든 kubelet의 상태 보고.
- 외부 도구 (Helm, ArgoCD 등).
모든 것이 API Server를 거친다. 다른 컴포넌트는 서로 직접 통신하지 않는다.
Stateless 설계
중요한 점: API Server는 stateless다. 상태는 모두 etcd에. 이는:
- 수평 확장 가능: 여러 API Server 인스턴스.
- Load balancing: 그 앞에 LB를 둘 수 있음.
- 장애 복구 쉬움: 죽어도 재시작하면 끝.
요청 처리 파이프라인
Kubernetes API 요청은 여러 단계를 거친다:
Client → [1. TLS] → [2. Authentication] → [3. Authorization]
→ [4. Admission Control] → [5. Validation] → [6. etcd]
1. TLS 종료: mTLS 또는 일반 TLS.
2. Authentication: "누구냐?"
- Certificate.
- Bearer token.
- Service account token.
- OIDC.
3. Authorization: "뭘 할 수 있냐?"
- RBAC (role-based access control).
- ABAC, Webhook, Node.
4. Admission Control: "이 요청이 유효/허용되는가?"
- Mutating webhooks: 요청 수정 (예: sidecar 자동 삽입).
- Validating webhooks: 거부 or 허용.
- Built-in admission: ResourceQuota, LimitRange.
5. Validation: 스키마 검증.
6. etcd: 최종 저장.
Admission Webhooks
Dynamic admission control의 강력함:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: pod-policy
webhooks:
- name: pod-policy.example.com
clientConfig:
service:
name: policy-service
namespace: kube-system
path: /validate
rules:
- operations: ["CREATE", "UPDATE"]
apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods"]
외부 webhook가 Pod 생성 요청을 가로채 검증 또는 거부할 수 있다. 이를 통해:
- OPA Gatekeeper: Policy as code.
- Kyverno: Kubernetes-native policy.
- Istio: Sidecar 자동 주입.
API Server의 저장: etcd
API Server는 모든 리소스를 etcd에 JSON 또는 protobuf로 저장:
/registry/pods/default/my-pod
/registry/deployments/default/my-deployment
/registry/services/default/my-service
/registry/nodes/worker-1
/registry/namespaces/default
경로 구조: /registry/<resource>/<namespace>/<name>.
Watch: 효율적 상태 구독
Kubernetes의 핵심 기능: watch API.
watch = client.CoreV1Api().list_pod_for_all_namespaces_with_http_info(watch=True)
for event in watch.stream():
print(event.type, event.object.metadata.name)
# ADDED, MODIFIED, DELETED
Polling 대신 push:
- Client가 한 번 watch 요청.
- 변경이 있을 때마다 실시간 이벤트 전송.
- Long-lived HTTP 연결 (chunked encoding).
이 덕분에 controller가 지연 없이 변경을 감지한다.
Resource Version
API Server는 각 리소스에 resourceVersion 을 부여:
metadata:
name: my-pod
resourceVersion: "12345"
- 모든 수정이 이 숫자를 증가시킴.
- Optimistic concurrency: 업데이트 시 resourceVersion 비교.
- 변경되었으면 → 409 Conflict → 클라이언트 재시도.
etcd Watch 연계
API Server의 watch는 etcd의 watch를 래핑:
- etcd가 변경 스트림을 제공.
- API Server가 이를 HTTP chunked로 변환해 클라이언트에게.
- 효율적이고 실시간.
Bookmarks와 List 최적화
문제: Long-running watch가 누락 이벤트를 감지하기 어려움.
Bookmark: 주기적 "상태 확인" 이벤트. "현재까지는 OK".
Chunked list: 큰 리스트를 여러 chunk로 전송.
이런 기능들이 대규모 클러스터에서 API Server의 성능을 보장한다.
3. etcd: 유일한 진실의 원천
왜 etcd인가
Kubernetes의 모든 상태는 etcd에 저장된다:
- Pod, Deployment, Service 등 리소스.
- ConfigMap, Secret.
- Cluster 정보, leader election 데이터.
왜 etcd:
- Raft 기반: 강한 일관성.
- Watch API: 변경 스트림.
- 분산: 3~5 노드 클러스터.
- Key-value: 단순.
- Go로 작성: Kubernetes와 같은 언어.
Raft 복습
(Raft 가이드의 요약)
- Leader + followers: 한 번에 한 리더.
- 과반수 필요: 3 노드 = 2 과반, 5 노드 = 3 과반.
- 로그 복제: Leader가 follower에 동기화.
- 선거: Leader 장애 시 자동 재선출.
Kubernetes에서의 의미:
- 3 etcd 노드: 1 장애 허용.
- 5 etcd 노드: 2 장애 허용.
- 권장: 3 또는 5. 짝수는 무의미.
Kubernetes 리소스 저장
etcd의 key 구조:
/registry/pods/default/nginx-xyz-abc
/registry/pods/kube-system/kube-apiserver-node1
/registry/deployments/default/nginx
/registry/services/kube-system/kube-dns
/registry/configmaps/default/app-config
/registry/secrets/default/db-password
/registry/nodes/worker-1
/registry/namespaces/default
각 key는 하나의 리소스. Value는 protobuf 또는 JSON 직렬화된 객체.
etcd 성능 특성
etcd의 성능 한계:
- 쓰기 처리량: 수천~수만/초.
- Watch 수: 클러스터당 수만.
- 저장 크기: 기본 2 GB, 최대 8 GB 권장.
- Latency: fsync 의존, SSD 필수.
Kubernetes 스케일링 한계
etcd의 성능이 Kubernetes 클러스터의 한계를 결정한다:
- 기본 권장: 5000 노드, 150,000 pod.
- 극한: 15,000 노드까지 검증.
- 상위 제한: etcd의 쓰기와 watch 처리량.
왜 이렇게 제한:
- 매 초 수천 개의 kubelet heartbeat.
- Controller의 watch 이벤트.
- Pod 생성/삭제 시 다수 업데이트.
- 모두 etcd 통과.
etcd 튜닝
Production 설정:
etcd:
dataDir: /var/lib/etcd # SSD 권장
electionTimeout: 1000 # ms
heartbeatInterval: 100 # ms
snapshotCount: 10000 # N 변경마다 snapshot
autoCompactionRetention: "8h" # 오래된 버전 정리
autoCompactionMode: periodic
quotaBackendBytes: 8589934592 # 8 GB
백업 필수:
ETCDCTL_API=3 etcdctl snapshot save backup.db
복구:
ETCDCTL_API=3 etcdctl snapshot restore backup.db
etcd를 잃으면 클러스터 상태를 잃는다. Backup 없이는 재앙.
4. Scheduler: Pod → Node 배치
Scheduler의 역할
kube-scheduler 는 단순한 목적을 가진다:
"Pending 상태의 Pod에 적절한 Node를 할당한다."
실제 Pod 실행은 kubelet이 한다. Scheduler는 결정만.
Scheduling Cycle
각 pending pod에 대해:
1. Filter (Predicate): 이 Node에 Pod 배치 가능한가?
2. Score (Priority): 가능한 Node들에 점수 매기기.
3. Select: 가장 점수 높은 Node 선택.
4. Bind: API Server에 Binding 객체 생성.
Filter Plugins (예전 Predicate)
"이 Node가 Pod를 받을 수 있는가?" 를 결정:
Resource Fit: CPU, memory 충분?
- Pod의
requestsvs Node의allocatable.
Node Affinity: 라벨 셀렉터.
Taints and Tolerations: Node taint에 대한 tolerance.
Pod Affinity/Anti-Affinity: 다른 pod와의 관계.
Volume 제약: PV가 이 Node에 접근 가능?
Node Ports: 요청한 hostPort 사용 가능?
만약 어떤 필터라도 실패하면 Node는 후보에서 제외. 모든 Node가 탈락하면 Pod는 Pending.
Score Plugins (예전 Priority)
후보 Node들에 0-100 점수:
ImageLocality: 이미 이미지가 있는 Node.
LeastAllocated: 리소스 여유 많은 Node 선호.
BalancedResourceAllocation: CPU와 memory 사용률 균형.
NodeAffinity (soft): 선호 라벨.
InterPodAffinity (soft): 다른 pod와의 거리.
TopologySpreadConstraints: 영역별 분산.
점수를 가중 합산해서 최고 점수 Node 선택.
Scheduling Framework
v1.15+에서 도입된 확장 가능한 아키텍처:
QueueSort → PreFilter → Filter → PostFilter
→ PreScore → Score → Reserve → Permit
→ PreBind → Bind → PostBind
각 단계마다 플러그인을 연결할 수 있다. Custom scheduling 로직 추가 가능.
Pod Affinity/Anti-Affinity
Affinity: Pod끼리 가까이.
- 예: frontend와 cache를 같은 Node에.
Anti-affinity: Pod끼리 멀리.
- 예: Database replica를 다른 Node에 (HA).
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: mydb
topologyKey: kubernetes.io/hostname
주의: Anti-affinity는 비용이 크다. 큰 클러스터에서 scheduling 느려짐.
Topology Spread Constraints
더 현대적이고 효율적인 분산 제어:
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: myapp
"각 zone에 pod 수 차이가 1 이내".
Multiple Schedulers
한 클러스터에 여러 스케줄러 가능:
spec:
schedulerName: my-custom-scheduler
특정 pod만 custom scheduler가 처리. 나머지는 default.
용도:
- Gang scheduling (여러 pod 동시 할당).
- GPU 특화.
- ML 워크로드.
Scheduler 성능
대규모 클러스터 성능:
- 초당 수백 pod 스케줄링.
- 10000+ node에서도 작동.
- 병목: 보통 API Server 또는 etcd.
5. Controller Manager: 조정의 심장
Controller란
Controller 는 Kubernetes의 핵심 프로그래밍 모델:
def run_controller():
while True:
desired = get_desired_state() # 사용자가 원하는
actual = get_actual_state() # 실제 상태
if desired != actual:
take_actions_to_reconcile()
sleep()
Reconciliation loop. 단순하지만 강력.
Built-in Controllers
kube-controller-manager 는 수십 개 controller를 실행:
Deployment Controller: Deployment 스펙 → ReplicaSet 생성.
ReplicaSet Controller: ReplicaSet → 원하는 수의 Pod.
Node Controller: Node 상태 감시, 다운 시 taint.
ServiceAccount Controller: 각 namespace에 default SA 생성.
Job Controller: Job 완료 추적.
CronJob Controller: 스케줄된 Job 생성.
Namespace Controller: Namespace 삭제 처리.
PV Controller: Persistent Volume 바인딩.
Endpoint Controller: Service → Endpoint.
Horizontal Pod Autoscaler: 메트릭 기반 스케일.
총 30+ controller가 동시 실행.
Controller의 작동
예시: Deployment Controller:
- Watch: API Server의 Deployment 리소스 감시.
- Event: 새 Deployment 생성됨.
- Read: Deployment 스펙 읽기 (replicas: 3).
- Check: 이 Deployment의 ReplicaSet이 있나?
- Action: 없으면 ReplicaSet 생성 (replicas: 3).
- Watch: ReplicaSet Controller가 ReplicaSet 감시.
- Event: 새 ReplicaSet.
- Check: 이 ReplicaSet의 Pod가 몇 개?
- Action: 0개 → Pod 3개 생성.
각 controller는 자기 리소스만 관리. 연쇄적으로 동작.
조정 루프의 멱등성
Controller의 핵심 원칙:
"같은 상태를 여러 번 조정해도 결과가 같아야 한다."
멱등성 (Idempotency): 이미 원하는 상태면 아무것도 하지 않음.
def reconcile(obj):
if already_in_desired_state(obj):
return # No-op
take_actions()
이 덕분에:
- Controller 재시작 안전.
- 이벤트 중복 무해.
- 분산 환경 안전.
Level-based vs Edge-based
Edge-based: "새 이벤트가 왔어" → 처리.
Level-based: "현재 상태를 보자" → 원하는 상태와 비교 → 조치.
Kubernetes는 level-based. 이벤트 놓쳐도 다음에 상태를 보면 복구 가능. 더 견고하다.
Leader Election
controller-manager는 고가용성을 위해 여러 인스턴스 실행:
- 한 번에 한 리더만 활성.
- Leader election으로 선출.
- 리더가 모든 controller 실행.
- 리더 장애 시 다른 인스턴스가 take over.
# ConfigMap 기반 leader election
apiVersion: coordination.k8s.io/v1
kind: Lease
metadata:
name: kube-controller-manager
spec:
holderIdentity: controller-manager-1
leaseDurationSeconds: 15
Cloud Controller Manager
cloud-controller-manager 는 클라우드별 로직 분리:
- Node controller: 클라우드 API로 Node 상태 확인.
- Service controller: LoadBalancer 생성.
- Route controller: VPC 라우트 설정.
- Volume controller: 클라우드 스토리지 관리.
AWS, GCP, Azure 등 각자의 구현.
6. kubelet: Node의 일꾼
kubelet의 역할
각 워커 노드에서 실행되는 agent. Pod를 실제로 실행하는 주체.
주요 책임:
- API Server에 연결해 이 Node에 할당된 Pod watch.
- Container runtime에게 컨테이너 실행 명령.
- Pod 상태를 API Server에 보고.
- Health check 실행.
- Volume 마운트.
Pod Lifecycle
1. Scheduler가 Pod를 Node에 할당.
2. kubelet이 자기 Node에 할당된 새 Pod 감지.
3. Volume 준비.
4. Image pull.
5. Container runtime에게 container 실행 명령.
6. Probe 시작.
7. 상태를 API Server에 보고.
Container Runtime Interface (CRI)
kubelet은 CRI를 통해 runtime과 통신:
kubelet → gRPC → CRI Runtime → OCI Container
CRI runtimes:
- containerd: 기본.
- CRI-O: Red Hat 스타일.
dockershim: v1.24부터 제거.
kubelet의 Cgroup 관리
kubelet이 Pod의 cgroup hierarchy 관리:
/sys/fs/cgroup/kubepods/
├── burstable/
│ ├── pod-uid-1/
│ │ ├── container-1/
│ │ └── container-2/
│ └── ...
├── besteffort/
└── guaranteed/
QoS classes:
- Guaranteed: limits == requests.
- Burstable: limits > requests.
- BestEffort: limits, requests 없음.
OOM 시 BestEffort가 먼저 죽음.
Liveness, Readiness, Startup Probes
Liveness: "이 컨테이너가 살아있는가?"
- 실패 시 재시작.
Readiness: "트래픽 받을 준비 됐나?"
- 실패 시 Service에서 제외.
Startup: "시작 중인가?"
- 실패해도 재시작 안 함.
- Liveness/Readiness 보호.
spec:
containers:
- name: app
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
Volume Management
kubelet이 volume 마운트 관리:
EmptyDir: 컨테이너 간 공유. HostPath: Node 파일시스템. PV/PVC: 영속 스토리지. CSI: 외부 드라이버.
각 Pod 시작 시:
- Volume attach (외부 스토리지).
- Volume mount.
- 컨테이너 시작.
Pod 종료 시 역순.
Node Status 보고
kubelet이 주기적으로 API Server에 상태 보고:
status:
conditions:
- type: Ready
status: "True"
lastHeartbeatTime: "2025-04-15T09:00:00Z"
capacity:
cpu: "16"
memory: 32Gi
allocatable:
cpu: "15800m"
memory: 31Gi
Heartbeat 간격: 기본 10초. 40초 동안 없으면 Node를 NotReady로 표시.
Node Pressure
kubelet이 Node 리소스 압박 감지:
MemoryPressure: 메모리 부족 → Pod eviction. DiskPressure: 디스크 부족 → 이미지 가비지 컬렉션. PIDPressure: 프로세스 수 부족.
Eviction 순서:
- Best-effort pods 먼저.
- Burstable (요청 초과하는 것).
- 마지막으로 Guaranteed.
7. Informer와 Reconciliation
Informer란
Kubernetes controller의 기본 빌딩 블록. client-go 라이브러리의 핵심.
역할:
- API Server에서 리소스 watch.
- 로컬 캐시에 저장.
- 이벤트 핸들러 호출 (ADD, UPDATE, DELETE).
- 캐시 기반 빠른 조회.
왜 Informer가 필요한가
Naive 접근:
for {
pods := apiServer.List("pods")
for _, p := range pods {
process(p)
}
sleep(10s)
}
문제:
- API Server 부담: 매번 전체 list.
- 느림: 큰 클러스터에서.
- 이벤트 놓침: 10초 sleep 동안.
Informer 해결:
- 한 번 list, 이후 watch.
- 로컬 캐시: API Server에 쿼리 불필요.
- 실시간 이벤트.
Informer 구조
factory := informers.NewSharedInformerFactory(clientset, 30*time.Second)
podInformer := factory.Core().V1().Pods()
podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
pod := obj.(*v1.Pod)
queue.Add(key(pod))
},
UpdateFunc: func(old, new interface{}) {
// ...
},
DeleteFunc: func(obj interface{}) {
// ...
},
})
factory.Start(stopCh)
factory.WaitForCacheSync(stopCh)
Work Queue Pattern
Informer + WorkQueue + Worker 조합이 controller의 표준 패턴:
[API Server] → [Informer] → [Cache] + [EventHandler]
↓
[WorkQueue]
↓
[Workers (N)]
↓
[Reconcile]
↓
[API Server]
Work Queue:
- 처리할 작업 키 저장.
- Rate limiting.
- Retry: 실패 시 재시도.
- Deduplication: 같은 키 중복 제거.
Workers:
- 여러 goroutine이 큐에서 일 가져옴.
reconcile(key)호출.- 성공 → 큐에서 제거.
- 실패 → 재큐.
Reconcile 함수
controller의 핵심 로직:
func (c *Controller) reconcile(key string) error {
// 1. Cache에서 현재 상태 가져오기
obj, exists, err := c.informer.GetIndexer().GetByKey(key)
if !exists {
// 삭제 처리
return nil
}
// 2. Desired vs Actual 비교
desired := deriveDesired(obj)
actual := getActualState(obj)
// 3. 차이가 있으면 조정
if !equal(desired, actual) {
return takeActions(desired, actual)
}
return nil
}
원칙:
- 멱등: 여러 번 호출해도 안전.
- 빠름: 캐시 활용.
- 에러 처리: 재시도 가능.
Rate Limiting과 Exponential Backoff
실패 시 즉시 재시도하면 장애 확대. WorkQueue는 지수 백오프:
Attempt 1: 즉시
Attempt 2: 5ms
Attempt 3: 10ms
Attempt 4: 20ms
...
Max: 1000 seconds
핫 loop 방지. API Server 보호.
8. CRD와 Operator
Custom Resource Definition
CRD는 Kubernetes API를 확장한다. 새로운 리소스 타입 정의:
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: databases.example.com
spec:
group: example.com
versions:
- name: v1
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
engine:
type: string
version:
type: string
replicas:
type: integer
scope: Namespaced
names:
kind: Database
plural: databases
singular: database
이제:
apiVersion: example.com/v1
kind: Database
metadata:
name: mydb
spec:
engine: postgres
version: "15"
replicas: 3
쓸 수 있다. Kubernetes는 이를 etcd에 저장. 하지만 아무것도 하지 않는다. 누군가가 이 리소스를 조정해야 한다.
Operator란
Operator: CRD + Controller.
- CRD: 새 리소스 정의.
- Controller: 이 리소스를 조정하는 로직.
즉, "Database" CRD를 만들고, 이를 감시하는 controller를 작성해서 실제 DB를 생성/관리.
예시: PostgresDB operator.
- 사용자가
kind: Database생성. - Operator controller가 감지.
- StatefulSet, Service, PVC 생성.
- PG 초기화.
- 백업 설정.
- 모니터링 추가.
한 줄의 YAML이 완전한 DB 클러스터를 만든다.
유명 Operators
Database:
- Prometheus Operator: Prometheus 클러스터 관리.
- Postgres Operator (Zalando, CrunchyData).
- MySQL Operator.
- MongoDB Enterprise Operator.
Service Mesh:
- Istio Operator.
CI/CD:
- ArgoCD: GitOps.
- Tekton.
Storage:
- Rook (Ceph).
- OpenEBS.
Operator SDK
Operator 개발 도구:
Operator SDK (Red Hat):
operator-sdk init --domain example.com --repo github.com/example/db-operator
operator-sdk create api --group db --version v1 --kind Database --resource --controller
Kubebuilder: 비슷한 용도. Kubernetes 커뮤니티 권장.
Controller-runtime: 저수준 라이브러리.
Operator Pattern
Kubernetes 확장의 표준 방법:
- CRD 정의: 도메인 리소스 스키마.
- Controller 작성: Reconcile 로직.
- 배포: Operator를 Pod로.
- 사용: YAML로 리소스 생성.
- Operator가 실제 일 수행.
철학: "Kubernetes를 데이터베이스로, controller를 logic으로".
Level of Operator Maturity
Level 1 - Basic Install: 설치/제거.
Level 2 - Seamless Upgrades: 업그레이드 자동화.
Level 3 - Full Lifecycle: 백업, failover.
Level 4 - Deep Insights: 메트릭, 알림, 로깅.
Level 5 - Auto Pilot: 완전 자동화. 사람 개입 최소화.
성숙한 operator는 SRE 역할을 자동화.
9. Networking: CNI와 kube-proxy
Pod Networking
Kubernetes의 네트워킹 모델:
- 각 Pod는 고유 IP.
- Pod 간 NAT 없이 통신.
- Node → Pod 통신 직접.
이를 위해 CNI (Container Network Interface) 플러그인이 필요.
CNI Plugins
Flannel: VXLAN overlay. 단순.
Calico: BGP 라우팅. 네트워크 정책.
Cilium: eBPF 기반. 현대적.
Weave Net: Mesh.
AWS VPC CNI: 클라우드 네이티브.
Service와 kube-proxy
Service: Pod의 가상 endpoint.
apiVersion: v1
kind: Service
metadata:
name: my-app
spec:
selector:
app: my-app
ports:
- port: 80
kube-proxy: Service IP → Pod IP 라우팅.
구현 방식:
iptables mode (기본):
- iptables 규칙으로 라우팅.
- 빠르지만 규칙 수가 많아지면 느림.
IPVS mode:
- 커널 IPVS 모듈 사용.
- 수만 Service 확장 가능.
eBPF mode (Cilium):
- kube-proxy 대체.
- 가장 빠름.
DNS
CoreDNS: 클러스터 내부 DNS.
my-service.my-namespace.svc.cluster.local
Service가 DNS 이름을 얻음. Pod 내부에서 이 이름으로 Service 접근.
10. Storage: PV, PVC, CSI
Persistent Volumes
PV (PersistentVolume): 클러스터의 스토리지 리소스.
PVC (PersistentVolumeClaim): 스토리지 요청.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
Dynamic Provisioning
StorageClass: 자동 PV 생성 규칙.
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast
provisioner: kubernetes.io/aws-ebs
parameters:
type: gp3
iops: "3000"
PVC가 생성되면 자동으로 PV 프로비저닝.
CSI (Container Storage Interface)
CSI: 스토리지 드라이버 표준 인터페이스.
AWS EBS CSI, GCP PD CSI, Ceph RBD, Longhorn 등이 구현.
kubelet이 CSI driver를 통해 volume 관리:
- CreateVolume: 클라우드에서 볼륨 생성.
- AttachVolume: Node에 attach.
- NodeStageVolume: 마운트 준비.
- NodePublishVolume: 실제 마운트.
11. Security
RBAC
Role-Based Access Control:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods
namespace: default
subjects:
- kind: User
name: alice
roleRef:
kind: Role
name: pod-reader
Service Accounts
Pod가 API에 접근할 때 사용:
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-app-sa
---
apiVersion: v1
kind: Pod
spec:
serviceAccountName: my-app-sa
Pod는 SA의 토큰으로 API 인증.
Pod Security Standards
Privileged, Baseline, Restricted 세 수준:
apiVersion: v1
kind: Namespace
metadata:
name: my-ns
labels:
pod-security.kubernetes.io/enforce: restricted
Restricted 네임스페이스는 root 실행 금지, capabilities 제한 등.
Network Policies
기본: 모든 Pod가 서로 통신 가능. Network Policy로 제한.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
spec:
podSelector:
matchLabels:
app: db
ingress:
- from:
- podSelector:
matchLabels:
app: web
ports:
- port: 5432
Web만 DB 접근 가능.
주의: CNI 플러그인이 지원해야. Calico, Cilium 등.
12. 실전 튜닝과 디버깅
일반적 문제와 해결
Pod가 Pending:
kubectl describe pod my-pod
# Events 확인
# - Insufficient cpu/memory
# - No nodes match node selector
# - volume not found
Pod가 ImagePullBackOff:
- 이미지 이름 오타.
- Private registry 인증 필요.
- Image pull secret 설정.
Pod가 CrashLoopBackOff:
kubectl logs my-pod --previous
# 이전 실행의 로그
Slow scheduling:
- Anti-affinity 남용.
- Topology constraints 복잡.
kube-scheduler로그 확인.
리소스 관리
Requests와 Limits 설정:
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "512Mi"
원칙:
- Requests: 스케줄링 기준, 최소 보장.
- Limits: 최대 허용.
- CPU limits는 throttling 유발, 주의.
Horizontal Pod Autoscaler
메트릭 기반 자동 스케일:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
spec:
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
Cluster Autoscaler
Node 수준 스케일:
- Pending pod가 있으면 Node 추가.
- Node가 과소 사용이면 제거.
- Cloud provider 통합.
모니터링
Prometheus + Grafana: 메트릭. Loki: 로그. Jaeger: 추적.
kube-state-metrics: K8s 객체 상태 메트릭.
클러스터 건강 지표:
- API Server latency.
- etcd disk sync time.
- Scheduler queue depth.
- Pending pod count.
퀴즈로 복습하기
Q1. Kubernetes의 "reconciliation loop"가 왜 그렇게 중요한가?
A.
Reconciliation loop는 Kubernetes의 프로그래밍 모델 자체다. 다른 시스템과의 근본적 차이를 만든다.
기존 시스템의 접근: 명령형 (Imperative):
# 전통적 배포
ssh server1 "docker run myapp:v2"
ssh server2 "docker run myapp:v2"
ssh server3 "docker run myapp:v2"
문제:
- server2가 실패하면? 수동 재시도.
- 중간에 네트워크 단절? 일부만 배포됨.
- 누군가 server1의 컨테이너를 수동 삭제? 감지 못 함.
- 상태 추적 불가.
Kubernetes의 접근: 선언형 + Reconciliation:
# deployment.yaml
spec:
replicas: 3
template:
spec:
containers:
- image: myapp:v2
kubectl apply -f deployment.yaml
"이 상태가 되게 해라". 어떻게는 Kubernetes가 알아서.
Reconciliation Loop의 동작:
while True:
desired = read_deployment_spec()
actual = count_running_pods_matching_label()
if actual < desired.replicas:
create_pods(desired.replicas - actual)
elif actual > desired.replicas:
delete_excess_pods(actual - desired.replicas)
# if equal: do nothing
sleep(interval)
주요 특성:
1. Level-triggered (상태 기반):
- 이벤트 누락에 강함.
- "뭐가 바뀌었나?"가 아니라 "현재 어떤가?".
- Controller가 crash되어도 재시작하면 다시 조정.
Edge-triggered의 문제:
- 이벤트 하나 놓치면 영원히 불일치.
- 복구를 위한 복잡한 로직 필요.
Level-triggered의 해결:
- 매 loop마다 현재 상태 재검사.
- 자동 복구.
2. 멱등성 (Idempotency):
같은 조정을 여러 번 해도 결과 동일:
- 이미 3개 pod 실행 중 → 아무것도 안 함.
- 2개만 실행 중 → 1개 추가.
- 4개 실행 중 → 1개 삭제.
이점:
- Controller 재시작 안전.
- 네트워크 지연으로 인한 중복 호출 무해.
- 병렬 controller 안전.
3. 자가 치유 (Self-healing):
Pod가 죽으면 (OOM, crash, node 장애):
- kubelet이 감지.
- API Server에 pod 상태 업데이트.
- ReplicaSet Controller가 감지.
- Desired (3) ≠ Actual (2) → 새 Pod 생성.
- Scheduler가 새 Pod에 Node 할당.
- kubelet이 시작.
- 다시 3개.
사람 개입 없음. 수 초~수십 초 안에 복구.
4. Declarative Configuration:
사용자는 "무엇을" 만 기술. "어떻게" 는 Kubernetes가.
이점:
- Version control 쉬움: YAML in Git.
- GitOps 가능: Git push = 배포.
- Reproducible: 같은 YAML = 같은 결과.
- Auditable: 변경 추적 가능.
5. 여러 Controller의 협업:
Kubernetes는 수십 개 controller가 동시 실행:
- Deployment Controller → ReplicaSet 생성.
- ReplicaSet Controller → Pod 생성.
- Scheduler → Pod에 Node 할당.
- Node Controller → Node 상태 감시.
- Service Controller → Endpoint 업데이트.
각자 자기 일만 함. 서로 직접 통신 안 함. API Server + etcd를 통해 간접 조정.
이 아키텍처가:
- Loose coupling: 한 controller 장애가 전체를 막지 않음.
- 독립 배포: 각 controller 따로 업그레이드.
- 확장성: 새 controller 추가 쉬움.
- 명확성: 각 controller의 책임 분명.
실전 영향:
1. 운영의 변화:
- 수동 작업 → 자동화.
- "pod 재시작" 스크립트 → 불필요.
- Monitoring → 상태 확인.
2. 개발자 경험:
- 인프라 선언 → YAML.
- 복잡한 배포 스크립트 → 간단한 apply.
- Rollback → 쉬움 (이전 YAML 적용).
3. Resilience:
- 장애에 강함 (자동 복구).
- 변경에 강함 (rolling update).
- 예측 가능 (선언적 상태).
한계:
Reconciliation loop도 완벽하지 않다:
1. Eventual consistency:
- 즉시 아님. 수 초 지연.
- Real-time 시스템엔 부적절.
2. Convergence 문제:
- 여러 controller가 상충하면 "조정 전쟁".
- 예: HPA와 사용자의 manual scale.
3. External state:
- 외부 DB, 클라우드 리소스 등은 K8s 모름.
- Operator로 확장해야.
4. Action은 여전히 명령형:
- "Pod 생성" 자체는 명령.
- Reconciler가 이 명령을 호출.
- 선언적 껍질 안의 명령형 로직.
교훈:
Reconciliation loop는 단순하지만 강력한 패턴이다. Kubernetes의 성공은 기술이 아니라 이 패러다임에 있다. 많은 사람들이 "YAML 기반 배포 도구"로만 Kubernetes를 생각하지만, 진짜 힘은 끊임없이 원하는 상태로 수렴하는 시스템이다.
이 패턴은 Kubernetes를 넘어서 확산되고 있다:
- Terraform: 선언적 인프라 (하지만 one-shot).
- Pulumi: 같은 접근.
- GitOps (ArgoCD, Flux): Kubernetes 위에 reconciliation.
- Crossplane: K8s operator로 클라우드 리소스 관리.
- System-level operators: DB, Kafka, ML 모델 등.
"Kubernetes as a platform": 사용자가 desired state 정의, K8s가 reconcile. 이 간단한 모델이 복잡한 운영 문제를 해결한다.
Reconciliation loop를 이해하면 Kubernetes를 "마법"이 아니라 "하나의 거대한 상태 머신" 으로 볼 수 있다. 그리고 그 순간, 모든 것이 명확해진다.
Q2. 왜 Kubernetes는 etcd 없이는 작동하지 못하는가?
A.
etcd는 Kubernetes의 "유일한 진실의 원천" 이다. 모든 상태가 거기에 있다.
저장되는 것:
- 모든 리소스 (Pod, Deployment, Service, ConfigMap, Secret, ...).
- Node 정보.
- RBAC 규칙.
- Custom Resources.
- Leader election 정보.
- 설정.
etcd가 없으면:
- 어떤 Pod가 있는지 모름.
- 어떤 노드가 있는지 모름.
- 어떤 설정인지 모름.
- 인증 정보 모름.
Kubernetes가 완전히 마비된다.
왜 etcd가 필수인가:
1. 강한 일관성 (Strong Consistency):
Kubernetes는 분산 시스템이다. 여러 API Server 인스턴스, 여러 controller, 여러 kubelet. 모두가 같은 상태를 봐야 한다:
- Controller A가 Pod를 만들었다.
- Controller B가 즉시 감지해야.
- 불일치 → 중복 또는 누락.
etcd는 Raft 기반이라 강한 일관성 보장:
- 쓰기 후 즉시 읽기 → 최신 값.
- 모든 노드가 같은 순서의 변경.
- Linearizable.
일관성 없으면:
- 2개 pod 생성해야 하는데 4개 생성.
- Deployment 삭제했는데 controller가 안 봄.
- 끔찍한 부정합.
2. Watch API:
Controller들은 변경을 실시간으로 알아야 한다:
// Controller가 Pod 변경 감시
watcher := client.CoreV1().Pods("").Watch(context.Background(), options)
for event := range watcher.ResultChan() {
// 이벤트 처리
}
etcd의 native watch:
- 변경 발생 시 즉시 알림.
- Polling 불필요.
- 효율적 (HTTP/2, long-lived).
다른 DB들은 watch가 2차 기능이지만 etcd는 1급 시민.
3. MVCC (Multi-Version Concurrency Control):
- 각 변경이 revision 받음.
- 과거 버전 조회 가능 (compaction 전까지).
- Optimistic concurrency: 충돌 감지.
Compare-And-Set 연산:
- "Resource version X에서 Y로 변경".
- Version이 다르면 실패.
- 동시 수정 충돌 자동 방지.
4. Leader Election:
여러 controller-manager, scheduler 중 한 번에 하나만 활성:
- etcd의 lease 기반.
- Lock 획득 = Leader.
- Leader 실패 시 다른 인스턴스가 lock 획득.
apiVersion: coordination.k8s.io/v1
kind: Lease
metadata:
name: kube-scheduler
namespace: kube-system
spec:
holderIdentity: scheduler-1
leaseDurationSeconds: 15
5. 분산 고가용성:
etcd는 여러 노드 클러스터:
- 3 노드: 1 장애 허용.
- 5 노드: 2 장애 허용.
- 7 노드: 3 장애 허용 (드물음, 쓰기 느려짐).
한 etcd 노드 장애에도 Kubernetes는 계속 작동.
왜 다른 DB는 안 되나:
MySQL/PostgreSQL:
- 강한 일관성 O.
- Watch API 약함 (trigger, logical replication).
- Leader election 없음.
- 전통적 DB에 적합한 용도.
Redis:
- 빠르지만 일관성 약함.
- Sentinel로 HA 가능하지만 etcd보다 약함.
- Watch (pub/sub) 있지만 다름.
Cassandra:
- AP (tunable consistency).
- Strong consistency 어려움.
- Kubernetes에 부적합.
ZooKeeper:
- etcd의 "원조" 같은 존재.
- 같은 용도 가능.
- 실제로 초기 Kubernetes는 ZooKeeper 고려.
- etcd가 "Go로 작성, 더 현대적" 이점.
etcd의 한계:
1. 크기 제한:
- 기본 2 GB, 최대 8 GB 권장.
- 큰 ConfigMap, Secret 많으면 한계.
2. 쓰기 처리량:
- 수천~수만 TPS.
- fsync 의존, SSD 필수.
- 네트워크 지연에 민감.
3. Watch 수:
- 수만~수십만.
- 너무 많으면 메모리 부담.
4. 복잡성:
- Raft 튜닝, 백업, 복구 복잡.
- "etcd 운영자"가 필요할 정도.
Kubernetes 스케일링 한계:
etcd = Kubernetes 스케일 한계:
- 5000 노드, 150,000 pod: 공식 권장.
- 10,000 노드: 검증됨 (신중히).
- 15,000 노드: 극한, Google 등만.
이 한계의 대부분이 etcd 성능이다:
- Pod 생성/삭제 = etcd 쓰기.
- kubelet heartbeat = etcd 쓰기.
- Controller watch = etcd watch.
- 수천 노드 = 수천 연결.
Google Borg의 선택:
Google의 Borg (Kubernetes의 전신)는 다른 접근:
- Chubby (ZooKeeper 원조) 사용.
- 하지만 스케일을 위해 여러 최적화.
- Hierarchical caching.
- 전용 proxy layer.
Kubernetes는 etcd를 그대로 쓰며 단순함을 선택했다. 그 대가로 스케일 한계가 있다.
대안의 시도들:
Kine: etcd를 SQL DB로 대체.
- SQLite, MySQL, PostgreSQL 백엔드.
- 단일 노드 또는 소규모 클러스터에 유용.
- K3s (Rancher)가 채택.
Redis-backed K8s: 실험적 시도. 성공 못함.
분산 SQL: 이론적 가능, 실전 없음.
결론: etcd의 대안이 쉽지 않다. 강한 일관성 + Watch + HA + 성능을 모두 제공하는 솔루션이 드물다.
운영상 함의:
1. etcd 백업 필수:
ETCDCTL_API=3 etcdctl snapshot save /backup/etcd-$(date +%F).db
매일, 매시간. etcd 잃으면 클러스터 잃음.
2. etcd 모니터링:
- Disk latency.
- Network latency.
- Memory usage.
- Watch count.
3. etcd 업그레이드 조심:
- Rolling restart.
- 한 번에 하나씩.
- Backup 후 진행.
4. 분리된 etcd:
- Production에선 control plane과 분리 권장.
- 전용 SSD.
- 전용 노드.
교훈:
"Kubernetes = API + etcd" 라는 말이 있다. 정확하다. API Server는 stateless 프록시일 뿐이고, etcd가 진짜 두뇌다.
etcd의 우수성이 Kubernetes의 성공을 가능하게 했다. 만약 Kubernetes가 약한 DB를 썼다면:
- 일관성 버그.
- 느린 scheduling.
- 복잡한 워크어라운드.
Kubernetes를 이해하려면 etcd를 이해해야 한다. Reading /registry/pods/default/nginx-xyz가 무슨 일인지 알면, Kubernetes가 어떻게 작동하는지 본질이 보인다.
**"The database is the system"**이라는 CockroachDB 창립자들의 말처럼, Kubernetes의 경우 etcd가 시스템의 심장이다. 없으면 죽는다.
Q3. Controller의 "멱등성 (idempotency)" 이 왜 필수적인가?
A.
멱등성의 정의:
같은 연산을 여러 번 실행해도 결과가 같다.
예시:
멱등 (idempotent):
def set_replicas(deployment, 3):
deployment.spec.replicas = 3 # 항상 3으로 설정
1번 호출: 3. 10번 호출: 3. 결과 동일.
비멱등 (non-idempotent):
def add_replica(deployment):
deployment.spec.replicas += 1 # 매번 +1
1번 호출: +1. 10번 호출: +10. 결과 다름.
Kubernetes Controller에서 왜 중요한가:
이유 1: Reconciliation Loop의 본질
Controller는 반복적으로 같은 상태를 조정한다:
while True:
reconcile(pod)
sleep(interval)
reconcile이 멱등이 아니면:
- 1번 호출: Pod 생성.
- 2번 호출: Pod 또 생성 → 중복.
- 10번 호출: 10개 pod.
혼란. Kubernetes는 같은 상태를 반복 검사하므로 각 검사가 no-op이어야 한다.
이유 2: 이벤트 중복
Watch API는 때때로 같은 이벤트를 두 번 보낼 수 있다:
- 네트워크 재연결.
- Controller 재시작.
- Resync 주기.
멱등이 아니면:
- ADD 이벤트 2번 → Pod 2개 생성.
- DELETE 이벤트 2번 → 이미 삭제된 것 또 삭제 시도 → 에러.
멱등이면:
- ADD 2번 → 첫 번째에 생성, 두 번째에 이미 존재하니 no-op.
- DELETE 2번 → 첫 번째에 삭제, 두 번째에 존재 안 하니 no-op.
안전.
이유 3: 분산 Controller
여러 controller 인스턴스 (HA):
- Leader election으로 한 번에 하나만 활성.
- 하지만 failover 중 두 인스턴스가 동시에 활성 가능.
- 같은 이벤트를 둘이 처리.
멱등이 아니면: 중복 처리 → 2배 리소스 생성.
멱등이면: 둘이 같은 결과를 만들려 함 → 한 번만 실제 변경.
이유 4: 재시작 안전
Controller가 crash 후 재시작:
- 모든 리소스를 다시 list.
- 각각 reconcile.
- 이전에 처리한 것도 다시 처리.
멱등이 아니면: 재시작마다 리소스 2배, 3배.
멱등이면: 이미 원하는 상태 → no-op → 안전.
실전 예시: Deployment Controller:
비멱등 (위험):
func reconcileDeployment(d *Deployment) {
// ReplicaSet 생성
rs := &ReplicaSet{...}
createReplicaSet(rs)
}
문제: 매 reconcile마다 새 ReplicaSet 생성.
멱등 (안전):
func reconcileDeployment(d *Deployment) {
// 이 Deployment의 ReplicaSet 조회
existing := findReplicaSet(d)
desired := deriveReplicaSet(d)
if existing == nil {
// 없으면 생성
createReplicaSet(desired)
} else if !equal(existing, desired) {
// 있으면 업데이트
updateReplicaSet(desired)
}
// 같으면 no-op
}
핵심:
- Read first: 현재 상태 확인.
- Compare: 원하는 것과 비교.
- Act only if needed: 필요할 때만 행동.
실전 예시: CRD Operator:
Database operator:
비멱등:
def reconcile(db_cr):
create_statefulset(db_cr)
create_service(db_cr)
create_configmap(db_cr)
매 호출마다 이미 있는 리소스를 또 만들려 함 → 에러.
멱등:
def reconcile(db_cr):
ensure_statefulset(db_cr) # 없으면 생성, 있으면 유지/업데이트
ensure_service(db_cr)
ensure_configmap(db_cr)
def ensure_statefulset(db_cr):
existing = get_statefulset(db_cr.name)
desired = build_statefulset(db_cr)
if existing is None:
create_statefulset(desired)
elif needs_update(existing, desired):
update_statefulset(desired)
# else: do nothing
멱등성 구현 패턴:
1. Owner References:
metadata:
ownerReferences:
- apiVersion: apps/v1
kind: Deployment
name: my-deployment
uid: 12345
"이 ReplicaSet은 Deployment 소유". Controller가 소유한 것만 관리. 중복 생성 방지.
2. Name 기반 조회:
// 이름으로 찾기
rs, err := client.AppsV1().ReplicaSets(ns).Get(ctx, name, getOpts)
if errors.IsNotFound(err) {
// 없음 → 생성
client.AppsV1().ReplicaSets(ns).Create(ctx, rs, createOpts)
} else if err != nil {
return err
} else {
// 있음 → 업데이트 판단
}
3. Labels and Selectors:
// 이 controller가 관리하는 리소스만 조회
list, _ := client.CoreV1().Pods(ns).List(ctx, metav1.ListOptions{
LabelSelector: "app=myapp,owner=my-controller",
})
4. Server-side Apply (Kubernetes 1.22+):
patch, _ := json.Marshal(desiredObject)
client.Patch(ctx, name, types.ApplyPatchType, patch, metav1.PatchOptions{
FieldManager: "my-controller",
Force: true,
})
Server가 원하는 필드만 업데이트. 다른 field manager와 안전 공존.
5. Status Conditions:
status:
conditions:
- type: Ready
status: "True"
lastTransitionTime: "2025-04-15T10:00:00Z"
reason: PodsRunning
message: "All 3 pods running"
Controller가 상태를 명시적으로 표현. Reconciliation이 멱등 판단에 활용.
안티 패턴:
1. 상대 수정:
obj.Spec.Replicas += 1 // BAD
2. 순차 의존:
createA()
createB() // A 실패해도 B 시도
각 단계가 독립적이어야.
3. 상태 비교 없음:
createPod(pod) // 매번 생성 시도
4. 비결정적:
obj.Spec.Name = "pod-" + randomString() // 매번 다름
이름이 변하면 동일 비교 불가.
테스트:
멱등성 테스트:
func TestReconcileIdempotent(t *testing.T) {
controller := NewController(...)
// 1번 호출
state1 := controller.Reconcile(ctx, key)
// 2번 호출
state2 := controller.Reconcile(ctx, key)
assert.Equal(t, state1, state2)
// 부작용 없음 확인
}
Property-based testing:
- 랜덤 입력.
- 멱등성 속성 검증.
- 여러 번 호출 결과 동일.
관점의 전환:
멱등성은 제약이 아니라 자유다.
자유 1: 언제든 재시작 가능. 자유 2: 병렬 실행 가능. 자유 3: 실수 복구 쉬움. 자유 4: 이벤트 순서 무관. 자유 5: 테스트 쉬움.
교훈:
Kubernetes controller를 작성하는 첫 번째 규칙: 멱등으로 만들어라.
이 한 줄이 다음을 보장한다:
- 견고함.
- 확장성.
- 운영 가능성.
- 디버깅 가능성.
Kubernetes 자체가 멱등성 위에 세워졌다. 모든 built-in controller가 멱등이다. 이 덕분에 K8s가 자가 치유하고, 실패를 견디고, 대규모로 운영된다.
새 operator나 controller를 작성한다면, 매 reconcile이 현재 상태를 조회하고 비교한 후에만 행동하도록 하라. "이미 처리했으니 skip" 같은 로컬 상태 금지. 항상 외부 상태만 참조.
이 원칙을 지키면 여러분의 controller는 Kubernetes 생태계와 완벽히 조화를 이룬다. 어기면 끝없는 디버깅이 기다린다.
Q4. Scheduler는 Pod를 Node에 어떻게 배치하는가?
A.
Scheduler의 역할 재확인:
kube-scheduler의 유일한 책임:
- Pending 상태의 Pod에 적절한 Node를 할당.
- 배치 결정만. 실제 실행은 kubelet.
스케줄링이 필요한 이유:
새 Pod를 만들면:
- API Server가 Pod를 etcd에 저장 (status=Pending).
- nodeName이 비어있음 (아직 배치 안 됨).
- Scheduler가 이를 감지.
- 가능한 Node 중 하나를 선택.
- Pod에 nodeName 설정.
- kubelet이 이 Pod가 자기 Node임을 감지.
- 컨테이너 실행.
Scheduling Cycle의 두 단계:
Phase 1: Filtering (Predicate) — "가능한가?"
각 Node에 대해 이진 결정: "이 Node가 Pod를 받을 수 있는가?"
Filter는 여러 플러그인의 조합:
NodeResourcesFit: CPU, memory 충분?
- Pod의
requests합 ≤ Node의allocatable.
PodFitsHost: spec.nodeName 지정되어 있으면 그 Node만.
PodFitsHostPorts: 요청된 hostPort 사용 가능?
MatchNodeSelector: nodeSelector 라벨 매치?
NoVolumeZoneConflict: Volume이 이 Node의 zone에 있나?
CheckNodeDiskPressure: Node가 DiskPressure?
CheckNodeMemoryPressure: Node가 MemoryPressure?
NoDiskConflict: 다른 pod와 volume 충돌?
PodToleratesNodeTaints: Node의 taint에 대한 tolerance?
PodAffinity: Anti-affinity 규칙 만족?
VolumeBinding: PVC 바인딩 가능?
어느 한 filter라도 false면 Node는 제외. 모든 Node가 제외되면 Pod는 Pending 상태 유지, 사용자에게 이유 표시.
Phase 2: Scoring (Priority) — "최선은?"
남은 후보 Node들에 0-100 점수:
NodeResourcesBalancedAllocation: CPU와 memory 사용률 균형.
NodeResourcesLeastAllocated: 리소스 여유 많은 Node 선호.
ImageLocality: 이미 이미지가 있는 Node.
InterPodAffinity: 선호하는 pod와 가까운 Node.
NodeAffinity (soft): 선호 라벨.
TaintToleration: Tolerance 점수.
SelectorSpreadPriority: Service 속 pod를 여러 Node에 분산.
TopologySpreadConstraints: 영역별 분산.
각 플러그인이 0-100 점수 계산. 가중 합해서 최종 점수.
finalScore = w1*plugin1 + w2*plugin2 + ...
최고 점수 Node 선택. 동점이면 랜덤.
예시: 간단한 시나리오
Pod: nginx, CPU 1 core, memory 512Mi 요청.
Nodes:
- Node A: 8 cores, 16Gi total. 여유: 6 cores, 12Gi.
- Node B: 4 cores, 8Gi total. 여유: 1 core, 1Gi (거의 가득).
- Node C: 8 cores, 16Gi total. 여유: 7 cores, 15Gi. 그러나
dedicated=databasetaint.
Filter 단계:
- A: ✓ (리소스 충분).
- B: ✓ (1 core ≥ 1 core, 1Gi ≥ 512Mi). 간신히.
- C: ✗ (taint tolerance 없음).
Score 단계:
- A: LeastAllocated ~75, Balanced ~80 → 78.
- B: LeastAllocated ~20, Balanced ~30 → 25.
선택: Node A (78 > 25).
고급 기능:
1. Pod Affinity:
spec:
affinity:
podAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: cache
topologyKey: kubernetes.io/hostname
"cache pod와 같은 Node에 배치되면 좋음".
2. Pod Anti-Affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: postgres
topologyKey: kubernetes.io/hostname
"같은 Node에 다른 postgres pod 있으면 배치 금지".
HA 용도: 복제본을 다른 Node에.
3. Topology Spread Constraints:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: myapp
"각 zone에 pod 수 차이가 1 이내".
Anti-affinity보다 유연하고 효율적.
4. Taints and Tolerations:
Node taint:
kubectl taint nodes node1 dedicated=gpu:NoSchedule
기본적으로 이 Node에 pod 배치 금지.
Pod tolerance:
spec:
tolerations:
- key: dedicated
operator: Equal
value: gpu
effect: NoSchedule
이 pod는 taint를 견딤 → 배치 가능.
용도: 전용 Node (GPU, 고성능).
Scheduling Framework (v1.19+):
Kubernetes의 modular scheduling:
QueueSort → PreFilter → Filter → PostFilter
→ PreScore → Score → NormalizeScore
→ Reserve → Permit → PreBind → Bind → PostBind
각 단계마다 플러그인을 끼울 수 있다. Custom logic 추가.
예시: 커스텀 플러그인:
type MyScorePlugin struct{}
func (p *MyScorePlugin) Score(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) (int64, *framework.Status) {
// Custom scoring logic
return 50, nil
}
이를 scheduler profile에 등록.
Multiple Schedulers:
한 클러스터에 여러 scheduler:
spec:
schedulerName: my-gpu-scheduler
이 pod는 my-gpu-scheduler만 처리. 다른 scheduler는 건드리지 않음.
용도:
- GPU/ML 전용.
- Gang scheduling (모든 worker 동시 배치).
- Custom ranking.
Scheduling Profiles:
하나의 scheduler 내에서 여러 프로필:
profiles:
- schedulerName: default-scheduler
plugins:
score:
disabled:
- name: NodeResourcesLeastAllocated
- schedulerName: low-priority
plugins:
score:
enabled:
- name: SomeCustomPlugin
같은 scheduler가 다르게 동작. Pod가 어떤 프로필을 쓸지 schedulerName로 선택.
실전 문제와 해결:
1. Pod가 Pending:
Events:
Warning FailedScheduling ... 0/10 nodes are available:
3 Insufficient cpu, 2 Insufficient memory,
5 node(s) didn't match node selector.
진단: 각 failure 이유 확인. 리소스 부족? Node selector 문제?
2. Skew:
Pod가 한 Node에 몰림:
- Topology spread constraints 추가.
- Anti-affinity 설정.
3. 느린 scheduling:
큰 클러스터에서 scheduling이 느리면:
- Affinity/anti-affinity 단순화.
- Filter 플러그인 최적화.
- Scheduler queue 조정.
4. GPU 스케줄링:
GPU는 표준 리소스 아님:
spec:
containers:
- resources:
limits:
nvidia.com/gpu: 1
Device plugin이 GPU를 리소스로 등록.
5. Gang Scheduling:
Spark, MPI 같은 워크로드는 모든 worker가 동시에 시작해야:
- 일부만 배치되면 대기.
- 모두 가능하면 동시 배치.
- Kube-batch, Volcano 같은 custom scheduler.
Scheduling 성능:
단일 scheduler 한계:
- 초당 수백 pod 배치.
- Affinity 복잡하면 느려짐.
- 10,000 노드 이상에서 bottleneck.
최적화:
- Plugin 선택.
- Parallelism (병렬 filter).
- Cache activity.
교훈:
Scheduler는 Kubernetes의 지능이다. 단순해 보이는 "Pod를 Node에 할당" 작업이 수십 개 플러그인, 복잡한 정책, 다양한 제약 조건을 고려한다.
실전 팁:
- 기본 scheduler를 이해하라: 대부분의 워크로드에 충분.
- Affinity는 신중히: 복잡하면 성능 저하.
- Taints + Tolerations으로 분리: 명확한 dedicated 용도.
- Topology spread 활용: HA를 쉽게.
- Custom scheduler는 마지막 수단: 특수한 경우만.
- PodDisruptionBudget: 배치 + disruption 제어.
Scheduler가 좋은 결정을 내리려면:
- 적절한 resource requests: 부정확하면 scheduler 속음.
- 명확한 labels: node selector가 유효.
- 적절한 taints: 전용 리소스 구분.
- Limits 주의: 특히 memory (OOM 위험).
Kubernetes scheduling은 간단한 API 뒤의 복잡한 시스템이다. 이해하면 수천 대의 노드에 수만 pod를 효율적으로 배치할 수 있다. 이것이 Kubernetes의 힘이다.
Q5. CRD와 Operator 패턴이 어떻게 Kubernetes를 "플랫폼의 플랫폼"으로 만드는가?
A.
Kubernetes의 비전:
"Kubernetes는 플랫폼을 만들기 위한 플랫폼이다." — Kelsey Hightower
즉, K8s 자체가 최종 사용자 애플리케이션이 아니라, 다른 플랫폼을 구축하는 기반이다. 이를 가능하게 하는 것이 CRD + Operator 패턴.
CRD (Custom Resource Definition):
Kubernetes API를 확장하는 방법. 새로운 리소스 타입 정의.
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: postgresclusters.db.example.com
spec:
group: db.example.com
versions:
- name: v1
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
replicas:
type: integer
minimum: 1
maximum: 10
version:
type: string
enum: ["13", "14", "15", "16"]
storage:
type: object
properties:
size:
type: string
scope: Namespaced
names:
kind: PostgresCluster
plural: postgresclusters
singular: postgrescluster
shortNames:
- pgc
이제 사용자는:
apiVersion: db.example.com/v1
kind: PostgresCluster
metadata:
name: mydb
spec:
replicas: 3
version: "15"
storage:
size: 100Gi
쓸 수 있다. kubectl get postgresclusters, kubectl describe pgc mydb 등. K8s의 모든 기능 (RBAC, kubectl, API) 이 이 custom resource에 적용.
하지만 CRD 단독은 "껍질":
CRD만 만들면 K8s는 그냥 데이터 저장만 한다. 실제로 Postgres를 설치하지 않는다.
Operator = CRD + Controller:
Operator는 CRD 리소스를 감시하는 controller다:
def reconcile_postgres_cluster(pg_cr):
# 1. 원하는 상태 파악
desired_replicas = pg_cr.spec.replicas
desired_version = pg_cr.spec.version
# 2. 현재 상태 확인
existing_sts = get_statefulset(pg_cr.name)
# 3. 조정
if existing_sts is None:
# 없음: StatefulSet, Service, PVC 등 생성
create_statefulset(pg_cr)
create_service(pg_cr)
create_pvcs(pg_cr)
initialize_postgres(pg_cr)
else:
# 있음: 업데이트 필요?
if existing_sts.spec.replicas != desired_replicas:
update_replicas(existing_sts, desired_replicas)
if existing_sts.version != desired_version:
perform_upgrade(pg_cr, desired_version)
# 4. 백업, 모니터링, 복구 등
ensure_backups(pg_cr)
ensure_monitoring(pg_cr)
"Database as YAML" 의 구현.
실전 예시: 유명한 Operators:
1. Prometheus Operator:
apiVersion: monitoring.coreos.com/v1
kind: Prometheus
metadata:
name: my-prometheus
spec:
replicas: 2
retention: 30d
storage:
volumeClaimTemplate:
spec:
resources:
requests:
storage: 100Gi
serviceMonitorSelector:
matchLabels:
team: platform
이 한 YAML로 완전한 Prometheus 클러스터 생성:
- StatefulSet.
- Service.
- RBAC.
- PVC.
- 설정 자동 생성.
- 모니터링 target 자동 발견.
2. Postgres Operator (Zalando):
apiVersion: acid.zalan.do/v1
kind: postgresql
metadata:
name: mypostgres
spec:
teamId: "team1"
numberOfInstances: 3
postgresql:
version: "15"
volume:
size: 10Gi
users:
myuser:
- superuser
- createdb
databases:
mydb: myuser
자동 수행:
- Master + 2 replica StatefulSet.
- Streaming replication.
- Patroni (failover 관리).
- Backup to S3 (WAL-E/WAL-G).
- User 생성.
- 모니터링.
- 자동 failover.
3. Cert-Manager:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: example-com-tls
spec:
secretName: example-com-tls-secret
dnsNames:
- example.com
- www.example.com
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
Let's Encrypt 인증서를 자동으로:
- 발급.
- DNS/HTTP challenge.
- 갱신 (만료 전).
- Secret에 저장.
4. ArgoCD:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
spec:
source:
repoURL: https://github.com/myorg/myapp
targetRevision: main
path: manifests
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: true
GitOps: Git 변경 → 자동 배포. Git이 진실의 원천.
5. Crossplane:
apiVersion: rds.aws.crossplane.io/v1alpha1
kind: RDSInstance
metadata:
name: my-rds
spec:
forProvider:
region: us-east-1
dbInstanceClass: db.t3.micro
engine: postgres
allocatedStorage: 20
Kubernetes로 AWS RDS 관리. AWS 콘솔, Terraform 없이 kubectl apply로 클라우드 리소스.
"Kubernetes as Control Plane for Everything".
Operator의 성숙도 (Level Model):
Level 1 - Basic Install:
- 설치, 업데이트, 제거.
- "apt install"과 비슷한 수준.
Level 2 - Seamless Upgrades:
- 자동 업그레이드.
- Rolling restart.
- Schema migration.
Level 3 - Full Lifecycle:
- 백업/복구.
- Failover.
- Scaling.
Level 4 - Deep Insights:
- 메트릭, 알림.
- 성능 추적.
- 장애 감지.
Level 5 - Auto Pilot:
- 자동 튜닝.
- 자가 치유.
- 용량 계획.
- 사람 개입 최소화.
Level 5 operator는 "SRE를 코드로" 구현한다.
OperatorHub:
Red Hat과 커뮤니티가 운영하는 Operator 레지스트리:
- https://operatorhub.io
- 수백 개 operator.
- 카테고리별 (Database, Monitoring, Security 등).
- 성숙도 표시.
Operator SDK (개발 도구):
operator-sdk init --domain example.com --repo github.com/example/my-operator
operator-sdk create api --group db --version v1alpha1 --kind Database --resource --controller
# 구현
operator-sdk build my-operator:v0.1.0
operator-sdk bundle create
CRD 설계 원칙:
1. Declarative Spec:
spec:
replicas: 3 # 원하는 상태
version: "15"
명령형 ("replicas를 증가시켜라") 금지. 항상 목표 상태.
2. Status for Observation:
status:
replicas: 3
readyReplicas: 2
conditions:
- type: Ready
status: "False"
reason: "Initializing"
Spec vs Status 분리:
- Spec: 사용자가 쓰는 것.
- Status: Operator가 쓰는 것.
3. Schema Validation:
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
required: ["replicas"]
properties:
replicas:
type: integer
minimum: 1
maximum: 100
잘못된 입력을 API 레벨에서 거부.
4. Versioning:
versions:
- name: v1alpha1
served: true
storage: true
- name: v1beta1
served: true
storage: false
API 진화 지원. 하위 호환성.
5. Subresources:
subresources:
status: {} # kubectl patch status 분리
scale: # kubectl scale 지원
specReplicasPath: .spec.replicas
statusReplicasPath: .status.replicas
Native K8s 기능 확장.
Reconciliation 패턴:
func (r *PostgresClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
// 1. CR 가져오기
pc := &dbv1.PostgresCluster{}
if err := r.Get(ctx, req.NamespacedName, pc); err != nil {
if errors.IsNotFound(err) {
return ctrl.Result{}, nil // 삭제됨
}
return ctrl.Result{}, err
}
// 2. 관련 리소스 확인/생성/업데이트
if err := r.ensureStatefulSet(ctx, pc); err != nil {
return ctrl.Result{}, err
}
if err := r.ensureService(ctx, pc); err != nil {
return ctrl.Result{}, err
}
// ... 더 많은 리소스
// 3. Status 업데이트
pc.Status.Replicas = getCurrentReplicas(pc)
pc.Status.Conditions = computeConditions(pc)
if err := r.Status().Update(ctx, pc); err != nil {
return ctrl.Result{}, err
}
// 4. 주기적 재조정
return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
}
멱등하고, 자가 치유하고, declarative하다.
"Platform of Platforms"의 의미:
Kubernetes가 플랫폼의 플랫폼이라는 말의 뜻:
1. Vocabulary Extension:
- "Pod, Service, Deployment" → built-in.
- "Database, Cluster, Pipeline" → 사용자 정의 via CRD.
- 팀이 자기 도메인 언어로 리소스 표현.
2. Unified Management:
모든 리소스가 같은 방식:
kubectl get/kubectl apply.- RBAC.
- Admission webhooks.
- Audit logs.
- 이벤트.
- GitOps.
새로운 도구를 배울 필요 없음. 이미 Kubernetes 아는 사람이면 사용 가능.
3. Composition:
Operator들을 조합해서 더 큰 플랫폼 구축:
- Prometheus Operator (모니터링)
-
- Cert-Manager (인증서)
-
- Istio Operator (service mesh)
-
- Postgres Operator (DB)
-
- ArgoCD (배포)
- = 완전한 PaaS.
4. Declarative Infrastructure:
인프라가 코드:
# 모든 인프라를 YAML로
- Database (Postgres Operator)
- Cache (Redis Operator)
- Queue (Kafka Operator)
- Monitoring (Prometheus Operator)
- TLS (Cert-Manager)
- Load Balancer (Service type=LoadBalancer)
- ...
Git에 저장. Rollback 가능. Reproducible.
5. Self-Service:
개발자가 직접 리소스 요청:
kind: Database
metadata:
name: my-app-db
spec:
tier: small
운영팀 개입 없이. 승인 흐름이 있다면 admission webhook으로.
실전 사례:
Uber의 내부 플랫폼:
- 수십 개 custom operator.
- 엔지니어가 "Service", "Database", "Pipeline"을 YAML로 선언.
- 백엔드는 Kubernetes.
Airbnb, Lyft, Netflix 등 대부분의 대기업:
- Kubernetes 기반 내부 플랫폼.
- Custom CRD가 자사 워크플로우 반영.
Kubernetes 없었다면:
- 각자 다른 API 설계.
- 다른 인증/인가 시스템.
- 다른 배포 도구.
- 다른 모니터링.
Kubernetes가 공통 기반이 되어 재사용 가능한 플랫폼 컴포넌트가 폭발했다.
CRD/Operator의 한계:
1. 복잡성:
- Operator 작성은 어렵다.
- Corner case 많음.
- 버그 가능성.
2. Operator 품질 편차:
- 좋은 operator: 자동 복구, 백업 등.
- 나쁜 operator: 단순 설치만.
3. Resource 증가:
- 각 CR이 etcd에 저장.
- 너무 많으면 etcd 부담.
4. Dependency hell:
- Operator끼리 의존성.
- 버전 충돌.
5. Learning curve:
- K8s 자체도 복잡.
- CRD + Operator는 더 복잡.
그럼에도 불구하고:
"표준 플랫폼 API"가 있다는 것이 혁명이다. 모든 회사가 자기 플랫폼을 Kubernetes라는 공통 기반 위에 구축한다. 이는 재사용, 이식, 도구 생태계를 가능하게 한다.
교훈:
Kubernetes의 진짜 가치는 "컨테이너 오케스트레이션"이 아니다. 그것은 시작일 뿐이다. 진짜 가치는 "Any infrastructure can be declarative YAML managed by reconciliation loops" 이다.
CRD + Operator 패턴을 이해하면 Kubernetes를 단순한 도구가 아니라 플랫폼 구축 프레임워크로 보게 된다. 그리고 당신의 팀도 자체 operator를 작성해서 회사 고유 플랫폼을 만들 수 있다는 것을 깨닫게 된다.
미래:
Operator 패턴은 계속 진화 중:
- Kubernetes as a Compute Layer: Crossplane 같은 도구.
- Edge Kubernetes: K3s, MicroK8s.
- ML Platforms: Kubeflow operators.
- Security Operators: OPA Gatekeeper, Kyverno.
- AI/LLM operators: 모델 서빙 자동화.
Kubernetes 위에서 무엇이든 선언적으로 관리할 수 있다. 이것이 플랫폼의 플랫폼의 의미다.
당신이 다음에 새 시스템을 설계할 때, 물어보자: "이것을 Kubernetes CRD로 표현할 수 있을까?". 답이 "예"라면, 엄청난 생태계 이점을 얻을 수 있다. RBAC, API 서버, 감사, 모니터링 — 모두 무료로. 이것이 Kubernetes가 단순한 오케스트레이터를 넘어 현대 인프라의 표준이 된 이유다.
마치며: 선언적 시스템의 승리
핵심 정리
- Declarative + Reconciliation: Kubernetes의 본질.
- API Server: 유일한 관문, stateless.
- etcd: 유일한 진실의 원천.
- Scheduler: Filter → Score → Bind.
- Controllers: 수십 개의 reconciliation loop.
- kubelet: Node의 실행자.
- Informer/Workqueue: Controller의 빌딩 블록.
- CRD + Operator: 플랫폼의 플랫폼.
Kubernetes가 가르쳐준 것
Kubernetes는 분산 시스템 설계의 교과서다:
- Level-triggered 우월: Edge-triggered보다 견고.
- Idempotency 필수: 분산에서 생존 조건.
- Watch API의 힘: Polling vs 실시간.
- Declarative + Controllers: 운영 자동화.
- Extensibility first: 표준 API 확장 메커니즘.
- Decoupling via API: 컴포넌트 분리.
이 원칙들은 Kubernetes를 넘어서 모든 분산 시스템 설계에 적용된다.
운영 체크리스트
Production Kubernetes:
- etcd 3/5 노드 HA.
- etcd 백업 자동화.
- API Server 이중화 + LB.
- Monitoring (Prometheus, Grafana).
- Logging (Loki 또는 ELK).
- Resource requests/limits.
- Network policies.
- RBAC 엄격화.
- Pod Security Standards.
- Regular upgrades.
- Disaster recovery plan.
- Chaos engineering 테스트.
마지막 교훈
Kubernetes는 단순히 컨테이너 오케스트레이터가 아니다. 그것은:
- 분산 시스템 교과서: reconciliation, consensus, watch.
- 확장 가능한 플랫폼: CRD로 무엇이든 관리.
- 표준 API: 업계 공통 언어.
- 운영 자동화 프레임워크: Operators.
- 클라우드 네이티브의 기반: Cloud Native의 정의.
이 모든 것이 10년 만에 업계를 바꿨다. 2015년 Kubernetes 1.0부터 2025년까지, 이 시스템은 표준 인프라가 되었다.
당신이 다음에 kubectl apply를 칠 때, 잠시 생각해 보자:
- YAML이 API Server로 간다.
- Admission webhook이 검증한다.
- etcd에 저장된다.
- Watch 이벤트가 controller에 간다.
- Controller가 다른 리소스를 생성한다.
- Scheduler가 Node에 할당한다.
- kubelet이 컨테이너를 실행한다.
- Status가 돌아온다.
이 모든 것이 수 초 내에 일어난다. 그리고 자가 치유한다. 실패에 견딘다. 확장된다.
Kubernetes를 이해하는 것은 현대 인프라를 이해하는 것이다. 단지 "YAML 쓰는 법"이 아니라, 분산 시스템 설계의 철학을 배우는 것이다. 이 지식은 Kubernetes를 넘어 당신의 모든 시스템 설계에 영향을 미칠 것이다.
"Kubernetes is not the destination, it's the journey." — 많은 엔지니어가 하는 말이다. 여정에서 배우는 것이 목적지보다 더 가치 있다.
참고 자료
- Kubernetes Documentation
- Kubernetes: Up and Running (Kelsey Hightower, Brendan Burns, Joe Beda)
- Designing Distributed Systems (Brendan Burns)
- Programming Kubernetes (Stefan Schimanski, Michael Hausenblas)
- Kubernetes Patterns (Bilgin Ibryam, Roland Huss)
- The Kubernetes Book (Nigel Poulton)
- Kubernetes the Hard Way (Kelsey Hightower)
- etcd Documentation
- Operator Pattern (kubernetes.io)
- OperatorHub.io
- CNCF Landscape
- Kubernetes Enhancements Proposals (KEPs)
현재 단락 (1/1857)
kubectl apply -f deployment.yaml